Esempio n. 1
0
    def edit(self, text):
        """Edit a given text.

        Args:
            text: The initial text to edit.
        """
        if self._text is not None:
            raise ValueError("Already editing a file!")
        self._text = text
        try:
            self._oshandle, self._filename = tempfile.mkstemp(
                text=True, prefix='qutebrowser-editor-')
            if text:
                encoding = config.get('general', 'editor-encoding')
                with open(self._filename, 'w', encoding=encoding) as f:
                    f.write(text)  # pragma: no branch
        except OSError as e:
            message.error(self._win_id, "Failed to create initial file: "
                                        "{}".format(e))
            return
        self._proc = guiprocess.GUIProcess(self._win_id, what='editor',
                                           parent=self)
        self._proc.finished.connect(self.on_proc_closed)
        self._proc.error.connect(self.on_proc_error)
        editor = config.get('general', 'editor')
        executable = editor[0]
        args = [self._filename if arg == '{}' else arg for arg in editor[1:]]
        log.procs.debug("Calling \"{}\" with args {}".format(executable, args))
        self._proc.start(executable, args)
    def edit(self, text):
        """Edit a given text.

        Args:
            text: The initial text to edit.
        """
        if self._text is not None:
            raise ValueError("Already editing a file!")
        self._text = text
        try:
            # Close while the external process is running, as otherwise systems
            # with exclusive write access (e.g. Windows) may fail to update
            # the file from the external editor, see
            # https://github.com/qutebrowser/qutebrowser/issues/1767
            with tempfile.NamedTemporaryFile(
                    mode='w',
                    prefix='qutebrowser-editor-',
                    encoding=config.val.editor.encoding,
                    delete=False) as fobj:
                if text:
                    fobj.write(text)
                self._file = fobj
        except OSError as e:
            message.error("Failed to create initial file: {}".format(e))
            return
        self._proc = guiprocess.GUIProcess(what='editor', parent=self)
        self._proc.finished.connect(self.on_proc_closed)
        self._proc.error.connect(self.on_proc_error)
        editor = config.val.editor.command
        executable = editor[0]
        args = [arg.replace('{}', self._file.name) for arg in editor[1:]]
        log.procs.debug("Calling \"{}\" with args {}".format(executable, args))
        self._proc.start(executable, args)
Esempio n. 3
0
    def _run_process(self,
                     cmd,
                     *args,
                     env=None,
                     verbose=False,
                     output_messages=False):
        """Start the given command.

        Args:
            cmd: The command to be started.
            *args: The arguments to hand to the command
            env: A dictionary of environment variables to add.
            verbose: Show notifications when the command started/exited.
            output_messages: Show the output as messages.
        """
        assert self._filepath is not None
        self._env['QUTE_FIFO'] = self._filepath
        if env is not None:
            self._env.update(env)

        self.proc = guiprocess.GUIProcess('userscript',
                                          additional_env=self._env,
                                          output_messages=output_messages,
                                          verbose=verbose,
                                          parent=self)
        self.proc.finished.connect(self.on_proc_finished)
        self.proc.error.connect(self.on_proc_error)
        self.proc.start(cmd, args)
Esempio n. 4
0
def _execute_fileselect_command(
    command: List[str],
    qb_mode: FileSelectionMode,
    tmpfilename: Optional[str] = None
) -> List[str]:
    """Execute external command to choose file.

    Args:
        multiple: Should selecting multiple files be allowed.
        tmpfilename: Path to the temporary file if used, otherwise None.

    Return:
        A list of selected file paths, or empty list if no file is selected.
        If multiple is False, the return value will have at most 1 item.
    """
    proc = guiprocess.GUIProcess(what='choose-file')
    proc.start(command[0], command[1:])

    loop = qtutils.EventLoop()
    proc.finished.connect(lambda _code, _status: loop.exit())
    loop.exec()

    if tmpfilename is None:
        selected_files = proc.stdout.splitlines()
    else:
        try:
            with open(tmpfilename, mode='r', encoding=sys.getfilesystemencoding()) as f:
                selected_files = f.read().splitlines()
        except OSError as e:
            message.error(f"Failed to open tempfile {tmpfilename} ({e})!")
            selected_files = []

    return list(_validated_selected_files(
        qb_mode=qb_mode, selected_files=selected_files))
