From e6e1e73bdc43d6a5d59e77d4de526191fd8a4fb7 Mon Sep 17 00:00:00 2001 From: Samuel Elliott Date: Sat, 12 Nov 2022 17:22:48 +0000 Subject: [PATCH] Add open hidden at login option for Windows and Linux https://github.com/samuelthomas2774/nxapi/issues/19 --- src/app/browser/preferences/index.tsx | 25 ++++---- src/app/common/types.ts | 7 +++ src/app/main/index.ts | 91 +++++++++++++++++++++++++-- src/app/main/ipc.ts | 8 +-- src/app/preload/index.ts | 8 +-- 5 files changed, 115 insertions(+), 24 deletions(-) diff --git a/src/app/browser/preferences/index.tsx b/src/app/browser/preferences/index.tsx index 70769d6..25c1c4b 100644 --- a/src/app/browser/preferences/index.tsx +++ b/src/app/browser/preferences/index.tsx @@ -22,11 +22,11 @@ export default function Preferences(props: PreferencesProps) { const [login_item, ,, forceRefreshLoginItem] = useAsync(useCallback(() => ipc.getLoginItemSettings(), [ipc])); const setOpenAtLogin = useCallback(async (open_at_login: boolean | 'mixed') => { - await ipc.setLoginItemSettings({...login_item, openAtLogin: !!open_at_login}); + await ipc.setLoginItemSettings({...login_item!, startup_enabled: !!open_at_login}); forceRefreshLoginItem(); }, [ipc, login_item]); const setOpenAsHidden = useCallback(async (open_as_hidden: boolean | 'mixed') => { - await ipc.setLoginItemSettings({...login_item, openAsHidden: !!open_as_hidden}); + await ipc.setLoginItemSettings({...login_item!, startup_hidden: !!open_as_hidden}); forceRefreshLoginItem(); }, [ipc, login_item]); @@ -114,7 +114,7 @@ export default function Preferences(props: PreferencesProps) { {/* Preferences */} - {ipc.platform === 'darwin' || ipc.platform === 'win32' ? + {login_item.supported || login_item.startup_enabled ? Startup @@ -124,32 +124,33 @@ export default function Preferences(props: PreferencesProps) { - setOpenAtLogin(!login_item.openAtLogin)}> + setOpenAtLogin(!login_item.startup_enabled)}> Open at login - {ipc.platform === 'darwin' ? - setOpenAsHidden(!login_item.openAsHidden)} + setOpenAsHidden(!login_item.startup_hidden)} > Open in background - : null} + : null} diff --git a/src/app/common/types.ts b/src/app/common/types.ts index 630a4ec..8902bac 100644 --- a/src/app/common/types.ts +++ b/src/app/common/types.ts @@ -47,3 +47,10 @@ export interface DiscordPresenceSourceUrl { export interface DiscordPresenceExternalMonitorsConfiguration { enable_splatnet3_monitoring?: boolean; } + +export interface LoginItem { + supported: boolean; + startup_enabled: boolean; + startup_hidden: boolean; +} +export type LoginItemOptions = Omit; diff --git a/src/app/main/index.ts b/src/app/main/index.ts index fb9aff6..aab909f 100644 --- a/src/app/main/index.ts +++ b/src/app/main/index.ts @@ -9,7 +9,7 @@ import MenuApp from './menu.js'; import { handleOpenWebServiceUri } from './webservices.js'; import { EmbeddedPresenceMonitor, PresenceMonitorManager } from './monitor.js'; import { createWindow } from './windows.js'; -import { DiscordPresenceConfiguration, WindowType } from '../common/types.js'; +import { DiscordPresenceConfiguration, LoginItem, LoginItemOptions, WindowType } from '../common/types.js'; import { initStorage, paths } from '../../util/storage.js'; import { checkUpdates, UpdateCacheData } from '../../common/update.js'; import Users, { CoralUser } from '../../common/users.js'; @@ -29,10 +29,28 @@ export const protocol_registration_options = dev && process.platform === 'win32' path.join(dir, 'dist', 'app', 'app-entry.cjs'), ], } : null; -export const login_item_options: LoginItemSettingsOptions = {}; +export const login_item_options: LoginItemSettingsOptions = { + path: process.execPath, + args: dev ? [ + path.join(dir, 'dist', 'app', 'app-entry.cjs'), + '--app-open-at-login=1', + ] : [ + '--app-open-at-login=1', + ], +}; + +enum LoginItemType { + NATIVE, + NATIVE_PARTIAL, + NOT_SUPPORTED, +} +const login_item_type: LoginItemType = + process.platform === 'darwin' ? LoginItemType.NATIVE : + process.platform === 'win32' ? LoginItemType.NATIVE_PARTIAL : + LoginItemType.NOT_SUPPORTED; debug('Protocol registration options', protocol_registration_options); -debug('Login item registration options', login_item_options); +debug('Login item registration options', LoginItemType[login_item_type], login_item_options); export class App { readonly store: Store; @@ -184,7 +202,11 @@ export async function init() { debug('App started'); - if (!app.getLoginItemSettings(login_item_options).wasOpenedAsHidden) { + const should_hide = + login_item_type === LoginItemType.NATIVE ? app.getLoginItemSettings(login_item_options).wasOpenedAsHidden : + process.argv.includes('--app-open-at-login=1') && (await appinstance.store.getLoginItem()).startup_hidden; + + if (!should_hide) { appinstance.showMainWindow(); } } @@ -254,6 +276,10 @@ class Updater { } } +interface SavedStartupOptions { + hide: boolean; +} + interface SavedMonitorState { users: { /** Nintendo Account ID */ @@ -277,6 +303,63 @@ export class Store extends EventEmitter { this.users = Users.coral(this, process.env.ZNC_PROXY_URL, false); } + async getLoginItem(): Promise { + const settings = app.getLoginItemSettings(login_item_options); + + if (login_item_type === LoginItemType.NATIVE) { + // Fully supported + return { + supported: true, + startup_enabled: settings.openAtLogin, + startup_hidden: settings.openAsHidden, + }; + } + + const startup_options: SavedStartupOptions | undefined = await this.storage.getItem('StartupOptions'); + const was_opened_at_login = process.argv.includes('--app-open-at-login=1'); + + if (login_item_type === LoginItemType.NATIVE_PARTIAL) { + // Partial native support + return { + supported: true, + startup_enabled: settings.openAtLogin, + startup_hidden: startup_options?.hide ?? false, + }; + } + + return { + supported: false, + startup_enabled: was_opened_at_login, + startup_hidden: startup_options?.hide ?? false, + }; + } + + async setLoginItem(settings: LoginItemOptions) { + if (login_item_type === LoginItemType.NATIVE) { + // Fully supported + app.setLoginItemSettings({ + ...login_item_options, + openAtLogin: settings.startup_enabled, + openAsHidden: settings.startup_hidden, + }); + return; + } + + if (login_item_type === LoginItemType.NATIVE_PARTIAL) { + // Partial native support + app.setLoginItemSettings({ + ...login_item_options, + openAtLogin: settings.startup_enabled, + }); + } + + const startup_options: SavedStartupOptions = { + hide: settings.startup_hidden, + }; + + await this.storage.setItem('StartupOptions', startup_options); + } + async saveMonitorState(monitors: PresenceMonitorManager) { const users = new Set(); const state: SavedMonitorState = { diff --git a/src/app/main/ipc.ts b/src/app/main/ipc.ts index a5f2b76..8fd0dd9 100644 --- a/src/app/main/ipc.ts +++ b/src/app/main/ipc.ts @@ -1,10 +1,10 @@ -import { app, BrowserWindow, clipboard, dialog, IpcMain, KeyboardEvent, LoginItemSettings, Menu, MenuItem, Settings, ShareMenu, SharingItem, shell, systemPreferences } from './electron.js'; +import { app, BrowserWindow, clipboard, dialog, IpcMain, KeyboardEvent, Menu, MenuItem, Settings, ShareMenu, SharingItem, shell, systemPreferences } from './electron.js'; import * as util from 'node:util'; import createDebug from 'debug'; import { User } from 'discord-rpc'; import openWebService, { QrCodeReaderOptions, WebServiceIpc, WebServiceValidationError } from './webservices.js'; import { createWindow, getWindowConfiguration } from './windows.js'; -import { DiscordPresenceConfiguration, DiscordPresenceSource, WindowType } from '../common/types.js'; +import { DiscordPresenceConfiguration, DiscordPresenceSource, LoginItemOptions, WindowType } from '../common/types.js'; import { CurrentUser, Friend, Game, PresenceState, WebService } from '../../api/coral-types.js'; import { askAddNsoAccount, askAddPctlAccount } from './na-auth.js'; import { App, login_item_options } from './index.js'; @@ -40,8 +40,8 @@ export function setupIpc(appinstance: App, ipcMain: IpcMain) { sendToAllWindows('nxapi:systemPreferences:accent-colour', accent_colour); }); - ipcMain.handle('nxapi:systemPreferences:getloginitem', () => app.getLoginItemSettings(login_item_options)); - ipcMain.handle('nxapi:systemPreferences:setloginitem', (e, settings: Settings) => app.setLoginItemSettings({...login_item_options, ...settings})); + ipcMain.handle('nxapi:systemPreferences:getloginitem', () => appinstance.store.getLoginItem()); + ipcMain.handle('nxapi:systemPreferences:setloginitem', (e, settings: LoginItemOptions) => appinstance.store.setLoginItem(settings)); ipcMain.handle('nxapi:update:get', () => appinstance.updater.cache ?? appinstance.updater.check()); ipcMain.handle('nxapi:update:check', () => appinstance.updater.check()); diff --git a/src/app/preload/index.ts b/src/app/preload/index.ts index b475989..f304063 100644 --- a/src/app/preload/index.ts +++ b/src/app/preload/index.ts @@ -2,8 +2,8 @@ import { contextBridge, ipcRenderer } from 'electron'; import { EventEmitter } from 'events'; import createDebug from 'debug'; import type { User } from 'discord-rpc'; -import type { LoginItemSettings, Settings, SharingItem } from '../main/electron.js'; -import type { DiscordPresenceConfiguration, DiscordPresenceSource, WindowConfiguration } from '../common/types.js'; +import type { SharingItem } from '../main/electron.js'; +import type { DiscordPresenceConfiguration, DiscordPresenceSource, LoginItem, LoginItemOptions, WindowConfiguration } from '../common/types.js'; import type { SavedToken } from '../../common/auth/coral.js'; import type { SavedMoonToken } from '../../common/auth/moon.js'; import type { UpdateCacheData } from '../../common/update.js'; @@ -30,8 +30,8 @@ events.setMaxListeners(0); const ipc = { getWindowData: () => invSync('browser:getwindowdata'), - getLoginItemSettings: () => inv('systemPreferences:getloginitem'), - setLoginItemSettings: (settings: Settings) => inv('systemPreferences:setloginitem', settings), + getLoginItemSettings: () => inv('systemPreferences:getloginitem'), + setLoginItemSettings: (settings: LoginItemOptions) => inv('systemPreferences:setloginitem', settings), getUpdateData: () => inv('update:get'), checkUpdates: () => inv('update:check'),