3 Commits

Author SHA1 Message Date
Storm Dragon e94af432c2 Merge branch 'testing' 2025-12-30 14:30:09 -05:00
Storm Dragon 489651e3fa Fall back to speech if sound for an item is unavailable or doesn't exist. 2025-12-30 14:29:37 -05:00
Storm Dragon e059063115 Better handling of sound only setting. 2025-12-30 07:04:16 -05:00
2 changed files with 107 additions and 9 deletions
+13 -3
View File
@@ -375,6 +375,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
if self._script.utilities.shouldVerbalizeAllPunctuation(obj): if self._script.utilities.shouldVerbalizeAllPunctuation(obj):
name = self._script.utilities.verbalizeAllPunctuation(name) name = self._script.utilities.verbalizeAllPunctuation(name)
name = self._stripRoleFromNameIfNeeded(obj, name, role)
result = [name] result = [name]
result.extend(self.voice(speech_generator.DEFAULT, obj=obj, **args)) result.extend(self.voice(speech_generator.DEFAULT, obj=obj, **args))
return result return result
@@ -423,6 +424,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
name = AXObject.get_name(obj) name = AXObject.get_name(obj)
if not self._script.utilities.hasExplicitName(obj): if not self._script.utilities.hasExplicitName(obj):
name = name.strip() name = name.strip()
name = self._stripRoleFromNameIfNeeded(obj, name, role)
result = [name] result = [name]
result.extend(self.voice(speech_generator.DEFAULT, obj=obj, **args)) result.extend(self.voice(speech_generator.DEFAULT, obj=obj, **args))
@@ -439,6 +441,8 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
label, objects = self._script.utilities.inferLabelFor(obj) label, objects = self._script.utilities.inferLabelFor(obj)
if label: if label:
role = args.get('role', AXObject.get_role(obj))
label = self._stripRoleFromNameIfNeeded(obj, label, role)
result = [label] result = [label]
result.extend(self.voice(speech_generator.DEFAULT, obj=obj, **args)) result.extend(self.voice(speech_generator.DEFAULT, obj=obj, **args))
return result return result
@@ -554,8 +558,13 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
role = args.get('role', AXObject.get_role(obj)) role = args.get('role', AXObject.get_role(obj))
roleSoundPresentation = _settingsManager.getSetting('roleSoundPresentation') roleSoundPresentation = _settingsManager.getSetting('roleSoundPresentation')
soundEnabled = _settingsManager.getSetting('enableSound')
roleSoundIcon = None
if roleSoundPresentation != settings.ROLE_SOUND_PRESENTATION_SPEECH_ONLY \
and soundEnabled:
roleSoundIcon = sound_theme_manager.getManager().getRoleSoundIcon(role)
if roleSoundPresentation == settings.ROLE_SOUND_PRESENTATION_SOUND_ONLY \ if roleSoundPresentation == settings.ROLE_SOUND_PRESENTATION_SOUND_ONLY \
and _settingsManager.getSetting('enableSound'): and soundEnabled and roleSoundIcon:
if AXUtilities.is_check_box(obj) \ if AXUtilities.is_check_box(obj) \
or AXUtilities.is_check_menu_item(obj) \ or AXUtilities.is_check_menu_item(obj) \
or AXUtilities.is_radio_button(obj) \ or AXUtilities.is_radio_button(obj) \
@@ -674,9 +683,10 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
if not _settingsManager.getSetting('enableSound'): if not _settingsManager.getSetting('enableSound'):
return result return result
roleSoundIcon = sound_theme_manager.getManager().getRoleSoundIcon(role)
if roleSoundPresentation == settings.ROLE_SOUND_PRESENTATION_SOUND_ONLY: if roleSoundPresentation == settings.ROLE_SOUND_PRESENTATION_SOUND_ONLY:
return [roleSoundIcon] if roleSoundIcon else [] if roleSoundIcon:
return [roleSoundIcon]
return result
if not roleSoundIcon: if not roleSoundIcon:
return result return result
+94 -6
View File
@@ -169,6 +169,8 @@ class SpeechGenerator(generator.Generator):
result = generator.Generator._generateName(self, obj, **args) result = generator.Generator._generateName(self, obj, **args)
if result: if result:
if isinstance(result[0], str):
result[0] = self._stripRoleFromNameIfNeeded(obj, result[0], role)
if role == Atspi.Role.LAYERED_PANE: if role == Atspi.Role.LAYERED_PANE:
result.extend(self.voice(SYSTEM, obj=obj, **args)) result.extend(self.voice(SYSTEM, obj=obj, **args))
else: else:
@@ -185,6 +187,9 @@ class SpeechGenerator(generator.Generator):
result = generator.Generator._generateLabel(self, obj, **args) result = generator.Generator._generateLabel(self, obj, **args)
if result: if result:
role = args.get('role', AXObject.get_role(obj))
if isinstance(result[0], str):
result[0] = self._stripRoleFromNameIfNeeded(obj, result[0], role)
result.extend(self.voice(DEFAULT, obj=obj, **args)) result.extend(self.voice(DEFAULT, obj=obj, **args))
return result return result
@@ -205,6 +210,7 @@ class SpeechGenerator(generator.Generator):
if not result: if not result:
name = AXObject.get_name(obj) name = AXObject.get_name(obj)
if name: if name:
name = self._stripRoleFromNameIfNeeded(obj, name, role)
result.append(name) result.append(name)
result.extend(self.voice(DEFAULT, obj=obj, **args)) result.extend(self.voice(DEFAULT, obj=obj, **args))
if result: if result:
@@ -216,6 +222,85 @@ class SpeechGenerator(generator.Generator):
return result return result
def _stripRoleFromNameIfNeeded(self, obj, name, role=None):
if not name:
return name
roleSoundPresentation = _settingsManager.getSetting('roleSoundPresentation')
if roleSoundPresentation != settings.ROLE_SOUND_PRESENTATION_SOUND_ONLY:
return name
if not _settingsManager.getSetting('enableSound'):
return name
if not self._shouldStripRoleFromName(obj):
return name
candidates = self._getRoleNameStripCandidates(obj, role)
if not candidates:
return name
strippedName = name.strip()
strippedNameLower = strippedName.casefold()
for candidate in candidates:
candidate = candidate.strip()
if not candidate:
continue
candidateLower = candidate.casefold()
if strippedNameLower == candidateLower:
continue
suffix = f" {candidateLower}"
if strippedNameLower.endswith(suffix):
return strippedName[:-(len(candidateLower) + 1)].rstrip()
suffix = f" ({candidateLower})"
if strippedNameLower.endswith(suffix):
return strippedName[:-(len(candidateLower) + 3)].rstrip()
return name
def _shouldStripRoleFromName(self, obj):
return AXUtilities.is_check_box(obj) \
or AXUtilities.is_check_menu_item(obj) \
or AXUtilities.is_combo_box(obj) \
or AXUtilities.is_push_button(obj) \
or AXUtilities.is_radio_button(obj) \
or AXUtilities.is_radio_menu_item(obj) \
or AXUtilities.is_toggle_button(obj) \
or AXUtilities.is_switch(obj)
def _getRoleNameStripCandidates(self, obj, role=None):
normalizedRole = role
if isinstance(normalizedRole, str):
if normalizedRole == "ROLE_SWITCH":
normalizedRole = Atspi.Role.SWITCH
else:
normalizedRole = None
candidates = []
if normalizedRole is None and isinstance(role, str):
localizedRoleName = self.getLocalizedRoleName(obj)
else:
localizedRoleName = self.getLocalizedRoleName(obj, role=normalizedRole or role)
if localizedRoleName:
candidates.append(localizedRoleName)
if " " in localizedRoleName:
candidates.append(localizedRoleName.replace(" ", ""))
if normalizedRole is not None and not isinstance(normalizedRole, str):
roleName = Atspi.role_get_name(normalizedRole)
if roleName:
candidates.append(roleName)
if " " in roleName:
candidates.append(roleName.replace(" ", ""))
if AXUtilities.is_push_button(obj):
candidates.append("button")
return list(dict.fromkeys(candidates))
def _generatePlaceholderText(self, obj, **args): def _generatePlaceholderText(self, obj, **args):
"""Returns an array of strings for use by speech and braille that """Returns an array of strings for use by speech and braille that
represent the 'placeholder' text. This is typically text that represent the 'placeholder' text. This is typically text that
@@ -581,8 +666,13 @@ class SpeechGenerator(generator.Generator):
result = [] result = []
role = args.get('role', AXObject.get_role(obj)) role = args.get('role', AXObject.get_role(obj))
roleSoundPresentation = _settingsManager.getSetting('roleSoundPresentation') roleSoundPresentation = _settingsManager.getSetting('roleSoundPresentation')
soundEnabled = _settingsManager.getSetting('enableSound')
roleSoundIcon = None
if roleSoundPresentation != settings.ROLE_SOUND_PRESENTATION_SPEECH_ONLY \
and soundEnabled:
roleSoundIcon = sound_theme_manager.getManager().getRoleSoundIcon(role)
if roleSoundPresentation == settings.ROLE_SOUND_PRESENTATION_SOUND_ONLY \ if roleSoundPresentation == settings.ROLE_SOUND_PRESENTATION_SOUND_ONLY \
and _settingsManager.getSetting('enableSound'): and soundEnabled and roleSoundIcon:
if AXUtilities.is_check_box(obj) \ if AXUtilities.is_check_box(obj) \
or AXUtilities.is_check_menu_item(obj) \ or AXUtilities.is_check_menu_item(obj) \
or AXUtilities.is_radio_button(obj) \ or AXUtilities.is_radio_button(obj) \
@@ -590,10 +680,6 @@ class SpeechGenerator(generator.Generator):
or AXUtilities.is_toggle_button(obj) \ or AXUtilities.is_toggle_button(obj) \
or AXUtilities.is_switch(obj): or AXUtilities.is_switch(obj):
return [] return []
roleSoundIcon = None
if roleSoundPresentation != settings.ROLE_SOUND_PRESENTATION_SPEECH_ONLY \
and _settingsManager.getSetting('enableSound'):
roleSoundIcon = sound_theme_manager.getManager().getRoleSoundIcon(role)
doNotPresent = [Atspi.Role.UNKNOWN, doNotPresent = [Atspi.Role.UNKNOWN,
Atspi.Role.REDUNDANT_OBJECT, Atspi.Role.REDUNDANT_OBJECT,
@@ -637,7 +723,9 @@ class SpeechGenerator(generator.Generator):
result.extend(self.voice(SYSTEM, obj=obj, **args)) result.extend(self.voice(SYSTEM, obj=obj, **args))
if roleSoundPresentation == settings.ROLE_SOUND_PRESENTATION_SOUND_ONLY: if roleSoundPresentation == settings.ROLE_SOUND_PRESENTATION_SOUND_ONLY:
return [roleSoundIcon] if roleSoundIcon else [] if roleSoundIcon:
return [roleSoundIcon]
return result
if result and roleSoundIcon: if result and roleSoundIcon:
if roleSoundPresentation == settings.ROLE_SOUND_PRESENTATION_SOUND_AND_SPEECH: if roleSoundPresentation == settings.ROLE_SOUND_PRESENTATION_SOUND_AND_SPEECH: