Xlib 和 Firefox 行为

Xlib and Firefox behavior

我正在尝试创建一个小型 window 管理器(只是为了好玩),但我在处理由 Firefox 创建的 windows 时遇到问题(仅适用于该应用程序,其他应用程序可以工作很好)

问题是,在我启动 Firefox 并添加我的装饰后,它似乎工作正常,但是如果我尝试点击菜单按钮,(sub)window 不会出现。

似乎发生的情况是,在单击之后,会触发具有以下值的 ClientMessage 事件:

Data: (null)
Data: _NET_WM_STATE_HIDDEN
Data: (null)
Data: (null)
Data: (null)

现在的问题是我不知道如何显示 window,即 window。 我试过:

但是没有成功。我不明白的是,这个客户端消息是否由菜单 subwindow 生成。

我应该如何显示 _NET_WM_STATE_HIDDEN 中的 window?

另外一个奇怪的问题是,我收到ClientMessage后,总是收到2个UnMapNotify Events。

我还有另一个问题,如果我想显示 "File, Edit" 菜单(在 Firefox 中,如果我没记错的话,当你按下 Alt 按钮时它会出现。

也许 Firefox 创建了 windows 的树?

这是我处理事件的循环:

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;
    }

现在在客户端消息中,实际上我只是想看看收集一些信息以了解正在发生的事情。从上面的代码中我可以看出,引发事件的 window 包含一个子项(再次:是菜单吗?还是不是?)

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 中显示 subwindows 2. 如何显示文件、编辑菜单。

更新

我注意到使用 xev 测试 Firefox 时发现了一些奇怪的事情,以了解为了显示应用程序而触发的事件。我看到在unity中使用Firefox,和在另一个window manager中使用Firefox,触发的事件是完全不同的。在 Unity 中我只有:

  1. 客户端消息
  2. 取消映射通知

而不是使用 Firefox,例如使用 xfce4,生成的 xevents 更多:

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

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

更新 2

我试图读取 ClientMessage window 中的 XWMhints 属性(可能是菜单 window),值是:

更新 3

我试图查看另一个 window 管理器是如何工作的,我正在查看 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 属性。

这里有一个link到calmwm的源码:https://github.com/chneukirchen/cwm/blob/linux/xevents.c

所以我仍然停留在那个点上。

更新 4

真的不知道有没有帮助,不过我试着去读MapNotify事件中的window属性,windowmap_state是IsViewable(2)。

更新 5

我在 SO 中发现了类似的问题,将 xlib 与 python 一起使用:Xlib python: cannot map firefox menus

解决方案建议使用 XSetInputFocus,我在 XMapNotify 处理程序上尝试过:

XSetInputFocus(display, local_event.xmap.window, RevertToParent, CurrentTime);

但是还是没有用,firefox菜单还是没有出现!! 我的右键也有同样的问题。

更新 6

玩 xconfigurenotify 事件和取消映射事件我发现: Xconfigure 请求有 2 个 window 字段:window 及以上,并且当 xconfigurerequest.window 值与 xunmap.window 值相同。

而且 xconfigurerequest.above 总是在变化,但是 xconfigurerequest.window 在所有事件中总是相同的。

xconfigurerequest.above 似乎与我要打开的菜单有关。例如:

仍然不知道这是否有帮助。

真的不知道 有人知道吗?

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


Overview

Any programmer accustomed to writing programs on Linux or System V-type Unixes will have encountered the program variously known as strace or truss, which monitors another program and produces a detailed log of every system call the program makes – in other words, all the program's interactions with the OS kernel. This is often an invaluable debugging tool, and almost as good an educational one.

When it's a GUI program (or rather, the GUI-related behaviour of a program) that you want to understand or debug, though, the level of interaction with the OS kernel is rarely the most useful one. More helpfully, one would like to log all the program's interactions with the X server in the same way.

