Exemplo n.º 1
0
    def on_drop(self, event):
        self._window.withdraw()
        if not (_is_on_window(event) or self._dragged_state in SPECIAL_STATES):
            log.info("popping off a tab")
            plugin_names = pluginloader.get_loaded_plugins()
            for bad_plugin in ['restart', 'geometry']:
                # these plugins are not suitable for popups
                if bad_plugin in plugin_names:
                    plugin_names.remove(bad_plugin)

            tab, state = self._dragged_state
            required_size = (tab.winfo_reqwidth(), tab.winfo_reqheight())
            message = (type(tab), state, plugin_names, required_size,
                       porcupine.get_init_kwargs(), event.x_root, event.y_root)
            with tempfile.NamedTemporaryFile(delete=False) as file:
                log.debug("writing dumped state to '%s'", file)
                pickle.dump(message, file)

            settings.save()  # let the new process use up-to-date settings
            process = subprocess.Popen(
                [sys.executable, '-m', __name__, file.name])
            log.debug("started subprocess with PID %d", process.pid)
            porcupine.get_tab_manager().close_tab(tab)

            # don't exit python until the subprocess exits, also log stuff
            threading.Thread(target=self._waiter_thread,
                             args=[process]).start()

        self._dragged_state = NOT_DRAGGING
Exemplo n.º 2
0
def run():
    if _root is None:
        raise RuntimeError("init() wasn't called")

    # the user can change the settings only if we get here, so there's
    # no need to wrap the whole thing in try/with/finally/whatever
    try:
        _root.mainloop()
    finally:
        settings.save()
Exemplo n.º 3
0
def test_save(cleared_global_settings):
    load_from_json_string('{"bar": "custom bar"}')

    settings.add_option('foo', 'default')
    settings.add_option('bar', 'default')
    settings.add_option('baz', 'default')
    settings.set('foo', 'custom foo')

    settings.save()
    with settings._get_json_path().open('r') as file:
        assert json.load(file) == {'foo': 'custom foo', 'bar': 'custom bar'}
Exemplo n.º 4
0
def test_save(cleared_global_settings):
    load_from_json_string('{"bar": "custom bar"}')

    settings.add_option("foo", "default")
    settings.add_option("bar", "default")
    settings.add_option("baz", "default")
    settings.set_("foo", "custom foo")

    settings.save()
    with settings._get_json_path().open("r") as file:
        assert json.load(file) == {"foo": "custom foo", "bar": "custom bar"}
Exemplo n.º 5
0
    def pop(self, tab: tabs.Tab, state: Any, geometry: str) -> None:
        log.info(f"Popping {repr(tab)} to {geometry} begins")
        message = (type(tab), state, geometry)
        with tempfile.NamedTemporaryFile(delete=False) as file:
            log.info(f"writing pickled state to {file.name}")
            pickle.dump(message, file)

        settings.save()  # let the new process use up-to-date settings

        # The subprocess must be called so that it has a sane sys.path.
        # In particular, import or don't import from current working
        # directory exactly like the porcupine that is currently running.
        # Importing from current working directory is bad if it contains
        # e.g. queue.py (#31), but good when that's where porcupine is
        # meant to be imported from (#230).
        code = f"import sys; sys.path[:] = {sys.path}; from porcupine.__main__ import main; main()"
        args = [sys.executable, "-c", code]

        args.append("--without-plugins")
        args.append(
            ",".join(
                {
                    info.name
                    for info in pluginloader.plugin_infos
                    if info.status == pluginloader.Status.DISABLED_ON_COMMAND_LINE
                }
                | {
                    # these plugins are not suitable for popups
                    # TODO: geometry and restart stuff don't get saved
                    "restart",
                    "geometry",
                }
            )
        )

        if get_parsed_args().verbose_logger is not None:
            args.append("--verbose-logger")
            args.append(get_parsed_args().verbose_logger)

        process = subprocess.Popen(
            args, env={**os.environ, "PORCUPINE_POPPINGTABS_STATE_FILE": file.name}
        )
        log.debug(f"started subprocess with PID {process.pid}")
        get_tab_manager().close_tab(tab)

        # don't exit python until the subprocess exits, also log stuff
        threading.Thread(target=self._waiting_thread, args=[process]).start()
