d,对接xcb

1002 篇文章 1 订阅
本文详细介绍了在不使用Xlib的情况下,直接使用XCB进行X11键盘事件处理的复杂性。涉及键盘映射、修饰符处理、键码到键符的转换以及XKB和Input扩展的使用。作者通过重构XCB包装器解决了重入问题,并提供了实用的代码示例。
摘要由CSDN通过智能技术生成

原文
因为现在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)还有XKBInput扩展,如果想支持结合字符,死键和输入法等.(我没到那步,因为(1)-(4)我已经有了一个非常可用的系统.XKB,如果服务器有它(几乎所有现代服务器都有),可通过X11核心协议模拟它的大部分功能来提供给未启用XKB扩展客户,只要正确处理(1)-(4),当前键盘输入"大部分"工作.)总之,这就是没有XlibX11键盘处理.

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时处理响应.
.getStringPropertyxcb.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();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值