☰
Qtile 屏幕配置完全指南:Screen、Bar、Widget 与多显示器布局实战
2026/10/6 18:37:04 网站建设 项目流程
  • 桌面应用
  • 操作系统

【免费下载链接】qtile

:cookie: A full-featured, hackable tiling window manager written and configured in Python (X11 + Wayland)

项目地址:https://gitcode.com/gh_mirrors/qt/qtile
点击查看免费下载

导读

本文围绕 Qtile 配置体系中与物理显示器直接相关的screens配置变量展开,系统讲解如何通过Screen、Bar、Widget三层结构组织状态栏、如何处理单屏/多屏(X11 与 Wayland)下的屏幕顺序,以及如何用generate_screens实现根据当前输出设备动态生成屏幕布局、用fake_screens把一块物理屏幕切分为多个逻辑屏幕。读完本文,你将能独立编写一套可插拔、可动态适配笔记本/扩展坞/多显示器环境的 Qtile 屏幕配置。


1. screens 配置变量的定位

在 Qtile 的配置体系中,screens是一个模块级变量,它是"物理屏幕 → 状态栏(Bar)→ 栏内控件(Widget)"这一整条配置链的根节点。也就是说,你在哪里放一个栏、栏里放哪些组件、栏放在屏幕的哪一边,都通过screens统一声明。

从源码看,Screen对象(libqtile/config.py)持有四个栏位:top、bottom、left、right,它们可以是Bar(带控件的栏),也可以是Gap(纯占位间隙,用于在屏幕边缘预留空间而不画任何内容)。Screen.gaps属性(libqtile/config.py)将这四个栏位统一暴露为可迭代对象,管理器在配置阶段会逐个调用它们的_configure,任何一个栏在配置期间抛出异常都会被单独捕获并 finalize,避免影响其他栏的初始化。

一个典型的Screen构造参数包括(libqtile/config.py):

  • top/bottom/left/right:四个方向上的Bar或Gap
  • background:屏幕背景色
  • wallpaper:壁纸图片路径
  • wallpaper_mode:壁纸绘制模式
  • x11_drag_polling_rate:X11 后端拖拽事件轮询频率上限
  • x/y/width/height:几何信息,通常仅在fake_screens场景下显式指定

2. 基础示例:串联 Screen、Bar 与 Widget

把三者串联起来的最简配置如下(这也是官方文档给出的基线示例):

from libqtile.config import Screen from libqtile import bar, widget window_name = widget.WindowName() screens = [ Screen( bottom=bar.Bar([ widget.GroupBox(), window_name, ], 30), ), Screen( bottom=bar.Bar([ widget.GroupBox(), window_name, ], 30), ) ]

这里有两个值得注意的细节:

第一,同一个 Widget 实例可以同时被多个 Bar 引用。上述示例中window_name被同时放进了两块栏。Qtile 会为每个引用位置自动生成镜像副本,内容在各副本之间保持一致。这正是为每块屏幕显示"当前聚焦窗口名"这类相同内容时的推荐做法,避免了为每个屏幕各 new 一个控件的样板代码。

第二,Bar 内部会对控件列表做一次拷贝。在 libqtile/bar.py 中,Bar.__init__执行了self.widgets = widgets.copy(),其注释明确指出:因为多个屏幕可能共享同一个列表对象,如果不拷贝,替换为镜像控件时会互相污染。从实现层面保证了"一个实例多处引用"的安全性。

3. Bar 的外观:背景、透明度与边框

bar.Bar(widgets, size, **config)的第二个位置参数size是栏的"厚度"——水平栏指高度、垂直栏指宽度(libqtile/bar.py)。其余外观参数通过关键字传入,其默认值在Bar.defaults中定义(libqtile/bar.py):

参数默认值说明
background"#000000"背景色,可为单色字符串或渐变颜色列表
opacity1整个栏窗口的透明度
margin0栏周围留白,支持int或[N, E, S, W]四值列表
border_color"#000000"边框颜色,支持str或[N, E, S, W]四值列表
border_width0边框宽度,支持int或[N, E, S, W]四值列表
reserveTrue是否预留屏幕空间;设为False时栏会浮在窗口之上

3.1 纯色与线性渐变背景

Bars 既支持纯色背景,也支持由颜色列表构成的线性渐变。例如:

  • bar.Bar(..., background="#000000")得到黑色背景(默认值)
  • bar.Bar(..., background=["#000000", "#FFFFFF"])得到从黑到白渐变的背景

3.2 透明度:alpha 通道与 opacity 的区别

Bars(以及 Widgets)通过给颜色追加 alpha 值实现透明度,例如:

bar.Bar(..., background="#00000000") # 完全透明的栏

注意这不同于opacity参数:background带 alpha 只影响栏自身的绘制,不会影响栏内控件内容;而opacity设置的是整个栏窗口(含全部控件)的透明度。

