예제 #1
0
    def spawn(self, win_id, userscript=False, quiet=False, *args):
        """Spawn a command in a shell.

        Note the {url} variable which gets replaced by the current URL might be
        useful here.

        //

        We use subprocess rather than Qt's QProcess here because we really
        don't care about the process anymore as soon as it's spawned.

        Args:
            userscript: Run the command as an userscript.
            quiet: Don't print the commandline being executed.
            *args: The commandline to execute.
        """
        log.procs.debug("Executing: {}, userscript={}".format(
            args, userscript))
        if not quiet:
            fake_cmdline = ' '.join(shlex.quote(arg) for arg in args)
            message.info(win_id, 'Executing: ' + fake_cmdline)
        if userscript:
            cmd = args[0]
            args = [] if not args else args[1:]
            self.run_userscript(cmd, *args)
        else:
            try:
                subprocess.Popen(args)
            except OSError as e:
                raise cmdexc.CommandError("Error while spawning command: "
                                          "{}".format(e))
예제 #2
0
 def on_lists_downloaded(self):
     """Install block lists after files have been downloaded."""
     with open(self._local_hosts_file, 'w', encoding='utf-8') as f:
         for host in sorted(self._blocked_hosts):
             f.write(host + '\n')
         message.info("adblock: Read {} hosts from {} sources.".format(
             len(self._blocked_hosts), self._done_count))
예제 #3
0
def message_info(text):
    """Show an info message in the statusbar.

    Args:
        text: The text to show.
    """
    message.info(text)
예제 #4
0
    def yank(self, url, context):
        """Yank an element to the clipboard or primary selection.

        Args:
            url: The URL to open as a QUrl.
            context: The HintContext to use.
        """
        sel = (context.target == Target.yank_primary and
               utils.supports_selection())

        flags = QUrl.FullyEncoded | QUrl.RemovePassword
        if url.scheme() == 'mailto':
            flags |= QUrl.RemoveScheme
        urlstr = url.toString(flags)

        new_content = urlstr

        # only second and consecutive yanks are to append to the clipboard
        if context.rapid and not context.first_run:
            try:
                old_content = utils.get_clipboard(selection=sel)
            except utils.ClipboardEmptyError:
                pass
            else:
                new_content = os.linesep.join([old_content, new_content])
        utils.set_clipboard(new_content, selection=sel)

        msg = "Yanked URL to {}: {}".format(
            "primary selection" if sel else "clipboard",
            urlstr)
        message.info(msg)
예제 #5
0
    def on_finished(self, code, status):
        """Show a message when the process finished."""
        self._started = False
        log.procs.debug("Process finished with code {}, status {}.".format(
            code, status))
        if status == QProcess.CrashExit:
            message.error(self._win_id,
                          "{} crashed!".format(self._what.capitalize()),
                          immediately=True)
        elif status == QProcess.NormalExit and code == 0:
            if self.verbose:
                message.info(self._win_id, "{} exited successfully.".format(
                    self._what.capitalize()))
        else:
            assert status == QProcess.NormalExit
            # We call this 'status' here as it makes more sense to the user -
            # it's actually 'code'.
            message.error(self._win_id, "{} exited with status {}.".format(
                self._what.capitalize(), code))

            stderr = bytes(self._proc.readAllStandardError()).decode('utf-8')
            stdout = bytes(self._proc.readAllStandardOutput()).decode('utf-8')
            if stdout:
                log.procs.error("Process stdout:\n" + stdout.strip())
            if stderr:
                log.procs.error("Process stderr:\n" + stderr.strip())
예제 #6
0
    def import_txt(self):
        """Import a history text file into sqlite if it exists.

        In older versions of qutebrowser, history was stored in a text format.
        This converts that file into the new sqlite format and moves it to a
        backup location.
        """
        path = os.path.join(standarddir.data(), 'history')
        if not os.path.isfile(path):
            return

        def action():
            with debug.log_time(log.init, 'Import old history file to sqlite'):
                try:
                    self._read(path)
                except ValueError as ex:
                    message.error('Failed to import history: {}'.format(ex))
                else:
                    bakpath = path + '.bak'
                    message.info('History import complete. Moving {} to {}'
                                 .format(path, bakpath))
                    os.rename(path, bakpath)

        # delay to give message time to appear before locking down for import
        message.info('Converting {} to sqlite...'.format(path))
        QTimer.singleShot(100, action)
예제 #7
0
    def set_command(self, win_id: {'special': 'win_id'},
                    sectname: {'name': 'section'}, optname: {'name': 'option'},
                    value=None, temp=False):
        """Set an option.

        If the option name ends with '?', the value of the option is shown
        instead.

        //

        Wrapper for self.set() to output exceptions in the status bar.

        Args:
            sectname: The section where the option is in.
            optname: The name of the option.
            value: The value to set.
            temp: Set value temporarily.
        """
        try:
            if optname.endswith('?'):
                val = self.get(sectname, optname[:-1], transformed=False)
                message.info(win_id, "{} {} = {}".format(
                    sectname, optname[:-1], val), immediately=True)
            else:
                if value is None:
                    raise cmdexc.CommandError("set: The following arguments "
                                              "are required: value")
                layer = 'temp' if temp else 'conf'
                self.set(layer, sectname, optname, value)
        except (NoOptionError, NoSectionError, configtypes.ValidationError,
                ValueError) as e:
            raise cmdexc.CommandError("set: {} - {}".format(
                e.__class__.__name__, e))
예제 #8
0
    def _handle_wheel(self, e):
        """Zoom on Ctrl-Mousewheel.

        Args:
            e: The QWheelEvent.
        """
        if self._ignore_wheel_event:
            # See https://github.com/qutebrowser/qutebrowser/issues/395
            self._ignore_wheel_event = False
            return True

        if e.modifiers() & Qt.ControlModifier:
            divider = config.get('input', 'mouse-zoom-divider')
            if divider == 0:
                return False
            factor = self._tab.zoom.factor() + (e.angleDelta().y() / divider)
            if factor < 0:
                return False
            perc = int(100 * factor)
            message.info("Zoom level: {}%".format(perc), replace=True)
            self._tab.zoom.set_factor(factor)
        elif e.modifiers() & Qt.ShiftModifier:
            if e.angleDelta().y() > 0:
                self._tab.scroller.left()
            else:
                self._tab.scroller.right()
            return True

        return False
