diff --git a/src/nvda_voicemeeter/builder.py b/src/nvda_voicemeeter/builder.py index a6389b7..ad8544e 100644 --- a/src/nvda_voicemeeter/builder.py +++ b/src/nvda_voicemeeter/builder.py @@ -17,6 +17,8 @@ class Builder: self.kind = self.vm.kind def run(self) -> list: + menu = self.make_menu() + layout0 = [] if self.kind.name == "basic": steps = (self.make_tab0_row0,) @@ -40,7 +42,18 @@ class Builder: tab3 = psg.Tab("Virtual Strips", layout2, key="virtual strip") tab_group = psg.TabGroup([[tab1, tab2, tab3]], change_submits=True, key="tabs") - return [[tab_group]] + return [[menu], [tab_group]] + + def make_menu(self) -> psg.Menu: + menu_def = [ + [ + "&Voicemeeter", + [ + "Restart Audio Engine::MENU", + ], + ], + ] + return [[psg.Menu(menu_def, key="menus")]] def make_tab0_row0(self) -> psg.Frame: """row0 represents hardware outs""" diff --git a/src/nvda_voicemeeter/parser.py b/src/nvda_voicemeeter/parser.py index 422d622..b1ea9d9 100644 --- a/src/nvda_voicemeeter/parser.py +++ b/src/nvda_voicemeeter/parser.py @@ -1,10 +1,15 @@ -from pyparsing import Group, OneOrMore, Optional, Suppress, Word, alphanums +from pyparsing import Group, OneOrMore, Optional, Suppress, Word, alphanums, restOfLine class Parser: def __init__(self): self.widget = Group(OneOrMore(Word(alphanums))) - self.token = Suppress("||") + self.widget_token = Suppress("||") self.identifier = Group(OneOrMore(Word(alphanums))) self.event = Group(OneOrMore(Word(alphanums))) - self.match = self.widget + self.token + self.identifier + Optional(self.token) + Optional(self.event) + self.menu_token = Suppress("::") + self.match = ( + self.widget + self.widget_token + self.identifier + Optional(self.widget_token) + Optional(self.event) + | self.identifier + self.menu_token + self.event + | restOfLine + ) diff --git a/src/nvda_voicemeeter/window.py b/src/nvda_voicemeeter/window.py index 65668b5..a3ea78d 100644 --- a/src/nvda_voicemeeter/window.py +++ b/src/nvda_voicemeeter/window.py @@ -29,11 +29,12 @@ class NVDAVMWindow(psg.Window): self.parser = Parser() self.builder = Builder(self) layout = self.builder.run() - super().__init__(title, layout, finalize=True) + super().__init__(title, layout, return_keyboard_events=True, finalize=True) [self[f"HARDWARE OUT||A{i + 1}"].Widget.config(takefocus=1) for i in range(self.kind.phys_out)] if self.kind.name != "basic": [self[f"PATCH COMPOSITE||PC{i + 1}"].Widget.config(takefocus=1) for i in range(self.kind.phys_out)] self.register_events() + self.current_focus = None def __enter__(self): return self @@ -94,8 +95,29 @@ class NVDAVMWindow(psg.Window): elif event == "tabs": self.nvda.speak(f"tab {values['tabs']}") continue + elif event in ("Escape:27", "\r"): + self.logger.debug("escape key pressed event") + self.TKroot.after(100, self.refresh) + if self.current_focus: + self.current_focus.set_focus() + self.logger.debug(f"setting focus to {self.current_focus.Key}") + continue match parsed_cmd := self.parser.match.parseString(event): + # Utility + case [" "]: + self.current_focus = self.find_element_with_focus() + + # Menus + case [["Restart", "Audio", "Engine"], ["MENU"]]: + self.perform_long_operation(self.vm.command.restart, "ENGINE RESTART||END") + case [["ENGINE", "RESTART"], ["END"]]: + self.TKroot.after( + 200, + self.nvda.speak, + f"Audio Engine restarted", + ) + # Tabs case [["tabs"], ["FOCUS", "IN"]]: self.nvda.speak(f"tab {values['tabs']}") @@ -181,7 +203,7 @@ class NVDAVMWindow(psg.Window): self.nvda.speak(f"STRIP {index} {output} {label if label else ''} {'on' if val else 'off'}") case _: self.logger.error(f"Unknown event {event}") - self.logger.debug(parsed_cmd) + self.logger.debug(f"parsed::{parsed_cmd}") def request_window_object(title, vm):