From 6f3b60ef2c232717534974f837175a40a2e99c66 Mon Sep 17 00:00:00 2001 From: H3CoF6 <1707889225@qq.com> Date: Fri, 20 Mar 2026 06:44:03 +0800 Subject: [PATCH 01/11] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dlinux=E6=89=93?= =?UTF-8?q?=E5=8C=85=E5=90=8E=E6=97=A0=E6=B3=95=E6=8B=89=E8=B5=B7wechat?= =?UTF-8?q?=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron/services/keyServiceLinux.ts | 36 ++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/electron/services/keyServiceLinux.ts b/electron/services/keyServiceLinux.ts index 1d9e10b..3af9ddf 100644 --- a/electron/services/keyServiceLinux.ts +++ b/electron/services/keyServiceLinux.ts @@ -4,6 +4,7 @@ import { existsSync, readdirSync, statSync, readFileSync } from 'fs' import { execFile, exec } from 'child_process' import { promisify } from 'util' import { createRequire } from 'module'; +import { spawn } from 'child_process' const require = createRequire(import.meta.url); const execFileAsync = promisify(execFile) @@ -51,12 +52,37 @@ export class KeyServiceLinux { await new Promise(r => setTimeout(r, 1000)) onStatus?.('正在尝试拉起微信...', 0) - const startCmds = [ - 'nohup wechat >/dev/null 2>&1 &', - 'nohup wechat-bin >/dev/null 2>&1 &', - 'nohup xwechat >/dev/null 2>&1 &' + + const cleanEnv = { ...process.env }; + delete cleanEnv.ELECTRON_RUN_AS_NODE; + delete cleanEnv.ELECTRON_NO_ATTACH_CONSOLE; + delete cleanEnv.APPDIR; + delete cleanEnv.APPIMAGE; + + const wechatBins = [ + 'wechat', + 'wechat-bin', + 'xwechat', + '/opt/wechat/wechat', + '/usr/bin/wechat', + '/opt/apps/com.tencent.wechat/files/wechat' ] - for (const cmd of startCmds) execAsync(cmd).catch(() => {}) + + for (const binName of wechatBins) { + try { + const child = spawn(binName, [], { + detached: true, + stdio: 'ignore', + env: cleanEnv + }); + + child.on('error', () => {}); + + child.unref(); + } catch (e) { + + } + } onStatus?.('等待微信进程出现...', 0) let pid = 0 From 29d49360f53b5a940f05d69f730be0fbd7c9b778 Mon Sep 17 00:00:00 2001 From: H3CoF6 <1707889225@qq.com> Date: Sat, 21 Mar 2026 00:17:43 +0800 Subject: [PATCH 02/11] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E8=AF=AD?= =?UTF-8?q?=E9=9F=B3=E8=BD=AC=E6=96=87=E5=AD=97=E6=AE=B5=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron/services/voiceTranscribeService.ts | 16 ++++++- src/pages/ChatPage.tsx | 52 +++++++++++++++++++-- 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/electron/services/voiceTranscribeService.ts b/electron/services/voiceTranscribeService.ts index cc75828..107cbc5 100644 --- a/electron/services/voiceTranscribeService.ts +++ b/electron/services/voiceTranscribeService.ts @@ -273,8 +273,20 @@ export class VoiceTranscribeService { }) worker.on('error', (err: Error) => resolve({ success: false, error: String(err) })) - worker.on('exit', (code: number) => { - if (code !== 0) resolve({ success: false, error: `Worker exited with code ${code}` }) + worker.on('exit', (code: number | null, signal: string | null) => { + if (code === null || signal === 'SIGSEGV') { + + console.error(`[VoiceTranscribe] Worker 异常崩溃,信号: ${signal}。可能是由于底层 C++ 运行库在当前系统上发生段错误。`); + resolve({ + success: false, + error: 'SEGFAULT_ERROR' + }); + return; + } + + if (code !== 0) { + resolve({ success: false, error: `Worker exited with code ${code}` }); + } }) } catch (error) { diff --git a/src/pages/ChatPage.tsx b/src/pages/ChatPage.tsx index 9a49bdc..91ffe81 100644 --- a/src/pages/ChatPage.tsx +++ b/src/pages/ChatPage.tsx @@ -7345,6 +7345,12 @@ function MessageBubble({ const [voiceWaveform, setVoiceWaveform] = useState([]) const voiceAutoDecryptTriggered = useRef(false) + + const [systemAlert, setSystemAlert] = useState<{ + title: string; + message: React.ReactNode; + } | null>(null) + // 转账消息双方名称 const [transferPayerName, setTransferPayerName] = useState(undefined) const [transferReceiverName, setTransferReceiverName] = useState(undefined) @@ -8024,9 +8030,9 @@ function MessageBubble({ } const result = await window.electronAPI.chat.getVoiceTranscript( - session.username, - String(message.localId), - message.createTime + session.username, + String(message.localId), + message.createTime ) if (result.success) { @@ -8034,6 +8040,21 @@ function MessageBubble({ voiceTranscriptCache.set(voiceTranscriptCacheKey, transcriptText) setVoiceTranscript(transcriptText) } else { + if (result.error === 'SEGFAULT_ERROR') { + console.warn('[ChatPage] 捕获到语音引擎底层段错误'); + + setSystemAlert({ + title: '引擎崩溃提示', + message: ( + <> + 语音识别引擎发生底层崩溃 (Segmentation Fault)。

+ 如果您使用的是 Linux 等自定义程度较高的系统,请检查 sherpa-onnx 的相关系统动态链接库 (如 glibc 等) 是否兼容。 + + ) + }); + + } + setVoiceTranscriptError(true) voiceTranscriptRequestedRef.current = false } @@ -9388,6 +9409,31 @@ function MessageBubble({ {isSelected && } )} + {systemAlert && createPortal( +
setSystemAlert(null)} style={{ zIndex: 99999 }}> +
e.stopPropagation()} style={{ maxWidth: '400px' }}> +
+ +
+
+

