如何让Compose Multiplatform应用始终显示在全屏程序之上?
解决Compose Multiplatform窗口无法覆盖全屏游戏的跨平台方案
默认的alwaysOnTop = true只能让窗口在普通应用上层,无法覆盖运行在独占全屏模式的游戏(如CS2、Valorant)——这类应用会抢占系统最高显示优先级,需要通过原生窗口API调整层级才能实现覆盖。以下是跨Windows/macOS/Linux的实现方案:
核心思路
通过Compose的LocalWindow.current获取原生窗口实例,针对不同平台调用对应系统API将窗口层级设置为高于全屏应用的级别,同时保留跨平台兼容性。
1. 定义跨平台抽象函数
首先创建一个expect函数,用于统一调用各平台的原生逻辑:
import androidx.compose.ui.window.Window expect fun setWindowAboveFullscreen(window: Window)
2. 各平台实现
Windows平台(依赖JNA)
先添加JNA依赖到build.gradle.kts:
dependencies { implementation("net.java.dev.jna:jna:5.13.0") implementation("net.java.dev.jna:jna-platform:5.13.0") }
然后实现actual函数:
import com.sun.jna.platform.win32.User32 import com.sun.jna.platform.win32.WinDef import com.sun.jna.platform.win32.WinUser actual fun setWindowAboveFullscreen(window: Window) { val hwnd = window.nativeWindow as WinDef.HWND // 设置窗口为TOPMOST层级 User32.INSTANCE.SetWindowPos( hwnd, WinUser.HWND_TOPMOST, 0, 0, 0, 0, WinUser.SWP_NOMOVE or WinUser.SWP_NOSIZE ) // 添加工具窗口属性,避免被全屏游戏拦截 val exStyle = User32.INSTANCE.GetWindowLong(hwnd, WinUser.GWL_EXSTYLE) User32.INSTANCE.SetWindowLong( hwnd, WinUser.GWL_EXSTYLE, exStyle or WinUser.WS_EX_TOOLWINDOW or WinUser.WS_EX_TRANSPARENT ) }
macOS平台
直接通过Skiko的原生NSWindow API设置层级:
import org.jetbrains.skiko.native.macos.NSWindow actual fun setWindowAboveFullscreen(window: Window) { val nsWindow = window.nativeWindow as NSWindow // SCREEN_SAVER层级高于全屏应用 nsWindow.setLevel(NSWindow.Level.SCREEN_SAVER.toLong()) }
Linux平台
通过X11 API设置窗口状态为永远置顶且在全屏窗口之上:
import org.jetbrains.skiko.native.linux.X11 import org.jetbrains.skiko.native.linux.X11Window import org.jetbrains.skiko.native.linux.XEvent actual fun setWindowAboveFullscreen(window: Window) { val x11Window = window.nativeWindow as X11Window val display = x11Window.display val win = x11Window.window val netWmStateAtom = X11.INSTANCE.XInternAtom(display, "_NET_WM_STATE", false) val aboveAtom = X11.INSTANCE.XInternAtom(display, "_NET_WM_STATE_ABOVE", false) val stayOnTopAtom = X11.INSTANCE.XInternAtom(display, "_NET_WM_STATE_STAYS_ON_TOP", false) // 发送消息设置窗口为ABOVE状态 sendNetWmStateMessage(display, win, netWmStateAtom, aboveAtom) // 发送消息设置窗口为STAYS_ON_TOP状态 sendNetWmStateMessage(display, win, netWmStateAtom, stayOnTopAtom) X11.INSTANCE.XFlush(display) } private fun sendNetWmStateMessage(display: Long, window: Long, messageAtom: Long, stateAtom: Long) { val event = XEvent().apply { client_message.type = X11.ClientMessage client_message.window = window client_message.message_type = messageAtom client_message.format = 32 client_message.data.l = longArrayOf(stateAtom, 0, 0, 0, 0) } X11.INSTANCE.XSendEvent( display, X11.INSTANCE.XDefaultRootWindow(display), false, X11.SubstructureRedirectMask or X11.SubstructureNotifyMask, event ) }
3. 在Compose应用中调用
在Window初始化后,通过LaunchedEffect调用设置函数:
import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.window.LocalWindow import androidx.compose.ui.window.application import androidx.compose.ui.window.Window fun main() = application { val currentWindow = LocalWindow.current LaunchedEffect(Unit) { setWindowAboveFullscreen(currentWindow) } Window( onCloseRequest = ::exitApplication, undecorated = true, alwaysOnTop = true, // 启用透明背景,避免遮挡游戏画面(按需开启) transparent = true ) { // 你的窗口内容 } }
注意事项
- 权限要求:macOS需要在「系统设置-隐私与安全性-辅助功能」中授予应用权限;Windows可能需要管理员权限才能覆盖部分全屏游戏;Linux部分桌面环境(如GNOME)可能需要调整窗口管理器设置。
- 独占全屏游戏:部分游戏使用硬件独占全屏模式,此时任何窗口都无法覆盖,只能通过游戏内设置切换到窗口化/无边框窗口模式才能生效。
- 性能优化:透明窗口会增加渲染开销,建议简化Compose内容的布局和绘制逻辑,避免影响游戏帧率。
内容的提问来源于stack exchange,提问作者Lolmerkat
相关产品推荐
相关产品推荐

