def update_bringme_toml_to_v1_7_0(self): user_dir_path = settings.settings(["paths", "USER_DIR"]) bringme_config_path = settings.settings(["paths", "SM_BRINGME_PATH"]) bringme_config = SelfModStateSavingConfig(bringme_config_path) bringme_config.load() directory_dict = bringme_config.get("folder") if directory_dict is not None and ContentRoot.USER_DIR not in directory_dict[ "caster rules"]: # make a backup copy copy_destination = str( Path(bringme_config_path).with_name("sm_bringme.toml.bak")) shutil.copy2(str(Path(bringme_config_path)), copy_destination) # update the user content paths in the bringme config directory_dict["caster rules"] = \ str(Path(user_dir_path).joinpath(ContentRoot.USER_DIR).joinpath("rules")) directory_dict["caster hooks"] = \ str(Path(user_dir_path).joinpath(ContentRoot.USER_DIR).joinpath("hooks")) directory_dict["caster transformers"] = \ str(Path(user_dir_path).joinpath(ContentRoot.USER_DIR).joinpath("transformers")) bringme_config.put("folder", directory_dict) bringme_config.save()
def test_bringme_toml_upgrade_correctness(self): """ Tests that bringme toml config file is correct after 1.7.0 update. """ # set up 1.0.0 bringme toml file bringme_toml_path = str( Path(TEST_USER_DIR).joinpath("settings/sm_bringme.toml")) self._setup_legacy_bringme_toml(bringme_toml_path) # run migration self.migrator.update_bringme_toml_to_v1_7_0() # assertions self._do_bringme_toml_assertions(bringme_toml_path)
def setup_dll(self): import sys import struct try: if struct.calcsize("P") * 8 == 32: self.tirg_dll = cdll.LoadLibrary( str( Path(settings.SETTINGS["paths"]["DLL_PATH"]).joinpath( "tirg-32.dll")).encode( sys.getfilesystemencoding())) else: self.tirg_dll = cdll.LoadLibrary( str( Path(settings.SETTINGS["paths"]["DLL_PATH"]).joinpath( "tirg-64.dll")).encode( sys.getfilesystemencoding())) except Exception as e: print("Legion loading failed with '%s'" % str(e)) self.tirg_dll.getTextBBoxesFromFile.argtypes = [c_char_p, c_int, c_int] self.tirg_dll.getTextBBoxesFromFile.restype = c_char_p self.tirg_dll.getTextBBoxesFromBytes.argtypes = [ c_char_p, c_int, c_int ] self.tirg_dll.getTextBBoxesFromBytes.restype = c_char_p
def test_bringme_toml_upgrade_idempotency(self): """ Tests that bringme toml config backup file is not overwritten after update runs 2nd time. """ # set up 1.0.0 bringme toml file bringme_toml_path = str( Path(TEST_USER_DIR).joinpath("settings/sm_bringme.toml")) self._setup_legacy_bringme_toml(bringme_toml_path) # run migration 2x self.migrator.update_bringme_toml_to_v1_7_0() self.migrator.update_bringme_toml_to_v1_7_0() # assertions self._do_bringme_toml_assertions(bringme_toml_path)
def _calculate_filepath_from_frame(stack, index): try: frame = stack[index] # module = inspect.getmodule(frame[1]) filepath = str(Path(frame[1])) if filepath.endswith("pyc"): filepath = filepath[:-1] return filepath except AttributeError as e: if not os.path.isfile(frame[1]): pyc = frame[1] + "c" if os.path.isfile(pyc): printer.out("\n {} \n Caster Detected a stale .pyc file. The stale file has been removed please restart Caster. \n".format(pyc)) os.remove(pyc) else: traceback.print_exc()
def _setup_legacy_user_content_structure(): for directory in ["rules", "hooks", "transformers"]: for pkg in ["pkg1", "pkg2"]: pkg_path = Path(TEST_USER_DIR).joinpath(directory).joinpath( pkg) pkg_path.mkdir( parents=True ) # not using exist_ok=True b/c tests shouldn't leave junk behind for python_file in [ "__init__.py", pkg + ".py", pkg + "_support.py" ]: pkg_path.joinpath(python_file).touch()
def _do_bringme_toml_assertions(self, bringme_toml_path): # test that backup exists and is correct backup_path = Path(TEST_USER_DIR).joinpath("settings").joinpath( "sm_bringme.toml.bak") backup_config = SelfModStateSavingConfig(str(backup_path)) backup_config.load() expected_backup = { "caster rules": str(Path(TEST_USER_DIR).joinpath("rules")), "caster hooks": str(Path(TEST_USER_DIR).joinpath("hooks")), "caster transformers": str(Path(TEST_USER_DIR).joinpath("transformers")), "some other path": str(Path("asdf").joinpath("zxcv")) } self.assertDictEqual(expected_backup, backup_config.get("folder")) # test that new values are correct (including that other paths aren't wiped) updated_config = SelfModStateSavingConfig(bringme_toml_path) updated_config.load() expected_paths = { "caster rules": str( Path(TEST_USER_DIR).joinpath( ContentRoot.USER_DIR).joinpath("rules")), "caster hooks": str( Path(TEST_USER_DIR).joinpath( ContentRoot.USER_DIR).joinpath("hooks")), "caster transformers": str( Path(TEST_USER_DIR).joinpath( ContentRoot.USER_DIR).joinpath("transformers")), "some other path": str(Path("asdf").joinpath("zxcv")) } self.assertDictEqual(expected_paths, updated_config.get("folder"))
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) _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 _get_defaults(): terminal_path_default = "C:/Program Files/Git/git-bash.exe" if not os.path.isfile(terminal_path_default): terminal_path_default = "" ahk_path_default = "C:/Program Files/AutoHotkey/AutoHotkey.exe" if not os.path.isfile(ahk_path_default): ahk_path_default = "" return { "paths": { "BASE_PATH": _BASE_PATH, "USER_DIR": _USER_DIR, # pathlib string conversion can be removed once pathlib is utilized throughout Caster. # DATA "SM_BRINGME_PATH": str(Path(_USER_DIR).joinpath("settings/sm_bringme.toml")), "SM_ALIAS_PATH": str(Path(_USER_DIR).joinpath("data/sm_aliases.toml")), "SM_CHAIN_ALIAS_PATH": str(Path(_USER_DIR).joinpath("data/sm_chain_aliases.toml")), "SM_HISTORY_PATH": str(Path(_USER_DIR).joinpath("data/sm_history.toml")), "RULES_CONFIG_PATH": str(Path(_USER_DIR).joinpath("settings/rules.toml")), "TRANSFORMERS_CONFIG_PATH": str(Path(_USER_DIR).joinpath("settings/transformers.toml")), "HOOKS_CONFIG_PATH": str(Path(_USER_DIR).joinpath("settings/hooks.toml")), "COMPANION_CONFIG_PATH": str(Path(_USER_DIR).joinpath("settings/companion_config.toml")), "DLL_PATH": str(Path(_BASE_PATH).joinpath("lib/dll/")), "GDEF_FILE": str(Path(_USER_DIR).joinpath("transformers/words.txt")), "LOG_PATH": str(Path(_USER_DIR).joinpath("log.txt")), "SAVED_CLIPBOARD_PATH": str(Path(_USER_DIR).joinpath("data/clipboard.json")), "SIKULI_SCRIPTS_PATH": str(Path(_USER_DIR).joinpath("sikuli")), "GIT_REPO_LOCAL_REMOTE_PATH": str( Path(_USER_DIR).joinpath( "settings/git_repo_local_to_remote_match.toml")), "GIT_REPO_LOCAL_REMOTE_DEFAULT_PATH": str( Path(_BASE_PATH).joinpath( "bin/share/git_repo_local_to_remote_match.toml.defaults")), # REMOTE_DEBUGGER_PATH is the folder in which pydevd.py can be found "REMOTE_DEBUGGER_PATH": str(Path("")), # SIKULIX EXECUTABLES "SIKULI_IDE": str(Path("")), "SIKULI_RUNNER": str(Path("")), # EXECUTABLES "AHK_PATH": str(Path(_BASE_PATH).joinpath(ahk_path_default)), "DOUGLAS_PATH": str(Path(_BASE_PATH).joinpath("asynch/mouse/grids.py")), "ENGINE_PATH": _validate_engine_path(), "HOMUNCULUS_PATH": str(Path(_BASE_PATH).joinpath("asynch/hmc/h_launch.py")), "LEGION_PATH": str(Path(_BASE_PATH).joinpath("asynch/mouse/legion.py")), "MEDIA_PATH": str(Path(_BASE_PATH).joinpath("bin/media")), "RAINBOW_PATH": str(Path(_BASE_PATH).joinpath("asynch/mouse/grids.py")), "REBOOT_PATH": str(Path(_BASE_PATH).joinpath("bin/reboot.bat")), "REBOOT_PATH_WSR": str(Path(_BASE_PATH).joinpath("bin/reboot_wsr.bat")), "SETTINGS_WINDOW_PATH": str(Path(_BASE_PATH).joinpath("asynch/settingswindow.py")), "SIKULI_SERVER_PATH": str( Path(_BASE_PATH).joinpath( "asynch/sikuli/server/xmlrpc_server.sikuli")), "SUDOKU_PATH": str(Path(_BASE_PATH).joinpath("asynch/mouse/grids.py")), "WSR_PATH": str( Path(_BASE_PATH).joinpath( "C:/Windows/Speech/Common/sapisvr.exe")), "TERMINAL_PATH": str(Path(terminal_path_default)), # CCR "CONFIGDEBUGTXT_PATH": str(Path(_USER_DIR).joinpath("data/configdebug.txt")), # PYTHON "PYTHONW": SYSTEM_INFORMATION["hidden console binary"], }, # Speech recognition engine settings "engine": { "default_engine_mode": False, "engine_mode": "normal", "default_mic": False, "mic_mode": "on", "mic_sleep_timer_on": True, "mic_sleep_timer": 300, # Seconds before microphone goes to sleep after last successful recognition. # Note: No greater than 5 minutes or 300 seconds unless DPI/DPI sleep settings are adjusted }, # python settings "python": { "automatic_settings": True, # Set to false to manually set "version" and "pip" below. "version": "python", # Depending Python setup (python, python2, python2.7, py, py -2) "pip": "pip" # Depending on PIP setup (pip ,pip2, pip2.7) }, # sikuli settings "sikuli": { "enabled": False, "version": "" }, # gitbash settings "gitbash": { "loading_time": 5, # the time to initialise the git bash window in seconds "fetching_time": 3 # the time to fetch a github repository in seconds }, # node rules path "Tree_Node_Path": { "SM_CSS_TREE_PATH": str(Path(_USER_DIR).joinpath("data/sm_css_tree.toml")), }, "online": { "online_mode": True, # False disables updates "last_update_date": "None", "update_interval": 7 # Days }, # Default enabled hooks: Use hook class name "hooks": { "default_hooks": ['PrinterHook'], }, # miscellaneous section "miscellaneous": { "dev_commands": True, "keypress_wait": 50, # milliseconds "max_ccr_repetitions": 16, "atom_palette_wait": 30, # hundredths of a second "integer_remap_opt_in": False, "short_integer_opt_out": False, "integer_remap_crash_fix": False, "print_rdescripts": True, "history_playback_delay_secs": 1.0, "legion_vertical_columns": 30, "legion_downscale_factor": "auto", "use_aenea": False, "hmc": True, "ccr_on": True, "dragonfly_pause_default": 0.003, # dragonfly _pause_default 0.02 is too slow! Caster default 0.003 }, # Grammar reloading section "grammar_reloading": { "reload_trigger": "timer", # manual or timer "reload_timer_seconds": 5, # seconds }, "formats": { "_default": { "text_format": [5, 0], "secondary_format": [1, 0], }, "C plus plus": { "text_format": [3, 1], "secondary_format": [2, 1], }, "C sharp": { "text_format": [3, 1], "secondary_format": [2, 1], }, "Dart": { "text_format": [3, 1], "secondary_format": [2, 1], }, "HTML": { "text_format": [5, 0], "secondary_format": [5, 2], }, "Java": { "text_format": [3, 1], "secondary_format": [2, 1], }, "Javascript": { "text_format": [3, 1], "secondary_format": [2, 1], }, "matlab": { "text_format": [3, 1], "secondary_format": [1, 3], }, "Python": { "text_format": [5, 3], "secondary_format": [2, 1], }, "Rust": { "text_format": [5, 3], "secondary_format": [2, 1], }, "sequel": { "text_format": [5, 3], "secondary_format": [1, 3], }, } }
def _find_natspeak(): ''' Finds engine 'natspeak.exe' path and verifies supported DNS versions via Windows Registry. ''' try: if six.PY2: import _winreg as winreg else: import winreg except ImportError: printer.out("Could not import winreg") return "" printer.out("Searching Windows Registry For DNS...") proc_arch = os.environ['PROCESSOR_ARCHITECTURE'].lower() try: proc_arch64 = os.environ['PROCESSOR_ARCHITEW6432'].lower() except KeyError: proc_arch64 = False if proc_arch == 'x86' and not proc_arch64: arch_keys = {0} elif proc_arch == 'x86' or proc_arch == 'amd64': arch_keys = {winreg.KEY_WOW64_32KEY, winreg.KEY_WOW64_64KEY} else: raise Exception("Unhandled arch: %s" % proc_arch) for arch_key in arch_keys: key = winreg.OpenKey( winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall", 0, winreg.KEY_READ | arch_key) for i in xrange(0, winreg.QueryInfoKey(key)[0]): skey_name = winreg.EnumKey(key, i) skey = winreg.OpenKey(key, skey_name) DisplayName, Publisher, DisplayVersion, InstallLocation = 'null' try: DisplayName = winreg.QueryValueEx(skey, 'DisplayName')[0] Publisher = winreg.QueryValueEx(skey, 'Publisher')[0] DisplayVersion = winreg.QueryValueEx(skey, 'DisplayVersion')[0] InstallLocation = winreg.QueryValueEx(skey, 'InstallLocation')[0] except OSError as error: if error.errno == 2: # Suppresses '[Error 2] The system cannot find the file specified' pass else: printer.out(error) finally: skey.Close() if Publisher == "Nuance Communications Inc." and "Dragon" in DisplayName: DnsVersion = int(str(DisplayVersion)[:2]) if DnsVersion >= 13: engine_path = str( Path(InstallLocation).joinpath( "Program/natspeak.exe")) if os.path.isfile(engine_path): printer.out("Search Complete.") return engine_path else: printer.out( "Dragon Naturally Speaking {} is not supported by Caster. Only versions 13 and above are supported. Purchase Dragon Naturally Speaking 13 or above" .format(DnsVersion)) printer.out("Cannot find dragon engine path") return ""
class BringRule(BaseSelfModifyingRule): """ BringRule adds entries to a 2-layered map which can be described as {type: {extra: target}} type: used for internal organization; types include website, file, folder, etc. extra: the word or words used to label the target target: a url or file/folder location """ pronunciation = "bring me" # Contexts _browser_context = AppContext(["chrome", "firefox"]) _explorer_context = AppContext("explorer.exe") | contexts.DIALOGUE_CONTEXT _terminal_context = contexts.TERMINAL_CONTEXT # Paths _terminal_path = settings.settings(["paths", "TERMINAL_PATH"]) _explorer_path = str(Path("C:\\Windows\\explorer.exe")) _user_dir = settings.SETTINGS["paths"]["USER_DIR"] _home_dir = str(Path.home()) def __init__(self): super(BringRule, self).__init__(settings.settings(["paths", "SM_BRINGME_PATH"])) def _initialize(self): """ Sets up defaults for first time only. """ if len(self._config.get_copy()) == 0: self._bring_reset_defaults() def _deserialize(self): """ This _deserialize creates mapping which uses the user-made extras. """ self._initialize() self._smr_mapping = { "bring me <program>": R(Function(self._bring_program)), "bring me <website>": R(Function(self._bring_website)), "bring me <folder> [in <app>]": R(Function(self._bring_folder)), "bring me <file>": R(Function(self._bring_file)), "refresh bring me": R(Function(self._load_and_refresh)), "<launch_type> to bring me as <key>": R(Function(self._bring_add)), "to bring me as <key>": R(Function(self._bring_add_auto)), "remove <key> from bring me": R(Function(self._bring_remove)), "restore bring me defaults": R(Function(self._bring_reset_defaults)), } self._smr_extras = [ Choice( "launch_type", { "[current] program": "program", "website": "website", "folder": "folder", "file": "file", }), Choice("app", { "terminal": "terminal", "explorer": "explorer", }), Dictation("key"), ] self._smr_extras.extend(self._rebuild_items()) self._smr_defaults = {"app": None} def _rebuild_items(self): # E.g. [Choice("folder", {"my pictures": ...}), ...] config_copy = self._config.get_copy() return [ Choice(header, { key: os.path.expandvars(value) for key, value in section.items() }) for header, section in config_copy.items() ] def _refresh(self, *args): """ :param args: in this case, args is the pre-altered copy of the state map to replace the current map with """ self._config.replace(args[0]) self.reset() def _load_and_refresh(self): """ """ self.reset() 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_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 _bring_remove(self, key): # Remove item from bring me config_copy = self._config.get_copy() deleted = False for section in config_copy.keys(): if key in config_copy[section]: del config_copy[section][str(key)] deleted = True break if deleted: self._refresh(config_copy) def _bring_reset_defaults(self): """ Restore bring me list to defaults """ self._refresh(BringRule._bm_defaults) def _bring_add_auto(self, key): """ Adds an entry for a program, a website, or a folder (without specifying), depending on which context get detected (if either). """ def add(launch_type): return Function(lambda: self._bring_add(launch_type, key)) ContextAction(add("program"), [ (BringRule._browser_context, add("website")), (BringRule._explorer_context, add("folder")), ]).execute() # =================== BringMe actions --> these do not change state def _bring_website(self, website): browser = utilities.default_browser_command() Popen(shlex.split(browser.replace('%1', website))) def _bring_folder(self, folder, app): if not app: ContextAction( Function(lambda: Popen([BringRule._explorer_path, folder])), [(BringRule._terminal_context, Text("cd \"%s\"\n" % folder)), (BringRule._explorer_context, Key("c-l/5") + Text("%s\n" % folder))]).execute() elif app == "terminal": Popen([ BringRule._terminal_path, "--cd=" + folder.replace("\\", "/") ]) elif app == "explorer": Popen([BringRule._explorer_path, folder]) def _bring_program(self, program): Popen(program) def _bring_file(self, file): threading.Thread(target=os.startfile, args=(file, )).start() # pylint: disable=no-member # =================== BringMe default setup: _bm_defaults = { "website": { # Documentation "caster documentation": "https://caster.readthedocs.io/en/latest/", "dragonfly documentation": "https://dragonfly2.readthedocs.io/en/latest/", # Caster Support "dragonfly gitter": "https://gitter.im/dictation-toolbox/dragonfly", "caster gitter": "https://gitter.im/dictation-toolbox/Caster", "caster discord": "https://discord.gg/9eAAsCJr", # General URLs "google": "https://www.google.com", }, "folder": { # OS folder Navigation "libraries | home": _home_dir, "my pictures": str(Path(_home_dir).joinpath("Pictures")), "my documents": str(Path(_home_dir).joinpath("Documents")), # Caster User Dir Navigation "caster user": str(Path(_user_dir)), "caster hooks": str(Path(_user_dir).joinpath("hooks")), "caster transformers": str(Path(_user_dir).joinpath("transformers")), "caster rules": str(Path(_user_dir).joinpath("rules")), "caster data": str(Path(_user_dir).joinpath("data")), "caster settings": str(Path(_user_dir).joinpath("settings")), "sick you lee": str(Path(_user_dir).joinpath("sikuli")), }, "program": { "notepad": str(Path("C:\\Windows\\notepad.exe")), }, "file": { # User Settings "caster settings file": str(Path(_user_dir).joinpath("settings/settings.toml")), "caster rules file": str(Path(_user_dir).joinpath("settings/rules.toml")), "caster bring me file": str(Path(_user_dir).joinpath("settings/sm_bringme.toml")), "caster hooks file": str(Path(_user_dir).joinpath("settings/hooks.toml")), "caster companion file": str(Path(_user_dir).joinpath("settings/companion_config.toml")), "caster transformers file": str(Path(_user_dir).joinpath("settings/transformers.toml")), # Caster Data "caster alias file": str(Path(_user_dir).joinpath("data/sm_aliases.toml")), "caster chain aliases file": str(Path(_user_dir).joinpath("data/sm_chain_aliases.toml")), "caster clipboard file": str(Path(_user_dir).joinpath("data/clipboard.json")), "caster record from history file": str(Path(_user_dir).joinpath("data/sm_history.toml")), "caster log file": str(Path(_user_dir).joinpath("data/log.txt")), # Simplified Transformer "caster transformer file": str(Path(_user_dir).joinpath("transformers/words.txt")), } }
from urllib.parse import unquote import tomlkit from dragonfly import Key, Pause, Window, get_current_engine from castervoice.lib.clipboard import Clipboard from castervoice.lib import printer from castervoice.lib.util import guidance if six.PY2: from castervoice.lib.util.pathlib import Path else: from pathlib import Path # pylint: disable=import-error try: # Style C -- may be imported into Caster, or externally BASE_PATH = str(Path(__file__).resolve().parent.parent) if BASE_PATH not in sys.path: sys.path.append(BASE_PATH) finally: from castervoice.lib import settings, printer DARWIN = sys.platform.startswith('darwin') LINUX = sys.platform.startswith('linux') WIN32 = sys.platform.startswith('win') # TODO: Move functions that manipulate or retrieve information from Windows to `window_mgmt_support` in navigation_rules. # TODO: Implement Optional exact title matching for `get_matching_windows` in Dragonfly def window_exists(windowname=None, executable=None): if Window.get_matching_windows(title=windowname, executable=executable): return True
def setUp(self): self._enable_file_writing() Path(TEST_USER_DIR).mkdir(exist_ok=True) self.migrator = UserDirUpdater(TEST_USER_DIR)
def _get_actual_paths(self): return set([str(f) for f in Path(TEST_USER_DIR).glob("**/*")])
def _move_legacy_package(self, package_name): source_path = Path(self.user_dir).joinpath(package_name) dest_path_base = Path(self.user_dir).joinpath( self.root_user_content_package_name).joinpath(package_name) dest_path_base.mkdir(parents=True, exist_ok=True) if source_path.exists(): for f in source_path.glob("*"): f.rename(dest_path_base.joinpath(f.stem)) source_path.rmdir()
def create_user_dir_directories(self): for directory in ["data", "sikuli", "settings"]: Path(self.user_dir).joinpath(directory).mkdir(parents=True, exist_ok=True)