def build_keymap(config, key, mappings=None): system = registry.get_plugin('system', key[1]).obj machine_class = registry.get_plugin('machine', key[2]).obj keymap = Keymap(machine_class.get_keys(), system.KEYS + machine_class.get_actions()) if mappings is None: mappings = system.KEYMAPS.get(key[2]) if mappings is None: if machine_class.KEYMAP_MACHINE_TYPE is not None: # Try fallback. return build_keymap(config, (key[0], key[1], machine_class.KEYMAP_MACHINE_TYPE)) # No fallback... mappings = {} keymap.set_mappings(mappings) return keymap
def _consume_engine_command(self, command): # The first commands can be used whether plover has output enabled or not. if command == 'RESUME': self._set_output(True) return True elif command == 'TOGGLE': self._toggle_output() return True elif command == 'QUIT': self.quit() return True if not self._is_running: return False # These commands can only be run when plover has output enabled. if command == 'SUSPEND': self._set_output(False) elif command == 'CONFIGURE': self._trigger_hook('configure') elif command == 'FOCUS': self._trigger_hook('focus') elif command == 'ADD_TRANSLATION': self._trigger_hook('add_translation') elif command == 'LOOKUP': self._trigger_hook('lookup') else: command_args = command.split(':', 1) command_fn = registry.get_plugin('command', command_args[0]).obj command_fn(self, command_args[1] if len(command_args) == 2 else '') return False
def get_dictionaries(self): system_name = self.get_system_name() try: system = registry.get_plugin('system', system_name).obj except: log.error("invalid system name: %s", system_name, exc_info=True) return [] section = SYSTEM_CONFIG_SECTION % system_name option = SYSTEM_DICTIONARIES_OPTION dictionaries = self._get(section, option, None) if dictionaries is None: dictionaries = self._legacy_get_dictionary_file_names() if dictionaries is None: dictionaries = [ DictionaryConfig(path) for path in system.DEFAULT_DICTIONARIES ] return dictionaries try: return [ DictionaryConfig.from_dict(d) for d in json.loads(dictionaries) ] except: log.error("invalid system dictionaries, resetting to default", exc_info=True) self.set_dictionaries(None) return self.get_dictionaries()
def setup(system_name): system_symbols = {} mod = registry.get_plugin('system', system_name).obj for symbol, init in _EXPORTS.items(): system_symbols[symbol] = init(mod) system_symbols['NAME'] = system_name globals().update(system_symbols)
def sub_commands(self): machine_name = self.engine.config["machine_type"] machine_class = registry.get_plugin("machine", machine_name).obj return [ SetMachineOption(self.output, name, default, self.engine) for name, default in machine_class.get_option_info().items() ]
def _consume_engine_command(self, command): # The first commands can be used whether plover has output enabled or not. if command == "RESUME": self._set_output(True) return True elif command == "TOGGLE": self._toggle_output() return True elif command == "QUIT": self._trigger_hook("quit") return True if not self._is_running: return False # These commands can only be run when plover has output enabled. if command == "SUSPEND": self._set_output(False) elif command == "CONFIGURE": self._trigger_hook("configure") elif command == "FOCUS": self._trigger_hook("focus") elif command == "ADD_TRANSLATION": self._trigger_hook("add_translation") elif command == "LOOKUP": self._trigger_hook("lookup") else: command_args = command.split(":", 2) command_fn = registry.get_plugin("command", command_args[0]).resolve() command_fn(self, command_args[1] if len(command_args) == 2 else "") return False
def _consume_engine_command(self, command, force=False): # The first commands can be used whether plover has output enabled or not. command_name, *command_args = command.split(':', 1) command_name = command_name.lower() if command_name == 'resume': self._set_output(True) return True elif command_name == 'toggle': self._toggle_output() return True elif command_name == 'quit': self.quit() return True if not force and not self._is_running: return False # These commands can only be run when plover has output enabled. if command_name == 'suspend': self._set_output(False) elif command_name == 'configure': self._trigger_hook('configure') elif command_name == 'focus': self._trigger_hook('focus') elif command_name == 'add_translation': self._trigger_hook('add_translation') elif command_name == 'lookup': self._trigger_hook('lookup') elif command_name == 'suggestions': self._trigger_hook('suggestions') else: command_fn = registry.get_plugin('command', command_name).obj command_fn(self, command_args[0] if command_args else '') return False
def _meta_to_action(meta, ctx): meta_name, meta_arg = _parse_meta(meta) if meta_name is not None: meta_fn = registry.get_plugin('meta', meta_name).obj action = meta_fn(ctx, meta_arg) else: action = ctx.new_action() return action
def _get_dictionary_module(filename): extension = splitext(filename)[1].lower()[1:] try: entrypoint = registry.get_plugin('dictionary', extension) except KeyError: raise DictionaryLoaderException( 'Unsupported extension: %s. Supported extensions: %s' % (extension, ', '.join(sorted(registry.list_plugins('dictionary'))))) return entrypoint.resolve()
def setup(system_name): system_symbols = {} mod = registry.get_plugin('system', system_name).obj for symbol in _DIRECT_EXPORTS: system_symbols[symbol] = getattr(mod, symbol) for symbol, init in _CALCULATED_EXPORTS.items(): system_symbols[symbol] = init(mod) system_symbols['NAME'] = system_name globals().update(system_symbols)
def setup(system_name): system_symbols = {} system_mod = registry.get_plugin('system', system_name).obj for symbol, init in _EXPORTS.items(): system_symbols[symbol] = init(system_mod) system_symbols['NAME'] = system_name globals().update(system_symbols) Stroke.setup(KEYS, IMPLICIT_HYPHEN_KEYS, NUMBER_KEY, NUMBERS, UNDO_STROKE_STENO)
def _machine_option(self, *args): machine_type = self._config['machine_type'] machine_class = registry.get_plugin('machine', machine_type).obj if issubclass(machine_class, Keyboard): opt_class = KeyboardOption elif issubclass(machine_class, SerialStenotypeBase): opt_class = SerialOption else: opt_class = NopeOption return opt_class(*args)
def setup(system_name, system_mod=None, system_dict=None): system_symbols = {} if system_mod is None: system_mod = registry.get_plugin('system', system_name).obj for symbol, init in _EXPORTS.items(): system_symbols[symbol] = init(system_mod) system_symbols['NAME'] = system_name if system_dict is None: system_dict = globals() system_dict.update(system_symbols)
def _get_dictionary_class(filename): extension = splitext(filename)[1].lower()[1:] try: dict_module = registry.get_plugin('dictionary', extension).obj except KeyError: raise ValueError( 'Unsupported extension: %s. Supported extensions: %s' % (extension, ', '.join(plugin.name for plugin in registry.list_plugins('dictionary')))) return dict_module
def _get_dictionary_module(filename): extension = splitext(filename)[1].lower()[1:] try: entrypoint = registry.get_plugin('dictionary', extension) except KeyError: raise DictionaryLoaderException( 'Unsupported extension: %s. Supported extensions: %s' % (extension, ', '.join(sorted( registry.list_plugins('dictionary'))))) return entrypoint.resolve()
def _start_extensions(self, extension_list): for extension_name in extension_list: log.info('starting `%s` extension', extension_name) try: extension = registry.get_plugin('extension', extension_name).obj(self) extension.start() except Exception: log.error('initializing extension `%s` failed', extension_name, exc_info=True) else: self._running_extensions[extension_name] = extension
def get_system_keymap(self, machine_type=None, system_name=None): if machine_type is None: machine_type = self.get_machine_type() try: machine_class = registry.get_plugin('machine', machine_type).resolve() except: log.error("invalid machine type: %s", machine_type, exc_info=True) return None if system_name is None: system_name = self.get_system_name() try: system = registry.get_plugin('system', system_name).resolve() except: log.error("invalid system name: %s", system_name, exc_info=True) return None section = SYSTEM_CONFIG_SECTION % system_name option = SYSTEM_KEYMAP_OPTION % machine_type mappings = self._get(section, option, None) if mappings is None: # No user mappings, use system default. mappings = system.KEYMAPS.get(machine_type) else: try: mappings = dict(json.loads(mappings)) except ValueError as e: log.error("invalid machine keymap, resetting to default", exc_info=True) self.set_system_keymap(None, machine_type) mappings = system.KEYMAPS.get(machine_type) if mappings is None: if machine_class.KEYMAP_MACHINE_TYPE is not None: # Try fallback. return self.get_system_keymap( machine_type=machine_class.KEYMAP_MACHINE_TYPE, system_name=system_name) # No fallback... mappings = {} keymap = Keymap(machine_class.get_keys(), system.KEYS + machine_class.get_actions()) keymap.set_mappings(mappings) return keymap
def get_system_keymap(self, machine_type=None, system_name=None): if machine_type is None: machine_type = self.get_machine_type() try: machine_class = registry.get_plugin('machine', machine_type).obj except: log.error("invalid machine type: %s", machine_type, exc_info=True) return None if system_name is None: system_name = self.get_system_name() try: system = registry.get_plugin('system', system_name).obj except: log.error("invalid system name: %s", system_name, exc_info=True) return None section = SYSTEM_CONFIG_SECTION % system_name option = SYSTEM_KEYMAP_OPTION % machine_type mappings = self._get(section, option, None) if mappings is None: # No user mappings, use system default. mappings = system.KEYMAPS.get(machine_type) else: try: mappings = dict(json.loads(mappings)) except ValueError: log.error("invalid machine keymap, resetting to default", exc_info=True) self.set_system_keymap(None, machine_type) mappings = system.KEYMAPS.get(machine_type) if mappings is None: if machine_class.KEYMAP_MACHINE_TYPE is not None: # Try fallback. return self.get_system_keymap( machine_type=machine_class.KEYMAP_MACHINE_TYPE, system_name=system_name ) # No fallback... mappings = {} keymap = Keymap(machine_class.get_keys(), system.KEYS + machine_class.get_actions()) keymap.set_mappings(mappings) return keymap
def get_system_name(self): system_name = self._get(BASE_SYSTEM_SECTION, SYSTEM_NAME_OPTION, None) if system_name is not None: try: system_name = registry.get_plugin('system', system_name).name except: log.error("invalid system name: %s", system_name, exc_info=True) self.set_system_name(DEFAULT_SYSTEM_NAME) system_name = None if system_name is None: system_name = DEFAULT_SYSTEM_NAME return system_name
def get_machine_type(self): machine_type = self._get(MACHINE_CONFIG_SECTION, MACHINE_TYPE_OPTION, None) if machine_type is not None: try: machine_type = registry.get_plugin('machine', machine_type).name except: log.error("invalid machine type: %s", machine_type, exc_info=True) self.set_machine_type(DEFAULT_MACHINE_TYPE) machine_type = None if machine_type is None: machine_type = DEFAULT_MACHINE_TYPE return machine_type
def main(): """Launch plover.""" description = "Run the plover stenotype engine. This is a graphical application." parser = argparse.ArgumentParser(description=description) parser.add_argument('--version', action='version', version='%s %s' % (__software_name__.capitalize(), __version__)) parser.add_argument('-l', '--log-level', choices=['debug', 'info', 'warning', 'error'], default=None, help='set log level') parser.add_argument('-g', '--gui', default='qt', help='set gui') args = parser.parse_args(args=sys.argv[1:]) if args.log_level is not None: log.set_level(args.log_level.upper()) log.setup_platform_handler() registry.load_plugins() registry.update() gui = registry.get_plugin('gui', args.gui).obj try: # Ensure only one instance of Plover is running at a time. with plover.oslayer.processlock.PloverLock(): if sys.platform.startswith('darwin'): appnope.nope() init_config_dir() # This must be done after calling init_config_dir, so # Plover's configuration directory actually exists. log.setup_logfile() log.info('Plover %s', __version__) config = Config() config.target_file = CONFIG_FILE code = gui.main(config) with open(config.target_file, 'wb') as f: config.save(f) except plover.oslayer.processlock.LockNotAcquiredException: gui.show_error('Error', 'Another instance of Plover is already running.') code = 1 except: gui.show_error('Unexpected error', traceback.format_exc()) code = 2 os._exit(code)
def _machine_option(self, *args): machine_options = { plugin.name: plugin.obj for plugin in registry.list_plugins('gui.qt.machine_option') } machine_type = self._config['machine_type'] machine_class = registry.get_plugin('machine', machine_type).obj for klass in machine_class.mro(): # Look for `module_name:class_name` before `class_name`. for name in ( '%s:%s' % (klass.__module__, klass.__name__), klass.__name__, ): opt_class = machine_options.get(name) if opt_class is not None: return opt_class(*args) return NopeOption(*args)
def get_machine_specific_options(self, machine_type=None): if machine_type is None: machine_type = self.get_machine_type() def convert(p, v): try: return p[1](v) except ValueError: return p[0] machine_class = registry.get_plugin('machine', machine_type).obj info = machine_class.get_option_info() defaults = {k: v[0] for k, v in info.items()} if self._config.has_section(machine_type): options = {o: self._config.get(machine_type, o) for o in self._config.options(machine_type) if o in info} options = {k: convert(info[k], v) for k, v in options.items()} defaults.update(options) return defaults
def get_machine_specific_options(self, machine_type=None): if machine_type is None: machine_type = self.get_machine_type() def convert(p, v): try: return p[1](v) except ValueError: return p[0] machine_class = registry.get_plugin('machine', machine_type).resolve() info = machine_class.get_option_info() defaults = {k: v[0] for k, v in info.items()} if self._config.has_section(machine_type): options = { o: self._config.get(machine_type, o) for o in self._config.options(machine_type) if o in info } options = {k: convert(info[k], v) for k, v in options.items()} defaults.update(options) return defaults
def validate(config, key, raw_options): if not isinstance(raw_options, (dict, configparser.SectionProxy)): raise InvalidConfigOption(raw_options, default(config, key)) machine_options = OrderedDict() invalid_options = OrderedDict() machine_class = registry.get_plugin('machine', key[1]).obj for name, params in sorted(machine_class.get_option_info().items()): fallback, convert = params try: raw_value = raw_options[name] except KeyError: value = fallback else: try: value = convert(raw_value) except ValueError: invalid_options[name] = raw_value value = fallback machine_options[name] = value if invalid_options: raise InvalidConfigOption(invalid_options, machine_options) return machine_options
def get_dictionary_file_names(self): system_name = self.get_system_name() try: system = registry.get_plugin('system', system_name).resolve() except: log.error("invalid system name: %s", system_name, exc_info=True) return [] section = SYSTEM_CONFIG_SECTION % system_name option = SYSTEM_DICTIONARIES_OPTION filenames = self._get(section, option, None) if filenames is None: filenames = self._legacy_get_dictionary_file_names() if filenames is None: filenames = system.DEFAULT_DICTIONARIES else: try: filenames = tuple(json.loads(filenames)) except ValueError as e: log.error("invalid system dictionaries, resetting to default", exc_info=True) self.set_dictionary_file_names(None) filenames = system.DEFAULT_DICTIONARIES return [expand_path(path) for path in filenames]
def get_dictionaries(self): system_name = self.get_system_name() try: system = registry.get_plugin('system', system_name).obj except: log.error("invalid system name: %s", system_name, exc_info=True) return [] section = SYSTEM_CONFIG_SECTION % system_name option = SYSTEM_DICTIONARIES_OPTION dictionaries = self._get(section, option, None) if dictionaries is None: dictionaries = self._legacy_get_dictionary_file_names() if dictionaries is None: dictionaries = [DictionaryConfig(path) for path in system.DEFAULT_DICTIONARIES] return dictionaries try: return [DictionaryConfig.from_dict(d) for d in json.loads(dictionaries)] except: log.error("invalid system dictionaries, resetting to default", exc_info=True) self.set_dictionaries(None) return self.get_dictionaries()
def main(): """Launch plover.""" description = "Run the plover stenotype engine. This is a graphical application." parser = argparse.ArgumentParser(description=description) parser.add_argument('--version', action='version', version='%s %s' % (__software_name__.capitalize(), __version__)) parser.add_argument( '-s', '--script', default=None, nargs=argparse.REMAINDER, help='use another plugin console script as main entrypoint, ' 'passing in the rest of the command line arguments, ' 'print list of available scripts when no argument is given') parser.add_argument('-l', '--log-level', choices=['debug', 'info', 'warning', 'error'], default=None, help='set log level') parser.add_argument('-g', '--gui', default=None, help='set gui') args = parser.parse_args(args=sys.argv[1:]) if args.log_level is not None: log.set_level(args.log_level.upper()) log.setup_platform_handler() log.info('Plover %s', __version__) log.info('configuration directory: %s', CONFIG_DIR) log.info('plugins directory: %s', PLUGINS_DIR) registry.update() if args.gui is None: gui_priority = { 'qt': 1, 'none': -1, } gui_list = sorted(registry.list_plugins('gui'), reverse=True, key=lambda gui: gui_priority.get(gui.name, 0)) gui = gui_list[0].obj else: gui = registry.get_plugin('gui', args.gui).obj try: if args.script is not None: if args.script: # Create a mapping of available console script, # with the following priorities (highest first): # - {project_name}-{version}:{script_name} # - {project_name}:{script_name} # - {script_name} console_scripts = {} for e in sorted( pkg_resources.iter_entry_points('console_scripts'), key=lambda e: (e.dist, e.name)): for key in ( '%s-%s:%s' % (e.dist.project_name, e.dist.version, e.name), '%s:%s' % (e.dist.project_name, e.name), e.name, ): console_scripts[key] = e entrypoint = console_scripts.get(args.script[0]) if entrypoint is None: log.error('no such script: %s', args.script[0]) code = 1 else: sys.argv = args.script try: code = entrypoint.load()() except SystemExit as e: code = e.code if code is None: code = 0 else: print('available script(s):') dist = None for e in sorted( pkg_resources.iter_entry_points('console_scripts'), key=lambda e: (str(e.dist), e.name)): if dist != e.dist: dist = e.dist print('%s:' % dist) print('- %s' % e.name) code = 0 os._exit(code) # Ensure only one instance of Plover is running at a time. with plover.oslayer.processlock.PloverLock(): if sys.platform.startswith('darwin'): appnope.nope() init_config_dir() # This must be done after calling init_config_dir, so # Plover's configuration directory actually exists. log.setup_logfile() config = Config() config.target_file = CONFIG_FILE code = gui.main(config) with open(config.target_file, 'wb') as f: config.save(f) except plover.oslayer.processlock.LockNotAcquiredException: gui.show_error('Error', 'Another instance of Plover is already running.') code = 1 except: gui.show_error('Unexpected error', traceback.format_exc()) code = 2 if code == -1: # Restart. args = sys.argv[:] if args[0].endswith('.py') or args[0].endswith('.pyc'): # We're running from source. assert args[0] == __file__ args[0:1] = [sys.executable, '-m', __spec__.name] # Execute atexit handlers. atexit._run_exitfuncs() if sys.platform.startswith('win32'): # Workaround https://bugs.python.org/issue19066 subprocess.Popen(args, cwd=os.getcwd()) code = 0 else: os.execv(args[0], args) os._exit(code)
def translate_macro(self, macro): macro_fn = registry.get_plugin('macro', macro.name).obj macro_fn(self, macro.stroke, macro.cmdline)
def default(config, key): system = registry.get_plugin('system', key[1]).obj return [DictionaryConfig(path) for path in system.DEFAULT_DICTIONARIES]
def _update(self, config_update=None, full=False, reset_machine=False): original_config = self._config.as_dict() # Update configuration. if config_update is not None: self._config.update(**config_update) config = self._config.as_dict() else: config = original_config # Create configuration update. if full: config_update = config else: config_update = { option: value for option, value in config.items() if value != original_config[option] } # Update logging. log.set_stroke_filename(config['log_file_name']) log.enable_stroke_logging(config['enable_stroke_logging']) log.enable_translation_logging(config['enable_translation_logging']) # Update output. self._formatter.set_space_placement(config['space_placement']) self._formatter.start_attached = config['start_attached'] self._formatter.start_capitalized = config['start_capitalized'] self._translator.set_min_undo_length(config['undo_levels']) # Update system. system_name = config['system_name'] if system.NAME != system_name: log.info('loading system: %s', system_name) system.setup(system_name) # Update machine. update_keymap = False start_machine = False machine_params = MachineParams(config['machine_type'], config['machine_specific_options'], config['system_keymap']) # Do not reset if only the keymap changed. if self._machine_params is None or \ self._machine_params.type != machine_params.type or \ self._machine_params.options != machine_params.options: reset_machine = True if reset_machine: if self._machine is not None: self._machine.stop_capture() self._machine = None machine_type = config['machine_type'] machine_options = config['machine_specific_options'] machine_class = registry.get_plugin('machine', machine_type).obj log.info('setting machine: %s', machine_type) self._machine = machine_class(machine_options) self._machine.set_suppression(self._is_running) self._machine.add_state_callback(self._machine_state_callback) self._machine.add_stroke_callback(self._machine_stroke_callback) self._machine_params = machine_params update_keymap = True start_machine = True elif self._machine is not None: update_keymap = 'system_keymap' in config_update if update_keymap: machine_keymap = config['system_keymap'] if machine_keymap is not None: self._machine.set_keymap(machine_keymap) if start_machine: self._machine.start_capture() # Update running extensions. enabled_extensions = config['enabled_extensions'] running_extensions = set(self._running_extensions) self._stop_extensions(running_extensions - enabled_extensions) self._start_extensions(enabled_extensions - running_extensions) # Trigger `config_changed` hook. if config_update: self._trigger_hook('config_changed', config_update) # Update dictionaries. config_dictionaries = OrderedDict( (d.path, d) for d in config['dictionaries']) copy_default_dictionaries(config_dictionaries.keys()) # Start by unloading outdated dictionaries. self._dictionaries_manager.unload_outdated() self._set_dictionaries([ d for d in self._dictionaries.dicts if d.path in config_dictionaries and \ d.path in self._dictionaries_manager ]) # And then (re)load all dictionaries. dictionaries = [] for result in self._dictionaries_manager.load( config_dictionaries.keys()): if isinstance(result, DictionaryLoaderException): d = ErroredDictionary(result.path, result.exception) # Only show an error if it's new. if d != self._dictionaries.get(result.path): log.error('loading dictionary `%s` failed: %s', shorten_path(result.path), str(result.exception)) else: d = result d.enabled = config_dictionaries[d.path].enabled dictionaries.append(d) self._set_dictionaries(dictionaries)
def __init__(self, engine, use_qt_notifications): super(MainWindow, self).__init__() self.setupUi(self) if hasattr(self, 'setUnifiedTitleAndToolBarOnMac'): self.setUnifiedTitleAndToolBarOnMac(True) self._engine = engine self._active_dialogs = {} self._dialog_class = { 'about' : AboutDialog, 'configuration' : ConfigWindow, } self.action_Quit.triggered.connect(QCoreApplication.quit) all_actions = find_menu_actions(self.menubar) # Dictionaries. self.dictionaries = DictionariesWidget(engine) self.dictionaries.add_translation.connect(self._add_translation) self.scroll_area.setWidget(self.dictionaries) self.dictionaries.setFocus() edit_menu = all_actions['menu_Edit'].menu() edit_menu.addAction(self.dictionaries.action_Undo) edit_menu.addSeparator() edit_menu.addAction(self.dictionaries.action_AddDictionaries) edit_menu.addAction(self.dictionaries.action_EditDictionaries) edit_menu.addAction(self.dictionaries.action_RemoveDictionaries) edit_menu.addSeparator() edit_menu.addAction(self.dictionaries.action_MoveDictionariesUp) edit_menu.addAction(self.dictionaries.action_MoveDictionariesDown) # Tray icon. self._trayicon = TrayIcon() self._trayicon.enable() self._trayicon.clicked.connect(self._engine.toggle_output) if use_qt_notifications: handler = NotificationHandler() handler.emitSignal.connect(self._trayicon.log) log.add_handler(handler) popup_menu = QMenu() for action_name in ( 'action_ToggleOutput', 'action_Reconnect', '', 'menu_Tools', '', 'action_Configure', '', 'menu_Help', '', 'action_Show', 'action_Quit', ): if action_name: popup_menu.addAction(all_actions[action_name]) else: popup_menu.addSeparator() self._trayicon.set_menu(popup_menu) engine.signal_connect('machine_state_changed', self._trayicon.update_machine_state) # Populate tools bar/menu. tools_menu = all_actions['menu_Tools'].menu() # Toolbar popup menu for selecting which tools are shown. self.toolbar_menu = QMenu() self.toolbar.setContextMenuPolicy(Qt.CustomContextMenu) self.toolbar.customContextMenuRequested.connect( lambda: self.toolbar_menu.popup(QCursor.pos()) ) for tool_name in sorted(registry.list_plugins('gui.qt.tool')): tool = registry.get_plugin('gui.qt.tool', tool_name).resolve() action_parameters = [] if tool.ICON is not None: icon = tool.ICON # Internal QT resources start with a `:`. if not icon.startswith(':'): icon = resource_filename(icon) action_parameters.append(QIcon(icon)) action_parameters.append(tool.TITLE) toolbar_action = None for parent in (tools_menu, self.toolbar, self.toolbar_menu): action = parent.addAction(*action_parameters) action.setObjectName(tool_name) if tool.__doc__ is not None: action.setToolTip(tool.__doc__) if tool.SHORTCUT is not None: action.setShortcut(QKeySequence.fromString(tool.SHORTCUT)) if parent == self.toolbar_menu: action.setCheckable(True) action.setChecked(True) assert toolbar_action is not None action.toggled.connect(toolbar_action.setVisible) else: if parent == self.toolbar: toolbar_action = action action.triggered.connect(partial(self._activate_dialog, tool_name, args=())) self._dialog_class[tool_name] = tool engine.signal_connect('output_changed', self.on_output_changed) # Machine. self.machine_type.addItems( _(machine) for machine in engine.list_plugins('machine') ) engine.signal_connect('config_changed', self.on_config_changed) engine.signal_connect('machine_state_changed', lambda machine, state: self.machine_state.setText(_(state.capitalize())) ) self.restore_state() # Commands. engine.signal_connect('add_translation', partial(self._add_translation, manage_windows=True)) engine.signal_connect('focus', self._focus) engine.signal_connect('configure', partial(self._configure, manage_windows=True)) engine.signal_connect('lookup', partial(self._activate_dialog, 'lookup', manage_windows=True)) # Load the configuration (but do not start the engine yet). if not engine.load_config(): self.on_configure() # Apply configuration settings. config = self._engine.config self._configured = False self.dictionaries.on_config_changed(config) self.set_visible(not config['start_minimized']) # Start the engine. engine.start()
def main(): """Launch plover.""" description = "Run the plover stenotype engine. This is a graphical application." parser = argparse.ArgumentParser(description=description) parser.add_argument('--version', action='version', version='%s %s' % (__software_name__.capitalize(), __version__)) parser.add_argument('-s', '--script', default=None, nargs=argparse.REMAINDER, help='use another plugin console script as main entrypoint, ' 'passing in the rest of the command line arguments, ' 'print list of available scripts when no argument is given') parser.add_argument('-l', '--log-level', choices=['debug', 'info', 'warning', 'error'], default=None, help='set log level') parser.add_argument('-g', '--gui', default=None, help='set gui') args = parser.parse_args(args=sys.argv[1:]) if args.log_level is not None: log.set_level(args.log_level.upper()) log.setup_platform_handler() log.info('Plover %s', __version__) log.info('configuration directory: %s', CONFIG_DIR) registry.update() if args.gui is None: gui_priority = { 'qt': 1, 'none': -1, } gui_list = sorted(registry.list_plugins('gui'), reverse=True, key=lambda gui: gui_priority.get(gui.name, 0)) gui = gui_list[0].obj else: gui = registry.get_plugin('gui', args.gui).obj try: if args.script is not None: if args.script: # Create a mapping of available console script, # with the following priorities (highest first): # - {project_name}-{version}:{script_name} # - {project_name}:{script_name} # - {script_name} console_scripts = {} for e in sorted(pkg_resources.iter_entry_points('console_scripts'), key=lambda e: (e.dist, e.name)): for key in ( '%s-%s:%s' % (e.dist.project_name, e.dist.version, e.name), '%s:%s' % (e.dist.project_name, e.name), e.name, ): console_scripts[key] = e entrypoint = console_scripts.get(args.script[0]) if entrypoint is None: log.error('no such script: %s', args.script[0]) code = 1 else: sys.argv = args.script try: code = entrypoint.load()() except SystemExit as e: code = e.code if code is None: code = 0 else: print('available script(s):') dist = None for e in sorted(pkg_resources.iter_entry_points('console_scripts'), key=lambda e: (str(e.dist), e.name)): if dist != e.dist: dist = e.dist print('%s:' % dist) print('- %s' % e.name) code = 0 os._exit(code) # Ensure only one instance of Plover is running at a time. with processlock.PloverLock(): if sys.platform.startswith('darwin'): appnope.nope() init_config_dir() # This must be done after calling init_config_dir, so # Plover's configuration directory actually exists. log.setup_logfile() config = Config() config.target_file = CONFIG_FILE code = gui.main(config) with open(config.target_file, 'wb') as f: config.save(f) except processlock.LockNotAcquiredException: gui.show_error('Error', 'Another instance of Plover is already running.') code = 1 except: gui.show_error('Unexpected error', traceback.format_exc()) code = 2 if code == -1: # Restart. args = sys.argv[:] if args[0].endswith('.py') or args[0].endswith('.pyc'): # We're running from source. assert args[0] == __file__ args[0:1] = [sys.executable, '-m', __spec__.name] # Execute atexit handlers. atexit._run_exitfuncs() if sys.platform.startswith('win32'): # Workaround https://bugs.python.org/issue19066 subprocess.Popen(args, cwd=os.getcwd()) code = 0 else: os.execv(args[0], args) os._exit(code)
def _atom_to_action(atom, ctx): """Convert an atom into an action. Arguments: atom -- A string holding an atom. An atom is an irreducible string that is either entirely a single meta command or entirely text containing no meta commands. last_action -- The context in which the new action takes place. Returns: An action for the atom. """ meta = _get_meta(atom) if meta is not None: meta = _unescape_atom(meta) if meta in META_COMMAS: action = _apply_meta_comma(meta, ctx) elif meta in META_STOPS: action = _apply_meta_stop(meta, ctx) elif meta == META_CAPITALIZE: action = _apply_meta_case(CASE_CAP_FIRST_WORD, ctx) elif meta == META_LOWER: action = _apply_meta_case(CASE_LOWER_FIRST_CHAR, ctx) elif meta == META_UPPER: action = _apply_meta_case(CASE_UPPER_FIRST_WORD, ctx) elif meta == META_RETRO_CAPITALIZE: action = _apply_meta_retro_case(CASE_CAP_FIRST_WORD, ctx) elif meta == META_RETRO_LOWER: action = _apply_meta_retro_case(CASE_LOWER_FIRST_CHAR, ctx) elif meta == META_RETRO_UPPER: action = _apply_meta_retro_case(CASE_UPPER_FIRST_WORD, ctx) elif (meta.startswith(META_CARRY_CAPITALIZATION) or meta.startswith(META_ATTACH_FLAG + META_CARRY_CAPITALIZATION)): action = _apply_meta_carry_capitalize(meta, ctx) elif meta.startswith(META_RETRO_FORMAT): action = _apply_meta_currency(meta, ctx) elif meta.startswith(META_COMMAND): action = _apply_meta_command(meta, ctx) elif meta.startswith(META_MODE): action = _apply_meta_mode(meta, ctx) elif meta.startswith(META_GLUE_FLAG): action = _apply_meta_glue(meta, ctx) elif (meta.startswith(META_ATTACH_FLAG) or meta.endswith(META_ATTACH_FLAG)): action = _apply_meta_attach(meta, ctx) elif meta.startswith(META_KEY_COMBINATION): action = _apply_meta_combo(meta, ctx) elif meta.startswith(META_CUSTOM): meta_args = meta[1:].split(':', 1) meta_fn = registry.get_plugin('meta', meta_args[0]).obj action = meta_fn(ctx, meta_args[1] if len(meta_args) == 2 else '') else: action = ctx.new_action() else: action = ctx.new_action() action.text = _unescape_atom(atom) # Finalize action's text. text = action.text if text is not None: # Update word. if action.word is None: last_word = None if action.glue and ctx.last_action.glue: last_word = ctx.last_action.word action.word = _rightmost_word((last_word or '') + text) # Apply case. case = ctx.last_action.next_case if case is None and action.prev_attach and ctx.last_action.upper_carry: case = CASE_UPPER_FIRST_WORD text = _apply_case(text, case) if case == CASE_UPPER_FIRST_WORD: action.upper_carry = not _has_word_boundary(text) # Apply mode. action.text = _apply_mode(text, action.case, action.space_char, action.prev_attach, ctx.last_action) # Update trailing space. action.trailing_space = '' if action.next_attach else action.space_char
def _update(self, config_update=None, full=False, reset_machine=False): original_config = self._config.as_dict() # Update configuration. if config_update is not None: self._config.update(**config_update) config = self._config.as_dict() else: config = original_config # Create configuration update. if full: config_update = config else: config_update = {option: value for option, value in config.items() if value != original_config[option]} if "machine_type" in config_update: for opt in ("machine_specific_options", "system_keymap"): config_update[opt] = config[opt] # Update logging. log.set_stroke_filename(config["log_file_name"]) log.enable_stroke_logging(config["enable_stroke_logging"]) log.enable_translation_logging(config["enable_translation_logging"]) # Update output. self._formatter.set_space_placement(config["space_placement"]) self._formatter.start_attached = config["start_attached"] self._formatter.start_capitalized = config["start_capitalized"] self._translator.set_min_undo_length(config["undo_levels"]) # Update system. system_name = config["system_name"] if system.NAME != system_name: log.info("loading system: %s", system_name) system.setup(system_name) # Update machine. update_keymap = False start_machine = False machine_params = MachineParams( config["machine_type"], config["machine_specific_options"], config["system_keymap"] ) if reset_machine or machine_params != self._machine_params: if self._machine is not None: self._machine.stop_capture() self._machine = None machine_type = config["machine_type"] machine_options = config["machine_specific_options"] try: machine_class = registry.get_plugin("machine", machine_type).resolve() except Exception as e: raise InvalidConfigurationError(str(e)) log.info("setting machine: %s", machine_type) self._machine = machine_class(machine_options) self._machine.set_suppression(self._is_running) self._machine.add_state_callback(self._machine_state_callback) self._machine.add_stroke_callback(self._machine_stroke_callback) self._machine_params = machine_params update_keymap = True start_machine = True elif self._machine is not None: update_keymap = "system_keymap" in config_update if update_keymap: machine_keymap = config["system_keymap"] if machine_keymap is not None: self._machine.set_keymap(machine_keymap) if start_machine: self._machine.start_capture() # Update dictionaries. dictionaries_files = config["dictionary_file_names"] copy_default_dictionaries(dictionaries_files) dictionaries = self._dictionaries_manager.load(dictionaries_files) self._dictionaries.set_dicts(dictionaries) # Trigger `config_changed` hook. if config_update: self._trigger_hook("config_changed", config_update)
def main(): """Launch plover.""" description = "Run the plover stenotype engine. This is a graphical application." parser = argparse.ArgumentParser(description=description) parser.add_argument('--version', action='version', version='%s %s' % (__software_name__.capitalize(), __version__)) parser.add_argument( '-s', '--script', default=None, nargs=argparse.REMAINDER, help='use another plugin console script as main entrypoint, ' 'passing in the rest of the command line arguments, ' 'print list of available scripts when no argument is given') parser.add_argument('-l', '--log-level', choices=['debug', 'info', 'warning', 'error'], default=None, help='set log level') parser.add_argument('-g', '--gui', default=None, help='set gui') args = parser.parse_args(args=sys.argv[1:]) if args.log_level is not None: log.set_level(args.log_level.upper()) log.setup_platform_handler() log.info('Plover %s', __version__) log.info('configuration directory: %s', CONFIG_DIR) if PLATFORM == 'mac': # Fixes PyQt issue on macOS Big Sur. os.environ['QT_MAC_WANTS_LAYER'] = '1' registry.update() if args.gui is None: gui_priority = { 'qt': 1, 'none': -1, } gui_list = sorted(registry.list_plugins('gui'), reverse=True, key=lambda gui: gui_priority.get(gui.name, 0)) gui = gui_list[0].obj else: gui = registry.get_plugin('gui', args.gui).obj try: if args.script is not None: if args.script: # Create a mapping of available console script, # with the following priorities (highest first): # - {project_name}-{version}:{script_name} # - {project_name}:{script_name} # - {script_name} console_scripts = {} for e in sorted( pkg_resources.iter_entry_points('console_scripts'), key=lambda e: (e.dist, e.name)): for key in ( '%s-%s:%s' % (e.dist.project_name, e.dist.version, e.name), '%s:%s' % (e.dist.project_name, e.name), e.name, ): console_scripts[key] = e entrypoint = console_scripts.get(args.script[0]) if entrypoint is None: log.error('no such script: %s', args.script[0]) code = 1 else: sys.argv = args.script try: code = entrypoint.load()() except SystemExit as e: code = e.code if code is None: code = 0 else: print('available script(s):') dist = None for e in sorted( pkg_resources.iter_entry_points('console_scripts'), key=lambda e: (str(e.dist), e.name)): if dist != e.dist: dist = e.dist print('%s:' % dist) print('- %s' % e.name) code = 0 os._exit(code) # Ensure only one instance of Plover is running at a time. with Controller() as controller: if controller.is_owner: # Not other instance, regular startup. if PLATFORM == 'mac': import appnope appnope.nope() init_config_dir() # This must be done after calling init_config_dir, so # Plover's configuration directory actually exists. log.setup_logfile() config = Config(CONFIG_FILE) code = gui.main(config, controller) else: log.info( 'another instance is running, sending `focus` command') # Other instance? Try focusing the main window. try: controller.send_command('focus') except ConnectionRefusedError: log.error('connection to existing instance failed, ' 'force cleaning before restart') # Assume the previous instance died, leaving # a stray socket, try cleaning it... if not controller.force_cleanup(): raise # ...and restart. code = -1 else: code = 0 except: gui.show_error('Unexpected error', traceback.format_exc()) code = 2 if code == -1: # Restart. args = sys.argv[:] if args[0].endswith('.py') or args[0].endswith('.pyc'): # We're running from source. spec = sys.modules['__main__'].__spec__ assert sys.argv[0] == spec.origin args[0:1] = [sys.executable, '-m', spec.name] # Execute atexit handlers. atexit._run_exitfuncs() if PLATFORM == 'win': # Workaround https://bugs.python.org/issue19066 subprocess.Popen(args, cwd=os.getcwd()) code = 0 else: os.execv(args[0], args) os._exit(code)
def validate(config, key, value): try: return registry.get_plugin(plugin_type, value).name except KeyError as e: raise InvalidConfigOption(value, default) from e
def default(config, key): machine_class = registry.get_plugin('machine', key[1]).obj return { name: params[0] for name, params in machine_class.get_option_info().items() }
def _update(self, config_update=None, full=False, reset_machine=False): original_config = self._config.as_dict() # Update configuration. if config_update is not None: self._config.update(**config_update) config = self._config.as_dict() else: config = original_config # Create configuration update. if full: config_update = config else: config_update = { option: value for option, value in config.items() if value != original_config[option] } if 'machine_type' in config_update: for opt in ( 'machine_specific_options', 'system_keymap', ): config_update[opt] = config[opt] # Update logging. log.set_stroke_filename(config['log_file_name']) log.enable_stroke_logging(config['enable_stroke_logging']) log.enable_translation_logging(config['enable_translation_logging']) # Update output. self._formatter.set_space_placement(config['space_placement']) self._formatter.start_attached = config['start_attached'] self._formatter.start_capitalized = config['start_capitalized'] self._translator.set_min_undo_length(config['undo_levels']) # Update system. system_name = config['system_name'] if system.NAME != system_name: log.info('loading system: %s', system_name) system.setup(system_name) # Update machine. update_keymap = False start_machine = False machine_params = MachineParams(config['machine_type'], config['machine_specific_options'], config['system_keymap']) # Do not reset if only the keymap changed. if self._machine_params is None or \ self._machine_params.type != machine_params.type or \ self._machine_params.options != machine_params.options: reset_machine = True if reset_machine: if self._machine is not None: self._machine.stop_capture() self._machine = None machine_type = config['machine_type'] machine_options = config['machine_specific_options'] try: machine_class = registry.get_plugin('machine', machine_type).obj except Exception as e: raise InvalidConfigurationError(str(e)) log.info('setting machine: %s', machine_type) self._machine = machine_class(machine_options) self._machine.set_suppression(self._is_running) self._machine.add_state_callback(self._machine_state_callback) self._machine.add_stroke_callback(self._machine_stroke_callback) self._machine_params = machine_params update_keymap = True start_machine = True elif self._machine is not None: update_keymap = 'system_keymap' in config_update if update_keymap: machine_keymap = config['system_keymap'] if machine_keymap is not None: self._machine.set_keymap(machine_keymap) if start_machine: self._machine.start_capture() # Update running extensions. enabled_extensions = config['enabled_extensions'] running_extensions = set(self._running_extensions) self._stop_extensions(running_extensions - enabled_extensions) self._start_extensions(enabled_extensions - running_extensions) # Trigger `config_changed` hook. if config_update: self._trigger_hook('config_changed', config_update) # Update dictionaries. config_dictionaries = OrderedDict( (d.path, d) for d in config['dictionaries'] ) copy_default_dictionaries(config_dictionaries.keys()) # Start by unloading outdated dictionaries. self._dictionaries_manager.unload_outdated() self._set_dictionaries([ d for d in self._dictionaries.dicts if d.path in config_dictionaries and \ d.path in self._dictionaries_manager ]) # And then (re)load all dictionaries. dictionaries = [] for result in self._dictionaries_manager.load(config_dictionaries.keys()): if isinstance(result, DictionaryLoaderException): d = ErroredDictionary(result.path, result.exception) # Only show an error if it's new. if d != self._dictionaries.get(result.path): log.error('loading dictionary `%s` failed: %s', shorten_path(result.path), str(result.exception)) else: d = result d.enabled = config_dictionaries[d.path].enabled dictionaries.append(d) self._set_dictionaries(dictionaries)
def _atom_to_action(atom, ctx): """Convert an atom into an action. Arguments: atom -- A string holding an atom. An atom is an irreducible string that is either entirely a single meta command or entirely text containing no meta commands. last_action -- The context in which the new action takes place. Returns: An action for the atom. """ meta = _get_meta(atom) if meta is not None: meta = _unescape_atom(meta) if meta in META_COMMAS: action = _apply_meta_comma(meta, ctx) elif meta in META_STOPS: action = _apply_meta_stop(meta, ctx) elif meta == META_CAPITALIZE: action = _apply_meta_case(CASE_CAP_FIRST_WORD, ctx) elif meta == META_LOWER: action = _apply_meta_case(CASE_LOWER_FIRST_CHAR, ctx) elif meta == META_UPPER: action = _apply_meta_case(CASE_UPPER_FIRST_WORD, ctx) elif meta == META_RETRO_CAPITALIZE: action = _apply_meta_retro_case(CASE_CAP_FIRST_WORD, ctx) elif meta == META_RETRO_LOWER: action = _apply_meta_retro_case(CASE_LOWER_FIRST_CHAR, ctx) elif meta == META_RETRO_UPPER: action = _apply_meta_retro_case(CASE_UPPER_FIRST_WORD, ctx) elif (meta.startswith(META_CARRY_CAPITALIZATION) or meta.startswith(META_ATTACH_FLAG + META_CARRY_CAPITALIZATION)): action = _apply_meta_carry_capitalize(meta, ctx) elif meta.startswith(META_RETRO_FORMAT): action = _apply_meta_currency(meta, ctx) elif meta.startswith(META_COMMAND): action = _apply_meta_command(meta, ctx) elif meta.startswith(META_MODE): action = _apply_meta_mode(meta, ctx) elif meta.startswith(META_GLUE_FLAG): action = _apply_meta_glue(meta, ctx) elif (meta.startswith(META_ATTACH_FLAG) or meta.endswith(META_ATTACH_FLAG)): action = _apply_meta_attach(meta, ctx) elif meta.startswith(META_KEY_COMBINATION): action = _apply_meta_combo(meta, ctx) elif meta.startswith(META_CUSTOM): meta_args = meta[1:].split(':', 1) meta_fn = registry.get_plugin('meta', meta_args[0]).obj action = meta_fn(ctx, meta_args[1] if len(meta_args) == 2 else '') else: action = ctx.new_action() else: action = ctx.new_action() action.text = _unescape_atom(atom) # Finalize action's text. text = action.text if text is not None: # Update word. if action.word is None: last_word = None if action.glue and ctx.last_action.glue: last_word = ctx.last_action.word action.word = _rightmost_word((last_word or '') + text) # Apply case. case = ctx.last_action.next_case if case is None and action.prev_attach and ctx.last_action.upper_carry: case = CASE_UPPER_FIRST_WORD text = _apply_case(text, case) if case == CASE_UPPER_FIRST_WORD: action.upper_carry = not _has_word_boundary(text) # Apply mode. action.text = _apply_mode(text, action.case, action.space_char, action.prev_attach, ctx.last_action) # Update trailing space. action.trailing_space = '' if action.next_attach else action.space_char return action