@@ -11,7 +11,14 @@ package me.him188.ani.app.platform.window
1111
1212import androidx.compose.ui.window.WindowPlacement
1313import androidx.compose.ui.window.WindowState
14+ import kotlinx.atomicfu.locks.ReentrantLock
15+ import kotlinx.atomicfu.locks.withLock
1416import me.him188.ani.app.platform.PlatformWindow
17+ import me.him188.ani.utils.logging.logger
18+ import org.freedesktop.dbus.connections.impl.DBusConnection
19+ import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder
20+ import org.freedesktop.dbus.interfaces.DBusInterface
21+ import org.freedesktop.dbus.types.UInt32
1522
1623class LinuxWindowUtils : AwtWindowUtils () {
1724 override suspend fun setUndecoratedFullscreen (
@@ -25,4 +32,87 @@ class LinuxWindowUtils : AwtWindowUtils() {
2532 windowState.apply { placement = WindowPlacement .Floating }
2633 }
2734 }
35+
36+ private val preventScreenSaverLock = ReentrantLock ()
37+
38+ @Volatile
39+ private var inhibitCookie: UInt32 ? = null
40+
41+ @Volatile
42+ private var dbusConnection: DBusConnection ? = null
43+
44+ override fun setPreventScreenSaver (prevent : Boolean ) = preventScreenSaverLock.withLock {
45+ if (prevent) {
46+ if (inhibitCookie == null ) {
47+ logger.info(" Inhibiting screen saver via org.freedesktop.ScreenSaver D-Bus" )
48+ try {
49+ val connection = withDbusClassLoader {
50+ DBusConnectionBuilder .forSessionBus().build()
51+ }
52+ val screenSaver = connection.getRemoteObject(
53+ " org.freedesktop.ScreenSaver" ,
54+ " /org/freedesktop/ScreenSaver" ,
55+ ScreenSaverInterface ::class .java,
56+ )
57+ val cookie = screenSaver.Inhibit (" Animeko" , " Playing video" )
58+ dbusConnection = connection
59+ inhibitCookie = cookie
60+ logger.info(" Screen saver inhibited with cookie: $cookie " )
61+ } catch (e: Exception ) {
62+ logger.error(" Failed to inhibit screen saver, see cause" , e)
63+ }
64+ }
65+ } else {
66+ val cookie = inhibitCookie
67+ val connection = dbusConnection
68+ if (cookie != null && connection != null ) {
69+ logger.info(" Uninhibiting screen saver with cookie: $cookie " )
70+ try {
71+ val screenSaver = connection.getRemoteObject(
72+ " org.freedesktop.ScreenSaver" ,
73+ " /org/freedesktop/ScreenSaver" ,
74+ ScreenSaverInterface ::class .java,
75+ )
76+ screenSaver.UnInhibit (cookie)
77+ } catch (e: Exception ) {
78+ logger.error(" Failed to uninhibit screen saver, see cause" , e)
79+ } finally {
80+ try {
81+ connection.close()
82+ } catch (_: Exception ) {
83+ }
84+ dbusConnection = null
85+ inhibitCookie = null
86+ }
87+ }
88+ }
89+ }
90+
91+ @Suppress(" FunctionName" )
92+ @org.freedesktop.dbus.annotations.DBusInterfaceName (" org.freedesktop.ScreenSaver" )
93+ interface ScreenSaverInterface : DBusInterface {
94+ fun Inhibit (applicationName : String , reason : String ): UInt32
95+ fun UnInhibit (cookie : UInt32 )
96+ }
97+
98+ private companion object {
99+ private val logger = logger<LinuxWindowUtils >()
100+
101+ /* *
102+ * ServiceLoader uses the thread context classloader to discover providers.
103+ * On the AWT EventDispatch thread, this may differ from the classloader that
104+ * loaded dbus-java, causing transport provider discovery to fail.
105+ * Temporarily switch to the classloader that loaded [DBusConnectionBuilder].
106+ */
107+ private inline fun <T > withDbusClassLoader (block : () -> T ): T {
108+ val currentThread = Thread .currentThread()
109+ val originalClassLoader = currentThread.contextClassLoader
110+ currentThread.contextClassLoader = DBusConnectionBuilder ::class .java.classLoader
111+ try {
112+ return block()
113+ } finally {
114+ currentThread.contextClassLoader = originalClassLoader
115+ }
116+ }
117+ }
28118}
0 commit comments