{systemAlert.title}

+

+ {systemAlert.message} +

+
+
+ +
+
+
, + document.body + )} ) From 45d4e74c98a613991127fc6fdb851c8c51f26d56 Mon Sep 17 00:00:00 2001 From: H3CoF6 <1707889225@qq.com> Date: Sat, 21 Mar 2026 02:14:38 +0800 Subject: [PATCH 03/11] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dlinux=E6=89=93?= =?UTF-8?q?=E5=8C=85=E5=90=8E=E6=97=A0=E6=B3=95=E6=AD=A3=E5=B8=B8=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E8=BF=9B=E7=A8=8B=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron/services/keyServiceLinux.ts | 67 +++++++++++++++++++++++----- package.json | 2 +- 2 files changed, 58 insertions(+), 11 deletions(-) diff --git a/electron/services/keyServiceLinux.ts b/electron/services/keyServiceLinux.ts index 3af9ddf..8fe2cc4 100644 --- a/electron/services/keyServiceLinux.ts +++ b/electron/services/keyServiceLinux.ts @@ -46,8 +46,32 @@ export class KeyServiceLinux { onStatus?: (message: string, level: number) => void ): Promise { try { + // 1. 构造一个包含常用系统命令路径的环境变量,防止打包后找不到命令 + const envWithPath = { + ...process.env, + PATH: `${process.env.PATH || ''}:/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin` + }; + onStatus?.('正在尝试结束当前微信进程...', 0) - await execAsync('killall -9 wechat wechat-bin xwechat').catch(() => {}) + console.log('[Debug] 开始执行进程清理逻辑...'); + + try { + const { stdout, stderr } = await execAsync('killall -9 wechat wechat-bin xwechat', { env: envWithPath }); + console.log(`[Debug] killall 成功退出. stdout: ${stdout}, stderr: ${stderr}`); + } catch (err: any) { + // 命令如果没找到进程通常会返回 code 1,这也是正常的,但我们需要记录下来 + console.log(`[Debug] killall 报错或未找到进程: ${err.message}`); + + // Fallback: 尝试使用 pkill 兜底 + try { + console.log('[Debug] 尝试使用备用命令 pkill...'); + await execAsync('pkill -9 -x "wechat|wechat-bin|xwechat"', { env: envWithPath }); + console.log('[Debug] pkill 执行完成'); + } catch (e: any) { + console.log(`[Debug] pkill 报错或未找到进程: ${e.message}`); + } + } + // 稍微等待进程完全退出 await new Promise(r => setTimeout(r, 1000)) @@ -76,11 +100,14 @@ export class KeyServiceLinux { env: cleanEnv }); - child.on('error', () => {}); + child.on('error', (err) => { + console.log(`[Debug] 拉起 ${binName} 失败:`, err.message); + }); child.unref(); - } catch (e) { - + console.log(`[Debug] 尝试拉起 ${binName} 完毕`); + } catch (e: any) { + console.log(`[Debug] 尝试拉起 ${binName} 发生异常:`, e.message); } } @@ -88,16 +115,35 @@ export class KeyServiceLinux { let pid = 0 for (let i = 0; i < 15; i++) { // 最多等 15 秒 await new Promise(r => setTimeout(r, 1000)) - const { stdout } = await execAsync('pidof wechat wechat-bin xwechat').catch(() => ({ stdout: '' })) - const pids = stdout.trim().split(/\s+/).filter(p => p) - if (pids.length > 0) { - pid = parseInt(pids[0], 10) - break + + try { + const { stdout } = await execAsync('pidof wechat wechat-bin xwechat', { env: envWithPath }); + const pids = stdout.trim().split(/\s+/).filter(p => p); + if (pids.length > 0) { + pid = parseInt(pids[0], 10); + console.log(`[Debug] 第 ${i + 1} 秒,通过 pidof 成功获取 PID: ${pid}`); + break; + } + } catch (err: any) { + console.log(`[Debug] 第 ${i + 1} 秒,pidof 失败: ${err.message.split('\n')[0]}`); + + // Fallback: 使用 pgrep 兜底 + try { + const { stdout: pgrepOut } = await execAsync('pgrep -x "wechat|wechat-bin|xwechat"', { env: envWithPath }); + const pids = pgrepOut.trim().split(/\s+/).filter(p => p); + if (pids.length > 0) { + pid = parseInt(pids[0], 10); + console.log(`[Debug] 第 ${i + 1} 秒,通过 pgrep 成功获取 PID: ${pid}`); + break; + } + } catch (e: any) { + console.log(`[Debug] 第 ${i + 1} 秒,pgrep 也失败: ${e.message.split('\n')[0]}`); + } } } if (!pid) { - const err = '未能自动启动微信,请手动启动并登录。' + const err = '未能自动启动微信,或获取PID失败,请查看控制台日志或手动启动并登录。' onStatus?.(err, 2) return { success: false, error: err } } @@ -108,6 +154,7 @@ export class KeyServiceLinux { return await this.getDbKey(pid, onStatus) } catch (err: any) { + console.error('[Debug] 自动获取流程彻底崩溃:', err); const errMsg = '自动获取微信 PID 失败: ' + err.message onStatus?.(errMsg, 2) return { success: false, error: errMsg } diff --git a/package.json b/package.json index b6ec942..213700d 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "linux": { "icon": "public/icon.png", "target": [ - "deb", + "appimage", "tar.gz" ], "category": "Utility", From 539f854dbfa054cd075ff77887fb8dee6e5107fc Mon Sep 17 00:00:00 2001 From: H3CoF6 <1707889225@qq.com> Date: Sat, 21 Mar 2026 02:53:03 +0800 Subject: [PATCH 04/11] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0wayland?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E5=92=8C=E6=B6=88=E6=81=AF=E5=BC=B9=E7=AA=97?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E5=A4=B1=E6=95=88=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron/main.ts | 7 +++ electron/preload.ts | 3 +- electron/services/keyServiceLinux.ts | 3 +- src/App.tsx | 67 ++++++++++++++++++++++++++++ src/types/electron.d.ts | 1 + 5 files changed, 78 insertions(+), 3 deletions(-) diff --git a/electron/main.ts b/electron/main.ts index a60c898..3a5dab3 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -1043,6 +1043,13 @@ function registerIpcHandlers() { return app.getVersion() }) + ipcMain.handle('app:checkWayland', async () => { + if (process.platform !== 'linux') return false; + + const sessionType = process.env.XDG_SESSION_TYPE?.toLowerCase(); + return Boolean(process.env.WAYLAND_DISPLAY || sessionType === 'wayland'); + }) + ipcMain.handle('log:getPath', async () => { return join(app.getPath('userData'), 'logs', 'wcdb.log') }) diff --git a/electron/preload.ts b/electron/preload.ts index f12a272..b1c9c30 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -63,7 +63,8 @@ contextBridge.exposeInMainWorld('electronAPI', { onUpdateAvailable: (callback: (info: { version: string; releaseNotes: string }) => void) => { ipcRenderer.on('app:updateAvailable', (_, info) => callback(info)) return () => ipcRenderer.removeAllListeners('app:updateAvailable') - } + }, + checkWayland: () => ipcRenderer.invoke('app:checkWayland'), }, // 日志 diff --git a/electron/services/keyServiceLinux.ts b/electron/services/keyServiceLinux.ts index 8fe2cc4..7364f83 100644 --- a/electron/services/keyServiceLinux.ts +++ b/electron/services/keyServiceLinux.ts @@ -1,10 +1,9 @@ import { app } from 'electron' import { join } from 'path' import { existsSync, readdirSync, statSync, readFileSync } from 'fs' -import { execFile, exec } from 'child_process' +import { execFile, exec, spawn } from 'child_process' import { promisify } from 'util' import { createRequire } from 'module'; -import { spawn } from 'child_process' const require = createRequire(import.meta.url); const execFileAsync = promisify(execFile) diff --git a/src/App.tsx b/src/App.tsx index eb8cd5c..e09f7ef 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -104,6 +104,44 @@ function App() { // 数据收集同意状态 const [showAnalyticsConsent, setShowAnalyticsConsent] = useState(false) + const [showWaylandWarning, setShowWaylandWarning] = useState(false) + + useEffect(() => { + const checkWaylandStatus = async () => { + try { + // 防止在非客户端环境报错,先检查 API 是否存在 + if (!window.electronAPI?.app?.checkWayland) return + + // 通过 configService 检查是否已经弹过窗 + const hasWarned = await window.electronAPI.config.get('waylandWarningShown') + + if (!hasWarned) { + const isWayland = await window.electronAPI.app.checkWayland() + if (isWayland) { + setShowWaylandWarning(true) + } + } + } catch (e) { + console.error('检查 Wayland 状态失败:', e) + } + } + + // 只有在协议同意之后并且已经进入主应用流程才检查 + if (!isAgreementWindow && !isOnboardingWindow && !agreementLoading) { + checkWaylandStatus() + } + }, [isAgreementWindow, isOnboardingWindow, agreementLoading]) + + const handleDismissWaylandWarning = async () => { + try { + // 记录到本地配置中,下次不再提示 + await window.electronAPI.config.set('waylandWarningShown', true) + } catch (e) { + console.error('保存 Wayland 提示状态失败:', e) + } + setShowWaylandWarning(false) + } + useEffect(() => { if (location.pathname !== '/settings') { settingsBackgroundRef.current = location @@ -432,6 +470,8 @@ function App() { checkLock() }, [isAgreementWindow, isOnboardingWindow, isVideoPlayerWindow]) + + // 独立协议窗口 if (isAgreementWindow) { return @@ -614,6 +654,33 @@ function App() { )} + {showWaylandWarning && ( +
+
+
+ +

环境兼容性提示 (Wayland)

+
+
+
+

检测到您当前正在使用 Wayland 显示服务器。

+

在 Wayland 环境下,出于系统级的安全与设计机制,应用程序无法直接控制新弹出窗口的位置

+

这可能导致某些独立窗口(如消息通知、图片查看器等)出现位置随机、或不受控制的情况。这是底层机制导致的,对此我们无能为力。

+
+

如果您觉得窗口位置异常严重影响了使用体验,建议尝试:

+

1. 在系统登录界面,将会话切换回 X11 (Xorg) 模式。

+

2. 修改您的桌面管理器 (WM/DE) 配置,强制指定该应用程序的窗口规则。

+
+
+
+
+ +
+
+
+
+ )} + {/* 更新提示对话框 */} Promise<{ success: boolean }> onDownloadProgress: (callback: (progress: number) => void) => () => void onUpdateAvailable: (callback: (info: { version: string; releaseNotes: string }) => void) => () => void + checkWayland: () => Promise } notification: { show: (data: { title: string; content: string; avatarUrl?: string; sessionId: string }) => Promise<{ success?: boolean; error?: string } | void> From 659b9f96801dfd1e6b7fc15b8070ad638f5cad7c Mon Sep 17 00:00:00 2001 From: H3CoF6 <1707889225@qq.com> Date: Sat, 21 Mar 2026 03:05:18 +0800 Subject: [PATCH 05/11] =?UTF-8?q?feat:=20=E8=AE=BE=E7=BD=AE=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2wayland=E8=AF=B4=E6=98=8E=E5=92=8C=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/SettingsPage.tsx | 89 ++++++++++++++++++++++++++------------ 1 file changed, 62 insertions(+), 27 deletions(-) diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index b010d93..bfed9b1 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -175,6 +175,21 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) { const isClearingCache = isClearingAnalyticsCache || isClearingImageCache || isClearingAllCache + const [isWayland, setIsWayland] = useState(false) + useEffect(() => { + const checkWaylandStatus = async () => { + if (window.electronAPI?.app?.checkWayland) { + try { + const wayland = await window.electronAPI.app.checkWayland() + setIsWayland(wayland) + } catch (e) { + console.error('检查 Wayland 状态失败:', e) + } + } + } + checkWaylandStatus() + }, []) + // 检查 Hello 可用性 useEffect(() => { if (window.PublicKeyCredential) { @@ -1169,6 +1184,11 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
选择通知弹窗在屏幕上的显示位置 + {isWayland && ( + + ⚠️ 注意:Wayland 环境下该配置可能无效! + + )}
( -
-

管理应用缓存数据

-
- - 留空使用默认目录 - { - const value = e.target.value - setCachePath(value) - scheduleConfigSave('cachePath', () => configService.setCachePath(value)) - }} - /> -
- - +
+

管理应用缓存数据

+
+ + 留空使用默认目录 + { + const value = e.target.value + setCachePath(value) + scheduleConfigSave('cachePath', () => configService.setCachePath(value)) + }} + /> + +
+ 当前缓存位置: + + {cachePath || (isMac ? '~/Documents/WeFlow' : isLinux ? '~/Documents/WeFlow' : '系统 文档\\WeFlow 目录')} + +
+ +
+ + +
-