Skip to content

feat: 灵动岛支持其他应用全屏时自动隐藏 - #407

Open
dtotaku wants to merge 1 commit into
SPlayer-Dev:devfrom
dtotaku:feat/dynamic-island-hide-on-fullscreen
Open

dtotaku wants to merge 1 commit into
SPlayer-Dev:devfrom
dtotaku:feat/dynamic-island-hide-on-fullscreen

Conversation

@dtotaku

@dtotaku dtotaku commented Oct 22, 2026

Copy link
Copy Markdown

改动类型

  • 新功能(feat)
  • 缺陷修复(fix)
  • 重构 / 优化(不改变对外行为)
  • 文档(docs)
  • 其他(请在「改动说明」中注明)

是否包含破坏性变更

  • 是(请在「改动说明」中详细描述)
  • 否

改动说明

动机:灵动岛当前用 setAlwaysOnTop(true, "screen-saver") 一刀切常驻置顶,没有任何前台全屏探测。用户玩游戏、看视频、演示或浏览器全屏时,灵动岛会一直压在全屏内容之上,既挡视线也无法被全屏窗口覆盖。

方案:新增一个只做「前台窗口是否全屏」判断的原生模块,主进程轮询该状态,全屏时隐藏灵动岛、退出后恢复显示并重新置顶。

之所以选择隐藏而不是取消置顶:topmost 窗口与全屏窗口是同层竞争关系,只要灵动岛还在屏幕上,要么压住全屏内容,要么被全屏内容压住,隐藏是唯一干净的处理。

主要变更

1. 新增原生模块 native/window-monitor(仅 Windows)

导出单个函数 isForegroundFullscreen(excludeHwnd?: number): boolean,无状态、只做单次同步查询,不持有任何窗口句柄。判定链:

GetForegroundWindow
  → 排除传入的自身 HWND(灵动岛被点击后会成为前台窗口)
  → 排除最小化 / 不可见窗口
  → 排除 WS_MAXIMIZE
  → 排除 Progman / WorkerW / Shell_TrayWnd
  → GetWindowRect 是否完整覆盖 MonitorFromWindow 的 rcMonitor(容差 2px)

其中 排除最大化是必须的:Windows 上最大化窗口的 GetWindowRect 返回 -8,-8,w+8,h+8,同样完整覆盖显示器,不排除会导致用户只是最大化窗口时灵动岛就消失。

2. electron/main/services/foreground.ts

300ms 轮询,仅在状态变化时回调(单次查询只有几个 Win32 调用,开销可忽略);非 Windows 平台恒返回 false,即保持原有「始终显示」行为。

3. electron/main/window/dynamicIsland.ts

  • 窗口实例创建后即启动监听,保证 ready-to-show 时能读到初始判定;
  • 全屏 → hide();恢复 → showInactive()(不抢焦点,保持非侵入定位)+ 重设 setAlwaysOnTop(..., "screen-saver");
  • ready-to-show 时若已处于全屏则跳过 show(),避免「先闪一下再消失」;
  • 窗口 closed 时停止监听。

4. 设置项 dynamicIsland.hideOnFullscreen

默认开启,仅 Windows 显示,中英文案已补。改动时通过 IPC 走 applyDynamicIslandHideOnFullscreen 即时生效。

关联 Issue

测试情况

平台:Windows 11(2560×1440)

原生模块单独验证(用 Node 直接 require 编译产物,不启动 Electron):

场景 返回值
全屏无边框窗口(覆盖 2560×1440) true
关闭该窗口后 false
最大化窗口 false(未误判)
普通窗口(570,255–1858,1263) false

联动验证(通过 CDP 驱动主窗口全屏,用 document.visibilityState 判断灵动岛是否被隐藏):

  • 主窗口进入全屏且为前台窗口时,灵动岛 visibilityState 变为 hidden;
  • 退出全屏后恢复为 visible。

回归检查:

  • 设置项开关即时生效,关闭后窗口立即恢复显示;
  • 灵动岛正常渲染封面与歌词,置顶、吸附、非遮挡模式等原有行为未受影响;
  • tsc(node)、vue-tsc(web)、eslint、electron-vite build 全部通过。

截图 / 录屏

灵动岛正常显示(隐藏生效前后的差异主要体现在窗口可见性,静态截图不易体现,故从略)。

自查清单

  • 本 PR 只包含一个主要功能 / 修复,没有夹带无关改动
  • 已在本地完整测试通过
  • 已运行 pnpm format,并确认 pnpm typecheck、pnpm lint 通过
  • 改动涉及原生模块时已 pnpm build:native 验证;未手写 native/*/index.d.ts
  • 已向 dev 分支提交

关于最后一项的说明

本地 napi build 无法执行:rustc 在编译 proc-macro2 / quote 的 build script 时崩溃(STATUS_ACCESS_VIOLATION,退出码 0xC0000005),加 --jobs 1 也一样。因此改用 cargo build -p window-monitor 直接编译并手动复制产物为 native/window-monitor/window-monitor.node,编译过程无警告。

native/window-monitor/index.d.ts 是按 NAPI-RS 生成格式手写的,内容与 Rust 签名一致(已通过 Node 实际加载验证导出名与参数行为):

export declare function isForegroundFullscreen(excludeHwnd?: number | null): boolean

合并前建议维护者在正常环境下执行一次 pnpm build:native 覆盖生成,确认无误。

新增 window-monitor 原生模块,通过 Win32 读取前台窗口矩形,判断其是否
完整覆盖所在显示器,据此在他人全屏(游戏 / 视频 / 演示 / 浏览器全屏)
时隐藏灵动岛,退出全屏后恢复显示与置顶,避免常驻 topmost 层级压在全屏
内容之上。

- native/window-monitor:无状态单次查询,排除最大化窗口与桌面类窗口
  (最大化同样铺满屏幕但不算全屏),支持传入自身 HWND 避免自我判定
- services/foreground.ts:300ms 轮询,仅在状态变化时回调,非 Windows
  平台降级为始终显示
- dynamicIsland:全屏隐藏、退出后 showInactive 恢复(不抢焦点);
  ready-to-show 时若已处于全屏则跳过显示,避免"先闪一下再消失"
- 设置项 dynamicIsland.hideOnFullscreen,默认开启,仅 Windows 显示

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant