d,对接xcb
原文
因为现在Xlib
是在XCB
之上实现的.
是的,但是使用X
(如*core*X
协议,没有Xlib
),这是我以前未预料到的全新
的混乱
程度.😛
基本上,X
服务器*仅*
交换与键盘上的物理键
相关联的任意数字的键码
.要理解它们,需要映射到表示命名键
的标准数字
的键符
.在Xlib
中,映射键码
到键符
只需1或2个
函数即可.然而,在XCB
中,你必须重新发明Xlib
的底层工作:
1
)首先,必须从X服务器
提取特定键码
可能映射到的键符表
的键盘
映射.
2
)但每个键码
可映射到>=4
个键符
;决定按键时,是哪一个的效果,要看当前
有效的修饰符(shift,capsLock
等),并且基于X协议
中描述的算法,决定使用4个键符
中的哪一个
.
这很简单
,除了修饰符位掩码
可能会根据服务器配置
而改变;为了找出
与"切换模式
"修饰符对应位
,需要向X服务器
请求修饰符映射
,并扫描表"切换模式
"键符
并记住对应位
.
3
)完了吗,不,还有更多.修饰符
和键盘
贴图都可随时间变化
,因此还必须处理通知映射
事件,并重新加载键盘
或修饰符
贴图相关部分(并可能刷新
你当前对修饰符位
的理解),潜在问题是通知映射
后的所有后续按键
事件都使用新映射
,因此必须确保处理后续按键
事件之前*刷新键盘映射和模式映射
.
4
)还没完.多数应用不知道也不关心
什么是键码或键符
;他们想转换键符
为实际字符
(ASCII,Unicode,等等
).为此,你要用键符->ISO10646
(即Unicode
值)表,这里
5
)还有XKB
和Input
扩展,如果想支持结合字符,死键和输入法
等.(我没到那步,因为(1)-(4)
我已经有了一个非常可用
的系统.XKB
,如果服务器有它(几乎所有现代
服务器都有),可通过X11
核心协议模拟
它的大部分功能来提供
给未启用XKB
扩展客户,只要正确处理(1)-(4)
,当前键盘输入
"大部分"工作.)总之,这就是没有Xlib
的X11
键盘处理.
6
)如果关心按键重复
事件(除了某些游戏
外,大多数处理文本输入
应用).啥都没有.每次收到KeyRelease
时,都必须提前查看下个事件
.使得事件循环
更加"有趣".
我做了.现在XCB
代码比以前更干净了:
auto xcb = new XCB(xcb_connect(null, null));
...
//从窗口中提取各种属性的实际代码的摘录,
xcb.get_window_attributes(winid, (attrs) {
win.override_redirect = cast(bool) attrs.override_redirect;
win.is_mapped = (attrs.map_state == XCB_MAP_STATE_VIEWABLE);
win.win_gravity = attrs.win_gravity;
});
xcb.getStringProperty(winid, XCB_ATOM_WM_NAME, (s) {
win.wmName = s.idup;
});
xcb.getStringProperty(winid, XCB_ATOM_WM_ICON_NAME, (s) {
win.wmIconName = s.idup;
});
xcb.getStringProperty(winid, XCB_ATOM_WM_CLASS, (s) {
auto split = s.indexOf('\0');
if (split != -1 && split < s.length)
{
win.wmInstanceName=s[0..split].idup;
win.wmClassName=s[split+1..$].idup;
}
});
...
xcb.flush();//处理所有响应
我把XCB
包装器更改为最终类
,来避免构上闭包
问题.基本上,XCB
对象跟踪当前的xcb_connection_t*
及每次调用XCB.xxx
函数时附加的响应回调队列
.请求如前
是非阻塞
的,在调用.flush
时处理响应
.
.getStringProperty
是xcb.get_property
加一些处理串响应
的标准样板
的语法糖.足以满足我现在需要.
想法非常简单,尽管在.flush
的实现中存在棘手问题:最初
实现是错误的,因为未考虑到响应回调
可能会触发更多请求
并再次递归
调用.flush
.所以不得不调整.flush
的实现以使其可重入
.
代码如下:
//代理对象,用于与`xcb`函数更好的接口.
final class XCB
{
static struct OnError
{
static void delegate(lazy string msg) warn;
static void delegate(lazy string msg) exception;
static void delegate(lazy string msg) ignore;
static this()
{
warn = (lazy msg) => stderr.writeln(msg);
exception = (lazy msg) => throw new Exception(msg);
ignore = (lazy msg) {};
}
}
private xcb_connection_t* xc;
private void delegate()[] fut;
this(xcb_connection_t* _conn)
in (_conn !is null)
{//构造器
xc = _conn;
}
//返回,XCB连接对象
xcb_connection_t* conn() { return xc; }
/*
对每对以(xcb_connection_t*xc,Args...)参数和"xcb_funcname_reply"的"xcb_funcname"形式的XCB函数
* 返回提供了对应形式方法的Reply类型的值:
* ------
* void XCB.funcname(Args, void delegate(Reply) cb, OnError onError)
* ------
*
*对每个不生成服务器回复的"xcb_funcname_checked"形式的XCB函数,对象提供了相应形式的方法
* ------
* void delegate() XCB.funcname(Args, void delegate() cb, OnError onError)
* ------
*
`cb`回调在发送请求后在`内部队列`中注册,不会立即调用.相反,必须调用`.flush`来从服务器提取响应,此时如果`服务器`返回成功,则调用`cb`,否则,执行onError操作.
*/
template opDispatch(string func)
{//调用XCB函数的语法糖
enum reqFunc = "xcb_" ~ func;
alias Args = Parameters!(mixin(reqFunc));
static assert(Args.length > 0 && is(Args[0] == xcb_connection_t*));
enum replyFunc = "xcb_" ~ func ~ "_reply";
static if (__traits(hasMember, xcb.xcb, replyFunc))
{
alias Reply = ReturnType!(mixin(replyFunc));
void opDispatch(Args[1..$] args, void delegate(Reply) cb, void delegate(lazy string) onError = OnError.warn)
{
auto cookie = mixin(reqFunc ~ "(xc, args)");
fut ~= {
import core.stdc.stdlib : free;
xcb_generic_error_t* e;
Reply reply = mixin(replyFunc ~ "(xc, cookie, &e)");
if (reply is null)
onError("%s失败: %s".format(reqFunc, e.toString));
else
{
scope(exit) free(reply);
cb(reply);
}
};
}
}
else
{//没有回复功能,改用通用检查
void opDispatch(Args[1..$] args, void delegate() cb = null,void delegate(lazy string) onError = OnError.warn)
{
auto cookie = mixin(reqFunc ~ "_checked(xc, args)");
fut ~= {
xcb_generic_error_t* e = xcb_request_check(xc, cookie);
if (e !is null)
onError("%s失败: %s".format(reqFunc, e.toString));
if (cb) cb();
};
}
}
}
unittest
{
alias F = opDispatch!"get_window_attributes";
//pragma(msg, typeof(F));
alias G = opDispatch!"map_window";
//pragma(msg, typeof(G));
}
enum maxStrWords = 40;
//有效长度是这个值*4
/*
检索串属性的方便方法.
重要:`cb` 收到的`const(char)[]`是瞬态的;
要在回调外保存,用`.dup或.idup`
*/
void getStringProperty(xcb_window_t winid, xcb_atom_t attr, void delegate(const(char)[]) cb, void delegate(lazy string) onError = OnError.warn)
{
this.get_property(0, winid, attr, XCB_ATOM_STRING, 0, maxStrWords,(resp) {
if (resp.format != 8)
{
return onError(format( "无法在 0x%x 上检索字符串%d属性 ", attr,winid));
}
void* val = xcb_get_property_value(resp);
cb((cast(char*)val)[0 .. resp.value_len]);
});
}
void flush()
{//运行任何排队的响应回调
if (xcb_flush(xc) < 0)
stderr.writeln("xcb_flush失败");
while (fut.length > 0)
{
auto f = fut[0];
fut = fut[1 .. $];
//必须在调用f之前完成重入
f();
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现