App+1 | 谈谈网页浏览器中的一个异类:qutebrowser
对以键盘操作为优先的用户来说,一旦学会了如何退出 Vim,可能不消多久,便想在肉眼可见之处使用 Vim 的这套模态编辑。多年来,不少软件都内置了 Vim 风格的按键绑定,来迎合这类用户的肌肉记忆。其中,更有一些开发者,决心将 Vim 的这套风格应用至网页浏览器——qutebrowser 便是其中典型。
模态浏览器
对应 Vim 这样的模态文本编辑器 (modal text editor),我姑且将 qutebrowser 称作为模态网页浏览器。在聊 qutebrowser 之前,先向从未接触过 Vim 的读者介绍下模态文本编辑器。
A modal text editor can be, as its name implies, in different modes. Depending on the current mode, keys have different effects: in insert mode most keys insert their character in the buffer, as in non-modal editors, but in normal (default) mode, keys have a different effect.
A modal text editor makes all these operations much more accessible, and easier to express. But they are not only about having convenient shortcuts.
—— Why Kakoune
如上所述,在 Vim 这样的模态编辑器中,不同模式下,同一个按键有着不同的行为与意义。即使你从未使用过 Vim,模态亦随处可见:当你切换大写锁定时,或是切换输入法时,你使用的就是一种模态界面。具体到 Vim,在普通模式 (Normal Mode) 下,当你按下 G,其实是执行了一个将光标移到文件末行的命令;而在插入模式 (Insert Mode) 下,你就只是输入了一个 G 罢了。下表是 qutebrowser 与 Vim 在普通模式下一些操作的对比:
| 按键 | Vim | qutebrowser |
| gg | 跳转到文件首行 | 跳转到网页顶部 |
| G | 跳转到文件末行 | 跳转到网页底部 |
| h/j/k/l | 充当方向键使光标在文件内移动 | 作为方向键来滚动网页 |
| Ctrl-f/Ctrl-b | 向下/向上翻一整页内容 | 向下/向上翻一整页内容 |
| d | d 表示 delete,按下 dd 以删除光标所在行的文本 | 按下 d 会关闭当前浏览的标签页 |
| y | y 表示 yank,按下 yy 以复制光标所在行的文本 | 按下 yy 以复制当前网页的链接 |
| . | 重复上一次操作,如再次删除当前行 | 重复上一次操作,如再次关闭页面 |
| u | u 表示 undo,可撤销刚才的操作,如恢复删除的那一行 | 撤销上一次操作,如重新打开之前关闭的标签页 |
| / | 在当前文本文件内进行搜索 | 在当前网页中搜索 |
| :wq | 保存当前文件并退出 Vim | 退出 qutebrowser,下次进入时将重新打开退出前的所有标签页 |


可见,qutebrowser 将网页视作 Vim 中的文本缓冲区 (text buffer),同样的按键在行为上也与 Vim 相贴近。而这样的设计,qutebrowser 并非首创。在介绍 qutebrowser 中的其他模式前,先简要回顾下这类「模态浏览器」的历史。
模态浏览器简史
模态网页浏览器大致可以分为两类:
- 以插件形式与传统浏览器伴生,为其赋予模态:如Vimperator、Pentadactyl、Tridactyl、Vimium等。
- 基于现有的Web引擎开发,完全为模态功能而构建的浏览器:如 Uzbl、dwb、Luakit、qutebrowser 等。
Vimperator 作为先行者,早已实现了模态浏览器所需的诸多核心功能。但由于宿主浏览器迭代带来的 API 变动,插件的兼容性问题层出不穷。随着 Firefox 从 XUL 转向 WebExtensions,插件所能行使的权限与能力被大幅收紧,导致其在体验上慢慢落后给那些基于 Web 引擎重新开发的模态浏览器。另一方面,如 dwb,luakit 等浏览器大多基于 Webkit 引擎,常常受累于其频发的性能与安全问题。2013 年底,Florian Bruhin 眼见 dwb 渐渐停止维护,便萌生了开发一款与之相似的浏览器的想法。这便是 qutebrowser 项目的开端。
体验qutebrowser
第一次打开 qutebrowser,你面对的可能会是这样的界面。

