Skip to content

Commit 25814e7

Browse files
committed
#489 attempt sleep prevention linux
1 parent 69ce8b5 commit 25814e7

File tree

8 files changed

+255
-99
lines changed

8 files changed

+255
-99
lines changed

hub/src/main/kotlin/uk/co/sentinelweb/cuer/hub/di/Modules.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package uk.co.sentinelweb.cuer.hub.di
22

33
import PlatformWifiInfo
4+
import SleepPreventerMac
45
import com.arkivanov.essenty.lifecycle.LifecycleRegistry
56
import com.russhwolf.settings.JvmPreferencesSettings
67
import com.russhwolf.settings.Settings
@@ -38,6 +39,7 @@ import uk.co.sentinelweb.cuer.db.di.JvmDatabaseModule
3839
import uk.co.sentinelweb.cuer.di.DomainModule
3940
import uk.co.sentinelweb.cuer.di.JvmDomainModule
4041
import uk.co.sentinelweb.cuer.domain.BuildConfigDomain
42+
import uk.co.sentinelweb.cuer.domain.NodeDomain
4143
import uk.co.sentinelweb.cuer.hub.BuildConfigInject
4244
import uk.co.sentinelweb.cuer.hub.service.remote.RemoteServerService
4345
import uk.co.sentinelweb.cuer.hub.service.remote.RemoteServerServiceManager
@@ -57,6 +59,9 @@ import uk.co.sentinelweb.cuer.hub.util.remote.FileEncryption
5759
import uk.co.sentinelweb.cuer.hub.util.remote.KeyStoreManager
5860
import uk.co.sentinelweb.cuer.hub.util.remote.RemoteConfigFileInitialiseer
5961
import uk.co.sentinelweb.cuer.hub.util.share.scan.TodoLinkScanner
62+
import uk.co.sentinelweb.cuer.hub.util.sleep.SleepPreventer
63+
import uk.co.sentinelweb.cuer.hub.util.sleep.SleepPreventerEmpty
64+
import uk.co.sentinelweb.cuer.hub.util.sleep.SleepPreventerLinuxDBus
6065
import uk.co.sentinelweb.cuer.hub.util.system_tray.SystemTrayComposePopup
6166
import uk.co.sentinelweb.cuer.hub.util.system_tray.SystemTrayIcon
6267
import uk.co.sentinelweb.cuer.hub.util.wrapper.EmptyVibrateWrapper
@@ -108,6 +113,18 @@ object Modules {
108113
}
109114
single { SystemTrayIcon() }
110115
factory { SystemTrayComposePopup() }
116+
single<SleepPreventer> {
117+
when (getOS()) {
118+
NodeDomain.DeviceType.MAC -> SleepPreventerMac()
119+
NodeDomain.DeviceType.LINUX -> SleepPreventerLinuxDBus()
120+
else -> SleepPreventerEmpty()
121+
// NodeDomain.DeviceType.ANDROID -> TODO()
122+
// NodeDomain.DeviceType.IOS -> TODO()
123+
// NodeDomain.DeviceType.WEB -> TODO()
124+
// NodeDomain.DeviceType.WINDOWS -> TODO()
125+
// NodeDomain.DeviceType.OTHER -> TODO()
126+
}
127+
}
111128
}
112129