官方文档特别注明一个 X11 后端的行为差异:在 X11 后端,如果background颜色是完全不透明的,栏的透明度特性会被禁用。也就是说,想让 X11 下的栏真正透明,背景色必须携带 alpha 通道。

3.3 边框:全局与四边定制

通过border_width和border_color可以为栏添加边框:

  • 传单个值时,四个边使用相同值;
  • 传四个值的列表时,按(top, right, bottom, left)顺序分别设置。

例如border_width=[2, 0, 2, 0]只在栏的顶部和底部画出 2 像素的边框。源码中,Bar.__init__会把int形式的宽度/边距统一扩展为四值列表(libqtile/bar.py),并调用is_valid_colors校验边框颜色合法性,非法颜色会降级为不绘制边框(libqtile/bar.py)。此外,边框宽度会被并入 margin 计算,即边框绘制在 margin 空出的区域内,不会挤压栏内控件的可用空间(libqtile/bar.py)。

4. 多屏幕:列表顺序与显示器顺序对齐

screens是一个Screen对象列表,列表中元素的顺序应当与显示服务器报告的屏幕顺序一致。Qtile 在_process_screens中按输出顺序逐个配对:第i个输出使用config_screens[i],如果配置的屏幕数量不足,会自动补一个空白Screen();如果输出数量少于配置数量,则多余的配置不会被使用(libqtile/core/manager.py)。

4.1 X11:用 xrandr 确认顺序

在 X11 下,可以通过命令查看当前显示器的顺序:

xrandr --listmonitors

输出的 monitor 编号顺序即 Qtile 理解中的屏幕顺序。调整显示器在桌面上的相对位置(左右/上下)时,请参考 Arch Wiki 的 Multihead 页面(此处不提供外部链接),关键是把screens列表顺序与物理布局一一对应。

4.2 Wayland:wlr-output-management 协议

Wayland 后端支持wlr-output-management协议,允许 Kanshi 这类外部工具动态配置输出(如旋转、分辨率、位置)。这意味着在多屏场景下,显示服务器侧的输出布局可以由外部工具接管,而 Qtile 的screens配置只需关注每个输出的栏样式即可。

4.3 屏幕顺序不匹配时的焦点问题

从源码实现看,Qtile 对Screen的相等性判断做了特殊处理(libqtile/config.py):当输出信息(port、make、model、serial)可用时,优先用输出信息判断两块 Screen 是否代表同一块物理显示器,否则才回退到几何位置比较。这保证了在显示器热插拔、几何变化后,组/焦点关系仍能正确跟随同一块物理屏幕——这正是多屏配置中"顺序要对齐"背后的机制支撑。

5. 动态屏幕配置:generate_screens

静态列表只适用于固定硬件环境。Qtile 允许把screens替换为一个名为generate_screens的函数,由它根据当前已连接的输出动态返回屏幕列表:

from libqtile.config import Output, Screen def generate_screens(outputs: list[Output]) -> list[Screen]: ...

函数签名固定为:接收list[Output],返回list[Screen]。Output是一个数据类(libqtile/config.py),其字段如下:

字段类型含义
portstr \| None显示服务器看到的连接器名称,如"HDMI-1"、"DP-1"
makestr \| None显示器报告的生产商字符串
modelstr \| None显示器报告的型号字符串
serialstr \| None显示器报告的序列号,跨重启稳定,适合唯一标识某台显示器
rectScreenRect该输出的几何信息(x、y、width、height)

其中rect字段在Output的相等比较中被显式排除(field(compare=False)),注释说明了原因:比较两个输出时应关注物理硬件是否同一,几何信息无关紧要。ScreenRect还提供了hsplit/vsplit两个切分方法(libqtile/config.py),可用于按列宽/行高把一块输出切成多个逻辑区域。

5.1 工作流:按输出数量分发,按序列号绑定布局

官方文档给出了一个完整的实战示例,其思路分为两层:

  • 第一层,按len(outputs)分发:1 个输出走one_screen(),2 个输出走two_screens(),更多走three_screens(outputs);
  • 第二层,在多屏场景下用output.serial把每个输出绑定到固定的屏幕布局,保证"无论插到哪个端口,左边永远是左布局、中间永远是中间布局"。

完整示例(合并自官方文档,widget_defaults与height由用户自行定义):