예제 #9
0
    def _on_finished(self, code, status):
        """Show a message when the process finished."""
        self._started = False
        log.procs.debug("Process finished with code {}, status {}.".format(
            code, status))

        encoding = locale.getpreferredencoding(do_setlocale=False)
        stderr = bytes(self._proc.readAllStandardError()).decode(
            encoding, 'replace')
        stdout = bytes(self._proc.readAllStandardOutput()).decode(
            encoding, 'replace')

        if status == QProcess.CrashExit:
            exitinfo = "{} crashed!".format(self._what.capitalize())
            message.error(exitinfo)
        elif status == QProcess.NormalExit and code == 0:
            exitinfo = "{} exited successfully.".format(
                self._what.capitalize())
            if self.verbose:
                message.info(exitinfo)
        else:
            assert status == QProcess.NormalExit
            # We call this 'status' here as it makes more sense to the user -
            # it's actually 'code'.
            exitinfo = ("{} exited with status {}, see :messages for "
                        "details.").format(self._what.capitalize(), code)
            message.error(exitinfo)

            if stdout:
                log.procs.error("Process stdout:\n" + stdout.strip())
            if stderr:
                log.procs.error("Process stderr:\n" + stderr.strip())

        qutescheme.spawn_output = self._spawn_format(exitinfo, stdout, stderr)
예제 #10
0
    def session_save(self, win_id, name: {'type': str}=default, current=False,
                     quiet=False, force=False):
        """Save a session.

        Args:
            win_id: The current window ID.
            name: The name of the session. If not given, the session configured
                  in general -> session-default-name is saved.
            current: Save the current session instead of the default.
            quiet: Don't show confirmation message.
            force: Force saving internal sessions (starting with an underline).
        """
        if (name is not default and
                name.startswith('_') and  # pylint: disable=no-member
                not force):
            raise cmdexc.CommandError("{} is an internal session, use --force "
                                      "to save anyways.".format(name))
        if current:
            if self._current is None:
                raise cmdexc.CommandError("No session loaded currently!")
            name = self._current
            assert not name.startswith('_')
        try:
            name = self.save(name)
        except SessionError as e:
            raise cmdexc.CommandError("Error while saving session: {}"
                                      .format(e))
        else:
            if not quiet:
                message.info(win_id, "Saved session {}.".format(name),
                             immediately=True)
예제 #11
0
    def _handle_wheel(self, e):
        """Zoom on Ctrl-Mousewheel.

        Args:
            e: The QWheelEvent.
        """
        if self._ignore_wheel_event:
            # See https://github.com/qutebrowser/qutebrowser/issues/395
            self._ignore_wheel_event = False
            return True

        if e.modifiers() & Qt.ControlModifier:
            mode = modeman.instance(self._tab.win_id).mode
            if mode == usertypes.KeyMode.passthrough:
                return False

            divider = config.val.zoom.mouse_divider
            if divider == 0:
                return False
            factor = self._tab.zoom.factor() + (e.angleDelta().y() / divider)
            if factor < 0:
                return False
            perc = int(100 * factor)
            message.info("Zoom level: {}%".format(perc), replace=True)
            self._tab.zoom.set_factor(factor)
        elif e.modifiers() & Qt.ShiftModifier:
            if e.angleDelta().y() > 0:
                self._tab.scroller.left()
            else:
                self._tab.scroller.right()
            return True

        return False
예제 #12
0
    def session_save(self, name: str = default, current=False, quiet=False,
                     force=False, only_active_window=False, with_private=False,
                     win_id=None):
        """Save a session.

        Args:
            name: The name of the session. If not given, the session configured
                  in general -> session-default-name is saved.
            current: Save the current session instead of the default.
            quiet: Don't show confirmation message.
            force: Force saving internal sessions (starting with an underline).
            only_active_window: Saves only tabs of the currently active window.
            with_private: Include private windows.
        """
        if name is not default and name.startswith('_') and not force:
            raise cmdexc.CommandError("{} is an internal session, use --force "
                                      "to save anyways.".format(name))
        if current:
            if self._current is None:
                raise cmdexc.CommandError("No session loaded currently!")
            name = self._current
            assert not name.startswith('_')
        try:
            if only_active_window:
                name = self.save(name, only_window=win_id,
                                 with_private=with_private)
            else:
                name = self.save(name, with_private=with_private)
        except SessionError as e:
            raise cmdexc.CommandError("Error while saving session: {}"
                                      .format(e))
        else:
            if not quiet:
                message.info("Saved session {}.".format(name))
예제 #13
0
파일: config.py 프로젝트: JIVS/qutebrowser
    def set_command(self, win_id: {'special': 'win_id'},
                    sectname: {'name': 'section'}=None,
                    optname: {'name': 'option'}=None, value=None, temp=False,
                    print_val: {'name': 'print'}=False):
        """Set an option.

        If the option name ends with '?', the value of the option is shown
        instead.

        If the option name ends with '!' and it is a boolean value, toggle it.

        //

        Wrapper for self.set() to output exceptions in the status bar.

        Args:
            sectname: The section where the option is in.
            optname: The name of the option.
            value: The value to set.
            temp: Set value temporarily.
            print_val: Print the value after setting.
        """
        if sectname is not None and optname is None:
            raise cmdexc.CommandError(
                "set: Either both section and option have to be given, or "
                "neither!")
        if sectname is None and optname is None:
            tabbed_browser = objreg.get('tabbed-browser', scope='window',
                                        window=win_id)
            tabbed_browser.openurl(QUrl('qute:settings'), newtab=False)
            return

        if optname.endswith('?'):
            optname = optname[:-1]
            print_val = True
        else:
            try:
                if optname.endswith('!') and value is None:
                    val = self.get(sectname, optname[:-1])
                    layer = 'temp' if temp else 'conf'
                    if isinstance(val, bool):
                        self.set(layer, sectname, optname[:-1], str(not val))
                    else:
                        raise cmdexc.CommandError(
                            "set: Attempted inversion of non-boolean value.")
                elif value is not None:
                    layer = 'temp' if temp else 'conf'
                    self.set(layer, sectname, optname, value)
                else:
                    raise cmdexc.CommandError("set: The following arguments "
                                              "are required: value")
            except (configexc.Error, configparser.Error) as e:
                raise cmdexc.CommandError("set: {} - {}".format(
                    e.__class__.__name__, e))

        if print_val:
            val = self.get(sectname, optname, transformed=False)
            message.info(win_id, "{} {} = {}".format(
                sectname, optname, val), immediately=True)
