Conversation
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
|
何意味 |
There was a problem hiding this comment.
Pull request overview
该 PR 在现有“置底/置顶”窗口层级基础上,新增一个 Windows 专用的“桌面层”层级,通过将窗口嵌入桌面 WorkerW/Progman 来提升兼容性并避免在 Win+D“显示桌面”时消失。
Changes:
- 新增
WindowFeatures.DesktopLayer,并在 Windows 平台通过SetParent + WorkerW实现桌面层嵌入。 - 主窗口窗口层级逻辑新增
WindowLayer == 2分支,并在设置页增加“桌面层”选项与说明。 - 更新 CsWin32 NativeMethods 列表以生成
SetParent/EnumWindows等 Win32 API。
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| platforms/ClassIsland.Platforms.Windows/Services/WindowPlatformService.cs | 增加查找 WorkerW 并通过 SetParent 将窗口嵌入桌面层的实现 |
| platforms/ClassIsland.Platforms.Windows/NativeMethods.txt | 补充生成 DesktopLayer 所需的 Win32 API 声明 |
| ClassIsland/Views/SettingPages/WindowSettingsPage.axaml | 在“窗口层级”设置中新增“桌面层”选项与说明 |
| ClassIsland/MainWindow.axaml.cs | 新增 WindowLayer=2 的窗口层级处理逻辑,并与通知/编辑模式交互 |
| ClassIsland.Platforms.Abstractions/Enums/WindowFeatures.cs | 增加 DesktopLayer 特性枚举值供平台层实现 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // 还原到正常父窗口(桌面根节点) | ||
| SetParent((HWND)handle, HWND.Null); | ||
| SetWindowPos((HWND)handle, HWND.HWND_BOTTOM, 0, 0, 0, 0, | ||
| SET_WINDOW_POS_FLAGS.SWP_NOSIZE | SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE | ||
| | SET_WINDOW_POS_FLAGS.SWP_NOSENDCHANGING | SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW); |
There was a problem hiding this comment.
关闭 DesktopLayer 时无条件 SetParent(hwnd, HWND.Null) 并 SetWindowPos 到 HWND_BOTTOM:这既不会恢复启用前的 parent/owner,也可能在退出桌面层(例如切到窗口模式/正常模式)时把窗口推到最底层导致“看起来消失”。建议在启用时记录原 parent/owner 并在关闭时恢复,同时避免在关闭分支里强制 HWND_BOTTOM,让调用方决定最终 Z-Order。
| Topmost = false; | ||
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.Bottommost, false); | ||
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.Topmost, false); | ||
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.SkipManagement, false); | ||
| // 通知弹出期间临时提升到 Topmost,不进入桌面层 | ||
| if (!ViewModel.IsNotificationWindowExplicitShowed && !ViewModel.IsEditMode) | ||
| { | ||
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.DesktopLayer, true); | ||
| } | ||
| else | ||
| { | ||
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.DesktopLayer, false); | ||
| Topmost = true; | ||
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.Topmost, true); | ||
| } | ||
| return; // 直接返回,跳过后续 SkipManagement / Topmost 覆盖逻辑 |
There was a problem hiding this comment.
UpdateWindowLayer 的桌面层(case 2)路径没有平台守卫且直接 return,若非 Windows 平台(或配置文件)把 WindowLayer 设为 2,会进入该分支但平台服务不支持 DesktopLayer,从而跳过后续层级设置逻辑导致窗口层级异常。建议在此处对 Windows 才启用该 case;非 Windows 回退到置底/置顶或忽略该值。
| Topmost = false; | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.Bottommost, false); | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.Topmost, false); | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.SkipManagement, false); | |
| // 通知弹出期间临时提升到 Topmost,不进入桌面层 | |
| if (!ViewModel.IsNotificationWindowExplicitShowed && !ViewModel.IsEditMode) | |
| { | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.DesktopLayer, true); | |
| } | |
| else | |
| { | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.DesktopLayer, false); | |
| Topmost = true; | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.Topmost, true); | |
| } | |
| return; // 直接返回,跳过后续 SkipManagement / Topmost 覆盖逻辑 | |
| if (OperatingSystem.IsWindows()) | |
| { | |
| Topmost = false; | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.Bottommost, false); | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.Topmost, false); | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.SkipManagement, false); | |
| // 通知弹出期间临时提升到 Topmost,不进入桌面层 | |
| if (!ViewModel.IsNotificationWindowExplicitShowed && !ViewModel.IsEditMode) | |
| { | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.DesktopLayer, true); | |
| } | |
| else | |
| { | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.DesktopLayer, false); | |
| Topmost = true; | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.Topmost, true); | |
| } | |
| return; // 直接返回,跳过后续 SkipManagement / Topmost 覆盖逻辑 | |
| } | |
| // 非 Windows 平台不支持 DesktopLayer:回退到普通置顶逻辑 | |
| Topmost = true; | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.DesktopLayer, false); | |
| break; |
| <ComboBoxItem> | ||
| <ci:IconText Glyph="" Text="桌面层" /> | ||
| </ComboBoxItem> |
There was a problem hiding this comment.
“桌面层”选项在设置页中对所有平台都可见,但描述又声明“仅在 Windows 上可用”。这会允许 Linux/macOS 用户选择一个不可用的 WindowLayer 值(2),进而触发不受支持的代码路径。建议对该 ComboBoxItem / 相关 SettingsExpanderItem 使用 OnPlatform(Windows) 控制可见性/可用性,或在 ViewModel 层限制该值只能在 Windows 出现。
| Topmost = false; | ||
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.Bottommost, false); | ||
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.Topmost, false); | ||
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.SkipManagement, false); | ||
| // 通知弹出期间临时提升到 Topmost,不进入桌面层 | ||
| if (!ViewModel.IsNotificationWindowExplicitShowed && !ViewModel.IsEditMode) | ||
| { | ||
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.DesktopLayer, true); | ||
| } | ||
| else | ||
| { | ||
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.DesktopLayer, false); | ||
| Topmost = true; | ||
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.Topmost, true); | ||
| } | ||
| return; // 直接返回,跳过后续 SkipManagement / Topmost 覆盖逻辑 |
There was a problem hiding this comment.
这里的逻辑期望在“通知弹出/编辑模式”时退出桌面层并临时置顶,但当前触发依赖通知状态变化时调用 UpdateWindowLayer。代码中 AcquireTopmostLock 目前只在 WindowLayer==0 时调用 UpdateWindowLayer,因此桌面层(WindowLayer==2)下通知弹出可能不会触发到此分支。建议把 AcquireTopmostLock 的条件扩展到包含 WindowLayer==2(或除置顶外都调用 UpdateWindowLayer)。
| Topmost = false; | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.Bottommost, false); | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.Topmost, false); | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.SkipManagement, false); | |
| // 通知弹出期间临时提升到 Topmost,不进入桌面层 | |
| if (!ViewModel.IsNotificationWindowExplicitShowed && !ViewModel.IsEditMode) | |
| { | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.DesktopLayer, true); | |
| } | |
| else | |
| { | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.DesktopLayer, false); | |
| Topmost = true; | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.Topmost, true); | |
| } | |
| return; // 直接返回,跳过后续 SkipManagement / Topmost 覆盖逻辑 | |
| // 通知弹出 / 编辑模式期间临时提升到 Topmost,不进入桌面层 | |
| Topmost = ViewModel.IsNotificationWindowExplicitShowed || ViewModel.IsEditMode; | |
| // 在进入桌面层逻辑前,清理与层级相关的标记,后续统一处理 | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.Bottommost, false); | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.Topmost, false); | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.SkipManagement, false); | |
| // 仅在非通知 / 非编辑模式下进入桌面层;否则退出桌面层以便临时置顶 | |
| PlatformServices.WindowPlatformService.SetWindowFeature(this, WindowFeatures.DesktopLayer, !Topmost); | |
| break; |
| if (progman == HWND.Null) | ||
| return nint.Zero; | ||
|
|
||
| SendMessage(progman, 0x052C, new WPARAM(0), new LPARAM(0)); |
There was a problem hiding this comment.
FindDesktopWorkerW 里对 Progman 使用同步 SendMessage(0x052C)。如果 Explorer/Shell 无响应,该调用可能阻塞 UI 线程并造成应用卡死。建议改用 SendMessageTimeout 并设置 SMTO_ABORTIFHUNG + 合理超时,避免桌面层切换带来不可恢复的卡顿。
| SendMessage(progman, 0x052C, new WPARAM(0), new LPARAM(0)); | |
| // 使用 SendMessageTimeout 避免在 Explorer/Shell 无响应时阻塞当前线程 | |
| unsafe | |
| { | |
| SendMessageTimeout( | |
| progman, | |
| 0x052C, | |
| new WPARAM(0), | |
| new LPARAM(0), | |
| Windows.Win32.UI.WindowsAndMessaging.SEND_MESSAGE_TIMEOUT_FLAGS.SMTO_ABORTIFHUNG, | |
| 2000, | |
| null); | |
| } |
| // 桌面层模式下不重设置顶状态(窗口始终在桌面层) | ||
| if (ViewModel.Settings.WindowLayer == 2) | ||
| { | ||
| return; | ||
| } | ||
| var handle = TryGetPlatformHandle()?.Handle ?? nint.Zero; | ||
| if (ViewModel.IsNotificationWindowExplicitShowed || ViewModel.Settings.WindowLayer == 1) |
There was a problem hiding this comment.
这里开始使用 WindowLayer==2 作为“桌面层”的新取值,但 Settings.WindowLayer 的 SettingsInfo.Enums/注释目前只覆盖 0/1(会影响基于 SettingsInfo 生成的预览/文档/某些 UI 显示)。建议同步把 WindowLayer 的元数据扩展到包含第三项,并考虑用常量/枚举替代魔法数字 2,避免后续散落更多硬编码。
| // 桌面层模式下不重设置顶状态(窗口始终在桌面层) | |
| if (ViewModel.Settings.WindowLayer == 2) | |
| { | |
| return; | |
| } | |
| var handle = TryGetPlatformHandle()?.Handle ?? nint.Zero; | |
| if (ViewModel.IsNotificationWindowExplicitShowed || ViewModel.Settings.WindowLayer == 1) | |
| const int DesktopWindowLayer = 2; | |
| const int TopmostWindowLayer = 1; | |
| // 桌面层模式下不重设置顶状态(窗口始终在桌面层) | |
| if (ViewModel.Settings.WindowLayer == DesktopWindowLayer) | |
| { | |
| return; | |
| } | |
| var handle = TryGetPlatformHandle()?.Handle ?? nint.Zero; | |
| if (ViewModel.IsNotificationWindowExplicitShowed || ViewModel.Settings.WindowLayer == TopmostWindowLayer) |
|
吓哭了,睡醒改 |
|
@HelloWRC codex限额没了? |
|
测试过可以了 |
|
测试发现如果用了 wallpaper engine 类软件,会覆盖掉该 pr 新增的桌面层界面。 |

这个 Pull Request 做了什么?
新增加”桌面层“窗口层级,可用于解决一些兼容性问题,同时可以在显示桌面时不消失。
检查清单
另外,开发文档有点坑了(