Major sound fixes and more added. Fixed a bug where switch items were being silently ignored.

This commit is contained in:
Storm Dragon
2025-12-30 05:27:59 -05:00
parent 32c39c4e3d
commit b0375faa45
13 changed files with 58 additions and 63 deletions
+2 -19
View File
@@ -1035,23 +1035,6 @@
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkCheckButton" id="enableModeChangeSoundCheckButton">
<property name="label" translatable="yes" comments="Translators: If this checkbox is checked, Cthulhu will play sounds when switching between browse mode and focus mode in web content.">Play sounds when switching between _browse and focus modes</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="enableModeChangeSoundCheckButtonToggled" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="soundThemeHBox">
<property name="visible">True</property>
@@ -1094,7 +1077,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="position">0</property>
</packing>
</child>
<child>
@@ -1139,7 +1122,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">1</property>
</packing>
</child>
</object>
-19
View File
@@ -2004,15 +2004,9 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
prefs = self.prefsDict
# Get widget references
self.enableModeChangeSoundCheckButton = self.get_widget(
"enableModeChangeSoundCheckButton")
self.soundThemeCombo = self.get_widget("soundThemeCombo")
self.roleSoundPresentationCombo = self.get_widget("roleSoundPresentationCombo")
# Set enable mode change sound checkbox
enabled = prefs.get("enableModeChangeSound", settings.enableModeChangeSound)
self.enableModeChangeSoundCheckButton.set_active(enabled)
# Populate sound theme combo box
themeManager = sound_theme_manager.getManager()
availableThemes = themeManager.getAvailableThemes()
@@ -2055,19 +2049,6 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
break
self.roleSoundPresentationCombo.set_active(presentationIndex)
# Update sensitivity based on checkbox
self._updateSoundThemeWidgetSensitivity()
def _updateSoundThemeWidgetSensitivity(self):
"""Update sound theme combo sensitivity based on enable checkbox."""
enabled = self.enableModeChangeSoundCheckButton.get_active()
self.soundThemeCombo.set_sensitive(enabled)
def enableModeChangeSoundCheckButtonToggled(self, widget):
"""Signal handler for the enable mode change sound checkbox."""
self.prefsDict["enableModeChangeSound"] = widget.get_active()
self._updateSoundThemeWidgetSensitivity()
def soundThemeComboChanged(self, widget):
"""Signal handler for the sound theme combo box."""
activeText = widget.get_active_text()
+6 -6
View File
@@ -915,11 +915,11 @@ formatting = {
},
Atspi.Role.CHECK_BOX: {
'focused': 'checkedState',
'unfocused': 'roleName + checkedState + required + invalid + availability',
'unfocused': 'checkedState + required + invalid + availability',
},
Atspi.Role.CHECK_MENU_ITEM: {
'focused': 'checkedState',
'unfocused': 'roleName + checkedState + availability + positionInSet',
'unfocused': 'checkedState + availability + positionInSet',
},
Atspi.Role.COMBO_BOX: {
'focused': 'expandableState',
@@ -970,11 +970,11 @@ formatting = {
},
Atspi.Role.RADIO_BUTTON: {
'focused': 'radioState',
'unfocused': 'roleName + radioState + availability + positionInSet',
'unfocused': 'radioState + availability + positionInSet',
},
Atspi.Role.RADIO_MENU_ITEM: {
'focused': 'radioState',
'unfocused': 'roleName + checkedState + availability + positionInSet',
'unfocused': 'checkedState + availability + positionInSet',
},
Atspi.Role.SCROLL_BAR: {
'focused': 'percentage',
@@ -994,7 +994,7 @@ formatting = {
},
'ROLE_SWITCH': {
'focused': 'switchState',
'unfocused': 'roleName + switchState + availability',
'unfocused': 'switchState + availability',
},
Atspi.Role.TABLE_CELL: {
'focused': 'expandableState',
@@ -1008,7 +1008,7 @@ formatting = {
},
Atspi.Role.TOGGLE_BUTTON: {
'focused': 'expandableState or toggleState',
'unfocused': 'roleName + (expandableState or toggleState) + availability',
'unfocused': '(expandableState or toggleState) + availability',
},
Atspi.Role.TREE_ITEM: {
'focused': 'expandableState',
-5
View File
@@ -929,11 +929,6 @@ USE_CARET_NAVIGATION = _("Control caret navigation")
# of a checkbox in which users can indicate their default preference.
USE_STRUCTURAL_NAVIGATION = _("Enable _structural navigation")
# Translators: This is the label for a checkbox in the preferences dialog.
# When enabled, Cthulhu will play sounds when switching between browse mode
# and focus mode in web content.
ENABLE_MODE_CHANGE_SOUND = _("Play sounds when switching between _browse and focus modes")
# Translators: This is the label for a combo box in the preferences dialog
# where users can select a sound theme. A sound theme is a collection of
# audio files that Cthulhu plays for various events.
+2
View File
@@ -1429,6 +1429,8 @@ class Script(default.Script):
return None
role = AXObject.get_role(obj)
if self.utilities.isSwitch(obj):
role = Atspi.Role.SWITCH
manager = sound_theme_manager.getManager()
icon = manager.getRoleSoundIcon(role)
if icon:
@@ -4009,6 +4009,9 @@ class Utilities(script_utilities.Utilities):
if not (obj and self.inDocumentContent(obj)):
return super().isSwitch(obj)
if AXObject.get_role(obj) == Atspi.Role.SWITCH:
return True
return 'switch' in self._getXMLRoles(obj)
def isNonNavigableEmbeddedDocument(self, obj):
+13 -4
View File
@@ -553,6 +553,16 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
result.extend(self.voice(speech_generator.SYSTEM, obj=obj, **args))
role = args.get('role', AXObject.get_role(obj))
roleSoundPresentation = _settingsManager.getSetting('roleSoundPresentation')
if roleSoundPresentation == settings.ROLE_SOUND_PRESENTATION_SOUND_ONLY \
and _settingsManager.getSetting('enableSound'):
if AXUtilities.is_check_box(obj) \
or AXUtilities.is_check_menu_item(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):
return []
enabled, disabled = self._getEnabledAndDisabledContextRoles()
if role in disabled:
return []
@@ -659,19 +669,18 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
if not result:
return result
roleSoundPresentation = _settingsManager.getSetting('roleSoundPresentation')
if roleSoundPresentation == settings.ROLE_SOUND_PRESENTATION_SPEECH_ONLY:
return result
if not _settingsManager.getSetting('enableSound'):
return result
roleSoundIcon = sound_theme_manager.getManager().getRoleSoundIcon(role)
if roleSoundPresentation == settings.ROLE_SOUND_PRESENTATION_SOUND_ONLY:
return [roleSoundIcon] if roleSoundIcon else []
if not roleSoundIcon:
return result
if roleSoundPresentation == settings.ROLE_SOUND_PRESENTATION_SOUND_ONLY:
return [roleSoundIcon]
return [roleSoundIcon] + result
def _generatePageSummary(self, obj, **args):
-2
View File
@@ -80,7 +80,6 @@ userCustomizableSettings = [
"playSoundForValue",
"roleSoundPresentation",
"soundTheme",
"enableModeChangeSound",
"verbalizePunctuationStyle",
"presentToolTips",
"sayAllStyle",
@@ -322,7 +321,6 @@ playSoundForPositionInSet = False
playSoundForValue = False
roleSoundPresentation = ROLE_SOUND_PRESENTATION_SOUND_AND_SPEECH
soundTheme = "default"
enableModeChangeSound = True
# Keyboard and Echo
keyboardLayout = GENERAL_KEYBOARD_LAYOUT_DESKTOP
+5
View File
@@ -371,6 +371,11 @@ class SoundGenerator(generator.Generator):
return []
role = args.get('role', AXObject.get_role(obj))
if isinstance(role, str):
if role == "ROLE_SWITCH":
role = Atspi.Role.SWITCH
else:
return []
filename = Atspi.role_get_name(role).replace(' ', '_')
result = self._convertFilenameToIcon(filename)
if result:
+15 -6
View File
@@ -172,6 +172,9 @@ class SoundThemeManager:
def _getRoleSoundCandidates(self, role):
"""Return candidate sound names for a given role."""
role = self._normalizeRole(role)
if role is None:
return []
candidates = ROLE_SOUND_ALIASES.get(role, [])
if candidates:
return candidates
@@ -182,8 +185,18 @@ class SoundThemeManager:
return []
def _normalizeRole(self, role):
if isinstance(role, str):
if role == "ROLE_SWITCH":
return Atspi.Role.SWITCH
return None
return role
def _getRoleStateSoundCandidates(self, role, stateKey):
"""Return candidate sound names for a given role/state pair."""
role = self._normalizeRole(role)
if role is None:
return []
bases = ROLE_STATE_SOUND_BASES.get(role, [])
if not bases:
return []
@@ -225,21 +238,18 @@ class SoundThemeManager:
return None
def _playThemeSound(self, soundName, interrupt=True, wait=False,
requireModeChangeSetting=False, requireSoundSetting=False):
requireSoundSetting=False):
"""Play a themed sound with optional gating and blocking.
Args:
soundName: The name of the sound file (without extension)
interrupt: Whether to interrupt currently playing sounds
wait: Whether to block until the sound finishes playing
requireModeChangeSetting: Honor enableModeChangeSound setting
requireSoundSetting: Honor enableSound setting
Returns:
True if sound was played, False otherwise
"""
if requireModeChangeSetting and not _settingsManager.getSetting('enableModeChangeSound'):
return False
if requireSoundSetting and not _settingsManager.getSetting('enableSound'):
return False
@@ -285,8 +295,7 @@ class SoundThemeManager:
return self._playThemeSound(
soundName,
interrupt=interrupt,
wait=wait,
requireModeChangeSetting=True
wait=wait
)
def playFocusModeSound(self):
+12 -2
View File
@@ -581,6 +581,15 @@ class SpeechGenerator(generator.Generator):
result = []
role = args.get('role', AXObject.get_role(obj))
roleSoundPresentation = _settingsManager.getSetting('roleSoundPresentation')
if roleSoundPresentation == settings.ROLE_SOUND_PRESENTATION_SOUND_ONLY \
and _settingsManager.getSetting('enableSound'):
if AXUtilities.is_check_box(obj) \
or AXUtilities.is_check_menu_item(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):
return []
roleSoundIcon = None
if roleSoundPresentation != settings.ROLE_SOUND_PRESENTATION_SPEECH_ONLY \
and _settingsManager.getSetting('enableSound'):
@@ -627,9 +636,10 @@ class SpeechGenerator(generator.Generator):
result.append(self.getLocalizedRoleName(obj, **args))
result.extend(self.voice(SYSTEM, obj=obj, **args))
if roleSoundPresentation == settings.ROLE_SOUND_PRESENTATION_SOUND_ONLY:
return [roleSoundIcon] if roleSoundIcon else []
if result and roleSoundIcon:
if roleSoundPresentation == settings.ROLE_SOUND_PRESENTATION_SOUND_ONLY:
return [roleSoundIcon]
if roleSoundPresentation == settings.ROLE_SOUND_PRESENTATION_SOUND_AND_SPEECH:
return [roleSoundIcon] + result