bspwm 入门
2021年6月10日 - 10815 字

本文首发于 bilibili:bspwm 入门. 考虑到 b 站的专栏格式非常差,链接不支持、代码格式也不支持. 另外自己写的东西放在一个商业公司的平台上不如放在自己手里,所以在这里也发一份.

我之前的视频(BV1kZ4y1u7dw)中我简单介绍了 bspwm 并展示了一些它自带的例子中的功能. 但那个视频只是一个简单介绍,其本质是一个安利,目标观众是使用其他 wm 或不使用 wm 的路人;那么针对被我成功安利决定来体验体验 bspwm 的新用户呢,我决定写这样一篇专栏,帮助大家入门 bspwm. 因此这篇文章的目标读者是正在使用或准备使用 bspwm 的用户,需要的前置知识为我上个视频中讲到的基本概念.

在这篇文章中,我会先对之前的视频做一些补充说明;然后我会举三个例子讲解一下怎么写简单的 bspc 语句来实现简单的功能,帮助大家巩固练习阅读文档的能力;接着我会讲一下 bspwm 自带的例子中的其他有趣的东西,包括 rules(规则)、panel(面板) 和 receptacle(容器)等;最后再随便谈点哲学.

这种带高亮的文字是对读者的建议或指示,可以作为练习.

对之前视频的补充说明

那个视频是有时效性的,当时所用的版本为 0.9.7

bspwm 是一个正在开发中的软件,因此有一些功能上的调整是非常正常的事,自带的例子有一些变化也是正常的. 比如这一个月过去了,版本号升级到了 0.9.10,我在视频中演示的两个文件 bspwmrc 和 sxhkdrc 分别有了小小的变化(2020.08.31):

这提醒我们:bspwm 是没有默认配置的,任何时候你都可以放心地使用包管理器升级软件而不用担心你设置好的操作发生变化;另一方面,当软件有升级时,我们可以到 GitHub 上去看一看新的提交,看一看这些变化是否适合自己,不错的变化可以同步到自己的配置里. 如果嫌麻烦就一直用最开始配置也没有任何问题.

其他发行版可能有自己的默认配置

这点我不是很确定,但我看 SparkyWiki 好像就有一些并不是它自带的例子中的配置. 可能有些发行版是做了自己的更改的,请使用其他发行版的用户自己留意一下.

关于登录时启动 bspwm

当时视频里没有细说,但其实很简单. 如果你不使用显示管理器(display manager),比如你使用 xinit 启动 Xorg,那么在 ~/.xinitrc 中写一句 exec bspwm 就好;如果你使用显示管理器(比如:GDM、LightDM、LXDM、SDDM、XDM、Entrance、Ly、Nodm 之类的),那么只要检查一下有 /usr/share/xsessions/bspwm.desktop 这样一个路径下的文件就好了,登录时显示管理器就会让你选择了,一般情况下你在安装时安装脚本都会自动帮你把这个文件放好的. (没有的话看这里

可以参考的其他阅读材料

bspwm 的文档是出了名的难读:它冗长、简练、格式又不是很规范. 因此我们可以参考些别的. 在这里列举一些:

  1. GitHub 主页上的 README,就是我在视频中演示 parent、children 和 brother 关系的图时的那个地方. 这份文档在本地也是有的,例如在 Arch Linux 下,它在:/usr/share/doc/bspwm/README.md.
  2. GitHub 上的 wiki 页,这也是一份“人话”的介绍,目录在右边 Pages 那里,现在它有 5 页,分别是起始页、命令语法的重写、窗口(window)命令的简要介绍、在 fish (另一个 shell)下需要额外注意的地方、窗口规则的属性 flag.
  3. ArchWiki,非常强大的、非常人性化的“人话”说明书!尤其是其中疑难解答的部分非常精华. 最后的 See also 也很有用.
  4. GentooWikiVoidLinux wiki,供参考.
  5. GitHub Issue 页,可以把浏览 issue(不仅仅是这一个项目的)培养成一个爱好,从中学习解决问题的思路.
  6. reddit 论坛,一个可以经常浏览的交流平台.
  7. Angad Sharma,Bspwm: A Bare-Bones Window Manager,一个很系统的教学,带着读者安装,一边写一点配置一边简单讲一讲,还包括了使用 nitrogen 更换壁纸、使用 compton 增加透明效果、polybar 的部分,以及我的视频没有讲到的:怎么用 bspc 写 node 和 window 的动作指令,四种布局方案(自动、手动、长边、螺旋),怎么写脚本切换这四种方案. 这是一份非常有用、写得非常好的、非常好理解的、全面的、系统的教程,非常建议阅读.
  8. The Darnedest Thing,2015 年的一篇文章,有点年头了,写得不系统,可作为课外读物.
  9. My Take on Tech,Some Tricks For sxhkd (and bspwm),和上面的一样,基础的东西默认你是知道的,有阅读门槛,不系统,作为课外读物会非常非常有帮助,你绝对能从中有所收获和启发.
  10. 最最重要的一点,好好利用搜索引擎. bspwm 有其他 wm 所不具备的一个优势就是它的名字非常有辨识度(对比 awesome 这种常用的英文形容词),因此只要你在搜索引擎中简单地打出 “bspwm” 五个字母,你就能完美命中你想找的东西,在 google、bing 甚至百度,在 YouTube 搜索栏,在 GitHub 搜索栏,在任何一个论坛的搜索栏…… 没事的时候搜索一下,说不定又有新出炉的、说人话的好文章.

bspc 语句

强烈建议阅读我在 1.4 小节 中列举的第 7 个参考材料中 The bspwm client 部分,尤其是其中的前两小节,即 Node Actions(节点操作)和 Window State Actions(窗口状态操作).

这里讲三个例子,来帮助大家巩固练习阅读文档的流程,因为我上一个视频的后半部分只讲了理论,没有演示实践,这里补一下.

例一:旋转

有这样一个需求:我们想把某个节点下的两个 children 窗口旋转一下,如果它是上下平分的我想把它旋转成左右平分,如果是左右平分的我想把它旋转成上下平分. 这是自带的例子中所没有的,怎么办呢?

我们在谷歌中以关键词 “bspwm rotate windows” 搜索一下,能找到一个 reddit 上的帖子,它里面有一个网友表示可以用 bspc node @/ -R 90 来旋转整个桌面下的所有窗口,或者通过 bspc node -f @parent 选择当前焦点的 parent 节点,然后再用 bspc node -R 90 来旋转. 在终端里按照他给的方法执行一下,我们发现是可以满足需求的. (注意:当执行了 bspc node -f @parent 后焦点同时含有了多个窗口,这时鼠标在哪个窗口,真正的焦点就在哪个窗口. )

我们往 sxhkdrc 中给这些操作绑定上快捷键:

1
2
3
4
5
6
7
8
9
# .config/sxhkd/sxhkdrc
# rotate the whole desktop
super + shift + r
    bspc node @/ -R 90

# rotate the parent of current focused
super + r
    bspc node -f @parent; \
    bspc node -R 90

然后重新载入这份配置后一试——就发现了一个 bug:super + r 的两条指令结束后焦点内含有两个窗口,如果重复执行 super + r ,焦点中的窗口会越来越多,参与旋转的窗口也越来越多.

为了解决这个问题,我们来看看这三条指令具体是什么意思. 看上去对于旋转来说最重要的应该是 -R 命令,我们 man bspwm 打开文档(在线文档在这里),使用 / 键搜索 -R,可以看到这样两行:

-R, --rotate 90|270|180

Rotate the tree rooted at the selected node.

从这里开始上下翻一翻,看一看这两行在整个文档中处于一个什么样的位置,它的上下文语境是什么样的. 我们把整份文档简化成这种样子:

BSPWM(1) 文档大纲
◉ 图. BSPWM(1) 文档大纲

可以看到 -R 是一个 node 指令中的 COMMAND 部分,node 指令的通用的格式(General Syntax)为(见上面大纲图中第 20 行):node [NODE_SEL] COMMANDS. 我在上一个视频中讲过,node 是小写字母,是终结符,是必须有的而且不能替换的. 后面的 [NODE_SEL]COMMANDS 都是非终结符,要进行替换.

先说 [NODE_SEL],它的含义很好理解:select a node,即这是一个节点选择器(node selector),我们在这个位置要选择一个用来执行后面 COMMAND 的对象节点. 它被一对中括号括起来,表示 NODE_SEL 是可选的. 从 reddit 网友给出的三条 node 指令中确实能看出它是可选的:指令 bspc node @/ -R 90 中含有 NODE_SEL ,并且在这个指令中将其替换为了 @/ ;而指令 bspc node -f @parent 和指令 bspc node -R 90 中就不含有 NODE_SEL. 再读一下文档:If NODE_SEL is omitted, focused is assumed,意思是如果不含有 NODE_SEL,就默认把当前的焦点窗口作为后面 COMMAND 的对象节点.

再说后面的 COMMANDS,它没有被中括号括起来,说明它是必写的,而且它也是非终结符,需要进行替换,下面列出了 COMMANDS 可以被替换成哪些东西,一共有 22 种可能性,而且每种的格式和说明也都写得很详细,reddit 网友给出的三条指令中,两条 -R 和一条 -f 都在其中,我也把这两条列在了上面的大纲里(见上面大纲图中第 22 - 23 行). 在这里只带着大家看一下 -R 命令,也就是我们最开始搜索 -R 时看到的那两行:

-R, --rotate 90|270|180

Rotate the tree rooted at the selected node.

如果想用 -R 的话,它的格式是:把 COMMANDS 替换为 -R 90-R 270-R 180 三选一,含义分别是:以刚刚选择的 NODE_SEL 节点为根,对它下面的所有子树进行顺时针 90 或 270 或 180 度的旋转.

剩下的问题就是 NODE_SEL 到底能替换成什么了,它的语法格式是怎么样的. 我们在这几行附近并没有看到说明,那就再用 / 键搜索 NODE_SEL,就找到了我上个视频的结尾处仔细分析过的一大串语法格式(见上面大纲图中第 10 行):

NODE_SEL := [NODE_SEL#](DIR|CYCLE_DIR|PATH|any|first_ancestor|last|newest|
    older|newer|focused|pointed|biggest|smallest|
    <node_id>)[.[!]focused][.[!]active][.[!]automatic][.[!]local]
        [.[!]leaf][.[!]window][.[!]STATE][.[!]FLAG][.[!]LAYER][.[!]SPLIT_TYPE]
        [.[!]same_class][.[!]descendant_of][.[!]ancestor_of]

网友给出的 @/ 是怎么来的呢?它表示什么意思呢?

  • 第一步:不选前面的 NODE_SEL#;也不选后面的一大堆中括号里的东西;小括号里的 13 选 1 选择 PATH. 现在 NODE_SEL 被替换变成了 PATH.
  • 第二步:查找下面 PATH 的定义,也就是:PATH := @[DESKTOP_SEL:][[/]JUMP](/JUMP)*. 这里 @ 必选,第二个 DESKTOP_SEL: 不选,第三个 [[/]JUMP]/JUMP,后面的 (/JUMP)* 选 0 个. 现在 PATH 被替换变成了 @/JUMP.
  • 第三步:查找下面 JUMP 的定义,也就是:JUMP := first|1|second|2|brother|parent|DIR. 这按理来说是一个 7 选 1,但是注意,这是这个文档写得不太规范的一个地方,在 Path Jumps 的描述中我们看到这样一句话:

The initial node is the focused node (or the root if the path starts with /) of the reference desktop (or the selected desktop if the path has a DESKTOP_SEL prefix).

这就把第二步中 PATH 的含义解释清楚了. 我们想通过一些路径来选择我们想要的节点,这大体上可以类比在微软 windows 系统中选择一个文件:先指定一个盘符,是 C 盘还是 D 盘,再进行路径的跳转,也就是打开一个文件夹再打开一个文件夹;这里是用 @[DESKTOP_SEL:] 先指定桌面(不写 DESKTOP_SEL 就是指定当前桌面),再用 JUMP 进行跳转,需要多次跳转的话后面就再写好多个 /JUMP. 那么只有一个 / 就表示选择了根节点,不再进行跳转了——这当然也是可以的,就好比我打开 C 盘后就停止了,不再打开下面的文件夹了. 所以我们这里 JUMP 就取空值,表示不再跳转,这是 7 选 1 之外的做法,显然是不符合 BNF 语法规范的.

无论如何,我们用 @/ 就选择了当前桌面的根节点,也就是当前桌面下的所有窗口,对它执行 -R 就完成了对当前桌面的所有窗口执行旋转操作,整条语句就是网友给出的 bspc node @/ -R 90.

那么同理,我们可以精确地选择当前节点的 parent 节点,即用 @parent 替换 NODE_SEL. 具体的流程为:NODE_SEL 替换为 PATHPATH 替换成 @JUMP(不选 DESKTOP_SEL 表示当前桌面;不选 / 表示初始节点从当前焦点所在的节点开始,而不是从当前桌面的根节点开始),JUMP 7 选 1 替换成 parent. 整条语句就变成了 bspc node @parent -R 90.

最后我们就可以写出我们想要的代码了:

1
2
3
4
5
6
7
8
# .config/sxhkd/sxhkdrc
# rotate the whole desktop
super + shift + r
    bspc node @/ -R 90

# rotate the parent of current focused
super + r
    bspc node @parent -R 90

还记得我们用两条语句完成 super + r 时的 bug 么?请读者们仿照我讲解的流程自行对照文档理解 reddit 网友给的指令中 -f 的含义,并尝试直接解决这个 bug.

这个例子我讲得比较细致,但希望读者朋友们能耐心搞明白它,作为阅读文档的实践练习. 这个搞明白了,就没有不会读的文档了.

例二:隐藏

有时我们想实现类似最小化的功能,这需要用到 bspwm 的隐藏. 这也是自带的例子中没有的,怎么办呢?

这里就直接揭晓答案了,在 1.4 小节中列举的第 4 个参考材料,即 GentooWiki 中就有这个功能的实现:见这里.

1
2
3
4
5
6
7
8
9
# .config/sxhkd/sxhkdrc

# hide window
super + v
    bspc node -g hidden

# unhide window
super + shift + v
    bspc node {,$(bspc query -N -n .hidden | tail -n1)} -g hidden=off

请读者们用类似例一中的工作流程对照文档理解一下其中的 -g 命令,更关键地,请大家自己学习理解 bspc query 命令,把这两行完完全全搞明白. 如果你对 Linux 基础命令不熟悉,可能也要简单地学习一下,比如关于管道的知识、 tail 的用法等.

一个问题:最后一行的那个大括号有必要么?直接使用 bspc node $(bspc query -N -n .hidden | tail -n1) -g hidden=off 可以么?

例三:把焦点从平铺窗口移动到浮动窗口

这是一位观众咨询我的问题:当焦点在平铺窗口上时,按 super + h/j/k/l 是不能移动焦点到浮动窗口上的,使用 super + c/o/i 之类的并不能让他满意,怎么办呢?自己写就好了.

如果读者们在上面两个例子中按照我最后的高亮字体进行了阅读学习,那么你现在一定对 bspc querybspc node -f 有了一定的认识. 这样的话,你目前的知识就足够解决这个问题了,不妨先自己试一试. 当然如果你对 shell 脚本语言不熟悉也要临时学一点点,比如可以到菜鸟教程这里看一看,解决这个问题需要用到 shell 流程控制请看这里.

这是我写的方法,仅供参考:

1
2
3
4
5
6
7
8
# .config/sxhkd/sxhkdrc
# focus a floating window from a tiled window
super + a
    if [ "$(bspc query -N -n focused.floating)" ]; then \
        bspc node -f last.tiled.local; \
    else \
        bspc node -f last.floating.local; \
    fi

希望大家能从这个例子中理解 NODE_SEL 后面那一大堆中括号的作用,叹号是表示取否定哦~

一个小技巧

分享一个我经常用的、很多人可能会忽视的、能快速帮你梳理文档结构的一个小技巧,那就是把自动补全打开,然后经常按按 tab 键. 比如打下 bspc ,按一下 tab 键会出现 9 个候选的 command 或 domain,然后随便选一个比如 bspc node,再按一下 tab 键,等等.

补全小技巧的示意效果图:bspc <TAB>
◉ 图. 补全小技巧的示意效果图:bspc <TAB>
补全小技巧的示意效果图:bspc node <TAB>
◉ 图. 补全小技巧的示意效果图:bspc node <TAB>
补全小技巧的示意效果图:bspc node <TAB><TAB>
◉ 图. 补全小技巧的示意效果图:bspc node <TAB><TAB>

上面三张图展示的是我的 zsh 补全效果,bash 或 fish 也都是有的,安装 bspwm 时安装脚本应该会把它们处理好,一般情况下打开补全就能用了. 如果没有的话可以自己弄,以我的 Arch Linux 为例,补全脚本分别在这里:

  • zsh. 本地:/usr/share/zsh/site-functions/_bspc项目中.
  • bash. 本地:/usr/share/bash-completion/completions/bspc项目中.
  • fish. 本地:/usr/share/fish/vendor_completions.d/bspc.fish项目中.

看代码量的话好像 bash 和 fish 不如 zsh 的补全代码多,不知道使用起来能不能达到我上面截图中 zsh 的补全效果.

添加规则的两种方式

内建 rule 指令

建议详细阅读我在 1.4 小节中列举的7 个参考材料中 The bspwm client 部分的第三小节:Window Rules,搞明白规则是用来干什么的,并学习用 xprop 命令查询一个程序窗口的 WM_CLASS.

关于规则的部分,文档里也只有寥寥不到 15 行的东西,只有 -a-r-l 三个 COMMANDS,而且基本上只用得到 -a 这一个,非常好读. 建议 man bspwm 后用 / 键搜索 Rule 进行阅读. 它的位置在上面的大纲中也能看到,Rule 是作为一个 DOMAIN 出现的.

关于规则指令后面可以加的属性([(hidden|sticky|private|locked|marked|center|follow|manage|focus|border)=(on|off)])可以参考我在 1.4 小节中列举的2 个参考材料即 GitHub 上的 wiki 中的 window rules attribute flags. 现在一共有 10 个是可以使用的,它们的意思是非常好理解的,我在上一个视频中也有讲到.

external rules

1.4 小节中列举的3 个参考材料也就是 Arch Wiki 中有一小节是关于规则的说明,它说 external rules (外部的规则)写起来更复杂,但也能处理更复杂的窗口规则(This is more complex, but can allow you to craft more complex window rules),并说我们可以看一下例子中的写法,除此以外就没有说更多了. 这是对新手入门十分不友好的,而且除此以外,在网上也找不到更全面的介绍了,那我们就来自己动手丰衣足食吧!

在线的例子在这里;本地也是有的:/usr/share/doc/bspwm/examples/external_rules. 里面有两个文件,分别是 bspwmrc 和 external_rules,我们对 bspwmrc 更熟悉,我们先打开它.

1
2
#! /bin/sh
bspc config external_rules_command "$(which external_rules)"

里面只有一行,但有个 external_rules_command 是我们不理解的,man bspwm 打开文档,用 / 键搜索一下 external_rules_command,找到它在文档中的位置,是位于 SETTINGS 里面的 Globle Settings,看上下文,和它平等地位的还有我们在上个视频中讲到的 normal_border_colorfocused_border_color、自带的 bspwmrc 例子中有的 split_ratioborderless_monoclegapless_monocle 等,这下我们心里就有底了,我们可以把这一行复制到 ~/.config/bspwm/bspwmrc 中,就像我们在上个视频中改边框颜色一样. 现在我们静下心来读一读文档中对 external_rules_command 的解释:

Absolute path to the command used to retrieve rule consequences. The command will receive the following arguments: window ID, class name, instance name, and intermediate consequences. The output of that command must have the following format: key1=value1 key2=value2 … (the valid key/value pairs are given in the description of the rule command).

(一个命令的绝对路径,这个命令是用来查找规则结果的. 这个命令要接收如下的参数:window ID、class name、instance name、intermediate consequences. 这个命令的输出结果必须是键值对的格式,合法的键值对在 rule 命令的描述中给出. )

如果在上一节中你按照我的指示去读了文档中关于 Rule 的部分那不到 15 行的内容,那你现在看到它最后一句说的键值对时一定能够会心一笑了.

现在我们知道了这短短的一行 bspc config 语句中最后的 "$(which external_rules)" 就是这里说的绝对路径了,再结合我们对 Linux 基本语句 which 的知识,我们就明白了另一份文件 external_rules 一定是一份脚本文件,而且要把它加到环境变量中以便我们能直接调用它,就像使用 lscd 一样直接使用 external_rules 作为一个命令,而且这个命令必须带四个参数,输出必须是键值对.

写自己的命令的方法:

(这一段是 Linux 使用基础中的基础,因为 Linux 的灵魂就是自己写脚本,如果你连这都不知道的话,就要加强 Linux 基础知识的学习了)

完成这件事有很多种做法,我个人的做法是建一个 ~/.bin/ 文件夹,然后在里面写脚本,shell 脚本也行,python 脚本也行,比如:

1
2
3
#!/usr/bin/env python                                                          
# ~/.bin/hello-world                                                           
print("hello!")                                                                

然后别忘了每次启动时把它自动加载到环境变量中:

1
2
3
4
# .xprofile or .xinitrc or .zprofile or ……                                     
if [ -d "$HOME/.bin" ] ; then                                                  
    PATH="$HOME/.bin:$PATH"                                                    
fi                                                                             

这样就能在终端中像执行 ls 一样执行 hello-world 了.

带着这些认识,我们打开 external_rules 看一看:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#! /bin/sh

wid=$1
class=$2
instance=$3
consequences=$4

if [ "$instance" = fontforge ] ; then
    title=$(xtitle "$wid")
    case "$title" in
        Layers|Tools|Warning)
            echo "focus=off"
            ;;
    esac
fi

case "$class" in
    Lutris|Liferea)
        eval "$consequences"
        [ "$state" ] || echo "state=pseudo_tiled"
        ;;
esac

(如果不会读 shell 语句,一定要自己去学习一下啊!在这里最起码得读得懂中括号、case 语句、eval 语句、双竖线才行. )

可以看到,和我们预想的一样,这是一份脚本,分为三个段落,第一段是接收四个参数,这没什么好说的;第二段是在第 3 个参数 instance name 是 fontforge 的情况下,通过 window ID 获取 window 的 title(标题),并对 title 的三种可能情况返回了一组键值对;第三段对 class name 的两种情况执行了第四个参数,并在 $state 变量为空时返回另一组键值对.

如果你认真阅读了这 22 行代码,你首先产生的一个疑问一定会是:xtitle (第 9 行)是什么命令?我们在搜索引擎中以“xtitle”为关键词进行搜索,发现了这样一个工具:baskerville/xtitle. 注意到这个工具的作者了么?他 GitHub 的用户名是 baskerville 哎!这不正是 bspwm 的作者么?!这下就不奇怪了吧,这个作者写的 bspwm 自带的例子中的 external rules 里含有另一个他写的小工具!就像 bspwm 需要用到的快捷键精灵 sxhkd 一样,sxhkd 也是他专门为 bspwm 写的呢!这个 xtitle 并不在 Arch Linux 的官方源中,我们需要自行编译它. 不过 Arch Linux 的 AUR 里是有的,我就直接从这上面克隆然后 makepkg 一下了.

把每条语句都理解清楚以后我们还是对这份文件本身一头雾水:这个脚本由谁来执行?什么时候执行?怎么执行?执行的时候怎么传递参数?那四个参数(window ID、class name、instance name、intermediate consequences)是什么?谁来决定?fontforgeLayers|Tools|WarningLutris|Liferea 这些都是什么意思?哪些程序的窗口会是这样的标题?

别急,我们来试验一番. 这里我们并不采用上面提到的环境变量的方法,我们直接在 .config/bspwm/bspwmrc 中的最后一行加上:bspc config external_rules_command "${HOME}/.config/bspwm/try_e_r",并用类似 external_rules 的格式创建 try_e_r 文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#! /bin/sh

# ~/.config/bspwm/try_e_r
wid=$1
class=$2
instance=$3
consequences=$4

echo -e "One group: \n \
    window ID : ${wid},\n \
    title : $(xtitle "${wid}"),\n \
    class name : ${class},\n \
    instance name : ${instance},\n \
    intermediate consequences : ${consequences}\n\n" \
    >> /tmp/try_e_r.log

然后我们按 super + Alt + r 重启 bspwm,在终端下使用 tail -f /tmp/try_e_r.log 来监听日志文件变化. 我们对窗口进行一些操作,看看 try_e_r.log 里什么时候会有新的变化,并看看这四个参数以及使用 xtitle 得到的结果分别都是什么. 贴一下我这里的测试结果:

external_rules 的测试结果
◉ 图. external_rules 的测试结果
external_rules 的测试结果
◉ 图. external_rules 的测试结果

打开监听变化的日志文件以后,我分别打开了一个终端,一个 firefox 浏览器,一个 SMPlayer. 当然第一个 Polybar 是我重启 bspwm 之后马上打开的第一个窗口,这无需多言,只是我有点不明白我明明把 bspc config external_rules_command "${HOME}/.config/bspwm/try_e_r" 写到 bspwmrc 的最后一行了,竟然还能在日志里看到 Polybar ?难道 shell 脚本不是从上到下一行一行执行的么?希望得到各位读者的指点.(那些日志中的输出你可能会对 Polybar 的 layer=above 不太熟悉,可以自己再额外看一看关于 layer 的信息.)

通过试验,我们明白了 external_rules 的工作流程:如果我们写了 external_rules 脚本,并在 bspwmrc 中写明了这个脚本的绝对路径,那么 bspwm 在每打开一个窗口时都会自动执行这个命令,并把这个窗口的信息通过那四个参数告诉给这个命令,然后根据得到的返回值也就是一些键值对来决定窗口的样貌与模式.

打个比方,external_rules 是我们创造的一位负责管理学生进入学校(负责管理窗口打开)的看门大爷(一个命令),我们有任何规则都可以告诉他(用脚本语言写进去),然后他就躺在传达室里默默无闻(它只是一个命令而已,你不调用,它就不会理你),一旦有一个学生要进入学校(一个窗口要被打开),送学生来的家长(bspwm)就会把看门大爷叫起来(调用这个命令),告诉看门大爷(向这个命令传参)这个学生的学号(这个窗口的 window ID)、他是哪个班的(class name)、他叫什么名字(instance name)、他来学校干什么(intermediate consequences),看门大爷就把他的学号输入到电脑里查一下(xtitle)他的小名(title)叫什么,然后根据这些信息和你告诉给他的规则来决定这个学生进入学校以后有什么额外要做的事情(这个窗口应该有什么额外的规则).