委婉地讲,模样比较复古。可假使你有 emacs 的使用经验,简陋的默认界面反倒给你自由客制化的信心。请忍耐片刻,在开始配置前,有必要先熟悉下 qutebrowser 中的几个重要模式。(美观起见,之后的截图均采用配置后的样式)

模式一览
普通模式 Normal Mode
普通模式,即 qutebrowser 的默认模式,也是平时使用最多的一个模式。除了让用户能以尽可能少的按键来浏览网页外,普通模式也充当入口,通过特定的几个按键来切换至其他模式。

普通模式下,除上文提及的用以滚动、关闭页面的一些快捷键外,常用的操作还有:
- 访问网址: 按下
o后,用户输入网址,网址将在当前页面打开。按键o其实是对 qutebrowser 内建命令open的绑定。许多命令都支持额外的参数,如O(大写)被默认绑定至open -t,意为将网址在新标签页中打开。 - 回退/前进/刷新: 使用按键
H/L在当前页面进行回退或前进。使用r(reload)
刷新页面。 - 切换标签页: 使用
K/J切换至上一个或下一个标签页。

qutebrowser 中,默认标签页的格式为 favicon + index + pagetitle。我们可以使用 Alt + 数字来切换至对应 index 的标签页,也可用数字 + d 来关闭对应的标签页。
命令模式 Command Mode
在普通模式下,当按下 :,我们便进入了命令模式。其中,最重要的命令是help,用以查询其他命令或配置项的文档。另一个重要的命令是spawn,用以唤起外部程序配合 qutebrowser 使用,比如使用 mpv 来播放页面中的视频。

大多数重要的命令都有其默认绑定。我会在之后介绍修改按键绑定的方法。
插入模式 Insert Mode
默认设置下,当你点击网页中的某个输入框时,你便进入了插入模式。你也可以在普通模式下按下i主动进入该模式。与传统浏览器中的输入体验不同,qutebrowser 允许你在插入模式下调用外部的编辑器进行文本操作。当你保存并退出编辑器后,编辑过的文本会直接回填至网页的输入框内。这意味着你可以在输入过程中使用你编辑器里的纠错、补全、甚至 AI 相关的功能。
提示模式 Hint Mode
浏览网页时,最常见的一种操作便是用鼠标点击打开网页上的某个链接。提示模式则让你仅通过键盘来完成此操作。

如上图所示,在普通模式下按 f 进入提示模式,接着按下页面上提示的字母,便可访问其对应的链接。
直通模式 Pass-Through Mode
一些网页(特别是 PWA 应用)本身会提供一些快捷键,无疑会与 qutebrowser 不同模式下的快捷键相冲突。在普通模式下,按下 Ctrl+v 进入 qutebrowser 的直通模式。该模式下,浏览器将被还原到一种无模式的状态,从而规避这类 Modal Error 的问题。
配置 qutebrowser
配置 qutebrowser 一般可采取两种方式:
通过访问 qute://settings进入内建的设置界面进行定制,改动后的值会存入你配置目录下的autoconfig.yml文件中。(访问qute://version来查看自己配置目录的具体位置)

或是直接编辑配置目录下的config.py文件。在 qutebrowser 内,可通过config-edit命令打开该文件。第一次配置时,可用config-write-py --default命令写入默认配置后再进行修改。修改完后,使用config-source命令重载配置文件。
为了方便管理,你可以将配置拆分为多份文件,再在 config.py中使用config.source来导入。
config.load_autoconfig(True)
config.source("conf.d/theme.py")
config.source("conf.d/options.py")
…
编辑配置时,使用 config.set或全局对象 c来设置选项:
config.set("tabs.position", "left")
# 等同于
c.tabs.position = "left"
使用 config.bind进行按键绑定:
# 在普通模式下将按键组合<Ctrl-h>绑定为命令history
config.bind("<Ctrl-h>", "history")
# 要在其他模式下添加绑定,需指定mode参数。
config.bind("<Ctrl-n>", "completion-item-focus next", mode="command")
配置过程中,请多用 :help来查看各命令/选项的文档,用:set和 :bind来查看某选项/某按键绑定在当前设置中的值。你也可以在我这份样例配置的基础上进行修改。

