refactor: remove dead code (#3337)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-09 01:41:26 +02:00
1 parent a19002652b
commit 93d1f8af46
1527 files changed
+21170 -73846

No files matched your search

+1 -1
View File
@@ -32,7 +32,7 @@ interface DesktopConfig extends Record<string, unknown> {
export type GlobalShortcutsPortalConsent = 'unset' | 'granted' | 'declined';
export interface PersistedGlobalShortcutAction {
interface PersistedGlobalShortcutAction {
action: GlobalShortcutAction;
description: string;
preferredTrigger: string | null;
-20
View File
@@ -321,11 +321,6 @@ export interface VirtmicLinkOptions {
workaround?: boolean;
}
export interface VirtmicSystemLinkOptions extends VirtmicLinkOptions {
onlySpeakers?: boolean;
onlyDefaultSpeakers?: boolean;
}
export interface VirtmicApi {
getAvailability: () => Promise<VirtmicAvailability>;
listTargets: (options?: {granular?: boolean}) => Promise<{
@@ -334,9 +329,6 @@ export interface VirtmicApi {
availability: VirtmicAvailability;
}>;
getRoutingGraph: () => Promise<VirtmicRoutingGraphResult>;
startInclude: (include: Array<VirtmicNode>, options?: VirtmicLinkOptions) => Promise<boolean>;
startSystem: (exclude: Array<VirtmicNode>, options?: VirtmicSystemLinkOptions) => Promise<boolean>;
resolveWindowPid: (sourceId: string) => Promise<number | null>;
stop: () => Promise<void>;
}
@@ -655,9 +647,6 @@ export interface ElectronAPI {
setDesktopDisableHardwareAcceleration: (
options: SetDesktopTroubleshootingDisableHardwareAccelerationOptions,
) => Promise<DesktopTroubleshootingSettings>;
desktopTroubleshootingReload: () => Promise<void>;
desktopTroubleshootingResetAppData: (options?: {confirm?: boolean}) => Promise<void>;
popupHelpMenu: () => Promise<void>;
onUpdaterEvent: (callback: (event: UpdaterEvent) => void) => () => void;
updaterCheck: (context: UpdaterContext) => Promise<void>;
windowMinimize: () => void;
@@ -694,7 +683,6 @@ export interface ElectronAPI {
getScreenRecordingPermissionStatus: () => Promise<InputMonitoringPermissionStatus>;
requestScreenRecordingPermission: () => Promise<InputMonitoringPermissionStatus>;
downloadFile: (url: string, defaultPath: string, sha256?: string | null) => Promise<DownloadFileResult>;
toggleDevTools: () => void;
showNotification: (options: NotificationOptions) => Promise<NotificationResult>;
shouldPlayNotificationSound: () => Promise<boolean>;
getStreamerModeCaptureAppStatus: () => Promise<StreamerModeCaptureAppStatus>;
@@ -702,16 +690,12 @@ export interface ElectronAPI {
closeNotifications: (ids: Array<string>) => void;
onNotificationClick: (callback: (id: string, url?: string) => void) => () => void;
setBadgeCount: (count: number) => void;
getBadgeCount: () => Promise<number>;
bounceDock: (type?: 'critical' | 'informational') => number;
cancelBounceDock: (id: number) => void;
setNativeLocale: (locale: string, strings: Record<string, string>) => void;
flashFrame: (persistent?: boolean) => void;
stopFlashFrame: () => void;
setTaskbarProgress: (fraction: number, mode?: 'normal' | 'indeterminate' | 'error' | 'paused' | 'none') => void;
onJumpListNewDm: (callback: () => void) => () => void;
setZoomFactor: (factor: number) => void;
getZoomFactor: () => Promise<number>;
getAccessibilitySupportEnabled: () => Promise<boolean>;
onAccessibilitySupportChanged: (callback: (enabled: boolean) => void) => () => void;
getLinuxAppearance: () => Promise<LinuxAppearanceSnapshot>;
@@ -723,12 +707,10 @@ export interface ElectronAPI {
setTrayRuntimeState: (state: Partial<TrayRuntimeStatePayload>) => void;
acquireStreamingPriority: () => void;
releaseStreamingPriority: () => void;
resetStreamingPriority: () => void;
getStreamingPriorityDiagnostics: () => Promise<StreamingPriorityDiagnostics>;
onTrayAction: (callback: (action: TrayActionPayload) => void) => () => void;
globalKeyHookStart: () => Promise<boolean>;
globalKeyHookStop: () => Promise<void>;
globalKeyHookIsRunning: () => Promise<boolean>;
checkInputMonitoringAccess: () => Promise<boolean>;
globalKeyHookRegister: (options: GlobalKeyHookRegisterOptions) => Promise<void>;
globalKeyHookUnregister: (id: string) => Promise<void>;
@@ -752,11 +734,9 @@ export interface ElectronAPI {
onGlobalMouseEvent: (callback: (event: GlobalMouseEvent) => void) => () => void;
onGlobalKeybindTriggered: (callback: (event: GlobalKeybindTriggeredEvent) => void) => () => void;
globalShortcuts: GlobalShortcutsApi;
spellcheckGetState: () => Promise<SpellcheckState>;
spellcheckSetState: (state: Partial<SpellcheckState>) => Promise<SpellcheckState>;
spellcheckGetAvailableLanguages: () => Promise<Array<string>>;
spellcheckGetBundledDictionaries: () => Promise<Array<SpellcheckBundledDictionary>>;
spellcheckSuggest: (word: string) => Promise<Array<string>>;
onSpellcheckStateChanged: (callback: (state: SpellcheckState) => void) => () => void;
onSpellcheckEngineResolved: (callback: (info: SpellcheckResolvedEngineInfo) => void) => () => void;
onTextareaContextMenu: (callback: (params: TextareaContextMenuParams) => void) => () => void;
-6
View File
@@ -5,7 +5,6 @@ import {
isValidDesktopSourceId,
isValidDisplayMediaRequestId,
normalizeDesktopSourceTypes,
shouldHonorSelectedAudio,
} from '@electron/main/DisplayMediaValidation';
import {isWaylandSession} from '@electron/main/LinuxSession';
import {startWindowsScreenCaptureGuardForSource} from '@electron/main/WindowsScreenCaptureGuard';
@@ -113,10 +112,6 @@ function collectOwnWindowMediaSourceIds(): Set<string> {
return ids;
}
function _isOwnWindowSourceId(sourceId: string): boolean {
return collectOwnWindowMediaSourceIds().has(sourceId);
}
function consumeWaylandPortalSurfacePreference(): DisplayMediaPortalSurfacePreference | null {
const preference = nextWaylandPortalSurfacePreference;
nextWaylandPortalSurfacePreference = null;
@@ -453,7 +448,6 @@ export function registerDisplayMediaHandlers(): void {
withAudio: withAudio === true,
});
startWindowsScreenCaptureGuardForSource(selectedSource, event.sender);
const _attachAudio = shouldHonorSelectedAudio(pending.audioRequested, withAudio);
const streams: Electron.Streams = {
video: selectedSource,
};
@@ -31,10 +31,6 @@ export function isValidDesktopSourceId(value: unknown): value is string {
return value.length > 'screen:'.length && (value.startsWith('screen:') || value.startsWith('window:'));
}
export function shouldHonorSelectedAudio(audioRequested: boolean, selectedWithAudio: unknown): boolean {
return audioRequested && selectedWithAudio === true;
}
export function isListOnlyDesktopSourcesOption(value: unknown): boolean {
if (typeof value !== 'object' || value === null) return false;
return (value as {listOnly?: unknown}).listOnly === true;
@@ -10,14 +10,14 @@ import {
usLayoutKeyToDomCode,
} from '@electron/main/GlobalShortcutKeys';
export interface ShortcutModifiers {
interface ShortcutModifiers {
ctrl: boolean;
alt: boolean;
shift: boolean;
meta: boolean;
}
export type ShortcutTrigger =
type ShortcutTrigger =
| {kind: 'key'; layoutKey: string | null; code: string | null}
| {kind: 'mouse'; button: number}
| {kind: 'modifier-only'; modifiers: Array<ModifierKind>; bothSides: boolean};
@@ -1405,7 +1405,6 @@ describe('LegacyGlobalKeyHookAdapter', () => {
await settle();
const invoke = registerLegacy(harness);
assert.equal(await invoke('global-key-hook-start'), false);
assert.equal(await invoke('global-key-hook-is-running'), false);
await invoke('global-key-hook-register', {id: 'key:voice_toggle_mute::F9:F9', keyName: 'F9'});
assert.equal(harness.portals.length, 0);
assert.deepEqual(harness.started, []);
@@ -29,9 +29,9 @@ import type {LinuxPortalShortcutsManager, PortalShortcutDefinition} from '@elect
export type HookBackendKind = 'x11' | 'evdev' | 'windows' | 'macos';
export type HookStartResult = 'ok' | 'permission' | 'start-failed';
export type InputHookMode = 'auto' | 'off' | 'evdev' | 'native';
type InputHookMode = 'auto' | 'off' | 'evdev' | 'native';
export interface NativeKeyIdentity {
interface NativeKeyIdentity {
keycode: number;
keyName: string;
}
@@ -92,13 +92,13 @@ export interface GlobalShortcutsEngineDeps {
log: (message: string, details?: Record<string, unknown>) => void;
}
export interface SanitizedSyncBinding {
interface SanitizedSyncBinding {
sourceId: string;
action: string;
combo: GlobalShortcutCombo;
}
export interface SanitizedSyncAction {
interface SanitizedSyncAction {
action: GlobalShortcutAction;
description: string;
preferredCombo: GlobalShortcutCombo | null;
-73
View File
@@ -60,15 +60,11 @@ import {
acquireStreamingPriority,
getStreamingPriorityDiagnostics,
releaseStreamingPriority,
resetStreamingPriority,
} from '@electron/main/StreamingPriority';
import {setTaskbarProgress, type TaskbarProgressMode} from '@electron/main/TaskbarProgress';
import {registerThemeLocalFileHandlers} from '@electron/main/ThemeLocalFiles';
import {
popupHelpMenu,
relaunchAndExit,
reloadMainWindow,
resetAppDataAndRestart,
setHardwareAccelerationDisabled,
setHardwareAccelerationDisabledAndRestart,
} from '@electron/main/Troubleshooting';
@@ -93,11 +89,9 @@ import {
setVoicePopoutAlwaysOnTop,
showWindow,
THEME_STUDIO_POPOUT_KEY,
toggleWindowDevTools,
} from '@electron/main/Window';
import {flashWindowForAttention, stopFlashingWindow} from '@electron/main/WindowFlash';
import {setWindowsBadgeOverlay} from '@electron/main/WindowsBadge';
import {registerWindowsToastIpcHandlers} from '@electron/main/WindowsToast';
import {
DESKTOP_MODULE_CHANNELS,
DESKTOP_UPDATE_CHANNELS,
@@ -234,32 +228,12 @@ export function registerIpcHandlers(): void {
return getLaunchDesktopTroubleshootingSettings();
},
);
ipcMain.handle('desktop-troubleshooting-reload', (): void => {
reloadMainWindow();
});
ipcMain.handle(
'desktop-troubleshooting-reset-app-data',
async (
_event,
options?: {
confirm?: boolean;
},
): Promise<void> => {
await resetAppDataAndRestart(options);
},
);
ipcMain.handle('desktop-troubleshooting-popup-help-menu', (event): void => {
popupHelpMenu(BrowserWindow.fromWebContents(event.sender));
});
ipcMain.on('streaming-priority-acquire', (event) => {
acquireStreamingPriority(event.sender);
});
ipcMain.on('streaming-priority-release', () => {
releaseStreamingPriority();
});
ipcMain.on('streaming-priority-reset', () => {
resetStreamingPriority();
});
ipcMain.handle('streaming-priority-get-diagnostics', () => getStreamingPriorityDiagnostics());
ipcMain.on('tray-runtime-state-update', (event, state: unknown) => {
if (!state || typeof state !== 'object') return;
@@ -417,26 +391,6 @@ export function registerIpcHandlers(): void {
requirePrivilegedRendererDocumentSender(event, 'clipboard-paste');
event.sender.paste();
});
ipcMain.handle(
'app-set-badge',
(
_event,
payload: {
count: number;
text?: string;
},
) => {
const count = Math.max(0, Math.floor(payload?.count ?? 0));
const label = payload?.text ?? String(count);
app.setBadgeCount(count);
if (process.platform === 'darwin' && app.dock) {
app.dock.setBadge(count > 0 ? label : '');
}
if (process.platform === 'win32') {
setWindowsBadgeOverlay(getMainWindow(), count);
}
},
);
ipcMain.handle(
'download-file',
async (
@@ -469,12 +423,6 @@ export function registerIpcHandlers(): void {
}
},
);
ipcMain.on('toggle-devtools', (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (win) {
toggleWindowDevTools(win);
}
});
ipcMain.handle('check-media-access', async (_event, type: MediaAccessType): Promise<string> => {
if (process.platform !== 'darwin') {
return 'granted';
@@ -518,7 +466,6 @@ export function registerIpcHandlers(): void {
await shell.openExternal('x-apple.systempreferences:com.apple.preference.security?Privacy_ListenEvent');
});
registerNotificationIpcHandlers(getMainWindow);
registerWindowsToastIpcHandlers();
registerMacTccIpcHandlers({
onStatus: (surface, status) => {
if (surface === 'input-monitoring' && status === 'granted') retryBlockedGlobalShortcutHooks();
@@ -533,32 +480,12 @@ export function registerIpcHandlers(): void {
app.setBadgeCount(count);
}
});
ipcMain.handle('get-badge-count', (): number => {
return app.getBadgeCount();
});
ipcMain.on('bounce-dock', (event, type: 'critical' | 'informational') => {
if (process.platform === 'darwin' && app.dock) {
const id = app.dock.bounce(type);
event.returnValue = id;
} else {
event.returnValue = -1;
}
});
ipcMain.on('cancel-bounce-dock', (_event, id: number) => {
if (process.platform === 'darwin' && app.dock && id >= 0) {
app.dock.cancelBounce(id);
}
});
ipcMain.on('set-zoom-factor', (event, factor: number) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (win && factor > 0) {
win.webContents.setZoomFactor(factor);
}
});
ipcMain.handle('get-zoom-factor', (event): number => {
const win = BrowserWindow.fromWebContents(event.sender);
return win?.webContents.getZoomFactor() ?? 1;
});
ipcMain.handle('get-accessibility-support-enabled', (): boolean => app.accessibilitySupportEnabled);
app.on('accessibility-support-changed', (_event, accessibilitySupportEnabled) => {
const mainWindow = getMainWindow();
+1 -1
View File
@@ -143,7 +143,7 @@ export function getNativeGatewayDisableReason(argv: ReadonlyArray<string> = proc
return null;
}
export function isPortableLaunchFlag(argv: ReadonlyArray<string> = process.argv): boolean {
function isPortableLaunchFlag(argv: ReadonlyArray<string> = process.argv): boolean {
return hasFlag(argv, PORTABLE_MODE_ARGS);
}
@@ -16,7 +16,7 @@ const MAX_LEGACY_ID_LENGTH = 512;
const MAX_LEGACY_KEY_NAME_LENGTH = 64;
const MAX_LEGACY_MOUSE_BUTTON = 31;
export interface LegacyLinuxEvdevStatus {
interface LegacyLinuxEvdevStatus {
supported: boolean;
hasAccess: boolean;
canPrompt: boolean;
@@ -27,7 +27,7 @@ export interface LegacyLinuxEvdevStatus {
inInputGroup: boolean;
}
export interface LegacyLinuxEvdevGrantResult {
interface LegacyLinuxEvdevGrantResult {
success: boolean;
needsRelogin: boolean;
error?: string;
@@ -237,10 +237,6 @@ export function registerLegacyGlobalKeyHookHandlers(
adapter.release(event.sender.id);
await engine.refreshHooks();
});
ipcMain.handle('global-key-hook-is-running', (event): boolean => {
requirePrivilegedRendererDocumentSender(event, 'global-key-hook-is-running');
return engine.hooksActive();
});
ipcMain.handle('check-input-monitoring-access', (event): boolean => {
requirePrivilegedRendererDocumentSender(event, 'check-input-monitoring-access');
return host.hasInputMonitoringAccess();
+3 -186
View File
@@ -4,13 +4,12 @@ import {createRequire} from 'node:module';
import {createChildLogger} from '@electron/common/Logger';
import type {
VirtmicAvailability,
VirtmicLinkOptions,
VirtmicNode,
VirtmicRoutingGraph,
VirtmicRoutingGraphResult,
VirtmicUnavailableReason,
} from '@electron/common/Types';
import {buildFluxerAudioExcludePatterns, isFluxerAudioNode} from '@electron/main/FluxerAudioIdentity';
import {isFluxerAudioNode} from '@electron/main/FluxerAudioIdentity';
import {getLinuxPortalsMode, getNativeAudioMode} from '@electron/main/LaunchOptions';
import {
isDBusObjectPathSegment,
@@ -18,29 +17,14 @@ import {
parseWindowSourceToken,
} from '@electron/main/LinuxAudioCaptureHelpers';
import {isWaylandSession, isX11Session} from '@electron/main/LinuxSession';
import {
isValidVirtmicLinkOptions,
isValidVirtmicNodeList,
isValidVirtmicSystemLinkOptions,
} from '@electron/main/NativeAudioValidation';
import {app, ipcMain} from 'electron';
import {ipcMain} from 'electron';
const logger = createChildLogger('LinuxAudioCapture');
const requireModule = createRequire(import.meta.url);
interface RoutingRule {
include?: ReadonlyArray<VirtmicNode>;
exclude?: ReadonlyArray<VirtmicNode>;
ignore_devices?: boolean;
only_speakers?: boolean;
only_default_speakers?: boolean;
workaround?: ReadonlyArray<VirtmicNode>;
}
interface AudioBridgeInstance {
inventory: (fields?: ReadonlyArray<string> | null) => Array<VirtmicNode>;
routingGraph?: () => VirtmicRoutingGraph;
apply: (rule: RoutingRule) => boolean;
release: () => void;
backend?: () => 'pipewire' | 'none';
}
@@ -62,9 +46,6 @@ interface LoadResult {
let cachedLoad: LoadResult | undefined;
let instance: AudioBridgeInstance | undefined;
let linkActive = false;
let lastRule: RoutingRule | undefined;
let relinkTimers: Array<NodeJS.Timeout> = [];
const LINUX_AUDIO_TARGET_INVENTORY_FIELDS = [
'media.class',
@@ -83,47 +64,6 @@ const LINUX_AUDIO_TARGET_INVENTORY_FIELDS = [
'object.serial',
] as const;
function clearRelinkTimers(): void {
for (const timer of relinkTimers) clearTimeout(timer);
relinkTimers = [];
}
function scheduleAudioServiceRelink(): void {
clearRelinkTimers();
if (!lastRule) return;
for (const delay of [250, 1500, 4000]) {
relinkTimers.push(
setTimeout(() => {
if (!linkActive || !lastRule) return;
try {
const refreshedRule = refreshDynamicRoutingRule(lastRule);
if (instance?.apply(refreshedRule)) lastRule = refreshedRule;
} catch (error) {
logger.debug('delayed audio-service re-link failed', error);
}
}, delay),
);
}
}
function mergeFreshExclusions(data: RoutingRule): Array<VirtmicNode> {
const merged = new Map<string, VirtmicNode>();
const append = (node: VirtmicNode): void => {
const key = JSON.stringify(node);
if (!merged.has(key)) merged.set(key, node);
};
for (const entry of data.exclude ?? []) append(entry);
for (const pattern of buildFluxerAudioExcludePatterns()) append(pattern);
return Array.from(merged.values());
}
function refreshDynamicRoutingRule(data: RoutingRule): RoutingRule {
const refreshed: RoutingRule = {...data, exclude: mergeFreshExclusions(data)};
const workaround = buildRecordStreamPinTarget();
if (workaround) refreshed.workaround = workaround;
return refreshed;
}
function unavailable(reason: VirtmicUnavailableReason): LoadResult {
return {availability: {available: false, reason}};
}
@@ -195,42 +135,6 @@ function getInstance(): AudioBridgeInstance | undefined {
return instance;
}
function getRendererAudioServicePid(): string | undefined {
try {
const metrics = app.getAppMetrics();
const audioService = metrics.find(
(proc) =>
(
proc as {
serviceName?: string;
}
).serviceName === 'audio.mojom.AudioService' || proc.name === 'Audio Service',
);
return audioService?.pid?.toString();
} catch {
return undefined;
}
}
function buildExclusions(
options: VirtmicLinkOptions,
extraExcludes: ReadonlyArray<VirtmicNode> = [],
): Array<VirtmicNode> {
const ignoreInputMedia = options.ignoreInputMedia ?? true;
const excludes: Array<VirtmicNode> = [];
excludes.push(...buildFluxerAudioExcludePatterns());
for (const entry of extraExcludes) excludes.push(entry);
if (ignoreInputMedia) excludes.push({'media.class': 'Stream/Input/Audio'});
if (options.ignoreVirtual) excludes.push({'node.virtual': 'true'});
return excludes;
}
function buildRecordStreamPinTarget(): Array<VirtmicNode> | undefined {
const audioPid = getRendererAudioServicePid();
if (!audioPid) return undefined;
return [{'application.process.id': audioPid, 'media.name': 'RecordStream'}];
}
function getVirtmicAvailability(): VirtmicAvailability {
return loadAddon().availability;
}
@@ -272,66 +176,6 @@ function getVirtmicRoutingGraph(): VirtmicRoutingGraphResult {
}
}
function startVirtmicInclude(include: unknown, options: unknown = {}): boolean {
if (!isValidVirtmicNodeList(include) || !isValidVirtmicLinkOptions(options)) {
return false;
}
const bay = getInstance();
if (!bay) {
return false;
}
const data: RoutingRule = {
include,
exclude: buildExclusions(options),
ignore_devices: options.ignoreDevices ?? true,
};
const workaround = buildRecordStreamPinTarget();
if (workaround) data.workaround = workaround;
try {
const ok = bay.apply(data);
if (ok) {
linkActive = true;
lastRule = data;
scheduleAudioServiceRelink();
}
return ok;
} catch (error) {
logger.warn('AudioBridge.apply(include) threw', error);
return false;
}
}
function startVirtmicSystem(exclude: unknown, options: unknown = {}): boolean {
if (!isValidVirtmicNodeList(exclude) || !isValidVirtmicSystemLinkOptions(options)) {
return false;
}
const bay = getInstance();
if (!bay) {
return false;
}
const data: RoutingRule = {
include: options.onlySpeakers === false ? [{'media.class': 'Stream/Output/Audio'}] : [],
exclude: buildExclusions(options, exclude),
ignore_devices: options.ignoreDevices ?? true,
only_speakers: options.onlySpeakers ?? true,
only_default_speakers: options.onlyDefaultSpeakers ?? true,
};
const workaround = buildRecordStreamPinTarget();
if (workaround) data.workaround = workaround;
try {
const ok = bay.apply(data);
if (ok) {
linkActive = true;
lastRule = data;
scheduleAudioServiceRelink();
}
return ok;
} catch (error) {
logger.warn('AudioBridge.apply(system) threw', error);
return false;
}
}
async function resolveWindowPidViaX11(xid: string): Promise<number | null> {
if (getLinuxPortalsMode(process.argv) === 'off') return null;
const portals = requireModule('@fluxer/linux-portals') as {
@@ -409,19 +253,6 @@ export async function resolveVirtmicWindowPid(sourceId: unknown): Promise<number
return null;
}
function stopVirtmic(): void {
clearRelinkTimers();
lastRule = undefined;
if (!instance || !linkActive) return;
try {
instance.release();
} catch (error) {
logger.warn('AudioBridge.release() threw', error);
} finally {
linkActive = false;
}
}
let handlersRegistered = false;
export function registerVirtmicHandlers(): void {
@@ -438,28 +269,14 @@ export function registerVirtmicHandlers(): void {
) => listVirtmicTargets(options),
);
ipcMain.handle('virtmic:get-routing-graph', (): VirtmicRoutingGraphResult => getVirtmicRoutingGraph());
ipcMain.handle('virtmic:start-include', (_event, include: unknown, options?: unknown): boolean =>
startVirtmicInclude(include, options ?? {}),
);
ipcMain.handle('virtmic:start-system', (_event, exclude: unknown, options?: unknown): boolean =>
startVirtmicSystem(exclude, options ?? {}),
);
ipcMain.handle(
'virtmic:resolve-window-pid',
(_event, sourceId: unknown): Promise<number | null> => resolveVirtmicWindowPid(sourceId),
);
ipcMain.handle('virtmic:stop', (): void => stopVirtmic());
ipcMain.handle('virtmic:stop', (): void => {});
}
export function cleanupVirtmic(): void {
stopVirtmic();
if (!handlersRegistered) return;
ipcMain.removeHandler('virtmic:get-availability');
ipcMain.removeHandler('virtmic:list');
ipcMain.removeHandler('virtmic:get-routing-graph');
ipcMain.removeHandler('virtmic:start-include');
ipcMain.removeHandler('virtmic:start-system');
ipcMain.removeHandler('virtmic:resolve-window-pid');
ipcMain.removeHandler('virtmic:stop');
handlersRegistered = false;
}
@@ -18,8 +18,3 @@ export function isX11WindowToken(token: string): boolean {
export function isDBusObjectPathSegment(token: string): boolean {
return /^[A-Za-z0-9_]+$/.test(token);
}
function _gnomeShellPidExpression(token: string): string {
const tokenLiteral = JSON.stringify(token);
return `global.get_window_actors().map(a=>a.meta_window).filter(w=>w.get_id&&w.get_id().toString()===${tokenLiteral}).map(w=>w.get_pid())[0]`;
}
@@ -36,7 +36,7 @@ export interface PortalClientOptions {
desktop: LinuxDesktopKind;
}
export interface PortalOpenResult {
interface PortalOpenResult {
version: number;
appIdSource: 'sandbox' | 'registered' | 'unregistered';
uniqueName: string;
@@ -1,11 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {
NativeAudioStartOptions,
VirtmicLinkOptions,
VirtmicNode,
VirtmicSystemLinkOptions,
} from '@electron/common/Types';
import type {NativeAudioStartOptions, VirtmicNode} from '@electron/common/Types';
interface InternalAudioFrame {
samples: Float32Array;
@@ -51,7 +46,7 @@ function isValidVirtmicNodePattern(value: unknown): value is VirtmicNode {
);
}
export function isValidVirtmicNodeList(value: unknown): value is Array<VirtmicNode> {
function isValidVirtmicNodeList(value: unknown): value is Array<VirtmicNode> {
return Array.isArray(value) && value.length <= MAX_LINUX_RULE_PATTERNS && value.every(isValidVirtmicNodePattern);
}
@@ -59,41 +54,6 @@ function isValidOptionalVirtmicNodeList(value: unknown): value is Array<VirtmicN
return value === undefined || isValidVirtmicNodeList(value);
}
export function isValidVirtmicLinkOptions(options: unknown): options is VirtmicLinkOptions {
if (!isPlainObject(options)) return false;
const allowedKeys = new Set(['ignoreDevices', 'ignoreInputMedia', 'ignoreVirtual', 'workaround']);
if (!Object.keys(options).every((key) => allowedKeys.has(key))) return false;
return (
isBooleanOrUndefined(options.ignoreDevices) &&
isBooleanOrUndefined(options.ignoreInputMedia) &&
isBooleanOrUndefined(options.ignoreVirtual) &&
isBooleanOrUndefined(options.workaround)
);
}
export function isValidVirtmicSystemLinkOptions(options: unknown): options is VirtmicSystemLinkOptions {
if (!isPlainObject(options)) return false;
const allowedKeys = new Set([
'ignoreDevices',
'ignoreInputMedia',
'ignoreVirtual',
'workaround',
'onlySpeakers',
'onlyDefaultSpeakers',
]);
if (!Object.keys(options).every((key) => allowedKeys.has(key))) return false;
return (
isValidVirtmicLinkOptions({
ignoreDevices: options.ignoreDevices,
ignoreInputMedia: options.ignoreInputMedia,
ignoreVirtual: options.ignoreVirtual,
workaround: options.workaround,
}) &&
isBooleanOrUndefined(options.onlySpeakers) &&
isBooleanOrUndefined(options.onlyDefaultSpeakers)
);
}
export function isValidLinuxRule(
rule: NativeAudioStartOptions['linuxRule'],
): rule is NonNullable<NativeAudioStartOptions['linuxRule']> {
+2 -2
View File
@@ -18,7 +18,7 @@ const COMMAND_TIMEOUT_MS = 1_000;
const MACOS_FOCUS_DB_DIR = path.join(os.homedir(), 'Library', 'DoNotDisturb', 'DB');
const WINDOWS_SUPPRESSING_STATES = new Set(['not-present', 'presentation-mode', 'quiet-time']);
export interface NotificationSoundPolicy {
interface NotificationSoundPolicy {
shouldPlaySound: boolean;
reason: string | null;
}
@@ -282,7 +282,7 @@ async function computeNotificationSoundPolicy(): Promise<NotificationSoundPolicy
return {shouldPlaySound: true, reason: null};
}
export async function getNotificationSoundPolicy(): Promise<NotificationSoundPolicy> {
async function getNotificationSoundPolicy(): Promise<NotificationSoundPolicy> {
const now = Date.now();
if (cachedPolicy && cachedPolicy.expiresAt > now) {
return cachedPolicy.promise;
@@ -13,9 +13,3 @@ export function recordRecentDeepLink(url: string): void {
logger.debug('addRecentDocument failed', {error});
}
}
function _clearRecentDeepLinks(): void {
try {
app.clearRecentDocuments();
} catch {}
}
-10
View File
@@ -990,10 +990,6 @@ const ensureSharedIpc = () => {
if (!Array.isArray(words)) return [];
return checkWords(event.sender.session, words);
});
ipcMain.handle('spellcheck:suggest', (event, word: string): Array<string> => {
if (typeof word !== 'string' || word.length === 0) return [];
return suggestWord(event.sender.session, word);
});
};
let rendererSpellcheckHandlersRegistered = false;
@@ -1032,12 +1028,6 @@ export const registerSpellcheck = (webContents: WebContents): void => {
});
if (!rendererSpellcheckHandlersRegistered) {
rendererSpellcheckHandlersRegistered = true;
ipcMain.handle('spellcheck-get-state', (event) => {
const targetSession = event.sender.session;
const next = applyLaunchSpellcheckMode(sessionState.get(targetSession) ?? {...defaultState});
sessionState.set(targetSession, next);
return next;
});
ipcMain.handle('spellcheck-set-state', async (event, patch: RendererSpellcheckPatch) => {
const targetSession = event.sender.session;
const current = sessionState.get(targetSession) ?? {...defaultState};
@@ -272,7 +272,7 @@ export const DICTIONARY_SOURCES = {
},
} satisfies Record<string, SpellcheckDictionarySource>;
export type SpellcheckDictionaryPackage = keyof typeof DICTIONARY_SOURCES;
type SpellcheckDictionaryPackage = keyof typeof DICTIONARY_SOURCES;
export interface SpellcheckDictionaryCatalogEntry extends SpellcheckBundledDictionary {
package: SpellcheckDictionaryPackage;
+16 -37
View File
@@ -7,7 +7,7 @@ import {destroyDesktopTray} from '@electron/main/DesktopTray';
import {relaunchStableLaunchPath} from '@electron/main/LinuxLaunchPath';
import {t} from '@electron/main/MainI18n';
import {clearSavedWindowBounds, getMainWindow, setQuitting} from '@electron/main/Window';
import {app, BrowserWindow, dialog, Menu, type MenuItemConstructorOptions, session} from 'electron';
import {app, BrowserWindow, dialog, type MenuItemConstructorOptions, session} from 'electron';
const logger = createChildLogger('Troubleshooting');
@@ -44,7 +44,7 @@ export function setHardwareAccelerationDisabledAndRestart(disable: boolean): voi
relaunchAndExit();
}
export function reloadMainWindow(): void {
function reloadMainWindow(): void {
const mainWindow = getMainWindow();
if (!mainWindow || mainWindow.isDestroyed()) {
logger.warn('Reload requested but no main window is available');
@@ -54,23 +54,21 @@ export function reloadMainWindow(): void {
mainWindow.webContents.reloadIgnoringCache();
}
export async function resetAppDataAndRestart(options?: {confirm?: boolean}): Promise<void> {
async function resetAppDataAndRestart(): Promise<void> {
const mainWindow = getMainWindow();
if (options?.confirm !== false) {
const choice = await dialog.showMessageBox(mainWindow ?? new BrowserWindow({show: false}), {
type: 'warning',
buttons: [t('desktop.troubleshooting.resetConfirm'), t('desktop.troubleshooting.resetCancel')],
defaultId: 1,
cancelId: 1,
title: t('desktop.troubleshooting.resetTitle'),
message: t('desktop.troubleshooting.resetMessage'),
detail: t('desktop.troubleshooting.resetDetail'),
noLink: true,
});
if (choice.response !== 0) {
logger.info('App data reset cancelled by user');
return;
}
const choice = await dialog.showMessageBox(mainWindow ?? new BrowserWindow({show: false}), {
type: 'warning',
buttons: [t('desktop.troubleshooting.resetConfirm'), t('desktop.troubleshooting.resetCancel')],
defaultId: 1,
cancelId: 1,
title: t('desktop.troubleshooting.resetTitle'),
message: t('desktop.troubleshooting.resetMessage'),
detail: t('desktop.troubleshooting.resetDetail'),
noLink: true,
});
if (choice.response !== 0) {
logger.info('App data reset cancelled by user');
return;
}
const partition = mainWindow?.webContents.session ?? session.defaultSession;
try {
@@ -129,22 +127,3 @@ export function buildTroubleshootingMenuItems(): Array<MenuItemConstructorOption
},
];
}
function buildHelpPopupMenu(): Menu {
const template: Array<MenuItemConstructorOptions> = [
{
label: t('desktop.appMenu.troubleshooting'),
submenu: buildTroubleshootingMenuItems(),
},
];
return Menu.buildFromTemplate(template);
}
export function popupHelpMenu(window: BrowserWindow | null): void {
const menu = buildHelpPopupMenu();
if (window && !window.isDestroyed()) {
menu.popup({window});
} else {
menu.popup();
}
}
-181
View File
@@ -1,181 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {createRequire} from 'node:module';
import {WINDOWS_APP_USER_MODEL_ID} from '@electron/common/DesktopIdentity';
import {createChildLogger} from '@electron/common/Logger';
import {ipcMain} from 'electron';
const logger = createChildLogger('WindowsToast');
const requireModule = createRequire(import.meta.url);
interface ToastSupport {
supported: boolean;
reason?: string;
}
interface ToastBindingText {
text: string;
hintMaxLines?: number;
hint?: 'attribution';
}
interface ToastImage {
uri: string;
placement?: 'hero' | 'appLogoOverride';
hintCrop?: 'circle' | 'none';
alt?: string;
}
interface ToastInputBox {
id: string;
type: 'text' | 'selection';
placeholder?: string;
title?: string;
options?: ReadonlyArray<{id: string; content: string}>;
}
interface ToastAction {
label: string;
args: string;
activationType?: 'foreground' | 'background' | 'protocol';
imageUri?: string;
hintInputId?: string;
}
interface WindowsToastNotifyRequest {
tag?: string;
group?: string;
expirationTime?: string;
scenario?: 'default' | 'urgent' | 'reminder' | 'incomingCall' | 'alarm';
audio?: 'default' | 'silent' | {silent?: boolean; loop?: boolean; src?: string};
lines: ReadonlyArray<ToastBindingText>;
images?: ReadonlyArray<ToastImage>;
inputs?: ReadonlyArray<ToastInputBox>;
actions?: ReadonlyArray<ToastAction>;
}
interface WinToastModule {
isSupported: () => ToastSupport;
notify: (opts: WindowsToastNotifyRequest & {aumid: string}) => Promise<void>;
dismiss: (opts: {aumid: string; tag: string; group?: string}) => Promise<void>;
clear: (opts: {aumid: string}) => Promise<void>;
loadError: Error | null;
}
let cached: WinToastModule | null | undefined;
let lastSupport: ToastSupport | undefined;
function loadAddon(): WinToastModule | null {
if (cached !== undefined) return cached;
if (process.platform !== 'win32') {
cached = null;
return cached;
}
try {
const mod = requireModule('@fluxer/win-toast') as WinToastModule;
if (mod.loadError) {
logger.warn('@fluxer/win-toast loaded but reported load error', {error: mod.loadError});
cached = null;
return cached;
}
cached = mod;
return cached;
} catch (error) {
logger.info('@fluxer/win-toast not available; rich toasts disabled', {error});
cached = null;
return cached;
}
}
function aumid(): string | null {
return WINDOWS_APP_USER_MODEL_ID || null;
}
function isWindowsToastSupported(): ToastSupport {
if (lastSupport) return lastSupport;
if (process.platform !== 'win32') {
lastSupport = {supported: false, reason: 'not-windows'};
return lastSupport;
}
const mod = loadAddon();
if (!mod) {
lastSupport = {supported: false, reason: 'addon-not-installed'};
return lastSupport;
}
if (aumid() == null) {
lastSupport = {supported: false, reason: 'no-aumid'};
return lastSupport;
}
try {
lastSupport = mod.isSupported();
} catch (error) {
lastSupport = {
supported: false,
reason: `isSupported threw: ${error instanceof Error ? error.message : String(error)}`,
};
}
return lastSupport;
}
async function showWindowsToast(req: WindowsToastNotifyRequest): Promise<boolean> {
const support = isWindowsToastSupported();
if (!support.supported) return false;
const mod = loadAddon();
const id = aumid();
if (!mod || id == null) return false;
try {
await mod.notify({...req, aumid: id});
return true;
} catch (error) {
logger.warn('win-toast notify failed', {error});
return false;
}
}
async function dismissWindowsToast(tag: string, group?: string): Promise<void> {
const mod = loadAddon();
const id = aumid();
if (!mod || id == null) return;
try {
await mod.dismiss({aumid: id, tag, group});
} catch (error) {
logger.debug('win-toast dismiss failed (often benign)', {error});
}
}
async function clearWindowsToasts(): Promise<void> {
const mod = loadAddon();
const id = aumid();
if (!mod || id == null) return;
try {
await mod.clear({aumid: id});
} catch (error) {
logger.debug('win-toast clear failed', {error});
}
}
let registered = false;
export function registerWindowsToastIpcHandlers(): void {
if (registered) return;
registered = true;
ipcMain.handle('win-toast:is-supported', (): ToastSupport => isWindowsToastSupported());
ipcMain.handle(
'win-toast:notify',
async (_event, req: WindowsToastNotifyRequest): Promise<boolean> => showWindowsToast(req),
);
ipcMain.handle(
'win-toast:dismiss',
async (_event, opts: {tag: string; group?: string}): Promise<void> => dismissWindowsToast(opts.tag, opts.group),
);
ipcMain.handle('win-toast:clear', async (): Promise<void> => clearWindowsToasts());
}
function _cleanupWindowsToastIpcHandlers(): void {
if (!registered) return;
ipcMain.removeHandler('win-toast:is-supported');
ipcMain.removeHandler('win-toast:notify');
ipcMain.removeHandler('win-toast:dismiss');
ipcMain.removeHandler('win-toast:clear');
registered = false;
}
-29
View File
@@ -62,10 +62,8 @@ import type {
UpdaterContext,
UpdaterEvent,
VirtmicAvailability,
VirtmicLinkOptions,
VirtmicNode,
VirtmicRoutingGraphResult,
VirtmicSystemLinkOptions,
} from '@electron/common/Types';
import {createBrowserHandoffPreloadAPI} from '@electron/preload/BrowserHandoffPreload';
import {createDesktopStoragePreloadAPI} from '@electron/preload/DesktopStoragePreload';
@@ -453,10 +451,6 @@ const api: ElectronAPI = {
options: SetDesktopTroubleshootingDisableHardwareAccelerationOptions,
): Promise<DesktopTroubleshootingSettings> =>
ipcRenderer.invoke('desktop-troubleshooting-set-disable-hardware-acceleration', options),
desktopTroubleshootingReload: (): Promise<void> => ipcRenderer.invoke('desktop-troubleshooting-reload'),
desktopTroubleshootingResetAppData: (options?: {confirm?: boolean}): Promise<void> =>
ipcRenderer.invoke('desktop-troubleshooting-reset-app-data', options),
popupHelpMenu: (): Promise<void> => ipcRenderer.invoke('desktop-troubleshooting-popup-help-menu'),
onUpdaterEvent: (callback: (event: UpdaterEvent) => void): (() => void) => {
const handler = (_event: Electron.IpcRendererEvent, data: UpdaterEvent) => callback(data);
ipcRenderer.on('updater-event', handler);
@@ -550,9 +544,6 @@ const api: ElectronAPI = {
options: PublicKeyCredentialCreationOptionsJSON,
requestContext?: {pin?: string; instanceKey?: string},
): Promise<RegistrationResponseJSON> => ipcRenderer.invoke('passkey-register', options, requestContext),
toggleDevTools: (): void => {
ipcRenderer.send('toggle-devtools');
},
getDesktopSources: (
types: Array<'screen' | 'window'>,
requestId?: string,
@@ -604,7 +595,6 @@ const api: ElectronAPI = {
setBadgeCount: (count: number): void => {
ipcRenderer.send('set-badge-count', count);
},
getBadgeCount: (): Promise<number> => ipcRenderer.invoke('get-badge-count'),
setNativeLocale: (locale: string, strings: Record<string, string>): void => {
ipcRenderer.send('native-locale-set', {locale, strings});
},
@@ -622,16 +612,9 @@ const api: ElectronAPI = {
ipcRenderer.on('jump-list-new-dm', handler);
return () => ipcRenderer.removeListener('jump-list-new-dm', handler);
},
bounceDock: (type?: 'critical' | 'informational'): number => {
return ipcRenderer.sendSync('bounce-dock', type ?? 'informational');
},
cancelBounceDock: (id: number): void => {
ipcRenderer.send('cancel-bounce-dock', id);
},
setZoomFactor: (factor: number): void => {
ipcRenderer.send('set-zoom-factor', factor);
},
getZoomFactor: (): Promise<number> => ipcRenderer.invoke('get-zoom-factor'),
getAccessibilitySupportEnabled: (): Promise<boolean> => ipcRenderer.invoke('get-accessibility-support-enabled'),
onAccessibilitySupportChanged: (callback: (enabled: boolean) => void): (() => void) => {
const handler = (_event: Electron.IpcRendererEvent, enabled: boolean): void => callback(enabled);
@@ -673,9 +656,6 @@ const api: ElectronAPI = {
releaseStreamingPriority: (): void => {
ipcRenderer.send('streaming-priority-release');
},
resetStreamingPriority: (): void => {
ipcRenderer.send('streaming-priority-reset');
},
getStreamingPriorityDiagnostics: (): Promise<StreamingPriorityDiagnostics> =>
ipcRenderer.invoke('streaming-priority-get-diagnostics'),
onTrayAction: (callback: (action: TrayActionPayload) => void): (() => void) => {
@@ -685,7 +665,6 @@ const api: ElectronAPI = {
},
globalKeyHookStart: (): Promise<boolean> => ipcRenderer.invoke('global-key-hook-start'),
globalKeyHookStop: (): Promise<void> => ipcRenderer.invoke('global-key-hook-stop'),
globalKeyHookIsRunning: (): Promise<boolean> => ipcRenderer.invoke('global-key-hook-is-running'),
checkInputMonitoringAccess: (): Promise<boolean> => ipcRenderer.invoke('check-input-monitoring-access'),
globalKeyHookRegister: (options: GlobalKeyHookRegisterOptions): Promise<void> =>
ipcRenderer.invoke('global-key-hook-register', options),
@@ -759,14 +738,12 @@ const api: ElectronAPI = {
};
},
},
spellcheckGetState: (): Promise<SpellcheckState> => ipcRenderer.invoke('spellcheck-get-state'),
spellcheckSetState: (state: Partial<SpellcheckState>): Promise<SpellcheckState> =>
ipcRenderer.invoke('spellcheck-set-state', state),
spellcheckGetAvailableLanguages: (): Promise<Array<string>> =>
ipcRenderer.invoke('spellcheck-get-available-languages'),
spellcheckGetBundledDictionaries: (): Promise<Array<SpellcheckBundledDictionary>> =>
ipcRenderer.invoke('spellcheck:get-bundled-dictionaries'),
spellcheckSuggest: (word: string): Promise<Array<string>> => ipcRenderer.invoke('spellcheck:suggest', word),
onSpellcheckEngineResolved: (callback: (info: SpellcheckResolvedEngineInfo) => void): (() => void) => {
const handler = (_event: Electron.IpcRendererEvent, data: SpellcheckResolvedEngineInfo): void => callback(data);
ipcRenderer.on('spellcheck-engine-resolved', handler);
@@ -802,12 +779,6 @@ const api: ElectronAPI = {
availability: VirtmicAvailability;
}> => ipcRenderer.invoke('virtmic:list', options),
getRoutingGraph: (): Promise<VirtmicRoutingGraphResult> => ipcRenderer.invoke('virtmic:get-routing-graph'),
startInclude: (include: Array<VirtmicNode>, options?: VirtmicLinkOptions): Promise<boolean> =>
ipcRenderer.invoke('virtmic:start-include', include, options),
startSystem: (exclude: Array<VirtmicNode>, options?: VirtmicSystemLinkOptions): Promise<boolean> =>
ipcRenderer.invoke('virtmic:start-system', exclude, options),
resolveWindowPid: (sourceId: string): Promise<number | null> =>
ipcRenderer.invoke('virtmic:resolve-window-pid', sourceId),
stop: (): Promise<void> => ipcRenderer.invoke('virtmic:stop'),
},
nativeAudio: {