From 473481149cd7178124ea1e5b6287979c099c1f76 Mon Sep 17 00:00:00 2001 From: Windsland52 <86581225+Windsland52@users.noreply.github.com> Date: Mon, 2 Mar 2026 18:46:56 +0800 Subject: [PATCH 1/5] feat: pin --- src-tauri/capabilities/default.json | 2 ++ src/components/TitleBar.tsx | 27 ++++++++++++++++++++++++++- src/i18n/locales/en-US.ts | 2 ++ src/i18n/locales/ja-JP.ts | 2 ++ src/i18n/locales/ko-KR.ts | 2 ++ src/i18n/locales/zh-CN.ts | 2 ++ src/i18n/locales/zh-TW.ts | 2 ++ 7 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 46856ddc..4d0414ca 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -18,6 +18,8 @@ "core:window:allow-unminimize", "core:window:allow-toggle-maximize", "core:window:allow-is-maximized", + "core:window:allow-set-always-on-top", + "core:window:allow-is-always-on-top", "core:window:allow-set-focus", "core:window:allow-close", "core:window:allow-start-dragging", diff --git a/src/components/TitleBar.tsx b/src/components/TitleBar.tsx index 17182ceb..c8e787d7 100644 --- a/src/components/TitleBar.tsx +++ b/src/components/TitleBar.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { Minus, Square, X, Copy, Box } from 'lucide-react'; +import { Minus, Square, X, Copy, Box, Pin } from 'lucide-react'; import { useAppStore } from '@/stores/appStore'; import { getInterfaceLangKey } from '@/i18n'; import { loadIconAsDataUrl } from '@/services/contentResolver'; @@ -13,6 +13,7 @@ type Platform = 'windows' | 'macos' | 'linux' | 'unknown'; export function TitleBar() { const { t } = useTranslation(); const [isMaximized, setIsMaximized] = useState(false); + const [isAlwaysOnTop, setIsAlwaysOnTop] = useState(false); const [platform, setPlatform] = useState('unknown'); const [iconUrl, setIconUrl] = useState(undefined); @@ -52,6 +53,7 @@ export function TitleBar() { // 获取初始状态 setIsMaximized(await appWindow.isMaximized()); + setIsAlwaysOnTop(await appWindow.isAlwaysOnTop()); // 监听窗口状态变化 unlisten = await appWindow.onResized(async () => { @@ -79,6 +81,18 @@ export function TitleBar() { } }; + const handleToggleAlwaysOnTop = async () => { + if (!isTauri()) return; + try { + const { getCurrentWindow } = await import('@tauri-apps/api/window'); + const newState = !isAlwaysOnTop; + await getCurrentWindow().setAlwaysOnTop(newState); + setIsAlwaysOnTop(newState); + } catch (err) { + loggers.ui.warn('Failed to toggle always on top:', err); + } + }; + const handleToggleMaximize = async () => { if (!isTauri()) return; try { @@ -142,6 +156,17 @@ export function TitleBar() { {/* 右侧:窗口控制按钮(仅 Windows/Linux 显示) */} {isTauri() && (
+ diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts index 4bddb52c..5bc0ad9c 100644 --- a/src/i18n/locales/en-US.ts +++ b/src/i18n/locales/en-US.ts @@ -41,6 +41,7 @@ export default { close: 'Close', pin: 'Pin', unpin: 'Unpin', + pinDisabled: 'Pin disabled (foreground screenshot)', }, // Settings diff --git a/src/i18n/locales/ja-JP.ts b/src/i18n/locales/ja-JP.ts index b4446769..c7f1bad9 100644 --- a/src/i18n/locales/ja-JP.ts +++ b/src/i18n/locales/ja-JP.ts @@ -40,6 +40,7 @@ export default { close: '閉じる', pin: '常に手前に表示', unpin: '常に手前に表示を解除', + pinDisabled: 'フォアグラウンドスクリーンショットのため固定不可', }, // 設定 diff --git a/src/i18n/locales/ko-KR.ts b/src/i18n/locales/ko-KR.ts index d1a8c191..3feee5b6 100644 --- a/src/i18n/locales/ko-KR.ts +++ b/src/i18n/locales/ko-KR.ts @@ -40,6 +40,7 @@ export default { close: '닫기', pin: '항상 위', unpin: '항상 위 해제', + pinDisabled: '전면 스크린샷 사용 중, 고정 불가', }, // 설정 diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index 2c31c2bc..0937edfb 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -41,6 +41,7 @@ export default { close: '关闭', pin: '置顶', unpin: '取消置顶', + pinDisabled: '当前控制器使用前台截图,无法置顶', }, // 设置 diff --git a/src/i18n/locales/zh-TW.ts b/src/i18n/locales/zh-TW.ts index d443ef4b..de314881 100644 --- a/src/i18n/locales/zh-TW.ts +++ b/src/i18n/locales/zh-TW.ts @@ -40,6 +40,7 @@ export default { close: '關閉', pin: '置頂', unpin: '取消置頂', + pinDisabled: '目前控制器使用前台截圖,無法置頂', }, // 設定 From 93a96d00fc6665fc21244ad2f90b5eb731a9c549 Mon Sep 17 00:00:00 2001 From: Windsland52 <86581225+Windsland52@users.noreply.github.com> Date: Mon, 2 Mar 2026 22:38:38 +0800 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20=E5=88=87=E6=8D=A2=E5=88=B0=E5=89=8D?= =?UTF-8?q?=E5=8F=B0=E6=88=AA=E5=9B=BE=E4=BC=9A=E5=8F=96=E6=B6=88=E7=BD=AE?= =?UTF-8?q?=E9=A1=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/TitleBar.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/components/TitleBar.tsx b/src/components/TitleBar.tsx index b0a0b271..e6bb2173 100644 --- a/src/components/TitleBar.tsx +++ b/src/components/TitleBar.tsx @@ -62,6 +62,16 @@ export function TitleBar() { return false; }, [projectInterface, instances, activeInstanceId]); + // 当置顶被禁用时,自动取消置顶 + useEffect(() => { + if (isPinDisabled && isAlwaysOnTop && windowRef.current) { + setIsAlwaysOnTop(false); + windowRef.current.setAlwaysOnTop(false).catch((err: unknown) => { + loggers.ui.warn('Failed to disable always on top:', err); + }); + } + }, [isPinDisabled, isAlwaysOnTop]); + // 监听窗口最大化状态变化(仅 Windows,用于切换最大化/还原按钮图标) useEffect(() => { if (!isTauri() || platform !== 'windows') return; From 83c1182d744d72aead6b8c76eb71a263f734a533 Mon Sep 17 00:00:00 2001 From: Windsland52 <86581225+Windsland52@users.noreply.github.com> Date: Tue, 3 Mar 2026 00:31:57 +0800 Subject: [PATCH 5/5] fix: bug --- src/components/TitleBar.tsx | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/components/TitleBar.tsx b/src/components/TitleBar.tsx index e6bb2173..3b7b7dad 100644 --- a/src/components/TitleBar.tsx +++ b/src/components/TitleBar.tsx @@ -10,6 +10,9 @@ import { isTauri } from '@/utils/paths'; // 平台类型 type Platform = 'windows' | 'macos' | 'linux' | 'unknown'; +// Win32 前台截图方法(需要禁用置顶) +const FOREGROUND_SCREENCAP_METHODS = new Set(['GDI', '1', 'DXGI_DesktopDup', '4', 'DXGI_DesktopDup_Window', '8', 'ScreenDC', '32']); + export function TitleBar() { const { t } = useTranslation(); const [isMaximized, setIsMaximized] = useState(false); @@ -54,8 +57,7 @@ export function TitleBar() { // 仅 Win32 的特定前台截图方法需要禁用置顶 if (controller.type === 'Win32' && controller.win32?.screencap) { const screencap = controller.win32.screencap; - const foregroundMethods = new Set(['GDI', '1', 'DXGI_DesktopDup', '4', 'DXGI_DesktopDup_Window', '8', 'ScreenDC', '32']); - return foregroundMethods.has(screencap); + return FOREGROUND_SCREENCAP_METHODS.has(screencap); } // 其他情况(ADB、PlayCover、Gamepad 或 Win32 后台截图)均支持置顶 @@ -72,9 +74,9 @@ export function TitleBar() { } }, [isPinDisabled, isAlwaysOnTop]); - // 监听窗口最大化状态变化(仅 Windows,用于切换最大化/还原按钮图标) + // 初始化 Tauri 窗口引用,并在 Windows 上监听最大化状态变化 useEffect(() => { - if (!isTauri() || platform !== 'windows') return; + if (!isTauri()) return; let unlisten: (() => void) | null = null; @@ -85,13 +87,16 @@ export function TitleBar() { windowRef.current = appWindow; // 获取初始状态 - setIsMaximized(await appWindow.isMaximized()); setIsAlwaysOnTop(await appWindow.isAlwaysOnTop()); - // 监听窗口状态变化 - unlisten = await appWindow.onResized(async () => { + // 仅 Windows 需要追踪最大化状态(macOS/Linux 使用原生标题栏) + if (platform === 'windows') { setIsMaximized(await appWindow.isMaximized()); - }); + + unlisten = await appWindow.onResized(async () => { + setIsMaximized(await appWindow.isMaximized()); + }); + } } catch (err) { loggers.ui.warn('Failed to setup window state listener:', err); }