X窗口系统
X窗口系统(X Window System,也常称为X11或X,天窗口系统)是一种以位图方式显示的软件窗口系统。最初是1984年麻省理工学院的研究,之后变成UNIX、类UNIX、以及OpenVMS等操作系统所一致适用的标准化软件工具包及显示架构的运作协议。X窗口系统通过软件工具及架构协议来创建操作系统所用的图形用户界面,此后则逐渐扩展适用到各形各色的其他操作系统上。现在几乎所有的操作系统都能支持与使用X。更重要的是知名的桌面环境——GNOME和KDE也都是以X窗口系统为基础建构成的。

The X Window SystemTM provides the base technology for developing graphical user interfaces. At a very basic level, X draws the elements of the GUI on the user's screen and builds in methods for sending user interactions back to the application. Using application GUI development toolkits, such as Motif, an application developer can quickly create an application interface to the end user.
由于X只是工具包及架构规范,本身并无实际参与运作的实体,所以必须有人依据此标准进行开发撰写。如此才有真正可用、可执行的实体,始可称为实现体。目前依据X的规范架构所开发撰写成的实现体中,以X.Org最为普遍且最受欢迎。X.Org所用的协议版本,X11,是在1987年9月所发布。而今最新的参考实现(参考性、示范性的实现体)版本则是X11 Release 7.7(简称:X11R7.7),而此项目由X.Org基金会所领导,且是以MIT授权和相似的授权许可的自由软件。
X的C/S模式和网络透明性
X能为GUI环境提供基本的框架:在屏幕上描绘、呈现图像与移动程序窗口,同时也受理、执行、及管理电脑与鼠标、键盘的交互程序。不过,X并没有管辖到用户界面的部分,而是由其他以X为基础的实现体来负责,也因为如此,以X为基础环境所开发成的视觉样式非常地多;不同的程序可能有截然不同的接口呈现。

在图例中,X服务器从键盘、鼠标端获取输入信息,之后将输入反馈显示于银幕,而网页浏览器及终端模拟器则在客户端的本机系统上执行。此外客户端也通过网络与远程的机器、服务器保持联系,以保消息状态的更新。如此的机制及架构能使远程执行的软件如同在本机端执行一样。服务器和客户端之间的通信协议的运作对计算机网络是透明的:客户端和服务器可以在同一台计算机上,也可以不是,或许其架构和操作系统也不同,但都能运行。客户机和服务器还能够使用安全连接在互联网上安全地通讯。
X采用C/S的架构模型,由一个X服务器与多个X客户端程序进行通讯,服务器接受对于图形输出(窗口)的请求并反馈用户输入(键盘、鼠标、触摸屏),服务器可能是一个能显示到其他显示系统的应用程序,也可能是控制某个PC的视频输出的系统程序,也可能是个特殊硬件。X的一大特点在于“网络透明性”:应用程序(“客户端”应用程序)所执行的机器,不一定是用户本地的机器(显示的“服务器”)。X中所提及的“客户端”和“服务器”等字眼用词也经常与人们一般想定的相反,“服务器”反而是在用户本地端的自有机器上执行,而非是在远程的另一部机器上执行。
X刻意不去规范应用程序在用户界面上的具体细节设计,这些包括按钮、菜单和窗口的标题栏等等,这些都由窗口管理器(window manager)、GUI构件工具包、桌面环境(desktop environment)或者应用程序指定的GUI(如POS)等等的用户软件来提供,然而因为架构设计上保留了高度的弹性发挥空间,致使多年来X在“基础、典型、一般性”的用户界面上,也都有数目惊人的多样性选择。
在X的系统架构中,窗口管理器用于控制窗口程序的位置和外观,其界面类似Microsoft的Windows或者Macintosh(例如:KDE的KWin或者GNOME的Metacity),不过在控制机制上却截然不同(例如:X提供的基本窗口管理器twm)。窗口管理器可能只是个框架(例如:twm),但也可能提供了全套的桌面环境功能(例如:Enlightenment)。
虽然不同的X用户界面可以有很大的差距、差异,然而绝大多数的用户在使用X时,多是用已经高度全套化的桌面环境,桌面环境不仅有窗口管理器,还具备各种应用程序以及协调一致的界面,目前最流行的桌面环境是GNOME和KDE,此两者已普遍使用于Linux操作系统上,而UNIX所用的标准桌面环境多是通用桌面环境CDE,然而也有些UNIX也开始采行GNOME。此外,X桌面环境及组件虽然极其多样,但同时也需要保持兼容性与互通性,关于此则有freedesktop.org积极与努力地维持各种不同X桌面环境的兼容性,使相竞态势下仍不失X的兼容本色。
freedesktop.org所主持的Wayland显示服务器同时运用了前述两种方法处理X的缺点,它完全替换了X的架构而且运用DRI来直接操作硬件。Canonical公司替他旗下的Ubuntu Linux操作系统开发中的Mir是一个类似的计划。预计支持ARM芯片组和x86架构的电脑。Mir通过Xmir向后兼容使用X Window的程序。
X.Org基金会
1999年5月开放团体组建了X.Org。X.Org指导了X11R6.5.1之前的版本发布,在此期间X的开发死气沉沉。2003年,随着Linux的流行X被大量安装,但是X.Org几乎无声无息,活跃的开发由XFree86承担。2004年年初X.Org基金会成立,这意味着X在管理上的根本转变。1988年以来X的管理者(包括过去的X.Org)都是厂商组织,而基金会由软件开发者领导,采用依靠外部参与的集市模式的社区开发。其成员身份对个人开放,法人成员则成为赞助者。X.Org还开始同freedesktop.org密切合作。
在XFree86 4.4RC2基础上合并了X11R6.6的修改,基金会在2004年4月发布了X11R6.7。Gettys和Packard使用了在旧许可证下发布的XFree86的最后的版本,但是强调了开放的开发模式并保持了与GPL的兼容性,从而带来了很多过去XFree86的开发者。2004年9月X11R6.8发布。它加入重要的新特性,包括对半透明窗口的初步支持、很多复杂的视觉效果、屏幕放大和简图,以及与3D沉浸式显示系统(例如Sun的Project Looking Glass和Croquet project)集成的设施。而外观策略由称为合成管理器(compositing managers)的外部应用程序提供。
随着硬件操作被移入内核,几乎对视频硬件的访问将通过OpenGL(没有硬件OpenGL的系统使用Mesa 3D)和基层直接渲染模块进行。这由XFree86 version 4引入并出现在X11R6.7及其后续版本。参考实现的架构被模块化,每个独立模块做为分离的项目维护。X11R6.9将是单体源代码而X11R7.0将有具备相同特征集的模块化源代码。
2012年6月发布的X11R7.7,Xorg server 1.12改变; Sync扩展3.1:增加Fence object支持; Xi 2.2多点触控支持; XFixes 5.0: Pointer Barriers。
X11 开发速度在2023年降至近二十年来最低水平
X11 已经是一个有点 “老龄化” 的技术了,由于历史遗留的问题以及系统臃肿,它的发展已经受到了严重影响,目前大多数的开发者都转向了 Wayland。简单地说,Wayland 也是一个显示服务器协议,旨在取代 X11。它被设计成比 X11 更容易使用,还旨在通过更直接的代码,让开发者更容易将其集成到 Linux 系统中。
X.Org Server 是由 X.Org 基金会管理的 X Window System 显示服务器的自由和开源实现。在 Wayland 高速发展的对比之下,大致也能够猜测出过去一年开发者在这方面的投入应该不大。不过在查看 2022 年 X.Org Server 的 Git 统计数据时,开发者实际对该项目对投入还是令人十分意外,因为 2022 年的的提交量和代码修改量创下了 20 年来的最低水平。

