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__)) args = parser.parse_args(args=sys.argv[1:]) try: # Ensure only one instance of Plover is running at a time. with plover.oslayer.processlock.PloverLock(): 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 gui = plover.gui.main.PloverGUI(config) gui.MainLoop() with open(config.target_file, 'wb') as f: config.save(f) except plover.oslayer.processlock.LockNotAcquiredException: show_error('Error', 'Another instance of Plover is already running.') except: show_error('Unexpected error', traceback.format_exc()) os._exit(1)
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='warning', help='set log level') args = parser.parse_args(args=sys.argv[1:]) log.set_level(args.log_level.upper()) try: # Ensure only one instance of Plover is running at a time. with plover.oslayer.processlock.PloverLock(): 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 gui = plover.gui.main.PloverGUI(config) gui.MainLoop() with open(config.target_file, 'wb') as f: config.save(f) except plover.oslayer.processlock.LockNotAcquiredException: show_error('Error', 'Another instance of Plover is already running.') except: show_error('Unexpected error', traceback.format_exc()) os._exit(1)
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') args = parser.parse_args(args=sys.argv[1:]) if args.log_level is not None: log.set_level(args.log_level.upper()) 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 gui = plover.gui.main.PloverGUI(config) gui.MainLoop() with open(config.target_file, 'wb') as f: config.save(f) except plover.oslayer.processlock.LockNotAcquiredException: show_error('Error', 'Another instance of Plover is already running.') except: show_error('Unexpected error', traceback.format_exc()) os._exit(1)
def run(self): self.run_command('bdist_app') args = '{out!r}, {name!r}, {settings!r}, lookForHiDPI=True'.format( out='dist/%s.dmg' % PACKAGE, name=__software_name__.capitalize(), settings='osx/dmg_resources/settings.py', ) log.info('running dmgbuild(%s)', args) script = "__import__('dmgbuild').build_dmg(" + args + ')' subprocess.check_call((sys.executable, '-u', '-c', script))
def emit(self, record): # Notification Center has no levels or timeouts. notification = NSUserNotification.alloc().init() notification.setTitle_(__software_name__.capitalize()) notification.setSubtitle_(record.levelname.title()) notification.setInformativeText_(self.format(record)) ns = NSUserNotificationCenter.defaultUserNotificationCenter() ns.setDelegate_(always_present_delegator) ns.deliverNotification_(notification)
def _show_about_dialog(self, event=None): """Called when the About... button is clicked.""" info = wx.AboutDialogInfo() info.Name = __software_name__.capitalize() info.Version = __version__ info.Copyright = __copyright__ info.Description = __long_description__ info.WebSite = __url__ info.Developers = __credits__ info.License = __license__ wx.AboutBox(info)
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') gui_choices = { 'none': 'plover.gui_none.main', 'qt': 'plover.gui_qt.main', } gui_default = 'qt' parser.add_argument('-g', '--gui', choices=gui_choices, default=gui_default, 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()) if not args.gui in gui_choices: raise ValueError('invalid gui: %r' % args.gui) gui = importlib.import_module(gui_choices[args.gui]) 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 _notify(level, message): nm = wx.NotificationMessage() nm.SetTitle(__software_name__.capitalize()) nm.SetMessage(message) if level <= log.INFO: flags = wx.ICON_INFORMATION elif level <= log.WARNING: flags = wx.ICON_WARNING else: flags = wx.ICON_ERROR nm.SetFlags(flags) nm.Show()
def pyinstaller(*args): py_args = [ '--log-level=INFO', '--specpath=build', '--additional-hooks-dir=windows', '--paths=.', '--name=%s-%s' % ( __software_name__.capitalize(), __version__, ), '--noconfirm', '--windowed', '--onefile' ] py_args.extend(args) py_args.append('windows/main.py') main = pkg_resources.load_entry_point('PyInstaller', 'console_scripts', 'pyinstaller') main(py_args)
def run(self): self.run_command('bdist_app') subprocess.check_call((sys.executable, '-c', textwrap.dedent(''' __import__('dmgbuild').build_dmg( {output!r}, {name!r}, {settings!r}, **{options!r}, ) ''').format( output='dist/%s.dmg' % PACKAGE, name=__software_name__.capitalize(), settings='osx/dmg_resources/settings.py', options=dict(lookForHiDPI=True), )))
def run(self): self.run_command('bdist_app') subprocess.check_call((sys.executable, '-c', textwrap.dedent( ''' __import__('dmgbuild').build_dmg( {output!r}, {name!r}, {settings!r}, **{options!r}, ) ''' ).format( output='dist/%s.dmg' % PACKAGE, name=__software_name__.capitalize(), settings='osx/dmg_resources/settings.py', options=dict(lookForHiDPI=True), )))
def emit(self, record): level = record.levelno message = self.format(record) if level <= log.INFO: timeout = 10 urgency = 0 elif level <= log.WARNING: timeout = 15 urgency = 1 else: timeout = 0 urgency = 2 n = pynotify.Notification(__software_name__.capitalize(), message) n.set_timeout(timeout * 1000) n.set_urgency(urgency) n.show()
class Launch(Command): description = 'run %s from source' % __software_name__.capitalize() command_consumes_arguments = True user_options = [] def initialize_options(self): self.args = None def finalize_options(self): pass def run(self): with self.project_on_sys_path(): from plover.main import main sys.argv = [' '.join(sys.argv[0:2]) + ' --'] + self.args sys.exit(main())
class Launch(setuptools.Command): description = 'run %s from source' % __software_name__.capitalize() command_consumes_arguments = True user_options = [] def initialize_options(self): self.args = None def finalize_options(self): pass def run(self): # Make sure metadata are up-to-date first. self.run_command('egg_info') reload(pkg_resources) from plover.main import main sys.argv = [' '.join(sys.argv[0:2]) + ' --'] + self.args main()
def __init__(self, config, controller, use_qt_notifications): # This is done dynamically so localization # support can be configure beforehand. from plover.gui_qt.main_window import MainWindow self._app = None self._win = None self._engine = None self._translator = None QCoreApplication.setApplicationName(__software_name__.capitalize()) QCoreApplication.setApplicationVersion(__version__) QCoreApplication.setOrganizationName('Open Steno Project') QCoreApplication.setOrganizationDomain('openstenoproject.org') self._app = QApplication([]) self._app.setAttribute(Qt.AA_UseHighDpiPixmaps) # Enable localization of standard Qt controls. log.info('setting language to: %s', _.lang) self._translator = QTranslator() translations_dir = QLibraryInfo.location(QLibraryInfo.TranslationsPath) self._translator.load('qtbase_' + _.lang, translations_dir) self._app.installTranslator(self._translator) QApplication.setQuitOnLastWindowClosed(False) self._app.engine = self._engine = Engine(config, controller, KeyboardEmulation()) # On macOS, quitting through the dock will result # in a direct call to `QCoreApplication.quit`. self._app.aboutToQuit.connect(self._app.engine.quit) signal.signal(signal.SIGINT, lambda signum, stack: self._engine.quit()) # Make sure the Python interpreter runs at least every second, # so signals have a chance to be processed. self._timer = QTimer() self._timer.timeout.connect(lambda: None) self._timer.start(1000) self._win = MainWindow(self._engine, use_qt_notifications)
class Launch(Command): description = 'run %s from source' % __software_name__.capitalize() command_consumes_arguments = True user_options = [] def initialize_options(self): self.args = None def finalize_options(self): pass def run(self): self.run_command('egg_info') python_path = os.environ.get('PYTHONPATH', '').split(os.pathsep) python_path.insert(0, os.getcwd()) os.environ['PYTHONPATH'] = os.pathsep.join(python_path) os.execv(sys.executable, [sys.executable, '-m', 'plover.main'] + self.args)
def __init__(self, config, use_qt_notifications): # This is done dynamically so localization # support can be configure beforehand. from plover.gui_qt.main_window import MainWindow self._app = None self._win = None self._engine = None self._translator = None QCoreApplication.setApplicationName(__software_name__.capitalize()) QCoreApplication.setApplicationVersion(__version__) QCoreApplication.setOrganizationName('Open Steno Project') QCoreApplication.setOrganizationDomain('openstenoproject.org') self._app = QApplication([]) self._app.setAttribute(Qt.AA_UseHighDpiPixmaps) # Enable localization of standard Qt controls. self._translator = QTranslator() translations_dir = QLibraryInfo.location(QLibraryInfo.TranslationsPath) self._translator.load('qtbase_' + get_language(), translations_dir) self._app.installTranslator(self._translator) QApplication.setQuitOnLastWindowClosed(False) self._app.engine = self._engine = Engine(config, KeyboardEmulation()) # On macOS, quitting through the dock will result # in a direct call to `QCoreApplication.quit`. self._app.aboutToQuit.connect(self._app.engine.quit) signal.signal(signal.SIGINT, lambda signum, stack: self._engine.quit()) # Make sure the Python interpreter runs at least every second, # so signals have a chance to be processed. self._timer = QTimer() self._timer.timeout.connect(lambda: None) self._timer.start(1000) self._win = MainWindow(self._engine, use_qt_notifications)
class Launch(Command): description = 'run %s from source' % __software_name__.capitalize() command_consumes_arguments = True user_options = [] def initialize_options(self): self.args = None def finalize_options(self): pass def run(self): with self.project_on_sys_path(): python_path = os.environ.get('PYTHONPATH', '').split(os.pathsep) python_path.insert(0, sys.path[0]) os.environ['PYTHONPATH'] = os.pathsep.join(python_path) cmd = [sys.executable, '-m', 'plover.main'] + self.args if sys.platform.startswith('win32'): # Workaround https://bugs.python.org/issue19066 subprocess.Popen(cmd, cwd=os.getcwd()) sys.exit(0) os.execv(cmd[0], cmd)
def log(self, level, message): if self._enabled: if level <= log.INFO: icon = QSystemTrayIcon.Information timeout = 10 elif level <= log.WARNING: icon = QSystemTrayIcon.Warning timeout = 15 else: icon = QSystemTrayIcon.Critical timeout = 25 self._trayicon.showMessage(__software_name__.capitalize(), message, icon, timeout * 1000) else: if level <= log.INFO: icon = QMessageBox.Information elif level <= log.WARNING: icon = QMessageBox.Warning else: icon = QMessageBox.Critical msgbox = QMessageBox() msgbox.setText(message) msgbox.setIcon(icon) msgbox.exec_()
class MainFrame(wx.Frame): """The top-level GUI element of the Plover application.""" # Class constants. TITLE = __software_name__.capitalize() ALERT_DIALOG_TITLE = TITLE CONNECTED_IMAGE_FILE = os.path.join(ASSETS_DIR, 'connected.png') DISCONNECTED_IMAGE_FILE = os.path.join(ASSETS_DIR, 'disconnected.png') REFRESH_IMAGE_FILE = os.path.join(ASSETS_DIR, 'refresh.png') PLOVER_ICON_FILE = os.path.join(ASSETS_DIR, 'plover.ico') BORDER = 5 STATUS_DISCONNECTED, STATUS_OUTPUT_DISABLED, STATUS_OUTPUT_ENABLED = range( -1, 2) ENABLE_OUTPUT_LABEL = "Enable" DISABLE_OUTPUT_LABEL = "Disable" HEADER_OUTPUT = "Output" HEADER_MACHINE = "Machine" HEADER_SETTINGS = "Settings" CONFIGURE_BUTTON_LABEL = u"Configure…" ABOUT_BUTTON_LABEL = u"About…" RECONNECT_BUTTON_LABEL = u"Reconnect…" COMMAND_SUSPEND = 'SUSPEND' COMMAND_ADD_TRANSLATION = 'ADD_TRANSLATION' COMMAND_LOOKUP = 'LOOKUP' COMMAND_RESUME = 'RESUME' COMMAND_TOGGLE = 'TOGGLE' COMMAND_CONFIGURE = 'CONFIGURE' COMMAND_FOCUS = 'FOCUS' COMMAND_QUIT = 'QUIT' def __init__(self, config): self.config = config # Note: don't set position from config, since it's not yet loaded. wx.Frame.__init__( self, None, title=self.TITLE, style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.RESIZE_BOX | wx.MAXIMIZE_BOX)) root = wx.Panel(self, style=wx.DEFAULT_FRAME_STYLE) # Menu Bar MenuBar = wx.MenuBar() self.SetMenuBar(MenuBar) # Application icon icon = wx.Icon(self.PLOVER_ICON_FILE, wx.BITMAP_TYPE_ICO) self.SetIcon(icon) # Configure button. self.configure_button = wx.Button(root, label=self.CONFIGURE_BUTTON_LABEL) self.configure_button.Bind(wx.EVT_BUTTON, self._show_config_dialog) # About button. self.about_button = wx.Button(root, label=self.ABOUT_BUTTON_LABEL) self.about_button.Bind(wx.EVT_BUTTON, self._show_about_dialog) # Status radio buttons. self.radio_output_enable = wx.RadioButton( root, label=self.ENABLE_OUTPUT_LABEL) self.radio_output_enable.Bind( wx.EVT_RADIOBUTTON, lambda e: self.steno_engine.set_is_running(True)) self.radio_output_disable = wx.RadioButton( root, label=self.DISABLE_OUTPUT_LABEL) self.radio_output_disable.Bind( wx.EVT_RADIOBUTTON, lambda e: self.steno_engine.set_is_running(False)) # Machine status. # TODO: Figure out why spinner has darker gray background. self.spinner = wx.animate.GIFAnimationCtrl(root, -1, SPINNER_FILE) self.spinner.GetPlayer().UseBackgroundColour(True) # Need to call this so the size of the control is not # messed up (100x100 instead of 16x16) on Linux... self.spinner.InvalidateBestSize() self.spinner.Hide() self.connected_bitmap = wx.Bitmap(self.CONNECTED_IMAGE_FILE, wx.BITMAP_TYPE_PNG) self.disconnected_bitmap = wx.Bitmap(self.DISCONNECTED_IMAGE_FILE, wx.BITMAP_TYPE_PNG) self.connection_ctrl = wx.StaticBitmap(root, bitmap=self.disconnected_bitmap) border_flag = wx.SizerFlags(1).Border(wx.ALL, self.BORDER) # Create Settings Box settings_sizer = wx.BoxSizer(wx.HORIZONTAL) settings_sizer.AddF(self.configure_button, border_flag.Expand()) settings_sizer.AddF(self.about_button, border_flag.Expand()) # Create Output Status Box box = wx.StaticBox(root, label=self.HEADER_OUTPUT) status_sizer = wx.StaticBoxSizer(box, wx.HORIZONTAL) status_sizer.AddF(self.radio_output_enable, border_flag) status_sizer.AddF(self.radio_output_disable, border_flag) # Create Machine Status Box machine_sizer = wx.BoxSizer(wx.HORIZONTAL) center_flag =\ wx.SizerFlags()\ .Align(wx.ALIGN_CENTER_VERTICAL)\ .Border(wx.LEFT | wx.RIGHT, self.BORDER) machine_sizer.AddF(self.spinner, center_flag) machine_sizer.AddF(self.connection_ctrl, center_flag) longest_machine = max(machine_registry.get_all_names(), key=len) longest_state = max((STATE_ERROR, STATE_INITIALIZING, STATE_RUNNING), key=len) longest_machine_status = '%s: %s' % (longest_machine, longest_state) self.machine_status_text = wx.StaticText(root, label=longest_machine_status) machine_sizer.AddF(self.machine_status_text, center_flag) refresh_bitmap = wx.Bitmap(self.REFRESH_IMAGE_FILE, wx.BITMAP_TYPE_PNG) self.reconnect_button = wx.BitmapButton(root, bitmap=refresh_bitmap) machine_sizer.AddF(self.reconnect_button, center_flag) # Assemble main UI global_sizer = wx.GridBagSizer(vgap=self.BORDER, hgap=self.BORDER) global_sizer.Add(settings_sizer, flag=wx.EXPAND, pos=(0, 0), span=(1, 2)) global_sizer.Add(status_sizer, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=self.BORDER, pos=(1, 0), span=(1, 2)) global_sizer.Add(machine_sizer, flag=wx.CENTER | wx.ALIGN_CENTER | wx.EXPAND | wx.LEFT, pos=(2, 0), border=self.BORDER, span=(1, 2)) self.machine_sizer = machine_sizer # Add a border around the entire sizer. border = wx.BoxSizer() border.AddF(global_sizer, wx.SizerFlags(1).Border(wx.ALL, self.BORDER).Expand()) root.SetSizerAndFit(border) border.SetSizeHints(self) self.Bind(wx.EVT_CLOSE, self._quit) self.Bind(wx.EVT_MOVE, self.on_move) self.reconnect_button.Bind(wx.EVT_BUTTON, lambda e: self._reconnect()) try: with open(config.target_file, 'rb') as f: self.config.load(f) except Exception: log.error('loading configuration failed, reseting to default', exc_info=True) self.config.clear() rect = wx.Rect(config.get_main_frame_x(), config.get_main_frame_y(), *self.GetSize()) self.SetRect(AdjustRectToScreen(rect)) self.steno_engine = app.StenoEngine() self.steno_engine.add_callback( lambda s: wx.CallAfter(self._update_status, s)) self.steno_engine.set_output( Output(self.consume_command, self.steno_engine)) self.steno_engine.add_stroke_listener( StrokeDisplayDialog.stroke_handler) if self.config.get_show_stroke_display(): StrokeDisplayDialog.display(self, self.config) self.steno_engine.formatter.add_listener( SuggestionsDisplayDialog.stroke_handler) if self.config.get_show_suggestions_display(): SuggestionsDisplayDialog.display(self, self.config, self.steno_engine) try: app.init_engine(self.steno_engine, self.config) except Exception: log.error('engine initialization failed', exc_info=True) self._show_config_dialog() def _reconnect(self): try: app.reset_machine(self.steno_engine, self.config) except Exception: log.error('machine reset failed', exc_info=True) def consume_command(self, command): # The first commands can be used whether plover has output enabled or not. if command == self.COMMAND_RESUME: wx.CallAfter(self.steno_engine.set_is_running, True) return True elif command == self.COMMAND_TOGGLE: wx.CallAfter(self.steno_engine.set_is_running, not self.steno_engine.is_running) return True elif command == self.COMMAND_QUIT: wx.CallAfter(self._quit) return True if not self.steno_engine.is_running: return False # These commands can only be run when plover has output enabled. if command == self.COMMAND_SUSPEND: wx.CallAfter(self.steno_engine.set_is_running, False) return True elif command == self.COMMAND_CONFIGURE: wx.CallAfter(self._show_config_dialog) return True elif command == self.COMMAND_FOCUS: def f(): self.Raise() self.Iconize(False) wx.CallAfter(f) return True elif command == self.COMMAND_ADD_TRANSLATION: wx.CallAfter(plover.gui.add_translation.Show, self, self.steno_engine, self.config) return True elif command == self.COMMAND_LOOKUP: wx.CallAfter(plover.gui.lookup.Show, self, self.steno_engine, self.config) return True return False def _update_status(self, state): if state: machine_name = machine_registry.resolve_alias( self.config.get_machine_type()) self.machine_status_text.SetLabel('%s: %s' % (machine_name, state)) self.spinner.Show(state == STATE_INITIALIZING) self.connection_ctrl.Show(state != STATE_INITIALIZING) if state == STATE_INITIALIZING: self.spinner.Play() else: self.spinner.Stop() if state == STATE_RUNNING: self.connection_ctrl.SetBitmap(self.connected_bitmap) elif state == STATE_ERROR: self.connection_ctrl.SetBitmap(self.disconnected_bitmap) self.machine_sizer.Layout() if not self.steno_engine.machine or self.steno_engine.machine.state is not STATE_RUNNING: status = self.STATUS_DISCONNECTED elif self.steno_engine.is_running: status = self.STATUS_OUTPUT_ENABLED else: status = self.STATUS_OUTPUT_DISABLED self._show_status(status) def _show_status(self, status): if status is self.STATUS_DISCONNECTED: controls_enabled = False output_enabled = False elif status is self.STATUS_OUTPUT_DISABLED: controls_enabled = True output_enabled = False elif status is self.STATUS_OUTPUT_ENABLED: controls_enabled = True output_enabled = True else: raise ValueError('invalid status: %s' % str(status)) # Enable / Disable controls self.radio_output_disable.Enable(controls_enabled) self.radio_output_enable.Enable(controls_enabled) # Set selected values self.radio_output_disable.SetValue(not output_enabled) self.radio_output_enable.SetValue(output_enabled) def _quit(self, event=None): if self.steno_engine: self.steno_engine.destroy() self.Destroy() def _show_config_dialog(self, event=None): dlg = ConfigurationDialog(self.steno_engine, self.config, parent=self) dlg.Show() def _show_about_dialog(self, event=None): """Called when the About... button is clicked.""" info = wx.AboutDialogInfo() info.Name = __software_name__.capitalize() info.Version = __version__ info.Copyright = __copyright__ info.Description = __long_description__ info.WebSite = __url__ info.Developers = __credits__ info.License = __license__ wx.AboutBox(info) def on_move(self, event): pos = self.GetScreenPositionTuple() self.config.set_main_frame_x(pos[0]) self.config.set_main_frame_y(pos[1]) event.Skip()
import setuptools from plover import ( __name__ as __software_name__, __version__, __description__, __long_description__, __url__, __download_url__, __license__, __copyright__, ) from utils.metadata import copy_metadata package_name = __software_name__.capitalize() def get_version(): if not os.path.exists('.git'): return None version = subprocess.check_output( 'git describe --tags --match=v[0-9]*'.split()).strip() m = re.match(r'^v(\d[\d.]*)(-\d+-g[a-f0-9]*)?$', version) assert m is not None, version version = m.group(1) if m.group(2) is not None: version += '+' + m.group(2)[1:].replace('-', '.') return version
import sys import setuptools setup_requires = [] options = {} kwargs = {} if sys.platform.startswith('darwin'): setup_requires.append('py2app') options['py2app'] = { 'argv_emulation': False, 'iconfile': 'osx/plover.icns', 'resources': 'plover/assets/', 'plist': { 'CFBundleName': __software_name__.capitalize(), 'CFBundleShortVersionString': __version__, 'CFBundleVersion': __version__, 'CFBundleIdentifier': 'org.openstenoproject.plover', 'NSHumanReadableCopyright': __copyright__, 'CFBundleDevelopmentRegion': 'English', } } # Py2app will not look at entry_points. kwargs['app'] = 'launch.py', setuptools.setup( name=__software_name__.capitalize(), version=__version__, description=__description__, long_description=__long_description__,
class MainFrame(wx.Frame): """The top-level GUI element of the Plover application.""" # Class constants. TITLE = __software_name__.capitalize() ALERT_DIALOG_TITLE = TITLE ON_IMAGE_FILE = os.path.join(ASSETS_DIR, 'plover_on.png') OFF_IMAGE_FILE = os.path.join(ASSETS_DIR, 'plover_off.png') CONNECTED_IMAGE_FILE = os.path.join(ASSETS_DIR, 'connected.png') DISCONNECTED_IMAGE_FILE = os.path.join(ASSETS_DIR, 'disconnected.png') REFRESH_IMAGE_FILE = os.path.join(ASSETS_DIR, 'refresh.png') BORDER = 5 RUNNING_MESSAGE = "running" STOPPED_MESSAGE = "stopped" ERROR_MESSAGE = "error" CONFIGURE_BUTTON_LABEL = "Configure..." ABOUT_BUTTON_LABEL = "About..." RECONNECT_BUTTON_LABEL = "Reconnect..." COMMAND_SUSPEND = 'SUSPEND' COMMAND_ADD_TRANSLATION = 'ADD_TRANSLATION' COMMAND_LOOKUP = 'LOOKUP' COMMAND_RESUME = 'RESUME' COMMAND_TOGGLE = 'TOGGLE' COMMAND_CONFIGURE = 'CONFIGURE' COMMAND_FOCUS = 'FOCUS' COMMAND_QUIT = 'QUIT' def __init__(self, config): self.config = config # Note: don't set position from config, since it's not yet loaded. wx.Frame.__init__( self, None, title=self.TITLE, style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.RESIZE_BOX | wx.MAXIMIZE_BOX)) # Status button. self.on_bitmap = wx.Bitmap(self.ON_IMAGE_FILE, wx.BITMAP_TYPE_PNG) self.off_bitmap = wx.Bitmap(self.OFF_IMAGE_FILE, wx.BITMAP_TYPE_PNG) self.status_button = wx.BitmapButton(self, bitmap=self.on_bitmap) self.status_button.Bind(wx.EVT_BUTTON, self._toggle_steno_engine) # Configure button. self.configure_button = wx.Button(self, label=self.CONFIGURE_BUTTON_LABEL) self.configure_button.Bind(wx.EVT_BUTTON, self._show_config_dialog) # Menu Bar MenuBar = wx.MenuBar() self.SetMenuBar(MenuBar) # About button. self.about_button = wx.Button(self, label=self.ABOUT_BUTTON_LABEL) self.about_button.Bind(wx.EVT_BUTTON, self._show_about_dialog) # Machine status. # TODO: Figure out why spinner has darker gray background. self.spinner = wx.animate.GIFAnimationCtrl(self, -1, SPINNER_FILE) self.spinner.GetPlayer().UseBackgroundColour(True) self.spinner.Hide() self.connected_bitmap = wx.Bitmap(self.CONNECTED_IMAGE_FILE, wx.BITMAP_TYPE_PNG) self.disconnected_bitmap = wx.Bitmap(self.DISCONNECTED_IMAGE_FILE, wx.BITMAP_TYPE_PNG) self.connection_ctrl = wx.StaticBitmap(self, bitmap=self.disconnected_bitmap) # Layout. global_sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.HORIZONTAL) sizer.Add(self.status_button, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=self.BORDER) sizer.Add(self.configure_button, flag=wx.TOP | wx.BOTTOM | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, border=self.BORDER) sizer.Add(self.about_button, flag=wx.TOP | wx.BOTTOM | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, border=self.BORDER) global_sizer.Add(sizer) sizer = wx.BoxSizer(wx.HORIZONTAL) sizer.Add(self.spinner, flag=(wx.LEFT | wx.BOTTOM | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL), border=self.BORDER) sizer.Add(self.connection_ctrl, flag=(wx.LEFT | wx.BOTTOM | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL), border=self.BORDER) longest_machine = max(machine_registry.get_all_names(), key=len) longest_state = max((STATE_ERROR, STATE_INITIALIZING, STATE_RUNNING), key=len) longest_status = '%s: %s' % (longest_machine, longest_state) self.machine_status_text = wx.StaticText(self, label=longest_status) sizer.Add(self.machine_status_text, flag=wx.BOTTOM | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, border=self.BORDER) refresh_bitmap = wx.Bitmap(self.REFRESH_IMAGE_FILE, wx.BITMAP_TYPE_PNG) self.reconnect_button = wx.BitmapButton(self, bitmap=refresh_bitmap) sizer.Add(self.reconnect_button, flag=wx.BOTTOM | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, border=self.BORDER) self.machine_status_sizer = sizer global_sizer.Add(sizer) self.SetSizer(global_sizer) global_sizer.Fit(self) self.Bind(wx.EVT_CLOSE, self._quit) self.Bind(wx.EVT_MOVE, self.on_move) self.reconnect_button.Bind( wx.EVT_BUTTON, lambda e: app.reset_machine(self.steno_engine, self.config)) try: with open(config.target_file, 'rb') as f: self.config.load(f) except InvalidConfigurationError as e: self._show_alert(unicode(e)) self.config.clear() rect = wx.Rect(config.get_main_frame_x(), config.get_main_frame_y(), *self.GetSize()) self.SetRect(AdjustRectToScreen(rect)) self.steno_engine = app.StenoEngine() self.steno_engine.add_callback( lambda s: wx.CallAfter(self._update_status, s)) self.steno_engine.set_output( Output(self.consume_command, self.steno_engine)) while True: try: app.init_engine(self.steno_engine, self.config) break except InvalidConfigurationError as e: self._show_alert(unicode(e)) dlg = ConfigurationDialog(self.steno_engine, self.config, parent=self) ret = dlg.ShowModal() if ret == wx.ID_CANCEL: self._quit() return self.steno_engine.add_stroke_listener( StrokeDisplayDialog.stroke_handler) if self.config.get_show_stroke_display(): StrokeDisplayDialog.display(self, self.config) self.steno_engine.formatter.add_listener( SuggestionsDisplayDialog.stroke_handler) if self.config.get_show_suggestions_display(): SuggestionsDisplayDialog.display(self, self.config, self.steno_engine) def consume_command(self, command): # The first commands can be used whether plover is active or not. if command == self.COMMAND_RESUME: wx.CallAfter(self.steno_engine.set_is_running, True) return True elif command == self.COMMAND_TOGGLE: wx.CallAfter(self.steno_engine.set_is_running, not self.steno_engine.is_running) return True elif command == self.COMMAND_QUIT: wx.CallAfter(self._quit) return True if not self.steno_engine.is_running: return False # These commands can only be run when plover is active. if command == self.COMMAND_SUSPEND: wx.CallAfter(self.steno_engine.set_is_running, False) return True elif command == self.COMMAND_CONFIGURE: wx.CallAfter(self._show_config_dialog) return True elif command == self.COMMAND_FOCUS: def f(): self.Raise() self.Iconize(False) wx.CallAfter(f) return True elif command == self.COMMAND_ADD_TRANSLATION: wx.CallAfter(plover.gui.add_translation.Show, self, self.steno_engine, self.config) return True elif command == self.COMMAND_LOOKUP: wx.CallAfter(plover.gui.lookup.Show, self, self.steno_engine, self.config) return True return False def _update_status(self, state): if state: machine_name = machine_registry.resolve_alias( self.config.get_machine_type()) self.machine_status_text.SetLabel('%s: %s' % (machine_name, state)) self.reconnect_button.Show(state == STATE_ERROR) self.spinner.Show(state == STATE_INITIALIZING) self.connection_ctrl.Show(state != STATE_INITIALIZING) if state == STATE_INITIALIZING: self.spinner.Play() else: self.spinner.Stop() if state == STATE_RUNNING: self.connection_ctrl.SetBitmap(self.connected_bitmap) elif state == STATE_ERROR: self.connection_ctrl.SetBitmap(self.disconnected_bitmap) self.machine_status_sizer.Layout() if self.steno_engine.machine: self.status_button.Enable() if self.steno_engine.is_running: self.status_button.SetBitmapLabel(self.on_bitmap) self.SetTitle("%s: %s" % (self.TITLE, self.RUNNING_MESSAGE)) else: self.status_button.SetBitmapLabel(self.off_bitmap) self.SetTitle("%s: %s" % (self.TITLE, self.STOPPED_MESSAGE)) else: self.status_button.Disable() self.status_button.SetBitmapLabel(self.off_bitmap) self.SetTitle("%s: %s" % (self.TITLE, self.ERROR_MESSAGE)) def _quit(self, event=None): if self.steno_engine: self.steno_engine.destroy() self.Destroy() def _toggle_steno_engine(self, event=None): """Called when the status button is clicked.""" self.steno_engine.set_is_running(not self.steno_engine.is_running) def _show_config_dialog(self, event=None): dlg = ConfigurationDialog(self.steno_engine, self.config, parent=self) dlg.Show() def _show_about_dialog(self, event=None): """Called when the About... button is clicked.""" info = wx.AboutDialogInfo() info.Name = __software_name__ info.Version = __version__ info.Copyright = __copyright__ info.Description = __long_description__ info.WebSite = __url__ info.Developers = __credits__ info.License = __license__ wx.AboutBox(info) def _show_alert(self, message): alert_dialog = wx.MessageDialog(self, message, self.ALERT_DIALOG_TITLE, wx.OK | wx.ICON_INFORMATION) alert_dialog.ShowModal() alert_dialog.Destroy() def on_move(self, event): pos = self.GetScreenPositionTuple() self.config.set_main_frame_x(pos[0]) self.config.set_main_frame_y(pos[1]) event.Skip()
TOP_DIR = os.path.dirname(WIN_DIR) NULL = open(os.devnull, 'r+b') TEMP_DIR = r'C:\Temp' PROG_DIR = r'C:\Progs' sys.path.insert(0, TOP_DIR) os.chdir(TOP_DIR) from plover import ( __name__ as __software_name__, __version__, ) APPNAME = __software_name__.capitalize() VERSION = __version__ ICON = os.path.join(TOP_DIR, __software_name__, 'assets', '%s.ico' % __software_name__) if sys.stdout.isatty() and not sys.platform.startswith('win32'): def info(fmt, *args): s = fmt % args print '[34m' + s + '[0m' else: def info(fmt, *args): s = fmt % args print s
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 show_message(self, message, icon=QSystemTrayIcon.Information, timeout=10000): self._trayicon.showMessage(__software_name__.capitalize(), message, icon, timeout)
from plover import log, __name__ as __software_name__ import pynotify import logging pynotify.init(__software_name__.capitalize()) class DbusNotificationHandler(logging.Handler): """ Handler using DBus notifications to show messages. """ def __init__(self): super(DbusNotificationHandler, self).__init__() self.setLevel(log.WARNING) self.setFormatter(logging.Formatter('%(levelname)s: %(message)s')) def emit(self, record): level = record.levelno message = self.format(record) if level <= log.INFO: timeout = 10 urgency = 0 elif level <= log.WARNING: timeout = 15 urgency = 1 else: timeout = 0 urgency = 2 n = pynotify.Notification(__software_name__.capitalize(), message)
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)
import os import logging from plyer import notification from plover import log, __name__ as __software_name__ from plover.oslayer.config import ASSETS_DIR, PLATFORM APPNAME = __software_name__.capitalize() if PLATFORM == 'win': APPICON = os.path.join(ASSETS_DIR, 'plover.ico') else: APPICON = os.path.join(ASSETS_DIR, 'plover_32x32.png') class PlyerNotificationHandler(logging.Handler): """ Handler using Plyer's notifications to show messages. """ def __init__(self): super().__init__() self.setLevel(log.WARNING) self.setFormatter( log.NoExceptionTracebackFormatter('%(levelname)s: %(message)s')) def emit(self, record): level = record.levelno message = self.format(record) if message.endswith('\n'): message = message[:-1] if level <= log.INFO: timeout = 10
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)
with open(version_file, 'w') as fp: fp.write('\n'.join(contents)) setup_requires = [] options = {} kwargs = {} if sys.platform.startswith('darwin'): setup_requires.append('py2app') options['py2app'] = { 'argv_emulation': False, 'iconfile': 'osx/plover.icns', 'resources': 'plover/assets/', 'plist': { 'CFBundleName': __software_name__.capitalize(), 'CFBundleShortVersionString': __version__, 'CFBundleVersion': __version__, 'CFBundleIdentifier': 'org.openstenoproject.plover', 'NSHumanReadableCopyright': __copyright__, 'CFBundleDevelopmentRegion': 'English', } } # Py2app will not look at entry_points. kwargs['app'] = 'launch.py', if sys.platform.startswith('win32'): setup_requires.append('PyInstaller==3.1.1') setup_requires.extend(('pytest-runner', 'pytest')) options['aliases'] = {
from plover import log, __name__ as __software_name__ import pynotify import logging pynotify.init(__software_name__.capitalize()) class DbusNotificationHandler(logging.Handler): """ Handler using DBus notifications to show messages. """ def __init__(self): super(DbusNotificationHandler, self).__init__() self.setLevel(log.WARNING) self.setFormatter( log.NoExceptionTracebackFormatter('%(levelname)s: %(message)s')) def emit(self, record): level = record.levelno message = self.format(record) if level <= log.INFO: timeout = 10 urgency = 0 elif level <= log.WARNING: timeout = 15 urgency = 1 else: timeout = 0 urgency = 2 n = pynotify.Notification(__software_name__.capitalize(), message) n.set_timeout(timeout * 1000) n.set_urgency(urgency) n.show()