Programs already exist that will do this. I'm aware of Xmon and Xtrace. But they tend to require a lot of effort to set up: you have to run the program to establish a listening server, then manually arrange for the target program to contact that instead of the real server – including some fiddly work with xauth. Ideally, you'd like tracing a program's X operations to be just as easy as tracing its kernel system calls: you'd like to type a command as simple as strace program-name arguments, and have everything automatically handled for you.

Also, the output of those programs is less easy to read than I'd have liked – by which I largely mean it's less like strace than I'd like it to be. strace has the nice property of putting each system call and its return value on the same line of output, so that you can see at a glance what each response was a response to. X protocol monitors, however, tend to follow the structure of the X protocol faithfully, meaning that each request and response is printed with a sequence number, and you have to match the two up by eye.

So this page presents xtruss, my own contribution to the field of X protocol loggers. It has a command-line syntax similar to strace – in its default mode, you just prefix "xtruss" to the same command line you would have run anyway – and its output format is also more like strace, putting requests and responses on the same line of output where reasonably possible.

strace also supports the feature of attaching to an already-running process and tracing it from the middle of its run – handy when something goes wrong with a long-running process that you didn't know in advance you were going to need to trace. xtruss supports this same feature, by means of the X RECORD extension (provided your X server supports it, which modern X.Org ones do); so in that mode, you can identify a window with the mouse (similarly to standard programs like xwininfo and xkill), and xtruss will attach to the X client program that owns the window you specified, and begin tracing it.


Description

xtruss is a utility which logs everything that passes between the X server and one or more X client programs. In this it is similar to xmon(1), but intended to combine xmon's basic functionality with an interface much more similar to strace(1).

Like xmon, xtruss in its default mode works by setting up a proxy X server, waiting for connections to that, and forwarding them on to the real X server. However, unlike xmon, you don't have to deal with any of that by hand: there's no need to start the trace utility in one terminal and manually attach processes to it from another, unless you really want to (in which case the -P option will do that). The principal mode of use is just to type xtruss followed by the command line of your X program; xtruss will automatically take care of adjusting the new program's environment to point at its proxy server, and (also unlike xmon) it will also take care of X authorisation automatically.

As an alternative mode of use, you can also attach xtruss to an already-running X application, if you didn't realise you were going to want to trace it until it had already been started. This mode requires cooperation from the X server – specifically, it can't work unless the server supports the RECORD protocol extension – but since modern X.Org servers do provide that, it's often useful.

这个问题很古老,但为了让任何偶然发现它并寻找答案的人受益,这里有一个经过编辑(切碎)的示例,说明我是如何根据上面的提示解决这个问题的:

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] = '[=10=]';
            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] = '[=10=]';
                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,因为有两个主要的事情需要处理,window 必须根据要求移除其隐藏状态(xcb_delete_property 调用)并且瞬态的父级 window 必须获得输入焦点(xcb_set_input_focus 调用;请注意 window 瞬态是获得焦点的瞬态,而不是瞬态本身)或 Firefox 将立即再次隐藏瞬态。

似乎对于将瞬变堆叠在其父级之上也很重要,因此 WM 应该尊重 ConfigureRequest 事件。

PS 即使这是公认的答案,它的代码是针对 xcb 的,如果您需要 xlib 的代码,请查看下面我的答案,代码适用于 xlib,它仅涵盖 MapNotify 事件

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

我将修改 Lightning Bolt 先生的回答,并针对 XLIB 进行调整,并继续关注他所说的关于 Transient window 的内容。答案可能不完整,但至少有了那个代码片段,现在我可以打开 firefox 菜单了。

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

正如闪电指出的那样,关键是 MapNotify 事件,所以 window 管理器应该接受那种事件,并且在生成时应该:

  1. 使用 XGetTransientWindowForHint
  2. 抓取任何瞬变 window
  3. 如果发现任何瞬变 window,我们需要使用 XSetInputFocus 将输入焦点设置到它。

您的 MapNotifyHandler 中的完整代码应如下所示:

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