从上图能看出在 2008 年以前,X.Org Server 经历了一段时间的高速发展,提交数量每年都在增加,并在 2008 年达到最高点,创造了最高提交数量的历史记录。此后 X.Org Server 的开发速度连年下滑,2022 年的提交数量甚至跟 20 年前,也就是 2003 年差不多。
2022 年,该项目的 Git 主分支只有 156 次提交,相比之下 2021 年还有 331 次提交,而 2008 年的最高点则是有 2114 次提交。开发者不光是在提交次数上出现了下滑,整体的代码量也在呈现下降趋势。2022 年 X.Org Server 的另一个低点是在 2022 年的 156 次提交中,只增加了 3618 行新代码,删除了 888 行,而 2021 年的 331 次提交则是新增了 3.14 万行代码。
从总代码行数来看,过去这些年 X.org Server 的代码维持在一个相对 “稳定” 的状态,基本上就是一条直线,近两年甚至还有下降的趋势。即便是提交数量差不多的 2003 年,X.Org Server 的代码变化量也没有如今这么少。虽然当时在他们的旧开发模式下只有 125 次提交,即便如此他们仍然有 86.5 万行代码增加和 68 万行代码被删除。自 2002 年以来,X.Org Server 的代码更新就没有这么少过。
2022 年只有 32 位开发者为 X.Org Server 做出了代码贡献,比前几年的 48 位开发者进一步有所降低,这也是近 20 年来的新低,作为对比在 2003 年,当时只有 10 位开发者有提交记录。红帽的 Olivier Fourdan 是 2022 年在 X.Org Server 上最高产的提交者,他的提交量占了近四分之一(23.08%)。在 Olivier 之后则分别是 Jeremy Huddleston Sequoia、Peter Hutterer、Michel Dänzer、Alan Coopersmith 和 Sultan Alsawaf。
在刚刚过去的 2024 年,X.Org Server 的代码提交次数达到了 2014 年以来的最高峰。据统计,X.Org Server 在2024年有 708 次提交... 比起 2018 年的 535 次提交(每年 200~300 次)要多得多。但即使是在 2010 年代中期,Wayland 还在积极开发的时候,每年也只有 400~500 次提交... 在 2024 年之前,提交次数最多的是 2014 年,当时有 952 次提交。按行数计算,2024 年 X.Org Server 新增了 11998 行代码,删除了 14680 行代码。这比近几年每年 X.Org Server 代码库中通常 5-6 千行代码的变化要多。但仍远低于 X.Org Server 积极开发的 2000 年代更高的代码更新量。
2024 年的 X.Org Server 之所以如此活跃主要有两个原因。
首先,X.Org Server 中的 XWayland 代码仍在继续积极开发,用于支持新的 Wayland 协议和其他修复/添加内容... XWayland 是 X.Org Server 中继续开发新功能的主要领域,也是唯一的领域。另一个原因是开源开发者 Enrico Weigelt。
Enrico Weigelt 主要负责 X.Org Server 的修复和改进工作,围绕 X.Org Server 的测试和更好的 CI BSD 覆盖范围。在红帽或英特尔等主要厂商都没有投资 X.Org Server 开发的情况下,Enrico 几乎是单枪匹马地完成了一些 X.Org Server 修复和其他小功能工作。他贡献了2024年 X.Org Server 63% 的 Git 提交... 其他活跃的开发者包括 Olivier Fourdan、Michel Dänzer、Alan Coopersmith、Peter Hutterer 和 Erik Kurzinger,他们大多只关注 XWayland 的改进与修正。除此之外,2023年 X.Org Server 的许多提交都是为了安全修复。
编程基础
本节转自Tech搬运工的博客空间,始发于2026年2月,感谢原作者。
1. 概述
X.Org 项目提供了 X 窗口系统的开源实现。开发工作是在 freedesktop.org 社区的通力合作下完成。X.Org 组织是非盈利教育机构,其董事会为这项工作服务,其成员领导这项工作。
Xorg (通常简称为 X)在 Linux 用户中非常流行,已经成为图形用户程序的必备条件,所以大部分发行版都提供了它。详情参见
Xorg 维基文章或访问Xorg 网站。
客户端-服务器分离是X11架构的基石。所有看到的图形界面,都由运行在远端的“客户端”程序生成,通过协议发送给本地的“服务器”来呈现。
1). X Server:唯一直接与硬件(显卡、显示器、键盘、鼠标、触摸板)对话的组件。
管理屏幕、色彩映射、字体。
接收输入设备事件。
在屏幕上绘制基本的图形原语(点、线、矩形、文本,而非现代GPU的复杂渲染)。
不负责窗口装饰、布局、切换——这些是窗口管理器(一个特殊客户端)的工作。
典型实现:Xorg。
2). X Client:即应用程序(如 xterm, firefox)。
包含所有业务逻辑。
决定“画什么”(内容),但不知道“具体怎么画到像素点上”。
通过向X Server发送协议请求来实现绘图和接收输入。
3). 窗口管理器/合成器 (特权 X Client) - 桌面外观和行为的总控。
窗口装饰:为应用窗口添加边框、标题栏和按钮。
布局控制:决定窗口的位置、大小和堆叠顺序。
焦点策略:管理哪个窗口接收键盘输入。
合成效果 (现代):作为合成器时,接收各个窗口的缓冲图像,混合后输出,实现阴影、动画、透明等效果。
APP、Xorg 和窗管的整体架构如下:

Xorg 内部的组件架构如下:

2. X11 常用函数
X11 窗口的创建和显示是一个典型的客户端-服务器交互过程,涉及多个关键步骤和函数。其核心是:在 X 服务器上创建资源(窗口),设置其属性,然后请求将其“映射”到屏幕上。下图清晰地展示了这一核心流程及主要阶段:

