diff --git a/apps/desktop/README.i18n.yaml b/apps/desktop/README.i18n.yaml index 2f89559d39..ec024ea7bf 100644 --- a/apps/desktop/README.i18n.yaml +++ b/apps/desktop/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write apps/desktop/README.md -README.md: 4de8eb539bba42919fd29740bb2741566f460ab1 -README.zh.md: 9e733caae70fc3fe4d0131548a14660cddbd2248 +README.md: 16bf5fd64fc4ea8bfe03e6e84e37cb87115bd74b +README.zh.md: 500240148cc8fb0c90f6029caaa4fdf57881c83d diff --git a/apps/desktop/README.md b/apps/desktop/README.md index 4de8eb539b..16bf5fd64f 100644 --- a/apps/desktop/README.md +++ b/apps/desktop/README.md @@ -12,6 +12,8 @@ Creator and the Web Plugin Manager use Desktop’s bundled pnpm under Electron N Desktop microphone access is restricted to audio requests from the primary `dsh-app://app` frame. macOS uses system microphone authorization and a packaged usage description. +Press F12 (Fn+F12 on media-key keyboards), Command+Option+I on macOS, or Ctrl+Shift+I on Windows to toggle DevTools for the focused application page, including in packaged builds. These native shortcuts use hidden application-menu items. Update overlays and packaged embedded browser guests disable DevTools. + ## Key technical decisions The original artwork lives in `resources/icon.png` and `resources/icon.svg`; platform adaptations retain the whale and gradients in `resources/icon-windows.*` and `resources/icon-macos.*`. Export each platform SVG as a transparent 1024×1024 PNG. Electron-builder generates the multi-size ICO for the Windows application, installer, and uninstaller ([Windows icon requirements](https://learn.microsoft.com/en-us/windows/apps/design/iconography/app-icon-construction)). The installation pages use matching artwork in both themes; the uninstaller's welcome and finish pages share `installer/assets/uninstaller-sidebar.png`, converted to a 164×314 BMP during preparation. diff --git a/apps/desktop/README.zh.md b/apps/desktop/README.zh.md index 9e733caae7..500240148c 100644 --- a/apps/desktop/README.zh.md +++ b/apps/desktop/README.zh.md @@ -12,6 +12,8 @@ Creator 和 Web Plugin Manager 在 Electron Node 模式下使用 Desktop 内置 桌面麦克风访问仅允许主 `dsh-app://app` 页面发起的音频请求。macOS 使用系统麦克风授权与随包用途说明。 +按 F12(多媒体功能键键盘上为 Fn+F12)、macOS 的 Command+Option+I 或 Windows 的 Ctrl+Shift+I,可切换当前获得焦点的应用页面的 DevTools,打包版本同样支持。这些原生快捷键通过隐藏的应用菜单项注册。更新遮罩和打包版本的内嵌浏览器禁用 DevTools。 + ## 关键技术决策 设计师原稿位于 `resources/icon.png` 和 `resources/icon.svg`;平台适配保留鲸鱼与渐变,分别位于 `resources/icon-windows.*` 和 `resources/icon-macos.*`。将各平台 SVG 导出为透明的 1024×1024 PNG。electron-builder 为 Windows 应用、安装程序和卸载程序生成多尺寸 ICO([Windows 图标要求](https://learn.microsoft.com/en-us/windows/apps/design/iconography/app-icon-construction))。安装页面在两种主题下使用匹配的图案;卸载程序的欢迎和完成页共用 `installer/assets/uninstaller-sidebar.png`,准备阶段将其转换为 164×314 BMP。 diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 15d7453dcb..1c0a5ae985 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -151,6 +151,7 @@ function createWindow(preload: string, show = false, primary = false): BrowserWi sandbox: true, webSecurity: true, webviewTag: primary, + devTools: true, }, }) window.webContents.setWindowOpenHandler(({ url }) => { @@ -672,9 +673,13 @@ async function main(): Promise { { role: 'quit', ...(darwin ? { label: currentDesktopLocale().messages.quitApplication } : process.platform === 'win32' ? { label: currentDesktopLocale().messages.exitApplication } : {}) }, ] - Menu.setApplicationMenu(process.platform === 'win32' ? null : Menu.buildFromTemplate([{ + const devToolsItems: MenuItemConstructorOptions[] = [ + { role: 'toggleDevTools', visible: false }, + { role: 'toggleDevTools', visible: false, accelerator: 'F12' }, + ] + Menu.setApplicationMenu(Menu.buildFromTemplate(process.platform === 'win32' ? devToolsItems : [{ label: darwin ? app.name : currentDesktopLocale().messages.application, - submenu: applicationItems(), + submenu: [...applicationItems(), ...devToolsItems], }, ...platformMenus])) if (process.platform === 'win32') { diff --git a/apps/desktop/tests/main-startup.spec.ts b/apps/desktop/tests/main-startup.spec.ts index 329cf5a9b8..53e42a94a3 100644 --- a/apps/desktop/tests/main-startup.spec.ts +++ b/apps/desktop/tests/main-startup.spec.ts @@ -368,6 +368,16 @@ describe('desktop main startup', () => { expect((await handler(new Request('dsh-app://unknown/update-dialog.html'))).status).toBe(404) }) + it('installs hidden native DevTools shortcuts in the macOS application menu', async () => { + vi.stubGlobal('process', { ...process, platform: 'darwin' }) + await readyForUpdate() + expect(applicationMenuItems().slice(-2)).toEqual([ + { role: 'toggleDevTools', visible: false }, + { role: 'toggleDevTools', visible: false, accelerator: 'F12' }, + ]) + expect(harness.windows[0]!.options).toMatchObject({ webPreferences: { devTools: true } }) + }) + it.each([ ['darwin', true, 'en-US'], ['darwin', false, 'zh-CN'], @@ -557,7 +567,10 @@ describe('desktop main startup', () => { } else if (platform === 'win32') { expect(window.options).toMatchObject({ titleBarStyle: 'hidden', titleBarOverlay: { height: WINDOWS_TITLEBAR_HEIGHT } }) expect(window.options).not.toHaveProperty('vibrancy') - expect(harness.menu.setApplicationMenu).toHaveBeenCalledWith(null) + expect(harness.menu.mock.calls[0]![0]).toEqual([ + { role: 'toggleDevTools', visible: false }, + { role: 'toggleDevTools', visible: false, accelerator: 'F12' }, + ]) } else { expect(window.options).not.toHaveProperty('titleBarStyle') expect(window.options).not.toHaveProperty('vibrancy') @@ -665,7 +678,7 @@ describe('desktop main startup', () => { window.webContents.mainFrame.url = 'dsh-app://unowned/index.html' listener(event, 'zh-CN', '#fff', '#000') expect(window.setTitleBarOverlay).not.toHaveBeenCalled() - expect(harness.menu.setApplicationMenu).toHaveBeenCalledExactlyOnceWith(null) + expect(harness.menu.setApplicationMenu).toHaveBeenCalledOnce() }) it('maps Windows caption menus to localized native commands and rejects foreign popup requests', async () => { @@ -719,7 +732,7 @@ describe('desktop main startup', () => { ? ['Desktop test', 'fileMenu', 'editMenu', 'windowMenu'] : ['Application', 'editMenu']) const application = template[0]!.submenu as MenuItemConstructorOptions[] - expect(application.map(describeItem)).toEqual(platform === 'darwin' + expect(application.filter(item => item.visible !== false).map(describeItem)).toEqual(platform === 'darwin' ? ['about', 'separator', en.checkUpdatesMenu, 'separator', 'hide', 'hideOthers', 'unhide', 'separator', 'quit'] : ['about', 'separator', en.checkUpdatesMenu, 'separator', 'quit']) expect(harness.menu.setApplicationMenu).toHaveBeenCalledOnce()