IJPL-216655 T: Attempt to load key 'ide.linux.window.buttons.config'

- The fix is rewritten

GitOrigin-RevId: 9cdb7bef61d0bfa0f2cc83fac3f916d9d1527613
This commit is contained in:
Pavel Porvatov
2026-02-16 20:22:24 +00:00
committed by intellij-monorepo-bot
parent 26de18eda7
commit cc50b5d607
3 changed files with 51 additions and 31 deletions
@@ -1,19 +1,23 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:ApiStatus.Internal
package com.intellij.openapi.wm.impl
import com.intellij.ide.AppLifecycleListener
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.openapi.components.StoragePathMacros
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.util.SystemInfoRt
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.util.registry.RegistryManager
import com.intellij.util.concurrency.ThreadingAssertions
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.jetbrains.annotations.ApiStatus
@@ -22,7 +26,7 @@ import org.jetbrains.annotations.ApiStatus
*/
@ApiStatus.Internal
@State(name = "WindowButtonsConfiguration", storages = [Storage(StoragePathMacros.CACHE_FILE)])
class WindowButtonsConfiguration(private val scope: CoroutineScope) : PersistentStateComponent<WindowButtonsConfiguration.State?> {
class WindowButtonsConfiguration(scope: CoroutineScope) : PersistentStateComponent<WindowButtonsConfiguration.State?> {
enum class WindowButton {
MINIMIZE,
MAXIMIZE,
@@ -32,6 +36,9 @@ class WindowButtonsConfiguration(private val scope: CoroutineScope) : Persistent
companion object {
fun getInstance(): WindowButtonsConfiguration? = if (isSupported()) service<WindowButtonsConfiguration>() else null
private val log = thisLogger()
private const val WINDOW_BUTTONS_CONFIG_KEY = "ide.linux.window.buttons.config"
private fun isSupported(): Boolean {
return SystemInfoRt.isLinux
}
@@ -40,35 +47,55 @@ class WindowButtonsConfiguration(private val scope: CoroutineScope) : Persistent
private var mutableStateFlow = MutableStateFlow<State?>(null)
val stateFlow: StateFlow<State?> = mutableStateFlow.asStateFlow()
init {
if (isSupported()) {
scope.launch(CoroutineName("WindowButtonsConfiguration")) {
val registryManager = RegistryManager.getInstanceAsync()
registryManager.awaitRegistryLoad()
var state = loadStateFromRegistry(registryManager) ?: loadStateFromOs()
mutableStateFlow.update { state }
}
}
}
override fun getState(): State? {
return mutableStateFlow.value
}
override fun loadState(state: State) {
mutableStateFlow.value = state
scheduleUpdateFromOs()
}
override fun noStateLoaded() {
scheduleUpdateFromOs()
}
fun scheduleUpdateFromOs(customConfig: String? = null) {
scope.launch {
loadStateFromOs(customConfig)
mutableStateFlow.update { currentState ->
// The saved state is used only for caching, the actual computed state takes priority.
// Normally the state isn't computed yet, unless loadState is called when the service is already up and running.
// This is possible, according to the API docs, but only if the files are modified when the IDE is running.
// In this case we definitely don't want to pick up the modified value, as the sources of truth are the OS and the registry.
currentState ?: state
}
}
private fun loadStateFromOs(customConfig: String?) {
var windowButtonsState: State? = null
if (isSupported()) {
val config = if (customConfig.isNullOrBlank()) X11UiUtil.getWindowButtonsConfig() else customConfig
if (config != null) {
windowButtonsState = parseFromString(config)
}
private fun loadStateFromRegistry(registryManager: RegistryManager): State? {
val value = registryManager.stringValue(WINDOW_BUTTONS_CONFIG_KEY)
if (value.isNullOrEmpty()) {
return null
}
mutableStateFlow.value = windowButtonsState
val result = parseFromString(value)
if (result == null) {
log.warn("Failed to parse '$WINDOW_BUTTONS_CONFIG_KEY' registry value: $value")
}
return result
}
private fun loadStateFromOs(): State? {
ThreadingAssertions.assertBackgroundThread()
val config = X11UiUtil.getWindowButtonsConfig() ?: return null
val result = parseFromString(config)
if (result == null) {
log.warn("Failed to parse OS window buttons config: $config")
}
return result
}
class State {
@@ -113,10 +140,3 @@ private fun stringsToWindowButtons(strings: List<String>): List<WindowButtonsCon
}
}
}
internal class WindowButtonsAppLifecycleListener : AppLifecycleListener {
override fun appStarted() {
WindowButtonsConfiguration.getInstance()?.scheduleUpdateFromOs(Registry.stringValue("ide.linux.window.buttons.config"))
}
}
@@ -2114,7 +2114,6 @@
topic="com.intellij.ide.ui.UISettingsListener"/>
<listener class="com.intellij.ui.MacCustomAppIconStartupService" topic="com.intellij.ide.AppLifecycleListener"/>
<listener class="com.intellij.openapi.editor.actions.ResetFontSizeAppInitListener" topic="com.intellij.ide.AppLifecycleListener"/>
<listener class="com.intellij.openapi.wm.impl.WindowButtonsAppLifecycleListener" topic="com.intellij.ide.AppLifecycleListener" os="linux"/>
<listener class="com.intellij.ui.ExperimentalUiAppLifecycleListener" topic="com.intellij.ide.AppLifecycleListener"/>
<listener class="com.intellij.ide.ui.laf.LafAndEditorColorSchemeDynamicPluginListener"
@@ -1886,6 +1886,7 @@ ide.linux.use.undecorated.border.restartRequired=true
ide.linux.window.buttons.config=
ide.linux.window.buttons.config.description=Custom window buttons configuration, which is get by `gsettings get org.gnome.desktop.wm.preferences button-layout` command by default. Format should be similar to the command result, e.g. `:minimize,maximize,close`
ide.linux.window.buttons.config.restartRequired=true
ide.tree.large.model.allowed=false
ide.tree.large.model.allowed.description=Allows using FixedHeightLayoutCache if setLargeModel(true)