Esempio n. 5
0
    def _start_editor(self, line=1, column=1):
        """Start the editor with the file opened as self._filename.

        Args:
            line: the line number to pass to the editor
            column: the column number to pass to the editor
        """
        self._proc = guiprocess.GUIProcess(what='editor', parent=self)
        self._proc.finished.connect(self._on_proc_closed)
        self._proc.error.connect(self._on_proc_error)
        editor = config.val.editor.command
        executable = editor[0]

        if self._watcher:
            assert self._filename is not None
            ok = self._watcher.addPath(self._filename)
            if not ok:
                log.procs.error("Failed to watch path: {}".format(
                    self._filename))
            self._watcher.fileChanged.connect(  # type: ignore[attr-defined]
                self._on_file_changed)

        args = [self._sub_placeholder(arg, line, column) for arg in editor[1:]]
        log.procs.debug("Calling \"{}\" with args {}".format(executable, args))
        self._proc.start(executable, args)
Esempio n. 6
0
    def open_file(self, cmdline=None):
        """Open the downloaded file.

        Args:
            cmdline: The command to use as string. A `{}` is expanded to the
                     filename. None means to use the system's default
                     application. If no `{}` is found, the filename is appended
                     to the cmdline.
        """
        assert self.successful
        filename = self._get_open_filename()
        if filename is None:  # pragma: no cover
            log.downloads.error("No filename to open the download!")
            return

        if cmdline is None:
            log.downloads.debug(
                "Opening {} with the system application".format(filename))
            url = QUrl.fromLocalFile(filename)
            QDesktopServices.openUrl(url)
            return

        cmd, *args = shlex.split(cmdline)
        args = [arg.replace('{}', filename) for arg in args]
        if '{}' not in cmdline:
            args.append(filename)
        log.downloads.debug("Opening {} with {}".format(
            filename, [cmd] + args))
        proc = guiprocess.GUIProcess(what='download')
        proc.start_detached(cmd, args)
def test_process_completion(monkeypatch, stubs, info):
    monkeypatch.setattr(guiprocess, 'QProcess', stubs.FakeProcess)
    p1 = guiprocess.GUIProcess('testprocess')
    p2 = guiprocess.GUIProcess('testprocess')
    p3 = guiprocess.GUIProcess('editor')

    p1.pid = 1001
    p1.cmd = 'cmd1'
    p1.args = []
    p1.outcome.running = False
    p1.outcome.status = QProcess.NormalExit
    p1.outcome.code = 0

    p2.pid = 1002
    p2.cmd = 'cmd2'
    p2.args = []
    p2.outcome.running = True

    p3.pid = 1003
    p3.cmd = 'cmd3'
    p3.args = []
    p3.outcome.running = False
    p3.outcome.status = QProcess.NormalExit
    p3.outcome.code = 1

    monkeypatch.setattr(guiprocess, 'all_processes', {
        1001: p1,
        1002: p2,
        1003: p3,
        1004: None,  # cleaned up
    })

    model = miscmodels.process(info=info)
    model.set_pattern('')

    expected = {
        'Testprocess': [
            ('1002', 'running', 'cmd2'),
            ('1001', 'successful', 'cmd1'),
        ],
        'Editor': [
            ('1003', 'unsuccessful', 'cmd3'),
        ],
    }
    _check_completions(model, expected)
Esempio n. 8
0
def proc(qtbot):
    """A fixture providing a GUIProcess and cleaning it up after the test."""
    p = guiprocess.GUIProcess(0, 'testprocess')
    yield p
    if p._proc.state() == QProcess.Running:
        with qtbot.waitSignal(p.finished, timeout=10000) as blocker:
            p._proc.terminate()
        if not blocker.signal_triggered:
            p._proc.kill()
