From 0d3f0f20fdc156703203744d441038bfae85eaba Mon Sep 17 00:00:00 2001 From: SuYao Date: Fri, 23 Jan 2026 14:18:56 +0800 Subject: [PATCH] fix(linux): fix icon display and deb installation issues (#12561) - Fix Wayland window icon not showing in alt-tab and titlebar - Fix fcitx5 input method cannot switch after sending message - Fix deb installation failure due to spaces in executable name - Add pacman build target for Arch Linux - Rename Linux executable from "Cherry Studio" to "CherryStudio" - Use nativeImage for proper icon loading on Linux Fixes #12295, Fixes #12494 Co-authored-by: Claude Opus 4.5 --- electron-builder.yml | 3 +++ package.json | 1 + src/main/index.ts | 8 ++++---- src/main/services/WindowService.ts | 9 ++++++--- .../pages/home/Inputbar/AgentSessionInputbar.tsx | 16 +++++++++++++++- .../src/pages/home/Inputbar/Inputbar.tsx | 16 +++++++++++++++- 6 files changed, 44 insertions(+), 9 deletions(-) diff --git a/electron-builder.yml b/electron-builder.yml index 00d9fad5be..a9fc5294cd 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -116,14 +116,17 @@ dmg: writeUpdateInfo: false linux: artifactName: ${productName}-${version}-${arch}.${ext} + executableName: CherryStudio target: - target: AppImage - target: deb - target: rpm + - target: pacman maintainer: electronjs.org category: Utility desktop: entry: + Name: Cherry Studio StartupWMClass: CherryStudio mimeTypes: - x-scheme-handler/cherrystudio diff --git a/package.json b/package.json index 6b6caa0007..60b6ab2954 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "1.7.13", "private": true, "description": "A powerful AI assistant for producer.", + "desktopName": "CherryStudio.desktop", "main": "./out/main/index.js", "author": "support@cherry-ai.com", "homepage": "https://github.com/CherryHQ/cherry-studio", diff --git a/src/main/index.ts b/src/main/index.ts index aab674ba50..6f07c769ba 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -76,12 +76,12 @@ if (isLinux && process.env.XDG_SESSION_TYPE === 'wayland') { } /** - * Set window class and name for X11 - * This ensures the system tray and window manager identify the app correctly + * Set window class and name for Linux + * This ensures the window manager identifies the app correctly on both X11 and Wayland */ if (isLinux) { - app.commandLine.appendSwitch('class', 'cherry-studio') - app.commandLine.appendSwitch('name', 'cherry-studio') + app.commandLine.appendSwitch('class', 'CherryStudio') + app.commandLine.appendSwitch('name', 'CherryStudio') } // DocumentPolicyIncludeJSCallStacksInCrashReports: Enable features for unresponsive renderer js call stacks diff --git a/src/main/services/WindowService.ts b/src/main/services/WindowService.ts index cf66405817..1e2bbdd802 100644 --- a/src/main/services/WindowService.ts +++ b/src/main/services/WindowService.ts @@ -9,11 +9,11 @@ import { getFilesDir } from '@main/utils/file' import { generateUserAgent } from '@main/utils/systemInfo' import { MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH } from '@shared/config/constant' import { IpcChannel } from '@shared/IpcChannel' -import { app, BrowserWindow, nativeTheme, screen, shell } from 'electron' +import { app, BrowserWindow, nativeImage, nativeTheme, screen, shell } from 'electron' import windowStateKeeper from 'electron-window-state' import { join } from 'path' -import icon from '../../../build/icon.png?asset' +import iconPath from '../../../build/icon.png?asset' import { titleBarOverlayDark, titleBarOverlayLight } from '../config' import { configManager } from './ConfigManager' import { contextMenu } from './ContextMenu' @@ -25,6 +25,9 @@ const DEFAULT_MINIWINDOW_HEIGHT = 400 // const logger = loggerService.withContext('WindowService') const logger = loggerService.withContext('WindowService') +// Create nativeImage for Linux window icon (required for Wayland) +const linuxIcon = isLinux ? nativeImage.createFromPath(iconPath) : undefined + export class WindowService { private static instance: WindowService | null = null private mainWindow: BrowserWindow | null = null @@ -82,7 +85,7 @@ export class WindowService { }), backgroundColor: isMac ? undefined : nativeTheme.shouldUseDarkColors ? '#181818' : '#FFFFFF', darkTheme: nativeTheme.shouldUseDarkColors, - ...(isLinux ? { icon } : {}), + ...(isLinux ? { icon: linuxIcon } : {}), webPreferences: { preload: join(__dirname, '../preload/index.js'), sandbox: false, diff --git a/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx b/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx index 850be7f727..c229e3e2cf 100644 --- a/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx @@ -427,10 +427,24 @@ const AgentSessionInputbarInner: FC = ({ assistant, agentId, session // Clear text after successful send (draft is cleared automatically via onChange) setText('') setTimeoutTimer('agentSession_sendMessage', () => setText(''), 500) + // Restore focus to textarea after sending to maintain IME state (fcitx5 issue) + focusTextarea() } catch (error) { logger.warn('Failed to send message:', error as Error) } - }, [sendDisabled, agentId, dispatch, assistant, sessionId, sessionTopicId, setText, setTimeoutTimer, text, files]) + }, [ + sendDisabled, + agentId, + dispatch, + assistant, + sessionId, + sessionTopicId, + setText, + setTimeoutTimer, + text, + files, + focusTextarea + ]) useEffect(() => { if (!document.querySelector('.topview-fullscreen-container')) { diff --git a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx index fc95082e50..c567108820 100644 --- a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx @@ -260,11 +260,25 @@ const InputbarInner: FC = ({ assistant: initialAssistant, se setFiles([]) setTimeoutTimer('sendMessage_1', () => setText(''), 500) setTimeoutTimer('sendMessage_2', () => resizeTextArea(), 0) + // Restore focus to textarea after sending to maintain IME state (fcitx5 issue) + focusTextarea() } catch (error) { logger.warn('Failed to send message:', error as Error) parent?.recordException(error as Error) } - }, [assistant, topic, text, mentionedModels, files, dispatch, setText, setFiles, setTimeoutTimer, resizeTextArea]) + }, [ + assistant, + topic, + text, + mentionedModels, + files, + dispatch, + setText, + setFiles, + setTimeoutTimer, + resizeTextArea, + focusTextarea + ]) const tokenCountProps = useMemo(() => { if (!config.showTokenCount || estimateTokenCount === undefined || !showInputEstimatedTokens) {