예제 #14
0
 def check_scroll_pos():
     """Check if the scroll position got smaller and show info."""
     if not backward and self.scroll_pos < old_scroll_pos:
         message.info(self.win_id, "Search hit BOTTOM, continuing "
                      "at TOP", immediately=True)
     elif backward and self.scroll_pos > old_scroll_pos:
         message.info(self.win_id, "Search hit TOP, continuing at "
                      "BOTTOM", immediately=True)
예제 #15
0
 def _write_backup(self, path):
     bak = path + '.bak'
     message.info('History import complete. Appending {} to {}'
                  .format(path, bak))
     with open(path, 'r', encoding='utf-8') as infile:
         with open(bak, 'a', encoding='utf-8') as outfile:
             for line in infile:
                 outfile.write('\n' + line)
     os.remove(path)
예제 #16
0
 def _pre_start(self, cmd, args):
     """Prepare starting of a QProcess."""
     if self._started:
         raise ValueError("Trying to start a running QProcess!")
     self.cmd = cmd
     self.args = args
     if self.verbose:
         fake_cmdline = ' '.join(shlex.quote(e) for e in [cmd] + list(args))
         message.info(self._win_id, 'Executing: ' + fake_cmdline)
예제 #17
0
def message_info(text, count=1):
    """Show an info message in the statusbar.

    Args:
        text: The text to show.
        count: How many times to show the message
    """
    for _ in range(count):
        message.info(text)
예제 #18
0
    def _print_value(self, option, pattern):
        """Print the value of the given option."""
        with self._handle_config_error():
            value = self._config.get_str(option, pattern=pattern)

        text = "{} = {}".format(option, value)
        if pattern is not None:
            text += " for {}".format(pattern)
        message.info(text)
예제 #19
0
    def set_command(self, win_id, section_=None, option=None, value=None,
                    temp=False, print_=False):
        """Set an option.

        If the option name ends with '?', the value of the option is shown
        instead.

        If the option name ends with '!' and it is a boolean value, toggle it.

        //

        Wrapper for self.set() to output exceptions in the status bar.

        Args:
            section_: The section where the option is in.
            option: The name of the option.
            value: The value to set.
            temp: Set value temporarily.
            print_: Print the value after setting.
        """
        if section_ is not None and option is None:
            raise cmdexc.CommandError(
                "set: Either both section and option have to be given, or "
                "neither!")
        if section_ is None and option is None:
            tabbed_browser = objreg.get('tabbed-browser', scope='window',
                                        window=win_id)
            tabbed_browser.openurl(QUrl('qute:settings'), newtab=False)
            return

        if option.endswith('?') and option != '?':
            option = option[:-1]
            print_ = True
        else:
            with self._handle_config_error():
                if option.endswith('!') and option != '!' and value is None:
                    option = option[:-1]
                    val = self.get(section_, option)
                    layer = 'temp' if temp else 'conf'
                    if isinstance(val, bool):
                        self.set(layer, section_, option, str(not val))
                    else:
                        raise cmdexc.CommandError(
                            "set: Attempted inversion of non-boolean value.")
                elif value is not None:
                    layer = 'temp' if temp else 'conf'
                    self.set(layer, section_, option, value)
                else:
                    raise cmdexc.CommandError("set: The following arguments "
                                              "are required: value")

        if print_:
            with self._handle_config_error():
                val = self.get(section_, option, transformed=False)
            message.info(win_id, "{} {} = {}".format(
                section_, option, val), immediately=True)
예제 #20
0
 def backup(self):
     """Create a backup if the content has changed from the original."""
     if not self._content:
         return
     try:
         fname = self._create_tempfile(self._content,
                                       'qutebrowser-editor-backup-')
         message.info('Editor backup at {}'.format(fname))
     except OSError as e:
         message.error('Failed to create editor backup: {}'.format(e))
예제 #21
0
 def _pre_start(self, cmd, args):
     """Prepare starting of a QProcess."""
     if self._started:
         raise ValueError("Trying to start a running QProcess!")
     self.cmd = cmd
     self.args = args
     fake_cmdline = ' '.join(shlex.quote(e) for e in [cmd] + list(args))
     log.procs.debug("Executing: {}".format(fake_cmdline))
     if self.verbose:
         message.info('Executing: ' + fake_cmdline)
예제 #22
0
파일: adblock.py 프로젝트: iggy/qutebrowser
 def read_hosts(self):
     """Read hosts from the existing blocked-hosts file."""
     self.blocked_hosts = set()
     if os.path.exists(self._hosts_file):
         with open(self._hosts_file, 'r', encoding='utf-8') as f:
             for line in f:
                 self.blocked_hosts.add(line.strip())
     else:
         if config.get('content', 'host-block-lists') is not None:
             message.info('last-focused',
                          "Run :adblock-update to get adblock lists.")
예제 #23
0
 def action():
     with debug.log_time(log.init, 'Import old history file to sqlite'):
         try:
             self._read(path)
         except ValueError as ex:
             message.error('Failed to import history: {}'.format(ex))
         else:
             bakpath = path + '.bak'
             message.info('History import complete. Moving {} to {}'
                          .format(path, bakpath))
             os.rename(path, bakpath)
예제 #24
0
 def _on_mouse_wheel_zoom(self, delta):
     """Handle zooming via mousewheel requested by the web view."""
     divider = config.get('input', 'mouse-zoom-divider')
     factor = self.factor() + delta.y() / divider
     if factor < 0:
         return
     perc = int(100 * factor)
     message.info(self._win_id, "Zoom level: {}%".format(perc))
     self._neighborlist.fuzzyval = perc
     self._set_factor_internal(factor)
     self._default_zoom_changed = True
예제 #25
0
파일: hints.py 프로젝트: pyrho/qutebrowser
    def _yank(self, url):
        """Yank an element to the clipboard or primary selection.

        Args:
            url: The URL to open as a QURL.
        """
        sel = self._context.target == Target.yank_primary
        mode = QClipboard.Selection if sel else QClipboard.Clipboard
        urlstr = url.toString(QUrl.FullyEncoded | QUrl.RemovePassword)
        QApplication.clipboard().setText(urlstr, mode)
        message.info(self._win_id, "URL yanked to {}".format(
            "primary selection" if sel else "clipboard"))
