11// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
22package com.intellij.ide.ui.laf
33
4+ import com.intellij.openapi.application.EDT
5+ import com.intellij.openapi.application.ModalityState
6+ import com.intellij.openapi.application.asContextElement
47import com.intellij.openapi.components.Service
58import com.intellij.openapi.diagnostic.thisLogger
69import com.intellij.openapi.util.SystemInfoRt
710import com.intellij.openapi.wm.impl.ExecResult
8- import com.intellij.openapi.wm.impl.X11UiUtilKt
11+ import com.intellij.openapi.wm.impl.LinuxUiUtil
912import com.intellij.openapi.wm.impl.output
1013import com.intellij.util.concurrency.ThreadingAssertions
1114import kotlinx.coroutines.CoroutineScope
15+ import kotlinx.coroutines.Dispatchers
16+ import kotlinx.coroutines.FlowPreview
17+ import kotlinx.coroutines.channels.BufferOverflow
1218import kotlinx.coroutines.flow.MutableSharedFlow
1319import kotlinx.coroutines.flow.MutableStateFlow
1420import kotlinx.coroutines.flow.StateFlow
1521import kotlinx.coroutines.flow.asStateFlow
1622import kotlinx.coroutines.flow.debounce
1723import kotlinx.coroutines.launch
24+ import kotlinx.coroutines.withContext
1825import java.util.concurrent.atomic.AtomicReference
1926import kotlin.time.Duration.Companion.seconds
2027
@@ -41,14 +48,13 @@ private val QUERY_COLOR_SCHEME = arrayOf(
4148 " string:org.freedesktop.appearance" ,
4249 " string:color-scheme" )
4350
44- @Suppress(" OPT_IN_USAGE" )
4551@Service
4652internal class DBusSettingsMonitorService (private val scope : CoroutineScope ) {
4753
4854 private var LOG = thisLogger()
4955
5056 private val darkSchemeFlow = MutableStateFlow <Boolean ?>(null )
51- private val darkSchemeDebounceFlow = MutableSharedFlow <Unit >(extraBufferCapacity = 1 )
57+ private val darkSchemeDebounceFlow = MutableSharedFlow <Unit >(extraBufferCapacity = 1 , onBufferOverflow = BufferOverflow . DROP_LATEST )
5258 private var dbusMonitorProcess = AtomicReference <Process ?>(null )
5359
5460 val isServiceAllowed: Boolean
@@ -62,6 +68,7 @@ internal class DBusSettingsMonitorService(private val scope: CoroutineScope) {
6268 darkSchemeFlow.value = calcDarkScheme()
6369
6470 try {
71+ @OptIn(FlowPreview ::class )
6572 darkSchemeDebounceFlow.debounce(DEBOUNCE_DURATION ).collect {
6673 darkSchemeFlow.value = calcDarkScheme()
6774 }
@@ -87,7 +94,7 @@ internal class DBusSettingsMonitorService(private val scope: CoroutineScope) {
8794 return
8895 }
8996
90- scope.launch {
97+ scope.launch( Dispatchers . EDT + ModalityState .any().asContextElement()) {
9198 darkScheme.collect {
9299 listener(it ? : false )
93100 }
@@ -97,7 +104,7 @@ internal class DBusSettingsMonitorService(private val scope: CoroutineScope) {
97104 private fun calcDarkScheme (): Boolean? {
98105 ThreadingAssertions .assertBackgroundThread()
99106
100- val output = X11UiUtilKt .exec(" DBusSettingsMonitorService gets color scheme" , * QUERY_COLOR_SCHEME ).output() ? : return null
107+ val output = LinuxUiUtil .exec(" DBusSettingsMonitorService gets color scheme" , * QUERY_COLOR_SCHEME ).output() ? : return null
101108
102109 val split = output.splitOutput()
103110 val value = split.lastOrNull()?.toIntOrNull()
@@ -124,36 +131,40 @@ internal class DBusSettingsMonitorService(private val scope: CoroutineScope) {
124131 return result
125132 }
126133
127- private fun startDbusMonitorListener () {
128- ThreadingAssertions .assertBackgroundThread()
129-
130- if (X11UiUtilKt .exec(" DBusSettingsMonitorService checks dbus-monitor" , * CHECK_MONITOR_CMD ) !is ExecResult .Success ) {
131- return
132- }
134+ /* *
135+ * The method starts a process and reads its output for the entire lifetime of the IDE.
136+ * It uses I/O operations and doesn't obey coroutine cancellation
137+ */
138+ private suspend fun startDbusMonitorListener () {
139+ withContext(Dispatchers .IO ) {
140+ if (LinuxUiUtil .exec(" DBusSettingsMonitorService checks dbus-monitor" , * CHECK_MONITOR_CMD ) !is ExecResult .Success ) {
141+ return @withContext
142+ }
133143
134- try {
135- dbusMonitorProcess.set(ProcessBuilder (* MONITOR_CMD ).start())
136- LOG .info(" DBus listener started" )
137- }
138- catch (e: Throwable ) {
139- LOG .info(" DBus listener cannot start" , e)
140- }
144+ try {
145+ dbusMonitorProcess.set(ProcessBuilder (* MONITOR_CMD ).start())
146+ LOG .info(" DBus listener started" )
147+ }
148+ catch (e: Throwable ) {
149+ LOG .info(" DBus listener cannot start" , e)
150+ }
141151
142- val process = dbusMonitorProcess.get() ? : return
152+ val process = dbusMonitorProcess.get() ? : return @withContext
143153
144- try {
145- process.inputStream.bufferedReader().forEachLine { line ->
146- val split = line.splitOutput()
147- if (split.size > 2 && split[split.size - 2 ] == SETTINGS_INTERFACE && split[split.size - 1 ] == SETTINGS_MEMBER ) {
148- LOG .info(" SettingChanged received: $line " )
149- darkSchemeDebounceFlow.tryEmit(Unit )
154+ try {
155+ process.inputStream.bufferedReader().forEachLine { line ->
156+ val split = line.splitOutput()
157+ if (split.size > 2 && split[split.size - 2 ] == SETTINGS_INTERFACE && split[split.size - 1 ] == SETTINGS_MEMBER ) {
158+ LOG .info(" SettingChanged received: $line " )
159+ check(darkSchemeDebounceFlow.tryEmit(Unit ))
160+ }
150161 }
151- }
152162
153- LOG .info(" DBus listener stopped: output ended unexpectedly (no errors)" )
154- }
155- catch (e: Throwable ) {
156- LOG .info(" DBus listener stopped" , e)
163+ LOG .info(" DBus listener stopped: output ended unexpectedly (no errors)" )
164+ }
165+ catch (e: Throwable ) {
166+ LOG .info(" DBus listener stopped" , e)
167+ }
157168 }
158169 }
159170
0 commit comments