滚动截图,细节调整

This commit is contained in:
zhongluofeng
2026-09-04 19:56:23 +08:00
parent 6b9f71da08
commit 27ad5d89a5
34 changed files with 3766 additions and 559 deletions
+22 -1
View File
@@ -311,6 +311,8 @@ export const useMonitorStore = defineStore('monitor', () => {
const snapshot = ref<SensorSnapshot | null>(null)
const kernelInfo = ref<MonitorKernelInfo | null>(null)
const errorMsg = ref<string | null>(null)
/** 提权运行但 PawnIO 驱动缺失(CPU 温度/功耗大概率无法读取) */
const pawnIoMissing = ref(false)
/** 累计收到的 monitor-data 事件数,用于诊断与"已连接"判定 */
const eventCount = ref(0)
/** 最近一次收到 monitor-data 的时间戳(ms */
@@ -690,10 +692,12 @@ export const useMonitorStore = defineStore('monitor', () => {
// OSD 开启时同步推送(合并到每帧一次,避免与网速事件重复推送)
if (osdConfig.value.overlayEnabled) scheduleOsdPush()
}))
unlistenFns.push(await listen(EVENTS.monitorReady, () => {
unlistenFns.push(await listen<{ isAdmin?: boolean; pawnIoInstalled?: boolean }>(EVENTS.monitorReady, (e) => {
refreshStatus()
// Kernel 就绪后主动拉取一次快照,避免等待 SSE 首事件导致 UI 空白
fetchSnapshot()
// PawnIO 驱动缺失检测:提权运行但仍缺驱动 → CPU 温度/功耗大概率无法读取
pawnIoMissing.value = e.payload?.isAdmin === true && e.payload?.pawnIoInstalled === false
}))
unlistenFns.push(await listen(EVENTS.monitorLoading, () => {
// 后端正在等待 Kernel ready,刷新状态以反映 running=true
@@ -1154,6 +1158,22 @@ export const useMonitorStore = defineStore('monitor', () => {
void pushOsdState()
})
osdEventUnlisteners.push(unlistenReq)
// 监听游戏全屏事件:前台出现全屏应用(游戏)时隐藏 OSD,
// 避免透明置顶悬浮窗占用 DWM 合成路径导致游戏掉帧;退出全屏后恢复。
const unlistenGameActive = await listen(EVENTS.osdGameActive, async () => {
gameFullscreen.value = true
if (osdConfig.value.overlayEnabled && osdConfig.value.gameAutoHide) {
await hideOverlayWindow()
}
})
osdEventUnlisteners.push(unlistenGameActive)
const unlistenGameInactive = await listen(EVENTS.osdGameInactive, async () => {
gameFullscreen.value = false
if (osdConfig.value.overlayEnabled && osdConfig.value.gameAutoHide) {
await ensureOverlayWindow()
}
})
osdEventUnlisteners.push(unlistenGameInactive)
}
/** initOsd 幂等守卫:监听/配置 watcher 只注册一次(App 启动 + 模块挂载均会调用) */
@@ -1277,6 +1297,7 @@ export const useMonitorStore = defineStore('monitor', () => {
snapshot,
kernelInfo,
errorMsg,
pawnIoMissing,
eventCount,
lastEventTime,
starting,
+71 -3
View File
@@ -253,8 +253,48 @@ export const useScreenshotStore = defineStore('screenshot', () => {
await ensureOverlay()
}
async function openEditor() {
new WebviewWindow(`screenshot-editor-${Date.now()}`, {
// ===== 常驻截图编辑器窗口 =====
const EDITOR_LABEL = WINDOWS.screenshotEditor
let editorWin: WebviewWindow | null = null
let editorReadyUnlisten: UnlistenFn | null = null
let editorReadyListenerInit = false
let editorReadyResolve: (() => void) | null = null
let editorReadyPromise: Promise<void> | null = null
function resetEditorReady() {
editorReadyPromise = new Promise<void>((resolve) => {
editorReadyResolve = resolve
})
}
async function waitEditorReady() {
await Promise.race([
editorReadyPromise,
new Promise<void>((r) => setTimeout(r, 5000)),
])
}
async function ensureEditorListener() {
if (editorReadyListenerInit) return
editorReadyListenerInit = true
resetEditorReady()
editorReadyUnlisten = await listen(EVENTS.screenshotEditorReady, () => {
editorReadyResolve?.()
})
}
/** 创建常驻编辑器窗口(隐藏,首次打开时创建一次,之后 show 复用不重建 WebView */
async function ensureEditorWindow() {
if (editorWin) {
const existing = await WebviewWindow.getByLabel(EDITOR_LABEL).catch(() => null)
if (existing) {
editorWin = existing
return
}
editorWin = null
}
resetEditorReady()
editorWin = new WebviewWindow(EDITOR_LABEL, {
url: 'index.html#screenshot-editor',
title: '截图编辑器',
width: 960,
@@ -268,10 +308,18 @@ export const useScreenshotStore = defineStore('screenshot', () => {
resizable: true,
shadow: true,
focus: true,
visible: true,
visible: false,
})
}
/** 打开编辑器:确保常驻窗口就绪后通知其加载编辑器图片槽中的新图(图由 Rust 侧直接写入) */
async function openEditor() {
await ensureEditorListener()
await ensureEditorWindow()
await waitEditorReady()
await emit(EVENTS.screenshotEditorLoad)
}
// ===== 历史 / 导出 =====
/** 由完整 PNG base64 生成缩略图 data URLcanvas 缩放至 ~256px 宽,JPEG 压缩) */
function makeThumb(pngBase64: string, width: number, height: number): Promise<string> {
@@ -468,6 +516,17 @@ export const useScreenshotStore = defineStore('screenshot', () => {
})
}
// ===== 滚动截图完成 → 进编辑器 =====
/** 滚动截图完成的监听(覆盖层通知 → 打开常驻编辑器;PNG 原始字节已由 Rust 写入编辑器图片槽) */
let scrollEditorUnlisten: UnlistenFn | null = null
async function initScrollEditorListener() {
if (scrollEditorUnlisten) return
scrollEditorUnlisten = await listen(EVENTS.scrollToEditor, () => {
void openEditor()
})
}
// ===== 全局快捷键 =====
/** 监听 Rust 侧 emit 的 'screenshot-shortcut' 事件(快捷键按下时触发) */
async function initShortcutListener() {
@@ -627,6 +686,14 @@ export const useScreenshotStore = defineStore('screenshot', () => {
pinReadyUnlisten()
pinReadyUnlisten = null
}
if (editorReadyUnlisten) {
editorReadyUnlisten()
editorReadyUnlisten = null
}
if (scrollEditorUnlisten) {
scrollEditorUnlisten()
scrollEditorUnlisten = null
}
}
function destroyShortcutListener() {
@@ -662,6 +729,7 @@ export const useScreenshotStore = defineStore('screenshot', () => {
setPinShortcut,
initExportListener,
destroyExportListener,
initScrollEditorListener,
initShortcutListener,
initShortcutRegistration,
destroyShortcutListener,
+163
View File
@@ -0,0 +1,163 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { listen, type UnlistenFn } from '@tauri-apps/api/event'
import { invoke } from '@tauri-apps/api/core'
import { EVENTS } from '@/lib/constants'
import { useMonitorStore } from '@/stores/monitorStore'
import { createLogger } from '@/lib/logger'
const logger = createLogger('updater')
/** 更新进度事件载荷(与 Rust UpdateProgress 对应) */
export interface UpdateProgress {
stage: string
percent: number
downloadedBytes: number
totalBytes: number | null
message: string
}
/**
* 应用 / ThingHK 内核更新的全局编排状态。
*
* 把更新进度与"need_stop 确认"从集团件作用域提升到全局 store:
* 切换模块导致 GeneralSettings 卸载后,进行中的下载进度、need_stop 等待、
* done 后的内核重启都能跨组件存活并正确完成,避免"切回来进度丢失 / 更新卡死 / 不重启内核"。
*/
export const useUpdaterStore = defineStore('updater', () => {
/** 应用本体更新中 */
const appUpdating = ref(false)
/** ThingHK 内核更新中 */
const kernelUpdating = ref(false)
/** 更新进度(下载/解压/need_stop/done 共用一条进度展示) */
const progress = ref<UpdateProgress | null>(null)
/** 应用更新下载中可取消(下载完成进入 applying 后不可取消) */
const downloadCancellable = ref(false)
/** ThingHK 内核包下载中可取消 */
const kernelDownloadCancellable = ref(false)
/** 停止监控内核的确认弹窗状态(need_stop 阶段由后端 event 触发) */
const thinghkConfirmState = ref<{
open: boolean
wasRunning: boolean
busy: boolean
resolved: boolean
}>({
open: false,
wasRunning: false,
busy: false,
resolved: false,
})
let subscribed = false
const unlisteners: UnlistenFn[] = []
let handlingNeedStop = false
/** 若应用被最小化/隐藏到后台,先弹到前台再显示确认框 */
const focusMain = () => {
invoke('quickpanel_focus_main_window').catch(() => { /* 忽略 */ })
}
/** need_stop:记录内核是否在运行,弹出确认框等待用户停止并继续 */
function handleNeedStop() {
if (handlingNeedStop) return
handlingNeedStop = true
try {
focusMain()
const monitor = useMonitorStore()
const wasRunning = monitor.status?.running ?? false
thinghkConfirmState.value = { open: true, wasRunning, busy: false, resolved: false }
} finally {
handlingNeedStop = false
}
}
/** 全局订阅 UPDATE_PROGRESSneed_stop / done / error 与组件挂载无关,切换标签不中断更新 */
async function subscribe() {
if (subscribed) return
subscribed = true
try {
unlisteners.push(
await listen<UpdateProgress>(EVENTS.updateProgress, (e) => {
progress.value = e.payload
const p = e.payload
if (p.stage === 'need_stop') {
handleNeedStop()
} else if (p.stage === 'done') {
const wasRunning = thinghkConfirmState.value.wasRunning
kernelUpdating.value = false
progress.value = null
// 更新前内核在运行 → 替换完成后恢复(重启内核),保持内核状态
if (wasRunning) {
const monitor = useMonitorStore()
void monitor.start()
}
} else if (p.stage === 'error') {
kernelUpdating.value = false
progress.value = null
}
}),
)
} catch (e) {
logger.error('[updater] 订阅更新进度失败: ' + e)
}
}
/** 确认停止内核并唤醒后端 apply 继续解压替换 */
async function confirmNeedStop() {
const s = thinghkConfirmState.value
if (s.resolved) return
s.resolved = true
s.busy = true
try {
if (s.wasRunning) {
const monitor = useMonitorStore()
await monitor.stop()
}
await invoke('update_thinghk_confirm')
} catch (e) {
logger.error('[updater] 停止监控内核失败: ' + e)
// 停止失败则中止更新,避免替换阶段因 exe 占用而报错
try {
await invoke('update_thinghk_cancel')
} catch { /* 忽略 */ }
} finally {
s.busy = false
s.open = false
}
}
/** 取消 ThingHK 内核更新(中止后端等待中的 apply 流程) */
function cancelNeedStop() {
const s = thinghkConfirmState.value
if (s.resolved) return
s.resolved = true
s.open = false
invoke('update_thinghk_cancel').catch(() => { /* 忽略 */ })
}
/** 复位全局更新状态(订阅与状态一并清理,供测试/异常恢复用) */
function reset() {
appUpdating.value = false
kernelUpdating.value = false
progress.value = null
downloadCancellable.value = false
kernelDownloadCancellable.value = false
thinghkConfirmState.value = { open: false, wasRunning: false, busy: false, resolved: false }
subscribed = false
unlisteners.forEach((fn) => fn())
unlisteners.length = 0
}
return {
appUpdating,
kernelUpdating,
progress,
downloadCancellable,
kernelDownloadCancellable,
thinghkConfirmState,
subscribe,
confirmNeedStop,
cancelNeedStop,
reset,
}
})