external_rules 的部分我觉得写到这里就足够了,它确确实实能写出非常复杂的规则出来. 关于 fontforgeLayers|Tools|WarningLutris|Liferea 的事情我觉得反而不是很重要,因为 Lutris 和 Liferea 的这两个软件你不一定会用得到,你只要知道怎么自己写规则,怎么利用好这个功能就足够了.

如果感兴趣或者想完整地体验一下 external_rules 的读者可以自行安装这两个软件,这两个软件在 Arch Linux 下的官方源中都有:

  • Liferea,一个信息汇总聚合平台,我猜是某种 RSS 阅读器.
  • Lutris,一个游戏平台,我猜是类似 steam 那种.

关于这份脚本,还有一个 $state 没有讲. 读者们可以用类似的方法看看它是什么.

四种布局方案

建议详细阅读我在 1.4 小节中列举的7 个参考材料中 Layouts 部分. 它里面还包括了一段快速切换布局方案的脚本.

待补充.

关于 panel

第三方的 panel 选择很多,比如 polybar、lemonbar、yabar,可以阅读我在 1.4 小节中列举的第 3 个参考材料即 Arch Wiki 中的相关部分.

这里想讲一下它的例子中自带的 panel ,待补充.

关于容器

待补充.

谈一点哲学

就是我自己有一些想法,随便满足一下表达欲.