下面是详细的步骤:
1. 客户端 → 服务器: CreateWindow
2. 服务器 → 客户端: Success Reply
3. 服务器 → 窗管: ConfigureRequest (事件)
4. 窗管 → 服务器: ConfigureWindow (调整大小位置)
5. 客户端 → 服务器: MapWindow (显示窗口)
6. 服务器 → 客户端: XNextEvent (事件循环)
2.1. 显示和连接管理
2.2. 窗口操作函数
2.3. 事件处理函数
2.4. 图形绘制函数
2.5. 属性操作函数
2.6. 输入和焦点函数
3. XEvent
在X11中,事件是服务器向客户端发送的通知,表示某些事情已经发生,比如键盘按键、鼠标移动、窗口暴露等。客户端需要处理这些事件以响应用户输入和系统状态变化。
由于 XEvent 是一个联合体,所以每次事件只有一个结构体是有效的,有效结构体与XEvent中的 type 相关。常用 type与结构体对应关系如下。
3.1. XSelectInput 事件订阅
XSelectInput() 是 X11 事件系统的核心“订阅”机制。它的作用是:告诉 X 服务器,你对特定窗口的哪些类型的事件感兴趣。如果不使用它,你的程序将收不到任何事件,成为“植物人”程序。如下事件比较特殊:
1). 窗口显示、大小、位置改变:即使没有订阅StructureNotifyMask,窗口管理器仍能改变窗口大小,但是窗口收不到对应的事件。
2). ClientMessage接收:不需要XSelectInput也能收到ClientMessage事件。如 WM_DELETE_WINDOW 窗口管理器发送的关闭请求能收到。
函数的原型如下:
int XSelectInput(Display *display, Window window, long event_mask);
参数:
display:X 服务器连接
window:要订阅事件的窗口
event_mask:事件掩码(位掩码),指定要接收的事件类型
3.1.1. 事件掩码详解表
3.1.2. 基础窗口事件订阅
// 创建窗口后立即设置事件订阅
Window create_app_window(Display *dpy, int x, int y, int w, int h) {
Window win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy),
x, y, w, h, 1, 0, 0);
// 必须订阅的事件(最基本交互)
XSelectInput(dpy, win,
ExposureMask | // 窗口需要重绘
KeyPressMask | // 按键按下
KeyReleaseMask | // 按键释放
ButtonPressMask | // 鼠标按钮按下
ButtonReleaseMask | // 鼠标按钮释放
PointerMotionMask | // 鼠标移动
StructureNotifyMask | // 窗口结构变化(大小/位置等)
FocusChangeMask | // 焦点变化
PropertyChangeMask | // 属性变化
EnterWindowMask | // 鼠标进入窗口
LeaveWindowMask); // 鼠标离开窗口
return win;
}
3.1.3. 按需订阅高级事件
// 根据功能需求选择性订阅
void setup_window_events(Display *dpy, Window win, int window_type) {
long base_mask = ExposureMask | KeyPressMask | StructureNotifyMask;
switch (window_type) {
case WINDOW_TYPE_MAIN:
XSelectInput(dpy, win, base_mask |
PropertyChangeMask | // 监控属性变化
FocusChangeMask | // 焦点变化
SubstructureNotifyMask); // 子窗口事件
break;
case WINDOW_TYPE_DIALOG:
XSelectInput(dpy, win, base_mask |
FocusChangeMask | // 对话框需要焦点
EnterWindowMask | // 鼠标进入/离开
LeaveWindowMask);
break;
case WINDOW_TYPE_POPUP:
// 弹出窗口可能不需要所有事件
XSelectInput(dpy, win,
ExposureMask | ButtonPressMask | KeyPressMask);
break;
}
}
3.1.4. 测试例子
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
// 1. 连接X服务器
Display *display = XOpenDisplay(NULL);
if (!display) {
fprintf(stderr, "无法打开显示\n");
return 1;
}
int screen = DefaultScreen(display);
Window root = RootWindow(display, screen);
// 2. 创建窗口
Window window = XCreateSimpleWindow(display, root,
100, 100, 400, 300,
1,
BlackPixel(display, screen),
WhitePixel(display, screen));
// 3. 关键:完全不调用 XSelectInput!
XSelectInput(display, window, 0); // 或者完全注释掉这行
// 4. 设置窗口标题
XStoreName(display, window, "测试窗口 - 无XSelectInput");
// 5. 设置WM_DELETE_WINDOW协议(这是ClientMessage)
Atom wm_delete_window = XInternAtom(display, "WM_DELETE_WINDOW", False);
XSetWMProtocols(display, window, &wm_delete_window, 1);
// 6. 映射窗口
XMapWindow(display, window);
XFlush(display);
printf("窗口已创建,ID: 0x%lx\n", window);
printf("程序没有调用 XSelectInput()!\n");
printf("请尝试:\n");
printf("1. 用鼠标拖动改变窗口大小\n");
printf("2. 点击窗口标题栏的关闭按钮\n");
printf("3. 在终端按Ctrl+C退出程序\n\n");
// 7. 非阻塞事件检查
int count = 0;
while (1) {
// 使用非阻塞方式检查事件
if (XPending(display) > 0) {
XEvent event;
XNextEvent(display, &event);
printf("收到事件!类型: %d", event.type);
// 检查事件类型
switch (event.type) {
case ClientMessage: {
XClientMessageEvent *cme = &event.xclient;
printf(" (ClientMessage)\n");
printf(" 消息类型: %s\n",
XGetAtomName(display, cme->message_type));
// 检查是否是关闭请求
if (cme->data.l[0] == wm_delete_window) {
printf(" 检测到WM_DELETE_WINDOW协议\n");
printf(" 窗口管理器请求关闭窗口\n");
}
break;
}
case ConfigureNotify: {
XConfigureEvent *ce = &event.xconfigure;
printf(" (ConfigureNotify)\n");
printf(" 窗口新大小: %dx%d\n", ce->width, ce->height);
printf(" 窗口新位置: (%d, %d)\n", ce->x, ce->y);
break;
}
case MapNotify:
printf(" (MapNotify) 窗口已映射\n");
break;
case Expose:
printf(" (Expose) 窗口需要重绘\n");
break;
default:
printf(" (未知类型)\n");
break;
}
} else {
// 没有事件时等待
printf("等待事件... (循环 %d)\n", ++count);
sleep(1);
}
}
// 8. 清理
XDestroyWindow(display, window);
XCloseDisplay(display);
return 0;
}
编译运行
$ gcc demo.c -lX11
$ ./a.out
4. X11 原子(Atoms)
Atom 是X11中用来表示字符串的整数标识符。由于字符串在网络上传输效率低,X11使用原子来代表常用的字符串,从而减少数据传输量。Atom 在连接期间被缓存,一旦定义,所有客户端都可以使用这个整数 ID 来引用该字符串。
原子的用途:
窗口属性(Properties):窗口属性由原子命名,例如WM_NAME、WM_CLASS等。
选择(Selections):用于剪贴板和数据传输,例如PRIMARY、CLIPBOARD。
协议(Protocols):如WM_DELETE_WINDOW,用于窗口管理器与应用程序的通信。
常用原子:
预定义原子:X11有一些预定义的原子,如PRIMARY、SECONDARY、CLIPBOARD等。
窗口属性原子:如WM_NAME(窗口标题)、WM_CLASS(应用程序类)、WM_PROTOCOLS(协议列表)等。
EWMH原子:用于扩展窗口管理器提示,如_NET_WM_NAME、_NET_WM_STATE、_NET_WM_WINDOW_TYPE等。
原子的操作:
# 获取原子:将字符串转换为原子。
cAtom atom = XInternAtom(display, "WM_NAME", False);
#获取原子名:将原子转换为字符串。
cchar *name = XGetAtomName(display, atom);
4.1. 预定义原子
// 在 Xatom.h 中定义
XA_PRIMARY // 主要选择(剪贴板)
XA_SECONDARY // 次要选择
XA_ARC // 窗口弧形
XA_ATOM // 原子类型
XA_BITMAP // 位图
XA_CARDINAL // 32位整数
XA_COLORMAP // 颜色映射
XA_CURSOR // 光标
XA_DRAWABLE // 可绘制对象
XA_FONT // 字体
XA_INTEGER // 整数
XA_PIXMAP // 像素图
XA_POINT // 点
XA_RECTANGLE // 矩形
XA_STRING // 字符串
XA_VISUALID // 视觉ID
XA_WINDOW // 窗口
4.2. 窗口管理器协议(ICCCM)
// ICCCM (Inter-Client Communication Conventions Manual)
WM_NAME // 窗口标题
WM_ICON_NAME // 图标名称
WM_CLASS // 应用程序类/实例名
WM_TRANSIENT_FOR // 临时窗口的父窗口
WM_PROTOCOLS // 支持的协议列表
WM_COLORMAP_WINDOWS // 颜色映射窗口列表
WM_HINTS // 窗口提示
WM_NORMAL_HINTS // 窗口大小提示
WM_CLIENT_MACHINE // 客户端机器名
// 协议原子
WM_DELETE_WINDOW // 关闭窗口请求
WM_TAKE_FOCUS // 焦点请求
WM_SAVE_YOURSELF // 保存状态请求
4.3. EWMH (Extended WM Hints)
现代桌面扩展,以 _NET_ 开头:
// 窗口类型
_NET_WM_WINDOW_TYPE
_NET_WM_WINDOW_TYPE_DESKTOP
_NET_WM_WINDOW_TYPE_DOCK
_NET_WM_WINDOW_TYPE_TOOLBAR
_NET_WM_WINDOW_TYPE_MENU
_NET_WM_WINDOW_TYPE_UTILITY
_NET_WM_WINDOW_TYPE_SPLASH
_NET_WM_WINDOW_TYPE_DIALOG
_NET_WM_WINDOW_TYPE_NORMAL
// 窗口状态
_NET_WM_STATE
_NET_WM_STATE_MODAL
_NET_WM_STATE_STICKY
_NET_WM_STATE_MAXIMIZED_VERT
_NET_WM_STATE_MAXIMIZED_HORZ
_NET_WM_STATE_SHADED
_NET_WM_STATE_SKIP_TASKBAR
_NET_WM_STATE_SKIP_PAGER
_NET_WM_STATE_HIDDEN
_NET_WM_STATE_FULLSCREEN
_NET_WM_STATE_ABOVE
_NET_WM_STATE_BELOW
// 其他重要 EWMH 原子
_NET_SUPPORTED // 支持的 EWMH 特性列表
_NET_CLIENT_LIST // 客户端窗口列表
_NET_CLIENT_LIST_STACKING // 堆叠顺序的客户端列表
_NET_NUMBER_OF_DESKTOPS // 虚拟桌面数量
_NET_CURRENT_DESKTOP // 当前虚拟桌面
_NET_DESKTOP_NAMES // 桌面名称
_NET_ACTIVE_WINDOW // 活动窗口
_NET_CLOSE_WINDOW // 关闭窗口(窗管发送)
_NET_WM_NAME // UTF-8 窗口标题
_NET_WM_VISIBLE_NAME // 可见名称
_NET_WM_ICON // 窗口图标
_NET_WM_PID // 进程ID
_NET_WM_USER_TIME // 用户最后活动时间
4.4. 其他常用原子
// 剪贴板相关
CLIPBOARD // 剪贴板选择
TARGETS // 可用目标格式列表
UTF8_STRING // UTF-8 字符串
COMPOUND_TEXT // 复合文本
TIMESTAMP // 时间戳
// 拖放 (XDND)
XdndAware // 支持拖放的窗口
XdndSelection // 拖放选择
XdndDrop // 拖放事件
XdndPosition // 拖放位置
XdndStatus // 拖放状态
XdndFinished // 拖放完成
// 应用程序状态
SM_CLIENT_ID // 会话管理客户端ID
_NET_WM_STATE_HIDDEN // 窗口隐藏状态
_GTK_SHOW_WINDOW_MENU // GTK 显示窗口菜单
4.5. 测试例子
void setup_window_properties(Display *dpy, Window w) {
// 设置窗口类
XClassHint class_hint;
class_hint.res_name = "myapp";
class_hint.res_class = "MyApplication";
XSetClassHint(dpy, w, &class_hint);
// 设置窗口标题(传统方式)
XStoreName(dpy, w, "My Application");
// 设置窗口标题(EWMH 方式)
Atom utf8_string = XInternAtom(dpy, "UTF8_STRING", False);
Atom net_wm_name = XInternAtom(dpy, "_NET_WM_NAME", False);
const char *title = "My Application";
XChangeProperty(dpy, w, net_wm_name, utf8_string, 8,
PropModeReplace, (unsigned char*)title, strlen(title));
// 设置窗口协议
Atom wm_protocols = XInternAtom(dpy, "WM_PROTOCOLS", False);
Atom wm_delete = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
Atom protocols[] = {wm_delete};
XSetWMProtocols(dpy, w, protocols, 1);
// 设置窗口类型
Atom net_wm_window_type = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
Atom net_wm_window_type_normal = XInternAtom(dpy,
"_NET_WM_WINDOW_TYPE_NORMAL", False);
XChangeProperty(dpy, w, net_wm_window_type, XA_ATOM, 32,
PropModeReplace,
(unsigned char*)&net_wm_window_type_normal, 1);
}
// 应用程序请求全屏的完整过程
void request_fullscreen(Display *dpy, Window w) {
Atom net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
Atom net_wm_state_fullscreen = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
XEvent ev;
memset(&ev, 0, sizeof(ev));
ev.xclient.type = ClientMessage;
ev.xclient.window = w;
ev.xclient.message_type = net_wm_state;
ev.xclient.format = 32;
ev.xclient.data.l[0] = 1; // _NET_WM_STATE_ADD
ev.xclient.data.l[1] = net_wm_state_fullscreen;
ev.xclient.data.l[2] = 0; // 第二个属性,0 表示没有
ev.xclient.data.l[3] = 1; // 源指示器:1=应用程序
ev.xclient.data.l[4] = 0;
// 发送到根窗口(窗管监听根窗口)
XSendEvent(dpy, DefaultRootWindow(dpy), False,
SubstructureRedirectMask | SubstructureNotifyMask, &ev);
XFlush(dpy);
}
5. 调试和查看工具
窗口调试相关的包有xdotool、wmctrl、x11-utils。其中 x11-utils 是 X11 的项目的调试包,包含了很多调试工具,下面是主要工具:
5.1. 使用 xev 查看事件
# 启动事件查看器
xev -event keyboard,mouse,expose,property,clientmessage
# 查看特定窗口的事件
xev -id $(xwininfo | grep "Window id" | awk '{print $4}')
5.2. 使用 xprop 查看原子和属性
# 查看窗口所有属性
xprop -notype -id <window_id>
# 查看激活窗口
xprop -root _NET_ACTIVE_WINDOW
# 查看特定属性
xprop -id <window_id> WM_NAME _NET_WM_STATE _NET_WM_WINDOW_TYPE
# 监控属性变化
xprop -spy -id <window_id> _NET_WM_STATE
5.3. 查看窗口层级
# 使用xwininfo查看层级关系
xwininfo -root -tree
# 使用wmctrl查看窗口列表和状态
wmctrl -l -G -x
# 查看特定窗口的属性
xprop -id $(xwininfo | grep "Window id" | awk '{print $4}')
5.4. 设置窗口状态
# 选择窗口将它至顶
wmctrl -r :SELECT: -b add,above
# 使当前窗口全屏
wmctrl -r :ACTIVE: -b add,fullscreen
# 移除全屏
wmctrl -r :ACTIVE: -b remove,fullscreen
# 最大化
wmctrl -r :ACTIVE: -b add,maximized_vert,maximized_horz
# 选择窗口将它的标题修改为 Selected Window
wmctrl -r :SELECT: -T "Selected Window"
# 选择窗口将它设为粘贴状态(所有虚拟桌面都显示)
wmctrl -r :SELECT: -b add,sticky
5.5. 分析 X11 通信包
5.5.1. 使用socat将Unix域套接字转换为TCP套接字
# 1. 创建 TCP 到 UNIX socket 的桥接
socat TCP-LISTEN:6001,bind=localhost,fork \
UNIX-CONNECT:/tmp/.X11-unix/X0 &
# 2. 启动 Wireshark 捕获
wireshark -k -i lo -f "tcp port 6001" &
# 3. 启动应用
DISPLAY=localhost:1 xedit
5.5.2. 使用x11trace或xtrace
# 安装
sudo apt-get install xtrace
# 捕获 xterm 的所有 X11 通信
xtrace -o x11.log xterm
5.5.3. 使用X11的自身功能
X服务器本身有扩展可以记录协议数据。启动Xorg时启用记录:
# 启动带日志的 Xorg
Xorg :1 -listen tcp -verbose 6 2>&1 | tee x11_protocol.log
# 然后分析日志文件
grep "Request" x11_protocol.log | head -20
5.5.4. strace 系统调用跟踪
# 跟踪所有 X11 相关的系统调用
strace -e trace=network,ipc -f xterm 2>&1 | grep -E "(connect|send|recv)"
5.5.5. 其他工具
x11vis(专门的 X11 协议图形化分析器)
x11trace 可作为中间人代理

