mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-24 23:06:51 +00:00
Merge pull request #514 from H3CoF6/dev
linux版本增加wayland说明 优化一点点页面显示 --- 我发现appimage可以用,之前觉得FUSE导致难以操控微信进程的 重新支持appimage,放弃对deb的打包(等appimage的-1006报错修好后彻底放弃)
This commit is contained in:
5
.github/workflows/release.yml
vendored
5
.github/workflows/release.yml
vendored
@@ -151,6 +151,7 @@ jobs:
|
|||||||
MAC_ASSET="$(pick_asset "\\.dmg$")"
|
MAC_ASSET="$(pick_asset "\\.dmg$")"
|
||||||
LINUX_DEB_ASSET="$(pick_asset "\\.deb$")"
|
LINUX_DEB_ASSET="$(pick_asset "\\.deb$")"
|
||||||
LINUX_TAR_ASSET="$(pick_asset "\\.tar\\.gz$")"
|
LINUX_TAR_ASSET="$(pick_asset "\\.tar\\.gz$")"
|
||||||
|
LINUX_APPIMAGE_ASSET="$(pick_asset "\\.AppImage$")"
|
||||||
|
|
||||||
build_link() {
|
build_link() {
|
||||||
local name="$1"
|
local name="$1"
|
||||||
@@ -163,6 +164,7 @@ jobs:
|
|||||||
MAC_URL="$(build_link "$MAC_ASSET")"
|
MAC_URL="$(build_link "$MAC_ASSET")"
|
||||||
LINUX_DEB_URL="$(build_link "$LINUX_DEB_ASSET")"
|
LINUX_DEB_URL="$(build_link "$LINUX_DEB_ASSET")"
|
||||||
LINUX_TAR_URL="$(build_link "$LINUX_TAR_ASSET")"
|
LINUX_TAR_URL="$(build_link "$LINUX_TAR_ASSET")"
|
||||||
|
LINUX_APPIMAGE_URL="$(build_link "$LINUX_APPIMAGE_ASSET")"
|
||||||
|
|
||||||
cat > release_notes.md <<EOF
|
cat > release_notes.md <<EOF
|
||||||
## 更新日志
|
## 更新日志
|
||||||
@@ -174,8 +176,9 @@ jobs:
|
|||||||
## 下载
|
## 下载
|
||||||
- Windows (Win10+): ${WINDOWS_URL:-$RELEASE_PAGE}
|
- Windows (Win10+): ${WINDOWS_URL:-$RELEASE_PAGE}
|
||||||
- macOS(M系列芯片): ${MAC_URL:-$RELEASE_PAGE}
|
- macOS(M系列芯片): ${MAC_URL:-$RELEASE_PAGE}
|
||||||
- Linux (.deb): ${LINUX_DEB_URL:-$RELEASE_PAGE}
|
- Linux (.deb) (即将废弃): ${LINUX_DEB_URL:-$RELEASE_PAGE}
|
||||||
- Linux (.tar.gz): ${LINUX_TAR_URL:-$RELEASE_PAGE}
|
- Linux (.tar.gz): ${LINUX_TAR_URL:-$RELEASE_PAGE}
|
||||||
|
- linux (.AppImage): ${LINUX_APPIMAGE_URL:-$RELEASE_PAGE}
|
||||||
|
|
||||||
> 如果某个平台链接暂时未生成,可进入完整发布页查看全部资源:$RELEASE_PAGE
|
> 如果某个平台链接暂时未生成,可进入完整发布页查看全部资源:$RELEASE_PAGE
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
@@ -1043,6 +1043,13 @@ function registerIpcHandlers() {
|
|||||||
return app.getVersion()
|
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 () => {
|
ipcMain.handle('log:getPath', async () => {
|
||||||
return join(app.getPath('userData'), 'logs', 'wcdb.log')
|
return join(app.getPath('userData'), 'logs', 'wcdb.log')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
onUpdateAvailable: (callback: (info: { version: string; releaseNotes: string }) => void) => {
|
onUpdateAvailable: (callback: (info: { version: string; releaseNotes: string }) => void) => {
|
||||||
ipcRenderer.on('app:updateAvailable', (_, info) => callback(info))
|
ipcRenderer.on('app:updateAvailable', (_, info) => callback(info))
|
||||||
return () => ipcRenderer.removeAllListeners('app:updateAvailable')
|
return () => ipcRenderer.removeAllListeners('app:updateAvailable')
|
||||||
}
|
},
|
||||||
|
checkWayland: () => ipcRenderer.invoke('app:checkWayland'),
|
||||||
},
|
},
|
||||||
|
|
||||||
// 日志
|
// 日志
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { app } from 'electron'
|
import { app } from 'electron'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { existsSync, readdirSync, statSync, readFileSync } from 'fs'
|
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 { promisify } from 'util'
|
||||||
import { createRequire } from 'module';
|
import { createRequire } from 'module';
|
||||||
import { spawn } from 'child_process'
|
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
const execFileAsync = promisify(execFile)
|
const execFileAsync = promisify(execFile)
|
||||||
@@ -46,8 +45,32 @@ export class KeyServiceLinux {
|
|||||||
onStatus?: (message: string, level: number) => void
|
onStatus?: (message: string, level: number) => void
|
||||||
): Promise<DbKeyResult> {
|
): Promise<DbKeyResult> {
|
||||||
try {
|
try {
|
||||||
|
// 1. 构造一个包含常用系统命令路径的环境变量,防止打包后找不到命令
|
||||||
|
const envWithPath = {
|
||||||
|
...process.env,
|
||||||
|
PATH: `${process.env.PATH || ''}:/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin`
|
||||||
|
};
|
||||||
|
|
||||||
onStatus?.('正在尝试结束当前微信进程...', 0)
|
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))
|
await new Promise(r => setTimeout(r, 1000))
|
||||||
|
|
||||||
@@ -76,11 +99,14 @@ export class KeyServiceLinux {
|
|||||||
env: cleanEnv
|
env: cleanEnv
|
||||||
});
|
});
|
||||||
|
|
||||||
child.on('error', () => {});
|
child.on('error', (err) => {
|
||||||
|
console.log(`[Debug] 拉起 ${binName} 失败:`, err.message);
|
||||||
|
});
|
||||||
|
|
||||||
child.unref();
|
child.unref();
|
||||||
} catch (e) {
|
console.log(`[Debug] 尝试拉起 ${binName} 完毕`);
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log(`[Debug] 尝试拉起 ${binName} 发生异常:`, e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,16 +114,35 @@ export class KeyServiceLinux {
|
|||||||
let pid = 0
|
let pid = 0
|
||||||
for (let i = 0; i < 15; i++) { // 最多等 15 秒
|
for (let i = 0; i < 15; i++) { // 最多等 15 秒
|
||||||
await new Promise(r => setTimeout(r, 1000))
|
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)
|
try {
|
||||||
if (pids.length > 0) {
|
const { stdout } = await execAsync('pidof wechat wechat-bin xwechat', { env: envWithPath });
|
||||||
pid = parseInt(pids[0], 10)
|
const pids = stdout.trim().split(/\s+/).filter(p => p);
|
||||||
break
|
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) {
|
if (!pid) {
|
||||||
const err = '未能自动启动微信,请手动启动并登录。'
|
const err = '未能自动启动微信,或获取PID失败,请查看控制台日志或手动启动并登录。'
|
||||||
onStatus?.(err, 2)
|
onStatus?.(err, 2)
|
||||||
return { success: false, error: err }
|
return { success: false, error: err }
|
||||||
}
|
}
|
||||||
@@ -108,6 +153,7 @@ export class KeyServiceLinux {
|
|||||||
|
|
||||||
return await this.getDbKey(pid, onStatus)
|
return await this.getDbKey(pid, onStatus)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
console.error('[Debug] 自动获取流程彻底崩溃:', err);
|
||||||
const errMsg = '自动获取微信 PID 失败: ' + err.message
|
const errMsg = '自动获取微信 PID 失败: ' + err.message
|
||||||
onStatus?.(errMsg, 2)
|
onStatus?.(errMsg, 2)
|
||||||
return { success: false, error: errMsg }
|
return { success: false, error: errMsg }
|
||||||
|
|||||||
@@ -95,6 +95,7 @@
|
|||||||
"linux": {
|
"linux": {
|
||||||
"icon": "public/icon.png",
|
"icon": "public/icon.png",
|
||||||
"target": [
|
"target": [
|
||||||
|
"appimage",
|
||||||
"deb",
|
"deb",
|
||||||
"tar.gz"
|
"tar.gz"
|
||||||
],
|
],
|
||||||
|
|||||||
67
src/App.tsx
67
src/App.tsx
@@ -104,6 +104,44 @@ function App() {
|
|||||||
// 数据收集同意状态
|
// 数据收集同意状态
|
||||||
const [showAnalyticsConsent, setShowAnalyticsConsent] = useState(false)
|
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(() => {
|
useEffect(() => {
|
||||||
if (location.pathname !== '/settings') {
|
if (location.pathname !== '/settings') {
|
||||||
settingsBackgroundRef.current = location
|
settingsBackgroundRef.current = location
|
||||||
@@ -432,6 +470,8 @@ function App() {
|
|||||||
checkLock()
|
checkLock()
|
||||||
}, [isAgreementWindow, isOnboardingWindow, isVideoPlayerWindow])
|
}, [isAgreementWindow, isOnboardingWindow, isVideoPlayerWindow])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 独立协议窗口
|
// 独立协议窗口
|
||||||
if (isAgreementWindow) {
|
if (isAgreementWindow) {
|
||||||
return <AgreementPage />
|
return <AgreementPage />
|
||||||
@@ -614,6 +654,33 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{showWaylandWarning && (
|
||||||
|
<div className="agreement-overlay">
|
||||||
|
<div className="agreement-modal">
|
||||||
|
<div className="agreement-header">
|
||||||
|
<Shield size={32} />
|
||||||
|
<h2>环境兼容性提示 (Wayland)</h2>
|
||||||
|
</div>
|
||||||
|
<div className="agreement-content">
|
||||||
|
<div className="agreement-text">
|
||||||
|
<p>检测到您当前正在使用 <strong>Wayland</strong> 显示服务器。</p>
|
||||||
|
<p>在 Wayland 环境下,出于系统级的安全与设计机制,<strong>应用程序无法直接控制新弹出窗口的位置</strong>。</p>
|
||||||
|
<p>这可能导致某些独立窗口(如消息通知、图片查看器等)出现位置随机、或不受控制的情况。这是底层机制导致的,对此我们无能为力。</p>
|
||||||
|
<br />
|
||||||
|
<p>如果您觉得窗口位置异常严重影响了使用体验,建议尝试:</p>
|
||||||
|
<p>1. 在系统登录界面,将会话切换回 <strong>X11 (Xorg)</strong> 模式。</p>
|
||||||
|
<p>2. 修改您的桌面管理器 (WM/DE) 配置,强制指定该应用程序的窗口规则。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="agreement-footer">
|
||||||
|
<div className="agreement-actions">
|
||||||
|
<button className="btn btn-primary" onClick={handleDismissWaylandWarning}>我知道了,不再提示</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 更新提示对话框 */}
|
{/* 更新提示对话框 */}
|
||||||
<UpdateDialog
|
<UpdateDialog
|
||||||
open={showUpdateDialog}
|
open={showUpdateDialog}
|
||||||
|
|||||||
@@ -175,6 +175,21 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
|
|
||||||
const isClearingCache = isClearingAnalyticsCache || isClearingImageCache || isClearingAllCache
|
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 可用性
|
// 检查 Hello 可用性
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (window.PublicKeyCredential) {
|
if (window.PublicKeyCredential) {
|
||||||
@@ -1169,6 +1184,11 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>通知显示位置</label>
|
<label>通知显示位置</label>
|
||||||
<span className="form-hint">选择通知弹窗在屏幕上的显示位置</span>
|
<span className="form-hint">选择通知弹窗在屏幕上的显示位置</span>
|
||||||
|
{isWayland && (
|
||||||
|
<span className="form-hint" style={{ color: '#ff4d4f', marginTop: '4px', display: 'block' }}>
|
||||||
|
⚠️ 注意:Wayland 环境下该配置可能无效!
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<div className="custom-select">
|
<div className="custom-select">
|
||||||
<div
|
<div
|
||||||
className={`custom-select-trigger ${positionDropdownOpen ? 'open' : ''}`}
|
className={`custom-select-trigger ${positionDropdownOpen ? 'open' : ''}`}
|
||||||
@@ -1652,34 +1672,49 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const renderCacheTab = () => (
|
const renderCacheTab = () => (
|
||||||
<div className="tab-content">
|
<div className="tab-content">
|
||||||
<p className="section-desc">管理应用缓存数据</p>
|
<p className="section-desc">管理应用缓存数据</p>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>缓存目录 <span className="optional">(可选)</span></label>
|
<label>缓存目录 <span className="optional">(可选)</span></label>
|
||||||
<span className="form-hint">留空使用默认目录</span>
|
<span className="form-hint">留空使用默认目录</span>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="留空使用默认目录"
|
placeholder="留空使用默认目录"
|
||||||
value={cachePath}
|
value={cachePath}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const value = e.target.value
|
const value = e.target.value
|
||||||
setCachePath(value)
|
setCachePath(value)
|
||||||
scheduleConfigSave('cachePath', () => configService.setCachePath(value))
|
scheduleConfigSave('cachePath', () => configService.setCachePath(value))
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="btn-row">
|
|
||||||
<button className="btn btn-secondary" onClick={handleSelectCachePath}><FolderOpen size={16} /> 浏览选择</button>
|
<div style={{ marginTop: '8px', fontSize: '13px', color: 'var(--text-secondary)' }}>
|
||||||
<button
|
当前缓存位置:
|
||||||
className="btn btn-secondary"
|
<code style={{
|
||||||
onClick={async () => {
|
background: 'var(--bg-secondary)',
|
||||||
setCachePath('')
|
padding: '3px 6px',
|
||||||
await configService.setCachePath('')
|
borderRadius: '4px',
|
||||||
}}
|
userSelect: 'all',
|
||||||
>
|
wordBreak: 'break-all',
|
||||||
<RotateCcw size={16} /> 恢复默认
|
marginLeft: '4px'
|
||||||
</button>
|
}}>
|
||||||
|
{cachePath || (isMac ? '~/Documents/WeFlow' : isLinux ? '~/Documents/WeFlow' : '系统 文档\\WeFlow 目录')}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="btn-row" style={{ marginTop: '12px' }}>
|
||||||
|
<button className="btn btn-secondary" onClick={handleSelectCachePath}><FolderOpen size={16} /> 浏览选择</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-secondary"
|
||||||
|
onClick={async () => {
|
||||||
|
setCachePath('')
|
||||||
|
await configService.setCachePath('')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RotateCcw size={16} /> 恢复默认
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="btn-row">
|
<div className="btn-row">
|
||||||
<button className="btn btn-secondary" onClick={handleClearAnalyticsCache} disabled={isClearingCache}>
|
<button className="btn btn-secondary" onClick={handleClearAnalyticsCache} disabled={isClearingCache}>
|
||||||
|
|||||||
1
src/types/electron.d.ts
vendored
1
src/types/electron.d.ts
vendored
@@ -61,6 +61,7 @@ export interface ElectronAPI {
|
|||||||
ignoreUpdate: (version: string) => Promise<{ success: boolean }>
|
ignoreUpdate: (version: string) => Promise<{ success: boolean }>
|
||||||
onDownloadProgress: (callback: (progress: number) => void) => () => void
|
onDownloadProgress: (callback: (progress: number) => void) => () => void
|
||||||
onUpdateAvailable: (callback: (info: { version: string; releaseNotes: string }) => void) => () => void
|
onUpdateAvailable: (callback: (info: { version: string; releaseNotes: string }) => void) => () => void
|
||||||
|
checkWayland: () => Promise<boolean>
|
||||||
}
|
}
|
||||||
notification: {
|
notification: {
|
||||||
show: (data: { title: string; content: string; avatarUrl?: string; sessionId: string }) => Promise<{ success?: boolean; error?: string } | void>
|
show: (data: { title: string; content: string; avatarUrl?: string; sessionId: string }) => Promise<{ success?: boolean; error?: string } | void>
|
||||||
|
|||||||
Reference in New Issue
Block a user