import subprocess from libqtile import bar, widget from libqtile.config import Output, Screen def one_screen(): # 示例:根据当前时区动态决定显示哪些时钟控件 current = subprocess.check_output( ["timedatectl", "show", "--value", "--property=Timezone"] ).decode("utf-8") # 美洲时区用 12 小时制,其余用 24 小时制 fmt = '%Y-%m-%d %a %I:%M %p' if not current.startswith("America"): fmt = '%Y-%m-%d %a %H:%M' clocks = [ widget.Clock(format=fmt, **widget_defaults), ] # 不在家(America/Denver)时,额外显示家里的时间 if current != "America/Denver\n": clocks.insert(0, widget.Clock( format='%I:%M %p Mountain', timezone='America/Denver', **widget_defaults, )) return [ Screen(top=bar.Bar([ widget.GroupBox(**widget_defaults), widget.Prompt(**widget_defaults), widget.Clipboard(timeout=None, width=bar.STRETCH, max_width=None), widget.Battery(**widget_defaults), widget.Systray(**widget_defaults), ] + clocks, height, )), ] def two_screens(): # 扩展坞上的笔记本,或投影演讲时的双屏 return [ Screen(top=bar.Bar([ widget.GroupBox(**widget_defaults), widget.Prompt(**widget_defaults), widget.Spacer(), ], height)), Screen(top=bar.Bar([ widget.GroupBox(**widget_defaults), widget.Clipboard(timeout=None, width=bar.STRETCH, max_width=None), widget.Systray(**widget_defaults), widget.Clock(format='%Y-%m-%d %a %I:%M %p', **widget_defaults), ], height)), ] def three_screens(outputs: list[Output]): # 用序列号绑定屏幕布局,与端口名无关 screens = [] for output in outputs: if output.serial == "M2GCR1AM28PL": # 左屏 scr = Screen(top=bar.Bar([ widget.GroupBox(**widget_defaults), widget.Prompt(**widget_defaults), widget.Spacer(), ], height)) elif output.serial == "1B8W0P3": # 中屏 scr = Screen(top=bar.Bar([ widget.GroupBox(**widget_defaults), widget.Systray(**widget_defaults), widget.Clock(format='%Y-%m-%d %a %I:%M %p', **widget_defaults), ], height)) elif output.serial == "M2GCR1AS21NL": # 右屏 scr = Screen(top=bar.Bar([ widget.GroupBox(**widget_defaults), widget.Spacer(), widget.Clock(format='%Y-%m-%d %a %I:%M %p', **widget_defaults), ], height)) else: raise Exception(f"unknown output {output}") screens.append(scr) return screens def generate_screens(outputs: list[Output]) -> list[Screen]: if len(outputs) == 1: return one_screen() elif len(outputs) == 2: return two_screens() else: return three_screens(outputs)

one_screen()展示了 generate_screens 的另一个价值:布局甚至可以根据运行环境实时计算——通过timedatectl读取当前时区,决定要不要额外插入一个"家里时间"的时钟控件。

5.2 优先级规则与源码佐证

官方文档明确:generate_screens与screens互斥,若同时定义,generate_screens优先,screens被忽略。源码在 libqtile/core/manager.py 中印证了这一行为:get_screens_from_config依次检查fake_screens→generate_screens→screens,并且当两者同时存在时会打出一条 warning 日志 "Both screens and generate_screens are defined in config. Using generate_screens."。

测试用例也覆盖了 generate_screens 的边界场景:

  • test/test_config.py 验证了返回屏幕数少于输出数时,多余输出被自动补默认 Screen 的行为;
  • test/test_config.py 验证了返回屏幕数多于输出数时多余屏幕被丢弃;
  • test/test_config.py 验证了按 serial 匹配输出返回对应屏幕的完整流程。

5.3 输出信息从哪里来

generate_screens收到的outputs由管理器的get_output_info()提供(libqtile/core/manager.py)。其内部会把坐标相同的输出做一次合并(取最大的宽高并集),用于兼容某些显示服务器把同一物理屏幕报告为多个输出(如 XWayland 场景)的情况;之后为每个输出位置构造一个Output对象传给配置函数。也就是说,outputs的数量不一定等于你物理上插了几根线,而是"去重后有效的输出区域"数量,理解这一点有助于避免在动态配置里做数量上的硬编码假设。

6. Fake Screens:把一块物理屏幕切分成多个逻辑屏幕

除了screens变量,Qtile 还提供了fake_screens变量,用于把一块物理显示器切分成多个逻辑 Screen。每个 fake screen 通过显式指定x、y、width、height(相对整个桌面坐标空间)来界定自己的区域,并各自携带独立的 Bar。

官方文档给出的四屏切分示例(含注释中的 ASCII 布局示意图),可以切成"中间带空洞"的非矩形区域组合:

# 布局示意(文档注释原图) # 600 300 # |-------------|-----| # | 480| |580 # | A | B | # |----------|--| | # | 400|--|-----| # | C | |400 # |----------| D | # 500 |--------| # 400

对应的配置片段(完整定义四个 Screen,此处展示前两个的完整写法):