The X Window SystemTM provides the base technology for developing graphical user interfaces. At a very basic level, X draws the elements of the GUI on the user's screen and builds in methods for sending user interactions back to the application. Using application GUI development toolkits, such as Motif, an application developer can quickly create an application interface to the end user.
由于X只是工具包及架构规范,本身并无实际参与运作的实体,所以必须有人依据此标准进行开发撰写。如此才有真正可用、可执行的实体,始可称为实现体。目前依据X的规范架构所开发撰写成的实现体中,以X.Org最为普遍且最受欢迎。X.Org所用的协议版本,X11,是在1987年9月所发布。而今最新的参考实现(参考性、示范性的实现体)版本则是X11 Release 7.7(简称:X11R7.7),而此项目由X.Org基金会所领导,且是以MIT授权和相似的授权许可的自由软件。
X的C/S模式和网络透明性
X能为GUI环境提供基本的框架:在屏幕上描绘、呈现图像与移动程序窗口,同时也受理、执行、及管理电脑与鼠标、键盘的交互程序。不过,X并没有管辖到用户界面的部分,而是由其他以X为基础的实现体来负责,也因为如此,以X为基础环境所开发成的视觉样式非常地多;不同的程序可能有截然不同的接口呈现。

在图例中,X服务器从键盘、鼠标端获取输入信息,之后将输入反馈显示于银幕,而网页浏览器及终端模拟器则在客户端的本机系统上执行。此外客户端也通过网络与远程的机器、服务器保持联系,以保消息状态的更新。如此的机制及架构能使远程执行的软件如同在本机端执行一样。服务器和客户端之间的通信协议的运作对计算机网络是透明的:客户端和服务器可以在同一台计算机上,也可以不是,或许其架构和操作系统也不同,但都能运行。客户机和服务器还能够使用安全连接在互联网上安全地通讯。
X采用C/S的架构模型,由一个X服务器与多个X客户端程序进行通讯,服务器接受对于图形输出(窗口)的请求并反馈用户输入(键盘、鼠标、触摸屏),服务器可能是一个能显示到其他显示系统的应用程序,也可能是控制某个PC的视频输出的系统程序,也可能是个特殊硬件。X的一大特点在于“网络透明性”:应用程序(“客户端”应用程序)所执行的机器,不一定是用户本地的机器(显示的“服务器”)。X中所提及的“客户端”和“服务器”等字眼用词也经常与人们一般想定的相反,“服务器”反而是在用户本地端的自有机器上执行,而非是在远程的另一部机器上执行。
X刻意不去规范应用程序在用户界面上的具体细节设计,这些包括按钮、菜单和窗口的标题栏等等,这些都由窗口管理器(window manager)、GUI构件工具包、桌面环境(desktop environment)或者应用程序指定的GUI(如POS)等等的用户软件来提供,然而因为架构设计上保留了高度的弹性发挥空间,致使多年来X在“基础、典型、一般性”的用户界面上,也都有数目惊人的多样性选择。
在X的系统架构中,窗口管理器用于控制窗口程序的位置和外观,其界面类似Microsoft的Windows或者Macintosh(例如:KDE的KWin或者GNOME的Metacity),不过在控制机制上却截然不同(例如:X提供的基本窗口管理器twm)。窗口管理器可能只是个框架(例如:twm),但也可能提供了全套的桌面环境功能(例如:Enlightenment)。
虽然不同的X用户界面可以有很大的差距、差异,然而绝大多数的用户在使用X时,多是用已经高度全套化的桌面环境,桌面环境不仅有窗口管理器,还具备各种应用程序以及协调一致的界面,目前最流行的桌面环境是GNOME和KDE,此两者已普遍使用于Linux操作系统上,而UNIX所用的标准桌面环境多是通用桌面环境CDE,然而也有些UNIX也开始采行GNOME。此外,X桌面环境及组件虽然极其多样,但同时也需要保持兼容性与互通性,关于此则有freedesktop.org积极与努力地维持各种不同X桌面环境的兼容性,使相竞态势下仍不失X的兼容本色。
freedesktop.org所主持的Wayland显示服务器同时运用了前述两种方法处理X的缺点,它完全替换了X的架构而且运用DRI来直接操作硬件。Canonical公司替他旗下的Ubuntu Linux操作系统开发中的Mir是一个类似的计划。预计支持ARM芯片组和x86架构的电脑。Mir通过Xmir向后兼容使用X Window的程序。
X.Org基金会
1999年5月开放团体组建了X.Org。X.Org指导了X11R6.5.1之前的版本发布,在此期间X的开发死气沉沉。2003年,随着Linux的流行X被大量安装,但是X.Org几乎无声无息,活跃的开发由XFree86承担。2004年年初X.Org基金会成立,这意味着X在管理上的根本转变。1988年以来X的管理者(包括过去的X.Org)都是厂商组织,而基金会由软件开发者领导,采用依靠外部参与的集市模式的社区开发。其成员身份对个人开放,法人成员则成为赞助者。X.Org还开始同freedesktop.org密切合作。
在XFree86 4.4RC2基础上合并了X11R6.6的修改,基金会在2004年4月发布了X11R6.7。Gettys和Packard使用了在旧许可证下发布的XFree86的最后的版本,但是强调了开放的开发模式并保持了与GPL的兼容性,从而带来了很多过去XFree86的开发者。2004年9月X11R6.8发布。它加入重要的新特性,包括对半透明窗口的初步支持、很多复杂的视觉效果、屏幕放大和简图,以及与3D沉浸式显示系统(例如Sun的Project Looking Glass和Croquet project)集成的设施。而外观策略由称为合成管理器(compositing managers)的外部应用程序提供。
随着硬件操作被移入内核,几乎对视频硬件的访问将通过OpenGL(没有硬件OpenGL的系统使用Mesa 3D)和基层直接渲染模块进行。这由XFree86 version 4引入并出现在X11R6.7及其后续版本。参考实现的架构被模块化,每个独立模块做为分离的项目维护。X11R6.9将是单体源代码而X11R7.0将有具备相同特征集的模块化源代码。
2012年6月发布的X11R7.7,Xorg server 1.12改变; Sync扩展3.1:增加Fence object支持; Xi 2.2多点触控支持; XFixes 5.0: Pointer Barriers。
X11 开发速度在2023年降至近二十年来最低水平
X11 已经是一个有点 “老龄化” 的技术了,由于历史遗留的问题以及系统臃肿,它的发展已经受到了严重影响,目前大多数的开发者都转向了 Wayland。简单地说,Wayland 也是一个显示服务器协议,旨在取代 X11。它被设计成比 X11 更容易使用,还旨在通过更直接的代码,让开发者更容易将其集成到 Linux 系统中。
X.Org Server 是由 X.Org 基金会管理的 X Window System 显示服务器的自由和开源实现。在 Wayland 高速发展的对比之下,大致也能够猜测出过去一年开发者在这方面的投入应该不大。不过在查看 2022 年 X.Org Server 的 Git 统计数据时,开发者实际对该项目对投入还是令人十分意外,因为 2022 年的的提交量和代码修改量创下了 20 年来的最低水平。

