// SPDX-License-Identifier: AGPL-3.0-or-later import {ConfirmModal} from '@app/features/app/components/dialogs/ConfirmModal'; import {SettingsSection} from '@app/features/app/components/dialogs/shared/SettingsSection'; import {SettingsTabContainer} from '@app/features/app/components/dialogs/shared/SettingsTabLayout'; import RuntimeConfig from '@app/features/app/state/RuntimeConfig'; import {MacPermissionsSettingsRow} from '@app/features/permissions/system/components/MacPermissionsSettingsRow'; import { getAutostartStatus, getCachedAutostartStatus, setAutostartEnabled, } from '@app/features/platform/utils/Autostart'; import * as ModalCommands from '@app/features/ui/commands/ModalCommands'; import {modal} from '@app/features/ui/commands/ModalCommands'; import {Switch} from '@app/features/ui/components/form/FormSwitch'; import { getCachedDesktopWindowBehaviorSettings, getDesktopWindowBehaviorPendingRestart, getDesktopWindowBehaviorSettings, relaunchDesktopApp, setDesktopWindowBehaviorSettings, } from '@app/features/ui/utils/DesktopWindowBehaviorUtils'; import {getNativePlatformSync, isDesktop, isNativeLinux, isNativeMacOS} from '@app/features/ui/utils/NativeUtils'; import { LATER_DESCRIPTOR, LAUNCH_AT_LOGIN_DESCRIPTOR as LAUNCH_PRODUCT_AT_LOGIN_DESCRIPTOR, RESTART_DESCRIPTOR, RESTART_NOW_DESCRIPTOR, } from '@app/features/user/utils/UserMessageDescriptors'; import type {DesktopWindowBehaviorSettings} from '@app/types/electron.d'; import {msg} from '@lingui/core/macro'; import {Trans, useLingui} from '@lingui/react/macro'; import {observer} from 'mobx-react-lite'; import type React from 'react'; import {useLayoutEffect, useState} from 'react'; const RESTART_PRODUCT_FOR_TRAY_CHANGE_DESCRIPTOR = msg({ message: "Restart {productName} for this change to take effect because Linux desktops can't reliably add or remove tray icons at runtime.", comment: 'Desktop setting description shown when a tray-icon change on Linux requires restart.', }); const USE_COLORED_TRAY_ICON_DESCRIPTOR = msg({ message: 'Use colored tray icon', comment: 'Desktop setting toggle label on Windows and Linux. Switches the system tray icon from monochrome to the full-color app icon.', }); const USE_COLORED_TRAY_ICON_DESCRIPTION_DESCRIPTOR = msg({ message: 'Show the full-color {productName} icon instead of a monochrome one that matches your taskbar.', comment: 'Desktop setting description under the colored tray icon toggle on Windows and Linux.', }); const DesktopSettingsTab: React.FC = observer(() => { const {i18n} = useLingui(); const cachedAutostart = getCachedAutostartStatus(); const cachedWindowBehavior = getCachedDesktopWindowBehaviorSettings(); const [autostartEnabled, setAutostartEnabledState] = useState(cachedAutostart ?? false); const [autostartBusy, setAutostartBusy] = useState(cachedAutostart === null); const [desktopWindowBehavior, setDesktopWindowBehaviorState] = useState( cachedWindowBehavior, ); const [desktopWindowBehaviorBusy, setDesktopWindowBehaviorBusy] = useState(cachedWindowBehavior === null); const [trayChangePendingRestart, setTrayChangePendingRestart] = useState(false); const platform = getNativePlatformSync(); const isMac = isNativeMacOS(platform); const isLinux = isNativeLinux(platform); useLayoutEffect(() => { let mounted = true; const initDesktopSettings = async () => { if (!isDesktop()) { if (mounted) { setAutostartBusy(false); setDesktopWindowBehaviorBusy(false); } return; } const needsAutostart = getCachedAutostartStatus() === null; const needsWindowBehavior = getCachedDesktopWindowBehaviorSettings() === null; if (!needsAutostart && !needsWindowBehavior) return; const [enabled, windowBehavior] = await Promise.all([ needsAutostart ? getAutostartStatus() : Promise.resolve(getCachedAutostartStatus()), needsWindowBehavior ? getDesktopWindowBehaviorSettings() : Promise.resolve(getCachedDesktopWindowBehaviorSettings()), ]); if (!mounted) return; if (enabled !== null) { setAutostartEnabledState(enabled); } if (windowBehavior !== null) { setDesktopWindowBehaviorState(windowBehavior); } setAutostartBusy(false); setDesktopWindowBehaviorBusy(false); }; void initDesktopSettings(); void getDesktopWindowBehaviorPendingRestart().then((pending) => { if (mounted) setTrayChangePendingRestart(pending); }); return () => { mounted = false; }; }, []); const handleAutostartChange = async (value: boolean) => { setAutostartBusy(true); const nextState = await setAutostartEnabled(value); if (nextState !== null) { setAutostartEnabledState(nextState); } setAutostartBusy(false); }; const handleDesktopWindowBehaviorChange = async (settings: Partial) => { setDesktopWindowBehaviorBusy(true); const nextSettings = await setDesktopWindowBehaviorSettings(settings); if (nextSettings !== null) { setDesktopWindowBehaviorState(nextSettings); } const pending = await getDesktopWindowBehaviorPendingRestart(); setTrayChangePendingRestart(pending); setDesktopWindowBehaviorBusy(false); return {pending}; }; const handleShowTrayIconChange = (value: boolean) => { void handleDesktopWindowBehaviorChange({showTrayIcon: value}).then(({pending}) => { if (!pending || !isNativeLinux()) return; ModalCommands.push( modal(() => ( Tray icon changes only apply after a restart on Linux because the system tray protocol doesn't reliably let apps add or remove icons at runtime. Restart now to apply your change. } primaryText={i18n._(RESTART_NOW_DESCRIPTOR)} primaryVariant="primary" secondaryText={i18n._(LATER_DESCRIPTOR)} onPrimary={async () => { await relaunchDesktopApp(); }} data-flx="user.desktop-settings-tab.handle-show-tray-icon-change.confirm-modal" /> )), ); }); }; return ( Desktop window} data-flx="user.desktop-settings-tab.settings-tab-section" > {isDesktop() && ( )} {isDesktop() && desktopWindowBehavior?.startMinimized !== undefined && ( Start minimized} description={ isMac ? ( When launched at login, start in the menu bar instead of opening the window ) : ( When launched at login, start in the system tray instead of opening the window ) } value={desktopWindowBehavior.startMinimized} disabled={desktopWindowBehaviorBusy || !autostartEnabled || !desktopWindowBehavior.showTrayIcon} onChange={(value) => handleDesktopWindowBehaviorChange({startMinimized: value})} data-flx="user.desktop-settings-tab.switch.start-minimized-change" /> )} {isDesktop() && desktopWindowBehavior && ( <> Remember window size and position} value={desktopWindowBehavior.rememberWindowState} disabled={desktopWindowBehaviorBusy} onChange={(value) => handleDesktopWindowBehaviorChange({rememberWindowState: value})} data-flx="user.desktop-settings-tab.switch.desktop-window-behavior-change" /> Show menu bar icon : Show system tray icon} description={ trayChangePendingRestart && isLinux ? i18n._(RESTART_PRODUCT_FOR_TRAY_CHANGE_DESCRIPTOR, {productName: RuntimeConfig.productName}) : undefined } value={desktopWindowBehavior.showTrayIcon} disabled={desktopWindowBehaviorBusy} onChange={handleShowTrayIconChange} data-flx="user.desktop-settings-tab.switch.show-tray-icon-change" /> {!isMac && desktopWindowBehavior.coloredTrayIcon !== undefined && ( handleDesktopWindowBehaviorChange({coloredTrayIcon: value})} data-flx="user.desktop-settings-tab.switch.colored-tray-icon-change" /> )} Minimize to menu bar : Minimize to tray} value={desktopWindowBehavior.minimizeToTray} disabled={desktopWindowBehaviorBusy || !desktopWindowBehavior.showTrayIcon} onChange={(value) => handleDesktopWindowBehaviorChange({minimizeToTray: value})} data-flx="user.desktop-settings-tab.switch.desktop-window-behavior-change--2" /> Close to menu bar : Close to tray} value={desktopWindowBehavior.closeToTray} disabled={desktopWindowBehaviorBusy || !desktopWindowBehavior.showTrayIcon} onChange={(value) => handleDesktopWindowBehaviorChange({closeToTray: value})} data-flx="user.desktop-settings-tab.switch.desktop-window-behavior-change--3" /> )} {isMac && ( macOS permissions} data-flx="user.desktop-settings-tab.macos-permissions-section" > )} ); }); export default DesktopSettingsTab;