Esempio n. 9
0
def open_file(filename: str, cmdline: str = None) -> None:
    """Open the given file.

    If cmdline is not given, downloads.open_dispatcher is used.
    If open_dispatcher is unset, the system's default application is used.

    Args:
        filename: The filename to open.
        cmdline: The command to use as string. A `{}` is expanded to the
                 filename. None means to use the system's default application
                 or `downloads.open_dispatcher` if set. If no `{}` is found,
                 the filename is appended to the cmdline.
    """
    # Import late to avoid circular imports:
    # - usertypes -> utils -> guiprocess -> message -> usertypes
    # - usertypes -> utils -> config -> configdata -> configtypes ->
    #   cmdutils -> command -> message -> usertypes
    from qutebrowser.config import config
    from qutebrowser.misc import guiprocess
    from qutebrowser.utils import version, message

    # the default program to open downloads with - will be empty string
    # if we want to use the default
    override = config.val.downloads.open_dispatcher

    if version.is_sandboxed():
        if cmdline:
            message.error("Cannot spawn download dispatcher from sandbox")
            return
        if override:
            message.warning("Ignoring download dispatcher from config in "
                            "sandbox environment")
            override = None

    # precedence order: cmdline > downloads.open_dispatcher > openUrl

    if cmdline is None and not override:
        log.misc.debug("Opening {} with the system application"
                       .format(filename))
        url = QUrl.fromLocalFile(filename)
        QDesktopServices.openUrl(url)
        return

    if cmdline is None and override:
        cmdline = override

    assert cmdline is not None

    cmd, *args = shlex.split(cmdline)
    args = [arg.replace('{}', filename) for arg in args]
    if '{}' not in cmdline:
        args.append(filename)
    log.misc.debug("Opening {} with {}"
                   .format(filename, [cmd] + args))
    proc = guiprocess.GUIProcess(what='open-file')
    proc.start_detached(cmd, args)
Esempio n. 10
0
 def _start_editor(self):
     """Start the editor with the file opened as self._filename."""
     self._proc = guiprocess.GUIProcess(what='editor', parent=self)
     self._proc.finished.connect(self.on_proc_closed)
     self._proc.error.connect(self.on_proc_error)
     editor = config.val.editor.command
     executable = editor[0]
     args = [arg.replace('{}', self._filename) for arg in editor[1:]]
     log.procs.debug("Calling \"{}\" with args {}".format(executable, args))
     self._proc.start(executable, args)
Esempio n. 11
0
def proc(qtbot, caplog):
    """A fixture providing a GUIProcess and cleaning it up after the test."""
    p = guiprocess.GUIProcess('testprocess')
    yield p
    if p._proc.state() == QProcess.Running:
        with caplog.at_level(logging.ERROR):
            with qtbot.waitSignal(p.finished, timeout=10000,
                                  raising=False) as blocker:
                p._proc.terminate()
            if not blocker.signal_triggered:
                p._proc.kill()
Esempio n. 12
0
def proc(qtbot):
    """A fixture providing a GUIProcess and cleaning it up after the test."""
    if os.name == 'nt':
        # WORKAROUND for https://github.com/pytest-dev/pytest-qt/issues/67
        pytest.skip("Test is flaky on Windows...")
    p = guiprocess.GUIProcess(0, 'test')
    yield p
    if p._proc.state() == QProcess.Running:
        with qtbot.waitSignal(p.finished, timeout=10000) as blocker:
            p._proc.terminate()
        if not blocker.signal_triggered:
            p._proc.kill()
Esempio n. 13
0
    def _spawn(self, url, context):
        """Spawn a simple command from a hint.

        Args:
            url: The URL to open as a QUrl.
            context: The HintContext to use.
        """
        urlstr = url.toString(QUrl.FullyEncoded | QUrl.RemovePassword)
        args = context.get_args(urlstr)
        cmd, *args = args
        proc = guiprocess.GUIProcess(self._win_id, what='command', parent=self)
        proc.start(cmd, args)
    def test_existing_process(self, qtbot, py_proc):
        proc = guiprocess.GUIProcess('testprocess')

        with qtbot.wait_signal(proc.finished, timeout=5000):
            proc.start(*py_proc("print('AT&T')"))

        _mimetype, data = qutescheme.qute_process(
            QUrl(f'qute://process/{proc.pid}'))
        print(data)

        assert f'<title>Process {proc.pid}</title>' in data
        assert '-c &#39;print(' in data
        assert 'Testprocess exited successfully.' in data
        assert f'<pre>AT&amp;T{os.linesep}</pre>' in data
        assert 'No output.' in data  # stderr