从上图能看出在 2008 年以前,X.Org Server 经历了一段时间的高速发展,提交数量每年都在增加,并在 2008 年达到最高点,创造了最高提交数量的历史记录。此后 X.Org Server 的开发速度连年下滑,2022 年的提交数量甚至跟 20 年前,也就是 2003 年差不多。
2022 年,该项目的 Git 主分支只有 156 次提交,相比之下 2021 年还有 331 次提交,而 2008 年的最高点则是有 2114 次提交。开发者不光是在提交次数上出现了下滑,整体的代码量也在呈现下降趋势。2022 年 X.Org Server 的另一个低点是在 2022 年的 156 次提交中,只增加了 3618 行新代码,删除了 888 行,而 2021 年的 331 次提交则是新增了 3.14 万行代码。
从总代码行数来看,过去这些年 X.org Server 的代码维持在一个相对 “稳定” 的状态,基本上就是一条直线,近两年甚至还有下降的趋势。即便是提交数量差不多的 2003 年,X.Org Server 的代码变化量也没有如今这么少。虽然当时在他们的旧开发模式下只有 125 次提交,即便如此他们仍然有 86.5 万行代码增加和 68 万行代码被删除。自 2002 年以来,X.Org Server 的代码更新就没有这么少过。
2022 年只有 32 位开发者为 X.Org Server 做出了代码贡献,比前几年的 48 位开发者进一步有所降低,这也是近 20 年来的新低,作为对比在 2003 年,当时只有 10 位开发者有提交记录。红帽的 Olivier Fourdan 是 2022 年在 X.Org Server 上最高产的提交者,他的提交量占了近四分之一(23.08%)。在 Olivier 之后则分别是 Jeremy Huddleston Sequoia、Peter Hutterer、Michel Dänzer、Alan Coopersmith 和 Sultan Alsawaf。
在刚刚过去的 2024 年,X.Org Server 的代码提交次数达到了 2014 年以来的最高峰。据统计,X.Org Server 在2024年有 708 次提交... 比起 2018 年的 535 次提交(每年 200~300 次)要多得多。但即使是在 2010 年代中期,Wayland 还在积极开发的时候,每年也只有 400~500 次提交... 在 2024 年之前,提交次数最多的是 2014 年,当时有 952 次提交。按行数计算,2024 年 X.Org Server 新增了 11998 行代码,删除了 14680 行代码。这比近几年每年 X.Org Server 代码库中通常 5-6 千行代码的变化要多。但仍远低于 X.Org Server 积极开发的 2000 年代更高的代码更新量。
2024 年的 X.Org Server 之所以如此活跃主要有两个原因。
首先,X.Org Server 中的 XWayland 代码仍在继续积极开发,用于支持新的 Wayland 协议和其他修复/添加内容... XWayland 是 X.Org Server 中继续开发新功能的主要领域,也是唯一的领域。另一个原因是开源开发者 Enrico Weigelt。
Enrico Weigelt 主要负责 X.Org Server 的修复和改进工作,围绕 X.Org Server 的测试和更好的 CI BSD 覆盖范围。在红帽或英特尔等主要厂商都没有投资 X.Org Server 开发的情况下,Enrico 几乎是单枪匹马地完成了一些 X.Org Server 修复和其他小功能工作。他贡献了2024年 X.Org Server 63% 的 Git 提交... 其他活跃的开发者包括 Olivier Fourdan、Michel Dänzer、Alan Coopersmith、Peter Hutterer 和 Erik Kurzinger,他们大多只关注 XWayland 的改进与修正。除此之外,2023年 X.Org Server 的许多提交都是为了安全修复。
编程基础
本节转自Tech搬运工的博客空间,始发于2026年2月,感谢原作者。
1. 概述
X.Org 项目提供了 X 窗口系统的开源实现。开发工作是在 freedesktop.org 社区的通力合作下完成。X.Org 组织是非盈利教育机构,其董事会为这项工作服务,其成员领导这项工作。
Xorg (通常简称为 X)在 Linux 用户中非常流行,已经成为图形用户程序的必备条件,所以大部分发行版都提供了它。详情参见
Xorg 维基文章或访问Xorg 网站。
客户端-服务器分离是X11架构的基石。所有看到的图形界面,都由运行在远端的“客户端”程序生成,通过协议发送给本地的“服务器”来呈现。
1). X Server:唯一直接与硬件(显卡、显示器、键盘、鼠标、触摸板)对话的组件。
管理屏幕、色彩映射、字体。
接收输入设备事件。
在屏幕上绘制基本的图形原语(点、线、矩形、文本,而非现代GPU的复杂渲染)。
不负责窗口装饰、布局、切换——这些是窗口管理器(一个特殊客户端)的工作。
典型实现:Xorg。
2). X Client:即应用程序(如 xterm, firefox)。
包含所有业务逻辑。
决定“画什么”(内容),但不知道“具体怎么画到像素点上”。
通过向X Server发送协议请求来实现绘图和接收输入。
3). 窗口管理器/合成器 (特权 X Client) - 桌面外观和行为的总控。
窗口装饰:为应用窗口添加边框、标题栏和按钮。
布局控制:决定窗口的位置、大小和堆叠顺序。
焦点策略:管理哪个窗口接收键盘输入。
合成效果 (现代):作为合成器时,接收各个窗口的缓冲图像,混合后输出,实现阴影、动画、透明等效果。
APP、Xorg 和窗管的整体架构如下:

