# Orca # # Copyright 2005-2008 Sun Microsystems Inc. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the # Free Software Foundation, Inc., Franklin Street, Fifth Floor, # Boston MA 02110-1301 USA. # pylint: disable=too-many-public-methods # pylint: disable=unused-argument """Functionality for working with speech servers.""" from __future__ import annotations from enum import Enum from typing import TYPE_CHECKING, Any from . import gsettings_registry from .ax_utilities_debugging import AXUtilitiesDebugging DEFAULT_VOICE: str = "default" UPPERCASE_VOICE: str = "uppercase" HYPERLINK_VOICE: str = "hyperlink" SYSTEM_VOICE: str = "system" @gsettings_registry.get_registry().gsettings_enum( "org.gnome.Orca.PunctuationStyle", values={"all": 0, "most": 1, "some": 2, "none": 3}, ) class PunctuationStyle(Enum): """Punctuation style enumeration.""" NONE = 3 SOME = 2 MOST = 1 ALL = 0 @property def string_name(self) -> str: """Returns the lowercase string name for this enum value.""" return self.name.lower() @gsettings_registry.get_registry().gsettings_enum( "org.gnome.Orca.CapitalizationStyle", values={"none": 0, "spell": 1, "icon": 2}, ) class CapitalizationStyle(Enum): """Capitalization style enumeration with string values from settings.""" NONE = "none" SPELL = "spell" ICON = "icon" @property def string_name(self) -> str: """Returns the lowercase string name for this enum value.""" return self.name.lower() if TYPE_CHECKING: from collections.abc import Callable, Iterator from typing import ClassVar import gi gi.require_version("Atspi", "2.0") from gi.repository import Atspi from . import input_event from .acss import ACSS class VoiceFamily(dict): """Holds the family description for a voice.""" NAME = "name" GENDER = "gender" LANG = "lang" DIALECT = "dialect" VARIANT = "variant" MALE = "male" FEMALE = "female" settings: ClassVar[dict[str, str | None]] = { NAME: None, GENDER: None, LANG: None, DIALECT: None, VARIANT: None, } def __init__(self, props: dict[str, Any] | None) -> None: """Create and initialize VoiceFamily.""" dict.__init__(self) self.update(VoiceFamily.settings) if props: self.update(props) class SayAllContext: """Contains information about the current state of a Say All operation.""" PROGRESS = 0 INTERRUPTED = 1 COMPLETED = 2 def __init__( self, obj: Atspi.Accessible, utterance: str, start_offset: int = -1, end_offset: int = -1, ) -> None: self.obj = obj self.utterance = utterance self.start_offset = start_offset self.end_offset = end_offset self.current_offset = start_offset self.current_end_offset = None def __str__(self) -> str: obj_string = AXUtilitiesDebugging.as_string(self.obj) return ( f"SAY ALL: {obj_string} '{self.utterance}' ({self.start_offset}-{self.end_offset}, " f"current: {self.current_offset})" ) def copy(self) -> SayAllContext: """Returns a copy of this SayAllContext.""" new = SayAllContext(self.obj, self.utterance, self.start_offset, self.end_offset) new.current_offset = self.current_offset new.current_end_offset = self.current_end_offset return new def __eq__(self, other: object) -> bool: if not isinstance(other, SayAllContext): return False return ( self.start_offset == other.start_offset and self.end_offset == other.end_offset and self.obj == other.obj and self.utterance == other.utterance ) def __hash__(self) -> int: return hash((self.start_offset, self.end_offset, self.obj, self.utterance)) class SpeechServer: """Provides speech server abstraction.""" @staticmethod def get_factory_name() -> str: """Returns a localized name describing this factory.""" return "" @staticmethod def get_speech_servers() -> list[Any]: """Returns a list of available speech servers.""" return [] @staticmethod def get_speech_server(_info: Any) -> SpeechServer | None: """Gets a given SpeechServer based upon the [name, id] info.""" return None @staticmethod def shutdown_active_servers() -> None: """Cleans up and shuts down this factory.""" def get_info(self) -> list[str]: """Returns [name, id] of the current speech server.""" return ["", ""] def get_voice_families(self) -> list[VoiceFamily]: """Returns a list of all known VoiceFamily instances provided by the server.""" return [] def get_voice_family(self) -> VoiceFamily: """Returns the current voice family as a VoiceFamily dictionary.""" return VoiceFamily({}) def set_voice_family(self, family: VoiceFamily) -> None: """Sets the voice family to family VoiceFamily dictionary.""" def speak_character( self, character: str, acss: ACSS | None = None, cap_style: CapitalizationStyle | None = None, ) -> None: """Speaks character.""" def speak_key_event(self, event: input_event.KeyboardEvent, acss: ACSS | None = None) -> None: """Speaks event.""" def speak(self, text: str | None = None, acss: ACSS | None = None) -> None: """Speaks text using the voice specified by acss.""" def say_all( self, utterance_iterator: Iterator[tuple[SayAllContext, ACSS]], progress_callback: Callable[[SayAllContext, int], None], ) -> None: """Iterates through the given utterance_iterator, speaking each utterance.""" def set_default_voice(self, default_voice: dict[str, Any]) -> None: """Sets the default voice ACSS properties for fallback use.""" def increase_speech_rate(self, step: int = 5) -> None: """Increases the speech rate.""" def decrease_speech_rate(self, step: int = 5) -> None: """Decreases the speech rate.""" def increase_speech_pitch(self, step: float = 0.5) -> None: """Increases the speech pitch.""" def decrease_speech_pitch(self, step: float = 0.5) -> None: """Decreases the speech pitch.""" def increase_speech_volume(self, step: float = 0.5) -> None: """Increases the speech volume.""" def decrease_speech_volume(self, step: float = 0.5) -> None: """Decreases the speech volume.""" def update_capitalization_style(self, style: str) -> None: """Updates the capitalization style used by the speech server.""" def update_punctuation_level(self, level: PunctuationStyle) -> None: """Punctuation level changed, inform this speechServer.""" def stop(self) -> None: """Stops ongoing speech and flushes the queue.""" def shutdown(self) -> None: """Shuts down the speech engine.""" def reset(self) -> None: """Resets the speech engine.""" def clear_cached_voice_properties(self) -> None: """Clear cached voice properties to force reapplication on next speech.""" def get_output_module(self) -> str: """Returns the output module associated with this speech server.""" return "" def set_output_module(self, module_id: str) -> None: """Sets the output module associated with this speech server.""" def list_output_modules(self) -> tuple[str, ...]: """Return names of available output modules as a tuple of strings.""" return () def get_voice_families_for_language( self, language: str, dialect: str = "", variant: str | None = None, maximum: int | None = None, ) -> list[tuple[str, str, str | None]]: """Returns the families for language available in the current synthesizer.""" return [] def _get_language_and_dialect(self, acss_family: dict[str, Any] | None) -> tuple[str, str]: """Returns the language and dialect from the ACSS family dictionary.""" return "", "" def get_language_and_dialect(self, acss_family: dict[str, Any] | None) -> tuple[str, str]: """Returns the language and dialect from the ACSS family dictionary.""" return self._get_language_and_dialect(acss_family)