113130
private val connectivityModule = module {

hub/src/main/kotlin/uk/co/sentinelweb/cuer/hub/ui/player/vlc/VlcPlayerSwingWindow.kt

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@ package uk.co.sentinelweb.cuer.hub.ui.player.vlc
33
import androidx.compose.ui.graphics.Color.Companion.Black
44
import com.sun.jna.StringArray
55
import httpLocalNetworkUrl
6-
import kotlinx.coroutines.ExperimentalCoroutinesApi
76
import loadSVG
87
import org.koin.core.component.KoinComponent
98
import org.koin.core.component.inject
109
import toImageIcon
1110
import uk.co.caprica.vlcj.binding.internal.libvlc_instance_t
12-
import uk.co.caprica.vlcj.binding.lib.LibVlc
1311
import uk.co.caprica.vlcj.binding.lib.LibVlc.libvlc_new
1412
import uk.co.caprica.vlcj.binding.lib.LibVlc.libvlc_release
1513
import uk.co.caprica.vlcj.factory.discovery.NativeDiscovery
@@ -22,7 +20,6 @@ import uk.co.caprica.vlcj.player.base.MediaPlayerEventAdapter
2220
import uk.co.caprica.vlcj.player.base.State
2321
import uk.co.caprica.vlcj.player.component.CallbackMediaPlayerComponent
2422
import uk.co.caprica.vlcj.support.version.LibVlcVersion
25-
import uk.co.sentinelweb.cuer.app.orchestrator.OrchestratorContract.Source.LOCAL_NETWORK
2623
import uk.co.sentinelweb.cuer.app.ui.player.PlayerContract
2724
import uk.co.sentinelweb.cuer.app.ui.player.PlayerContract.PlayerCommand.*
2825
import uk.co.sentinelweb.cuer.app.ui.player.PlayerContract.View.Event.*
@@ -32,16 +29,11 @@ import uk.co.sentinelweb.cuer.core.providers.CoroutineContextProvider
3229
import uk.co.sentinelweb.cuer.core.providers.PlayerConfigProvider
3330
import uk.co.sentinelweb.cuer.core.providers.TimeProvider
3431
import uk.co.sentinelweb.cuer.core.wrapper.LogWrapper
35-
import uk.co.sentinelweb.cuer.core.wrapper.URLEncoder
36-
import uk.co.sentinelweb.cuer.domain.PlatformDomain
3732
import uk.co.sentinelweb.cuer.domain.PlayerNodeDomain
3833
import uk.co.sentinelweb.cuer.domain.PlayerStateDomain
3934
import uk.co.sentinelweb.cuer.domain.PlayerStateDomain.*
4035
import uk.co.sentinelweb.cuer.domain.PlaylistItemDomain
4136
import uk.co.sentinelweb.cuer.remote.server.LocalRepository
42-
import uk.co.sentinelweb.cuer.remote.server.RemoteWebServerContract.Companion.VIDEO_STREAM_API
43-
import uk.co.sentinelweb.cuer.remote.server.http
44-
import uk.co.sentinelweb.cuer.remote.server.locator
4537
import java.awt.BorderLayout
4638
import java.awt.BorderLayout.*
4739
import java.awt.Color
@@ -51,14 +43,16 @@ import javax.swing.*
5143
import javax.swing.JOptionPane.ERROR_MESSAGE
5244
import javax.swing.event.ChangeEvent
5345

46+
const val WINDOW_NAME = "CuerVlcSwingVideo"
47+
5448
class VlcPlayerSwingWindow(
5549
private val coordinator: VlcPlayerUiCoordinator,
5650
private val folderListUseCase: GetFolderListUseCase,
5751
private val showHideControls: VlcPlayerShowHideControls,
5852
private val keyMap: VlcPlayerKeyMap,
5953
private val localRepository: LocalRepository,
6054
private val coroutineContextProvider: CoroutineContextProvider
61-
) : JFrame(), KoinComponent {
55+
) : JFrame(WINDOW_NAME), KoinComponent {
6256

6357
lateinit var mediaPlayerComponent: CallbackMediaPlayerComponent
6458
private val log: LogWrapper by inject()
@@ -272,8 +266,8 @@ class VlcPlayerSwingWindow(
272266

273267
is Pause -> mediaPlayerComponent.mediaPlayer().controls().pause()
274268
is Play -> mediaPlayerComponent.mediaPlayer().controls().play()
275-
is SkipFwd -> mediaPlayerComponent.mediaPlayer().controls().skipTime(command.ms.toLong())
276-
is SkipBack -> mediaPlayerComponent.mediaPlayer().controls().skipTime(-command.ms.toLong())
269+
is SkipFwd -> mediaPlayerComponent.mediaPlayer().controls().skipTime(command.ms)
270+
is SkipBack -> mediaPlayerComponent.mediaPlayer().controls().skipTime(-command.ms)
277271
is SeekTo -> mediaPlayerComponent.mediaPlayer().controls().setTime(command.ms)
278272
}.also { log.d("command:${command::class.java.simpleName}") }
279273

@@ -497,10 +491,10 @@ class VlcPlayerSwingWindow(
497491
fun tryload(): Boolean {
498492
try {
499493
val instance: libvlc_instance_t = libvlc_new(0, StringArray(arrayOf<String>()))
500-
libvlc_release(instance);
501-
val version: LibVlcVersion = LibVlcVersion();
494+
libvlc_release(instance)
495+
val version = LibVlcVersion()
502496
if (version.isSupported()) {
503-
return true;
497+
return true
504498
}
505499
} catch (e: UnsatisfiedLinkError) {
506500
e.printStackTrace()

hub/src/main/kotlin/uk/co/sentinelweb/cuer/hub/ui/player/vlc/VlcPlayerUiCoordinator.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package uk.co.sentinelweb.cuer.hub.ui.player.vlc
22

3-
import SleepPreventer
43
import androidx.compose.foundation.layout.height
54
import androidx.compose.material3.MaterialTheme
65
import androidx.compose.runtime.Composable
@@ -13,7 +12,6 @@ import com.arkivanov.mvikotlin.core.utils.diff
1312
import com.arkivanov.mvikotlin.core.view.BaseMviView
1413
import com.arkivanov.mvikotlin.core.view.ViewRenderer
1514
import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory
16-
import kotlinx.coroutines.ExperimentalCoroutinesApi
1715
import kotlinx.coroutines.flow.MutableStateFlow
1816
import kotlinx.coroutines.launch
1917
import org.koin.core.component.KoinComponent
@@ -43,6 +41,7 @@ import uk.co.sentinelweb.cuer.domain.ext.serialise
4341
import uk.co.sentinelweb.cuer.hub.ui.home.HomeUiCoordinator
4442
import uk.co.sentinelweb.cuer.hub.util.extension.DesktopScopeComponent
4543
import uk.co.sentinelweb.cuer.hub.util.extension.desktopScopeWithSource
44+
import uk.co.sentinelweb.cuer.hub.util.sleep.SleepPreventer
4645
import uk.co.sentinelweb.cuer.hub.util.view.UiCoordinator
4746

4847
class VlcPlayerUiCoordinator(
@@ -66,6 +65,7 @@ class VlcPlayerUiCoordinator(
6665
private val playlistOrchestrator: PlaylistOrchestrator by inject()
6766
private val coroutines: CoroutineContextProvider by inject()
6867
private val queueProducer: QueueMediatorContract.Producer by inject()
68+
private val sleepPreventer: SleepPreventer by inject()
6969

7070
private lateinit var playerWindow: VlcPlayerSwingWindow
7171

@@ -106,7 +106,7 @@ class VlcPlayerUiCoordinator(
106106
playerWindow = if (VlcPlayerSwingWindow.checkShowWindow()) {
107107
scope.get<VlcPlayerSwingWindow>(VlcPlayerSwingWindow::class)
108108
.apply { assemble(screen) }
109-
.also { SleepPreventer.preventSleep() }
109+
.also { sleepPreventer.preventSleep() }
110110
} else error("Can't find VLC")
111111
}
112112

@@ -116,7 +116,7 @@ class VlcPlayerUiCoordinator(
116116
?.takeIf { it.source == MEMORY }
117117
?.also { playlistOrchestrator.delete(it.id, it.deepOptions()) }
118118
playerWindow.destroy()
119-
SleepPreventer.allowSleep()
119+
sleepPreventer.allowSleep()
120120
lifecycle.onPause()
121121
lifecycle.onStop()
122122
controller.onViewDestroyed()
Lines changed: 5 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,7 @@
1-
import com.sun.jna.Library
2-
import com.sun.jna.Native
3-
import com.sun.jna.Pointer
4-
import com.sun.jna.PointerType
5-
import com.sun.jna.ptr.PointerByReference
6-
import uk.co.sentinelweb.cuer.core.providers.getOS
7-
import uk.co.sentinelweb.cuer.domain.NodeDomain
8-
import uk.co.sentinelweb.cuer.domain.NodeDomain.DeviceType.MAC
1+
package uk.co.sentinelweb.cuer.hub.util.sleep
92

10-
// uses JNA to prevent sleep
11-
class SleepPreventer {
3+
interface SleepPreventer {
4+
fun preventSleep()
125

13-
interface IOKit : Library {
14-
fun IOPMAssertionCreateWithName(
15-
assertionType: Pointer,
16-
assertionLevel: Int,
17-
assertionName: Pointer,
18-
assertionID: PointerByReference
19-
): Int
20-
21-
fun IOPMAssertionRelease(assertionID: Pointer): Int
22-
}
23-
24-
interface CoreFoundation : Library {
25-
fun CFStringCreateWithCString(
26-
alloc: Pointer?,
27-
cStr: String,
28-
encoding: Int
29-
): Pointer
30-
}
31-
32-
class CFStringRef(s: String) : PointerType() {
33-
init {
34-
pointer = CoreFoundationLib.CFStringCreateWithCString(null, s, kCFStringEncodingUTF8)
35-
}
36-
}
37-
38-
companion object {
39-
private const val kCFStringEncodingUTF8 = 0x08000100
40-
private const val ASSERTION_TYPE_NO_DISPLAY_SLEEP = "NoDisplaySleepAssertion"
41-
private const val ASSERTION_LEVEL_ON = 255
42-
private var assertionIDKeeper: PointerByReference? = null
43-
private val CoreFoundationLib: CoreFoundation by lazy { Native.load("CoreFoundation", CoreFoundation::class.java)}
44-
private val iokit: IOKit by lazy {Native.load("IOKit", IOKit::class.java)}
45-
46-
@JvmStatic
47-
fun preventSleep() {
48-
if (getOS() == MAC) {
49-
if (assertionIDKeeper == null) {
50-
val assertionID = PointerByReference()
51-
val assertionType = CFStringRef(ASSERTION_TYPE_NO_DISPLAY_SLEEP).pointer
52-
val assertionName = CFStringRef("Prevent sleep while playing video").pointer
53-
val iokit: IOKit = Native.load("IOKit", IOKit::class.java)
54-
55-
val result =
56-
iokit.IOPMAssertionCreateWithName(assertionType, ASSERTION_LEVEL_ON, assertionName, assertionID)
57-
if (result != 0) {
58-
assertionIDKeeper = null
59-
println("Failed to create assertion: $result")
60-
} else {
61-
assertionIDKeeper = assertionID
62-
println("Sleep prevention activated.")
63-
}
64-
}
65-
}
66-
}
67-
68-
@JvmStatic
69-
fun allowSleep() {
70-
if (getOS() == MAC) {
71-
assertionIDKeeper?.let {
72-
val result = iokit.IOPMAssertionRelease(it.value)
73-
if (result == 0) {
74-
println("Sleep prevention deactivated.")
75-
assertionIDKeeper = null
76-
} else {
77-
println("Failed to release assertion: $result")
78-
}
79-
}
80-
}
81-
}
82-
}
83-
}
6+
fun allowSleep()
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package uk.co.sentinelweb.cuer.hub.util.sleep
2+
3+
class SleepPreventerEmpty : SleepPreventer {
4+
override fun preventSleep() = Unit
5+
6+
override fun allowSleep() = Unit
7+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package uk.co.sentinelweb.cuer.hub.util.sleep
2+
3+
import java.io.BufferedReader
4+
import java.io.InputStreamReader
5+
6+
class SleepPreventerLinuxDBus : SleepPreventer {
7+
private var inhibitCookie: String? = null
8+
9+
//dbus-send --session --print-reply=literal --dest=org.freedesktop.PowerManager /org/freedesktop/PowerManager org.freedesktop.PowerManager.Inhibit string:MyApp string:Inhibit screensaver
10+
override fun preventSleep() {
11+
try {
12+
// Inhibit screensaver via dbus-send
13+
val pb = ProcessBuilder(
14+
"dbus-send", "--session",
15+
"--print-reply",
16+
"--dest=org.freedesktop.ScreenSaver",
17+
"/ScreenSaver",
18+
"org.freedesktop.ScreenSaver.Inhibit",
19+
"string:Cuer",
20+
"string:Inhibiting for demonstration"
21+
)
22+
pb.redirectErrorStream(true)
23+
val process = pb.start()
24+
25+
BufferedReader(InputStreamReader(process.inputStream)).use { reader ->
26+
// Read the response from dbus-send which contains the inhibit cookie
27+
var response: String
28+
while ((reader.readLine().also { response = it }) != null) {
29+
if (response.contains("uint32")) {
30+
inhibitCookie =
31+
response.split("uint32\\s+".toRegex()).dropLastWhile { it.isEmpty() }
32+
.toTypedArray()[1].trim { it <= ' ' }
33+
println("Inhibit Cookie: " + inhibitCookie)
34+
}
35+
}
36+
}
37+
// Optionally wait for the process to complete or handle as needed
38+
process.waitFor()
39+
} catch (e: java.lang.Exception) {
40+
e.printStackTrace()
41+
}
42+
}
43+
44+
override fun allowSleep() {
45+
if (inhibitCookie.isNullOrEmpty()) {
46+
System.err.println("Inhibit cookie is null or empty. Cannot allow screen sleep.")
47+
return
48+
}
49+
50+
try {
51+
// Uninhibit screensaver via dbus-send using the stored inhibit cookie
52+
val pb = ProcessBuilder(
53+
"dbus-send", "--session",
54+
"--dest=org.freedesktop.ScreenSaver",
55+
"/ScreenSaver",
56+
"org.freedesktop.ScreenSaver.UnInhibit",
57+
"uint32:" + inhibitCookie
58+
)
59+
pb.inheritIO()
60+
val process = pb.start()
61+
62+
// Optionally wait for the process to complete or handle as needed
63+
process.waitFor()
64+
} catch (e: Exception) {
65+
e.printStackTrace()
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)