diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a91344..b715700 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## Next +- Make macOS media key permission request non-intrusive + ## 0.19.9 - 2025 Jul 4 - Fix column size not updating when resizing window - Fix column sorting not working diff --git a/src/App.svelte b/src/App.svelte index 4f93c41..fc114fe 100755 --- a/src/App.svelte +++ b/src/App.svelte @@ -1,6 +1,6 @@ - + { + if (media_keys_result?.needs_accessibility_permission) { + media_keys_result = null + media_keys_result = await ipc_renderer.invoke('init_media_keys', false) + } + }} +/> Ferrum @@ -204,6 +219,56 @@
+ {#if media_keys_result} +
+
+ {#if media_keys_result.error} + {media_keys_result.error} + + + {:else if media_keys_result.needs_accessibility_permission} + Do you want to Ferrum to control the media keys? You may need to delete and re-add the + app from the accessibility list + + + {/if} +
+
+ {/if}
{#if $queue_visible} diff --git a/src/components/Button.svelte b/src/components/Button.svelte index 583e9d3..d589cb5 100644 --- a/src/components/Button.svelte +++ b/src/components/Button.svelte @@ -5,17 +5,28 @@ interface $$Props extends HTMLBaseAttributes { secondary?: boolean danger?: boolean + thin?: boolean type?: 'button' | 'submit' | 'reset' } export let secondary = false export let danger = false + export let thin = false export let type: 'button' | 'submit' | 'reset' = 'button' let normal = !danger && !secondary $: normal = !danger && !secondary - @@ -44,6 +55,9 @@ right: 0px border-radius: 7px transition: all 120ms var(--cubic-out) + button.thin + padding: 4px 10px + font-size: 12px button.normal &::before background-color: hsl(220, 100%, 46%) diff --git a/src/electron/ipc.ts b/src/electron/ipc.ts index d6f8a38..4e1405a 100644 --- a/src/electron/ipc.ts +++ b/src/electron/ipc.ts @@ -3,6 +3,7 @@ import { ipc_main } from './typed_ipc' import path from 'path' import is from './is' import { check_for_updates } from './update' +import { init_media_keys } from './shortcuts' ipc_main.handle('check_for_updates', (_e) => { return check_for_updates() ?? null @@ -44,6 +45,10 @@ ipc_main.handle('volume_change', async (_e, up) => { } }) +ipc_main.handle('init_media_keys', async (_e, prompt) => { + return await init_media_keys(prompt) +}) + ipc_main.handle('show_tracks_menu', (e, options) => { return new Promise((resolve) => { const menu = Menu.buildFromTemplate([ diff --git a/src/electron/main.ts b/src/electron/main.ts index 21188fd..43eef3e 100644 --- a/src/electron/main.ts +++ b/src/electron/main.ts @@ -5,7 +5,6 @@ import addon from '../../ferrum-addon' if (is.dev) app.setName('Ferrum Dev') import { init_menu_bar } from './menubar' -import { init_media_keys } from './shortcuts' import('./ipc') import path from 'path' import url from 'url' @@ -60,6 +59,10 @@ protocol.registerSchemesAsPrivileged([ }, ]) +export const browser_windows = { + main_window: null as BrowserWindow | null, +} + app.whenReady().then(async () => { let main_window: BrowserWindow | null = new BrowserWindow({ width: 1305, @@ -75,10 +78,7 @@ app.whenReady().then(async () => { backgroundColor: '#0D1115', show: false, }) - - if (!is.dev) { - await init_media_keys(main_window) - } + browser_windows.main_window = main_window protocol.registerFileProtocol('track', (request, callback) => { const url = decodeURI(request.url) @@ -165,6 +165,7 @@ app.whenReady().then(async () => { }) main_window.on('closed', () => { main_window = null + browser_windows.main_window = main_window }) ipc_main.handle('app_loaded', () => { app_loaded = true diff --git a/src/electron/shortcuts.ts b/src/electron/shortcuts.ts index 473728e..1a47d34 100644 --- a/src/electron/shortcuts.ts +++ b/src/electron/shortcuts.ts @@ -1,79 +1,42 @@ -import { globalShortcut, dialog, systemPreferences, BrowserWindow } from 'electron' +import { globalShortcut, systemPreferences } from 'electron' import is from './is' -import type { WebContents } from './typed_ipc' +import { browser_windows } from './main' -function try_registering(main_window: BrowserWindow) { - const web_contents = main_window.webContents as WebContents +let was_success = false +function try_registering() { + if (was_success) { + return true + } + const web_contents = browser_windows.main_window?.webContents const success1 = globalShortcut.register('MediaPlayPause', () => { - if (main_window !== null) web_contents.send('playPause') + web_contents?.send('playPause') }) if (!success1) return false globalShortcut.register('MediaNextTrack', () => { - if (main_window !== null) web_contents.send('Next') + web_contents?.send('Next') }) globalShortcut.register('MediaPreviousTrack', () => { - if (main_window !== null) web_contents.send('Previous') + web_contents?.send('Previous') }) globalShortcut.register('MediaStop', () => { - if (main_window !== null) web_contents.send('Stop') + web_contents?.send('Stop') }) + was_success = true return true } -async function request_loop(main_window: BrowserWindow) { - let first_request = true - for (;;) { - let msg = 'No accessibility permissions detected.' - if (!first_request) { - msg = 'Click Done when you have granted Ferrum accessibility permissions.' - } else { - first_request = true - } - const result = await dialog.showMessageBox(main_window, { - type: 'info', - message: `${msg} To grant them, open System Preferences, click Security & Privacy, click Privacy, click Accessibility, then select Ferrum's checkbox`, - buttons: ['Done', 'Cancel'], - defaultId: 0, - }) - if (result.response === 1 || systemPreferences.isTrustedAccessibilityClient(true)) { - return - } - } -} - -async function get_trusted_accesibility_macos(main_window: BrowserWindow) { - const result = await dialog.showMessageBox(main_window, { - type: 'info', - message: 'Ferrum needs accessibility permissions to exclusively take over the media keys', - buttons: ['Continue', 'Ignore'], - defaultId: 0, - }) - if (result.response === 0) { - setTimeout(() => { - systemPreferences.isTrustedAccessibilityClient(true) // prompt - }, 500) - await request_loop(main_window) - try_registering(main_window) - } -} - -export async function init_media_keys(main_window: BrowserWindow) { - const success = try_registering(main_window) +export async function init_media_keys(prompt: boolean) { + const success = try_registering() if (!success) { if (is.mac) { - if (systemPreferences.isTrustedAccessibilityClient(false)) { - await dialog.showMessageBox(main_window, { - type: 'info', - message: 'Could not register media key shortcuts', - }) + if (systemPreferences.isTrustedAccessibilityClient(prompt)) { + return { error: 'Could not register media key shortcuts' } } else { - await get_trusted_accesibility_macos(main_window) + return { needs_accessibility_permission: true } } } else { - await dialog.showMessageBox(main_window, { - type: 'info', - message: 'Could not register media key shortcuts', - }) + return { error: 'Could not register media key shortcuts' } } } + return null } diff --git a/src/electron/typed_ipc.ts b/src/electron/typed_ipc.ts index d7abb72..da05836 100644 --- a/src/electron/typed_ipc.ts +++ b/src/electron/typed_ipc.ts @@ -162,6 +162,13 @@ type Commands = { showTracklistMenu: (options: { id: string; isFolder: boolean; isRoot: boolean }) => void show_columns_menu: (options: { menu: MenuItemConstructorOptions[] }) => void volume_change: (up: boolean) => void + init_media_keys: ( + prompt: boolean, + ) => Promise< + | { needs_accessibility_permission: undefined; error: string } + | { needs_accessibility_permission: boolean; error: undefined } + | null + > 'update:Shuffle': (checked: boolean) => void 'update:Repeat': (checked: boolean) => void