Module speechlight.windows
¶
Windows speech.
Class Speech(BaseSpeech)
¶
Implements Speech for Windows.
Source code in speechlight/windows.py
class Speech(BaseSpeech): # NOQA: PLR0904
"""Implements Speech for Windows."""
_find_window: Optional[Any] = None
_nvda: Optional[Any] = None
_sa: Optional[Any] = None
_sapi: Optional[Any] = None
_jfw: Optional[Any] = None
def __init__(self) -> None: # pragma: no cover
"""Defines the constructor."""
if sys.platform == "win32":
self._find_window: ctypes._NamedFuncPointer = ctypes.WinDLL("user32").FindWindowW
self._find_window.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p]
self._find_window.restype = ctypes.c_void_p
arch: str = "32" if SYSTEM_ARCHITECTURE == "32bit" else "64"
self._nvda: ctypes.WinDLL = ctypes.windll.LoadLibrary(
str(LIB_DIRECTORY / f"nvdaControllerClient{arch}.dll")
)
self._sa: ctypes.WinDLL = ctypes.windll.LoadLibrary(str(LIB_DIRECTORY / f"SAAPI{arch}.dll"))
self._nvda.nvdaController_brailleMessage.argtypes = (ctypes.c_wchar_p,)
self._nvda.nvdaController_speakText.argtypes = (ctypes.c_wchar_p,)
self._sa.SA_BrlShowTextW.argtypes = (ctypes.c_wchar_p,)
self._sa.SA_SayW.argtypes = (ctypes.c_wchar_p,)
@property
def sapi(self) -> Any: # type: ignore[misc] # pragma: no cover
"""The SAPI COM object."""
self._sapi = dispatch("SAPI.SpVoice")
return self._sapi
@property
def jfw(self) -> Any: # type: ignore[misc] # pragma: no cover
"""The JFW COM object."""
self._jfw = dispatch("FreedomSci.JawsApi")
return self._jfw
def jfw_braille(self, text: str) -> None:
"""
Brailles text using JFW.
Args:
text: The text to braille.
"""
self.jfw_output(text, braille=True)
def jfw_output(
self,
text: str,
*,
braille: bool = False,
speak: bool = False,
interrupt: bool = False,
) -> None:
"""
Outputs text using JFW.
Args:
text: The output text.
braille: Output text in braille.
speak: Output text using speech.
interrupt: True if the speech should be silenced before speaking.
"""
jfw = self.jfw
if jfw is not None:
if speak:
jfw.SayString(text, int(bool(interrupt)))
if braille:
jfw.RunFunction('BrailleString("{text}")'.format(text=text.replace('"', "'")))
def jfw_running(self) -> bool:
"""
Determines if JFW is running.
Returns:
True if JFW is running, False otherwise.
"""
status: bool = False
if self._find_window is not None:
status = bool(self._find_window("JFWUI2", None))
return status
def jfw_say(self, text: str, *, interrupt: bool = False) -> None:
"""
Speak text using JFW.
Args:
text: The text to be spoken.
interrupt: True if the speech should be silenced before speaking.
"""
self.jfw_output(text, speak=True, interrupt=interrupt)
def jfw_silence(self) -> None:
"""Cancels JFW speech and flushes the speech buffer."""
jfw = self.jfw
if jfw is not None:
jfw.StopSpeech()
def nvda_braille(self, text: str) -> None:
"""
Brailles text using NVDA.
Args:
text: The text to braille.
"""
if self._nvda is not None:
self._nvda.nvdaController_brailleMessage(text)
def nvda_output(self, text: str, *, interrupt: bool = False) -> None:
"""
Outputs text using NVDA.
Args:
text: The output text.
interrupt: True if the speech should be silenced before speaking.
"""
self.nvda_say(text, interrupt=interrupt)
self.nvda_braille(text)
def nvda_running(self) -> bool:
"""
Determines if NVDA is running.
Returns:
True if NVDA is running, False otherwise.
"""
status: bool = False
if self._nvda is not None:
status = bool(self._nvda.nvdaController_testIfRunning() == 0)
return status
def nvda_say(self, text: str, *, interrupt: bool = False) -> None:
"""
Speak text using NVDA.
Args:
text: The text to be spoken.
interrupt: True if the speech should be silenced before speaking.
"""
if self._nvda is not None:
if interrupt:
self.nvda_silence()
self._nvda.nvdaController_speakText(text)
def nvda_silence(self) -> None:
"""Cancels NVDA speech and flushes the speech buffer."""
if self._nvda is not None:
self._nvda.nvdaController_cancelSpeech()
def sa_braille(self, text: str) -> None:
"""
Brailles text using System Access.
Args:
text: The text to braille.
"""
if self._sa is not None:
self._sa.SA_BrlShowTextW(text)
def sa_output(self, text: str, *, interrupt: bool = False) -> None:
"""
Outputs text using System Access.
Args:
text: The output text.
interrupt: True if the speech should be silenced before speaking.
"""
self.sa_say(text, interrupt=interrupt)
self.sa_braille(text)
def sa_running(self) -> bool:
"""
Determines if System Access is running.
Returns:
True if System Access is running, False otherwise.
"""
status: bool = False
if self._sa is not None:
status = bool(self._sa.SA_IsRunning())
return status
def sa_say(self, text: str, *, interrupt: bool = False) -> None:
"""
Speak text using System Access.
Args:
text: The text to be spoken.
interrupt: True if the speech should be silenced before speaking.
"""
if self._sa is not None:
if interrupt:
self.sa_silence()
self._sa.SA_SayW(text)
def sa_silence(self) -> None:
"""Cancels System Access speech and flushes the speech buffer."""
if self._sa is not None:
self._sa.SA_StopAudio()
def sapi_say(self, text: str, *, interrupt: bool = False) -> None:
"""
Speak text using SAPI.
Args:
text: The text to be spoken.
interrupt: True if the speech should be silenced before speaking.
"""
if self.sapi is not None:
if interrupt:
self.sapi.Speak(text, SPF_ASYNC | SPF_PURGE_BEFORE_SPEAK | SPF_IS_NOT_XML)
else:
self.sapi.Speak(text, SPF_ASYNC | SPF_IS_NOT_XML)
def sapi_silence(self) -> None:
"""Cancels SAPI speech and flushes the speech buffer."""
if self.sapi is not None:
self.sapi.Speak("", SPF_ASYNC | SPF_PURGE_BEFORE_SPEAK | SPF_IS_NOT_XML)
def braille(self, text: str) -> None: # NOQA: D102
if self.nvda_running():
self.nvda_braille(text)
elif self.sa_running():
self.sa_braille(text)
elif self.jfw_running():
self.jfw_braille(text)
def output(self, text: str, *, interrupt: bool = False) -> None: # NOQA: D102
if self.nvda_running():
self.nvda_output(text, interrupt=interrupt)
elif self.sa_running():
self.sa_output(text, interrupt=interrupt)
elif self.jfw_running():
self.jfw_output(text, braille=True, speak=True, interrupt=interrupt)
else:
self.sapi_say(text, interrupt=interrupt)
def say(self, text: str, *, interrupt: bool = False) -> None: # NOQA: D102
if self.nvda_running():
self.nvda_say(text, interrupt=interrupt)
elif self.sa_running():
self.sa_say(text, interrupt=interrupt)
elif self.jfw_running():
self.jfw_say(text, interrupt=interrupt)
else:
self.sapi_say(text, interrupt=interrupt)
def silence(self) -> None: # NOQA: D102
if self.nvda_running():
self.nvda_silence()
elif self.sa_running():
self.sa_silence()
elif self.jfw_running():
self.jfw_silence()
else:
self.sapi_silence()
def speaking(self) -> bool: # NOQA: D102
if self.nvda_running() or self.sa_running() or self.jfw_running():
# None of the screen reader APIs support retrieving speaking status.
return False
if self.sapi is not None:
return bool(self.sapi.Status.RunningState != 1)
return False
Attribute jfw: Any
property
readonly
¶
The JFW COM object.
Attribute sapi: Any
property
readonly
¶
The SAPI COM object.
Method __init__(self)
special
¶
Defines the constructor.
Source code in speechlight/windows.py
def __init__(self) -> None: # pragma: no cover
"""Defines the constructor."""
if sys.platform == "win32":
self._find_window: ctypes._NamedFuncPointer = ctypes.WinDLL("user32").FindWindowW
self._find_window.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p]
self._find_window.restype = ctypes.c_void_p
arch: str = "32" if SYSTEM_ARCHITECTURE == "32bit" else "64"
self._nvda: ctypes.WinDLL = ctypes.windll.LoadLibrary(
str(LIB_DIRECTORY / f"nvdaControllerClient{arch}.dll")
)
self._sa: ctypes.WinDLL = ctypes.windll.LoadLibrary(str(LIB_DIRECTORY / f"SAAPI{arch}.dll"))
self._nvda.nvdaController_brailleMessage.argtypes = (ctypes.c_wchar_p,)
self._nvda.nvdaController_speakText.argtypes = (ctypes.c_wchar_p,)
self._sa.SA_BrlShowTextW.argtypes = (ctypes.c_wchar_p,)
self._sa.SA_SayW.argtypes = (ctypes.c_wchar_p,)
Method braille(self, text)
¶
Brailles text.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
text |
str |
The text to be brailled. |
required |
Source code in speechlight/windows.py
def braille(self, text: str) -> None: # NOQA: D102
if self.nvda_running():
self.nvda_braille(text)
elif self.sa_running():
self.sa_braille(text)
elif self.jfw_running():
self.jfw_braille(text)
Method jfw_braille(self, text)
¶
Brailles text using JFW.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
text |
str |
The text to braille. |
required |
Source code in speechlight/windows.py
def jfw_braille(self, text: str) -> None:
"""
Brailles text using JFW.
Args:
text: The text to braille.
"""
self.jfw_output(text, braille=True)
Method jfw_output(self, text, *, braille=False, speak=False, interrupt=False)
¶
Outputs text using JFW.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
text |
str |
The output text. |
required |
braille |
bool |
Output text in braille. |
False |
speak |
bool |
Output text using speech. |
False |
interrupt |
bool |
True if the speech should be silenced before speaking. |
False |
Source code in speechlight/windows.py
def jfw_output(
self,
text: str,
*,
braille: bool = False,
speak: bool = False,
interrupt: bool = False,
) -> None:
"""
Outputs text using JFW.
Args:
text: The output text.
braille: Output text in braille.
speak: Output text using speech.
interrupt: True if the speech should be silenced before speaking.
"""
jfw = self.jfw
if jfw is not None:
if speak:
jfw.SayString(text, int(bool(interrupt)))
if braille:
jfw.RunFunction('BrailleString("{text}")'.format(text=text.replace('"', "'")))
Method jfw_running(self)
¶
Determines if JFW is running.
Returns:
Type | Description |
---|---|
bool |
True if JFW is running, False otherwise. |
Source code in speechlight/windows.py
def jfw_running(self) -> bool:
"""
Determines if JFW is running.
Returns:
True if JFW is running, False otherwise.
"""
status: bool = False
if self._find_window is not None:
status = bool(self._find_window("JFWUI2", None))
return status
Method jfw_say(self, text, *, interrupt=False)
¶
Speak text using JFW.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
text |
str |
The text to be spoken. |
required |
interrupt |
bool |
True if the speech should be silenced before speaking. |
False |
Source code in speechlight/windows.py
def jfw_say(self, text: str, *, interrupt: bool = False) -> None:
"""
Speak text using JFW.
Args:
text: The text to be spoken.
interrupt: True if the speech should be silenced before speaking.
"""
self.jfw_output(text, speak=True, interrupt=interrupt)
Method jfw_silence(self)
¶
Cancels JFW speech and flushes the speech buffer.
Source code in speechlight/windows.py
def jfw_silence(self) -> None:
"""Cancels JFW speech and flushes the speech buffer."""
jfw = self.jfw
if jfw is not None:
jfw.StopSpeech()
Method nvda_braille(self, text)
¶
Brailles text using NVDA.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
text |
str |
The text to braille. |
required |
Source code in speechlight/windows.py
def nvda_braille(self, text: str) -> None:
"""
Brailles text using NVDA.
Args:
text: The text to braille.
"""
if self._nvda is not None:
self._nvda.nvdaController_brailleMessage(text)
Method nvda_output(self, text, *, interrupt=False)
¶
Outputs text using NVDA.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
text |
str |
The output text. |
required |
interrupt |
bool |
True if the speech should be silenced before speaking. |
False |
Source code in speechlight/windows.py
def nvda_output(self, text: str, *, interrupt: bool = False) -> None:
"""
Outputs text using NVDA.
Args:
text: The output text.
interrupt: True if the speech should be silenced before speaking.
"""
self.nvda_say(text, interrupt=interrupt)
self.nvda_braille(text)
Method nvda_running(self)
¶
Determines if NVDA is running.
Returns:
Type | Description |
---|---|
bool |
True if NVDA is running, False otherwise. |
Source code in speechlight/windows.py
def nvda_running(self) -> bool:
"""
Determines if NVDA is running.
Returns:
True if NVDA is running, False otherwise.
"""
status: bool = False
if self._nvda is not None:
status = bool(self._nvda.nvdaController_testIfRunning() == 0)
return status
Method nvda_say(self, text, *, interrupt=False)
¶
Speak text using NVDA.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
text |
str |
The text to be spoken. |
required |
interrupt |
bool |
True if the speech should be silenced before speaking. |
False |
Source code in speechlight/windows.py
def nvda_say(self, text: str, *, interrupt: bool = False) -> None:
"""
Speak text using NVDA.
Args:
text: The text to be spoken.
interrupt: True if the speech should be silenced before speaking.
"""
if self._nvda is not None:
if interrupt:
self.nvda_silence()
self._nvda.nvdaController_speakText(text)
Method nvda_silence(self)
¶
Cancels NVDA speech and flushes the speech buffer.
Source code in speechlight/windows.py
def nvda_silence(self) -> None:
"""Cancels NVDA speech and flushes the speech buffer."""
if self._nvda is not None:
self._nvda.nvdaController_cancelSpeech()
Method output(self, text, *, interrupt=False)
¶
Speaks and brailles text.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
text |
str |
The output text. |
required |
interrupt |
bool |
True if the speech should be silenced before speaking. |
False |
Source code in speechlight/windows.py
def output(self, text: str, *, interrupt: bool = False) -> None: # NOQA: D102
if self.nvda_running():
self.nvda_output(text, interrupt=interrupt)
elif self.sa_running():
self.sa_output(text, interrupt=interrupt)
elif self.jfw_running():
self.jfw_output(text, braille=True, speak=True, interrupt=interrupt)
else:
self.sapi_say(text, interrupt=interrupt)
Method sa_braille(self, text)
¶
Brailles text using System Access.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
text |
str |
The text to braille. |
required |
Source code in speechlight/windows.py
def sa_braille(self, text: str) -> None:
"""
Brailles text using System Access.
Args:
text: The text to braille.
"""
if self._sa is not None:
self._sa.SA_BrlShowTextW(text)
Method sa_output(self, text, *, interrupt=False)
¶
Outputs text using System Access.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
text |
str |
The output text. |
required |
interrupt |
bool |
True if the speech should be silenced before speaking. |
False |
Source code in speechlight/windows.py
def sa_output(self, text: str, *, interrupt: bool = False) -> None:
"""
Outputs text using System Access.
Args:
text: The output text.
interrupt: True if the speech should be silenced before speaking.
"""
self.sa_say(text, interrupt=interrupt)
self.sa_braille(text)
Method sa_running(self)
¶
Determines if System Access is running.
Returns:
Type | Description |
---|---|
bool |
True if System Access is running, False otherwise. |
Source code in speechlight/windows.py
def sa_running(self) -> bool:
"""
Determines if System Access is running.
Returns:
True if System Access is running, False otherwise.
"""
status: bool = False
if self._sa is not None:
status = bool(self._sa.SA_IsRunning())
return status
Method sa_say(self, text, *, interrupt=False)
¶
Speak text using System Access.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
text |
str |
The text to be spoken. |
required |
interrupt |
bool |
True if the speech should be silenced before speaking. |
False |
Source code in speechlight/windows.py
def sa_say(self, text: str, *, interrupt: bool = False) -> None:
"""
Speak text using System Access.
Args:
text: The text to be spoken.
interrupt: True if the speech should be silenced before speaking.
"""
if self._sa is not None:
if interrupt:
self.sa_silence()
self._sa.SA_SayW(text)
Method sa_silence(self)
¶
Cancels System Access speech and flushes the speech buffer.
Source code in speechlight/windows.py
def sa_silence(self) -> None:
"""Cancels System Access speech and flushes the speech buffer."""
if self._sa is not None:
self._sa.SA_StopAudio()
Method sapi_say(self, text, *, interrupt=False)
¶
Speak text using SAPI.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
text |
str |
The text to be spoken. |
required |
interrupt |
bool |
True if the speech should be silenced before speaking. |
False |
Source code in speechlight/windows.py
def sapi_say(self, text: str, *, interrupt: bool = False) -> None:
"""
Speak text using SAPI.
Args:
text: The text to be spoken.
interrupt: True if the speech should be silenced before speaking.
"""
if self.sapi is not None:
if interrupt:
self.sapi.Speak(text, SPF_ASYNC | SPF_PURGE_BEFORE_SPEAK | SPF_IS_NOT_XML)
else:
self.sapi.Speak(text, SPF_ASYNC | SPF_IS_NOT_XML)
Method sapi_silence(self)
¶
Cancels SAPI speech and flushes the speech buffer.
Source code in speechlight/windows.py
def sapi_silence(self) -> None:
"""Cancels SAPI speech and flushes the speech buffer."""
if self.sapi is not None:
self.sapi.Speak("", SPF_ASYNC | SPF_PURGE_BEFORE_SPEAK | SPF_IS_NOT_XML)
Method say(self, text, *, interrupt=False)
¶
Speaks text.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
text |
str |
The text to be spoken. |
required |
interrupt |
bool |
True if the speech should be silenced before speaking. |
False |
Source code in speechlight/windows.py
def say(self, text: str, *, interrupt: bool = False) -> None: # NOQA: D102
if self.nvda_running():
self.nvda_say(text, interrupt=interrupt)
elif self.sa_running():
self.sa_say(text, interrupt=interrupt)
elif self.jfw_running():
self.jfw_say(text, interrupt=interrupt)
else:
self.sapi_say(text, interrupt=interrupt)
Method silence(self)
¶
Cancels speech and flushes the speech buffer.
Source code in speechlight/windows.py
def silence(self) -> None: # NOQA: D102
if self.nvda_running():
self.nvda_silence()
elif self.sa_running():
self.sa_silence()
elif self.jfw_running():
self.jfw_silence()
else:
self.sapi_silence()
Method speaking(self)
¶
Determines if text is currently being spoken.
Returns:
Type | Description |
---|---|
bool |
True if text is currently being spoken, False otherwise. |
Source code in speechlight/windows.py
def speaking(self) -> bool: # NOQA: D102
if self.nvda_running() or self.sa_running() or self.jfw_running():
# None of the screen reader APIs support retrieving speaking status.
return False
if self.sapi is not None:
return bool(self.sapi.Status.RunningState != 1)
return False
Function dispatch(*args, **kwargs)
¶
Calls win32com.client.Dispatch with the supplied arguments.
If the call fails, then an attempt is made to clear the cache and try again.
Note
https://stackoverflow.com/questions/33267002/why-am-i-suddenly-getting-a-no-attribute-clsidtopackagemap-error-with-win32com
Parameters:
Name | Type | Description | Default |
---|---|---|---|
*args |
Any |
Positional arguments to be passed to win32com.client.Dispatch. |
() |
**kwargs |
Any |
Key-word only arguments to be passed to win32com.client.Dispatch. |
{} |
Returns:
Type | Description |
---|---|
Any |
The resulting COM reference. |
Source code in speechlight/windows.py
def dispatch(*args: Any, **kwargs: Any) -> Any: # pragma: no cover
"""
Calls win32com.client.Dispatch with the supplied arguments.
If the call fails, then an attempt is made to clear the cache and try again.
Note:
https://stackoverflow.com/questions/33267002/why-am-i-suddenly-getting-a-no-attribute-clsidtopackagemap-error-with-win32com
Args:
*args: Positional arguments to be passed to win32com.client.Dispatch.
**kwargs: Key-word only arguments to be passed to win32com.client.Dispatch.
Returns:
The resulting COM reference.
"""
app = None
if sys.platform == "win32":
try:
from win32com import client # NOQA: PLC0415
app = client.Dispatch(*args, **kwargs)
except AttributeError:
# Remove cache and try again.
from win32com.client import gencache # NOQA: PLC0415
if not hasattr(gencache, "GetGeneratePath"):
return None
cache_location = gencache.GetGeneratePath()
del gencache
modules = [m.__name__ for m in sys.modules.values()]
for module in modules:
if re.match(r"win32com\.(?:gen_py|client)\..+", module):
del sys.modules[module]
if "gen_py" in cache_location:
shutil.rmtree(cache_location, ignore_errors=True)
from win32com import client # NOQA: PLC0415
app = client.Dispatch(*args, **kwargs)
except ComError:
return None
del client
return app