阅读文档与解决问题

我虽然在之前的视频里简单说了说怎么阅读文档——会了语法之后只要耐心就好了,但我并没有很鼓励大家都去啃文档,因为这并不高效. 哪怕我很认真我很耐心地在视频中演示了一些东西,我也仅仅是希望中文的 Linux 社区少一点点浮躁、多一点点耐心,仅此而已.

那么更高效、也更符合现实的对待文档的态度是什么呢?我想用英语的阅读理解做个简单的类比.

在初中/高中的英语考试中有一类题型叫做阅读理解,试卷上会有一篇文章,然后文章后面有一些题目,每个题目有四个选项,考生需要阅读文章,然后做题. 面对这种阅读理解题,大体是有两种解题策略的:一是先读文章再做题;二是先读题目再到文章中去找相关的句子. 前者能让你真正读懂这篇文章,但是会有点慢;后者能让你快速把题目做出来,但你可能并不知道文章讲了什么. 选取那种策略主要还是看你怎么对待考试:如果你享受读英语的过程、好奇文章讲了什么,那么你可能会采取前者;如果你只是为了获得分数,其他的都不重要,那么你可能会采取后者.

照搬到 Linux 使用上也是差不多的. 文档相当于文章,你的需求相当于文章后面的问题,要不要读文档、怎么读文档,完全取决于你为什么要用 Linux,你的心态是怎么样的. 你在做事情的时候经常会有新的需求提出来么?你是一个需求很多的人还是一个需求很少的人?大体情况下,阅读文档能让你解决问题、完成需求的时间变短,但阅读文档本身需要大量的时间. 我个人的看法是,如果你的需求不太多,那就不需要阅读文档,遇到问题解决问题就好;如果你的需求比较多,那可能磨刀不误砍柴工了. 到底还是一个平衡的问题.

