From e25c137eeccfba2b714a04f8941fc1d373379ae0 Mon Sep 17 00:00:00 2001 From: 4gray Date: Sun, 12 Apr 2026 18:56:20 +0200 Subject: [PATCH] build: include X11 override in Snap package configuration Entire-Checkpoint: 21b9b9f90f23 --- README.md | 14 + electron-builder.json | 1 + .../verify-electron-package-layout.mjs | 252 +++++++++++++++++- 3 files changed, 264 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b1e67834d..1e0557eda 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,20 @@ Alternatively, you can launch IPTVnator from the terminal with the flag: iptvnator --no-sandbox ``` +### GNU/Linux: Wayland startup failure + +If IPTVnator exits on GNU/Linux with errors about failing to connect to +Wayland or initialize the Ozone platform, force X11/XWayland instead: + +```bash +iptvnator --ozone-platform=x11 +``` + +This workaround is mainly for older or problematic Linux graphics stacks. The +Snap package already includes this X11 override by default. For AppImage, +direct binaries, and other Linux package formats, pass the flag manually when +needed. + ## How to Build and Develop Requirements: diff --git a/electron-builder.json b/electron-builder.json index 18eecec31..e5d7e9344 100644 --- a/electron-builder.json +++ b/electron-builder.json @@ -100,6 +100,7 @@ "confinement": "strict", "grade": "stable", "summary": "IPTV application for M3U playlists, Xtream Codes API, and Stalker portals", + "executableArgs": ["--ozone-platform=x11"], "environment": { "DISABLE_WAYLAND": "1" } diff --git a/tools/packaging/verify-electron-package-layout.mjs b/tools/packaging/verify-electron-package-layout.mjs index 01193fcca..1efb9f24f 100644 --- a/tools/packaging/verify-electron-package-layout.mjs +++ b/tools/packaging/verify-electron-package-layout.mjs @@ -15,7 +15,14 @@ if (!platform) { const workspaceRoot = process.cwd(); const executablesRoot = path.join(workspaceRoot, 'dist', 'executables'); const packageJsonPath = path.join(workspaceRoot, 'package.json'); -const electronBuilderConfigPath = path.join(workspaceRoot, 'electron-builder.json'); +const electronBuilderConfigPath = path.join( + workspaceRoot, + 'electron-builder.json' +); +const builderEffectiveConfigPath = path.join( + executablesRoot, + 'builder-effective-config.yaml' +); const flatpakMetainfoPath = path.join( workspaceRoot, 'apps', @@ -28,6 +35,7 @@ const electronBuilderConfig = JSON.parse( fs.readFileSync(electronBuilderConfigPath, 'utf8') ); const flatpakFinishArgs = electronBuilderConfig.flatpak?.finishArgs ?? []; +const snapConfigInspection = loadSnapConfigInspection(); const workerRelativeDir = path.join( 'dist', 'apps', @@ -49,7 +57,9 @@ const nativeModuleRelativeDirs = [ const linuxExecutableName = getLinuxExecutableName(); function directoryExists(directoryPath) { - return fs.existsSync(directoryPath) && fs.statSync(directoryPath).isDirectory(); + return ( + fs.existsSync(directoryPath) && fs.statSync(directoryPath).isDirectory() + ); } function fileExists(filePath) { @@ -133,6 +143,219 @@ function getLinuxExecutableName() { return packageMetadata.name.toLowerCase(); } +function parseYamlScalar(value) { + const normalizedValue = value.trim(); + + if (normalizedValue === 'true') { + return true; + } + + if (normalizedValue === 'false') { + return false; + } + + if (normalizedValue === 'null') { + return null; + } + + const singleQuotedMatch = normalizedValue.match(/^'(.*)'$/); + if (singleQuotedMatch) { + return singleQuotedMatch[1].replace(/''/g, "'"); + } + + const doubleQuotedMatch = normalizedValue.match(/^"(.*)"$/); + if (doubleQuotedMatch) { + return doubleQuotedMatch[1].replace(/\\"/g, '"').replace(/\\\\/g, '\\'); + } + + return normalizedValue; +} + +function getYamlSectionLines(yamlContent, sectionName) { + const lines = yamlContent.split(/\r?\n/); + const sectionHeader = `${sectionName}:`; + const sectionLines = []; + let isInsideSection = false; + + for (const line of lines) { + if (!isInsideSection) { + if (line === sectionHeader) { + isInsideSection = true; + } + continue; + } + + if (line.trim() === '') { + sectionLines.push(line); + continue; + } + + const indentation = line.match(/^ */)?.[0].length ?? 0; + if (indentation === 0) { + break; + } + + sectionLines.push(line); + } + + return isInsideSection ? sectionLines : null; +} + +function parseIndentedYamlSequence(lines, startIndex, indentation) { + const values = []; + let nextIndex = startIndex; + + while (nextIndex < lines.length) { + const line = lines[nextIndex]; + const trimmedLine = line.trim(); + + if (trimmedLine === '') { + nextIndex += 1; + continue; + } + + const currentIndentation = line.match(/^ */)?.[0].length ?? 0; + if (currentIndentation < indentation) { + break; + } + + if (currentIndentation > indentation || !trimmedLine.startsWith('- ')) { + break; + } + + values.push(parseYamlScalar(trimmedLine.slice(2))); + nextIndex += 1; + } + + return { + values, + nextIndex, + }; +} + +function parseIndentedYamlMapping(lines, startIndex, indentation) { + const values = {}; + let nextIndex = startIndex; + + while (nextIndex < lines.length) { + const line = lines[nextIndex]; + const trimmedLine = line.trim(); + + if (trimmedLine === '') { + nextIndex += 1; + continue; + } + + const currentIndentation = line.match(/^ */)?.[0].length ?? 0; + if (currentIndentation < indentation) { + break; + } + + if (currentIndentation > indentation) { + break; + } + + const separatorIndex = trimmedLine.indexOf(':'); + if (separatorIndex === -1) { + break; + } + + const key = trimmedLine.slice(0, separatorIndex).trim(); + const rawValue = trimmedLine.slice(separatorIndex + 1).trim(); + values[key] = parseYamlScalar(rawValue); + nextIndex += 1; + } + + return { + values, + nextIndex, + }; +} + +function parseEffectiveSnapConfig(yamlContent) { + const sectionLines = getYamlSectionLines(yamlContent, 'snap'); + + if (!sectionLines) { + return null; + } + + const snapConfig = {}; + + for (let index = 0; index < sectionLines.length; index += 1) { + const line = sectionLines[index]; + const trimmedLine = line.trim(); + + if (trimmedLine === '') { + continue; + } + + const indentation = line.match(/^ */)?.[0].length ?? 0; + if (indentation !== 2) { + continue; + } + + const separatorIndex = trimmedLine.indexOf(':'); + if (separatorIndex === -1) { + continue; + } + + const key = trimmedLine.slice(0, separatorIndex).trim(); + const rawValue = trimmedLine.slice(separatorIndex + 1).trim(); + + if (rawValue !== '') { + snapConfig[key] = parseYamlScalar(rawValue); + continue; + } + + if (key === 'executableArgs') { + const { values, nextIndex } = parseIndentedYamlSequence( + sectionLines, + index + 1, + 4 + ); + snapConfig[key] = values; + index = nextIndex - 1; + continue; + } + + if (key === 'environment') { + const { values, nextIndex } = parseIndentedYamlMapping( + sectionLines, + index + 1, + 4 + ); + snapConfig[key] = values; + index = nextIndex - 1; + } + } + + return snapConfig; +} + +function loadSnapConfigInspection() { + if (fileExists(builderEffectiveConfigPath)) { + const effectiveConfigContent = fs.readFileSync( + builderEffectiveConfigPath, + 'utf8' + ); + const effectiveSnapConfig = parseEffectiveSnapConfig( + effectiveConfigContent + ); + + if (effectiveSnapConfig) { + return { + config: effectiveSnapConfig, + sourcePath: builderEffectiveConfigPath, + }; + } + } + + return { + config: electronBuilderConfig.snap ?? {}, + sourcePath: electronBuilderConfigPath, + }; +} + function verifyLinuxLauncher(resourceDir, errors) { const appDir = path.dirname(resourceDir); const launcherPath = path.join(appDir, linuxExecutableName); @@ -186,6 +409,26 @@ function verifyFlatpakPermissions(errors) { } } +function verifySnapPackagingConfig(errors) { + const snapExecutableArgs = Array.isArray( + snapConfigInspection.config?.executableArgs + ) + ? snapConfigInspection.config.executableArgs + : []; + + if (!snapExecutableArgs.includes('--ozone-platform=x11')) { + errors.push( + `Snap config in ${snapConfigInspection.sourcePath} must include --ozone-platform=x11 in executableArgs.` + ); + } + + if (snapConfigInspection.config?.allowNativeWayland === true) { + errors.push( + `Snap config in ${snapConfigInspection.sourcePath} must not enable allowNativeWayland while the X11 startup workaround is required.` + ); + } +} + function verifyResourceDir(resourceDir) { const missingWorkers = workerFiles.filter( (workerFile) => @@ -219,9 +462,12 @@ function verifyResourceDir(resourceDir) { if (platform === 'linux') { if (!fileExists(flatpakMetainfoPath)) { - errors.push(`Missing Flatpak metainfo file: ${flatpakMetainfoPath}`); + errors.push( + `Missing Flatpak metainfo file: ${flatpakMetainfoPath}` + ); } verifyFlatpakPermissions(errors); + verifySnapPackagingConfig(errors); verifyLinuxLauncher(resourceDir, errors); }