From 065c231d98c6ec28f022443db101aa660ad79f5c Mon Sep 17 00:00:00 2001 From: Colton Loftus <70598503+C-Loftus@users.noreply.github.com> Date: Sat, 6 Apr 2024 12:55:28 -0400 Subject: [PATCH] Update CI with Mypy and Precommit (#29) * update state to be class based for mypy * precommit trigger * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * better comment * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * clean up * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * editor config for precommit * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .editorconfig | 17 + .gitignore | 2 +- .vscode/extensions.json | 2 +- .vscode/settings.json | 4 +- .vscode/typings/__builtins__.pyi | 8 +- .../chat-gpt-browser.talon | 17 +- .../cursorless-sightless.talon | 1 - application-integration/ms-edge-tts.talon | 4 +- .../models/en_US-amy-low.onnx.json | 316 +++++++++--------- core/callbacks.py | 39 ++- core/core-agnostic.py | 24 +- core/core-linux.py | 19 +- core/core-mac.py | 3 +- core/core-windows.py | 5 +- core/overrides.py | 2 +- core/screenreader_ipc/ipc_client.py | 33 +- core/screenreader_ipc/ipc_schema.py | 2 +- core/settings.py | 3 +- jaws/jaws.py | 3 +- lib/HTMLbuilder.py | 6 +- lib/sound/sound.py | 3 +- lib/sound/soundEffects.py | 5 +- .../_template_addon_release.json | 54 +-- .../addon/globalPlugins/nvda-addon.py | 15 +- .../copy_to_scratchpad.ps1 | 2 +- nvda/.addOn/sight-free-talon-server/readme.md | 2 - .../site_tools/gettexttool/__init__.py | 58 ++-- nvda/.addOn/sight-free-talon-server/style.css | 58 ++-- nvda/debug-nvda.talon | 10 +- nvda/nvda-parser.py | 5 +- nvda/nvda.py | 56 ++-- nvda/nvda.talon | 146 +++----- nvda/overrides.talon | 1 - orca/orca.py | 3 +- orca/orca.talon | 4 +- pyproject.toml | 18 +- readme.md | 15 +- sight-free-global.talon | 29 +- utils/access-focus.py | 2 +- utils/help.py | 113 ++++--- utils/help.talon | 3 +- utils/log/log.talon | 12 +- utils/log/log_checker.py | 63 ++-- utils/utils.py | 7 +- utils/utils.talon | 9 +- utils/windows-utils.talon | 4 +- voiceover/readme.md | 1 - voiceover/voiceover.py | 6 +- voiceover/voiceover_tts.applescript | 6 +- 49 files changed, 613 insertions(+), 607 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0d6fd5f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# See https://EditorConfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_size = 4 +indent_style = space +max_line_length = 88 +trim_trailing_whitespace = true + +[*.{md,yaml,yml}] +indent_size = 2 + +[Makefile] +indent_style = tab diff --git a/.gitignore b/.gitignore index 6ca5b0b..042e54c 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,4 @@ manifest.ini .ruff_cache/ .mypy_cache/ -__pycache__/ \ No newline at end of file +__pycache__/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 468c28c..8ef0c5d 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -4,4 +4,4 @@ "ms-python.vscode-pylance", "charliermarsh.ruff" ] -} \ No newline at end of file +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 197b3c9..c423e57 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -17,5 +17,5 @@ "source.fixAll": "always" }, "editor.defaultFormatter": "charliermarsh.ruff" - }, -} \ No newline at end of file + } +} diff --git a/.vscode/typings/__builtins__.pyi b/.vscode/typings/__builtins__.pyi index 88febec..48cbde4 100644 --- a/.vscode/typings/__builtins__.pyi +++ b/.vscode/typings/__builtins__.pyi @@ -1,6 +1,2 @@ -def _(msg: str) -> str: - ... - - -def pgettext(context: str, message: str) -> str: - ... +def _(msg: str) -> str: ... +def pgettext(context: str, message: str) -> str: ... diff --git a/application-integration/chat-gpt-browser.talon b/application-integration/chat-gpt-browser.talon index 21aedfb..2ba4996 100644 --- a/application-integration/chat-gpt-browser.talon +++ b/application-integration/chat-gpt-browser.talon @@ -1,12 +1,11 @@ title: /https://chat.openai.com/ - - -Open new chat: key(ctrl-shift-o) -Focus chat input: key(shift-esc) -Copy last code block: key(ctrl-shift-;) -Copy last response: key(ctrl-shift-c) +Open new chat: key(ctrl-shift-o) +Focus chat input: key(shift-esc) +Copy last code block: key(ctrl-shift-;) +Copy last response: key(ctrl-shift-c) -speak [last] ( response | output | chat ): +speak [last] (response | output | chat): key(ctrl-shift-c) sleep(0.3) user.tts(clip.text()) @@ -16,6 +15,6 @@ speak last code [block]: sleep(0.3) user.tts(clip.text()) -Set custom instructions: key(ctrl-shift-i) -Toggle sidebar: key(ctrl-shift-s) -Delete chat: key(ctrl-shift-delete) +Set custom instructions: key(ctrl-shift-i) +Toggle sidebar: key(ctrl-shift-s) +Delete chat: key(ctrl-shift-delete) diff --git a/application-integration/cursorless-sightless.talon b/application-integration/cursorless-sightless.talon index 6580111..0b9b93c 100644 --- a/application-integration/cursorless-sightless.talon +++ b/application-integration/cursorless-sightless.talon @@ -1,5 +1,4 @@ tag: user.cursorless - - speak : txt = user.cursorless_get_text(cursorless_target) diff --git a/application-integration/ms-edge-tts.talon b/application-integration/ms-edge-tts.talon index 88b50ad..42b9bed 100644 --- a/application-integration/ms-edge-tts.talon +++ b/application-integration/ms-edge-tts.talon @@ -2,7 +2,6 @@ os: windows and app.name: Microsoft Edge os: windows and app.exe: msedge.exe - - # This file takes advantage of the functionality for more natural text to speech within Microsoft edge # as of 2023, this functionality is not present on Linux @@ -12,8 +11,7 @@ read aloud: sleep(1) key(ctrl) -toggle immersive reader: - key(f9) +toggle immersive reader: key(f9) toggle immersive reader and read aloud: key(ctrl-shift-u) diff --git a/core/additional_voices/models/en_US-amy-low.onnx.json b/core/additional_voices/models/en_US-amy-low.onnx.json index 43cc67d..c60767d 100644 --- a/core/additional_voices/models/en_US-amy-low.onnx.json +++ b/core/additional_voices/models/en_US-amy-low.onnx.json @@ -1,160 +1,160 @@ { - "audio": { - "sample_rate": 16000, - "quality": "low" - }, - "espeak": { - "voice": "en-us" - }, - "inference": { - "noise_scale": 0.667, - "length_scale": 1, - "noise_w": 0.8 - }, - "phoneme_map": {}, - "phoneme_id_map": { - "_": [0], - "^": [1], - "$": [2], - " ": [3], - "!": [4], - "'": [5], - "(": [6], - ")": [7], - ",": [8], - "-": [9], - ".": [10], - ":": [11], - ";": [12], - "?": [13], - "a": [14], - "b": [15], - "c": [16], - "d": [17], - "e": [18], - "f": [19], - "h": [20], - "i": [21], - "j": [22], - "k": [23], - "l": [24], - "m": [25], - "n": [26], - "o": [27], - "p": [28], - "q": [29], - "r": [30], - "s": [31], - "t": [32], - "u": [33], - "v": [34], - "w": [35], - "x": [36], - "y": [37], - "z": [38], - "æ": [39], - "ç": [40], - "ð": [41], - "ø": [42], - "ħ": [43], - "ŋ": [44], - "œ": [45], - "ǀ": [46], - "ǁ": [47], - "ǂ": [48], - "ǃ": [49], - "ɐ": [50], - "ɑ": [51], - "ɒ": [52], - "ɓ": [53], - "ɔ": [54], - "ɕ": [55], - "ɖ": [56], - "ɗ": [57], - "ɘ": [58], - "ə": [59], - "ɚ": [60], - "ɛ": [61], - "ɜ": [62], - "ɞ": [63], - "ɟ": [64], - "ɠ": [65], - "ɡ": [66], - "ɢ": [67], - "ɣ": [68], - "ɤ": [69], - "ɥ": [70], - "ɦ": [71], - "ɧ": [72], - "ɨ": [73], - "ɪ": [74], - "ɫ": [75], - "ɬ": [76], - "ɭ": [77], - "ɮ": [78], - "ɯ": [79], - "ɰ": [80], - "ɱ": [81], - "ɲ": [82], - "ɳ": [83], - "ɴ": [84], - "ɵ": [85], - "ɶ": [86], - "ɸ": [87], - "ɹ": [88], - "ɺ": [89], - "ɻ": [90], - "ɽ": [91], - "ɾ": [92], - "ʀ": [93], - "ʁ": [94], - "ʂ": [95], - "ʃ": [96], - "ʄ": [97], - "ʈ": [98], - "ʉ": [99], - "ʊ": [100], - "ʋ": [101], - "ʌ": [102], - "ʍ": [103], - "ʎ": [104], - "ʏ": [105], - "ʐ": [106], - "ʑ": [107], - "ʒ": [108], - "ʔ": [109], - "ʕ": [110], - "ʘ": [111], - "ʙ": [112], - "ʛ": [113], - "ʜ": [114], - "ʝ": [115], - "ʟ": [116], - "ʡ": [117], - "ʢ": [118], - "ʲ": [119], - "ˈ": [120], - "ˌ": [121], - "ː": [122], - "ˑ": [123], - "˞": [124], - "β": [125], - "θ": [126], - "χ": [127], - "ᵻ": [128], - "ⱱ": [129] - }, - "num_symbols": 130, - "num_speakers": 1, - "speaker_id_map": {}, - "piper_version": "0.2.0", - "language": { - "code": "en_US", - "family": "en", - "region": "US", - "name_native": "English", - "name_english": "English", - "country_english": "United States" - }, - "dataset": "amy" + "audio": { + "sample_rate": 16000, + "quality": "low" + }, + "espeak": { + "voice": "en-us" + }, + "inference": { + "noise_scale": 0.667, + "length_scale": 1, + "noise_w": 0.8 + }, + "phoneme_map": {}, + "phoneme_id_map": { + "_": [0], + "^": [1], + "$": [2], + " ": [3], + "!": [4], + "'": [5], + "(": [6], + ")": [7], + ",": [8], + "-": [9], + ".": [10], + ":": [11], + ";": [12], + "?": [13], + "a": [14], + "b": [15], + "c": [16], + "d": [17], + "e": [18], + "f": [19], + "h": [20], + "i": [21], + "j": [22], + "k": [23], + "l": [24], + "m": [25], + "n": [26], + "o": [27], + "p": [28], + "q": [29], + "r": [30], + "s": [31], + "t": [32], + "u": [33], + "v": [34], + "w": [35], + "x": [36], + "y": [37], + "z": [38], + "æ": [39], + "ç": [40], + "ð": [41], + "ø": [42], + "ħ": [43], + "ŋ": [44], + "œ": [45], + "ǀ": [46], + "ǁ": [47], + "ǂ": [48], + "ǃ": [49], + "ɐ": [50], + "ɑ": [51], + "ɒ": [52], + "ɓ": [53], + "ɔ": [54], + "ɕ": [55], + "ɖ": [56], + "ɗ": [57], + "ɘ": [58], + "ə": [59], + "ɚ": [60], + "ɛ": [61], + "ɜ": [62], + "ɞ": [63], + "ɟ": [64], + "ɠ": [65], + "ɡ": [66], + "ɢ": [67], + "ɣ": [68], + "ɤ": [69], + "ɥ": [70], + "ɦ": [71], + "ɧ": [72], + "ɨ": [73], + "ɪ": [74], + "ɫ": [75], + "ɬ": [76], + "ɭ": [77], + "ɮ": [78], + "ɯ": [79], + "ɰ": [80], + "ɱ": [81], + "ɲ": [82], + "ɳ": [83], + "ɴ": [84], + "ɵ": [85], + "ɶ": [86], + "ɸ": [87], + "ɹ": [88], + "ɺ": [89], + "ɻ": [90], + "ɽ": [91], + "ɾ": [92], + "ʀ": [93], + "ʁ": [94], + "ʂ": [95], + "ʃ": [96], + "ʄ": [97], + "ʈ": [98], + "ʉ": [99], + "ʊ": [100], + "ʋ": [101], + "ʌ": [102], + "ʍ": [103], + "ʎ": [104], + "ʏ": [105], + "ʐ": [106], + "ʑ": [107], + "ʒ": [108], + "ʔ": [109], + "ʕ": [110], + "ʘ": [111], + "ʙ": [112], + "ʛ": [113], + "ʜ": [114], + "ʝ": [115], + "ʟ": [116], + "ʡ": [117], + "ʢ": [118], + "ʲ": [119], + "ˈ": [120], + "ˌ": [121], + "ː": [122], + "ˑ": [123], + "˞": [124], + "β": [125], + "θ": [126], + "χ": [127], + "ᵻ": [128], + "ⱱ": [129] + }, + "num_symbols": 130, + "num_speakers": 1, + "speaker_id_map": {}, + "piper_version": "0.2.0", + "language": { + "code": "en_US", + "family": "en", + "region": "US", + "name_native": "English", + "name_english": "English", + "country_english": "United States" + }, + "dataset": "amy" } diff --git a/core/callbacks.py b/core/callbacks.py index 44a120b..5887014 100644 --- a/core/callbacks.py +++ b/core/callbacks.py @@ -1,4 +1,15 @@ -from talon import scope, registry, ui, actions, settings, speech_system, app +from typing import ClassVar, Optional + +from talon import actions, app, registry, scope, settings, speech_system, ui + + +class CallbackState: + last_mode: ClassVar[Optional[str]] = None + + # We have to keep track of the last title so we don't repeat it + # since sometimes Talon triggers a "title switch" when + # the title actually hasn't changed, i.e. when a text file is saved + last_title: ClassVar[Optional[str]] = None def on_phrase(parsed_phrase): @@ -11,6 +22,7 @@ def on_phrase(parsed_phrase): # Easier to just catch the exception try: actions.user.cancel_current_speaker() + # Logging is handled individually by engine. Ignore here except Exception: pass @@ -26,12 +38,6 @@ def on_app_switch(app): actions.user.echo_context() -# We have to keep track of the last title so we don't repeat it -# since sometimes Talon triggers a "title switch" when -# the title actually hasn't changed, i.e. when a text file is saved -last_title = None - - def on_title_switch(win): if not actions.user.echo_context_enabled(): return @@ -42,19 +48,16 @@ def on_title_switch(win): # trime the title to 20 characters so super long addresses don't get read active_window_title = active_window_title[:20] - global last_title - if last_title == active_window_title: + if CallbackState.last_title == active_window_title: return - last_title = active_window_title + CallbackState.last_title = active_window_title actions.user.tts(f"{active_window_title}") -last_mode = None - - def on_update_contexts(): - global last_mode + last_mode = CallbackState.last_mode + modes = scope.get("mode") or [] MIXED = "command" in modes and "dictation" in modes @@ -99,13 +102,13 @@ def on_update_contexts(): actions.user.tts("Talon dictation mode") if SLEEP: - last_mode = "sleep" + CallbackState.last_mode = "sleep" elif MIXED: - last_mode = "mixed" + CallbackState.last_mode = "mixed" elif COMMAND: - last_mode = "command" + CallbackState.last_mode = "command" elif DICTATION: - last_mode = "dictation" + CallbackState.last_mode = "dictation" def on_ready(): diff --git a/core/core-agnostic.py b/core/core-agnostic.py index 96643f9..01f4eea 100644 --- a/core/core-agnostic.py +++ b/core/core-agnostic.py @@ -3,15 +3,21 @@ and are agnostic to the tts voice being used or the operating system """ -from typing import Optional, Callable -from talon import Module, actions, Context, settings, app +from typing import Callable, ClassVar, Optional + +from talon import Context, Module, actions, app, settings mod = Module() ctx = Context() +class AgnosticState: + # Store the handle for the subprocess that will cancel the current speaker + speaker_cancel_callback: ClassVar[Optional[Callable]] = None + + # We want to get the settings from the talon file but then update -# them locally here so we can change them globally via expose talon actions +# them locally here so we can change them globally via exposed talon actions def initialize_settings(): ctx.settings["user.echo_dictation"] = settings.get("user.echo_dictation", True) ctx.settings["user.echo_context"] = settings.get("user.echo_context", False) @@ -21,8 +27,6 @@ def initialize_settings(): # initialize the settings only after the user settings have been loaded app.register("ready", initialize_settings) -speaker_cancel_callback: Optional[Callable] = None - @mod.action_class class Actions: @@ -31,21 +35,19 @@ def set_cancel_callback(callback: Callable): Sets the callback to call when the current speaker is cancelled. Only necessary to set if the tts is coming from a subprocess where we need to store a handle """ - global speaker_cancel_callback - speaker_cancel_callback = callback + AgnosticState.speaker_cancel_callback = callback def cancel_current_speaker(): """Cancels the current speaker""" - global speaker_cancel_callback - if not speaker_cancel_callback: + if not AgnosticState.speaker_cancel_callback: return try: - speaker_cancel_callback() + AgnosticState.speaker_cancel_callback() except Exception as e: print(e) finally: - speaker_cancel_callback = None + AgnosticState.speaker_cancel_callback = None def braille(text: str): """Output braille with the screenreader""" diff --git a/core/core-linux.py b/core/core-linux.py index ff92592..49b206c 100644 --- a/core/core-linux.py +++ b/core/core-linux.py @@ -1,7 +1,8 @@ -from talon import Context, actions, settings import os import subprocess -from typing import Literal +from typing import ClassVar, Literal + +from talon import Context, actions, settings ctxLinux = Context() ctxLinux.matches = r""" @@ -9,7 +10,8 @@ """ -speaker: Literal["espeak", "piper"] = "espeak" +class LinuxState: + speaker: ClassVar[Literal["espeak", "piper"]] = "espeak" @ctxLinux.action_class("user") @@ -20,12 +22,11 @@ def toggle_reader(): def switch_voice(): """Switches the tts voice""" - global speaker - if speaker == "espeak": - speaker = "piper" + if LinuxState.speaker == "espeak": + LinuxState.speaker = "piper" actions.user.tts("Switched to piper") else: - speaker = "espeak" + LinuxState.speaker = "espeak" actions.user.tts("Switched to espeak") def tts(text: str, interrupt: bool = True): @@ -33,13 +34,13 @@ def tts(text: str, interrupt: bool = True): if interrupt: actions.user.cancel_current_speaker() - match speaker: + match LinuxState.speaker: case "espeak": actions.user.espeak(text) case "piper": actions.user.piper(text) case _: - raise ValueError(f"Unknown speaker {speaker}") + raise ValueError(f"Unknown speaker {LinuxState.speaker}") def espeak(text: str): """Text to speech with a robotic/narrator voice""" diff --git a/core/core-mac.py b/core/core-mac.py index 70f5b13..d8ae531 100644 --- a/core/core-mac.py +++ b/core/core-mac.py @@ -1,6 +1,7 @@ -from talon import Context, actions, settings import subprocess +from talon import Context, actions, settings + ctxMac = Context() ctxMac.matches = r""" os: mac diff --git a/core/core-windows.py b/core/core-windows.py index a7db87b..692659e 100644 --- a/core/core-windows.py +++ b/core/core-windows.py @@ -1,6 +1,7 @@ -from talon import Context, settings, actions -from collections import OrderedDict import os +from collections import OrderedDict + +from talon import Context, actions, settings if os.name == "nt": import pywintypes diff --git a/core/overrides.py b/core/overrides.py index 9025e4b..04e832f 100644 --- a/core/overrides.py +++ b/core/overrides.py @@ -1,4 +1,4 @@ -from talon import actions, Context, settings, Module, app +from talon import Context, Module, actions, app, settings ctx = Context() diff --git a/core/screenreader_ipc/ipc_client.py b/core/screenreader_ipc/ipc_client.py index c41ecdb..c975830 100644 --- a/core/screenreader_ipc/ipc_client.py +++ b/core/screenreader_ipc/ipc_client.py @@ -1,20 +1,21 @@ -from talon import Module, Context, actions, settings, cron -import os import ipaddress import json +import os import socket import threading -from typing import Tuple, assert_never, Optional +from typing import Optional, Tuple, assert_never + +from talon import Context, Module, actions, cron, settings + from .ipc_schema import ( IPC_COMMAND, - IPCServerResponse, IPCClientResponse, + IPCServerResponse, + ResponseBundle, ServerSpec, ServerStatusResult, - ResponseBundle, ) - mod = Module() lock = threading.Lock() @@ -33,13 +34,11 @@ def handle_ipc_result( match client_response, server_response: case ( - ( - IPCClientResponse.NO_RESPONSE - | IPCClientResponse.TIMED_OUT - | IPCClientResponse.GENERAL_ERROR, - _, - ) as error - ): + IPCClientResponse.NO_RESPONSE + | IPCClientResponse.TIMED_OUT + | IPCClientResponse.GENERAL_ERROR, + _, + ) as error: raise RuntimeError( f"Clientside {error=} communicating with screenreader extension" ) @@ -64,11 +63,9 @@ def handle_ipc_result( "Invalid JSON payload sent from client to screenreader" ) case ( - ( - ServerStatusResult.INTERNAL_SERVER_ERROR - | ServerStatusResult.RUNTIME_ERROR - ) as error - ): + ServerStatusResult.INTERNAL_SERVER_ERROR + | ServerStatusResult.RUNTIME_ERROR + ) as error: raise RuntimeError(f"{error} processing command '{cmd}'") case _: assert_never((cmd, value, status)) diff --git a/core/screenreader_ipc/ipc_schema.py b/core/screenreader_ipc/ipc_schema.py index acad2f2..8e80df1 100644 --- a/core/screenreader_ipc/ipc_schema.py +++ b/core/screenreader_ipc/ipc_schema.py @@ -1,5 +1,5 @@ import enum -from typing import List, Literal, Any, TypedDict, Optional +from typing import Any, List, Literal, Optional, TypedDict # Exhaustive list of commands that can be sent to the NVDA addon server diff --git a/core/settings.py b/core/settings.py index 833da6e..b2e55df 100644 --- a/core/settings.py +++ b/core/settings.py @@ -1,6 +1,7 @@ -from talon import Module from typing import Literal +from talon import Module + mod = Module() mod.setting( diff --git a/jaws/jaws.py b/jaws/jaws.py index fe735e8..7667230 100644 --- a/jaws/jaws.py +++ b/jaws/jaws.py @@ -1,9 +1,10 @@ # from __future__ import absolute_import # import pywintypes -from talon import actions, Module, cron, Context import os +from talon import Context, Module, actions, cron + # class Jaws(): # """Supports the Jaws for Windows screen reader.""" diff --git a/lib/HTMLbuilder.py b/lib/HTMLbuilder.py index 773bf99..8edd877 100644 --- a/lib/HTMLbuilder.py +++ b/lib/HTMLbuilder.py @@ -1,15 +1,15 @@ # Talon's imgui gui library is not accessible to screen readers. # By using HTML we can create temporary web pages that are accessible to screen readers. -import tempfile -import webbrowser import enum import os import platform +import tempfile +import webbrowser STYLE = """