Skip to content

feat: 新增”桌面层“窗口层级,解决兼容性问题#1728

Open
Braydenccc wants to merge 4 commits intoClassIsland:masterfrom
Braydenccc:master
Open

feat: 新增”桌面层“窗口层级,解决兼容性问题#1728
Braydenccc wants to merge 4 commits intoClassIsland:masterfrom
Braydenccc:master

Conversation

@Braydenccc
Copy link
Copy Markdown

这个 Pull Request 做了什么?

新增加”桌面层“窗口层级,可用于解决一些兼容性问题,同时可以在显示桌面时不消失。

检查清单

  • 我已经在本地测试过这个 PR,确保欲实现的功能或修复的问题能正常工作。

另外,开发文档有点坑了(

@Braydenccc Braydenccc requested review from a team and HelloWRC as code owners March 21, 2026 17:51
Copilot AI review requested due to automatic review settings March 21, 2026 17:51
@chatgpt-codex-connector
Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@Braydenccc
Copy link
Copy Markdown
Author

何意味

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +201 to +205
// 还原到正常父窗口(桌面根节点)
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);
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

关闭 DesktopLayer 时无条件 SetParent(hwnd, HWND.Null) 并 SetWindowPos 到 HWND_BOTTOM:这既不会恢复启用前的 parent/owner,也可能在退出桌面层(例如切到窗口模式/正常模式)时把窗口推到最底层导致“看起来消失”。建议在启用时记录原 parent/owner 并在关闭时恢复,同时避免在关闭分支里强制 HWND_BOTTOM,让调用方决定最终 Z-Order。

Copilot uses AI. Check for mistakes.
Comment thread ClassIsland/MainWindow.axaml.cs Outdated
Comment on lines +811 to +826
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 覆盖逻辑
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UpdateWindowLayer 的桌面层(case 2)路径没有平台守卫且直接 return,若非 Windows 平台(或配置文件)把 WindowLayer 设为 2,会进入该分支但平台服务不支持 DesktopLayer,从而跳过后续层级设置逻辑导致窗口层级异常。建议在此处对 Windows 才启用该 case;非 Windows 回退到置底/置顶或忽略该值。

Suggested change
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;

Copilot uses AI. Check for mistakes.
Comment on lines +154 to +156
<ComboBoxItem>
<ci:IconText Glyph="&#xE62F;" Text="桌面层" />
</ComboBoxItem>
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

“桌面层”选项在设置页中对所有平台都可见,但描述又声明“仅在 Windows 上可用”。这会允许 Linux/macOS 用户选择一个不可用的 WindowLayer 值(2),进而触发不受支持的代码路径。建议对该 ComboBoxItem / 相关 SettingsExpanderItem 使用 OnPlatform(Windows) 控制可见性/可用性,或在 ViewModel 层限制该值只能在 Windows 出现。

Copilot uses AI. Check for mistakes.
Comment thread ClassIsland/MainWindow.axaml.cs Outdated
Comment on lines +811 to +826
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 覆盖逻辑
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里的逻辑期望在“通知弹出/编辑模式”时退出桌面层并临时置顶,但当前触发依赖通知状态变化时调用 UpdateWindowLayer。代码中 AcquireTopmostLock 目前只在 WindowLayer==0 时调用 UpdateWindowLayer,因此桌面层(WindowLayer==2)下通知弹出可能不会触发到此分支。建议把 AcquireTopmostLock 的条件扩展到包含 WindowLayer==2(或除置顶外都调用 UpdateWindowLayer)。

Suggested change
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;

Copilot uses AI. Check for mistakes.
if (progman == HWND.Null)
return nint.Zero;

SendMessage(progman, 0x052C, new WPARAM(0), new LPARAM(0));
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FindDesktopWorkerW 里对 Progman 使用同步 SendMessage(0x052C)。如果 Explorer/Shell 无响应,该调用可能阻塞 UI 线程并造成应用卡死。建议改用 SendMessageTimeout 并设置 SMTO_ABORTIFHUNG + 合理超时,避免桌面层切换带来不可恢复的卡顿。

Suggested change
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);
}

Copilot uses AI. Check for mistakes.
Comment thread ClassIsland/MainWindow.axaml.cs Outdated
Comment on lines 744 to 750
// 桌面层模式下不重设置顶状态(窗口始终在桌面层)
if (ViewModel.Settings.WindowLayer == 2)
{
return;
}
var handle = TryGetPlatformHandle()?.Handle ?? nint.Zero;
if (ViewModel.IsNotificationWindowExplicitShowed || ViewModel.Settings.WindowLayer == 1)
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里开始使用 WindowLayer==2 作为“桌面层”的新取值,但 Settings.WindowLayer 的 SettingsInfo.Enums/注释目前只覆盖 0/1(会影响基于 SettingsInfo 生成的预览/文档/某些 UI 显示)。建议同步把 WindowLayer 的元数据扩展到包含第三项,并考虑用常量/枚举替代魔法数字 2,避免后续散落更多硬编码。

Suggested change
// 桌面层模式下不重设置顶状态(窗口始终在桌面层)
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)

Copilot uses AI. Check for mistakes.
@Braydenccc
Copy link
Copy Markdown
Author

吓哭了,睡醒改

@Braydenccc
Copy link
Copy Markdown
Author

@HelloWRC codex限额没了?

@Braydenccc
Copy link
Copy Markdown
Author

测试过可以了

@Braydenccc
Copy link
Copy Markdown
Author

测试发现如果用了 wallpaper engine 类软件,会覆盖掉该 pr 新增的桌面层界面。

@HelloWRC
Copy link
Copy Markdown
Member

我感觉这样引入一个具有操作其它应用流程的功能,可能会产生比应用源先的“置底”功能更多的兼容性问题。目前在我的测试中,功能没有正常工作(如图)。并且考虑到此功能会将主界面嵌入 Progman,可能还会产生以下潜在问题:

  • 主界面可能会被 Wallpaper Engine 等壁纸软件覆盖
  • 当 explorer 退出时,主界面会消失,且无法通过切换窗口层级恢复
image

结合以上原因,目前我觉得这个 PR 引入的功能可能并不是解决相关问题的一个好方法。

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.

3 participants