build: include X11 override in Snap package configuration

Entire-Checkpoint: 21b9b9f90f23
This commit is contained in:
4gray committed 2026-04-12 18:56:20 +02:00
1 parent 35141bec31
commit e25c137eec
3 files changed
+264 -3

No files matched your search

+14
View File
@@ -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:
+1
View File
@@ -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"
}
@@ -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);
}