- 桌面应用
- 操作系统
【免费下载链接】qtile
:cookie: A full-featured, hackable tiling window manager written and configured in Python (X11 + Wayland)
Lazy objects(惰性对象)是 Qtile 配置文件中最核心的编程模型之一:它以"命令引用"的形式包装 Qtile 命令 API 中的任意命令,在按键、鼠标事件真正触发时才执行。读完本文你将掌握 lazy 对象的底层原理、全部常用函数清单,以及如何用它绑定按键、注入自定义函数和实现条件触发。
Lazy 是什么:先创建引用,事件触发时才执行
在 Qtile 的命令体系中,几乎所有可执行操作——切换布局、移动窗口、重启 Qtile——都被抽象为 Commands API 中的命令。Lazy objects 正是"以任意命令为执行目标"的通用载体:
Lazy objects are a way of executing any of the commands available in Qtile's commands API。
"Lazy(惰性)"一词点明了其核心语义:命令不会在调用lazy.xxx()的那一刻执行。lazy 对象只创建对相关命令的一个引用(reference),这个引用要等到相应事件被触发时(例如一次按键按下)才会真正被执行。因此,配置文件里最常见的用法就是把 lazy 调用绑定到按键、mouse_callbacks和鼠标事件上,用来操作窗口、布局和分组,以及执行退出(shutdown)、重启(restart)、重载配置(reload_config)等应用级命令。
一个最经典的入门示例:
from libqtile.config import Key from libqtile.lazy import lazy keys = [ Key( ["mod1"], "k", lazy.layout.down() ), Key( ["mod1"], "j", lazy.layout.up() ) ][!NOTE] 如文首所述,
lazy调用并不会调用相应命令,而只是创建对它的引用。虽然这使它非常适合绑定到按键和 widget 的mouse_callbacks,但同时也意味着lazy 调用不能被包含在用户自定义函数中(例如不能在def my_func(): lazy.window.kill()这样的函数体内直接书写 lazy 调用)。需要函数式自定义逻辑时,请使用后文介绍的lazy.function。
底层机制:命令图 + 惰性命令接口
从源码层面看,lazy 机制建立在 Qtile 的"命令图(command graph)"抽象之上。command/graph.py 定义了CommandGraphRoot、CommandGraphNode、CommandGraphCall等对象。命令图的根节点包含bar、group、layout、screen、widget、window、core这七类子对象——这正好解释了为什么lazy.后可以直接跟layout、window、group、screen等命名空间。
真正的入口在 libqtile/lazy.py 的最底部:
lazy = InteractiveCommandClient(LazyCommandInterface())InteractiveCommandClient(定义于 command/client.py)通过__getattr__魔法方法把lazy.layout.down()这样的链式属性访问解析为对命令图的一次导航与定位。关键的惰性由LazyCommandInterface提供:
class LazyCommandInterface(CommandInterface): def execute(self, call: CommandGraphCall, args: tuple, kwargs: dict) -> LazyCall: """Lazily evaluate the given call""" return LazyCall(call, args, kwargs) def has_command(self, node: CommandGraphNode, command: str) -> bool: return True def has_item(self, node: CommandGraphNode, object_type: str, item: str | int) -> bool: return True对比 command/interface.py 中另外两种实现——进程内直接调用的QtileCommandInterface和跨进程 IPC 调用的IPCCommandInterface——可以清楚看出:LazyCommandInterface并不真正执行命令,它只是把CommandGraphCall(命令图中要调用的命令节点)、位置参数和关键字参数封装进一个LazyCall对象并原样返回。而has_command/has_item一律返回True,意味着合法性校验被推迟到运行时,这也正是"惰性"的源码级体现。
一个LazyCall实例就是你在按键回调里拿到的东西:它保存着命令名(name)、定位该命令的命令图路径(selectors)、参数(args)与关键字参数(kwargs)。当按键事件触发、Qtile 执行该回调时,才会沿命令图解析并真正调用底层命令。
常用 Lazy 函数一览
以下函数均可从命令树上的对象(如lazy.layout、lazy.window)调用。下面按用途分组列出最常用的函数,完整命令列表请参阅 Commands API 索引。
通用函数(General functions)
| 函数 | 说明 |
|---|---|
lazy.spawn("application") | 运行application程序 |
lazy.spawncmd() | 在栏(bar)上打开命令提示符,配合 prompt widget 使用(参见 prompt 相关 widget 文档) |
lazy.reload_config() | 重载配置文件 |
lazy.restart() | 重启 Qtile。在 X11 后端下不会关闭你的窗口 |
lazy.shutdown() | 关闭整个 Qtile |
这五个函数几乎出现在每一个 Qtile 配置里,典型的按键绑定组合是:mod + ctrl + r重载配置、mod + ctrl + q退出、mod + shift + r重启。
组与布局函数(Group functions)
| 函数 | 说明 |
|---|---|
lazy.next_layout() | 在当前组使用下一个布局 |
lazy.prev_layout() | 在当前组使用上一个布局 |
lazy.screen.next_group() | 切换到右侧的组 |
lazy.screen.prev_group() | 切换到左侧的组 |
lazy.screen.toggle_group() | 切换到上次访问的组 |
lazy.group.next_window() | 将窗口焦点切换到组内的下一个窗口 |
lazy.group.prev_window() | 将窗口焦点切换到组内的上一个窗口 |
lazy.group["group_name"].toscreen() | 切换到名为group_name的组。可接受可选参数toggle(默认为False):若该组已在本屏幕,默认不做任何事;传入toggle=True则可与上次使用的组来回切换 |
lazy.layout.increase_ratio() | 增大主窗口(master)所占空间,挤压从窗口(slave) |
lazy.layout.decrease_ratio() | 减小主窗口所占空间,扩大从窗口 |
其中toscreen(toggle=True)是"在两组之间快速往返"的经典技巧,很多用户会把它绑定到某个按键上用作"回到上一个工作区"。
窗口函数(Window functions)
| 函数 | 说明 |
|---|---|
lazy.window.kill() | 关闭当前聚焦的窗口 |
lazy.layout.next() | 将窗口焦点切换到栈(stack)布局的其他窗格 |
lazy.window.togroup("group_name") | 把当前聚焦窗口移动到名为group_name的组 |
lazy.window.toggle_floating() | 在当前窗口的浮动(floating)与非浮动模式间切换 |
lazy.window.toggle_fullscreen() | 在当前窗口的全屏与非全屏模式间切换 |
lazy.window.move_up() | 把当前窗口移到栈中其上方窗口之上 |
lazy.window.move_down() | 把当前窗口移到栈中其下方窗口之下 |
lazy.window.move_to_top() | 把当前窗口移到所有同等优先级窗口之上(普通窗口不会被移到kept_above窗口之上) |
lazy.window.move_to_bottom() | 把当前窗口移到所有同等优先级窗口之下(普通窗口不会被移到kept_below窗口之下) |
lazy.window.keep_above() | 让当前窗口保持在其他窗口之上 |
lazy.window.keep_below() | 让当前窗口保持在其他窗口之下 |
lazy.window.bring_to_front() | 把当前窗口带到所有窗口之上,忽略kept_above优先级 |
move_to_top/move_to_bottom与bring_to_front的差别值得注意:前两者尊重kept_above/kept_below的分层语义,只在同层窗口内排序;后者则无视优先级强制置顶。
屏幕函数(Screen functions)
| 函数 | 说明 |
|---|---|
lazy.screen.set_wallpaper(path, mode=None) | 将壁纸设置为指定的图片。mode可选值:None(不缩放)、'fill'(居中并缩放以填满屏幕)、'stretch'(拉伸以填满屏幕) |
一个完整示例:
Key(["mod4"], "w", lazy.screen.set_wallpaper("/path/to/wallpaper.png", mode="fill"))ScratchPad DropDown 函数
ScratchPad 是 Qtile 的"便签板"机制(实现位于 libqtile/scratchpad.py),允许以 DropDown 形式把进程收纳为可随时唤出的浮窗。以下 lazy 函数专用于操作 DropDown:
| 函数 | 说明 |
|---|---|
lazy.group["group_name"].dropdown_toggle("name") | 切换指定 DropDown 窗口的可见性;首次使用时会启动该 DropDown 配置的进程 |
lazy.group["group_name"].hide_all() | 隐藏该组下所有 DropDown 窗口 |
lazy.group["group_name"].dropdown_reconfigure("name", **configuration) | 更新指定 DropDown 的运行时配置 |
在源码层面,scratchpad.py 中分别以dropdown_toggle(第 335 行)、hide_all(第 350 行)、dropdown_reconfigure(第 358 行)实现了这三个命令,并被@expose_command暴露到命令接口(命令暴露机制见 command/base.py 中的expose_command装饰器),因此它们既能被 lazy 调用,也能通过 IPC 与 qtile shell 调用。
用户自定义函数(User-defined functions)
| 函数 | 说明 |
|---|---|
lazy.function(func, *args, **kwargs) | 调用func(qtile, *args, **kwargs)。注意qtile对象会被自动作为第一个参数传入 |
lazy.function是让自定义逻辑进入键绑定的官方入口。由于 lazy 调用本身不能写进自定义函数体,正确姿势是把自定义函数作为lazy.function的参数包装起来。
深入实践:lazy.function 的两种传参方式
lazy.function还可以用作函数装饰器。此时函数签名中的第一个参数依然是自动注入的qtile对象:
from libqtile.config import Key from libqtile.lazy import lazy @lazy.function def my_function(qtile): ... keys = [ Key( ["mod1"], "k", my_function ) ]除了"无参"用法,还可以通过以下两种方式向自定义函数传递参数。
方式一:内联定义
直接把参数附加在lazy.function调用上:
from libqtile.config import Key from libqtile.lazy import lazy from libqtile.log_utils import logger def multiply(qtile, value, multiplier=10): logger.warning(f"Multiplication results: {value * multiplier}") keys = [ Key( ["mod1"], "k", lazy.function(multiply, 10, multiplier=2) ) ]方式二:装饰器
参数也可以传给被装饰后的函数对象(注意此时调用的是multiply(10, multiplier=2)的返回值,它同样是一个LazyCall):
from libqtile.config import Key from libqtile.lazy import lazy from libqtile.log_utils import logger @lazy.function def multiply(qtile, value, multiplier=10): logger.warning(f"Multiplication results: {value * multiplier}") keys = [ Key( ["mod1"], "k", multiply(10, multiplier=2) ) ]这两种方式在底层是等价的。从 libqtile/lazy.py 中LazyCall.__call__的实现可以看到,装饰器形式之所以能写成multiply(10, multiplier=2),是因为LazyCall实现了__call__:
def __call__(self, *args, **kwargs): # We need to return a new object so the arguments are not shared between # a single instance of the LazyCall object. return LazyCall(self._call, (*self._args, *args), {**self._kwargs, **kwargs})注意注释强调的要点:调用会返回一个新的LazyCall,从而避免同一个LazyCall实例在多处复用时参数互相污染。
条件触发:when() 方法限定执行场景
很多高级配置需要"只在特定条件下才触发按键"。为此LazyCall提供了when()方法(同样定义于 libqtile/lazy.py),支持以下筛选条件:
| 参数 | 类型 | 说明 |
|---|---|---|
focused | Match或None | 限定当前窗口必须满足的匹配条件 |
if_no_focused | bool | 是否让focused条件在没有聚焦窗口时也视为匹配。默认False,即无聚焦窗口时focused条件不生效;当focused是正则且希望在无窗口时也放行时设为True |
layout | str/Iterable[str]或None | 限定一个或多个布局名;None表示所有布局均可 |
when_floating | bool | True时仅在当前窗口为浮动状态触发;False时仅在非浮动状态触发 |
func | callable | 回调函数返回True时才触发(每次按键时求值) |
condition | bool | 一个布尔值,决定 lazy 对象是否运行。与func不同,它在配置文件首次加载时只求值一次 |
对应的判定逻辑在LazyCall.check(self, q)中逐项实现:分别检查条件condition、聚焦窗口Match(配合if_no_focused)、浮动状态when_floating、布局白名单_layouts,以及可调用对象func;任何一项不满足即返回False,只有全部通过才真正执行命令。
以下是 keys.rst 中给出的实战组合:
from libqtile.config import Key keys = [ # 仅在特定布局下触发 Key( [mod, 'shift'], "j", lazy.layout.grow().when(layout='verticaltile'), lazy.layout.grow_down().when(layout='columns') ), # 限定当前窗口非浮动时才触发 Key([mod], "f", lazy.window.toggle_fullscreen().when(when_floating=False)), # 限定当前窗口为浮动时才触发 Key([mod], "f", lazy.window.toggle_fullscreen().when(when_floating=True)), # 用 Match 匹配当前窗口属性(如 wm_class) Key([mod], "f", lazy.window.toggle_fullscreen().when(focused=Match(wm_class="yourclasshere"))) ]Match对象的完整用法(支持按title、wm_class、role、wm_type、wm_instance_class、net_wm_pid、wid匹配,以及&、|、~、^逻辑组合)请参阅 Matching windows 文档。
结合鼠标绑定与默认配置中的真实用法
lazy 调用同样大量出现在 mouse.rst 描述的Drag/Click鼠标绑定中,例如经典的三键方案:
from libqtile.config import Click, Drag mouse = [ Drag([mod], "Button1", lazy.window.set_position_floating(), start=lazy.window.get_position()), Drag([mod], "Button3", lazy.window.set_size_floating(), start=lazy.window.get_size()), Click([mod], "Button2", lazy.window.bring_to_front()) ]在随包分发的 默认配置文件 里,可以看到 lazy 调用与Key结合的真实样板:lazy.layout.down()/lazy.layout.up()绑定到mod + j/k移动焦点,lazy.layout.next()绑定到mod + space切换窗口焦点,lazy.layout.grow_left()/grow_right()/grow_down()/grow_up()绑定到mod + ctrl + h/l/j/k调整窗口大小,另有lazy.spawncmd()、lazy.spawn(...)、lazy.reload_config()、lazy.restart()、lazy.shutdown()等通用绑定。如果你刚开始配置 Qtile,这份文件是观察 lazy 对象组织方式的绝佳参照。
小结
Lazy objects 是 Qtile"配置即 Python 程序"理念的枢纽:通过lazy = InteractiveCommandClient(LazyCommandInterface()),每一次lazy.xxx()调用都被转换为命令图上的一个引用,事件触发时才沿bar / group / layout / screen / widget / window / core路径解析并执行。掌握本文介绍的常用函数、lazy.function的两种参数注入方式、when()条件触发,以及Match、Drag/Click的配合用法,你就能写出既能覆盖日常操作、又具备精细条件控制的高质量 Qtile 配置。更多命令的完整清单,请继续查阅 Commands API 索引 与 Keys 配置文档。
- 桌面应用
- 操作系统
【免费下载链接】qtile
:cookie: A full-featured, hackable tiling window manager written and configured in Python (X11 + Wayland)
相关推荐
Qtile 鼠标绑定完全指南:深入理解 Click / Drag / EzClick / EzDrag 配置
Qtile 鼠标绑定完全指南:深入理解 Click / Drag / EzClick / EzDrag 配置 导读 Qtile 作为一款使用 Python 编写
桌面应用操作系统WezTerm show-keys 命令完全指南:导出并校验你的按键与鼠标绑定配置
WezTerm show keys 命令完全指南:导出并校验你的按键与鼠标绑定配置 wezterm show keys 是 WezTerm 提供的一个命令行工具
桌面应用开发工具跨平台Qtile 命令图接口(Interfaces)全解析:lazy、qtile shell、cmd-obj 与 Python 命令客户端
Qtile 命令图接口(Interfaces)全解析:lazy、qtile shell、cmd obj 与 Python 命令客户端 导读 Qtile 是一款用
桌面应用操作系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考