예제 #26
0
 def on_finished(self, code, status):
     """Show a message when the process finished."""
     self._started = False
     log.procs.debug("Process finished with code {}, status {}.".format(code, status))
     if status == QProcess.CrashExit:
         message.error(self._win_id, "{} crashed!".format(self._what.capitalize()), immediately=True)
     elif status == QProcess.NormalExit and code == 0:
         if self.verbose:
             message.info(self._win_id, "{} exited successfully.".format(self._what.capitalize()))
     else:
         assert status == QProcess.NormalExit
         message.error(self._win_id, "{} exited with status {}.".format(self._what.capitalize(), code))
예제 #27
0
    def zoom_perc(self, perc, fuzzyval=True):
        """Zoom to a given zoom percentage.

        Args:
            perc: The zoom percentage as int.
            fuzzyval: Whether to set the NeighborLists fuzzyval.
        """
        if fuzzyval:
            self._zoom.fuzzyval = int(perc)
        if perc < 0:
            raise cmdexc.CommandError("Can't zoom {}%!".format(perc))
        self.setZoomFactor(float(perc) / 100)
        message.info(self._win_id, "Zoom level: {}%".format(perc))
예제 #28
0
    def yank(self, url, context):
        """Yank an element to the clipboard or primary selection.

        Args:
            url: The URL to open as a QUrl.
            context: The HintContext to use.
        """
        sel = context.target == Target.yank_primary and utils.supports_selection()

        urlstr = url.toString(QUrl.FullyEncoded | QUrl.RemovePassword)
        utils.set_clipboard(urlstr, selection=sel)

        msg = "Yanked URL to {}: {}".format("primary selection" if sel else "clipboard", urlstr)
        message.info(msg)
예제 #29
0
파일: adblock.py 프로젝트: JIVS/qutebrowser
 def read_hosts(self):
     """Read hosts from the existing blocked-hosts file."""
     self.blocked_hosts = set()
     if os.path.exists(self._hosts_file):
         try:
             with open(self._hosts_file, 'r', encoding='utf-8') as f:
                 for line in f:
                     self.blocked_hosts.add(line.strip())
         except OSError:
             log.misc.exception("Failed to read host blocklist!")
     else:
         if config.get('content', 'host-block-lists') is not None:
             message.info('current',
                          "Run :adblock-update to get adblock lists.")
예제 #30
0
    def read_hosts(self):
        """Read hosts from the existing blocked-hosts file."""
        self._blocked_hosts = set()

        self._read_hosts_file(self._config_hosts_file,
                              self._config_blocked_hosts)

        found = self._read_hosts_file(self._local_hosts_file,
                                      self._blocked_hosts)

        if not found:
            args = objreg.get('args')
            if (config.get('content', 'host-block-lists') is not None and
                    args.basedir is None):
                message.info("Run :adblock-update to get adblock lists.")
예제 #31
0
    def yank(self, url, context):
        """Yank an element to the clipboard or primary selection.

        Args:
            url: The URL to open as a QUrl.
            context: The HintContext to use.
        """
        sel = (context.target == Target.yank_primary
               and utils.supports_selection())

        flags = QUrl.FullyEncoded | QUrl.RemovePassword
        if url.scheme() == 'mailto':
            flags |= QUrl.RemoveScheme
        urlstr = url.toString(flags)
        utils.set_clipboard(urlstr, selection=sel)

        msg = "Yanked URL to {}: {}".format(
            "primary selection" if sel else "clipboard", urlstr)
        message.info(msg)
예제 #32
0
    def read_hosts(self):
        """Read hosts from the existing blocked-hosts file."""
        self._blocked_hosts = set()

        if self._local_hosts_file is None:
            return

        self._read_hosts_file(self._config_hosts_file,
                              self._config_blocked_hosts)

        found = self._read_hosts_file(self._local_hosts_file,
                                      self._blocked_hosts)

        if not found:
            args = objreg.get('args')
            if (config.get('content', 'host-block-lists') is not None and
                    args.basedir is None):
                message.info('current',
                             "Run :adblock-update to get adblock lists.")
예제 #33
0
    def bind(self, key, command=None, *, mode='normal', force=False):
        """Bind a key to a command.

        Args:
            key: The keychain or special key (inside `<...>`) to bind.
            command: The command to execute, with optional args, or None to
                     print the current binding.
            mode: A comma-separated list of modes to bind the key in
                  (default: `normal`).
            force: Rebind the key if it is already bound.
        """
        if utils.is_special_key(key):
            # <Ctrl-t>, <ctrl-T>, and <ctrl-t> should be considered equivalent
            key = key.lower()

        if command is None:
            cmd = self.get_bindings_for(mode).get(key, None)
            if cmd is None:
                message.info("{} is unbound in {} mode".format(key, mode))
            else:
                message.info("{} is bound to '{}' in {} mode".format(key, cmd,
                                                                     mode))
            return

        modenames = self._normalize_sectname(mode).split(',')
        for m in modenames:
            if m not in configdata.KEY_DATA:
                raise cmdexc.CommandError("Invalid mode {}!".format(m))
        try:
            modes = [usertypes.KeyMode[m] for m in modenames]
            self._validate_command(command, modes)
        except KeyConfigError as e:
            raise cmdexc.CommandError(str(e))
        try:
            self._add_binding(mode, key, command, force=force)
        except DuplicateKeychainError as e:
            raise cmdexc.CommandError("Duplicate keychain {} - use --force to "
                                      "override!".format(str(e.keychain)))
        except KeyConfigError as e:
            raise cmdexc.CommandError(e)
        for m in modenames:
            self.changed.emit(m)
            self._mark_config_dirty()
예제 #34
0
    def prompt_yank(self, sel=False):
        """Yank URL to clipboard or primary selection.

        Args:
            sel: Use the primary selection instead of the clipboard.
        """
        if self._prompt is None:
            raise AssertionError
        question = self._prompt.question
        if question.url is None:
            message.error('No URL found.')
            return
        if sel and utils.supports_selection():
            target = 'primary selection'
        else:
            sel = False
            target = 'clipboard'
        utils.set_clipboard(question.url, sel)
        message.info("Yanked to {}: {}".format(target, question.url))
