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
-
-
-
- False
- True
- 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