From 2b1c1d5e59da158718ab0344f95f426d0377bfdd Mon Sep 17 00:00:00 2001 From: jeffvli Date: Thu, 30 Mar 2023 08:09:20 -0700 Subject: [PATCH] Add tray settings (#49) --- assets/pause-circle.png | Bin 0 -> 896 bytes assets/play-circle.png | Bin 0 -> 971 bytes assets/skip-next.png | Bin 0 -> 479 bytes assets/skip-previous.png | Bin 0 -> 524 bytes src/main/main.ts | 163 +++++++++++++++--- .../settings/components/settings-content.tsx | 11 +- .../components/window/window-settings.tsx | 48 +++++- src/renderer/store/settings.store.ts | 4 + 8 files changed, 198 insertions(+), 28 deletions(-) create mode 100644 assets/pause-circle.png create mode 100644 assets/play-circle.png create mode 100644 assets/skip-next.png create mode 100644 assets/skip-previous.png diff --git a/assets/pause-circle.png b/assets/pause-circle.png new file mode 100644 index 0000000000000000000000000000000000000000..2636f00b29879958aca4950d8a8a1400a602a97c GIT binary patch literal 896 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTCmUKs7M+SzC{oH>NS%G|oWRD45dJguM!v-tY$DUh!@P+6=(yLU`z6LcLCBs@Y8vBJ&@uo z@Q5sCVBk9f!i-b3`J@>bm|{I$978hhy`5?3FXAZUe!n3=Ajg5b>4?PQ3)x%R-X|FR z;`I39d}G002@{pFD`H)}obm@8^wrd=n$);Vf*LyQx?7*zxgA};bFb{h>Zj-K-T8g? z&f9Z;*sH$$*v}bSvgjN0^@hv`Tn4N?jECE|zhRu;z?&c&!5Y^tQ0L~m-fso}72ngJ z1NW|<*R5C=R=Y?}#@Xg<2{&`#QY5$C~#XOdu79WsaFzg+mXQKM3MjI5987oVonesdkan+y4(J7)<}U z_a%3h{0Byjt!|#)E2m}Z9}p|BYx%JFXVNvE7=7({;m0c4ckiFzGLXG((mqp(aow?P zXBJ-IiRsIoc{pVvL-I!5;*7%6Tm~@<7Dvp{6MEn>Q8Yn+TL|C%E|woMw~gAxwy$Bx zPmX@JAyDN&RL*Ics5_gO)_}}7y7q3;J6nUmWzVI=ig_P6?V5Gnh`HTdr#$(GRf^?m zk;))56ZQi~1rCMnQvB>H=5&K)-mz_YNt)hrpUl51tMsqE@%xc%!m2OJ>{>Q(-|5xi zH@NP%Pe5(!sR*4CnfSNi^Ia#@FPyh3h0nKZx3=ji`|Yb9nIAv(8 zvb5iGl2gcM!`EsjRdP`( zkYX@0Ff!0JGydYNbP0l+XkK%a3oA literal 0 HcmV?d00001 diff --git a/assets/play-circle.png b/assets/play-circle.png new file mode 100644 index 0000000000000000000000000000000000000000..0d10870e55c816c6ee7a354e008277b29c2e4251 GIT binary patch literal 971 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTCmUKs7M+SzC{oH>NS%G|oWRD45dJguM!v-tY$DUh!@P+6=(yLU`z6LcLCBs@Y8vBJ&@uo z@Q5sCVBk9f!i-b3`J@>bm{xeYIEG~0dppxQU)WKk_52gnqb+KUOqKyj+?`q)9Y#O- zH8@mKqykia2%0q7IVuJ;Ot^Gm$%BguOW7@XU8h_S)R}N%@xhKA4;Rnan(}7r=H14- zPaj?$^FFTr`MIlS=1Q(yzwckZq`YH;qvz#GX)3;+THaQwnK~6q^6+?Z z<70o$T(vEb+QEONW4-?Y+XN&250ejYUGQMIw~lQC&zuun|Fkx|XWSE8ePF7|!S=@E znho1uD;m6!-p8=#9K*hq;SLAWpKWf*f5mI?=&*xW>Sf>4>pomBYRS)9C%*V4qrGCL zcaYiT=yF*PEy?6>hw5Zre0+FFouH|iZw#Kb8 z58|1e&#^HHv%YD}4y$ThfB3GL@Zl6=W8KQrHR8&}2lBWc#5bN^*|>e?3%EIuw;*a})30bwoHKHUXu_VNS%G|oWRD45dJguM!v-tY$DUh!@P+6=(yLU`z6LcLCBs@Y8vBJ&@uo z@Q5sCVBk9f!i-b3`J{n@dpunnLo)8Yy}rrEq)nG{Z{UlQ%!2$`J&LOY++}d=HltUc=O!r6qa+ZShZ@^ zE(LeN{59{wBm%bp{h?ao8c~vxSdwa$T$Bo=7>o>z40H{RfGEVk%*x2v%E(mPz`)AD zK<<^p0u&9o`6-!cmAEzJZ~q|!)F276Aviy+q&%@Gm7%=6TrV>(yEr+qAXP8FD1G)j R8!4b722WQ%mvv4FO#phcns@*J literal 0 HcmV?d00001 diff --git a/assets/skip-previous.png b/assets/skip-previous.png new file mode 100644 index 0000000000000000000000000000000000000000..1408850860dd8584c405a7d36ddc56a65b017f8e GIT binary patch literal 524 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTCmUKs7M+SzC{oH>NS%G|oWRD45dJguM!v-tY$DUh!@P+6=(yLU`z6LcLCBs@Y8vBJ&@uo z@Q5sCVBk9f!i-b3`J{n@FFjoxLo)8Yy}s9%*-@Y^vDw2zK}ogj5!0OpYlWq(+a0*K zG`KLXJHTt9q*nHV-E?o~mBTGt*N4tKxUL_h(r_4qK$5*P$C? zzjUQps{SINr&UW_BT7;dOH!?pi&B9UgOP!efv%wu5QP|+Ss58y85nCD7+4t?*vl6^ zN70a*pOTqYiCcr8)2V2n21$?&!TD(=<%vb94CUqJdYO6I#mR{Use1WE>9gP2NC6cw Nc)I$ztaD0e0ssdcxC8(I literal 0 HcmV?d00001 diff --git a/src/main/main.ts b/src/main/main.ts index e2010894..cfa77ae5 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -9,7 +9,16 @@ * `./src/main.js` using webpack. This gives us some performance wins. */ import path from 'path'; -import { app, BrowserWindow, shell, ipcMain, globalShortcut } from 'electron'; +import { + app, + BrowserWindow, + shell, + ipcMain, + globalShortcut, + Tray, + Menu, + nativeImage, +} from 'electron'; import electronLocalShortcut from 'electron-localshortcut'; import log from 'electron-log'; import { autoUpdater } from 'electron-updater'; @@ -18,7 +27,7 @@ import MpvAPI from 'node-mpv'; import { disableMediaKeys, enableMediaKeys } from './features/core/player/media-keys'; import { store } from './features/core/settings/index'; import MenuBuilder from './menu'; -import { resolveHtmlPath } from './utils'; +import { isLinux, isMacOS, isWindows, resolveHtmlPath } from './utils'; import './features'; declare module 'node-mpv'; @@ -36,6 +45,9 @@ if (store.get('ignore_ssl')) { } let mainWindow: BrowserWindow | null = null; +let tray: Tray | null = null; +let exitFromTray = false; +let forceQuit = false; if (process.env.NODE_ENV === 'production') { const sourceMapSupport = require('source-map-support'); @@ -67,19 +79,105 @@ if (!singleInstance) { app.quit(); } +const RESOURCES_PATH = app.isPackaged + ? path.join(process.resourcesPath, 'assets') + : path.join(__dirname, '../../assets'); + +const getAssetPath = (...paths: string[]): string => { + return path.join(RESOURCES_PATH, ...paths); +}; + +export const getMainWindow = () => { + return mainWindow; +}; + +const createWinThumbarButtons = () => { + if (isWindows()) { + console.log('setting buttons'); + getMainWindow()?.setThumbarButtons([ + { + click: () => getMainWindow()?.webContents.send('renderer-player-previous'), + icon: nativeImage.createFromPath(getAssetPath('skip-previous.png')), + tooltip: 'Previous Track', + }, + { + click: () => getMainWindow()?.webContents.send('renderer-player-play-pause'), + icon: nativeImage.createFromPath(getAssetPath('play-circle.png')), + tooltip: 'Play/Pause', + }, + { + click: () => getMainWindow()?.webContents.send('renderer-player-next'), + icon: nativeImage.createFromPath(getAssetPath('skip-next.png')), + tooltip: 'Next Track', + }, + ]); + } +}; + +const createTray = () => { + if (isMacOS()) { + return; + } + + tray = isLinux() ? new Tray(getAssetPath('icon.png')) : new Tray(getAssetPath('icon.ico')); + const contextMenu = Menu.buildFromTemplate([ + { + click: () => { + getMainWindow()?.webContents.send('renderer-player-play-pause'); + }, + label: 'Play/Pause', + }, + { + click: () => { + getMainWindow()?.webContents.send('renderer-player-next'); + }, + label: 'Next Track', + }, + { + click: () => { + getMainWindow()?.webContents.send('renderer-player-previous'); + }, + label: 'Previous Track', + }, + { + click: () => { + getMainWindow()?.webContents.send('renderer-player-stop'); + }, + label: 'Stop', + }, + { + type: 'separator', + }, + { + click: () => { + mainWindow?.show(); + createWinThumbarButtons(); + }, + label: 'Open main window', + }, + { + click: () => { + exitFromTray = true; + app.quit(); + }, + label: 'Quit', + }, + ]); + + tray.on('double-click', () => { + mainWindow?.show(); + createWinThumbarButtons(); + }); + + tray.setToolTip('Feishin'); + tray.setContextMenu(contextMenu); +}; + const createWindow = async () => { if (isDevelopment) { await installExtensions(); } - const RESOURCES_PATH = app.isPackaged - ? path.join(process.resourcesPath, 'assets') - : path.join(__dirname, '../../assets'); - - const getAssetPath = (...paths: string[]): string => { - return path.join(RESOURCES_PATH, ...paths); - }; - mainWindow = new BrowserWindow({ frame: false, height: 900, @@ -153,14 +251,41 @@ const createWindow = async () => { mainWindow.minimize(); } else { mainWindow.show(); + createWinThumbarButtons(); } }); mainWindow.on('closed', () => { - // mainWindow?.webContents.send('renderer-player-quit'); mainWindow = null; }); + mainWindow.on('close', (event) => { + if (!exitFromTray && store.get('window_exit_to_tray')) { + if (isMacOS() && !forceQuit) { + exitFromTray = true; + } + event.preventDefault(); + mainWindow?.hide(); + } + }); + + mainWindow.on('minimize', (event: any) => { + if (store.get('window_minimize_to_tray') === true) { + event.preventDefault(); + mainWindow?.hide(); + } + }); + + if (isWindows()) { + app.setAppUserModelId(process.execPath); + } + + if (isMacOS()) { + app.on('before-quit', () => { + forceQuit = true; + }); + } + const menuBuilder = new MenuBuilder(mainWindow); menuBuilder.buildMenu(); @@ -175,16 +300,8 @@ const createWindow = async () => { new AppUpdater(); }; -/** - * Add event listeners... - */ - app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling,MediaSessionService'); -export const getMainWindow = () => { - return mainWindow; -}; - const MPV_BINARY_PATH = store.get('mpv_path') as string | undefined; const MPV_PARAMETERS = store.get('mpv_parameters') as Array | undefined; @@ -267,11 +384,10 @@ app.on('window-all-closed', () => { // Respect the OSX convention of having the application in memory even // after all windows have been closed - if (process.platform !== 'darwin') { - app.quit(); - } else { - mpv.stop(); + if (isMacOS()) { mainWindow = null; + } else { + app.quit(); } }); @@ -279,6 +395,7 @@ app .whenReady() .then(() => { createWindow(); + createTray(); app.on('activate', () => { // On macOS it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. diff --git a/src/renderer/features/settings/components/settings-content.tsx b/src/renderer/features/settings/components/settings-content.tsx index f61ac783..1000012a 100644 --- a/src/renderer/features/settings/components/settings-content.tsx +++ b/src/renderer/features/settings/components/settings-content.tsx @@ -1,6 +1,7 @@ import { lazy } from 'react'; import { Tabs } from '/@/renderer/components'; import { useSettingsStore, useSettingsStoreActions } from '/@/renderer/store/settings.store'; +import isElectron from 'is-electron'; import styled from 'styled-components'; const GeneralTab = lazy(() => @@ -44,7 +45,7 @@ export const SettingsContent = () => { General Playback - Window + {isElectron() && Window} @@ -52,9 +53,11 @@ export const SettingsContent = () => { - - - + {isElectron() && ( + + + + )} ); diff --git a/src/renderer/features/settings/components/window/window-settings.tsx b/src/renderer/features/settings/components/window/window-settings.tsx index a5ea7d8e..620eb9e1 100644 --- a/src/renderer/features/settings/components/window/window-settings.tsx +++ b/src/renderer/features/settings/components/window/window-settings.tsx @@ -5,7 +5,7 @@ import { SettingsSection, SettingOption, } from '/@/renderer/features/settings/components/settings-section'; -import { Select } from '/@/renderer/components'; +import { Select, Switch } from '/@/renderer/components'; const WINDOW_BAR_OPTIONS = [ { label: 'Web (hidden)', value: Platform.WEB }, @@ -13,6 +13,8 @@ const WINDOW_BAR_OPTIONS = [ { label: 'macOS', value: Platform.MACOS }, ]; +const localSettings = isElectron() ? window.electron.localSettings : null; + export const WindowSettings = () => { const settings = useWindowSettings(); const { setSettings } = useSettingsStoreActions(); @@ -39,6 +41,50 @@ export const WindowSettings = () => { isHidden: !isElectron(), title: 'Window bar style', }, + { + control: ( + { + if (!e) return; + localSettings?.set('window_minimize_to_tray', e.currentTarget.checked); + setSettings({ + window: { + ...settings, + minimizeToTray: e.currentTarget.checked, + }, + }); + }} + /> + ), + description: 'Minimize the application to the system tray', + isHidden: !isElectron(), + title: 'Minimize to tray', + }, + { + control: ( + { + if (!e) return; + localSettings?.set('window_exit_to_tray', e.currentTarget.checked); + setSettings({ + window: { + ...settings, + exitToTray: e.currentTarget.checked, + }, + }); + }} + /> + ), + description: 'Exit the application to the system tray', + isHidden: !isElectron(), + title: 'Exit to tray', + }, ]; return ; diff --git a/src/renderer/store/settings.store.ts b/src/renderer/store/settings.store.ts index cafd9a02..aa19a448 100644 --- a/src/renderer/store/settings.store.ts +++ b/src/renderer/store/settings.store.ts @@ -71,6 +71,8 @@ export interface SettingsState { songs: DataTableProps; }; window: { + exitToTray: boolean; + minimizeToTray: boolean; windowBarStyle: Platform; }; } @@ -243,6 +245,8 @@ export const useSettingsStore = create()( }, }, window: { + exitToTray: false, + minimizeToTray: false, windowBarStyle: Platform.WEB, }, })),