- 桌面应用
- 操作系统
【免费下载链接】qtile
:cookie: A full-featured, hackable tiling window manager written and configured in Python (X11 + Wayland)
导读
在 Qtile 中,窗口的尺寸与位置通常由当前布局(layout)决定,但窗口自身仍可通过一组丰富的命令改变外观与行为:切换浮动状态、进入全屏、调节透明度、调整 Z 轴图层次序、跨组/跨屏移动等。本文以 docs/manual/commands/api/windows.rst 为骨架,结合 libqtile/backend/base/window.py、X11/Wayland 双后端实现与对应测试,系统梳理窗口对象暴露的全部命令及其底层原理。读完本文,你将能够在键位绑定、鼠标回调、qtile shell、qtile cmd-obj 以及 Python 脚本(CommandClient / InteractiveCommandClient)中熟练调用这些窗口命令,并理解 X11 与 Wayland 后端在实现上的差异。
窗口对象与命令图
Qtile 将窗口管理器拆解为八类基本对象,构成一棵命令图(command graph):layouts、windows、groups、bars、widgets、screens、core,以及一个特殊的root节点。图形结构见 docs/manual/commands/command_graph.rst,每一条边都表示"持有对某对象的引用"。
窗口节点正是这八类节点之一。如 windows.rst 所述:
- 窗口的尺寸与位置由当前布局决定;
- 但窗口仍可以多种方式改变自身外观,例如切换浮动状态、全屏、调节透明度;
- 窗口还可以访问与其显示相关的对象,即它所在的屏幕(screen)、所属的工作组(group)和当前布局(layout)。
这种连通性使得在某个对象上触发的回调中,可以轻易"顺藤摸瓜"地访问到相关对象。
文档自动生成机制
windows.rst 本身是一份由 Sphinx 指令生成的 API 页面:它通过qtile_commands指令,以libqtile.backend.base.Window为基类,将代码中所有用@expose_command()装饰的方法自动展开为命令文档。对应的指令实现位于 docs/qtile_docs/commands.py:它反射类成员,检查_cmd标记,收集全部公开命令,并同时生成lazy.window.<command>()与qtile cmd-obj -o window -f <command>两种调用语法的示例。因此,只要源码中新增了@expose_command()方法,本页 API 文档就会自动同步更新,这保证了文档与实现始终一致。
访问窗口命令的多种接口
命令图中的命令可通过五种接口调用,详见 docs/manual/commands/interfaces.rst。以窗口命令为例,同一操作在五种接口中的写法分别为:
# 1. lazy 接口(用于配置脚本中的键位与鼠标回调) lazy.window.toggle_floating() # 2. qtile shell(将命令图映射为虚拟文件系统) > cd window window > toggle_floating() # 3. qtile cmd-obj(适合 shell 脚本) qtile cmd-obj -o window -f toggle_floating # 4. CommandClient(低级 Python 接口) >>> from libqtile.command.client import CommandClient >>> c = CommandClient() >>> c.navigate("window", None).call("toggle_floating") # 5. InteractiveCommandClient(高级 Python 接口,语法模仿 lazy) >>> from libqtile.command.client import InteractiveCommandClient >>> c = InteractiveCommandClient() >>> c.window.toggle_floating()需要注意:从根节点出发时,当前window、group、layout、screen都可以省略选择器而直接访问"当前对象";而widget与bar节点必须携带选择器。若某条路径解析不到对象(例如访问一个未显示在屏幕上的组所关联的 screen),会抛出CommandError: No object screen in path ...异常。
窗口对象的类层次:Window / Internal / Static
在 libqtile/backend/base/window.py 中,窗口对象被划分为三个抽象子类,均继承自_Window(CommandObject的子类),由各后端分别实现:
Window:普通客户端窗口,由布局管理。它持有qtile引用、float_x/float_y浮动偏移、bordercolor边框颜色与_float_state浮动状态等关键属性。repr形如Window(name='...', wid=...)。Internal:Qtile 自身拥有的内部窗口(典型如 bar)。它需要实现create_drawer()来绘制内容,并处理按键、指针进入/离开/移动、按钮点击等事件。Static:绑定到屏幕而非工作组的窗口,info()命令返回name、wm_class、x、y、width、height、id、opacity等字段。
此外,窗口还有若干只读属性供命令与布局使用,例如has_fixed_ratio()(客户端要求固定宽高比,对应 X11 的PAspect提示)、has_fixed_size()(固定尺寸)与urgent(紧急请求焦点,X11 下对应_NET_WM_STATE_DEMANDS_ATTENTION)。
布局定尺寸,窗口定状态:浮动 / 全屏 / 最大化 / 最小化
窗口的几何行为由浮动状态机统一管理。状态定义见 libqtile/backend/base/float_states.py:
class FloatStates(enum.Enum): NOT_FLOATING = 1 # 平铺 FLOATING = 2 # 浮动 MAXIMIZED = 3 # 最大化 FULLSCREEN = 4 # 全屏 TOP = 5 # 置顶 MINIMIZED = 6 # 最小化基类的Window.maximized、fullscreen、minimized属性均直接以_float_state为准。进入/退出全屏的核心逻辑在_set_fullscreen()(window.py#L305-L323):进入全屏时先调用save_float_state()记录当前几何与状态,然后按屏幕全尺寸(扣除fullscreen_border_width)重排浮动几何;退出时调用restore_float_state()恢复。最大化(maximizedsetter)与之对称,使用max_border_width作为边框扣除。save_float_state()与restore_float_state()(window.py#L340-L361)把base_x/base_y/base_width/base_height作为"退出全屏/最大化后恢复"的锚点。
状态切换命令
基类Window声明了一组成对的抽象命令(均由具体后端实现):
| 命令 | 作用 |
|---|---|
toggle_floating()/enable_floating()/disable_floating() | 切换/强制进入/强制退出浮动 |
toggle_maximize()/toggle_minimize() | 切换最大化 / 最小化 |
toggle_fullscreen()/enable_fullscreen()/disable_fullscreen() | 切换/进入/退出全屏 |
在 Wayland 后端(libqtile/backend/wayland/window.py#L864-L893)中,这些命令实现得非常直接:toggle_floating()即self.floating = not self.floating,toggle_fullscreen()即self.fullscreen = not self.fullscreen,fullscreen与maximized的 setter 进一步触发_update_fullscreen()/_update_maximized()向合成器同步状态。进入浮动时若配置了floats_kept_above=True,窗口会被自动放入 keep-above 图层(见get_new_layer(),wayland/window.py#L634-L641)。
X11 后端在info()中(x11/window.py#L668-L694)额外返回float_info(浮动时的 x/y/width/height)与floating/maximized/minimized/fullscreen布尔字段。测试 test/backend/x11/test_window.py#L195-L203 验证了窗口从浮动(10×10)切换为平铺(398×578)再切换为全屏(800×600)的几何变化。
透明度控制
基类Window提供三组透明度命令(window.py#L533-L551):
set_opacity(opacity):设置透明度,取值范围0.1~1.0(小于 0.1 被钳制到 0.1,大于 1 被钳制到 1);up_opacity()/down_opacity():以 0.1 为步长递增/递减;- 只读属性
opacity(0 表示全透明,1 表示不透明)。
在 X11 后端,透明度读写映射到 EWMH 的_NET_WM_WINDOW_OPACITY属性(x11/window.py#L705-L722):写入时将 0.0~1.0 的浮点值按0xFFFFFFFF缩放为整数,读取时再换算回两位小数。测试 test/backend/wayland/test_window.py#L122-L159 完整覆盖了边界行为:set_opacity(5.0)被钳到 1.0、set_opacity(-1.0)被钳到 0.1、连按down_opacity()十次后停在 0.1、连按up_opacity()十五次后停在 1.0——这也验证了"基类的 IPC 命令在 0.1 处钳制而非 0.0"这一细节。
Z 轴图层:置顶、置底与堆叠次序
窗口堆叠遵循 EWMH 的分层规则(desktop 层 → below 层 → 普通层 → above/dock 层 → 聚焦的全屏层),Qtile 还额外增加了一个"scratchpad 永远在最上"的图层。基类定义了五组图层命令(window.py#L153-L215):
| 命令 | 语义 |
|---|---|
keep_above(enable=None) | 保持窗口在其它窗口之上;不传参时翻转当前状态 |
keep_below(enable=None) | 保持窗口在其它窗口之下;不传参时翻转当前状态 |
move_up(force=False) | 沿 Z 轴向上移动一个位置;普通窗口不会被提升到 keep_above 窗口之上 |
move_down(force=False) | 沿 Z 轴向下移动一个位置;普通窗口不会被压到 keep_below 窗口之下 |
move_to_top() | 移动到当前图层的顶部(例如三个 keep_above 窗口中,调用者排第一) |
move_to_bottom() | 移动到当前图层的底部 |
bring_to_front() | 无视所有分层规则直接置顶;窗口失去焦点后按规则重新入栈 |
force=True的语义:move_up(force=True)允许把 keep_below 的窗口提升上去(同时清除其 keep_below 状态),move_down(force=True)同理允许压过 keep_above 窗口。keep_above/keep_below在 X11 后端通过读写_NET_WM_STATE中的_NET_WM_STATE_ABOVE/_NET_WM_STATE_BELOW原子实现(x11/window.py#L1394-L1484),随后调用change_layer()依据get_layering_information()(x11/window.py#L910-L971)重排堆叠。
Wayland 后端则直接映射到 wlr 合成器的图层:keep_above将视图 reparent 到LAYER_KEEPABOVE,keep_below到LAYER_KEEPBELOW,bring_to_front到LAYER_BRINGTOFRONT并调用qw_view_raise_to_top(wayland/window.py#L80-L121)。对应测试 test/backend/wayland/test_window.py#L88-L98 验证了bring_to_front()会把窗口从LAYER_LAYOUT移到LAYER_BRINGTOFRONT。
位置与尺寸:浮动几何的精细控制
基类抽象定义了以下几何命令,全部由后端实现:
| 命令 | 作用 |
|---|---|
place(x, y, width, height, borderwidth, bordercolor, above=False, margin=None, respect_hints=False) | 把窗口放到指定位置并设置尺寸(布局调度的底层通道) |
get_position() | 返回(x, y) |
get_size() | 返回(width, height) |
move_floating(dx, dy) | 浮动窗口相对位移 |
resize_floating(dw, dh) | 浮动窗口增减尺寸 |
set_position_floating(x, y) | 浮动窗口移动到绝对坐标 |
set_size_floating(w, h) | 浮动窗口设置为指定尺寸 |
set_position(x, y) | 浮动窗口移动;平铺窗口则与指针下的窗口交换位置 |
center() | 将浮动窗口在屏幕上居中 |
place()是布局调度窗口的底层通道。X11 实现(x11/window.py#L804-L908)展示了它的完整细节:
margin接受单个 int(四边等距)或[N, E, S, W]列表,并据此收缩目标几何;respect_hints=True时会尊重客户端的 WM_SIZE_HINTS:受PMinSize/PMaxSize钳制、按PAspect校正宽高比、按base_width/width_inc与base_height/height_inc对齐增量;- 会保存
float_x/float_y屏幕相对偏移,调用configure()下发 X 协议,随后paint_borders()绘制边框并发送合成ConfigureNotify。
Wayland 的place()(wayland/window.py#L178-L258)同样处理 margin,并把边框颜色列表转换为 C 层的qw_border数组后调用合成器接口;其set_position()在窗口为平铺状态时会遍历同组窗口,与指针所在窗口调用group.layout.swap()交换(wayland/window.py#L840-L857)。
center()命令是基类默认实现(window.py#L569-L592):仅对浮动窗口生效,且要求窗口已属于某个显示中的屏幕,然后按(screen.width - self.width) // 2计算居中坐标并调用place(..., above=True, respect_hints=True)。
在鼠标回调中的典型用法
窗口几何命令最常见的实战场景是鼠标拖拽与调整大小,参考 docs/manual/config/mouse.rst:
from libqtile.config import Click, Drag mouse = [ # 用 mod+左键拖动浮动窗口 Drag([mod], "Button1", lazy.window.set_position_floating(), start=lazy.window.get_position()), # 用 mod+右键调整浮动窗口大小 Drag([mod], "Button3", lazy.window.set_size_floating(), start=lazy.window.get_size()), # 用 mod+中键置顶窗口 Click([mod], "Button2", lazy.window.bring_to_front()) ]其中start=提供了拖拽的起始几何快照,Drag会在指针移动时把增量传入绑定命令。
跨组与跨屏移动:togroup 与 toscreen
togroup(group_name=None, switch_group=False, toggle=False)(基类文档位于 window.py#L480-L505)把窗口移动到指定工作组:
# 移动到当前组(无实际效果) togroup() # 移动到名为 "a" 的组 togroup("a") # 移动到组 "a",并切换到该组 togroup("a", switch_group=True) # 若组 "a" 已在屏幕显示,则改用上一个使用过的组 togroup("a", toggle=True)toscreen(index=None)则把窗口移动到指定屏幕——本质上是把窗口移交到该屏幕当前显示的组(window.py#L507-L531):
# 移动到当前屏幕 toscreen() # 移动到屏幕 0 toscreen(0)索引越界会抛出CommandError: No such screen: ...。Wayland 的togroup()实现(wayland/window.py#L526-L559)还会先hide()再移交,并在组未配置persist=True时清理空组。
静态窗口:从布局中解放
static(screen=None, x=None, y=None, width=None, height=None)把普通窗口转换为静态窗口——它不再属于任何工作组,而是直接绑定屏幕,几何由用户指定,其余未指定的值沿用窗口当前状态。转换时窗口对象的defunct标记被置为 True(表示不再作为普通窗口被管理),并触发client_managedhook(Wayland 实现见 wayland/window.py#L469-L523)。测试 test/backend/wayland/test_window.py#L40-L58 验证了 XDG shell 窗口调用static()后仍可从info()["shell"]读到"XDG",而 layer shell 窗口的 shell 字段为"layer"。
关闭、聚焦与信息查询
kill
kill()关闭窗口。X11 实现遵循 ICCCM:若窗口声明了WM_DELETE_WINDOW协议,则发送ClientMessage礼貌请求关闭;否则直接KillClient(x11/window.py#L724-L747)。Wayland 实现直接调用合成器视图的kill()(wayland/window.py#L167-L168)。典型键位绑定:Key(["mod1"], "F4", lazy.window.kill())。
focus 与焦点机制
focus(warp=True)聚焦窗口并可选地将指针移动到窗口中心。X11 实现(x11/window.py#L1289-L1333)展示了一套完整的 ICCCM 聚焦协商流程:
- 若
WM_HINTS的InputHint为真,直接SetInputFocus; - 否则若窗口声明了
WM_TAKE_FOCUS协议,发送携带合法时间戳的ClientMessage(时间戳不能是CurrentTime); - 聚焦成功后同步
_NET_WM_STATE_FOCUSED状态、清除 urgent 标记、更新_NET_ACTIVE_WINDOW、重抓/解除旧窗口的按钮事件,并触发client_focushook。
此外还有两个与焦点相关的辅助属性:can_steal_focus决定窗口是否可以抢走焦点(X11 下类型为notification的窗口默认不允许);activate_by_config()则依据配置项focus_on_window_activation(取值focus/smart/urgent/never,也可为可调用对象)决定窗口请求激活时的行为——smart模式下若窗口在别的屏幕,会置为 urgent 并触发client_urgent_hint_changedhook(window.py#L611-L632)。
info 与 inspect
info()返回窗口的元信息。基类文档要求至少包含name、x、y、width、height、group、id、wm_class(window.py#L136-L151);X11 与 Wayland 实现在此基础上追加floating、maximized、minimized、fullscreen、float_info等字段,Wayland 还额外提供shell与opacity(wayland/window.py#L657-L684)。get_hints()(仅 X11)返回 WM_HINTS / WM_SIZE_HINTS 解析结果。inspect()(仅 X11)返回"关于窗口的一切":X 属性(backing_store、map_state、事件掩码等)、属性列表、协议、normal hints、state 与 float_info(x11/window.py#L1340-L1392),对应测试 test/backend/x11/test_window.py#L759 的test_inspect_window。
空闲抑制:add_idle_inhibitor 与 remove_idle_inhibitor
为了在播放视频、全屏展示等场景下阻止系统进入空闲/睡眠,基类暴露了空闲抑制命令(window.py#L639-L652):
add_idle_inhibitor(inhibitor_type="open"):为窗口创建抑制规则,inhibitor_type可选"open"、"focus"、"fullscreen"、"visible",默认"open";remove_idle_inhibitor():移除该窗口的抑制规则。
两者最终都交由qtile.core.idle_inhibitor_manager统一管理(对应 X11 的_NET_WM_IDLE_INHIBIT与 Wayland 的 idle inhibit 协议,见 libqtile/backend/x11/idle_notify.py 与 libqtile/backend/wayland/idle_inhibit.py)。配置中还可以通过idle_inhibitors规则批量声明"哪些窗口、在什么条件下自动添加抑制"(window.py#L634-L637)。
在键位配置中的综合实战
把上述命令组装成一套常见的窗口快捷键配置,参考 docs/manual/config/keys.rst:
from libqtile.config import Key, Match from libqtile.lazy import lazy keys = [ # 关闭窗口 Key([mod], "c", lazy.window.kill()), # 切换浮动 Key([mod], "space", lazy.window.toggle_floating()), # 切换全屏(可配合 .when() 限定条件,例如仅在窗口浮动时生效) Key([mod], "f", lazy.window.toggle_fullscreen().when(when_floating=True)), # 仅对特定 wm_class 生效 Key([mod], "x", lazy.window.toggle_fullscreen().when(focused=Match(wm_class="yourclasshere"))), # Z 轴调整 Key([mod], "k", lazy.window.move_up()), Key([mod], "j", lazy.window.move_down()), Key([mod, "shift"], "k", lazy.window.keep_above()), Key([mod, "shift"], "j", lazy.window.keep_below()), Key([mod, "control"], "k", lazy.window.bring_to_front()), # 跨组移动:把窗口移到组 "2" 并切换过去 Key([mod, "shift"], "2", lazy.window.togroup("2", switch_group=True)), # 透明度微调 Key([mod], "equal", lazy.window.up_opacity()), Key([mod], "minus", lazy.window.down_opacity()), ]其中lazy.window.<command>()创建的是对命令的引用而非立即调用——这正是它能与键位/鼠标回调绑定的前提(详见 docs/manual/config/lazy.rst 中的 Window functions 一节与 docs/manual/commands/interfaces.rst 的说明)。togroup的可选参数switch_group、toggle也都在 lazy.rst 中有对应描述。
后端差异小结与测试佐证
| 能力 | X11 后端实现 | Wayland 后端实现 |
|---|---|---|
| 置顶/置底 | EWMH_NET_WM_STATE原子 +ConfigureWindow堆叠 | wlr layer-shell 图层 reparent |
| 透明度 | _NET_WM_WINDOW_OPACITY属性 | 合成器直接支持 |
| 焦点 | ICCCMWM_TAKE_FOCUS/ InputHint 协商 | qw_server_active_view+focus_window() |
| 几何提示 | 完整尊重WM_SIZE_HINTS | respect_hints暂标注为 TODO(wayland/window.py#L214) |
| 专属命令 | get_hints()、inspect() | is_visible()直接查询合成器可见性 |
所有窗口命令均被 test/backend/x11/test_window.py 与 test/backend/wayland/test_window.py 覆盖验证,例如 X11 侧的浮动/全屏几何切换(test_window.py#L195-L203)、大小/宽高比提示(test_min_size_hint等)、多色边框(test_multiple_borders),以及 Wayland 侧的透明度边界、图层切换与info()的 shell 字段。
结语
窗口命令 API 是 Qtile"可破解(hackable)"特性的重要一环:布局决定几何,而窗口自身的浮动、全屏、透明度与图层状态完全交给用户通过命令图控制。无论是编写键位配置、实现鼠标手势,还是通过 IPC 编写外部脚本,掌握 windows.rst 中列出的这组命令,就等于掌握了 Qtile 窗口层全部的用户可编程能力。想进一步了解其余对象的命令,可继续阅读 docs/manual/commands/api/index.rst 中的 root、layouts、groups、bars、widgets、screens 与 backend 各页。
- 桌面应用
- 操作系统
【免费下载链接】qtile
:cookie: A full-featured, hackable tiling window manager written and configured in Python (X11 + Wayland)
相关推荐
Qtile 命令图接口(Interfaces)全解析:lazy、qtile shell、cmd-obj 与 Python 命令客户端
Qtile 命令图接口(Interfaces)全解析:lazy、qtile shell、cmd obj 与 Python 命令客户端 导读 Qtile 是一款用
桌面应用操作系统OpenSAGE与原版SAGE引擎对比:兼容性测试与功能差异深度分析
OpenSAGE与原版SAGE引擎对比:兼容性测试与功能差异深度分析 OpenSAGE 是一款免费开源的 SAGE 引擎重实现项目,旨在重现 EA Pacifi
Qtile Shell 实战指南:用命令行交互式驾驭 Qtile 命令图
Qtile Shell 实战指南:用命令行交互式驾驭 Qtile 命令图 Qtile shell( qtile shell )是 Qtile 提供的一个命令行式
桌面应用操作系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考