OBS 窗口置顶拆解:三平台三种姿势,却共用一个函数
OBS 窗口置顶拆解三平台三种姿势却共用一个函数【免费下载链接】obs-studioOBS Studio - Free and open source software for live streaming and screen recording项目地址: https://gitcode.com/GitHub_Trending/ob/obs-studio如果你一边用 OBS Studio 推流一边还要盯弹幕、改场景、动音轨最烦的事莫过于预览窗口被浏览器盖住。OBS 的窗口置顶Always On Top功能就是为这个痛点存在的主窗口、预览投影、全屏多视图都能钉在桌面最上层。本文基于源码走一遍这条功能线帮你看清楚一次菜单点击如何变成系统级 API 调用三个操作系统各用了什么手段以及哪些入口能让它开箱即顶。从菜单点击到系统调用一条完整的调用链主窗口的置顶入口是 View 菜单下的 Always On Top 勾选项声明在 frontend/forms/OBSBasic.ui 中。点击后进入信号槽函数on_actionAlwaysOnTop_triggered()它就在 frontend/widgets/OBSBasic_MainControls.cpp 里void OBSBasic::on_actionAlwaysOnTop_triggered() { #ifndef _WIN32 CloseDialogs(); // 非 Windows 下先关掉属性对话框 #endif QMetaObject::invokeMethod(this, OBSBasic::ToggleAlwaysOnTop, Qt::QueuedConnection); }有意思的是这里没有直接改窗口标志而是把真正的切换排队到事件循环之后执行。代码注释给了解释非 Windows 平台切换置顶时 Qt 会重建原生窗口句柄如果属性面板之类的对话框还开着重建过程可能把它们的句柄弄丢。先把对话框关掉、再异步切换是个稳妥的顺序。真正干活的是ToggleAlwaysOnTop()它体现了先读后写的设计——不盲目设状态而是先问系统当前是什么状态void OBSBasic::ToggleAlwaysOnTop() { bool isAlwaysOnTop IsAlwaysOnTop(this); ui-actionAlwaysOnTop-setChecked(!isAlwaysOnTop); SetAlwaysOnTop(this, !isAlwaysOnTop); show(); }IsAlwaysOnTop和SetAlwaysOnTop这对读写函数声明在 frontend/utility/platform.hpp是所有平台共用的接口签名// frontend/utility/platform.hpp bool IsAlwaysOnTop(QWidget *window); void SetAlwaysOnTop(QWidget *window, bool enable);一句话直觉上层只关心这个窗口要不要置顶至于底下是 Win32 消息、Cocoa 窗口层级还是 X11 属性全部被这 20 行以内的接口挡住了。整条链路画出来是这样三个系统三种置顶姿势 接口统一了但三个平台的落地方式差异不小。先看 Windows实现在 frontend/utility/platform-windows.cpp一次SetWindowPos打天下void SetAlwaysOnTop(QWidget *window, bool enable) { HWND hwnd (HWND)window-winId(); SetWindowPos(hwnd, enable ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); }HWND_TOPMOST是置顶开关本身后三个SWP_标志保证只翻开关、不动位置大小、不抢焦点。读状态时则直接取窗口扩展样式里的WS_EX_TOPMOST位不依赖 Qt。macOS 和 Linux 则都依赖 Qt 的Qt::WindowStaysOnTopHint标志但细节不同。frontend/utility/platform-osx.mm 在启用时多做了一件事——拿到原生NSView把窗口抬到屏幕保护程序层级if (enable) { NSView *view (__bridge NSView *) reinterpret_castvoid *(window-winId()); [[view window] setLevel:NSScreenSaverWindowLevel]; flags | Qt::WindowStaysOnTopHint; } else { flags ~Qt::WindowStaysOnTopHint; } window-setWindowFlags(flags); window-show();为什么 macOS 要双管齐下NSScreenSaverWindowLevel让窗口压过绝大多数普通应用窗口而 Qt 标志负责让框架层面的状态自洽Linux 端frontend/utility/platform-x11.cpp则只用 Qt 标志由 Qt 将其翻译成 X11 的_NET_WM_STATE_ABOVE窗口管理器提示。两个非 Windows 实现都以setWindowFlagsshow()收尾——这不是冗余改窗口标志会销毁重建原生句柄必须重新show()窗口才不会消失。平台核心手段关键 API / 参数状态读取方式WindowsWin32 直调SetWindowPosHWND_TOPMOSTGetWindowLong读WS_EX_TOPMOSTmacOSCocoa Qt 双保险setLevel:NSScreenSaverWindowLevelWindowStaysOnTopHintQt 窗口标志位Linux (X11)纯 QtWindowStaysOnTopHint→_NET_WM_STATE_ABOVEQt 窗口标志位状态从哪来配置文件与命令行 置顶不是点了才有。OBS 主窗口初始化frontend/widgets/OBSBasic.cpp时会做两件事读上次保存的状态再合并命令行参数bool alwaysOnTop config_get_bool(App()-GetUserConfig(), BasicWindow, AlwaysOnTop); // ...isWayland 判断省略... if (!isWayland (alwaysOnTop || opt_always_on_top)) { SetAlwaysOnTop(this, true); ui-actionAlwaysOnTop-setChecked(true); } else if (isWayland) { ui-actionAlwaysOnTop-setEnabled(false); ui-actionAlwaysOnTop-setVisible(false); }三个入口值得记一下持久化配置user.conf里BasicWindow段的AlwaysOnTop布尔值勾过菜单后下次启动自动生效命令行opt_always_on_top在 frontend/obs-main.cpp 的参数解析里定义启动时带上--always-on-top即可跳过菜单直接置顶适合做自动化脚本投影窗口全屏投影、多视图用的OBSProjectorfrontend/widgets/OBSProjector.cpp有独立的置顶开关通过SetIsAlwaysOnTop()走同一个SetAlwaysOnTop所以主窗口置顶和投影窗口置顶可以各管各的。边界在哪Wayland 与全屏的例外源码里最诚实的一段是上面那个isWayland分支Wayland 的窗口栈完全由合成器管理应用层面没有置顶这回事OBS 干脆把这个菜单项禁掉并隐藏而不是假装能生效。也就是说Wayland 下的 Linux 用户没有这个功能X11 会话下才完整可用。另外两个常见困惑Windows 上置顶窗口依然会被真正独占全屏的应用比如游戏全屏模式压住因为独占全屏绕过了正常的窗口栈macOS 的置顶窗口在 Mission Control 里的归类也可能和普通窗口不同。这些都不是 bug是各自系统窗口模型的边界。SetAlwaysOnTop这套设计的取舍很清晰用一个不到两行的接口把三个系统差异最大的窗口行为收敛成一个函数调用上层主窗口、预览、投影三处调用方完全无感。代价是它只能做到在正常窗口栈里最上面至于合成器说了算的场景Wayland、独占全屏它选择不硬来——这对一个要跑在生产直播里的软件来说反而是最正确的姿态。【免费下载链接】obs-studioOBS Studio - Free and open source software for live streaming and screen recording项目地址: https://gitcode.com/GitHub_Trending/ob/obs-studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考