mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
refactor: remove dead code (#3337)
This commit is contained in:
1 parent
a19002652b
commit
93d1f8af46
1527 files changed
+21170
-73846
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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']> {
|
||||
|
||||
@@ -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 {}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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: {
|
||||
|
||||
Reference in new issue
Block a user