IJPL-248178 Preserve external ATK Wrapper configuration

Remove the javax.accessibility.assistive_technologies VM option when disabling ATK Wrapper instead of writing an empty value.

An empty system property overrides values from ~/.accessibility.properties and the JRE accessibility properties file, which may unintentionally disable assistive technologies configured outside the IDE.

(cherry picked from commit 9481432d6b82f9d806933b5aff690a72a87603e5)

GitOrigin-RevId: d83fbe5c559468ff685f971cb69beafee940b81b
This commit is contained in:
Tatiana Elfimova
2026-08-10 17:55:49 +00:00
committed by intellij-monorepo-bot
parent 2c38270c89
commit 24dd20f124
@@ -140,20 +140,47 @@ object LinuxAccessibilitySupport {
updateAtkWrapperVmOption(shouldEnableAtkWrapper = isScreenReaderSupportEnabled)
}
/**
* Updates the `javax.accessibility.assistive_technologies` VM option in the user's .vmoptions file
* (see [VMOptions.getUserOptionsFile]). By contract, the product ships no such option in
* `bin/<product>64.vmoptions`, so the user's file is the only .vmoptions file that can declare it, and the value
* read here always comes from the same file the value is written to.
*
* That VM option is not the only way the Java ATK Wrapper can be turned on: it may also be declared in
* `~/.accessibility.properties` or `<jre>/conf/accessibility.properties`. The IDE never writes those files, so
* [ATK_WRAPPER] normally does not appear there unless it was set up outside the IDE. The JVM prefers the
* system property over both files; see the priority list in [ScreenReader.isEnabled].
*
* Turning the wrapper off deletes the option instead of writing an empty value on purpose. An empty value wins
* over both properties files, so it would also switch off everything else declared there - technologies the IDE
* neither knows about nor put there. Deleting the option keeps such a deliberate setup working, at the price of
* the JVM falling back to it: when a properties file declares [ATK_WRAPPER], the wrapper stays enabled until the
* user removes it there as well.
*
* Known limitation: the value written here is computed from the .vmoptions files and from the arguments of the
* running JVM only. What the properties files declare is invisible to that computation, so it is not carried over
* - turning the wrapper on writes an option that overrides those files for this IDE.
*
* @return true when the user's .vmoptions file was changed, so the IDE has to be restarted to apply it
*/
private fun updateAtkWrapperVmOption(shouldEnableAtkWrapper: Boolean): Boolean {
if (!SystemInfoRt.isLinux || !VMOptions.canWriteOptions()) {
return false
}
val currentOptionValue = VMOptions.readOption(ASSISTIVE_TECHNOLOGIES_VM_OPTION_PREFIX, false)
val fallbackOptionValue = VMOptions.readOption(ASSISTIVE_TECHNOLOGIES_VM_OPTION_PREFIX, true)
val newOptionValue = computeUpdatedAssistiveTechnologiesOptionValue(
currentOptionValue = currentOptionValue,
fallbackOptionValue = fallbackOptionValue,
val configuredOptionValue = VMOptions.readOption(ASSISTIVE_TECHNOLOGIES_VM_OPTION_PREFIX, false)
val runtimeOptionValue = VMOptions.readOption(ASSISTIVE_TECHNOLOGIES_VM_OPTION_PREFIX, true)
// optionValue is the option in the .vmoptions files, or the arguments of the running JVM when the files say
// nothing about it. See the known limitation above for what is not part of it.
val newOptionValue = computeAssistiveTechnologiesOptionValue(
optionValue = configuredOptionValue ?: runtimeOptionValue,
shouldEnableAtkWrapper = shouldEnableAtkWrapper,
)
return newOptionValue != currentOptionValue && runCatching {
return newOptionValue != configuredOptionValue && runCatching {
// Only the user's .vmoptions file is rewritten here, and a null value deletes the option from it.
// The properties files are left as they are; see the KDoc above for why the option is deleted
// rather than set to an empty value, and what the JVM falls back to afterward.
VMOptions.setProperty(ASSISTIVE_TECHNOLOGIES_PROPERTY, newOptionValue)
}.onFailure {
LOG.warn("Failed to update custom VM options for Java ATK Wrapper support. " +
@@ -165,30 +192,30 @@ object LinuxAccessibilitySupport {
return isGnomeScreenReaderSettingEnabled() || isOrcaProcessRunning()
}
private fun computeUpdatedAssistiveTechnologiesOptionValue(
currentOptionValue: String?,
fallbackOptionValue: String?,
/**
* Adds [ATK_WRAPPER] to a comma-separated `javax.accessibility.assistive_technologies` value, or removes it.
*
* @param optionValue the value the option currently has, or null when it is not set
* @param shouldEnableAtkWrapper whether the Java ATK Wrapper has to be present in the resulting value
*
* @return the comma-separated value to write into the option, or null when no assistive technologies are left,
* in which case the caller removes the option from the .vmoptions file
*/
private fun computeAssistiveTechnologiesOptionValue(
optionValue: String?,
shouldEnableAtkWrapper: Boolean,
): String? {
val fallbackAssistiveTechnologies = parseAssistiveTechnologiesOptionValue(fallbackOptionValue)
val updatedAssistiveTechnologies = parseAssistiveTechnologiesOptionValue(currentOptionValue ?: fallbackOptionValue)
val assistiveTechnologies = parseAssistiveTechnologiesOptionValue(optionValue)
if (shouldEnableAtkWrapper) {
updatedAssistiveTechnologies.add(ATK_WRAPPER)
assistiveTechnologies.add(ATK_WRAPPER)
}
else {
updatedAssistiveTechnologies.remove(ATK_WRAPPER)
assistiveTechnologies.remove(ATK_WRAPPER)
}
if (currentOptionValue == null && updatedAssistiveTechnologies == fallbackAssistiveTechnologies) {
return null
}
return when {
updatedAssistiveTechnologies.isNotEmpty() -> updatedAssistiveTechnologies.joinToString(",")
fallbackOptionValue != null -> ""
else -> null
}
return assistiveTechnologies
.takeIf { it.isNotEmpty() }
?.joinToString(",")
}
private fun isLinuxScreenMagnifierEnabled(): Boolean {
@@ -196,7 +223,8 @@ object LinuxAccessibilitySupport {
}
/**
* `javax.accessibility.assistive_technologies` is comma-delimited list (https://docs.oracle.com/en/java/javase/21/access/accessibility-properties.html)
* `javax.accessibility.assistive_technologies` is a comma-delimited list
* (https://docs.oracle.com/en/java/javase/21/access/accessibility-properties.html)
*/
private fun parseAssistiveTechnologiesOptionValue(optionValue: String?): LinkedHashSet<String> {
return optionValue