Arch Linux 下 Qtile 桌面管理器
Table of Contents
安装
对于这种轻量级的桌面非常赞,只需要: yay -S qtile
, 又因为有 lightdm, 甚至启动项都不用配,直接就能用了。有一些额外包,如果用它提供的 widget 组件时需要。
配置
相对而言, qtile 的文档写得比较简陋,没有 bspwm 那么详细。
配置路径是 ~/.config/config.py
, 样板配置在 /usr/share/doc/qtile/default_config.py
.
基本概念
在 qtile 中,显示器是 screen, 工作区是 group, 窗口是 window, 布局是 layout.
键盘映射 Key
在配置文件中,变量 keys
记录了所有的键盘映射,它是一个列表,元素是键盘对象。样板配置里面给出了例子:
mod = "mod4"
keys = [
Key([mod], "h", lazy.layout.left(), desc="Move focus to left"),
Key([mod], "l", lazy.layout.right(), desc="Move focus to right"),
Key([mod], "j", lazy.layout.down(), desc="Move focus down"),
Key([mod], "k", lazy.layout.up(), desc="Move focus up"),
Key([mod], "space", lazy.layout.next(), desc="Move window focus to other window"),
Key([mod, "shift"], "h", lazy.layout.shuffle_left(), desc="Move window to the left"),
Key([mod, "shift"], "l", lazy.layout.shuffle_right(), desc="Move window to the right"),
Key([mod, "shift"], "j", lazy.layout.shuffle_down(), desc="Move window down"),
Key([mod, "shift"], "k", lazy.layout.shuffle_up(), desc="Move window up"),
]
首先定义 mod
变量,它是 mod4
, 也就是 windows 键。
键盘对象 Key
接收至少三个参数:
- 第一个参数是一个列表,表示修饰键,比如上面定义的都需要按住 windows 键,而后四个则需要按住 windows + shift
- 第二个参数是热键,很容易理解
- 第三个参数是按键映射的函数。 lazy 表示不是立刻执行,而是调度后执行
例子中的第四个参数是描述,可选。
此外, qtile 还提供了 Vim-like 和 Emacs-like 的按键映射。比如我用的 Emacs-like:
from libqtile.config import EzKey
EzKey.modifier_keys = {
'W': 'mod4',
'M': 'mod1',
'S': 'shift',
'C': 'control',
}
先是映射修饰符,这里是把 Windows 键映射到 W
. 在 Emacs 中 Ctrl + Shift + w 记作 C-W
, 没有专门的 shift 按键;但是 qtile 有,于是把它映射到 S
, 但这样又与 Emacs 中的 meta (也就是 windows ) 键冲突了,就很讨厌。注意, mod1
表示 Alt 键。
随后的映射就是:
# Win + Ctrl + h => Grow window to the left
Key([mod, "control"], "h", lazy.layout.grow_left())
# Emacs-like
EzKey("W-C-h", lazy.layout.grow_left())
键盘启动程序
用 lazy.spawn("cmd")
来启动程序。比如把 Win + t 映射到启动 kitty 终端模拟器:
EzKey("W-t", lazy.spawn("kitty"))
spawn 里面是命令行的命令,可以加参数。
键盘启动自定义程序
所有的程序都应该是 LazyCall, 所以不能直接把用户自定义的函数映射到键盘上,需要加一个修饰器:
@lazy.function
def my_function(qtile):
pass
Key([], "k", my_function())
, 函数固定传入一个 qtile 对象。且在函数体中不能再用 lazy. 想执行程序调用 qtile.cmd_spawn
. 想执行切换窗口、工作区等操作,可以用 qtile.groups[i].cmd_toscreen()
找到对应的窗口或工作区来切换。
具体有哪些函数可用,官方并没给,我是手动试出来的。在 Debug 那一节我会给出我自己的笨蛋方法。
工作区 Group
定义工作区:
groups = [Group(str(i)) for i in range(1,10)]
for i in groups:
keys.extend(
[
EzKey("W-%s" % i.name, lazy.group[i.name].toscreen()),
EzKey("W-S-%s" % i.name, lazy.window.togroup(i.name, switch_group=True)),
]
)
groups.append(Group("10"))
keys.extend(
[
EzKey("W-0", lazy.group["10"].toscreen()),
EzKey("W-S-0", lazy.window.togroup("10", switch_group=True)),
]
)
实例化 Group 对象就是定义了一个工作区,传入工作区的名字,为了保持一致,我定义了第 10 号工作区,通过 Win + 0 切换。
布局排版 Layout
Qtile 支持多种布局,可以在文档的 build-in layout 那一节看到相关介绍。我采用的是 Bsp, 即 bspwm 的布局,它根据当前窗口的尺寸长边分割,也就是 bspwm 文档中的 longest-side-scheme:
配置如下:
layouts = [
layout.Bsp(
border_focus=dracula_purple,
border_width=4,
margin=5,
fair=False,
),
]
dracula_purple
是 Dracula 配色的紫色,自行定义的。 fair
设为 False 分割窗口只会在焦距的窗口分割,不管它的尺寸如何。
注意到 layouts
是一个列表,也就是说可以设置多个布局方式,通过 lazy.next_layout()
来切换。
屏幕 Screen
屏幕同样可以设置多个屏幕对应多种不同的展示内容,目前我是单屏,所以实例化一个 Screen
即可。可以在实例化时传入 wallpaper
参数来设置壁纸,也可以用 wallpaper 的 widget. 我这里直接用 Screen 设置,后续可以调用类方法 set_wallpaper
来方便的切换壁纸。
screens = [
Screen(
top=bar.Gap(45),
bottom=bar.Gap(5),
left=bar.Gap(5),
right=bar.Gap(5),
wallpaper="~/Pictures/output/15.png",
wallpaper_mode='fill'
)
]
窗口间距
在布局设置时,传入了 margin
参数,它表示窗口的外边距。如此一来,分割的两个窗口之间的距离其实是两倍的 margin, 因为每个窗口都有一个。以水平分割的两个窗口为例,我更希望的是窗口到屏幕边缘与窗口之间的距离相等,如:
所以在设置了布局的 margin 之后,窗口到屏幕的距离需要再加一个 margin, 这样这三个距离都是两倍 margin. 故在上一节屏幕的设定中,左下右都加了一个距离为 5 的 Gap ,也就是空隙。为什么上边距是 45,是要给 eww 状态栏留空间。
窗口浮动
如样本配置中的
floating_layout = layout.Floating(
float_rules=[
# Run the utility of 'xprop' to see the wm class and name of an X client.
*layout.Floating.default_float_rules,
Match(wm_class="confirmreset"), # gitk
Match(wm_class="makebranch"), # gitk
Match(wm_class="maketag"), # gitk
Match(wm_class="ssh-askpass"), # ssh-askpass
Match(title="branchdialog"), # gitk
Match(title="pinentry"), # GPG key password entry
]
)
添加规则告诉 qtile, 哪些窗口是浮动的。
钩子 Hook
Qtile 用 hook 来加入定制。比如一个简单开机自启动的例子:
@hook.subscribe.startup_once
def auto_start():
import subprocess
import os
script = os.path.join(os.path.dirname(__file__), "autostart.sh")
subprocess.Popen(script)
在 hook startup_once
也就是启动时这个函数会执行,且只执行一次,restart Qtile 是不会触发这个钩子的。把要自启动的程序写到同目录下的 autostart.sh
脚本中即可。
或者,我想一些让某些窗口浮动,且居于屏幕中间——虽然可以向 floating_layout
中添加规则,但实际上窗口只是浮动了,但是位置和大小不会改变。例如词典 goldendict:
@hook.subscribe.client_new
def goldendict_rule(window):
ins, cls = window.window.get_wm_class()
if cls == "GoldenDict":
window.floating = True
window.place(660, 290, 600, 500, 0 , '')
它会在新窗口建立 (钩子 client_new
) 时判断,如果新窗口是 goldendict, 那么浮动,大小设置为 600x500, 且位置居中(通过 x 和 y )来调整。
再比如,我想给一个工作区就显示一个应用:
@lazy.function
def dust_mu4e(qtile):
g = qtile.groups[8]
g.cmd_toscreen()
if not g.windows:
qtile.cmd_spawn("emacs -f mu4e")
keys.extend(
[
EzKey("W-r", dust_mu4e()),
]
)
,如此按 Win + r, 就会跳转到工作区 9 (索引比名称少 1),如果工作区没窗口,则打开 mu4e.
更多的钩子可以参考官方文档。
命令行工具
命令行可以用 qtile cmd-obj
来执行操作,完整命令 qtile cmd-obj -o obj -f func
.
其中: obj
是对象,有 cmd/bar/group/layout/screen/widget/window/core. cmd 是全局对象,group 是窗口。 func
是要执行的函数,根据对象的不同而不同。
给几个例子:
- 重新加载配置文件:
qtile cmd-obj -o cmd -f reload_config
- 切换到工作区 4:
qtile cmd-obj -o group 4 -f toscreen
.
具体还是有些函数,自行 help
(用得不太多)。
Debug
首先,qtile 会生成一个日志,路径是 ~/.local/share/qtile/qtile.log
. 路径可以调,但我忘了在那个地方看到的,现在找不到了。
调用 from libqtile.utils import logger
, 然后用 logger.warn
去输出(debug 等级都不到)。用 obj.__dir__()
来看 obj
有哪些方法可以调用。
后记
没太感觉 qtile 用 Python 来写会占用多大资源或者响应多慢,用起来其实还挺 OK, 配置用我最熟悉的 Python 就写一些比较 hack 的方法去和其他比如 eww 模组联动。