From d0b809a1f85793975c87193eaa0998c4a43f7347 Mon Sep 17 00:00:00 2001 From: Doji Date: Tue, 11 Aug 2026 20:26:54 +0800 Subject: [PATCH] fix(stage-tamagotchi): let AIRI start on Linux ARM64 (#2253) --- .../services/electron/global-shortcut.test.ts | 17 +++++----- .../main/services/electron/global-shortcut.ts | 32 +++++++++++++------ .../src/main/windows/dashboard/index.ts | 4 +-- .../src/main/windows/main/index.ts | 4 +-- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/apps/stage-tamagotchi/src/main/services/electron/global-shortcut.test.ts b/apps/stage-tamagotchi/src/main/services/electron/global-shortcut.test.ts index 4438ca36e..ba2f1d773 100644 --- a/apps/stage-tamagotchi/src/main/services/electron/global-shortcut.test.ts +++ b/apps/stage-tamagotchi/src/main/services/electron/global-shortcut.test.ts @@ -106,14 +106,13 @@ async function setupMocks() { }, })) - vi.doMock('uiohook-napi', () => ({ - uIOhook: { - on: vi.fn(), - removeListener: vi.fn(), - start: vi.fn(), - stop: vi.fn(), - }, - UiohookKey: new Proxy({}, { get: () => 0 }), + vi.doMock('./global-shortcut-uiohook', () => ({ + createUiohookDriver: () => ({ + tryRegister: vi.fn(async (binding: ShortcutBinding) => ({ id: binding.id, ok: true })), + unregisterById: vi.fn(), + unregisterAll: vi.fn(), + dispose: vi.fn(), + }), })) vi.doMock('@moeru/eventa', async (importOriginal) => { @@ -182,7 +181,7 @@ describe('setupGlobalShortcutService', () => { registerMockWindow(service, ctx) const handler = ctx.invokeHandlers.get('eventa:invoke:electron:shortcut:register')! - const result = handler({ ...exampleBinding('ptt'), receiveKeyUps: true }) as { id: string, ok: boolean } + const result = await handler({ ...exampleBinding('ptt'), receiveKeyUps: true }) as { id: string, ok: boolean } expect(result).toEqual({ id: 'ptt', ok: true }) expect(m.registerMock).not.toHaveBeenCalled() }) diff --git a/apps/stage-tamagotchi/src/main/services/electron/global-shortcut.ts b/apps/stage-tamagotchi/src/main/services/electron/global-shortcut.ts index c54f17c53..226ed1ebd 100644 --- a/apps/stage-tamagotchi/src/main/services/electron/global-shortcut.ts +++ b/apps/stage-tamagotchi/src/main/services/electron/global-shortcut.ts @@ -2,6 +2,8 @@ import type { createContext } from '@moeru/eventa/adapters/electron/main' import type { ShortcutBinding, ShortcutRegistrationResult } from '@proj-airi/stage-shared/global-shortcut' import type { BrowserWindow } from 'electron' +import type { UiohookDriver } from './global-shortcut-uiohook' + import { useLogg } from '@guiiai/logg' import { defineInvokeHandler } from '@moeru/eventa' import { formatElectronAccelerator, ShortcutFailureReasons } from '@proj-airi/stage-shared/global-shortcut' @@ -15,7 +17,6 @@ import { electronShortcutUnregisterAll, } from '../../../shared/eventa' import { onAppBeforeQuit } from '../../libs/bootkit/lifecycle' -import { createUiohookDriver } from './global-shortcut-uiohook' export type EventaContext = ReturnType['context'] @@ -57,10 +58,7 @@ export function setupGlobalShortcutService(): GlobalShortcutService { } } - const uiohookDriver = createUiohookDriver({ - broadcastTriggered, - logger: log, - }) + let uiohookDriver: UiohookDriver | undefined function tryRegisterElectron(binding: ShortcutBinding): ShortcutRegistrationResult { const electronAccelerator = formatElectronAccelerator(binding.accelerator) @@ -79,7 +77,23 @@ export function setupGlobalShortcutService(): GlobalShortcutService { return { id: binding.id, ok: true } } - function tryRegisterUiohook(binding: ShortcutBinding): ShortcutRegistrationResult { + async function tryRegisterUiohook(binding: ShortcutBinding): Promise { + if (!uiohookDriver) { + // NOTICE: + // uiohook-napi@1.5.5 ships an x86-64 binary in its Linux ARM64 prebuild. + // Loading the existing driver only when needed keeps AIRI startup working. + // Source: https://app.unpkg.com/uiohook-napi@1.5.5/files/prebuilds/linux-arm64 + // Remove this workaround when that prebuild contains an ARM64 binary. + try { + const { createUiohookDriver } = await import('./global-shortcut-uiohook') + uiohookDriver = createUiohookDriver({ broadcastTriggered, logger: log }) + } + catch (error) { + log.withError(error).warn('uiohook driver is unavailable on this system') + return { id: binding.id, ok: false, reason: ShortcutFailureReasons.Unsupported } + } + } + const result = uiohookDriver.tryRegister(binding) if (result.ok) active.set(binding.id, { binding, owner: 'renderer', driver: 'uiohook' }) @@ -127,12 +141,12 @@ export function setupGlobalShortcutService(): GlobalShortcutService { } } else { - uiohookDriver.unregisterById(id) + uiohookDriver?.unregisterById(id) } active.delete(id) } - function tryRegister(binding: ShortcutBinding): ShortcutRegistrationResult { + function tryRegister(binding: ShortcutBinding): ShortcutRegistrationResult | Promise { if (active.has(binding.id)) { return { id: binding.id, ok: false, reason: ShortcutFailureReasons.DuplicateId } } @@ -189,7 +203,7 @@ export function setupGlobalShortcutService(): GlobalShortcutService { const dispose: GlobalShortcutService['dispose'] = () => { unregisterAll(true) - uiohookDriver.dispose() + uiohookDriver?.dispose() contexts.clear() } diff --git a/apps/stage-tamagotchi/src/main/windows/dashboard/index.ts b/apps/stage-tamagotchi/src/main/windows/dashboard/index.ts index 56e45c353..cdb4205cc 100644 --- a/apps/stage-tamagotchi/src/main/windows/dashboard/index.ts +++ b/apps/stage-tamagotchi/src/main/windows/dashboard/index.ts @@ -10,8 +10,6 @@ import { dirname, join, resolve } from 'node:path' import { env } from 'node:process' import { fileURLToPath } from 'node:url' -import clickDragPlugin from 'electron-click-drag-plugin' - import { is } from '@electron-toolkit/utils' import { defineInvokeHandler } from '@moeru/eventa' import { createContext } from '@moeru/eventa/adapters/electron/main' @@ -149,6 +147,8 @@ export async function setupDashboardWindow(params: { * Workaround: https://github.com/noobfromph/electron-click-drag-plugin */ if (!isLinux) { + const { default: clickDragPlugin } = await import('electron-click-drag-plugin') + function handleStartDraggingWindow() { try { const windowId = window.getNativeWindowHandle() diff --git a/apps/stage-tamagotchi/src/main/windows/main/index.ts b/apps/stage-tamagotchi/src/main/windows/main/index.ts index 3c82a4db6..7558651f8 100644 --- a/apps/stage-tamagotchi/src/main/windows/main/index.ts +++ b/apps/stage-tamagotchi/src/main/windows/main/index.ts @@ -17,8 +17,6 @@ import { dirname, join, resolve } from 'node:path' import { env } from 'node:process' import { fileURLToPath } from 'node:url' -import clickDragPlugin from 'electron-click-drag-plugin' - import { is } from '@electron-toolkit/utils' import { defineInvokeHandler } from '@moeru/eventa' import { createContext } from '@moeru/eventa/adapters/electron/main' @@ -201,6 +199,8 @@ export async function setupMainWindow(params: { * Workaround: https://github.com/noobfromph/electron-click-drag-plugin */ if (!isLinux) { + const { default: clickDragPlugin } = await import('electron-click-drag-plugin') + function handleStartDraggingWindow() { try { const windowId = window.getNativeWindowHandle()