Exemplo n.º 6
0
def main() -> None:
    # Arguments are parsed in two steps:
    #   1. only the arguments needed for importing plugins
    #   2. everything else
    #
    # Between those steps, plugins get a chance to add more command-line options.
    parser = argparse.ArgumentParser(
        epilog=_EPILOG,
        formatter_class=argparse.RawDescriptionHelpFormatter,
        add_help=False,  # help in step 1 wouldn't show options added by plugins
    )
    parser.add_argument(
        '--version', action='version',
        version=("Porcupine %s" % porcupine_version),
        help="display the Porcupine version number and exit")
    parser.add_argument(
        '-v', '--verbose', action='store_true',
        help=("print all logging messages to stderr, only warnings and errors "
              "are printed by default (but all messages always go to a log "
              "file in %s as well)" % _logs.LOG_DIR))
    parser.add_argument(
        '--print-plugindir', action=_PrintPlugindirAction,
        help="find out where to install custom plugins")
    plugingroup = parser.add_argument_group("plugin loading options")
    plugingroup.add_argument(
        '--no-plugins', action='store_false', dest='use_plugins',
        help=("don't load any plugins, this is useful for "
              "understanding how much can be done with plugins"))
    plugingroup.add_argument(
        '--without-plugins', metavar='PLUGINS', default='',
        help=("don't load PLUGINS (see --print-plugindir), "
              "e.g. --without-plugins=highlight disables syntax highlighting, "
              "multiple plugin names can be given comma-separated"))

    args_parsed_in_first_step, junk = parser.parse_known_args()

    dirs.makedirs()
    _logs.setup(args_parsed_in_first_step.verbose)

    settings.init_enough_for_using_disabled_plugins_list()
    if args_parsed_in_first_step.use_plugins:
        if args_parsed_in_first_step.without_plugins:
            disable_list = args_parsed_in_first_step.without_plugins.split(',')
        else:
            disable_list = []
        pluginloader.import_plugins(disable_list)

        bad_disables = set(disable_list) - {info.name for info in pluginloader.plugin_infos}
        if bad_disables:
            one_of_them, *the_rest = bad_disables
            parser.error(f"--without-plugins: no plugin named {one_of_them!r}")

    parser.add_argument('--help', action='help', help="show this message")
    pluginloader.run_setup_argument_parser_functions(parser)
    parser.add_argument(
        'files', metavar='FILES', nargs=argparse.ZERO_OR_MORE,
        help="open these files when Porcupine starts, - means stdin")
    plugingroup.add_argument(
        '--shuffle-plugins', action='store_true',
        help=("respect setup_before and setup_after, but otherwise setup the "
              "plugins in a random order instead of sorting by name "
              "alphabetically, useful for making sure that your plugin's "
              "setup_before and setup_after define everything needed; usually "
              "plugins are not shuffled in order to make the UI consistent"))
    args = parser.parse_args()

    _state.init(args)
    settings.init_the_rest_after_initing_enough_for_using_disabled_plugins_list()
    menubar._init()

    pluginloader.run_setup_functions(args.shuffle_plugins)
    get_main_window().event_generate('<<PluginsLoaded>>')

    tabmanager = get_tab_manager()
    for path_string in args.files:
        if path_string == '-':
            # don't close stdin so it's possible to do this:
            #
            #   $ porcu - -
            #   bla bla bla
            #   ^D
            #   bla bla
            #   ^D
            tab = tabs.FileTab(tabmanager, content=sys.stdin.read())
        else:
            tab = tabs.FileTab.open_file(tabmanager, pathlib.Path(path_string))
        tabmanager.add_tab(tab)

    try:
        get_main_window().mainloop()
    finally:
        settings.save()
    log.info("exiting Porcupine successfully")
Exemplo n.º 7
0
def save_and_read_file() -> str:
    settings.save()
    with settings._get_json_path().open('r', encoding='utf-8') as file:
        return json.load(file)