예제 #35
0
    def debug_dump_history(self, dest):
        """Dump the history to a file in the old pre-SQL format.

        Args:
            dest: Where to write the file to.
        """
        dest = os.path.expanduser(dest)

        lines = ('{}{} {} {}'.format(int(x.atime), '-r' * x.redirect, x.url,
                                     x.title)
                 for x in self.select(sort_by='atime', sort_order='asc'))

        try:
            with open(dest, 'w', encoding='utf-8') as f:
                f.write('\n'.join(lines))
            message.info("Dumped history to {}".format(dest))
        except OSError as e:
            raise cmdutils.CommandError(
                'Could not write history: {}'.format(e))
예제 #36
0
    def bind(self, win_id: str, key: str = None, command: str = None, *,
             mode: str = 'normal', default: bool = False) -> None:
        """Bind a key to a command.

        If no command is given, show the current binding for the given key.
        Using :bind without any arguments opens a page showing all keybindings.

        Args:
            key: The keychain to bind. Examples of valid keychains are `gC`,
                 `<Ctrl-X>` or `<Ctrl-C>a`.
            command: The command to execute, with optional args.
            mode: The mode to bind the key in (default: `normal`). See `:help
                  bindings.commands` for the available modes.
            default: If given, restore a default binding.
        """
        if key is None:
            tabbed_browser = objreg.get('tabbed-browser', scope='window',
                                        window=win_id)
            tabbed_browser.load_url(QUrl('qute://bindings'), newtab=True)
            return

        seq = self._parse_key(key)

        if command is None:
            if default:
                # :bind --default: Restore default
                with self._handle_config_error():
                    self._keyconfig.bind_default(seq, mode=mode,
                                                 save_yaml=True)
                return

            # No --default -> print binding
            with self._handle_config_error():
                cmd = self._keyconfig.get_command(seq, mode)
            if cmd is None:
                message.info("{} is unbound in {} mode".format(seq, mode))
            else:
                message.info("{} is bound to '{}' in {} mode".format(
                    seq, cmd, mode))
            return

        with self._handle_config_error():
            self._keyconfig.bind(seq, command, mode=mode, save_yaml=True)
예제 #37
0
    def wheelEvent(self, e):
        """Zoom on Ctrl-Mousewheel.

        Args:
            e: The QWheelEvent.
        """
        if e.modifiers() & Qt.ControlModifier:
            e.accept()
            divider = config.get('input', 'mouse-zoom-divider')
            factor = self.zoomFactor() + e.angleDelta().y() / divider
            if factor < 0:
                return
            perc = int(100 * factor)
            message.info(self.win_id, "Zoom level: {}%".format(perc))
            self._zoom.fuzzyval = perc
            self.setZoomFactor(factor)
            self._default_zoom_changed = True
        else:
            super().wheelEvent(e)
예제 #38
0
    def session_save(self, name: typing.Union[str, Sentinel] = default,
                     current: bool = False,
                     quiet: bool = False,
                     force: bool = False,
                     only_active_window: bool = False,
                     with_private: bool = False,
                     win_id: int = None) -> None:
        """Save a session.

        Args:
            name: The name of the session. If not given, the session configured
                  in session.default_name is saved.
            current: Save the current session instead of the default.
            quiet: Don't show confirmation message.
            force: Force saving internal sessions (starting with an underline).
            only_active_window: Saves only tabs of the currently active window.
            with_private: Include private windows.
        """
        if (not isinstance(name, Sentinel) and
                name.startswith('_') and
                not force):
            raise cmdutils.CommandError("{} is an internal session, use "
                                        "--force to save anyways."
                                        .format(name))
        if current:
            if self._current is None:
                raise cmdutils.CommandError("No session loaded currently!")
            name = self._current
            assert not name.startswith('_')
        try:
            if only_active_window:
                name = self.save(name, only_window=win_id,
                                 with_private=True)
            else:
                name = self.save(name, with_private=with_private)
        except SessionError as e:
            raise cmdutils.CommandError("Error while saving session: {}"
                                        .format(e))
        else:
            if quiet:
                log.sessions.debug("Saved session {}.".format(name))
            else:
                message.info("Saved session {}.".format(name))
예제 #39
0
    def _on_finished(self, code, status):
        """Show a message when the process finished."""
        self._started = False
        log.procs.debug("Process finished with code {}, status {}.".format(
            code, status))

        encoding = locale.getpreferredencoding(do_setlocale=False)
        stderr = self._proc.readAllStandardError().data().decode(
            encoding, 'replace')
        stdout = self._proc.readAllStandardOutput().data().decode(
            encoding, 'replace')

        if self._output_messages:
            if stdout:
                message.info(stdout.strip())
            if stderr:
                message.error(stderr.strip())

        if status == QProcess.CrashExit:
            exitinfo = "{} crashed.".format(self._what.capitalize())
            message.error(exitinfo)
        elif status == QProcess.NormalExit and code == 0:
            exitinfo = "{} exited successfully.".format(
                self._what.capitalize())
            if self.verbose:
                message.info(exitinfo)
        else:
            assert status == QProcess.NormalExit
            # We call this 'status' here as it makes more sense to the user -
            # it's actually 'code'.
            exitinfo = ("{} exited with status {}, see :messages for "
                        "details.").format(self._what.capitalize(), code)
            message.error(exitinfo)

            if stdout:
                log.procs.error("Process stdout:\n" + stdout.strip())
            if stderr:
                log.procs.error("Process stderr:\n" + stderr.strip())

        qutescheme.spawn_output = self._spawn_format(exitinfo, stdout, stderr)
        self.final_stdout = stdout
        self.final_stderr = stderr
예제 #40
0
    def prompt_fileselect_external(self):
        """Choose a location using a configured external picker.

        This spawns the external fileselector configured via
        `fileselect.folder.command`.
        """
        assert self._prompt is not None
        if not isinstance(self._prompt, FilenamePrompt):
            raise cmdutils.CommandError(
                "Can only launch external fileselect for FilenamePrompt, "
                f"not {self._prompt.__class__.__name__}")
        # XXX to avoid current cyclic import
        from qutebrowser.browser import shared
        folders = shared.choose_file(shared.FileSelectionMode.folder)
        if not folders:
            message.info("No folder chosen.")
            return
        # choose_file already checks that this is max one folder
        assert len(folders) == 1
        self.prompt_accept(folders[0])