from libqtile.config import Screen from libqtile import bar, widget fake_screens = [ Screen( bottom=bar.Bar( [ widget.Prompt(), widget.Sep(), widget.WindowName(), widget.Sep(), widget.Systray(), widget.Sep(), widget.Clock(format='%H:%M:%S %d.%m.%Y') ], 24, background="#555555" ), x=0, y=0, width=600, height=480 ), Screen( top=bar.Bar( [ widget.GroupBox(), widget.WindowName(), widget.Clock() ], 30, ), x=600, y=0, width=300, height=580 ), # 第三个 Screen:x=0, y=480, width=500, height=400(top 栏) # 第四个 Screen:x=500, y=580, width=400, height=400(top 栏) ]

这个例子里各区域高度不同(B 比 A 高)、D 向下突出,因此切分结果中间有一个"空洞"——Qtile 的 fake screens 完全支持这种任意几何划分。

6.1 fake_screens 在源码中的处理路径

从管理器源码可以看到 fake_screens 的两条特殊处理路径:

  • get_output_info()中,若配置了fake_screens,直接为每个 fake screen 构造一个无端口/厂商信息的Output(libqtile/core/manager.py),也就是说 fake screens 会"伪装"成一组输出参与后续流程;
  • get_screens_from_config()中,fake_screens优先级最高,直接原样返回配置对象(libqtile/core/manager.py)。

在实际测试中,fake_screens 是 Qtile 单测与文档截图生成的基础设施。例如 test/test_fakescreen.py 定义了一个 4 块 fake screen 的配置,并在test_basic中断言第一个 screen 的几何信息为x=0, y=0, width=500, height=340(test/test_fakescreen.py);test/widgets/test_base.py、test/widgets/docs_screenshots/conftest.py 等测试同样依赖 fake_screens 来模拟无显示环境下的栏布局验证。

6.2 与普通多屏的区别

  • screens/generate_screens:每个 Screen 对应一块真实输出,几何由显示服务器决定;
  • fake_screens:几何完全由配置中的x/y/width/height决定,同一块物理屏上可以有多个 Screen,适合在单个超宽屏上实现"多工作区并排"(类似平铺分区但每个分区拥有独立栏)的用法。

需要提醒的是,使用 fake_screens 时x、y、width、height必须手动给出且相互之间不应重叠,否则会破坏 Qtile 对屏幕区域的划分假设。

7. 第三方状态栏:dzen2 / xmobar 等

如果你来自其他窗口管理器,已经配置好了 dzen2、xmobar 之类的独立状态栏,Qtile 完全可以继续沿用它们——不需要任何额外配置。第三方栏本质上是普通 X11/Wayland 窗口,Qtile 会自动把屏幕空间让给它们。做法很简单:把栏作为独立程序启动(如放入自动启动脚本),然后在配置里不为其对应的屏幕定义 Bar,或用bar.Gap预留出相应高度的空间,Qtile 就会在其下排布窗口。这得益于 Qtile 的栏/间隙系统对"非 Qtile 窗口"的天然兼容——它只关心需要自己绘制的区域。

8. 参考:核心类速查

本节汇总本文涉及的三个核心类,均可在源码中进一步查阅:

  • Screen(libqtile/config.py):物理/逻辑屏幕对象,持有四个方向的 Bar/Gap、背景与壁纸参数、几何信息及输出绑定关系;
  • Bar(libqtile/bar.py):可容纳 Widget 的栏,负责控件绘制、边框、边距、透明度与屏幕空间预留;
  • Gap(libqtile/bar.py):纯占位间隙,用于在屏幕边缘留出空间而不渲染任何内容。

三者共同构成了 Qtile "屏幕 → 栏 → 控件"的完整配置层级。结合本文提到的_process_screens、get_output_info、get_screens_from_config等管理器内部流程,你可以进一步阅读 libqtile/core/manager.py 理解热插拔与屏幕重配置的完整生命周期。


结语

从静态的screens列表,到按输出数量与序列号分发的generate_screens,再到把单块物理屏切分为多逻辑区的fake_screens,Qtile 的屏幕配置体系覆盖了从单显示器到复杂多屏工作站的几乎全部场景。配置时只需记住三个要点:列表顺序对齐显示服务器顺序、动态场景优先使用generate_screens(并与screens互斥)、跨端口固定布局依赖Output.serial的稳定性。掌握这套机制后,你的 Qtile 配置就能在笔记本、扩展坞与多显示器工位之间无缝切换。

  • 桌面应用
  • 操作系统

【免费下载链接】qtile

:cookie: A full-featured, hackable tiling window manager written and configured in Python (X11 + Wayland)

项目地址:https://gitcode.com/gh_mirrors/qt/qtile
点击查看免费下载

相关推荐

上一篇:Android开发者必学:MultipleStatusView的事件监听与状态回调处理技巧
下一篇:OfficeToPDF:高效文档转换利器,一键生成专业PDF

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询