def initialize(): global SETTINGS, SYSTEM_INFORMATION global _BASE_PATH, _USER_DIR, _SETTINGS_PATH if SETTINGS is not None: return # calculate prerequisites SYSTEM_INFORMATION = _get_platform_information() _BASE_PATH = str(Path(__file__).resolve().parent.parent) if os.getenv("CASTER_USER_DIR") is not None: _USER_DIR = os.getenv("CASTER_USER_DIR") else: _USER_DIR = user_data_dir(appname="caster", appauthor=False) _SETTINGS_PATH = str(Path(_USER_DIR).joinpath("settings/settings.toml")) # Kick everything off. SETTINGS = _init(_SETTINGS_PATH) from castervoice.lib.migration import UserDirUpdater migrator = UserDirUpdater(_USER_DIR) migrator.create_user_dir_directories() migrator.update_user_dir_packages_to_v1_7_0() migrator.update_bringme_toml_to_v1_7_0() _debugger_path = SETTINGS["paths"]["REMOTE_DEBUGGER_PATH"] # pylint: disable=invalid-sequence-index if _debugger_path not in sys.path and os.path.isdir(_debugger_path): sys.path.append(_debugger_path) # set up printer -- it doesn't matter where you do this; messages will start printing to the console after this dh = printer.get_delegating_handler() dh.register_handler(printer.SimplePrintMessageHandler()) # begin using printer printer.out("Caster User Directory: {}".format(_USER_DIR))
def _validate_engine_path(): ''' Validates path 'Engine Path' in settings.toml ''' if not sys.platform.startswith('win'): return '' try: import natlink # pylint: disable=import-error except ImportError: return '' if os.path.isfile(_SETTINGS_PATH): with io.open(_SETTINGS_PATH, "rt", encoding="utf-8") as toml_file: data = tomlkit.loads(toml_file.read()).value engine_path = data["paths"]["ENGINE_PATH"] if os.path.isfile(engine_path): return engine_path else: engine_path = _find_natspeak() data["paths"]["ENGINE_PATH"] = engine_path try: formatted_data = str(tomlkit.dumps(data)) with io.open(_SETTINGS_PATH, "w", encoding="utf-8") as toml_file: toml_file.write(formatted_data) printer.out( "Setting engine path to {}".format(engine_path)) except Exception as e: printer.out("Error saving settings file {} {} ".format( e, _SETTINGS_PATH)) return engine_path else: return _find_natspeak()
def initialize(): global SETTINGS, SYSTEM_INFORMATION global _BASE_PATH, _USER_DIR, _SETTINGS_PATH if SETTINGS is not None: return # calculate prerequisites SYSTEM_INFORMATION = _get_platform_information() _BASE_PATH = os.path.realpath(__file__).rsplit(os.path.sep + "lib", 1)[0].replace("\\", "/") _USER_DIR = _validate_user_dir().replace("\\", "/") _SETTINGS_PATH = os.path.normpath( os.path.join(_USER_DIR, "data/settings.toml")) for directory in ["data", "rules", "transformers", "hooks", "sikuli"]: d = _USER_DIR + "/" + directory if not os.path.exists(d): os.makedirs(d) # Kick everything off. SETTINGS = _init(_SETTINGS_PATH) _debugger_path = SETTINGS["paths"]["REMOTE_DEBUGGER_PATH"] if _debugger_path not in sys.path and os.path.isdir(_debugger_path): sys.path.append(_debugger_path) printer.out("Caster User Directory: " + _USER_DIR)
def register_rule(self, rule_class, details): """ Takes a newly loaded copy of a rule (MappingRule or MergeRule), validates it, stores it for later instantiation, and adds it to the file tracking list. :param rule_class: :param details: :return: """ class_name = rule_class.__name__ # do not load or watch invalid rules invalidation = self._get_invalidation(rule_class, details) if invalidation is not None: print("invalidated by something or other") printer.out(invalidation) return _set_rdescripts(rule_class.mapping, class_name) ''' rule should be safe for loading at this point: register it but do not load here -- this method only registers ''' managed_rule = ManagedRule(rule_class, details) self._managed_rules[class_name] = managed_rule # set up de/activation command self._activator.register_rule(managed_rule) # watch this file for future changes if not details.watch_exclusion: self._reload_observable.register_watched_file(details.get_filepath())
def clear_log(): # Function to clear status window. # Natlink status window not used an out-of-process mode. # TODO: window_exists utilized when engine launched through Dragonfly CLI via bat in future try: if WIN32: clearcmd = "cls" # Windows OS else: clearcmd = "clear" # Linux if get_current_engine().name == 'natlink': import natlinkstatus # pylint: disable=import-error status = natlinkstatus.NatlinkStatus() if status.NatlinkIsEnabled() == 1: import win32gui # pylint: disable=import-error handle = get_window_by_title( "Messages from Python Macros") or get_window_by_title( "Messages from Natlink") rt_handle = win32gui.FindWindowEx(handle, None, "RICHEDIT", None) win32gui.SetWindowText(rt_handle, "") else: if window_exists(windowname="Caster: Status Window"): os.system(clearcmd) else: if window_exists(windowname="Caster: Status Window"): os.system(clearcmd) else: printer.out("clear_log: Not implemented with GUI") except Exception as e: printer.out(e)
def _bring_add(self, launch_type, key): # Add current program or highlighted text to bring me if launch_type == "program": path = utilities.get_active_window_path() if not path: # dragonfly.get_current_engine().speak("program not detected") printer.out("Program path for bring me not found ") elif launch_type == 'file': files = utilities.get_selected_files(folders=False) path = files[0] if files else None # or allow adding multiple files elif launch_type == 'folder': files = utilities.get_selected_files(folders=True) path = files[ 0] if files else None # or allow adding multiple folders else: Key("a-d/5").execute() fail, path = context.read_selected_without_altering_clipboard() if fail == 2: # FIXME: A better solution would be to specify a number of retries and the time interval. time.sleep(0.1) _, path = context.read_selected_without_altering_clipboard() if not path: printer.out("Selection for bring me not found ") Key("escape").execute() if not path: # logger.warn('Cannot add %s as %s to bringme: cannot get path', launch, key) return config_copy = self._config.get_copy() config_copy[launch_type][str(key)] = path self._refresh(config_copy)
def focus_mousegrid(gridtitle): ''' Loops over active windows for MouseGrid window titles. Issue #171 When MouseGrid window titles found focuses MouseGrid overly. ''' if WIN32: # May not be needed for Linux/Mac OS - testing required try: for i in range(9): matches = Window.get_matching_windows(title=gridtitle, executable="python") if not matches: Pause("50").execute() else: break if matches: for handle in matches: handle.set_foreground() break else: printer.out( "`Title: `{}` no matching windows found".format(gridtitle)) except Exception as e: printer.out("Error focusing MouseGrid: {}".format(e)) else: pass
def _handle_companion_rules(self, enabled_diff): newly_enabled = list() newly_disabled = set() diff = [(rcn, True) for rcn in enabled_diff.newly_enabled] + \ [(rcn, False) for rcn in enabled_diff.newly_disabled] for difference in diff: rcn = difference[0] enabled = difference[1] for companion_rcn in self._companion_config.get_companions(rcn): if companion_rcn in self._managed_rules: mr = self._managed_rules[companion_rcn] is_ccr = mr.get_details().declared_ccrtype is not None if is_ccr: raise InvalidCompanionConfigurationError(companion_rcn) self._change_rule_enabled(companion_rcn, enabled, False) if enabled: newly_enabled.append(companion_rcn) else: newly_disabled.add(companion_rcn) else: invalid_msg = "Invalid companion rule (not loaded): {}" printer.out(invalid_msg.format(companion_rcn)) return RulesEnabledDiff(enabled_diff.newly_enabled + newly_enabled, enabled_diff.newly_disabled | newly_disabled)
def reboot(): # TODO: Save engine arguments elsewhere and retrieves for reboot. Allows for user-defined arguments. popen_parameters = [] engine = get_current_engine() if engine.name == 'kaldi': engine.disconnect() subprocess.Popen([sys.executable, '-m', 'dragonfly', 'load', '_*.py', '--engine', 'kaldi', '--no-recobs-messages']) if engine.name == 'sapi5inproc': engine.disconnect() subprocess.Popen([sys.executable, '-m', 'dragonfly', 'load', '--engine', 'sapi5inproc', '_*.py', '--no-recobs-messages']) if engine.name in ["sapi5shared", "sapi5"]: popen_parameters.append(settings.SETTINGS["paths"]["REBOOT_PATH_WSR"]) popen_parameters.append(settings.SETTINGS["paths"]["WSR_PATH"]) printer.out(popen_parameters) subprocess.Popen(popen_parameters) if engine.name == 'natlink': import natlinkstatus # pylint: disable=import-error status = natlinkstatus.NatlinkStatus() if status.NatlinkIsEnabled() == 1: # Natlink in-process popen_parameters.append(settings.SETTINGS["paths"]["REBOOT_PATH"]) popen_parameters.append(settings.SETTINGS["paths"]["ENGINE_PATH"]) username = status.getUserName() popen_parameters.append(username) printer.out(popen_parameters) subprocess.Popen(popen_parameters) else: # Natlink out-of-process engine.disconnect() subprocess.Popen([sys.executable, '-m', 'dragonfly', 'load', '--engine', 'natlink', '_*.py', '--no-recobs-messages'])
def test_broken_handler(self): """ Tests that messages passed to a broken handler will still be printed to the console. """ # set up arg capture on default handler args_capturer = _PrinterArgsCapturer() self._delegating_handler._error_handler._print = args_capturer._print # create and register a broken handler class BrokenHandler(BaseMessageHandler): def __init__(self): super(BrokenHandler, self).__init__() def handle_message(self, items): raise Exception("something unexpected happened") self._delegating_handler.register_handler(BrokenHandler()) # queue up messages printer.out("asdf") # wait for the timer on the other thread time.sleep(1.5) # assert that the default handler still printed the message despite the error self.assertEqual("asdf", args_capturer.captured_args[0])
def idem_import_module(self, module_name, fn_name): """ Returns the content requested from the specified module. """ module = None if module_name in _MODULES: module = _MODULES[module_name] module = self._reimport_module(module) else: module = self._import_module(module_name) if module is None: return None # get them and add them to nexus fn = None try: fn = getattr(module, fn_name) except AttributeError: msg = "No method named '{}' was found on '{}'. Did you forget to implement it?".format(fn_name, module_name) if ContentLoader._detect_pythonpath_module_name_in_use(module): msg = "{} module name is already in use: {}".format(module_name, module.__file__) printer.out(msg) return None except: msg = "Error loading module '{}'." printer.out(msg.format(module_name)) return None return fn()
def get_clipboard_files(folders=False): ''' Enumerate clipboard content and return files either directly copied or highlighted path copied ''' if sys.platform.startswith('win'): import win32clipboard # pylint: disable=import-error files = None win32clipboard.OpenClipboard() f = get_clipboard_formats() if win32clipboard.CF_HDROP in f: files = win32clipboard.GetClipboardData(win32clipboard.CF_HDROP) elif win32clipboard.CF_UNICODETEXT in f: files = [ win32clipboard.GetClipboardData(win32clipboard.CF_UNICODETEXT) ] elif win32clipboard.CF_TEXT in f: files = [win32clipboard.GetClipboardData(win32clipboard.CF_TEXT)] elif win32clipboard.CF_OEMTEXT in f: files = [ win32clipboard.GetClipboardData(win32clipboard.CF_OEMTEXT) ] if folders: files = [f for f in files if os.path.isdir(f)] if files else None else: files = [f for f in files if os.path.isfile(f)] if files else None win32clipboard.CloseClipboard() return files else: printer.out("get_clipboard_files: Not implemented for OS")
def receive(self, file_path_changed): """ This being called indicates that the file at file_path_changed has been updated and that it should be reloaded and potentially replace the old copy. DO NOT CALL THIS MANUALLY. Should only be called by the reload observable. :param file_path_changed: str :return: """ try: module_name = GrammarManager._get_module_name_from_file_path( file_path_changed) rule_class, details = self._content_loader.idem_import_module( module_name, ContentType.GET_RULE) # re-register: self.register_rule(rule_class, details) class_name = rule_class.__name__ if class_name in self._config.get_enabled_rcns_ordered(): self._delegate_enable_rule(class_name, True) except Exception as error: printer.out( 'Grammar Manager: {} - See error message above'.format(error)) self._hooks_runner.execute(OnErrorEvent())
def initialize(): global SETTINGS, SYSTEM_INFORMATION global _BASE_PATH, _USER_DIR, _SETTINGS_PATH if SETTINGS is not None: return # calculate prerequisites SYSTEM_INFORMATION = _get_platform_information() _BASE_PATH = str(Path(__file__).resolve().parent.parent) if os.getenv("CASTER_USER_DIR") is not None: _USER_DIR = os.getenv("CASTER_USER_DIR") else: _USER_DIR = user_data_dir(appname="caster", appauthor=False) _SETTINGS_PATH = str(Path(_USER_DIR).joinpath("settings/settings.toml")) for directory in [ "data", "rules", "transformers", "hooks", "sikuli", "settings" ]: d = Path(_USER_DIR).joinpath(directory) d.mkdir(parents=True, exist_ok=True) # Kick everything off. SETTINGS = _init(_SETTINGS_PATH) _debugger_path = SETTINGS["paths"]["REMOTE_DEBUGGER_PATH"] # pylint: disable=invalid-sequence-index if _debugger_path not in sys.path and os.path.isdir(_debugger_path): sys.path.append(_debugger_path) printer.out("Caster User Directory: {}".format(_USER_DIR))
def __init__(self, parser=TRParser): try: parser_instance = parser() self._definitions = parser_instance.create_definitions() if len(self._definitions) > 0: printer.out("Text replacing transformer from file 'words.txt' activated ...") except Exception: printer.out("Unable to parse words.txt")
def _print_not_found_message(self, file_path): """ Print the 'not found' error only once. """ if file_path not in self._deleted: msg = "{} appears to have been deleted or renamed. Please reboot Caster to re-track." printer.out(msg.format(file_path)) self._deleted.add(file_path)
def simple_log(to_file=False): msg = list_to_string(sys.exc_info()) printer.out(msg) for tb in traceback.format_tb(sys.exc_info()[2]): printer.out(tb) if to_file: with io.open(settings.SETTINGS["paths"]["LOG_PATH"], 'at', encoding="utf-8") as f: f.write(msg + "\n")
def test_instantiation(self): utilities_mocking.mock_toml_files() for module in self._rule_modules(): printer.out("Test instantiating {}".format(str(module))) content = module.get_rule() rule_class = content[0] self._run_rule_modifications_for_testing(rule_class) rule_class()
def reset(self): class_name = self.__class__.__name__ if self._reload_shim is None: printer.out( "Reload shim is not set for {}. Rule cannot hot-reset, will only reset with engine." .format(class_name)) return self._reload_shim.signal_reload(class_name)
def remote_debug(who_called_it=None): if who_called_it is None: who_called_it = "An unidentified process" try: import pydevd # @UnresolvedImport pylint: disable=import-error pydevd.settrace() except Exception: printer.out("ERROR: " + who_called_it + " called utilities.remote_debug() but the debug server wasn't running.")
def restore_window(): ''' Restores last minimized window triggered minimize_window. ''' global lasthandle if lasthandle is None: printer.out("No previous window minimized by voice") else: Window.restore(lasthandle)
def mouse_alternates(cls, mode, monitor=1, rough=True): # Launches the Mouse Grid args = [] if cls.GRID_PROCESS is not None: cls.GRID_PROCESS.poll() # If close by Task Manager # TODO Test MacOS/Linux. Handle error codes when Grid close by Task Manager. if cls.GRID_PROCESS.returncode == 15: cls.GRID_PROCESS = None cls.MODE = None else: # This message should only occur if grid is visible. printer.out( "Mouse Grid navigation already in progress \n Return Code: {}" .format(cls.GRID_PROCESS.returncode)) return if mode == "legion": from castervoice.asynch.mouse.legion import LegionScanner r = monitors[int(monitor) - 1].rectangle bbox = [ int(r.x), int(r.y), int(r.x) + int(r.dx) - 1, int(r.y) + int(r.dy) - 1 ] ls = LegionScanner() ls.scan(bbox, rough) tscan = ls.get_update() args = [ settings.settings(["paths", "PYTHONW"]), settings.settings(["paths", "LEGION_PATH"]), "-t", tscan[0], "-m", str(monitor) ] elif mode == "rainbow": args = [ settings.settings(["paths", "PYTHONW"]), settings.settings(["paths", "RAINBOW_PATH"]), "-g", "r", "-m", str(monitor) ] elif mode == "douglas": args = [ settings.settings(["paths", "PYTHONW"]), settings.settings(["paths", "DOUGLAS_PATH"]), "-g", "d", "-m", str(monitor) ] elif mode == "sudoku": args = [ settings.settings(["paths", "PYTHONW"]), settings.settings(["paths", "SUDOKU_PATH"]), "-g", "s", "-m", str(monitor) ] cls.MODE = mode cls.GRID_PROCESS = subprocess.Popen(args) if args else None
def execute(self, event): for hook in self._hooks: if not self._hooks_config.is_hook_active(hook.get_class_name()): continue if hook.match(event.get_type()): try: hook.run(event) except: err = "Error while running hook {} with {} event." printer.out(err.format(hook, event.get_type()))
def _run_on_disable(self): # Manages run_on_disable hook state try: if self.hook_state: self.hook_state = False self.run_on_disable() else: printer.out("{} is already disabled.".format(self.get_class_name())) except Exception as err: message = "{}: Error with `disable` hook function.\n {}" printer.out(message.format(self.get_class_name(), err))
def _retry_server_proxy(self): printer.out("Attempting Caster-Sikuli connection [...]") try: self._start_server_proxy() if self._timer: self._timer.stop() self._timer = None except socket.error: pass except Exception: traceback.print_exc()
def _start_server_proxy(self): """ This method will fail if the server isn't started yet. """ # this will never fail: self._server_proxy = control.nexus().comm.get_com("sikuli") # this will fail if the server isn't started yet: self._server_proxy.list_functions() # success at this point: printer.out("Caster-Sikuli server started successfully.") SikuliController._ENABLE_GEN_RULE.execute()
def get_clipboard_files(folders=False): ''' Enumerate clipboard content and return files/folders either directly copied or highlighted path copied ''' files = None if WIN32: import win32clipboard # pylint: disable=import-error win32clipboard.OpenClipboard() f = get_clipboard_formats() try: if win32clipboard.CF_HDROP in f: files = win32clipboard.GetClipboardData( win32clipboard.CF_HDROP) elif win32clipboard.CF_UNICODETEXT in f: files = [ win32clipboard.GetClipboardData( win32clipboard.CF_UNICODETEXT) ] elif win32clipboard.CF_TEXT in f: files = [ win32clipboard.GetClipboardData(win32clipboard.CF_TEXT) ] elif win32clipboard.CF_OEMTEXT in f: files = [ win32clipboard.GetClipboardData(win32clipboard.CF_OEMTEXT) ] if folders: files = [f for f in files if os.path.isdir(f)] if files else None else: files = [f for f in files if os.path.isfile(f)] if files else None win32clipboard.CloseClipboard() return files except Exception as e: win32clipboard.CloseClipboard() printer.out(e) if LINUX: f = get_clipboard_formats() if "UTF8_STRING" in f: files = enum_files_from_clipboard("UTF8_STRING") elif "TEXT" in f: files = enum_files_from_clipboard("TEXT") elif "text/plain" in f: files = enum_files_from_clipboard("text/plain") if folders: files = [f for f in files if os.path.isdir(str(f))] if files else None else: files = [f for f in files if os.path.isfile(str(f))] if files else None return files
def _import_module(self, module_name): """ Attempts to import a module by name. :param module_name: string :return: (module) module """ try: return self._module_load_fn(module_name) except Exception as e: printer.out("Could not import '{}'. Module has errors: {}".format(module_name, traceback.format_exc())) return None
def transform_rule(self, rule_instance): r = rule_instance orig_class = TransformersRunner._get_rule_class(r) for transformer in self._transformers: try: r = transformer.get_transformed_rule(r) TransformersRunner._post_transform_validate(orig_class, r) except: err = "Error while running transformer {} with {} rule." printer.out(err.format(transformer, r)) traceback.print_exc() return r
def _reimport_module(self, module): """ Reimports an already imported module. Python 2/3 compatible method. """ try: reload_fn = self._get_reload_fn() return reload_fn(module) except: msg = "An error occurred while importing '{}': {}" printer.out(msg.format(str(module), traceback.format_exc())) return None