diff --git a/CHANGELOG.md b/CHANGELOG.md index bda6b9d..9c1b709 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,15 @@ ### 变更 - 暂无 +## [4.1.6] - 2026-04-08 + +### 修复 +- 修复 Windows 独立窗口标题栏仍与原生最小化、最大化、关闭按钮重叠的问题,改为按 `windowControlsOverlay` 实际可用区域实时计算右侧避让 + +### 变更 +- 渲染层新增对原生标题栏 overlay 几何变化的监听,窗口缩放或系统控件占位变化时会自动同步左右安全区 +- 移除独立窗口标题栏额外写死的 Windows 右侧补偿,统一由共享 chrome 安全区变量接管 + ## [4.1.5] - 2026-04-08 ### 修复 diff --git a/README.md b/README.md index 7fee9a1..aba2910 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ **一款现代化的微信聊天记录查看与分析工具** [![License](https://img.shields.io/badge/license-CC--BY--NC--SA--4.0-blue.svg)](LICENSE) -[![Version](https://img.shields.io/badge/version-4.1.5-green.svg)](package.json) +[![Version](https://img.shields.io/badge/version-4.1.6-green.svg)](package.json) [![Platform](https://img.shields.io/badge/platform-Windows-0078D6.svg?logo=windows)]() [![Electron](https://img.shields.io/badge/Electron-39-47848F.svg?logo=electron)]() [![React](https://img.shields.io/badge/React-19-61DAFB.svg?logo=react)]() diff --git a/package-lock.json b/package-lock.json index ee7f78d..2daf99a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ciphertalk", - "version": "4.1.5", + "version": "4.1.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ciphertalk", - "version": "4.1.5", + "version": "4.1.6", "hasInstallScript": true, "license": "CC-BY-NC-SA-4.0", "dependencies": { diff --git a/package.json b/package.json index a2fafef..a44a2c7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ciphertalk", - "version": "4.1.5", + "version": "4.1.6", "description": "密语 - 微信聊天记录查看工具", "author": "ILoveBingLu", "license": "CC-BY-NC-SA-4.0", diff --git a/src/App.tsx b/src/App.tsx index 2e2bccf..7412b80 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -36,7 +36,7 @@ import { initTldList } from './utils/linkify' import LockScreen from './pages/LockScreen' import { useAuthStore } from './stores/authStore' import { X, Shield, Loader2 } from 'lucide-react' -import { applyWindowChromeToDocument } from './utils/windowChrome' +import { applyWindowChromeToDocument, syncWindowControlsOverlayToDocument } from './utils/windowChrome' import './App.scss' type AppUpdateInfo = { @@ -122,20 +122,42 @@ function App() { useEffect(() => { let cancelled = false + let removeOverlayListeners: (() => void) | undefined - const applyPlatformChrome = (platform?: string) => { - if (cancelled) return - applyWindowChromeToDocument(platform) + const bindPlatformChrome = (platform?: string) => { + const syncPlatformChrome = () => { + if (cancelled) return + applyWindowChromeToDocument(platform) + syncWindowControlsOverlayToDocument(platform) + } + + syncPlatformChrome() + + const overlay = navigator.windowControlsOverlay + if (!overlay) { + return + } + + overlay.addEventListener('geometrychange', syncPlatformChrome) + window.addEventListener('resize', syncPlatformChrome) + + removeOverlayListeners = () => { + overlay.removeEventListener('geometrychange', syncPlatformChrome) + window.removeEventListener('resize', syncPlatformChrome) + } } void window.electronAPI.app.getPlatformInfo().then((info) => { - applyPlatformChrome(info.platform) + if (cancelled) return + bindPlatformChrome(info.platform) }).catch(() => { - applyPlatformChrome('win32') + if (cancelled) return + bindPlatformChrome('win32') }) return () => { cancelled = true + removeOverlayListeners?.() } }, []) diff --git a/src/components/TitleBar.scss b/src/components/TitleBar.scss index 560518c..1f4b978 100644 --- a/src/components/TitleBar.scss +++ b/src/components/TitleBar.scss @@ -123,8 +123,6 @@ } .title-bar.variant-standalone.is-win { - padding-right: calc(var(--window-controls-right-safe) + 8px); - .title-bar-left { flex: 1; min-width: 0; @@ -133,7 +131,6 @@ .title-bar-right { flex-shrink: 0; overflow: visible; - margin-right: 4px; } .titles { diff --git a/src/types/windowControlsOverlay.d.ts b/src/types/windowControlsOverlay.d.ts new file mode 100644 index 0000000..35f490e --- /dev/null +++ b/src/types/windowControlsOverlay.d.ts @@ -0,0 +1,22 @@ +interface WindowControlsOverlay extends EventTarget { + readonly visible: boolean + getTitlebarAreaRect(): DOMRect + addEventListener( + type: 'geometrychange', + listener: (this: WindowControlsOverlay, event: Event) => void, + options?: boolean | AddEventListenerOptions + ): void + removeEventListener( + type: 'geometrychange', + listener: (this: WindowControlsOverlay, event: Event) => void, + options?: boolean | EventListenerOptions + ): void +} + +declare global { + interface Navigator { + windowControlsOverlay?: WindowControlsOverlay + } +} + +export {} diff --git a/src/utils/windowChrome.ts b/src/utils/windowChrome.ts index e60f9fa..ef792cc 100644 --- a/src/utils/windowChrome.ts +++ b/src/utils/windowChrome.ts @@ -6,6 +6,11 @@ type WindowChromeMetrics = { toolbarGap: string } +type WindowControlsOverlayPadding = { + left: number + right: number +} + const DEFAULT_PLATFORM: WindowPlatform = 'win32' const WINDOW_CHROME_HEIGHT = '40px' @@ -27,6 +32,30 @@ const WINDOW_CHROME_METRICS: Record = { } } +const WINDOW_CONTROLS_OVERLAY_PADDING: Record = { + win32: { + left: 16, + right: 12 + }, + darwin: { + left: 12, + right: 16 + }, + linux: { + left: 16, + right: 12 + } +} + +function parsePixels(value: string) { + const parsed = Number.parseFloat(value) + return Number.isFinite(parsed) ? parsed : 0 +} + +function toPixels(value: number) { + return `${Math.max(0, Math.round(value))}px` +} + export function normalizeWindowPlatform(platform?: string | null): WindowPlatform { if (platform === 'darwin' || platform === 'linux' || platform === 'win32') { return platform @@ -52,3 +81,31 @@ export function applyWindowChromeToDocument(platform?: string | null, root: HTML root.style.setProperty('--window-controls-right-safe', metrics.controlsRightSafe) root.style.setProperty('--window-toolbar-gap', metrics.toolbarGap) } + +export function syncWindowControlsOverlayToDocument( + platform?: string | null, + root: HTMLElement = document.documentElement, + viewportWidth: number = window.innerWidth +) { + const overlay = navigator.windowControlsOverlay + if (!overlay || !overlay.visible || viewportWidth <= 0) { + return false + } + + const titlebarAreaRect = overlay.getTitlebarAreaRect() + if (titlebarAreaRect.width <= 0 || titlebarAreaRect.height <= 0) { + return false + } + + const normalizedPlatform = normalizeWindowPlatform(platform) + const overlayPadding = WINDOW_CONTROLS_OVERLAY_PADDING[normalizedPlatform] + const controlsRightWidth = Math.max(0, viewportWidth - titlebarAreaRect.x - titlebarAreaRect.width) + const chromeHeight = Math.max(parsePixels(WINDOW_CHROME_HEIGHT), titlebarAreaRect.height) + const controlsLeftSafe = Math.max(overlayPadding.left, titlebarAreaRect.x + overlayPadding.left) + const controlsRightSafe = Math.max(overlayPadding.right, controlsRightWidth + overlayPadding.right) + + root.style.setProperty('--window-chrome-height', toPixels(chromeHeight)) + root.style.setProperty('--window-controls-left-safe', toPixels(controlsLeftSafe)) + root.style.setProperty('--window-controls-right-safe', toPixels(controlsRightSafe)) + return true +}