为什么我不再使用 qutebrowser
我一度将 qutebrowser 作为主力浏览器使用,但最终还是放弃了它。主要原因如下:
性能与安全问题
- qutebrowser 的网页渲染依赖 QtWebEngine,后者基于 Chromium。若你电脑安装的 QtWebEngine 版本滞后,你可能无法及时获取来自 Chromium 的安全更新。
- 有不少用户反应 qutebrowser 可能存在内存泄漏的问题,可见此 Issue。
贫瘠的插件生态
如上所述,QtWebEngine 基于 Chromium,但其目前尚不支持 Web Extensions,这意味着 qutebrowser 用户无法享受 Chrome 的插件生态。
虽然 qutebrowser 允许你编写 userscripts 对浏览器进行扩展,但一来 userscripts 提供的扩展能力有限,实现的效果往往无法与 Chrome 下的同类插件相比(如 qutebrowser 虽内建有广告屏蔽器,但无法做到如 AdGuard 那样的页面元素级的隐藏);二来自行开发维护所需的插件,也要耗费用户不少精力。
过于简陋的基础功能
我使用 qutebrowser,源自一次 degoogle 的尝试。qutebrowser 不要求你与任何账户绑定,虽然它没有用户系统,但由于其设置、书签和 userscripts 都是纯文本文件,你可以使用如 git 这样的工具在不同设备间同步它们。遗憾的是,qutebrowser 的缺陷不在于它缺乏必要的功能,而是这些必要的功能实现得太过基础,甚至简陋。
书签
qutebrowser 的书签分为 quickmark 和 bookmark 两种,在功能上两者却并没有本质的差别。新用户常为此困惑,作者也认为应该将两者合并(参见此 Issue),但目前该合并仍未完成。除开不必要的两种书签的问题,qutebrowser 还缺乏对书签文件夹、标签的支持,而这些功能在主流浏览器中都不成问题。
历史记录
如果说 qutebrowser 的书签功能尚可以通过一些第三方软件(如 linkding 这样的自架服务)来补足的话,管理 qutebrowser 的浏览历史记录则比较灾难了。

如图所示,qutebrowser 的历史页面仅仅显示过往记录,你无法进行勾选,或按浏览时间来批量地删除。qutebrowser 内建的命令history-clear只能删除全部的历史记录,这对任何使用过主流浏览器的用户来说都是难以接受的。
下载管理
qutebrowser 没有提供页面来显示之前的下载记录,且下载过程中无法暂停。
标签页管理
qutebrowser 不支持标签页分组。它的垂直标签实现也非常基础,没有折叠、树状分页的功能。
结语
对厌倦了种种AI噱头的浏览器用户来讲,qutebrowser 的全键盘交互令人耳目一新。在其核心卖点「模态浏览」上,qutebrowser 的实现相当成熟。但在浏览器应有的基础功能方面,它又显得比较粗糙敷衍。这些缺憾不能归咎给 qutebrowser 的作者,在调校 qutebrowser 期间,我越发感受到现代网页浏览器的庞杂繁复,以至于个人想要驱动这样的项目几乎成了一个无法完成的任务。无奈放弃后,最终我还是选择了一个 Ungoogled Chromium,回过头想,我也许不是那么地讨厌使用鼠标。

- 如果你仍对模态浏览器感兴趣,可关注 glide 这一项目,作者在 Firefox 的基础上进行开发,相信能解决我在本文提到的 qutebrowser 中基础功能、插件生态上的一些问题。
- 本文仅题图使用 AI 生成,由于这篇文章丢在草稿箱里已久,我也不记得当时生图用的 prompt 是什么了。
> 简单、好用、专注的写作软件,少数派为你呈现 🚀
> 守护孩子的天马行空,从零开始美术教育启蒙 🎨