feat: Add option to start app minimized to tray

This commit is contained in:
2026-06-16 00:20:30 +07:00
parent efc810c2e2
commit 4a73901d0c
3 changed files with 116 additions and 12 deletions
+87 -11
View File
@@ -11,24 +11,73 @@ function settingsPath(): string {
return join(app.getPath('userData'), 'settings.json')
}
function loadDisabledMirrors(): number[] {
interface Settings {
disabledMirrors: number[]
startMinimized: boolean
}
function loadSettings(): Settings {
try {
const raw = fs.readFileSync(settingsPath(), 'utf8')
const data = JSON.parse(raw)
return Array.isArray(data?.disabledMirrors) ? data.disabledMirrors : []
return {
disabledMirrors: Array.isArray(data?.disabledMirrors) ? data.disabledMirrors : [],
startMinimized: data?.startMinimized === true
}
} catch {
return []
return { disabledMirrors: [], startMinimized: false }
}
}
function saveDisabledMirrors(indices: number[]): void {
fs.writeFileSync(settingsPath(), JSON.stringify({ disabledMirrors: indices }), 'utf8')
function saveSettings(settings: Settings): void {
fs.writeFileSync(settingsPath(), JSON.stringify(settings), 'utf8')
}
const LINUX_AUTOSTART_DESKTOP_FILE = join(
app.getPath('home'),
'.config',
'autostart',
'cafe.kirameki.discord-rpc-relay.desktop'
)
function getLinuxAutostart(): boolean {
return fs.existsSync(LINUX_AUTOSTART_DESKTOP_FILE)
}
function setLinuxAutostart(enabled: boolean, startMinimized: boolean): boolean {
if (!enabled) {
fs.rmSync(LINUX_AUTOSTART_DESKTOP_FILE, { force: true })
return false
}
const exe = process.env.APPIMAGE ?? app.getPath('exe')
const exec = startMinimized ? `"${exe}" --hidden` : `"${exe}"`
const desktopEntry = [
'[Desktop Entry]',
'Type=Application',
'Name=Discord RPC Relay',
`Exec=${exec}`,
'Terminal=false',
'X-GNOME-Autostart-enabled=true'
].join('\n')
fs.mkdirSync(join(app.getPath('home'), '.config', 'autostart'), { recursive: true })
fs.writeFileSync(LINUX_AUTOSTART_DESKTOP_FILE, desktopEntry + '\n', 'utf8')
return true
}
let mainWindow: BrowserWindow | null = null
let tray: Tray | null = null
let quitting = false
function shouldStartHidden(): boolean {
if (process.argv.includes('--hidden')) return true
if (process.platform === 'darwin') {
return app.getLoginItemSettings().wasOpenedAtLogin && loadSettings().startMinimized
}
return false
}
function createWindow(): void {
if (mainWindow) {
mainWindow.show()
@@ -50,7 +99,9 @@ function createWindow(): void {
})
mainWindow.on('ready-to-show', () => {
mainWindow?.show()
if (!shouldStartHidden()) {
mainWindow?.show()
}
})
mainWindow.on('close', (e) => {
@@ -135,7 +186,8 @@ app.whenReady().then(() => {
optimizer.watchWindowShortcuts(window)
})
relay.setDisabledMirrors(loadDisabledMirrors())
const settings = loadSettings()
relay.setDisabledMirrors(settings.disabledMirrors)
ipcMain.handle('relay:get-version', () => ({
version: app.getVersion(),
@@ -144,14 +196,38 @@ app.whenReady().then(() => {
ipcMain.handle('relay:get-status', () => relay.getStatus())
ipcMain.handle('relay:start', () => relay.start())
ipcMain.handle('relay:stop', () => relay.stop())
ipcMain.handle('relay:get-autostart', () => app.getLoginItemSettings().openAtLogin)
ipcMain.handle('relay:set-autostart', (_e, enabled: boolean) => {
app.setLoginItemSettings({ openAtLogin: enabled })
ipcMain.handle('relay:get-autostart', () => {
if (process.platform === 'linux') return getLinuxAutostart()
return app.getLoginItemSettings().openAtLogin
})
ipcMain.handle('relay:set-autostart', (_e, enabled: boolean) => {
const current = loadSettings()
if (process.platform === 'linux') {
return setLinuxAutostart(enabled, current.startMinimized)
}
app.setLoginItemSettings({
openAtLogin: enabled,
args: current.startMinimized ? ['--hidden'] : []
})
return app.getLoginItemSettings().openAtLogin
})
ipcMain.handle('relay:get-start-minimized', () => loadSettings().startMinimized)
ipcMain.handle('relay:set-start-minimized', (_e, enabled: boolean) => {
const current = loadSettings()
saveSettings({ ...current, startMinimized: enabled })
if (process.platform === 'linux') {
if (getLinuxAutostart()) setLinuxAutostart(true, enabled)
} else if (app.getLoginItemSettings().openAtLogin) {
app.setLoginItemSettings({ openAtLogin: true, args: enabled ? ['--hidden'] : [] })
}
return enabled
})
ipcMain.handle('relay:set-mirror-enabled', (_e, index: number, enabled: boolean) => {
const status = relay.setMirrorEnabled(index, enabled)
saveDisabledMirrors(relay.getDisabledMirrors())
const current = loadSettings()
saveSettings({ ...current, disabledMirrors: relay.getDisabledMirrors() })
return status
})
+3
View File
@@ -11,6 +11,9 @@ const api = {
getAutostart: (): Promise<boolean> => ipcRenderer.invoke('relay:get-autostart'),
setAutostart: (enabled: boolean): Promise<boolean> =>
ipcRenderer.invoke('relay:set-autostart', enabled),
getStartMinimized: (): Promise<boolean> => ipcRenderer.invoke('relay:get-start-minimized'),
setStartMinimized: (enabled: boolean): Promise<boolean> =>
ipcRenderer.invoke('relay:set-start-minimized', enabled),
setMirrorEnabled: (index: number, enabled: boolean): Promise<RelayStatus> =>
ipcRenderer.invoke('relay:set-mirror-enabled', index, enabled),
onStatus: (callback: (status: RelayStatus) => void): (() => void) => {
+26 -1
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'
import { ChevronLeft, Settings } from 'lucide-react'
import { ChevronLeft, Info, Settings } from 'lucide-react'
import type { RelayStatus } from '../../main/relay'
import { InstanceRow } from './components/InstanceRow'
import { Toggle } from './components/Toggle'
@@ -102,6 +102,7 @@ export function App(): React.JSX.Element {
const [status, setStatus] = useState<RelayStatus>(EMPTY_STATUS)
const [loading, setLoading] = useState(false)
const [autostart, setAutostart] = useState(false)
const [startMinimized, setStartMinimized] = useState(false)
const [view, setView] = useState<'main' | 'settings'>('main')
const [appVersion, setAppVersion] = useState<{ version: string; commit: string } | null>(null)
@@ -109,6 +110,7 @@ export function App(): React.JSX.Element {
void (async () => {
setStatus(await window.api.getStatus())
setAutostart(await window.api.getAutostart())
setStartMinimized(await window.api.getStartMinimized())
setAppVersion(await window.api.getVersion())
})()
@@ -138,6 +140,10 @@ export function App(): React.JSX.Element {
setAutostart(await window.api.setAutostart(enabled))
}
const onToggleStartMinimized = async (enabled: boolean): Promise<void> => {
setStartMinimized(await window.api.setStartMinimized(enabled))
}
if (status.unsupported) {
return (
<div className="flex flex-col h-screen items-center justify-center p-5 gap-3 text-center">
@@ -170,6 +176,25 @@ export function App(): React.JSX.Element {
<Toggle checked={autostart} onChange={onToggleAutostart} />
</section>
<section className="rounded-xl bg-zinc-800/60 border border-zinc-700 p-4 flex items-center justify-between">
<div className="flex flex-col">
<span className="text-sm">Start minimized</span>
<span className="text-xs text-zinc-500">Open to the tray instead of a window</span>
</div>
<div className="flex items-center gap-2">
{!autostart && (
<Info className="w-4 h-4 text-zinc-400 hover:text-zinc-100 cursor-help transition-colors shrink-0">
<title>Only applies when &quot;Start on login&quot; is enabled.</title>
</Info>
)}
<Toggle
checked={startMinimized}
onChange={onToggleStartMinimized}
disabled={!autostart}
/>
</div>
</section>
{appVersion && (
<p className="text-xs text-zinc-500 mt-auto">
Version {appVersion.version} ({appVersion.commit})