Xorg 内部的组件架构如下:

2. X11 常用函数
X11 窗口的创建和显示是一个典型的客户端-服务器交互过程,涉及多个关键步骤和函数。其核心是:在 X 服务器上创建资源(窗口),设置其属性,然后请求将其“映射”到屏幕上。下图清晰地展示了这一核心流程及主要阶段:

下面是详细的步骤:
1. 客户端 → 服务器: CreateWindow
2. 服务器 → 客户端: Success Reply
3. 服务器 → 窗管: ConfigureRequest (事件)
4. 窗管 → 服务器: ConfigureWindow (调整大小位置)
5. 客户端 → 服务器: MapWindow (显示窗口)
6. 服务器 → 客户端: XNextEvent (事件循环)
2.1. 显示和连接管理
| 函数 | 作用 |
| XOpenDisplay | 连接到 X 服务器 |
| XCloseDisplay | 关闭与 X 服务器的连接 |
| XDefaultScreen | 获取默认屏幕编号 |
| XDisplayName | 获取显示名称 |
2.2. 窗口操作函数
| 函数 | 作用 |
| XCreateWindow XCreateSimpleWindow | 创建新窗口 |
| XDestroyWindow | 销毁窗口 |
| XMapWindow | 映射窗口(显示) |
| XUnmapWindow | 取消映射窗口(隐藏) |
| XRaiseWindow | 将窗口提升到堆叠顶部 |
| XLowerWindow | 将窗口降低到堆叠底部 |
| XMoveWindow | 移动窗口位置 |
| XResizeWindow | 调整窗口大小 |
| XReparentWindow | 改变窗口的父窗口 |
| XGetWindowAttributes | 获取窗口属性 |
2.3. 事件处理函数
| 函数 | 作用 |
| XSelectInput | 选择要接收的事件类型 |
| XNextEvent | 获取下一个事件 |
| XCheckWindowEvent | 检查指定窗口的事件 |
| XCheckTypedEvent | 检查特定类型的事件 |
| XSendEvent | 发送事件到窗口 |
| XPending | 检查待处理事件数量 |
2.4. 图形绘制函数
| 函数 | 作用 |
| XCreateGC | 创建图形上下文 |
| XFreeGC | 释放图形上下文 |
| XDrawLine | 绘制直线 |
| XDrawRectangle | 绘制矩形边框 |
| XFillRectangle | 填充矩形 |
| XDrawString | 绘制文本 |
| XClearWindow | 清除窗口内容 |
2.5. 属性操作函数
| 函数 | 作用 |
| XChangeProperty | 修改窗口属性 |
| XGetWindowProperty | 获取窗口属性 |
| XDeleteProperty | 删除窗口属性 |
| XSetWMProperties | 设置窗口管理器属性 |
2.6. 输入和焦点函数
| 函数 | 作用 |
| XGrabKey | 捕获键盘按键 |
| XGrabButton | 捕获鼠标按钮 |
| XSetInputFocus | 设置输入焦点 |
| XGetInputFocus | 获取当前焦点窗口 |
3. XEvent
在X11中,事件是服务器向客户端发送的通知,表示某些事情已经发生,比如键盘按键、鼠标移动、窗口暴露等。客户端需要处理这些事件以响应用户输入和系统状态变化。
由于 XEvent 是一个联合体,所以每次事件只有一个结构体是有效的,有效结构体与XEvent中的 type 相关。常用 type与结构体对应关系如下。
| 事件类型 | 事件结构体 | 描述 |
| KeyPress | XKeyEvent | 键盘按键被按下事件,XKeyEvent结构体记录被按下的键盘。 |
| KeyRelease | XKeyEvent | 键盘按键被按下松开事件 |
| ButtonPress | XButtonEvent | 鼠标键被按下事件 |
| ButtonRelease | XButtonEvent | 鼠标键被按下松开事件 |
| MotionNotify | XMotionEvent | 鼠标在窗口内移动事件 |
| EnterNotify | XCrossingEvent | 鼠标进入窗口事件 |
| LeaveNotify | XCrossingEvent | 鼠标离开窗口事件 |
| FocusIn | XFocusChangeEvent | 输入焦点进入当前窗口 |
| FocusOut | XFocusChangeEvent | 输入焦点离开当前窗口 |
| Expose | XExposeEvent | 当前窗口被显示或者区域需要重绘时产生的事件 |
| CreateNotify | XCreateWindowEvent | 窗口被创建时事件 |
| DestroyNotify | XDestroyWindowEvent | 窗口被销毁时事件 |
| MapNotify | XMapEvent | 窗口显示时事件 |
| ConfigureNotify | XConfigureEvent | 窗口位置、大小改变时事件 |
| ClientMessage | XClientMessageEvent | 用于窗口之间的通信,如WM和应用 |
3.1. XSelectInput 事件订阅
XSelectInput() 是 X11 事件系统的核心“订阅”机制。它的作用是:告诉 X 服务器,你对特定窗口的哪些类型的事件感兴趣。如果不使用它,你的程序将收不到任何事件,成为“植物人”程序。如下事件比较特殊:
1). 窗口显示、大小、位置改变:即使没有订阅StructureNotifyMask,窗口管理器仍能改变窗口大小,但是窗口收不到对应的事件。
2). ClientMessage接收:不需要XSelectInput也能收到ClientMessage事件。如 WM_DELETE_WINDOW 窗口管理器发送的关闭请求能收到。
函数的原型如下:
int XSelectInput(Display *display, Window window, long event_mask);
参数:
display:X 服务器连接
window:要订阅事件的窗口
event_mask:事件掩码(位掩码),指定要接收的事件类型
3.1.1. 事件掩码详解表
| 掩码常量 | 对应事件 | 典型用途 |
| ExposureMask | Expose, GraphicsExpose | 窗口需要重绘时 |
| KeyPressMask | KeyPress | 键盘输入处理 |
| KeyReleaseMask | KeyRelease | 按键释放(游戏、输入法) |
| ButtonPressMask | ButtonPress | 鼠标点击 |
| ButtonReleaseMask | ButtonRelease | 鼠标释放 |
| PointerMotionMask | MotionNotify | 鼠标移动跟踪 |
| ButtonMotionMask | MotionNotify(按下时) | 拖拽操作 |
| EnterWindowMask | EnterNotify | 鼠标进入窗口 |
| LeaveWindowMask | LeaveNotify | 鼠标离开窗口 |
| FocusChangeMask | FocusIn, FocusOut | 焦点管理 |
| StructureNotifyMask | ConfigureNotify, MapNotify, UnmapNotify等 | 窗口大小/位置/映射状态变化 |
| PropertyChangeMask | PropertyNotify | 监控窗口属性变化(如标题) |
| VisibilityChangeMask | VisibilityNotify | 窗口可见性变化 |
| SubstructureNotifyMask | 子窗口相关事件 | 管理子窗口 |
| SubstructureRedirectMask | 子窗口请求事件 | 窗口管理器使用 |
3.1.2. 基础窗口事件订阅
// 创建窗口后立即设置事件订阅
Window create_app_window(Display *dpy, int x, int y, int w, int h) {
Window win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy),
x, y, w, h, 1, 0, 0);
// 必须订阅的事件(最基本交互)
XSelectInput(dpy, win,
ExposureMask | // 窗口需要重绘
KeyPressMask | // 按键按下
KeyReleaseMask | // 按键释放
ButtonPressMask | // 鼠标按钮按下
ButtonReleaseMask | // 鼠标按钮释放
PointerMotionMask | // 鼠标移动
StructureNotifyMask | // 窗口结构变化(大小/位置等)
FocusChangeMask | // 焦点变化
PropertyChangeMask | // 属性变化
EnterWindowMask | // 鼠标进入窗口
LeaveWindowMask); // 鼠标离开窗口
return win;
}
3.1.3. 按需订阅高级事件
// 根据功能需求选择性订阅
void setup_window_events(Display *dpy, Window win, int window_type) {
long base_mask = ExposureMask | KeyPressMask | StructureNotifyMask;
switch (window_type) {
case WINDOW_TYPE_MAIN:
XSelectInput(dpy, win, base_mask |
PropertyChangeMask | // 监控属性变化
FocusChangeMask | // 焦点变化
SubstructureNotifyMask); // 子窗口事件
break;
case WINDOW_TYPE_DIALOG:
XSelectInput(dpy, win, base_mask |
FocusChangeMask | // 对话框需要焦点
EnterWindowMask | // 鼠标进入/离开
LeaveWindowMask);
break;
case WINDOW_TYPE_POPUP:
// 弹出窗口可能不需要所有事件
XSelectInput(dpy, win,
ExposureMask | ButtonPressMask | KeyPressMask);
break;
}
}
3.1.4. 测试例子
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
// 1. 连接X服务器
Display *display = XOpenDisplay(NULL);
if (!display) {
fprintf(stderr, "无法打开显示\n");
return 1;
}
int screen = DefaultScreen(display);
Window root = RootWindow(display, screen);
// 2. 创建窗口
Window window = XCreateSimpleWindow(display, root,
100, 100, 400, 300,
1,
BlackPixel(display, screen),
WhitePixel(display, screen));
// 3. 关键:完全不调用 XSelectInput!
XSelectInput(display, window, 0); // 或者完全注释掉这行
// 4. 设置窗口标题
XStoreName(display, window, "测试窗口 - 无XSelectInput");
// 5. 设置WM_DELETE_WINDOW协议(这是ClientMessage)
Atom wm_delete_window = XInternAtom(display, "WM_DELETE_WINDOW", False);
XSetWMProtocols(display, window, &wm_delete_window, 1);
// 6. 映射窗口
XMapWindow(display, window);
XFlush(display);
printf("窗口已创建,ID: 0x%lx\n", window);
printf("程序没有调用 XSelectInput()!\n");
printf("请尝试:\n");
printf("1. 用鼠标拖动改变窗口大小\n");
printf("2. 点击窗口标题栏的关闭按钮\n");
printf("3. 在终端按Ctrl+C退出程序\n\n");
// 7. 非阻塞事件检查
int count = 0;
while (1) {
// 使用非阻塞方式检查事件
if (XPending(display) > 0) {
XEvent event;
XNextEvent(display, &event);
printf("收到事件!类型: %d", event.type);
// 检查事件类型
switch (event.type) {
case ClientMessage: {
XClientMessageEvent *cme = &event.xclient;
printf(" (ClientMessage)\n");
printf(" 消息类型: %s\n",
XGetAtomName(display, cme->message_type));
// 检查是否是关闭请求
if (cme->data.l[0] == wm_delete_window) {
printf(" 检测到WM_DELETE_WINDOW协议\n");
printf(" 窗口管理器请求关闭窗口\n");
}
break;
}
case ConfigureNotify: {
XConfigureEvent *ce = &event.xconfigure;
printf(" (ConfigureNotify)\n");
printf(" 窗口新大小: %dx%d\n", ce->width, ce->height);
printf(" 窗口新位置: (%d, %d)\n", ce->x, ce->y);
break;
}
case MapNotify:
printf(" (MapNotify) 窗口已映射\n");
break;
case Expose:
printf(" (Expose) 窗口需要重绘\n");
break;
default:
printf(" (未知类型)\n");
break;
}
} else {
// 没有事件时等待
printf("等待事件... (循环 %d)\n", ++count);
sleep(1);
}
}
// 8. 清理
XDestroyWindow(display, window);
XCloseDisplay(display);
return 0;
}
编译运行
$ gcc demo.c -lX11
$ ./a.out
4. X11 原子(Atoms)
Atom 是X11中用来表示字符串的整数标识符。由于字符串在网络上传输效率低,X11使用原子来代表常用的字符串,从而减少数据传输量。Atom 在连接期间被缓存,一旦定义,所有客户端都可以使用这个整数 ID 来引用该字符串。
原子的用途:
窗口属性(Properties):窗口属性由原子命名,例如WM_NAME、WM_CLASS等。
选择(Selections):用于剪贴板和数据传输,例如PRIMARY、CLIPBOARD。
协议(Protocols):如WM_DELETE_WINDOW,用于窗口管理器与应用程序的通信。
常用原子:
预定义原子:X11有一些预定义的原子,如PRIMARY、SECONDARY、CLIPBOARD等。
窗口属性原子:如WM_NAME(窗口标题)、WM_CLASS(应用程序类)、WM_PROTOCOLS(协议列表)等。
EWMH原子:用于扩展窗口管理器提示,如_NET_WM_NAME、_NET_WM_STATE、_NET_WM_WINDOW_TYPE等。
原子的操作:
# 获取原子:将字符串转换为原子。
cAtom atom = XInternAtom(display, "WM_NAME", False);
#获取原子名:将原子转换为字符串。
cchar *name = XGetAtomName(display, atom);
4.1. 预定义原子
// 在 Xatom.h 中定义
XA_PRIMARY // 主要选择(剪贴板)
XA_SECONDARY // 次要选择
XA_ARC // 窗口弧形
XA_ATOM // 原子类型
XA_BITMAP // 位图
XA_CARDINAL // 32位整数
XA_COLORMAP // 颜色映射
XA_CURSOR // 光标
XA_DRAWABLE // 可绘制对象
XA_FONT // 字体
XA_INTEGER // 整数
XA_PIXMAP // 像素图
XA_POINT // 点
XA_RECTANGLE // 矩形
XA_STRING // 字符串
XA_VISUALID // 视觉ID
XA_WINDOW // 窗口
4.2. 窗口管理器协议(ICCCM)
// ICCCM (Inter-Client Communication Conventions Manual)
WM_NAME // 窗口标题
WM_ICON_NAME // 图标名称
WM_CLASS // 应用程序类/实例名
WM_TRANSIENT_FOR // 临时窗口的父窗口
WM_PROTOCOLS // 支持的协议列表
WM_COLORMAP_WINDOWS // 颜色映射窗口列表
WM_HINTS // 窗口提示
WM_NORMAL_HINTS // 窗口大小提示
WM_CLIENT_MACHINE // 客户端机器名
// 协议原子
WM_DELETE_WINDOW // 关闭窗口请求
WM_TAKE_FOCUS // 焦点请求
WM_SAVE_YOURSELF // 保存状态请求
4.3. EWMH (Extended WM Hints)
现代桌面扩展,以 _NET_ 开头:
// 窗口类型
_NET_WM_WINDOW_TYPE
_NET_WM_WINDOW_TYPE_DESKTOP
_NET_WM_WINDOW_TYPE_DOCK
_NET_WM_WINDOW_TYPE_TOOLBAR
_NET_WM_WINDOW_TYPE_MENU
_NET_WM_WINDOW_TYPE_UTILITY
_NET_WM_WINDOW_TYPE_SPLASH
_NET_WM_WINDOW_TYPE_DIALOG
_NET_WM_WINDOW_TYPE_NORMAL
// 窗口状态
_NET_WM_STATE
_NET_WM_STATE_MODAL
_NET_WM_STATE_STICKY
_NET_WM_STATE_MAXIMIZED_VERT
_NET_WM_STATE_MAXIMIZED_HORZ
_NET_WM_STATE_SHADED
_NET_WM_STATE_SKIP_TASKBAR
_NET_WM_STATE_SKIP_PAGER
_NET_WM_STATE_HIDDEN
_NET_WM_STATE_FULLSCREEN
_NET_WM_STATE_ABOVE
_NET_WM_STATE_BELOW
// 其他重要 EWMH 原子
_NET_SUPPORTED // 支持的 EWMH 特性列表
_NET_CLIENT_LIST // 客户端窗口列表
_NET_CLIENT_LIST_STACKING // 堆叠顺序的客户端列表
_NET_NUMBER_OF_DESKTOPS // 虚拟桌面数量
_NET_CURRENT_DESKTOP // 当前虚拟桌面
_NET_DESKTOP_NAMES // 桌面名称
_NET_ACTIVE_WINDOW // 活动窗口
_NET_CLOSE_WINDOW // 关闭窗口(窗管发送)
_NET_WM_NAME // UTF-8 窗口标题
_NET_WM_VISIBLE_NAME // 可见名称
_NET_WM_ICON // 窗口图标
_NET_WM_PID // 进程ID
_NET_WM_USER_TIME // 用户最后活动时间
4.4. 其他常用原子
// 剪贴板相关
CLIPBOARD // 剪贴板选择
TARGETS // 可用目标格式列表
UTF8_STRING // UTF-8 字符串
COMPOUND_TEXT // 复合文本
TIMESTAMP // 时间戳
// 拖放 (XDND)
XdndAware // 支持拖放的窗口
XdndSelection // 拖放选择
XdndDrop // 拖放事件
XdndPosition // 拖放位置
XdndStatus // 拖放状态
XdndFinished // 拖放完成
// 应用程序状态
SM_CLIENT_ID // 会话管理客户端ID
_NET_WM_STATE_HIDDEN // 窗口隐藏状态
_GTK_SHOW_WINDOW_MENU // GTK 显示窗口菜单
4.5. 测试例子
void setup_window_properties(Display *dpy, Window w) {
// 设置窗口类
XClassHint class_hint;
class_hint.res_name = "myapp";
class_hint.res_class = "MyApplication";
XSetClassHint(dpy, w, &class_hint);
// 设置窗口标题(传统方式)
XStoreName(dpy, w, "My Application");
// 设置窗口标题(EWMH 方式)
Atom utf8_string = XInternAtom(dpy, "UTF8_STRING", False);
Atom net_wm_name = XInternAtom(dpy, "_NET_WM_NAME", False);
const char *title = "My Application";
XChangeProperty(dpy, w, net_wm_name, utf8_string, 8,
PropModeReplace, (unsigned char*)title, strlen(title));
// 设置窗口协议
Atom wm_protocols = XInternAtom(dpy, "WM_PROTOCOLS", False);
Atom wm_delete = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
Atom protocols[] = {wm_delete};
XSetWMProtocols(dpy, w, protocols, 1);
// 设置窗口类型
Atom net_wm_window_type = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
Atom net_wm_window_type_normal = XInternAtom(dpy,
"_NET_WM_WINDOW_TYPE_NORMAL", False);
XChangeProperty(dpy, w, net_wm_window_type, XA_ATOM, 32,
PropModeReplace,
(unsigned char*)&net_wm_window_type_normal, 1);
}
// 应用程序请求全屏的完整过程
void request_fullscreen(Display *dpy, Window w) {
Atom net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
Atom net_wm_state_fullscreen = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
XEvent ev;
memset(&ev, 0, sizeof(ev));
ev.xclient.type = ClientMessage;
ev.xclient.window = w;
ev.xclient.message_type = net_wm_state;
ev.xclient.format = 32;
ev.xclient.data.l[0] = 1; // _NET_WM_STATE_ADD
ev.xclient.data.l[1] = net_wm_state_fullscreen;
ev.xclient.data.l[2] = 0; // 第二个属性,0 表示没有
ev.xclient.data.l[3] = 1; // 源指示器:1=应用程序
ev.xclient.data.l[4] = 0;
// 发送到根窗口(窗管监听根窗口)
XSendEvent(dpy, DefaultRootWindow(dpy), False,
SubstructureRedirectMask | SubstructureNotifyMask, &ev);
XFlush(dpy);
}
5. 调试和查看工具
窗口调试相关的包有xdotool、wmctrl、x11-utils。其中 x11-utils 是 X11 的项目的调试包,包含了很多调试工具,下面是主要工具:
| 工具名称 | 主要功能描述 |
| xdpyinfo | 查询并显示X服务器的详细信息,如支持的扩展、屏幕参数等。 |
| xlsclients | 列出当前连接到X服务器的所有客户端应用程序。 |
| xwininfo | 查询窗口的详细信息,如 ID、几何尺寸、位置等。常用 xwininfo -tree -root 查看窗口树结构。 |
| xprop | 查看和修改窗口的X属性,是调试窗口管理器协议的关键工具。 |
| xev | 创建一个窗口并实时打印所有X事件,是学习X11事件模型的绝佳工具。 |
| xlsfonts | 列出X服务器上可用的字体。 |
| xfontsel | 提供一个图形界面来浏览和选择字体。 |
| xfd | 打开一个窗口,显示指定字体的所有字符。 |
| appres, editres, listres, viewres | 用于查询和编辑X应用程序的资源(Resource)数据库。 |
| xkill | 强制终止行为异常的X客户端。执行后会变成骷髅光标,点击要关闭的窗口即可。 |
| xlsatoms | 列出X服务器上定义的所有原子(Atom),即属性的唯一标识符。 |
| xdriinfo | 查询Direct Rendering Infrastructure驱动配置。 |
| xvinfo | 打印X-Video扩展的功能信息。 |
| xmessage | 弹出一个简单的消息对话框,常用于脚本中给用户提示。 |
| luit | 在非UTF-8终端与UTF-8应用之间进行编码转换的过滤器。 |
5.1. 使用 xev 查看事件
# 启动事件查看器
xev -event keyboard,mouse,expose,property,clientmessage
# 查看特定窗口的事件
xev -id $(xwininfo | grep "Window id" | awk '{print $4}')
5.2. 使用 xprop 查看原子和属性
# 查看窗口所有属性
xprop -notype -id <window_id>
# 查看激活窗口
xprop -root _NET_ACTIVE_WINDOW
# 查看特定属性
xprop -id <window_id> WM_NAME _NET_WM_STATE _NET_WM_WINDOW_TYPE
# 监控属性变化
xprop -spy -id <window_id> _NET_WM_STATE
5.3. 查看窗口层级
# 使用xwininfo查看层级关系
xwininfo -root -tree
# 使用wmctrl查看窗口列表和状态
wmctrl -l -G -x
# 查看特定窗口的属性
xprop -id $(xwininfo | grep "Window id" | awk '{print $4}')
5.4. 设置窗口状态
# 选择窗口将它至顶
wmctrl -r :SELECT: -b add,above
# 使当前窗口全屏
wmctrl -r :ACTIVE: -b add,fullscreen
# 移除全屏
wmctrl -r :ACTIVE: -b remove,fullscreen
# 最大化
wmctrl -r :ACTIVE: -b add,maximized_vert,maximized_horz
# 选择窗口将它的标题修改为 Selected Window
wmctrl -r :SELECT: -T "Selected Window"
# 选择窗口将它设为粘贴状态(所有虚拟桌面都显示)
wmctrl -r :SELECT: -b add,sticky
5.5. 分析 X11 通信包
5.5.1. 使用socat将Unix域套接字转换为TCP套接字
# 1. 创建 TCP 到 UNIX socket 的桥接
socat TCP-LISTEN:6001,bind=localhost,fork \
UNIX-CONNECT:/tmp/.X11-unix/X0 &
# 2. 启动 Wireshark 捕获
wireshark -k -i lo -f "tcp port 6001" &
# 3. 启动应用
DISPLAY=localhost:1 xedit
5.5.2. 使用x11trace或xtrace
# 安装
sudo apt-get install xtrace
# 捕获 xterm 的所有 X11 通信
xtrace -o x11.log xterm
5.5.3. 使用X11的自身功能
X服务器本身有扩展可以记录协议数据。启动Xorg时启用记录:
# 启动带日志的 Xorg
Xorg :1 -listen tcp -verbose 6 2>&1 | tee x11_protocol.log
# 然后分析日志文件
grep "Request" x11_protocol.log | head -20
5.5.4. strace 系统调用跟踪
# 跟踪所有 X11 相关的系统调用
strace -e trace=network,ipc -f xterm 2>&1 | grep -E "(connect|send|recv)"
5.5.5. 其他工具
x11vis(专门的 X11 协议图形化分析器)
x11trace 可作为中间人代理