Esempio n. 15
0
    def _start_editor(self, line=1, column=1):
        """Start the editor with the file opened as self._filename.

        Args:
            line: the line number to pass to the editor
            column: the column number to pass to the editor
        """
        self._proc = guiprocess.GUIProcess(what='editor', parent=self)
        self._proc.finished.connect(self.on_proc_closed)
        self._proc.error.connect(self.on_proc_error)
        editor = config.val.editor.command
        executable = editor[0]

        args = [self._sub_placeholder(arg, line, column) for arg in editor[1:]]
        log.procs.debug("Calling \"{}\" with args {}".format(executable, args))
        self._proc.start(executable, args)
Esempio n. 16
0
    def _run_process(self, cmd, *args, env, verbose):
        """Start the given command.

        Args:
            cmd: The command to be started.
            *args: The arguments to hand to the command
            env: A dictionary of environment variables to add.
            verbose: Show notifications when the command started/exited.
        """
        self._env = {'QUTE_FIFO': self._filepath}
        if env is not None:
            self._env.update(env)
        self._proc = guiprocess.GUIProcess(self._win_id, 'userscript',
                                           additional_env=self._env,
                                           verbose=verbose, parent=self)
        self._proc.finished.connect(self.on_proc_finished)
        self._proc.error.connect(self.on_proc_error)
        self._proc.start(cmd, args)
Esempio n. 17
0
def open_file(filename, cmdline=None):
    """Open the given file.

    If cmdline is not given, general->default-open-dispatcher is used.
    If default-open-dispatcher is unset, the system's default application is
    used.

    Args:
        filename: The filename to open.
        cmdline: The command to use as string. A `{}` is expanded to the
                 filename. None means to use the system's default application
                 or `default-open-dispatcher` if set. If no `{}` is found, the
                 filename is appended to the cmdline.
    """
    # Import late to avoid circular imports:
    # utils -> config -> configdata -> configtypes -> cmdutils -> command ->
    # utils
    from qutebrowser.misc import guiprocess
    from qutebrowser.config import config
    # the default program to open downloads with - will be empty string
    # if we want to use the default
    override = config.get('general', 'default-open-dispatcher')

    # precedence order: cmdline > default-open-dispatcher > openUrl

    if cmdline is None and not override:
        log.misc.debug("Opening {} with the system application"
                       .format(filename))
        url = QUrl.fromLocalFile(filename)
        QDesktopServices.openUrl(url)
        return

    if cmdline is None and override:
        cmdline = override

    cmd, *args = shlex.split(cmdline)
    args = [arg.replace('{}', filename) for arg in args]
    if '{}' not in cmdline:
        args.append(filename)
    log.misc.debug("Opening {} with {}"
                   .format(filename, [cmd] + args))
    proc = guiprocess.GUIProcess(what='open-file')
    proc.start_detached(cmd, args)
Esempio n. 18
0
def test_start_env(monkeypatch, qtbot, py_proc):
    monkeypatch.setenv('QUTEBROWSER_TEST_1', '1')
    env = {'QUTEBROWSER_TEST_2': '2'}
    proc = guiprocess.GUIProcess(0, 'testprocess', additional_env=env)

    argv = py_proc("""
        import os
        import json
        env = dict(os.environ)
        print(json.dumps(env))
        sys.exit(0)
    """)

    with qtbot.waitSignals([proc.started, proc.finished], raising=True,
                           timeout=10000):
        proc.start(*argv)

    data = bytes(proc._proc.readAll()).decode('utf-8')
    ret_env = json.loads(data)
    assert 'QUTEBROWSER_TEST_1' in ret_env
    assert 'QUTEBROWSER_TEST_2' in ret_env
