Files
fluxer/fluxer_desktop/src/main/LinuxAppearance.ts
T

121 lines
3.5 KiB
TypeScript

// SPDX-License-Identifier: AGPL-3.0-or-later
import {createRequire} from 'node:module';
import {createChildLogger} from '@electron/common/Logger';
import type {LinuxAppearanceSnapshot} from '@electron/common/Types';
import {getLinuxPortalsMode} from '@electron/main/LaunchOptions';
export type {LinuxAppearanceSnapshot} from '@electron/common/Types';
const logger = createChildLogger('LinuxAppearance');
const requireModule = createRequire(import.meta.url);
interface SettingsChangeEvent {
namespace: string;
key: string;
uint32?: number;
accent?: {r: number; g: number; b: number};
}
interface SettingsCtor {
new (onChange: (event: SettingsChangeEvent) => void): {close: () => void};
}
interface PortalsModule {
readColorScheme: () => 'no-preference' | 'prefer-dark' | 'prefer-light';
readContrast: () => 'no-preference' | 'high';
readAccentColor: () => {r: number; g: number; b: number} | null;
Settings: SettingsCtor | null;
loadError: Error | null;
}
let cached: PortalsModule | null | undefined;
function loadAddon(): PortalsModule | null {
if (cached !== undefined) return cached;
if (process.platform !== 'linux') {
cached = null;
return cached;
}
if (getLinuxPortalsMode(process.argv) === 'off') {
logger.info('@fluxer/linux-portals disabled by launch diagnostics; appearance reads disabled');
cached = null;
return cached;
}
try {
const mod = requireModule('@fluxer/linux-portals') as PortalsModule;
if (mod.loadError) {
logger.info('@fluxer/linux-portals reported load error', {error: mod.loadError});
cached = null;
return cached;
}
cached = mod;
return cached;
} catch (error) {
logger.info('@fluxer/linux-portals not available; appearance reads disabled', {error});
cached = null;
return cached;
}
}
export function readLinuxAppearance(): LinuxAppearanceSnapshot {
const mod = loadAddon();
if (!mod) {
return {colorScheme: 'no-preference', contrast: 'no-preference', accent: null};
}
return {
colorScheme: mod.readColorScheme(),
contrast: mod.readContrast(),
accent: mod.readAccentColor(),
};
}
type LinuxAppearanceListener = (snapshot: LinuxAppearanceSnapshot) => void;
export interface LinuxAppearanceSubscription {
close(): void;
}
export function subscribeLinuxAppearance(listener: LinuxAppearanceListener): LinuxAppearanceSubscription | null {
const mod = loadAddon();
if (!mod?.Settings) return null;
let closed = false;
let instance: {close: () => void} | null = null;
try {
instance = new mod.Settings((event) => {
if (closed) return;
const snapshot: LinuxAppearanceSnapshot = {
colorScheme: mod.readColorScheme(),
contrast: mod.readContrast(),
accent: mod.readAccentColor(),
};
if (event.namespace === 'org.freedesktop.appearance') {
if (event.key === 'color-scheme' && event.uint32 != null) {
snapshot.colorScheme =
event.uint32 === 1 ? 'prefer-dark' : event.uint32 === 2 ? 'prefer-light' : 'no-preference';
} else if (event.key === 'contrast' && event.uint32 != null) {
snapshot.contrast = event.uint32 === 1 ? 'high' : 'no-preference';
} else if (event.key === 'accent-color') {
snapshot.accent =
event.accent && event.accent.r >= 0 && event.accent.g >= 0 && event.accent.b >= 0 ? event.accent : null;
}
}
listener(snapshot);
});
} catch (error) {
logger.warn('linux-portals Settings subscribe failed', {error});
return null;
}
return {
close: () => {
if (closed) return;
closed = true;
try {
instance?.close();
} catch (error) {
logger.warn('linux-portals Settings close threw', {error});
}
},
};
}