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();
}
}
}
浙公网安备 33010602011771号