예제 #41
0
 def on_finished(self, code, status):
     """Show a message when the process finished."""
     self._started = False
     log.procs.debug("Process finished with code {}, status {}.".format(
         code, status))
     if status == QProcess.CrashExit:
         message.error(self._win_id,
                       "{} crashed!".format(self._what.capitalize()),
                       immediately=True)
     elif status == QProcess.NormalExit and code == 0:
         if self.verbose:
             message.info(
                 self._win_id,
                 "{} exited successfully.".format(self._what.capitalize()))
     else:
         assert status == QProcess.NormalExit
         message.error(
             self._win_id,
             "{} exited with status {}.".format(self._what.capitalize(),
                                                code))
예제 #42
0
    def set_command(self, win_id: {'special': 'win_id'},
                    sectname: {'name': 'section'}=None,
                    optname: {'name': 'option'}=None, value=None, temp=False):
        """Set an option.

        If the option name ends with '?', the value of the option is shown
        instead.

        //

        Wrapper for self.set() to output exceptions in the status bar.

        Args:
            sectname: The section where the option is in.
            optname: The name of the option.
            value: The value to set.
            temp: Set value temporarily.
        """
        if sectname is not None and optname is None:
            raise cmdexc.CommandError(
                "set: Either both section and option have to be given, or "
                "neither!")
        if sectname is None and optname is None:
            tabbed_browser = objreg.get('tabbed-browser', scope='window',
                                        window=win_id)
            tabbed_browser.openurl(QUrl('qute:settings'), newtab=False)
            return
        try:
            if optname.endswith('?'):
                val = self.get(sectname, optname[:-1], transformed=False)
                message.info(win_id, "{} {} = {}".format(
                    sectname, optname[:-1], val), immediately=True)
            else:
                if value is None:
                    raise cmdexc.CommandError("set: The following arguments "
                                              "are required: value")
                layer = 'temp' if temp else 'conf'
                self.set(layer, sectname, optname, value)
        except (configexc.Error, configparser.Error) as e:
            raise cmdexc.CommandError("set: {} - {}".format(
                e.__class__.__name__, e))
예제 #43
0
        def _selection_callback(text):
            if not text and not quiet:
                message.info("Nothing selected")

            def inthread():
                with tempfile.NamedTemporaryFile(mode='wt',
                                                 encoding='utf-8') as infile:
                    infile.write(text)
                    infile.flush()

                    tempname = '"' + infile.name + '"'
                    cmd = f"x-terminal-emulator-exe {prefix} 'cat {tempname} | {program} {maybeshell}'"
                    p = subprocess.Popen(['sh', '-c', cmd])
                    p.wait()

                    if p.returncode != 0 and not quiet:
                        message.error(
                            f'shell-send process "{cmd}" failed with: {p.returncode}'
                        )

            threading.Thread(target=inthread).start()
예제 #44
0
    def _handle_wheel(self, e):
        """Zoom on Ctrl-Mousewheel.

        Args:
            e: The QWheelEvent.
        """
        if self._ignore_wheel_event:
            # See https://github.com/The-Compiler/qutebrowser/issues/395
            self._ignore_wheel_event = False
            return True

        if e.modifiers() & Qt.ControlModifier:
            divider = config.get('input', 'mouse-zoom-divider')
            factor = self._tab.zoom.factor() + (e.angleDelta().y() / divider)
            if factor < 0:
                return False
            perc = int(100 * factor)
            message.info(self._tab.win_id, "Zoom level: {}%".format(perc))
            self._tab.zoom.set_factor(factor)

        return False
예제 #45
0
    def session_save(self,
                     name: str = default,
                     current=False,
                     quiet=False,
                     force=False,
                     only_active_window=False,
                     with_private=False,
                     win_id=None):
        """Save a session.

        Args:
            name: The name of the session. If not given, the session configured
                  in general -> session-default-name is saved.
            current: Save the current session instead of the default.
            quiet: Don't show confirmation message.
            force: Force saving internal sessions (starting with an underline).
            only_active_window: Saves only tabs of the currently active window.
            with_private: Include private windows.
        """
        if name is not default and name.startswith('_') and not force:
            raise cmdexc.CommandError("{} is an internal session, use --force "
                                      "to save anyways.".format(name))
        if current:
            if self._current is None:
                raise cmdexc.CommandError("No session loaded currently!")
            name = self._current
            assert not name.startswith('_')
        try:
            if only_active_window:
                name = self.save(name,
                                 only_window=win_id,
                                 with_private=with_private)
            else:
                name = self.save(name, with_private=with_private)
        except SessionError as e:
            raise cmdexc.CommandError(
                "Error while saving session: {}".format(e))
        else:
            if not quiet:
                message.info("Saved session {}.".format(name))
예제 #46
0
    def _handle_wheel(self, e):
        """Zoom on Ctrl-Mousewheel.

        Args:
            e: The QWheelEvent.

        Return:
            True if the event should be filtered, False otherwise.
        """
        if self._ignore_wheel_event:
            # See https://github.com/qutebrowser/qutebrowser/issues/395
            self._ignore_wheel_event = False
            return True
        elif e.modifiers() & Qt.ControlModifier:
            mode = modeman.instance(self._tab.win_id).mode
            if mode == usertypes.KeyMode.passthrough:
                return False

            divider = config.val.zoom.mouse_divider
            if divider == 0:
                # Disable mouse zooming
                return True

            factor = self._tab.zoom.factor() + (e.angleDelta().y() / divider)
            if factor < 0:
                return True

            perc = int(100 * factor)
            message.info("Zoom level: {}%".format(perc), replace=True)
            self._tab.zoom.set_factor(factor)
            return True
        elif (e.modifiers() & Qt.ShiftModifier
              and not qtutils.version_check('5.9', compiled=False)):
            if e.angleDelta().y() > 0:
                self._tab.scroller.left()
            else:
                self._tab.scroller.right()
            return True

        return False
예제 #47
0
    def _pre_start(self, cmd: str, args: Sequence[str]) -> None:
        """Resolve the given command and prepare starting of a QProcess.

        Doing the resolving in Python here instead of letting Qt do it serves
        two purposes:

        - Being able to show a nicer error message without having to parse the
          string we get from Qt: https://bugreports.qt.io/browse/QTBUG-44769
        - Not running the file from the current directory on Unix with
          Qt < 5.15.? and 6.2.4, as a WORKAROUND for CVE-2022-25255:
          https://invent.kde.org/qt/qt/qtbase/-/merge_requests/139
          https://www.qt.io/blog/security-advisory-qprocess
          https://lists.qt-project.org/pipermail/announce/2022-February/000333.html
        """
        if self.outcome.running:
            raise ValueError("Trying to start a running QProcess!")
        self.cmd = cmd
        self.resolved_cmd = shutil.which(cmd)
        self.args = args
        log.procs.debug(f"Executing: {self}")
        if self.verbose:
            message.info(f'Executing: {self}')
