From 858a5ceaec556a8f4c7e954801b18eba34f24f79 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Fri, 23 Jan 2026 13:52:09 +0800 Subject: [PATCH] feat: add Linux system title bar setting option (#12040) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add Linux system title bar setting option Add a setting in Display Settings that allows Linux users to switch between custom title bar (frameless window with WindowControls) and native system title bar. The setting requires app restart to take effect since Electron's frame option cannot be changed at runtime. - Add useSystemTitleBar config key and getter/setter in ConfigManager - Add IPC channel and handler for setting persistence - Add Redux state and action for useSystemTitleBar - Add UI toggle in DisplaySettings (Linux only) - Hide WindowControls when system title bar is enabled on Linux - Modify WindowService to check setting when creating window on Linux - Add translations for all supported locales 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: improve window controls positioning in left navbar layout Move WindowControls inside NavbarContainer as a flex item instead of using fixed positioning. This ensures the window controls are properly positioned adjacent to the navbar content when using the left navbar layout. - Remove position: fixed from WindowControlsContainer styling - Include WindowControls as last child of NavbarContainer - Remove extra padding-right from NavbarRight since controls are now in the flex layout 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: remove padding-right from TabsBar to align window controls to edge Remove the 8px padding-right for Windows/Linux in TabsBar that was causing window controls to not be flush against the window edge. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: add right padding for settings button when Linux uses system title bar * fix: restore NavbarRight paddingRight to 15px for consistent spacing Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: Claude --- packages/shared/IpcChannel.ts | 1 + src/main/ipc.ts | 3 ++ src/main/services/ConfigManager.ts | 9 +++++ src/main/services/WindowService.ts | 3 +- src/preload/index.ts | 1 + .../src/components/Tab/TabContainer.tsx | 6 ++- .../WindowControls/WindowControls.styled.ts | 3 -- .../src/components/WindowControls/index.tsx | 7 ++++ src/renderer/src/components/app/Navbar.tsx | 10 ++--- src/renderer/src/hooks/useSettings.ts | 5 +++ src/renderer/src/i18n/locales/en-us.json | 7 ++++ src/renderer/src/i18n/locales/zh-cn.json | 7 ++++ src/renderer/src/i18n/locales/zh-tw.json | 7 ++++ src/renderer/src/i18n/translate/de-de.json | 7 ++++ src/renderer/src/i18n/translate/el-gr.json | 7 ++++ src/renderer/src/i18n/translate/es-es.json | 7 ++++ src/renderer/src/i18n/translate/fr-fr.json | 7 ++++ src/renderer/src/i18n/translate/ja-jp.json | 7 ++++ src/renderer/src/i18n/translate/pt-pt.json | 7 ++++ src/renderer/src/i18n/translate/ru-ru.json | 7 ++++ src/renderer/src/pages/home/Navbar.tsx | 3 +- .../DisplaySettings/DisplaySettings.tsx | 37 ++++++++++++++++++- src/renderer/src/store/settings.ts | 8 ++++ 23 files changed, 150 insertions(+), 16 deletions(-) diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index 8361a917e5..8504c47005 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -48,6 +48,7 @@ export enum IpcChannel { App_QuoteToMain = 'app:quote-to-main', App_SetDisableHardwareAcceleration = 'app:set-disable-hardware-acceleration', + App_SetUseSystemTitleBar = 'app:set-use-system-title-bar', Notification_Send = 'notification:send', Notification_OnClick = 'notification:on-click', diff --git a/src/main/ipc.ts b/src/main/ipc.ts index ccaa664ab8..4d32ada513 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -900,6 +900,9 @@ export async function registerIpc(mainWindow: BrowserWindow, app: Electron.App) ipcMain.handle(IpcChannel.App_SetDisableHardwareAcceleration, (_, isDisable: boolean) => { configManager.setDisableHardwareAcceleration(isDisable) }) + ipcMain.handle(IpcChannel.App_SetUseSystemTitleBar, (_, isActive: boolean) => { + configManager.setUseSystemTitleBar(isActive) + }) ipcMain.handle(IpcChannel.TRACE_SAVE_DATA, (_, topicId: string) => saveSpans(topicId)) ipcMain.handle(IpcChannel.TRACE_GET_DATA, (_, topicId: string, traceId: string, modelName?: string) => getSpans(topicId, traceId, modelName) diff --git a/src/main/services/ConfigManager.ts b/src/main/services/ConfigManager.ts index 98537c85a1..b1eafb31b2 100644 --- a/src/main/services/ConfigManager.ts +++ b/src/main/services/ConfigManager.ts @@ -45,6 +45,7 @@ export enum ConfigKeys { SelectionAssistantFilterMode = 'selectionAssistantFilterMode', SelectionAssistantFilterList = 'selectionAssistantFilterList', DisableHardwareAcceleration = 'disableHardwareAcceleration', + UseSystemTitleBar = 'useSystemTitleBar', Proxy = 'proxy', EnableDeveloperMode = 'enableDeveloperMode', ClientId = 'clientId', @@ -251,6 +252,14 @@ export class ConfigManager { this.set(ConfigKeys.DisableHardwareAcceleration, value) } + getUseSystemTitleBar(): boolean { + return this.get(ConfigKeys.UseSystemTitleBar, false) + } + + setUseSystemTitleBar(value: boolean) { + this.set(ConfigKeys.UseSystemTitleBar, value) + } + setAndNotify(key: string, value: unknown) { this.set(key, value, true) } diff --git a/src/main/services/WindowService.ts b/src/main/services/WindowService.ts index c76753156d..ae8bc71d8a 100644 --- a/src/main/services/WindowService.ts +++ b/src/main/services/WindowService.ts @@ -75,7 +75,8 @@ export class WindowService { trafficLightPosition: { x: 8, y: 13 } } : { - frame: false // Frameless window for Windows and Linux + // On Linux, allow using system title bar if setting is enabled + frame: isLinux && configManager.getUseSystemTitleBar() ? true : false }), backgroundColor: isMac ? undefined : nativeTheme.shouldUseDarkColors ? '#181818' : '#FFFFFF', darkTheme: nativeTheme.shouldUseDarkColors, diff --git a/src/preload/index.ts b/src/preload/index.ts index cb8b0f6919..f2a892142e 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -503,6 +503,7 @@ const api = { quoteToMainWindow: (text: string) => ipcRenderer.invoke(IpcChannel.App_QuoteToMain, text), setDisableHardwareAcceleration: (isDisable: boolean) => ipcRenderer.invoke(IpcChannel.App_SetDisableHardwareAcceleration, isDisable), + setUseSystemTitleBar: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetUseSystemTitleBar, isActive), trace: { saveData: (topicId: string) => ipcRenderer.invoke(IpcChannel.TRACE_SAVE_DATA, topicId), getData: (topicId: string, traceId: string, modelName?: string) => diff --git a/src/renderer/src/components/Tab/TabContainer.tsx b/src/renderer/src/components/Tab/TabContainer.tsx index 5ac1353be8..2d4d34a249 100644 --- a/src/renderer/src/components/Tab/TabContainer.tsx +++ b/src/renderer/src/components/Tab/TabContainer.tsx @@ -2,12 +2,13 @@ import { PlusOutlined } from '@ant-design/icons' import { loggerService } from '@logger' import { Sortable, useDndReorder } from '@renderer/components/dnd' import HorizontalScrollContainer from '@renderer/components/HorizontalScrollContainer' -import { isMac } from '@renderer/config/constant' +import { isLinux, isMac } from '@renderer/config/constant' import { DEFAULT_MIN_APPS } from '@renderer/config/minapps' import { useTheme } from '@renderer/context/ThemeProvider' import { useFullscreen } from '@renderer/hooks/useFullscreen' import { useMinappPopup } from '@renderer/hooks/useMinappPopup' import { useMinapps } from '@renderer/hooks/useMinapps' +import { useSettings } from '@renderer/hooks/useSettings' import { getThemeModeLabel, getTitleLabel } from '@renderer/i18n/label' import tabsService from '@renderer/services/TabsService' import { useAppDispatch, useAppSelector } from '@renderer/store' @@ -122,6 +123,7 @@ const TabsContainer: React.FC = ({ children }) => { const { settedTheme, toggleTheme } = useTheme() const { hideMinappPopup, minAppsCache } = useMinappPopup() const { minapps } = useMinapps() + const { useSystemTitleBar } = useSettings() const { t } = useTranslation() const getTabId = (path: string): string => { @@ -268,7 +270,7 @@ const TabsContainer: React.FC = ({ children }) => { - + { const [isMaximized, setIsMaximized] = useState(false) const { t } = useTranslation() + const { useSystemTitleBar } = useSettings() useEffect(() => { // Check initial maximized state @@ -67,6 +69,11 @@ const WindowControls: React.FC = () => { return null } + // Hide on Linux if using system title bar + if (isLinux && useSystemTitleBar) { + return null + } + const handleMinimize = () => { window.api.windowControls.minimize() } diff --git a/src/renderer/src/components/app/Navbar.tsx b/src/renderer/src/components/app/Navbar.tsx index bfad371f1b..2a5a4e7864 100644 --- a/src/renderer/src/components/app/Navbar.tsx +++ b/src/renderer/src/components/app/Navbar.tsx @@ -22,12 +22,10 @@ export const Navbar: FC = ({ children, ...props }) => { } return ( - <> - - {children} - - {!isTopNavbar && !minappShow && } - + + {children} + {!minappShow && } + ) } diff --git a/src/renderer/src/hooks/useSettings.ts b/src/renderer/src/hooks/useSettings.ts index 3a3de7f89a..84549d40de 100644 --- a/src/renderer/src/hooks/useSettings.ts +++ b/src/renderer/src/hooks/useSettings.ts @@ -34,6 +34,7 @@ import { setTopicPosition, setTray as _setTray, setTrayOnClose, + setUseSystemTitleBar as _setUseSystemTitleBar, setWindowStyle } from '@renderer/store/settings' import type { SidebarIcon, ThemeMode, TranslateLanguageCode } from '@renderer/types' @@ -117,6 +118,10 @@ export function useSettings() { setDisableHardwareAcceleration(disableHardwareAcceleration: boolean) { dispatch(setDisableHardwareAcceleration(disableHardwareAcceleration)) window.api.setDisableHardwareAcceleration(disableHardwareAcceleration) + }, + setUseSystemTitleBar(useSystemTitleBar: boolean) { + dispatch(_setUseSystemTitleBar(useSystemTitleBar)) + window.api.setUseSystemTitleBar(useSystemTitleBar) } } } diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 1f7dece2be..a1fefe1277 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -4944,6 +4944,13 @@ "show": "Show Tray Icon", "title": "Tray" }, + "use_system_title_bar": { + "confirm": { + "content": "Changing the title bar style requires restarting the app to take effect. Do you want to restart now?", + "title": "Restart Required" + }, + "title": "Use System Title Bar (Linux)" + }, "zoom": { "reset": "Reset", "title": "Page Zoom" diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index fe5078e684..51d71aa7c3 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -4944,6 +4944,13 @@ "show": "显示托盘图标", "title": "托盘" }, + "use_system_title_bar": { + "confirm": { + "content": "更改标题栏样式需要重启应用才能生效,是否现在重启?", + "title": "需要重启应用" + }, + "title": "使用系统标题栏 (Linux)" + }, "zoom": { "reset": "重置", "title": "缩放" diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index b6697bca37..ea880e5965 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -4944,6 +4944,13 @@ "show": "顯示系統匣圖示", "title": "系統匣" }, + "use_system_title_bar": { + "confirm": { + "content": "[to be translated]:Changing the title bar style requires restarting the app to take effect. Do you want to restart now?", + "title": "[to be translated]:Restart Required" + }, + "title": "[to be translated]:Use System Title Bar (Linux)" + }, "zoom": { "reset": "重設", "title": "縮放" diff --git a/src/renderer/src/i18n/translate/de-de.json b/src/renderer/src/i18n/translate/de-de.json index 98b6ec7d84..99733e41a8 100644 --- a/src/renderer/src/i18n/translate/de-de.json +++ b/src/renderer/src/i18n/translate/de-de.json @@ -4944,6 +4944,13 @@ "show": "Tray-Symbol anzeigen", "title": "Tray" }, + "use_system_title_bar": { + "confirm": { + "content": "[to be translated]:Changing the title bar style requires restarting the app to take effect. Do you want to restart now?", + "title": "[to be translated]:Restart Required" + }, + "title": "[to be translated]:Use System Title Bar (Linux)" + }, "zoom": { "reset": "Zurücksetzen", "title": "Zoom" diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index 3add278020..5b0d6a6e06 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -4944,6 +4944,13 @@ "show": "Εμφάνιση εικονιδίου συνδρομής", "title": "Συνδρομή" }, + "use_system_title_bar": { + "confirm": { + "content": "[to be translated]:Changing the title bar style requires restarting the app to take effect. Do you want to restart now?", + "title": "[to be translated]:Restart Required" + }, + "title": "[to be translated]:Use System Title Bar (Linux)" + }, "zoom": { "reset": "Επαναφορά", "title": "Κλίμακα" diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index 620e731c9f..4b08aeafd0 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -4944,6 +4944,13 @@ "show": "Mostrar bandera del sistema", "title": "Bandera" }, + "use_system_title_bar": { + "confirm": { + "content": "[to be translated]:Changing the title bar style requires restarting the app to take effect. Do you want to restart now?", + "title": "[to be translated]:Restart Required" + }, + "title": "[to be translated]:Use System Title Bar (Linux)" + }, "zoom": { "reset": "Restablecer", "title": "Escala" diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index 30af8e8d6c..a3affc5609 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -4944,6 +4944,13 @@ "show": "Afficher l'icône dans la barre d'état système", "title": "Barre d'état système" }, + "use_system_title_bar": { + "confirm": { + "content": "[to be translated]:Changing the title bar style requires restarting the app to take effect. Do you want to restart now?", + "title": "[to be translated]:Restart Required" + }, + "title": "[to be translated]:Use System Title Bar (Linux)" + }, "zoom": { "reset": "Réinitialiser", "title": "Zoom" diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index 0c69fb863c..01a89c23f6 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -4944,6 +4944,13 @@ "show": "トレイアイコンを表示", "title": "トレイ" }, + "use_system_title_bar": { + "confirm": { + "content": "[to be translated]:Changing the title bar style requires restarting the app to take effect. Do you want to restart now?", + "title": "[to be translated]:Restart Required" + }, + "title": "[to be translated]:Use System Title Bar (Linux)" + }, "zoom": { "reset": "リセット", "title": "ページズーム" diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 94534d9e4d..70b08fc8c0 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -4944,6 +4944,13 @@ "show": "Mostrar ícone de bandeja", "title": "Tray" }, + "use_system_title_bar": { + "confirm": { + "content": "[to be translated]:Changing the title bar style requires restarting the app to take effect. Do you want to restart now?", + "title": "[to be translated]:Restart Required" + }, + "title": "[to be translated]:Use System Title Bar (Linux)" + }, "zoom": { "reset": "Redefinir", "title": "Escala" diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index 519918de1a..229a36f2a1 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -4944,6 +4944,13 @@ "show": "Показать значок в трее", "title": "Трей" }, + "use_system_title_bar": { + "confirm": { + "content": "[to be translated]:Changing the title bar style requires restarting the app to take effect. Do you want to restart now?", + "title": "[to be translated]:Restart Required" + }, + "title": "[to be translated]:Use System Title Bar (Linux)" + }, "zoom": { "reset": "Сбросить", "title": "Масштаб страницы" diff --git a/src/renderer/src/pages/home/Navbar.tsx b/src/renderer/src/pages/home/Navbar.tsx index 0d93a4bd01..f63939eb01 100644 --- a/src/renderer/src/pages/home/Navbar.tsx +++ b/src/renderer/src/pages/home/Navbar.tsx @@ -1,7 +1,6 @@ import { Navbar, NavbarCenter, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar' import { HStack } from '@renderer/components/Layout' import SearchPopup from '@renderer/components/Popups/SearchPopup' -import { isLinux, isWin } from '@renderer/config/constant' import { modelGenerating } from '@renderer/hooks/useRuntime' import { useSettings } from '@renderer/hooks/useSettings' import { useShortcut } from '@renderer/hooks/useShortcuts' @@ -123,7 +122,7 @@ const HeaderNavbar: FC = ({ justifyContent: 'flex-end', flex: activeTopicOrSession === 'topic' ? 1 : 'none', position: 'relative', - paddingRight: isWin || isLinux ? '144px' : '15px', + paddingRight: '15px', minWidth: activeTopicOrSession === 'topic' ? '' : 'auto' }} className="home-navbar-right"> diff --git a/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx b/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx index 444fba569f..65ccd93442 100644 --- a/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx +++ b/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx @@ -2,10 +2,11 @@ import CodeEditor from '@renderer/components/CodeEditor' import { ResetIcon } from '@renderer/components/Icons' import { HStack } from '@renderer/components/Layout' import TextBadge from '@renderer/components/TextBadge' -import { isMac, THEME_COLOR_PRESETS } from '@renderer/config/constant' +import { isLinux, isMac, THEME_COLOR_PRESETS } from '@renderer/config/constant' import { DEFAULT_SIDEBAR_ICONS } from '@renderer/config/sidebar' import { useTheme } from '@renderer/context/ThemeProvider' import { useNavbarPosition, useSettings } from '@renderer/hooks/useSettings' +import { useTimer } from '@renderer/hooks/useTimer' import useUserTheme from '@renderer/hooks/useUserTheme' import { useAppDispatch } from '@renderer/store' import type { AssistantIconType } from '@renderer/store/settings' @@ -68,12 +69,15 @@ const DisplaySettings: FC = () => { sidebarIcons, setTheme, assistantIconType, - userTheme + userTheme, + useSystemTitleBar, + setUseSystemTitleBar } = useSettings() const { navbarPosition, setNavbarPosition } = useNavbarPosition() const { theme, settedTheme } = useTheme() const { t } = useTranslation() const dispatch = useAppDispatch() + const { setTimeoutTimer } = useTimer() const [currentZoom, setCurrentZoom] = useState(1.0) const { setUserTheme } = useUserTheme() @@ -88,6 +92,26 @@ const DisplaySettings: FC = () => { [setWindowStyle] ) + const handleUseSystemTitleBarChange = (checked: boolean) => { + window.modal.confirm({ + title: t('settings.use_system_title_bar.confirm.title'), + content: t('settings.use_system_title_bar.confirm.content'), + okText: t('common.confirm'), + cancelText: t('common.cancel'), + centered: true, + onOk() { + setUseSystemTitleBar(checked) + setTimeoutTimer( + 'handleUseSystemTitleBarChange', + () => { + window.api.relaunchApp() + }, + 500 + ) + } + }) + } + const handleColorPrimaryChange = useCallback( (colorHex: string) => { setUserTheme({ @@ -260,6 +284,15 @@ const DisplaySettings: FC = () => { )} + {isLinux && ( + <> + + + {t('settings.use_system_title_bar.title')} + + + + )} diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index 3ba3cc4da8..01460ba035 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -198,6 +198,8 @@ export interface SettingsState { enableQuickPanelTriggers: boolean // 硬件加速设置 disableHardwareAcceleration: boolean + // 使用系统标题栏 (仅Linux) + useSystemTitleBar: boolean exportMenuOptions: { image: boolean markdown: boolean @@ -383,6 +385,8 @@ export const initialState: SettingsState = { confirmRegenerateMessage: true, // 硬件加速设置 disableHardwareAcceleration: false, + // 使用系统标题栏 (仅Linux) + useSystemTitleBar: false, exportMenuOptions: { image: true, markdown: true, @@ -818,6 +822,9 @@ const settingsSlice = createSlice({ setDisableHardwareAcceleration: (state, action: PayloadAction) => { state.disableHardwareAcceleration = action.payload }, + setUseSystemTitleBar: (state, action: PayloadAction) => { + state.useSystemTitleBar = action.payload + }, setOpenAISummaryText: (state, action: PayloadAction) => { state.openAI.summaryText = action.payload }, @@ -998,6 +1005,7 @@ export const { setConfirmDeleteMessage, setConfirmRegenerateMessage, setDisableHardwareAcceleration, + setUseSystemTitleBar, setOpenAISummaryText, setOpenAIVerbosity, setOpenAIStreamOptionsIncludeUsage,