diff --git a/sounds/default/switch_off.wav b/sounds/default/switch_off.wav new file mode 100644 index 0000000..956175c Binary files /dev/null and b/sounds/default/switch_off.wav differ diff --git a/sounds/default/switch_on.wav b/sounds/default/switch_on.wav new file mode 100644 index 0000000..496f0e7 Binary files /dev/null and b/sounds/default/switch_on.wav differ diff --git a/src/cthulhu/cthulhu-setup.ui b/src/cthulhu/cthulhu-setup.ui index f12d146..34abfcd 100644 --- a/src/cthulhu/cthulhu-setup.ui +++ b/src/cthulhu/cthulhu-setup.ui @@ -1035,23 +1035,6 @@ False vertical 6 - - - Play sounds when switching between _browse and focus modes - True - True - False - True - 0 - True - - - - False - True - 0 - - True @@ -1094,7 +1077,7 @@ False True - 1 + 0 @@ -1139,7 +1122,7 @@ False True - 2 + 1 diff --git a/src/cthulhu/cthulhu_gui_prefs.py b/src/cthulhu/cthulhu_gui_prefs.py index ff9e7e5..f2dec26 100644 --- a/src/cthulhu/cthulhu_gui_prefs.py +++ b/src/cthulhu/cthulhu_gui_prefs.py @@ -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() diff --git a/src/cthulhu/formatting.py b/src/cthulhu/formatting.py index 0d42196..929c4a1 100644 --- a/src/cthulhu/formatting.py +++ b/src/cthulhu/formatting.py @@ -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', diff --git a/src/cthulhu/guilabels.py b/src/cthulhu/guilabels.py index 730e0fb..a057472 100644 --- a/src/cthulhu/guilabels.py +++ b/src/cthulhu/guilabels.py @@ -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. diff --git a/src/cthulhu/scripts/web/script.py b/src/cthulhu/scripts/web/script.py index c12c94c..a9f4341 100644 --- a/src/cthulhu/scripts/web/script.py +++ b/src/cthulhu/scripts/web/script.py @@ -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: diff --git a/src/cthulhu/scripts/web/script_utilities.py b/src/cthulhu/scripts/web/script_utilities.py index 466b3d1..3ab47ae 100644 --- a/src/cthulhu/scripts/web/script_utilities.py +++ b/src/cthulhu/scripts/web/script_utilities.py @@ -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): diff --git a/src/cthulhu/scripts/web/speech_generator.py b/src/cthulhu/scripts/web/speech_generator.py index fcf95c7..deb6eb9 100644 --- a/src/cthulhu/scripts/web/speech_generator.py +++ b/src/cthulhu/scripts/web/speech_generator.py @@ -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): diff --git a/src/cthulhu/settings.py b/src/cthulhu/settings.py index 5a2cad0..d4ea97d 100644 --- a/src/cthulhu/settings.py +++ b/src/cthulhu/settings.py @@ -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 diff --git a/src/cthulhu/sound_generator.py b/src/cthulhu/sound_generator.py index 0b0246b..cba7b22 100644 --- a/src/cthulhu/sound_generator.py +++ b/src/cthulhu/sound_generator.py @@ -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: diff --git a/src/cthulhu/sound_theme_manager.py b/src/cthulhu/sound_theme_manager.py index dda047c..0759cc1 100644 --- a/src/cthulhu/sound_theme_manager.py +++ b/src/cthulhu/sound_theme_manager.py @@ -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): diff --git a/src/cthulhu/speech_generator.py b/src/cthulhu/speech_generator.py index 6cfd3ec..8bc1473 100644 --- a/src/cthulhu/speech_generator.py +++ b/src/cthulhu/speech_generator.py @@ -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