예제 #48
0
    def _handle_wheel(self, e):
        """Zoom on Ctrl-Mousewheel.

        Args:
            e: The QWheelEvent.

        Return:
            True if the event should be filtered, False otherwise.
        """
        if self._ignore_wheel_event:
            # See https://github.com/qutebrowser/qutebrowser/issues/395
            self._ignore_wheel_event = False
            return True

        # Don't allow scrolling while hinting
        mode = modeman.instance(self._tab.win_id).mode
        if mode == usertypes.KeyMode.hint:
            return True

        elif e.modifiers() & Qt.ControlModifier:
            if mode == usertypes.KeyMode.passthrough:
                return False

            divider = config.val.zoom.mouse_divider
            if divider == 0:
                # Disable mouse zooming
                return True

            factor = self._tab.zoom.factor() + (e.angleDelta().y() / divider)
            if factor < 0:
                return True

            perc = int(100 * factor)
            message.info(f"Zoom level: {perc}%", replace='zoom-level')
            self._tab.zoom.set_factor(factor)
            return True

        return False
예제 #49
0
    def wheelEvent(self, e):
        """Zoom on Ctrl-Mousewheel.

        Args:
            e: The QWheelEvent.
        """
        if self._ignore_wheel_event:
            self._ignore_wheel_event = False
            # See https://github.com/The-Compiler/qutebrowser/issues/395
            return
        if e.modifiers() & Qt.ControlModifier:
            e.accept()
            divider = config.get('input', 'mouse-zoom-divider')
            factor = self.zoomFactor() + e.angleDelta().y() / divider
            if factor < 0:
                return
            perc = int(100 * factor)
            message.info(self.win_id, "Zoom level: {}%".format(perc))
            self._zoom.fuzzyval = perc
            self.setZoomFactor(factor)
            self._default_zoom_changed = True
        else:
            super().wheelEvent(e)
예제 #50
0
    def import_txt(self):
        """Import a history text file into sqlite if it exists.

        In older versions of qutebrowser, history was stored in a text format.
        This converts that file into the new sqlite format and moves it to a
        backup location.
        """
        path = os.path.join(standarddir.data(), 'history')
        if not os.path.isfile(path):
            return

        def action():
            with debug.log_time(log.init, 'Import old history file to sqlite'):
                try:
                    self._read(path)
                except ValueError as ex:
                    message.error('Failed to import history: {}'.format(ex))
                else:
                    self._write_backup(path)

        # delay to give message time to appear before locking down for import
        message.info('Converting {} to sqlite...'.format(path))
        QTimer.singleShot(100, action)
예제 #51
0
    def yank(self, title=False, sel=False):
        """Yank the current URL/title to the clipboard or primary selection.

        Args:
            sel: Use the primary selection instead of the clipboard.
            title: Yank the title instead of the URL.
        """
        clipboard = QApplication.clipboard()
        if title:
            s = self._tabbed_browser().tabText(self._current_index())
        else:
            s = self._current_url().toString(QUrl.FullyEncoded
                                             | QUrl.RemovePassword)
        if sel and clipboard.supportsSelection():
            mode = QClipboard.Selection
            target = "primary selection"
        else:
            mode = QClipboard.Clipboard
            target = "clipboard"
        log.misc.debug("Yanking to {}: '{}'".format(target, s))
        clipboard.setText(s, mode)
        what = 'Title' if title else 'URL'
        message.info(self._win_id, "{} yanked to {}".format(what, target))
예제 #52
0
    def bind(self, key, command=None, *, mode='normal', default=False):
        """Bind a key to a command.

        Args:
            key: The keychain or special key (inside `<...>`) to bind.
            command: The command to execute, with optional args, or None to
                     print the current binding.
            mode: A comma-separated list of modes to bind the key in
                  (default: `normal`). See `:help bindings.commands` for the
                  available modes.
            default: If given, restore a default binding.
        """
        if command is None:
            if default:
                # :bind --default: Restore default
                with self._handle_config_error():
                    self._keyconfig.bind_default(key,
                                                 mode=mode,
                                                 save_yaml=True)
                return

            # No --default -> print binding
            if utils.is_special_key(key):
                # self._keyconfig.get_command does this, but we also need it
                # normalized for the output below
                key = utils.normalize_keystr(key)
            with self._handle_config_error():
                cmd = self._keyconfig.get_command(key, mode)
            if cmd is None:
                message.info("{} is unbound in {} mode".format(key, mode))
            else:
                message.info("{} is bound to '{}' in {} mode".format(
                    key, cmd, mode))
            return

        with self._handle_config_error():
            self._keyconfig.bind(key, command, mode=mode, save_yaml=True)
예제 #53
0
    def set_command(self,
                    win_id: {'special': 'win_id'},
                    sectname: {'name': 'section'},
                    optname: {'name': 'option'},
                    value=None,
                    temp=False):
        """Set an option.

        If the option name ends with '?', the value of the option is shown
        instead.

        //

        Wrapper for self.set() to output exceptions in the status bar.

        Args:
            sectname: The section where the option is in.
            optname: The name of the option.
            value: The value to set.
            temp: Set value temporarily.
        """
        try:
            if optname.endswith('?'):
                val = self.get(sectname, optname[:-1], transformed=False)
                message.info(win_id,
                             "{} {} = {}".format(sectname, optname[:-1], val),
                             immediately=True)
            else:
                if value is None:
                    raise cmdexc.CommandError("set: The following arguments "
                                              "are required: value")
                layer = 'temp' if temp else 'conf'
                self.set(layer, sectname, optname, value)
        except (NoOptionError, NoSectionError, configtypes.ValidationError,
                ValueError) as e:
            raise cmdexc.CommandError("set: {} - {}".format(
                e.__class__.__name__, e))
