Xlib和Firefox的行为表现

62
我正在尝试创建一个小的窗口管理器(只是为了好玩),但是在处理由Firefox创建的窗口时出现问题(其他应用程序正常运行)。
问题在于,启动Firefox并添加我的装饰后,它似乎工作正常,但是如果我尝试单击菜单按钮,(子)窗口不会出现。
看起来发生的情况是,在单击后,会触发一个具有以下值的ClientMessage事件:
Data: (null)
Data: _NET_WM_STATE_HIDDEN
Data: (null)
Data: (null)
Data: (null)

现在的问题是我不知道如何展示窗口,哪个窗口。

  • 我尝试使用XRaiseWindow
  • 尝试使用XMapWindow
  • 我试图获取瞬态窗口并显示它

但都没有成功。我不理解的是这个客户端消息是否由菜单子窗口生成。

我应该如何展示一个处于_NET_WM_STATE_HIDDEN状态的窗口?

另一个奇怪的问题是,在接收到ClientMessage之后,我总是会收到2个UnMapNotify事件。

我还有另一个问题,如果我想展示“文件、编辑”菜单(在Firefox中,如果我没记错的话,按下Alt键时会出现)。

也许Firefox创建了一个窗口树?

这是我处理事件的循环:

while(1){
    XNextEvent(display, &local_event);
    switch(local_event.type){
        case ConfigureNotify:
            configure_notify_handler(local_event, display);
        break;
        case MotionNotify:
            motion_handler(local_event, display);
        break;
        case CreateNotify:
            cur_win = local_event.xcreatewindow.window;
            char *window_name;
            XFetchName(display, cur_win, &window_name);
            printf("Window name: %s\n", window_name);
            if(window_name!=NULL){
                if(!strcmp(window_name, "Parent")){
                    printf("Adding borders\n");
                    XSetWindowBorderWidth(display, cur_win, BORDER_WIDTH);
                }
                XFree(window_name);
            }
        break;
        case MapNotify:
            map_notify_handler(local_event,display, infos);
        break;
        case UnmapNotify: 
            printf("UnMapNotify\n");
        break;
        case DestroyNotify:
            printf("Destroy Event\n");
            destroy_notify_handler(local_event,display);
        break;
        case ButtonPress:
            printf("Event button pressed\n");
            button_handler(local_event, display, infos);
        break;
        case KeyPress:
            printf("Keyboard key pressed\n");
            keyboard_handler(local_event, display);
        break;
        case ClientMessage:
            printf("------------ClientMessage\n");
            printf("\tMessage: %s\n", XGetAtomName(display,local_event.xclient.message_type));
            printf("\tFormat: %d\n", local_event.xclient.format); 
            Atom *atoms = (Atom *)local_event.xclient.data.l;
            int i =0;
            for(i=0; i<=5; i++){
                printf("\t\tData %d: %s\n", i, XGetAtomName(display, atoms[i]));
            }
            int nchild;
            Window *child_windows;
            Window parent_window;
            Window root_window;
            XQueryTree(display, local_event.xclient.window, &root_window, &parent_window, &child_windows, &nchild);
            printf("\tNumber of childs: %d\n", nchild);
        break;
    }

现在在客户端消息中,我只是试图收集一些信息来了解发生了什么。从上面的代码中可以看到,引发事件的窗口包含一个子窗口(再次确认:这是菜单吗?还是其他的什么?)

添加装饰的MapNotify事件代码如下:

void map_notify_handler(XEvent local_event, Display* display, ScreenInfos infos){
    printf("----------Map Notify\n");
    XWindowAttributes win_attr;
    char *child_name;
    XGetWindowAttributes(display, local_event.xmap.window, &win_attr);
    XFetchName(display, local_event.xmap.window, &child_name);
    printf("\tAttributes: W: %d - H: %d - Name: %s - ID %lu\n", win_attr.width, win_attr.height, child_name, local_event.xmap.window);
    Window trans = None;    
    XGetTransientForHint(display, local_event.xmap.window, &trans); 
    printf("\tIs transient: %ld\n", trans);
    if(child_name!=NULL){
      if(strcmp(child_name, "Parent") && local_event.xmap.override_redirect == False){
        Window new_win = draw_window_with_name(display, RootWindow(display, infos.screen_num), "Parent", infos.screen_num, 
                           win_attr.x, win_attr.y, win_attr.width, win_attr.height+DECORATION_HEIGHT, 0, 
                           BlackPixel(display, infos.screen_num));
        XMapWindow(display, new_win);
        XReparentWindow(display,local_event.xmap.window, new_win,0, DECORATION_HEIGHT);
        set_window_item(local_event.xmap.window, new_win);
        XSelectInput(display, local_event.xmap.window, StructureNotifyMask);
        printf("\tParent window id: %lu\n", new_win);
        put_text(display, new_win, child_name, "9x15", 10, 10, BlackPixel(display,infos.screen_num), WhitePixel(display, infos.screen_num));
      }
    }
    XFree(child_name);
}

现在有人能帮我解决这些问题吗?不幸的是,我已经搜索了很多次,但没有成功。

总之,我的问题有两个: 1. 如何显示来自Firefox的子窗口 2. 如何显示文件、编辑菜单。

更新

我用xev测试Firefox时发现了一些奇怪的事情,以了解显示应用程序所触发的事件。我发现在Unity中使用Firefox和在另一个窗口管理器中使用Firefox时,触发的事件完全不同。在Unity中,我只有:

  1. ClientMessage
  2. UnmapNotify

而在其他窗口管理器中使用Firefox(例如xfce4),生成的xevents更多:

  1. VisiblityNotify(不止一个)
  2. Expose事件(不止一个)

但是,如果我尝试在我的wm中启用VisibilityChangeMask,我会收到以下事件:

  • ConfigureNotify
  • ClientMessage
  • MapNotify
  • 2 UnMapNotify

更新2

我尝试读取ClientMessage窗口(可能是菜单窗口)中的XWMhints属性,其值为:

  • 对于标志67 = InputHint、StateHint、WIndowGroupHint

  • 对于初始状态NormalState

更新3

我尝试查看另一个窗口管理器的工作原理,正在查看calmwm的源代码。我的理解是,当ClientMessage事件到达时,使用_NET_WM_STATE消息更新这些属性,在_NET_WM_STATE_HIDDEN的情况下清除此属性,结果将是该属性将被删除。因此,我尝试更新我的代码以删除该属性,但仍然无法正常工作。无论如何,在client_message_handler中相关的更新代码现在看起来像这样:

Atom *atoms = (Atom *)local_event.xclient.data.l;
int i =0;
for(i=0; i<=5; i++){
    printf("\t\tData %d: %s\n", i, XGetAtomName(display, atoms[i]));
    if(i==1){
        printf("\t Deleting Property: _NET_WM_STATE_HIDDEN \n");
        XDeleteProperty(display, cur_window, atoms[i]);
    }
}

这只是一个测试,我确定在我的情况下i=1是_NET_WM_STATE_HIDDEN属性。这里有一个链接指向calmwm源代码:https://github.com/chneukirchen/cwm/blob/linux/xevents.c。所以我还卡在那个点上。 更新4 我真的不知道它是否有帮助,但我尝试在MapNotify事件中读取窗口属性,窗口map_state为IsViewable(2)。 更新5 我在SO中找到了一个类似的问题,使用xlib和python:Xlib python: cannot map firefox menus。解决方案建议使用XSetInputFocus,我在我的XMapNotify处理程序中尝试了一下。
XSetInputFocus(display, local_event.xmap.window, RevertToParent, CurrentTime);

但这仍然没有帮助,火狐菜单仍然不会出现!!我右键也有同样的问题。
更新6
通过玩xconfigurenotify事件和unmap事件,我发现: Xconfigure请求有2个窗口字段:窗口和above,当 xconfigurerequest.window值与xunmap.window值相同时。
而且xconfigurerequest.above总是在变化,但在所有事件中,xconfigurerequest.window始终相同。
似乎xconfigurerequest.above与我试图打开的菜单有关。例如:
如果右键单击页面,我会得到一个ID(对于每个后续单击始终相同) 如果我右键单击选项卡,则上面的值就是另一个值 如果我左键单击Firefox主菜单,情况也是如此
还不知道这是否有帮助。
真的不知道 有人有任何想法吗?

4
我和我的朋友经常使用Firefox进行x11、gtk、gdk和其他c库的编程。我们通过设置“_NET_ACTIVE_WINDOW”提示和“XMapRaised”来将窗口聚焦:https://gist.github.com/Noitidart/c7be5489fd38f8ecc76b#file-_ff-addon-snippet-x11_focuswindowbynativehandle-js-L280-L283 我会再仔细看看你的问题,并告诉我的朋友。 - yatg
谢谢@yatg的评论,我对你发送给我的代码片段有一个问题,l0字段是不是相当于在C语言中的xclient.data.l0字段?为什么将其设置为2? - Ivan
4
请查看这段代码,它更加简洁:https://github.com/Noitidart/NativeShot/blob/winnt-found-workaround-for-winnt-nondpi-scale/modules/workers/MainWorker.js#L132-L159。之所以为2,是因为这是“_NET_WM_STATE_TOGGLE”的魔法数字,可以在此处看到: https://github.com/Noitidart/NativeShot/blob/winnt-found-workaround-for-winnt-nondpi-scale/modules/ostypes_x11.jsm#L245。 XFlush 对于动作的执行非常关键,如果未执行 XFlush,则不会前台显示,至少在我们的经验中是这样,所以使用 XFlush 可能是您的解决方案,请告诉我结果如何。 - yatg
1
嗨yatg。我没有太多时间来做很多测试和解决问题。无论如何,我有几个问题。我的理解是当我收到_NET_WM_STATUS_HIDDEN时,我需要发送TOGGLE事件。但是发送给谁?窗口还是显示器?如果我将其发送到显示器,我会收到事件回传。那么接下来该怎么办?调用XRaiseWindow吗?问题是在客户端消息之后,我只收到两个UnmapNotify事件。无论如何,项目的源代码在这里:https://goo.gl/4toUy8,处理ClientMessage事件的代码在这里:https://goo.gl/8A0W6p!谢谢:D - Ivan
2
你的主要问题是:FireFox菜单/子窗口有什么“不同”。问:您是否考虑下载FF源代码并构建可供您逐步调试的调试版本?https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Source_Code/Downloading_Source_Archives - paulsm4
显示剩余4条评论
3个回答

3
这个问题虽然古老,但为了那些偶然发现并寻找答案的人,以下是我根据上面提供的提示进行修改后的解决方案示例:
while (event = xcb_poll_for_event(connection)) {
    uint8_t actual_event = event->response_type & 127;
    switch (actual_event) {
        case XCB_MAP_NOTIFY: ;
            xcb_map_notify_event_t *map_evt = (xcb_map_notify_event_t *)event;
            if (map_evt->override_redirect) {
                xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_transient_for(connection, map_evt->window);
                xcb_window_t transient_for = 0;
                xcb_icccm_get_wm_transient_for_reply(connection, cookie, &transient_for, NULL);
                if (transient_for) {
                    xcb_set_input_focus(connection, XCB_INPUT_FOCUS_POINTER_ROOT, transient_for, XCB_CURRENT_TIME);
                }
                xcb_flush(connection);
            }
            break;
        case XCB_CLIENT_MESSAGE: ;
            xcb_client_message_event_t *message_evt = (xcb_client_message_event_t *)event;
            xcb_get_atom_name_cookie_t name_cookie = xcb_get_atom_name(connection, message_evt->type);
            xcb_get_atom_name_reply_t *name_reply = xcb_get_atom_name_reply(connection, name_cookie, NULL);
            int length = xcb_get_atom_name_name_length(name_reply);
            char *atom_name = malloc(length + 1);
            strncpy(atom_name, xcb_get_atom_name_name(name_reply), length);
            atom_name[length] = '\0';
            free(atom_name);
            free(name_reply);

            if (message_evt->type == ewmh->_NET_WM_STATE) {
                xcb_atom_t atom = message_evt->data.data32[1];
                unsigned int action = message_evt->data.data32[0];
                xcb_get_atom_name_cookie_t name_cookie = xcb_get_atom_name(connection, atom);
                xcb_get_atom_name_reply_t *name_reply = xcb_get_atom_name_reply(connection, name_cookie, NULL);
                int length = xcb_get_atom_name_name_length(name_reply);
                char *atom_name = malloc(length + 1);
                strncpy(atom_name, xcb_get_atom_name_name(name_reply), length);
                atom_name[length] = '\0';
                if (action == XCB_EWMH_WM_STATE_REMOVE) {
                    if (atom == ewmh->_NET_WM_STATE_HIDDEN) {
                        xcb_delete_property(connection, message_evt->window, ewmh->_NET_WM_STATE_HIDDEN);
                    }
                }
                free(atom_name);
                free(name_reply);
            }
            break;
    }
}

为了解释清楚,需要处理的重要事件是MapNotify和ClientMessage,因为有两个主要问题需要处理:请求时必须移除窗口的隐藏状态(xcb_delete_property调用),并且短暂窗口的父窗口必须获得输入焦点(xcb_set_input_focus调用;请注意,获得焦点的是短暂窗口的父窗口,而不是短暂窗口本身),否则Firefox会立即再次隐藏短暂窗口。
此外,看起来很重要的是将短暂窗口堆叠在它们的父级上,以便WM应该尊重ConfigureRequest事件。
PS即使这是被接受的答案,其代码是针对xcb的,如果您需要xlib的代码,请查看下面我的答案,并对适用于xlib的代码进行修改,它仅涵盖MapNotify事件。

嘿,感谢您的回答,即使有点晚,我也很感激! :) 我会尝试您的建议,我看到您正在使用xcb,而我只想使用xlib。那个问题阻碍了我的项目开发,也许我可以尝试实现您的建议,希望能解决这个问题!同时,我会给您一个+1的答案! :D - Ivan
哦天啊,它真的起作用了! :) 仅仅4年半之后,我终于成功显示了Firefox菜单!哈哈!非常感谢! - Ivan