Esempio n. 19
0
def test_start_env(monkeypatch, qtbot, py_proc):
    monkeypatch.setenv('QUTEBROWSER_TEST_1', '1')
    env = {'QUTEBROWSER_TEST_2': '2'}
    proc = guiprocess.GUIProcess('testprocess', additional_env=env)

    cmd, args = py_proc("""
        import os
        import json
        import sys

        env = dict(os.environ)
        print(json.dumps(env))
        sys.exit(0)
    """)

    with qtbot.wait_signals([proc.started, proc.finished], timeout=10000,
                           order='strict'):
        proc.start(cmd, args)

    assert 'QUTEBROWSER_TEST_1' in proc.stdout
    assert 'QUTEBROWSER_TEST_2' in proc.stdout
Esempio n. 20
0
    def open_file(self, cmdline=None):
        """Open the downloaded file.

        Args:
            cmdline: The command to use as string. A `{}` is expanded to the
                     filename. None means to use the system's default
                     application or `default-open-dispatcher` if set. If no
                     `{}` is found, the filename is appended to the cmdline.
        """
        assert self.successful
        filename = self._get_open_filename()
        if filename is None:  # pragma: no cover
            log.downloads.error("No filename to open the download!")
            return

        # the default program to open downloads with - will be empty string
        # if we want to use the default
        override = config.get('general', 'default-open-dispatcher')

        # precedence order: cmdline > default-open-dispatcher > openUrl

        if cmdline is None and not override:
            log.downloads.debug(
                "Opening {} with the system application".format(filename))
            url = QUrl.fromLocalFile(filename)
            QDesktopServices.openUrl(url)
            return

        if cmdline is None and override:
            cmdline = override

        cmd, *args = shlex.split(cmdline)
        args = [arg.replace('{}', filename) for arg in args]
        if '{}' not in cmdline:
            args.append(filename)
        log.downloads.debug("Opening {} with {}".format(
            filename, [cmd] + args))
        proc = guiprocess.GUIProcess(what='download')
        proc.start_detached(cmd, args)
Esempio n. 21
0
def test_start_env(monkeypatch, qtbot, py_proc):
    monkeypatch.setenv('QUTEBROWSER_TEST_1', '1')
    env = {'QUTEBROWSER_TEST_2': '2'}
    proc = guiprocess.GUIProcess('testprocess', additional_env=env)

    argv = py_proc("""
        import os
        import json
        import sys

        env = dict(os.environ)
        print(json.dumps(env))
        sys.exit(0)
    """)

    with qtbot.waitSignal(proc.started, timeout=10000), \
         qtbot.waitSignal(proc.finished, timeout=10000):
        proc.start(*argv)

    data = qutescheme.spawn_output
    assert 'QUTEBROWSER_TEST_1' in data
    assert 'QUTEBROWSER_TEST_2' in data
Esempio n. 22
0
def choose_file(multiple: bool) -> List[str]:
    """Select file(s) for uploading, using external command defined in config.

    Args:
        multiple: Should selecting multiple files be allowed.

    Return:
        A list of selected file paths, or empty list if no file is selected.
        If multiple is False, the return value will have at most 1 item.
    """
    handle = tempfile.NamedTemporaryFile(prefix='qutebrowser-fileselect-', delete=False)
    handle.close()
    tmpfilename = handle.name
    with utils.cleanup_file(tmpfilename):
        if multiple:
            command = config.val.fileselect.multiple_files.command
        else:
            command = config.val.fileselect.single_file.command

        proc = guiprocess.GUIProcess(what='choose-file')
        proc.start(command[0],
                   [arg.replace('{}', tmpfilename) for arg in command[1:]])

        loop = qtutils.EventLoop()
        proc.finished.connect(lambda _code, _status: loop.exit())
        loop.exec()

        try:
            with open(tmpfilename, mode='r', encoding=sys.getfilesystemencoding()) as f:
                selected_files = f.read().splitlines()
        except OSError as e:
            message.error(f"Failed to open tempfile {tmpfilename} ({e})!")
            selected_files = []

    if not multiple:
        if len(selected_files) > 1:
            message.warning("More than one file chosen, using only the first")
            return selected_files[:1]
    return selected_files
Esempio n. 23
0
def fake_proc(monkeypatch, stubs):
    """A fixture providing a GUIProcess with a mocked QProcess."""
    p = guiprocess.GUIProcess(0, 'testprocess')
    monkeypatch.setattr(p, '_proc', stubs.fake_qprocess())
    return p