Exemplo n.º 8
0
def main() -> None:
    # Arguments are parsed in two steps:
    #   1. only the arguments needed for importing plugins
    #   2. everything else
    #
    # Between those steps, plugins get a chance to add more command-line options.
    parser = argparse.ArgumentParser(
        epilog=_EPILOG,
        formatter_class=argparse.RawDescriptionHelpFormatter,
        add_help=False,  # help in step 1 wouldn't show options added by plugins
    )
    parser.add_argument(
        "--version",
        action="version",
        version=f"Porcupine {porcupine_version}",
        help="display the Porcupine version number and exit",
    )
    parser.add_argument(
        "--print-plugindir",
        action=_PrintPlugindirAction,
        help="find out where to install custom plugins",
    )

    verbose_group = parser.add_mutually_exclusive_group()
    verbose_group.add_argument(
        "-v",
        "--verbose",
        dest="verbose_logger",
        action="store_const",
        const="",
        help=("print all logging messages to stderr, only warnings and errors "
              "are printed by default (but all messages always go to a log "
              "file as well)"),
    )
    verbose_group.add_argument(
        "--verbose-logger",
        help=("increase verbosity for just one logger only, e.g. "
              "--verbose-logger=porcupine.plugins.highlight "
              "to see messages from highlight plugin"),
    )

    plugingroup = parser.add_argument_group("plugin loading options")
    plugingroup.add_argument(
        "--no-plugins",
        action="store_false",
        dest="use_plugins",
        help=("don't load any plugins, this is useful for "
              "understanding how much can be done with plugins"),
    )
    plugingroup.add_argument(
        "--without-plugins",
        metavar="PLUGINS",
        default="",
        help=("don't load PLUGINS (see --print-plugindir), "
              "e.g. --without-plugins=highlight disables syntax highlighting, "
              "multiple plugin names can be given comma-separated"),
    )

    args_parsed_in_first_step, junk = parser.parse_known_args()

    pathlib.Path(dirs.user_cache_dir).mkdir(parents=True, exist_ok=True)
    (pathlib.Path(dirs.user_config_dir) / "plugins").mkdir(parents=True,
                                                           exist_ok=True)
    pathlib.Path(dirs.user_log_dir).mkdir(parents=True, exist_ok=True)
    _logs.setup(args_parsed_in_first_step.verbose_logger)

    settings.init_enough_for_using_disabled_plugins_list()
    if args_parsed_in_first_step.use_plugins:
        if args_parsed_in_first_step.without_plugins:
            disable_list = args_parsed_in_first_step.without_plugins.split(",")
        else:
            disable_list = []
        pluginloader.import_plugins(disable_list)

        bad_disables = set(disable_list) - {
            info.name
            for info in pluginloader.plugin_infos
        }
        if bad_disables:
            one_of_them, *the_rest = bad_disables
            parser.error(f"--without-plugins: no plugin named {one_of_them!r}")

    parser.add_argument("--help", action="help", help="show this message")
    pluginloader.run_setup_argument_parser_functions(parser)
    parser.add_argument(
        "files",
        metavar="FILES",
        nargs=argparse.ZERO_OR_MORE,
        help="open these files when Porcupine starts, - means stdin",
    )
    plugingroup.add_argument(
        "--shuffle-plugins",
        action="store_true",
        help=("respect setup_before and setup_after, but otherwise setup the "
              "plugins in a random order instead of sorting by name "
              "alphabetically, useful for making sure that your plugin's "
              "setup_before and setup_after define everything needed; usually "
              "plugins are not shuffled in order to make the UI consistent"),
    )

    args = parser.parse_args()
    _state.init(args)

    # Prevent showing up a not-ready-yet root window to user
    get_main_window().withdraw()

    settings.init_the_rest_after_initing_enough_for_using_disabled_plugins_list(
    )
    menubar._init()
    pluginloader.run_setup_functions(args.shuffle_plugins)

    tabmanager = get_tab_manager()
    for path_string in args.files:
        if path_string == "-":
            # don't close stdin so it's possible to do this:
            #
            #   $ porcu - -
            #   bla bla bla
            #   ^D
            #   bla bla
            #   ^D
            tab = tabs.FileTab(tabmanager, content=sys.stdin.read())
        else:
            tab = tabs.FileTab.open_file(tabmanager, pathlib.Path(path_string))
        tabmanager.add_tab(tab)

    get_main_window().deiconify()
    try:
        get_main_window().mainloop()
    finally:
        settings.save()
    log.info("exiting Porcupine successfully")
Exemplo n.º 9
0
    def on_drop(self, event: 'tkinter.Event[tkinter.Misc]') -> None:
        self._window.withdraw()
        if not (_is_on_window(event)
                or isinstance(self._dragged_state, SpecialState)):
            log.info("popping off a tab")
            tab, state = self._dragged_state
            required_size = (tab.winfo_reqwidth(), tab.winfo_reqheight())
            message = (type(tab), state, required_size, event.x_root,
                       event.y_root)

            with tempfile.NamedTemporaryFile(delete=False) as file:
                log.debug("writing dumped state to '%s'", file)
                pickle.dump(message, file)

            settings.save()  # let the new process use up-to-date settings

            # Empty string (aka "load from current working directory") becomes
            # the first item of sys.path when using -m, which isn't great if
            # your current working directory contains e.g. queue.py (issue 31).
            #
            # However, if the currently running porcupine imports from current
            # working directory (e.g. python3 -m porcupine), then the subprocess
            # should do that too.
            if os.getcwd() in sys.path:
                args = [sys.executable, '-m', 'porcupine']
            else:
                python_code = '''
import sys
if sys.path[0] == '':
    del sys.path[0]
from porcupine.__main__ import main
main()
'''
                args = [sys.executable, '-c', python_code]

            args.append('--without-plugins')
            args.append(','.join({
                info.name
                for info in pluginloader.plugin_infos
                if info.status == pluginloader.Status.DISABLED_ON_COMMAND_LINE
            } | {
                # these plugins are not suitable for popups
                # TODO: geometry and restart stuff don't get saved
                'restart',
                'geometry',
            }))

            if get_parsed_args().verbose:
                args.append('--verbose')

            process = subprocess.Popen(
                args,
                env={
                    **os.environ, 'PORCUPINE_POPPINGTABS_STATE_FILE': file.name
                })
            log.debug("started subprocess with PID %d", process.pid)
            get_tab_manager().close_tab(tab)

            # don't exit python until the subprocess exits, also log stuff
            threading.Thread(target=self._waiter_thread,
                             args=[process]).start()

        self._dragged_state = NOT_DRAGGING