1

使用 xtruss — 一款易于使用的X协议跟踪程序


概述

任何习惯于在Linux或System V类型的Unix上编写程序的程序员都会遇到名为strace或truss的程序,它监视另一个程序并生成每个系统调用的详细日志 - 换句话说,所有程序与OS内核的交互。这通常是一种宝贵的调试工具,也是一种非常好的教育工具。

当您想要理解或调试GUI程序(或者更确切地说是程序的GUI相关行为)时,与OS内核的交互水平很少是最有用的。更有帮助的是,希望以同样的方式记录程序与X服务器的所有交互。

已经存在可以做到这一点的程序。我知道Xmon和Xtrace。但是它们往往需要大量的设置工作:您必须运行该程序以建立监听服务器,然后手动安排目标程序联系该服务器而不是真实服务器 - 包括一些繁琐的xauth工作。理想情况下,您希望跟踪程序的X操作与跟踪其内核系统调用一样容易:您希望键入一个像strace program-name arguments这样简单的命令,并且一切都会自动处理。

此外,这些程序的输出不太容易阅读,这并不符合我的要求 - 我主要指的是它不像我希望的那样像strace。 strace具有将每个系统调用及其返回值放置在同一行输出的好处,因此您可以一目了然地看到每个响应是对什么响应的。然而,X协议监视器往往忠实地遵循X协议的结构,这意味着每个请求和响应都会打印出序列号,并且您必须通过眼睛匹配两者。

