首页 > 代码库 > 使用XCB编写X Window程序(05):使用异步的方式和X Server通讯及获取和设置窗口的属性

使用XCB编写X Window程序(05):使用异步的方式和X Server通讯及获取和设置窗口的属性

  在前面的例子中,我们从来没有关心过调用XCB函数时,该函数和X Server的通讯是同步的还是异步的。因为在前面的例子中,我们基本上不关心XCB函数的返回值,只有在上一篇中,由于某些操作需要关心它们是否成功(比如是否成功打开字体、是否成功创建GC等),才涉及到XCB函数的返回值。

  在这一篇中会更进一步,因为是获取窗口的属性,所以肯定要关注从X Server获取的数据,这时,将会涉及到XCB函数同X Server的通讯是同步的还是异步的。什么是同步?就是说调用一个函数向X Server发出一个请求后,该函数要一直等到X Server回复的数据函数才返回。什么是异步?就是说调用一个XCB函数向X Server发出一个请求后,不用等X Server回复的数据,该函数直接返回。传统的Xlib使用的是同步的方式,而XCB则是使用的异步方式。很显然,使用异步的方式可以获得更好的效率。这也是XCB取代Xlib的一个主要原因。

  那么既然XCB使用的是异步的方式,调用一个XCB函数后没有等到X Server的回复该函数就已经返回了,那么我们真的需要X Server回复的数据怎么办呢?对于这个问题,在XCB中使用的是这样一种解决方式:XCB函数先返回一个cookie,数据传输在后台进行,由XCB自动处理,当需要使用从X Server获取的数据时,再调用另外一个函数通过该cookie获取数据。后面将会看到具体的示例。

 

操控窗口的属性

  前面展示了怎么创建窗口以及怎么在窗口中画图。其实对于窗口,还可以有更多的操作。可以认为这些操作都是通过获取和设置窗口的属性来进行。窗口可以有很多的属性,而且这些属性的设置方式还不一样。下面来回顾一下创建窗口的函数:

xcb_void_cookie_t xcb_create_window (    xcb_connection_t *connection,    /* Pointer to the xcb_connection_t structure */    uint8_t           depth,         /* Depth of the screen */    xcb_window_t      wid,           /* Id of the window */    xcb_window_t      parent,        /* Id of an existing window that should be the parent of the new window */    int16_t           x,             /* X position of the top-left corner of the window (in pixels) */    int16_t           y,             /* Y position of the top-left corner of the window (in pixels) */    uint16_t          width,         /* Width of the window (in pixels) */    uint16_t          height,        /* Height of the window (in pixels) */    uint16_t          border_width,  /* Width of the window‘s border (in pixels) */    uint16_t          _class,    xcb_visualid_t    visual,    uint32_t          value_mask,    const uint32_t   *value_list );

  可以看到:1、有一部分属性是直接通过xcb_create_window的参数设置的,比如窗口的颜色深度、位置、大小、边框宽度等;2、一部分窗口的属性通过mask、values的方式设置,比如前景色、背景色、需要关注的事件等;3、还有一部分属性需要通过另外一种方式来设置,这种方式我之前没有展示过,那就是通过xcb_change_property函数来设置,这些属性比较特别,每一个都称之为atom。从xcb_create_window的函数签名还可以看出,xcb_create_window确实是返回一个xcb_void_cookie_t类型的值,也就是返回一个cookie。这验证了我之前关于XCB函数是异步的这个说法。

  xcb_change_property的函数签名如下:

xcb_void_cookie_t xcb_change_property (    xcb_connection_t *c,       /* Connection to the X server */    uint8_t          mode,     /* Property mode */    xcb_window_t     window,   /* Window */    xcb_atom_t       property, /* Property to change */    xcb_atom_t       type,     /* Type of the property */    uint8_t          format,   /* Format of the property (8, 16, 32) */    uint32_t         data_len, /* Length of the data parameter */    const void       *data     /* Data */); 

  可以看出,该函数要同时关注atom的id、atom的类型及atom的值,还要关注该值的格式和长度。也就是说,每一个atom由一个id标识,它可以有自己的类型和值。比如一个atom可能是一个数字,而另一atom可能是一个字符串。该函数的mode参数可以取下面值中的一个:

XCB_PROP_MODE_REPLACEXCB_PROP_MODE_PREPENDXCB_PROP_MODE_APPEND

 

  使用atom来表示窗口的属性主要是为了和别的程序之间进行沟通,比如和窗口管理器沟通。下面是一个设置窗口标题以及窗口最小化后任务栏标题的示例:

    #include <string.h>    #include <xcb/xcb.h>    #include <xcb/xcb_atom.h>    int    main ()    {        /* open the connection to the X server */        xcb_connection_t *connection = xcb_connect (NULL, NULL);        /* get the first screen */        xcb_screen_t *screen = xcb_setup_roots_iterator (xcb_get_setup (connection)).data;        /* create the window */        xcb_window_t window = xcb_generate_id (connection);        xcb_create_window (connection,                            0,                             /* depth               */                           window,                           screen->root,                  /* parent window       */                           0, 0,                          /* x, y                */                           250, 150,                      /* width, height       */                           10,                            /* border_width        */                           XCB_WINDOW_CLASS_INPUT_OUTPUT, /* class               */                           screen->root_visual,           /* visual              */                           0, NULL );                     /* masks, not used     */        /* set the title of the window */        char *title = "Hello World !";        xcb_change_property (connection,                             XCB_PROP_MODE_REPLACE,                             window,                             XCB_ATOM_WM_NAME,                             XCB_ATOM_STRING,                             8,                             strlen (title),                             title );        /* set the title of the window icon */        char *iconTitle = "Hello World ! (iconified)";        xcb_change_property (connection,                             XCB_PROP_MODE_REPLACE,                             window,                             XCB_ATOM_WM_ICON_NAME,                             XCB_ATOM_STRING,                             8,                             strlen(iconTitle),                             iconTitle);        /* map the window on the screen */        xcb_map_window (connection, window);        xcb_flush (connection);        /* event loop (in this case, no events to handle) */         while (1) {}        return 0;    }

  如果想更改窗口的大小、位置、边框宽度等属性的话,可以通过如下函数进行:

xcb_void_cookie_t xcb_configure_window (    xcb_connection_t *c,            /* The connection to the X server*/    xcb_window_t      window,       /* The window to configure */    uint16_t          value_mask,   /* The mask */    const uint32_t   *value_list    /* The values to set */);

  可以看到,该函数再次用到了mask、valuelist这种模式。该函数可操作的属性有如下这些:

    XCB_CONFIG_WINDOW_X             // new x coordinate of the window‘s top left corner    XCB_CONFIG_WINDOW_Y             // new y coordinate of the window‘s top left corner    XCB_CONFIG_WINDOW_WIDTH         // new width of the window    XCB_CONFIG_WINDOW_HEIGHT        // new height of the window    XCB_CONFIG_WINDOW_BORDER_WIDTH  // new width of the border of the window    XCB_CONFIG_WINDOW_SIBLING    XCB_CONFIG_WINDOW_STACK_MODE    // the new stacking order

  写了半天也还没写到XCB函数和X Server异步通讯的用法。为了不把这篇随笔写得又臭又长,xcb_configure_window具体怎么用我就不举例了。大家可以自行阅读XCB的官方教程。下面来看什么是异步。

 

获取窗口的属性

  有很多函数可以获取窗口的属性,比如最常见的 xcb_get_geometry 和 xcb_get_geometry_reply ,看到geometry肯定会想到长宽高、横坐标纵坐标什么的。确实,这两个函数就是用来获取窗口的位置、大小这样一些信息的。而且这两个函数总是成对出现。因为xcb_get_geometry被调用后,它是马上返回的,这时X Server的数据还没传回来呢,所以,等数据传回来了,还得调用xcb_get_geometry_reply把这些数据读出来。这两个函数的签名如下:

xcb_get_geometry_cookie_t xcb_get_geometry (    xcb_connection_t *connection,    xcb_drawable_t    drawable );xcb_get_geometry_reply_t *xcb_get_geometry_reply (    xcb_connection_t          *connection,    xcb_get_geometry_cookie_t  cookie,    xcb_generic_error_t      **error);

  可以看到,第一个函数返回一个类型为xcb_get_geometry_cookie_t的cookie,第二个函数再以该cookie为参数,返回具体的geometry信息,这就是异步。该信息是一个类型为xcb_get_geometry_reply_t的指针,该类型定义如下:

typedef struct {        uint8_t      response_type;        uint8_t      depth;         /* depth of the window */        uint16_t     sequence;        uint32_t     length;        xcb_window_t root;          /* Id of the root window *>        int16_t      x;             /* X coordinate of the window‘s location */        int16_t      y;             /* Y coordinate of the window‘s location */        uint16_t     width;         /* Width of the window */        uint16_t     height;        /* Height of the window */        uint16_t     border_width;  /* Width of the window‘s border */    } xcb_get_geometry_reply_t;

  前面说过,窗口有很多属性。geometry属性指示窗口属性的一部分,所以,还有一对更猛的获取窗口属性的函数及其使用到的数据结构,如下:

typedef struct {        uint8_t        response_type;        uint8_t        backing_store;        uint16_t       sequence;        uint32_t       length;        xcb_visualid_t visual;                /* Visual of the window */        uint16_t       _class;        uint8_t        bit_gravity;        uint8_t        win_gravity;        uint32_t       backing_planes;        uint32_t       backing_pixel;        uint8_t        save_under;        uint8_t        map_is_installed;        uint8_t        map_state;             /* Map state of the window */        uint8_t        override_redirect;        xcb_colormap_t colormap;              /* Colormap of the window */        uint32_t       all_event_masks;        uint32_t       your_event_mask;        uint16_t       do_not_propagate_mask;} xcb_get_window_attributes_reply_t;xcb_get_window_attributes_cookie_t xcb_get_window_attributes (    xcb_connection_t *connection,    xcb_window_t      window );xcb_get_window_attributes_reply_t *xcb_get_window_attributes_reply (    xcb_connection_t                   *connection,    xcb_get_window_attributes_cookie_t  cookie,    xcb_generic_error_t               **e );

  这些都还是基本的。编写简单的窗口程序,了解这些基本的方法就够了。但是GUI编程,总还有一些高深的需求,比如枚举系统中所有的窗口。我这里用到了“枚举”这个词,是因为Win32 API用了这个词。而在X Window中,这样的操作叫做“遍历”。很显然,在GUI系统中,所有的窗口都是通过parent、child这样的方式呈树形组织起来的,所以通过“遍历”窗口树来“枚举”系统中的所有窗口,其实也不难。这个任务需要用到这样的函数和数据结构:

typedef struct {        uint8_t      response_type;        uint8_t      pad0;        uint16_t     sequence;        uint32_t     length;        xcb_window_t root;        xcb_window_t parent;       /* Id of the parent window */        uint16_t     children_len;        uint8_t      pad1[14];} xcb_query_tree_reply_t;xcb_query_tree_cookie_t xcb_query_tree (    xcb_connection_t        *connection,    xcb_window_t             window );xcb_query_tree_reply_t *xcb_query_tree_reply (    xcb_connection_t        *connection,    xcb_query_tree_cookie_t  cookie,    xcb_generic_error_t    **error );

  不过该函数好像只能从子窗口查找父窗口,很显然要完成遍历,还必须要能从父窗口查找子窗口。X Window中有通过父窗口查找子窗口的方法吗?大家慢慢研究去吧。

(京山游侠于2014-07-21发布于博客园,转载请注明出处。)