-
Notifications
You must be signed in to change notification settings - Fork 11
New SRAL_GetTTSEngines and SRAL_GetAssistiveTechEngines bit mask functions #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,8 @@ class SRALEngine(IntEnum): | |
| VOICE_OVER = 1 << 8 | ||
| NS_SPEECH = 1 << 9 | ||
| AV_SPEECH = 1 << 10 | ||
| ANDROID_ACCESSIBILITY_MANAGER = 1 << 11 | ||
| ANDROID_TEXT_TO_SPEECH = 1 << 12 | ||
|
Comment on lines
+34
to
+35
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as above, missing engines from the Android PRs |
||
|
|
||
| class SRALFeature(IntEnum): | ||
| """ | ||
|
|
@@ -46,6 +48,18 @@ class SRALFeature(IntEnum): | |
| SPEAK_TO_MEMORY = 1 << 8 | ||
| SPELLING = 1 << 9 | ||
|
|
||
| class SRALEngineCategory(IntEnum): | ||
| """ | ||
| Broad categories an engine can belong to. | ||
|
|
||
| Unlike SRALEngine, these values are not bit flags; an engine has exactly | ||
| one category. | ||
| """ | ||
| UNKNOWN = 0 | ||
| SCREEN_READER = 1 | ||
| TEXT_TO_SPEECH_ENGINE = 2 | ||
| ACCESSIBILITY_PROVIDER = 3 | ||
|
|
||
| class SRALParam(IntEnum): | ||
| """ | ||
| Enumeration of engine parameters. | ||
|
|
@@ -217,6 +231,15 @@ def ptr(self): | |
| _sral_lib.SRAL_GetActiveEngines.argtypes = [] | ||
| _sral_lib.SRAL_GetActiveEngines.restype = ctypes.c_int | ||
|
|
||
| _sral_lib.SRAL_GetTTSEngines.argtypes = [] | ||
| _sral_lib.SRAL_GetTTSEngines.restype = ctypes.c_int | ||
|
|
||
| _sral_lib.SRAL_GetAssistiveTechEngines.argtypes = [] | ||
| _sral_lib.SRAL_GetAssistiveTechEngines.restype = ctypes.c_int | ||
|
|
||
| _sral_lib.SRAL_GetEngineCategory.argtypes = [ctypes.c_int] | ||
| _sral_lib.SRAL_GetEngineCategory.restype = ctypes.c_int | ||
|
|
||
| _sral_lib.SRAL_GetEngineName.argtypes = [ctypes.c_int] | ||
| _sral_lib.SRAL_GetEngineName.restype = ctypes.c_char_p | ||
|
|
||
|
|
@@ -772,6 +795,59 @@ def get_active_engines(self) -> int: | |
| if not _sral_lib: return 0 | ||
| return _sral_lib.SRAL_GetActiveEngines() | ||
|
|
||
| def get_tts_engines(self) -> int: | ||
| """ | ||
| Get the bitmask of engines that are pure text-to-speech synthesizers. | ||
|
|
||
| The mask is derived at runtime from each available engine's category, | ||
| so it reflects the engines available on the current platform and | ||
| requires the library to be initialized. | ||
|
|
||
| Intended use: pass to set_engines_exclude when the application wants | ||
| to opt out of TTS output (e.g., only speak through assistive tech | ||
| unless the user has enabled an in-app TTS option). | ||
|
|
||
| Returns: | ||
| A bitmask of SRALEngine enums representing TTS engines. | ||
| """ | ||
| self._check_initialized() | ||
| if not _sral_lib: return 0 | ||
| return _sral_lib.SRAL_GetTTSEngines() | ||
|
|
||
| def get_assistive_tech_engines(self) -> int: | ||
| """ | ||
| Get the bitmask of engines that represent assistive technology | ||
| (screen readers and the accessibility providers that drive them). | ||
|
|
||
| The mask is derived at runtime from each available engine's category, | ||
| so it reflects the engines available on the current platform and | ||
| requires the library to be initialized. | ||
|
|
||
| Returns: | ||
| A bitmask of SRALEngine enums representing assistive-tech engines. | ||
| """ | ||
| self._check_initialized() | ||
| if not _sral_lib: return 0 | ||
| return _sral_lib.SRAL_GetAssistiveTechEngines() | ||
|
|
||
| def get_engine_category(self, engine: SRALEngine) -> SRALEngineCategory: | ||
| """ | ||
| Get the category of the specified engine. | ||
|
|
||
| The category is reported by the engine itself. The engine is resolved | ||
| against the engines available on the current platform, so an engine | ||
| that is not available here returns SRALEngineCategory.UNKNOWN. | ||
|
|
||
| Args: | ||
| engine: The identifier of the engine to query. | ||
|
|
||
| Returns: | ||
| The engine's SRALEngineCategory. | ||
| """ | ||
| self._check_initialized() | ||
| if not _sral_lib: return SRALEngineCategory.UNKNOWN | ||
| return SRALEngineCategory(_sral_lib.SRAL_GetEngineCategory(engine.value)) | ||
|
|
||
| def set_engines_exclude(self, engines_exclude: int) -> bool: | ||
| """ | ||
| Exclude certain engines from auto-update. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,8 +27,12 @@ const ( | |
| NSSpeechEngine | ||
| // AVSpeechEngine — AVFoundation Speech Synthesizer (AVSpeechSynthesizer) for Apple platforms. | ||
| AVSpeechEngine | ||
| // AndroidAccessibilityManagerEngine — Android AccessibilityManager, drives the active screen reader (typically TalkBack). | ||
| AndroidAccessibilityManagerEngine | ||
| // AndroidTextToSpeechEngine — Android TextToSpeech synthesizer. | ||
| AndroidTextToSpeechEngine | ||
| // AllEngines is a bitmask of all supported engines. | ||
| AllEngines Engine = NVDAEngine | JAWSEngine | ZDSREngine | NarratorEngine | UIAEngine | SAPIEngine | SpeechDispatcherEngine | NSSpeechEngine | VoiceOverEngine | AVSpeechEngine | ||
| AllEngines Engine = NVDAEngine | JAWSEngine | ZDSREngine | NarratorEngine | UIAEngine | SAPIEngine | SpeechDispatcherEngine | NSSpeechEngine | VoiceOverEngine | AVSpeechEngine | AndroidAccessibilityManagerEngine | AndroidTextToSpeechEngine | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing engines from the Android PRs 😅 |
||
| // InvalidEngine represents an error or uninitialized engine state. | ||
| InvalidEngine Engine = -1 | ||
| // NoSpecifiedEngine is used for auto-selection of the engine. | ||
|
|
@@ -68,6 +72,24 @@ func (f Feature) IsSupported(other Feature) bool { | |
| return (f & other) != 0 | ||
| } | ||
|
|
||
| // EngineCategory is the broad category an engine belongs to. Unlike Engine, | ||
| // these values are not bit flags; an engine has exactly one category. | ||
| type EngineCategory int | ||
|
|
||
| const ( | ||
| // UnknownCategory indicates the category is unknown, or the engine is not | ||
| // available/initialized. | ||
| UnknownCategory EngineCategory = iota | ||
| // ScreenReaderCategory — a screen reader (e.g. NVDA, JAWS, ZDSR, VoiceOver). | ||
| ScreenReaderCategory | ||
| // TextToSpeechCategory — a pure text-to-speech synthesizer (e.g. SAPI, | ||
| // Speech Dispatcher, NSSpeech, AVSpeech, Android TextToSpeech). | ||
| TextToSpeechCategory | ||
| // AccessibilityProviderCategory — an accessibility provider that drives | ||
| // whatever assistive tech is consuming it (e.g. UIA, Android AccessibilityManager). | ||
| AccessibilityProviderCategory | ||
| ) | ||
|
|
||
| // EngineParam defines parameters that can be set or retrieved from a speech engine. | ||
| type EngineParam int | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,7 +40,7 @@ void PrintEngineNames(int engineBitmask, const char* title) { | |
| return; | ||
| } | ||
| bool found = false; | ||
| for (int engine_val = SRAL_ENGINE_NVDA; engine_val <= SRAL_ENGINE_AV_SPEECH; engine_val <<= 1) { | ||
| for (int engine_val = SRAL_ENGINE_NVDA; engine_val <= SRAL_ENGINE_ANDROID_TEXT_TO_SPEECH; engine_val <<= 1) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. throughout this file we reference |
||
| if (engineBitmask & engine_val) { | ||
| const char* name = SRAL_GetEngineName(engine_val); | ||
| printf(" - %s (0x%X)\n", name ? name : "Unknown Engine", engine_val); | ||
|
|
@@ -53,6 +53,15 @@ void PrintEngineNames(int engineBitmask, const char* title) { | |
| printf("\n"); | ||
| } | ||
|
|
||
| const char* CategoryName(int category) { | ||
| switch (category) { | ||
| case SRAL_ENGINE_CATEGORY_SCREEN_READER: return "Screen Reader"; | ||
| case SRAL_ENGINE_CATEGORY_TEXT_TO_SPEECH_ENGINE: return "Text-To-Speech Engine"; | ||
| case SRAL_ENGINE_CATEGORY_ACCESSIBILITY_PROVIDER: return "Accessibility Provider"; | ||
| default: return "Unknown"; | ||
| } | ||
| } | ||
|
|
||
| void print_supported_features(int features) { | ||
| printf("Supported Features (0x%X):\n", features); | ||
| if (features == 0) { | ||
|
|
@@ -122,26 +131,47 @@ int main(void) { | |
| int active_engines = SRAL_GetActiveEngines(); | ||
| PrintEngineNames(active_engines, "Currently Active/Usable Engines"); | ||
|
|
||
| int tts_engines = SRAL_GetTTSEngines(); | ||
| PrintEngineNames(tts_engines, "TTS Engines (category)"); | ||
|
|
||
| int at_engines = SRAL_GetAssistiveTechEngines(); | ||
| PrintEngineNames(at_engines, "Assistive-Tech Engines (category)"); | ||
|
|
||
| bool at_active = (active_engines & at_engines) != 0; | ||
| printf("Assistive tech currently active: %s\n\n", at_active ? "yes" : "no"); | ||
|
|
||
| CHECK((tts_engines & at_engines) == 0, | ||
| "TTS and assistive-tech masks are disjoint.", | ||
| "TTS and assistive-tech masks overlap!"); | ||
|
|
||
| printf("\nCategory of each available engine (SRAL_GetEngineCategory):\n"); | ||
| for (int e_val = SRAL_ENGINE_NVDA; e_val <= SRAL_ENGINE_ANDROID_TEXT_TO_SPEECH; e_val <<= 1) { | ||
| if (available_engines & e_val) { | ||
| printf(" - %s: %s\n", SRAL_GetEngineName(e_val), CategoryName(SRAL_GetEngineCategory(e_val))); | ||
| } | ||
| } | ||
| printf("\n"); | ||
|
|
||
| int current_engine_id = SRAL_GetCurrentEngine(); | ||
| printf("Current Default Engine: %s (0x%X)\n", SRAL_GetEngineName(current_engine_id) ? SRAL_GetEngineName(current_engine_id) : "None/Unknown", current_engine_id); | ||
|
|
||
| printf("\nNames of all SRAL_Engines enum members (as per SRAL_GetEngineName):\n"); | ||
| for (int e_val = SRAL_ENGINE_NVDA; e_val <= SRAL_ENGINE_AV_SPEECH; e_val <<= 1) { | ||
| for (int e_val = SRAL_ENGINE_NVDA; e_val <= SRAL_ENGINE_ANDROID_TEXT_TO_SPEECH; e_val <<= 1) { | ||
| const char* name = SRAL_GetEngineName(e_val); | ||
| printf(" Engine ID 0x%X: %s\n", e_val, name ? name : "(Name not defined or not a single engine ID)"); | ||
| } | ||
|
|
||
|
|
||
| int specific_engine_for_ex_tests = SRAL_ENGINE_NONE; | ||
| if (active_engines != SRAL_ENGINE_NONE) { | ||
| for (int e_val = SRAL_ENGINE_NVDA; e_val <= SRAL_ENGINE_AV_SPEECH; e_val <<= 1) { | ||
| for (int e_val = SRAL_ENGINE_NVDA; e_val <= SRAL_ENGINE_ANDROID_TEXT_TO_SPEECH; e_val <<= 1) { | ||
| if ((active_engines & e_val) && e_val != current_engine_id) { | ||
| specific_engine_for_ex_tests = e_val; | ||
| break; | ||
| } | ||
| } | ||
| if (specific_engine_for_ex_tests == SRAL_ENGINE_NONE) { | ||
| for (int e_val = SRAL_ENGINE_NVDA; e_val <= SRAL_ENGINE_AV_SPEECH; e_val <<= 1) { | ||
| for (int e_val = SRAL_ENGINE_NVDA; e_val <= SRAL_ENGINE_ANDROID_TEXT_TO_SPEECH; e_val <<= 1) { | ||
| if (active_engines & e_val) { | ||
| specific_engine_for_ex_tests = e_val; | ||
| break; | ||
|
|
@@ -616,6 +646,19 @@ int main(void) { | |
|
|
||
| CHECK(engines_to_exclude == new_engines_to_exclude, "Engines exclude set/get matches", "Engines exclude set/get mismatch"); | ||
|
|
||
| // Regression check: excluding the whole TTS category must never leave a TTS | ||
| // engine selected as current. Previously a sticky fallback engine (e.g. NS | ||
| // Speech) could keep speaking through TTS after it had been excluded. | ||
| CHECK_SRAL(SRAL_SetEnginesExclude(SRAL_GetTTSEngines()), "Excluded the TTS engine category."); | ||
| int current_with_tts_excluded = SRAL_GetCurrentEngine(); | ||
| printf(" Current engine with TTS excluded: %s (0x%X)\n", | ||
| SRAL_GetEngineName(current_with_tts_excluded) ? SRAL_GetEngineName(current_with_tts_excluded) : "None", | ||
| current_with_tts_excluded); | ||
| CHECK((current_with_tts_excluded & SRAL_GetTTSEngines()) == 0, | ||
| "No TTS engine is current while the TTS category is excluded.", | ||
| "A TTS engine is still current despite the TTS category being excluded!"); | ||
| SRAL_SetEnginesExclude(engines_to_exclude); // Restore the prior exclude state. | ||
|
|
||
|
|
||
| TEST_SECTION("Unregister Keyboard Hooks"); | ||
| SRAL_UnregisterKeyboardHooks(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
noticed that we were missing the Apple Silicone build targets, so including that here as well