我个人是不会从头到尾阅读文档的,我的习惯几乎就是在 2.1 小节 例一:旋转 那里的工作流程:当我有需求时,我会通过搜索引擎寻找解决方案,然后我会对照文档理解别人的解决方案,最后适当做出改进或直接使用. 为了安全起见,我绝不会看都不看就把别人的代码复制下来直接执行.

Unix 哲学

我在上一个视频中讲到 bspwm 的优点时,有很多网友就说到了 bspwm 符合 Unix 哲学. 那么 Unix 的哲学是什么呢?在维基百科中可以找到 1978 年 Doug Mcllroy 最原始四句表述,我们这里单取出第一条来看:

Make each program do one thing well. To do a new job, build afresh rather than complicate old programs by adding new “features”.

简单地说就是一个程序只应该做一件事并把它做好. 其实我说到这里大家应该就明白了,bspwm 就单纯地只负责窗口管理的部分,那么键盘输入的部分怎么办呢?bspwm 的作者又为了这个任务创造了一个新的工具 sxhkd. 这样做的好处是我们可以使用其他工具来进行快捷键绑定的工作;或者反过来:在不用 bspwm 的情况下,单纯只安装 sxhkd 来处理键盘输入,比如使用键盘调整音量、调整屏幕亮度的工作都可以由 sxhkd 来完成.

这样的 Unix 哲学在 bspwm 的身上体现得淋漓尽致:

  • 在 external_rules 的部分,为了根据窗口的 window id 获取窗口的标题(title),作者又额外创造了 xtitle. 见本文第 3.2 小节;
  • 在 panel 部分,为了判断是否存在一个 bar,作者又额外创造了一个对窗口进行操作的工具 xdo. 见本文第 5 章, 别见了,第 5 章没写,看一下这份代码的第 3 行你就明白了.

这是不是能激发我们对 Unix 哲学的一些思考呢?

社区

bspwm 是一个文档写得很难读的软件,它自带的 examples 里有好多内容缺乏系统的介绍,像这里面的 external_rules、overlapping_borders、panel、receptacles 都缺少很好的文章来介绍它们的功能和使用方法.

我在这篇文章中对 external_rules 做了一点简单的介绍,本来其他的内容我也计划写一写的,但实在是累了,而且这个 b 站专栏的富文本编辑器用起来太费劲了……所以其他的内容我就不再写了,但是依然列在这里,希望我这块砖头能引到良玉. 大佬们请一定不要吝啬你们的知识啊,稍微分享分享就是好的;像我们普通用户可以把自己使用 bspwm 中遇到的问题和做出的努力稍微力所能及地随便记录一下,这对于中文的 bspwm 社区绝对是一件好事.


虽然好多东西没写,这篇文章还是太长了. 希望读完此文的读者能从中学习到:bspc 语句的使用、阅读文档的方法、解决问题的流程、面对陌生事物(以 external_rules 为例)时的手段、调试的方法(写入 log,实时监测变化)以及我想传达的对软件的轻视、对技能的重视——因为随着技术的快速迭代,任何一个软件最终都会被更好的软件所替代,而那些解决问题的方法、面对问题时的耐心与踏实,却是我们使用 Linux 中、甚至是平时生活中一直都需要的.