mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-29 05:51:26 +08:00
feat: Implement deep linking for AppImage on Linux (#5360)
Signed-off-by: emaryn <197520219+emaryn@users.noreply.github.com> Co-authored-by: emaryn <emaryn@users.noreply.github.com> Co-authored-by: 亢奋猫 <kangfenmao@qq.com>
This commit is contained in:
parent
261eeb097a
commit
da72a5706f
@ -77,6 +77,8 @@ linux:
|
||||
desktop:
|
||||
entry:
|
||||
StartupWMClass: CherryStudio
|
||||
mimeTypes:
|
||||
- x-scheme-handler/cherrystudio
|
||||
publish:
|
||||
provider: generic
|
||||
url: https://releases.cherry-ai.com
|
||||
|
||||
@ -8,7 +8,12 @@ import Logger from 'electron-log'
|
||||
import { registerIpc } from './ipc'
|
||||
import { configManager } from './services/ConfigManager'
|
||||
import mcpService from './services/MCPService'
|
||||
import { CHERRY_STUDIO_PROTOCOL, handleProtocolUrl, registerProtocolClient } from './services/ProtocolClient'
|
||||
import {
|
||||
CHERRY_STUDIO_PROTOCOL,
|
||||
handleProtocolUrl,
|
||||
registerProtocolClient,
|
||||
setupAppImageDeepLink
|
||||
} from './services/ProtocolClient'
|
||||
import { registerShortcuts } from './services/ShortcutService'
|
||||
import { TrayService } from './services/TrayService'
|
||||
import { windowService } from './services/WindowService'
|
||||
@ -53,6 +58,9 @@ if (!app.requestSingleInstanceLock()) {
|
||||
|
||||
setAppDataDir()
|
||||
|
||||
// Setup deep link for AppImage on Linux
|
||||
await setupAppImageDeepLink()
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
installExtension([REDUX_DEVTOOLS, REACT_DEVELOPER_TOOLS])
|
||||
.then((name) => console.log(`Added Extension: ${name}`))
|
||||
|
||||
@ -1,3 +1,11 @@
|
||||
import { exec } from 'node:child_process'
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { promisify } from 'node:util'
|
||||
|
||||
import { app } from 'electron'
|
||||
import Logger from 'electron-log'
|
||||
|
||||
import { handleMcpProtocolUrl } from './urlschema/mcp-install'
|
||||
import { windowService } from './WindowService'
|
||||
|
||||
@ -39,3 +47,78 @@ export function handleProtocolUrl(url: string) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const execAsync = promisify(exec)
|
||||
|
||||
const DESKTOP_FILE_NAME = 'cherrystudio-url-handler.desktop'
|
||||
|
||||
/**
|
||||
* Sets up deep linking for the AppImage build on Linux by creating a .desktop file.
|
||||
* This allows the OS to open cherrystudio:// URLs with this App.
|
||||
*/
|
||||
export async function setupAppImageDeepLink(): Promise<void> {
|
||||
// Only run on Linux and when packaged as an AppImage
|
||||
if (process.platform !== 'linux' || !process.env.APPIMAGE) {
|
||||
return
|
||||
}
|
||||
|
||||
Logger.info('AppImage environment detected on Linux, setting up deep link.')
|
||||
|
||||
try {
|
||||
const appPath = app.getPath('exe')
|
||||
if (!appPath) {
|
||||
Logger.error('Could not determine App path.')
|
||||
return
|
||||
}
|
||||
|
||||
const homeDir = app.getPath('home')
|
||||
const applicationsDir = path.join(homeDir, '.local', 'share', 'applications')
|
||||
const desktopFilePath = path.join(applicationsDir, DESKTOP_FILE_NAME)
|
||||
|
||||
// Ensure the applications directory exists
|
||||
await fs.mkdir(applicationsDir, { recursive: true })
|
||||
|
||||
// Content of the .desktop file
|
||||
// %U allows passing the URL to the application
|
||||
// NoDisplay=true hides it from the regular application menu
|
||||
const desktopFileContent = `[Desktop Entry]
|
||||
Name=Cherry Studio
|
||||
Exec=${escapePathForExec(appPath)} %U
|
||||
Terminal=false
|
||||
Type=Application
|
||||
MimeType=x-scheme-handler/${CHERRY_STUDIO_PROTOCOL};
|
||||
NoDisplay=true
|
||||
`
|
||||
|
||||
// Write the .desktop file (overwrite if exists)
|
||||
await fs.writeFile(desktopFilePath, desktopFileContent, 'utf-8')
|
||||
Logger.info(`Created/Updated desktop file: ${desktopFilePath}`)
|
||||
|
||||
// Update the desktop database
|
||||
// It's important to update the database for the changes to take effect
|
||||
try {
|
||||
const { stdout, stderr } = await execAsync(`update-desktop-database ${escapePathForExec(applicationsDir)}`)
|
||||
if (stderr) {
|
||||
Logger.warn(`update-desktop-database stderr: ${stderr}`)
|
||||
}
|
||||
Logger.info(`update-desktop-database stdout: ${stdout}`)
|
||||
Logger.info('Desktop database updated successfully.')
|
||||
} catch (updateError) {
|
||||
Logger.error('Failed to update desktop database:', updateError)
|
||||
// Continue even if update fails, as the file is still created.
|
||||
}
|
||||
} catch (error) {
|
||||
// Log the error but don't prevent the app from starting
|
||||
Logger.error('Failed to setup AppImage deep link:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a path for safe use within the Exec field of a .desktop file
|
||||
* and for shell commands. Handles spaces and potentially other special characters
|
||||
* by quoting.
|
||||
*/
|
||||
function escapePathForExec(filePath: string): string {
|
||||
// Simple quoting for paths with spaces.
|
||||
return `'${filePath.replace(/'/g, "'\\''")}'`
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user