Exemplo n.º 10
0
def main():
    parser = argparse.ArgumentParser(
        prog=('%s -m porcupine' % utils.short_python_command),
        epilog=_EPILOG,
        formatter_class=argparse.RawDescriptionHelpFormatter)

    parser.add_argument('-v',
                        '--version',
                        action='version',
                        version=("Porcupine %s" % porcupine.__version__),
                        help="display the Porcupine version number and exit")
    parser.add_argument('--print-plugindir',
                        action=_PrintPlugindirAction,
                        help="find out where to install custom plugins")
    parser.add_argument(
        'files',
        metavar='FILES',
        action=_ExtendAction,
        nargs=argparse.ZERO_OR_MORE,
        type=argparse.FileType("r"),
        help="open these files when Porcupine starts, - means stdin")
    parser.add_argument(
        '-n',
        '--new-file',
        dest='files',
        action='append_const',
        const=None,
        help='create a "New File" tab; may be specified multiple times')

    plugingroup = parser.add_mutually_exclusive_group()
    plugingroup.add_argument(
        '--no-plugins',
        action='store_false',
        dest='yes_plugins',
        help=("don't load the plugins, this is useful for "
              "understanding how much can be done with plugins"))
    plugingroup.add_argument(
        '--shuffle-plugins',
        action='store_true',
        help=("respect setup_before and setup_after, but otherwise setup the "
              "plugins in a random order instead of sorting by name "
              "alphabetically"))

    loggroup = parser.add_mutually_exclusive_group()
    loggroup.add_argument(
        '--logfile',
        type=argparse.FileType('w'),
        help=("write information about what Porcupine is doing to this file, "
              "defaults to a .txt file in %s" % dirs.cachedir))
    loggroup.add_argument('--verbose',
                          dest='logfile',
                          action='store_const',
                          const=sys.stderr,
                          help="use stderr as the log file")

    args = parser.parse_args()

    filelist = []
    for file in args.files:
        if file is sys.stdin:
            # don't close stdin so it's possible to do this:
            #
            #   $ porcupine - -
            #   bla bla bla
            #   ^D
            #   bla bla
            #   ^D
            filelist.append((None, file.read()))
        elif file is None:
            # -n or --new-file was used
            filelist.append((None, ''))
        else:
            with file:
                filelist.append((os.path.abspath(file.name), file.read()))

    try:
        if filelist:
            _ipc.send(filelist)
            print("The", ("file" if len(filelist) == 1 else "files"),
                  "will be opened in the already running Porcupine.")
        else:
            # see queue_opener()
            _ipc.send([(None, None)])
            print("Porcupine is already running.")
        return
    except ConnectionRefusedError:
        # not running yet, become the Porcupine that other Porcupines
        # connect to
        pass

    dirs.makedirs()
    _logs.setup(args.logfile)
    log.info("starting Porcupine %s on %s", porcupine.__version__,
             platform.platform().replace('-', ' '))
    log.info("running on Python %d.%d.%d from %s",
             *(list(sys.version_info[:3]) + [sys.executable]))

    root = tkinter.Tk()
    porcupine.init(root)
    root.title("Porcupine")
    root.geometry('650x500')  # TODO: make a plugin instead of hard-coding
    root.protocol('WM_DELETE_WINDOW', porcupine.quit)

    if args.yes_plugins:
        _pluginloader.load(shuffle=args.shuffle_plugins)

    # see queue_opener()
    for path, content in filelist:
        open_file(path, content)

    # the user can change the settings only if we get here, so there's
    # no need to wrap the try/with/finally/whatever the whole thing
    with _ipc.session() as queue:
        root.after_idle(queue_opener, queue)
        try:
            root.mainloop()
        finally:
            settings.save()

    log.info("exiting Porcupine successfully")