因此,本页介绍了xtruss,我对X协议记录器领域的贡献。它具有类似于strace的命令行语法 - 在其默认模式下,您只需在与原来相同的命令行前缀“xtruss”即可 - 其输出格式也更像strace,在合理的情况下将请求和响应放在同一行输出。

strace还支持连接到已经运行的进程并从中间跟踪它的功能 - 当长时间运行的进程出现问题时,您不知道您需要跟踪它,这时非常方便。 xtruss通过X RECORD扩展支持此功能(前提是您的X服务器支持它,现代X.Org服务器确实支持它);因此,在该模式下,您可以使用鼠标识别窗口(类似于xwininfo和xkill等标准程序),然后xtruss将连接到拥有您指定的窗口的X客户端程序,并开始跟踪它。


描述

xtruss是一个实用程序,可记录X服务器和一个或多个X客户端程序之间传递的所有内容。在这方面,它类似于xmon(1),但旨在将xmon的基本功能与更类似于strace(1)的接口相结合。

像xmon一样,xtruss在其默认模式下通过设置代理X服务器,等待连接并将其转发到实际的X服务器来工作。但是,与xmon不同,您不必手动处理任


1

好的,经过4.5年半后,我将回答自己的问题。

我将修订Lightning Bolt先生的答案,并适应于XLIB,重点关注他对瞬态窗口所说的内容。答案可能不完整,但至少有了那段代码片段,现在我能够打开Firefox菜单了。

我会接受他的答案,因为他提出了正确的解决方案。

正如Lightning Bolt所指出的,关键是MapNotify事件,所以窗口管理器应该接受这种事件,并在生成它时执行以下操作:

  1. 使用XGetTransientWindowForHint抓取任何瞬态窗口
  2. 如果找到任何瞬态窗口,则需要使用XSetInputFocus将其设置为输入焦点。

在您的MapNotifyHandler中,完整的代码应如下:

Window trans = None;    
XGetTransientForHint(display, local_event.xmap.window, &trans); 
if(trans != None){
        XSetInputFocus(display, trans, RevertToParent, CurrentTime);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接