You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 02:52:12