예제 #54
0
    def bind(self, key, command=None, *, mode='normal', force=False):
        """Bind a key to a command.

        Args:
            key: The keychain or special key (inside `<...>`) to bind.
            command: The command to execute, with optional args, or None to
                     print the current binding.
            mode: A comma-separated list of modes to bind the key in
                  (default: `normal`). See `:help bindings.commands` for the
                  available modes.
            force: Rebind the key if it is already bound.
        """
        if command is None:
            if utils.is_special_key(key):
                # self._keyconfig.get_command does this, but we also need it
                # normalized for the output below
                key = utils.normalize_keystr(key)
            cmd = self._keyconfig.get_command(key, mode)
            if cmd is None:
                message.info("{} is unbound in {} mode".format(key, mode))
            else:
                message.info("{} is bound to '{}' in {} mode".format(
                    key, cmd, mode))
            return

        try:
            self._keyconfig.bind(key,
                                 command,
                                 mode=mode,
                                 force=force,
                                 save_yaml=True)
        except configexc.DuplicateKeyError as e:
            raise cmdexc.CommandError(
                "bind: {} - use --force to override!".format(e))
        except configexc.KeybindingError as e:
            raise cmdexc.CommandError("bind: {}".format(e))
예제 #55
0
    def on_finished(self, code, status):
        """Show a message when the process finished."""
        self._started = False
        log.procs.debug("Process finished with code {}, status {}.".format(
            code, status))
        if status == QProcess.CrashExit:
            message.error("{} crashed!".format(self._what.capitalize()))
        elif status == QProcess.NormalExit and code == 0:
            if self.verbose:
                message.info("{} exited successfully.".format(
                    self._what.capitalize()))
        else:
            assert status == QProcess.NormalExit
            # We call this 'status' here as it makes more sense to the user -
            # it's actually 'code'.
            message.error("{} exited with status {}.".format(
                self._what.capitalize(), code))

            stderr = bytes(self._proc.readAllStandardError()).decode('utf-8')
            stdout = bytes(self._proc.readAllStandardOutput()).decode('utf-8')
            if stdout:
                log.procs.error("Process stdout:\n" + stdout.strip())
            if stderr:
                log.procs.error("Process stderr:\n" + stderr.strip())
예제 #56
0
 def _yank_url(url: str) -> None:
     utils.set_clipboard(url)
     message.info("Version url {} yanked to clipboard.".format(url))
예제 #57
0
 def _print_value(self, option):
     """Print the value of the given option."""
     with self._handle_config_error():
         value = self._config.get_str(option)
     message.info("{} = {}".format(option, value))
예제 #58
0
    def _on_ready_read_stdout(self) -> None:
        if not self._output_messages:
            return

        self._process_text(self._proc.readAllStandardOutput(), 'stdout')
        message.info(self._elide_output(self.stdout), replace=f"stdout-{self.pid}")
예제 #59
0
def _open_special_pages(args):
    """Open special notification pages which are only shown once.

    Args:
        args: The argparse namespace.
    """
    if args.basedir is not None:
        # With --basedir given, don't open anything.
        return

    general_sect = configfiles.state['general']
    tabbed_browser = objreg.get('tabbed-browser', scope='window',
                                window='last-focused')

    pages = [
        # state, condition, URL
        ('quickstart-done',
         True,
         'https://www.qutebrowser.org/quickstart.html'),

        ('config-migration-shown',
         os.path.exists(os.path.join(standarddir.config(),
                                     'qutebrowser.conf')),
         'qute://help/configuring.html'),

        ('webkit-warning-shown',
         objects.backend == usertypes.Backend.QtWebKit,
         'qute://warning/webkit'),

        ('session-warning-shown',
         qtutils.version_check('5.15', compiled=False),
         'qute://warning/sessions'),
    ]

    if 'quickstart-done' not in general_sect:
        # New users aren't going to be affected by the Qt 5.15 session change much, as
        # they aren't used to qutebrowser saving the full back/forward history in
        # sessions.
        general_sect['session-warning-shown'] = '1'

    for state, condition, url in pages:
        if general_sect.get(state) != '1' and condition:
            tabbed_browser.tabopen(QUrl(url), background=False)
            general_sect[state] = '1'

    # Show changelog on new releases
    change = configfiles.state.qutebrowser_version_changed
    if change == configfiles.VersionChange.equal:
        return

    setting = config.val.changelog_after_upgrade
    if not change.matches_filter(setting):
        log.init.debug(
            f"Showing changelog is disabled (setting {setting}, change {change})")
        return

    try:
        changelog = resources.read_file('html/doc/changelog.html')
    except OSError as e:
        log.init.warning(f"Not showing changelog due to {e}")
        return

    qbversion = qutebrowser.__version__
    if f'id="v{qbversion}"' not in changelog:
        log.init.warning("Not showing changelog (anchor not found)")
        return

    message.info(f"Showing changelog after upgrade to qutebrowser v{qbversion}.")
    changelog_url = f'qute://help/changelog.html#v{qbversion}'
    tabbed_browser.tabopen(QUrl(changelog_url), background=False)
예제 #60
0
    def set_command(self,
                    win_id,
                    section_=None,
                    option=None,
                    value=None,
                    temp=False,
                    print_=False):
        """Set an option.

        If the option name ends with '?', the value of the option is shown
        instead.

        If the option name ends with '!' and it is a boolean value, toggle it.

        //

        Wrapper for self.set() to output exceptions in the status bar.

        Args:
            section_: The section where the option is in.
            option: The name of the option.
            value: The value to set.
            temp: Set value temporarily.
            print_: Print the value after setting.
        """
        if section_ is not None and option is None:
            raise cmdexc.CommandError(
                "set: Either both section and option have to be given, or "
                "neither!")
        if section_ is None and option is None:
            tabbed_browser = objreg.get('tabbed-browser',
                                        scope='window',
                                        window=win_id)
            tabbed_browser.openurl(QUrl('qute:settings'), newtab=False)
            return

        if option.endswith('?') and option != '?':
            option = option[:-1]
            print_ = True
        else:
            with self._handle_config_error():
                if option.endswith('!') and option != '!' and value is None:
                    option = option[:-1]
                    val = self.get(section_, option)
                    layer = 'temp' if temp else 'conf'
                    if isinstance(val, bool):
                        self.set(layer, section_, option, str(not val))
                    else:
                        raise cmdexc.CommandError(
                            "set: Attempted inversion of non-boolean value.")
                elif value is not None:
                    layer = 'temp' if temp else 'conf'
                    self.set(layer, section_, option, value)
                else:
                    raise cmdexc.CommandError("set: The following arguments "
                                              "are required: value")

        if print_:
            with self._handle_config_error():
                val = self.get(section_, option, transformed=False)
            message.info(win_id,
                         "{} {} = {}".format(section_, option, val),
                         immediately=True)