Interacting with the window manager
After we have seen how to create windows and draw on them, we take one step back, and look at how our windows are interacting with their environment (the full screen and the other windows). First of all, our application needs to interact with the window manager. The window manager is responsible to decorating drawn windows (i.e. adding a frame, an iconify button, a system menu, a title bar, etc), as well as handling icons shown when windows are being iconified. It also handles ordering of windows on the screen, and other administrative tasks. We need to give it various hints as to how we want it to treat our application's windows.
1. Window properties
Many of the parameters communicated to the window manager are passed using data called "properties". These properties are attached by the X server to different windows, and are stored in a format that makes it possible to read them from different machines that may use different architectures (remember that an X client program may run on a remote machine).
The property and its type (a string, an integer, etc) are Id. Their type are xcb_atom_t:
typedef uint32_t xcb_atom_t;
To change the property of a window, we use the following function:
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 */
The mode parameter coud be one of the following values (defined in enumeration xcb_prop_mode_t in the xproto.h header file):
XCB_PROP_MODE_REPLACE
XCB_PROP_MODE_PREPEND
XCB_PROP_MODE_APPEND
2. Setting the window name and icon name
The first thing we want to do would be to set the name for our window. This is done using the xcb_change_property() function. This name may be used by the window manager as the title of the window (in the title bar), in a task list, etc. The property atom to use to set the name of a window is XCB_ATOM_WM_NAME (and XCB_ATOM_WM_ICON_NAME for the iconified window) and its type is XCB_ATOM_STRING. Here is an example of utilization:
#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;
}
Note: the use of the atoms needs our program to be compiled and linked against xcb_atom, so that we have to use
gcc prog.c -o prog `pkg-config --cflags --libs xcb-atom`
...for the program to compile fine.
Simple window operations
One more thing we can do to our window is manipulate them on the screen (resize them, move them, raise or lower them, iconify them, and so on). Some window operations functions are supplied by XCB for this purpose.
1. Mapping and un-mapping a window
The first pair of operations we can apply on a window is mapping it, or un-mapping it. Mapping a window causes the window to appear on the screen, as we have seen in our simple window program example. Un-mapping it causes it to be removed from the screen (although the window as a logical entity still exists). This gives the effect of making a window hidden (unmapped) and shown again (mapped). For example, if we have a dialog box window in our program, instead of creating it every time the user asks to open it, we can create the window once, in an un-mapped mode, and when the user asks to open it, we simply map the window on the screen. When the user clicked the 'OK' or 'Cancel' button, we simply un-map the window. This is much faster than creating and destroying the window, however, the cost is wasted resources, both on the client side, and on the X server side.
To map a window, you use the following function:
xcb_void_cookie_t xcb_map_window (xcb_connection_t *c,
xcb_window_t window );
To have a simple example, see the example above. The mapping operation will cause an Expose event to be sent to our application, unless the window is completely covered by other windows.
Un-mapping a window is also simple. You use the function
xcb_void_cookie_t xcb_unmap_window (xcb_connection_t *c,
xcb_window_t window );
The utilization of this function is the same as xcb_map_window().
2. Configuring a window
As we have seen when we have created our first window, in the X Events subsection, we can set some attributes for the window (that is, the position, the size, the events the window will receive, etc). If we want to modify them, but the window is already created, we can change them by using the following function:
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 */
We set the value_mask to one or several mask values that are in the xcb_config_window_t enumeration in the xproto.h header:
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
We then give to value_mask the new value. We now describe how to use xcb_configure_window_t in some useful situations.
3. Moving a window around the screen
An operation we might want to do with windows is to move them to a different location. This can be done like this:
const static uint32_t values[] = { 10, 20 };
/* Move the window to coordinates x = 10 and y = 20 */
xcb_configure_window (connection, window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values);
Note that when the window is moved, it might get partially exposed or partially hidden by other windows, and thus we might get Expose events due to this operation.
4. Resizing a window
Yet another operation we can do is to change the size of a window. This is done using the following code:
const static uint32_t values[] = { 200, 300 };
/* Resize the window to width = 200 and height = 300 */
xcb_configure_window (connection, window, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values);
We can also combine the move and resize operations using one single call to xcb_configure_window_t:
const static uint32_t values[] = { 10, 20, 200, 300 };
/* Move the window to coordinates x = 10 and y = 20 */
/* and resize the window to width = 200 and height = 300 */
xcb_configure_window (connection, window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values);
5. Changing windows stacking order: raise and lower
Until now, we changed properties of a single window. We'll see that there are properties that relate to the window and other windows. One of them is the stacking order. That is, the order in which the windows are layered on top of each other. The front-most window is said to be on the top of the stack, while the back-most window is at the bottom of the stack. Here is how to manipulate our windows stack order:
const static uint32_t values[] = { XCB_STACK_MODE_ABOVE };
/* Move the window on the top of the stack */
xcb_configure_window (connection, window, XCB_CONFIG_WINDOW_STACK_MODE, values);
const static uint32_t values[] = { XCB_STACK_MODE_BELOW };
/* Move the window on the bottom of the stack */
xcb_configure_window (connection, window, XCB_CONFIG_WINDOW_STACK_MODE, values);
6. Getting information about a window
Just like we can set various attributes of our windows, we can also ask the X server supply the current values of these attributes. For example, we can check where a window is located on the screen, what is its current size, whether it is mapped or not, etc. The structure that contains some of this information is:
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;
Two functions fill this structure:
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);
You use them as follows:
xcb_get_geometry_cookie_t geomCookie = xcb_get_geometry (connection, window); // window is a xcb_drawable_t
xcb_get_geometry_reply_t *geom = xcb_get_geometry_reply (connection, geomCookie, NULL);
/* ...do stuff with geom... */
free (geom);
One problem is that the returned location of the window is relative to its parent window. This makes these coordinates rather useless for any window manipulation functions, like moving it on the screen. In order to overcome this problem, we need to take a two-step operation. First, we find out the Id of the parent window of our window. We then translate the above relative coordinates to the screen coordinates.
To get the Id of the parent window, we need this structure:
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;
To fill this structure, we use these two functions:
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 );
The translated coordinates will be found in this structure:
typedef struct {
uint8_t response_type;
uint8_t same_screen;
uint16_t sequence;
uint32_t length;
xcb_window_t child;
uint16_t dst_x; /* Translated x coordinate */
uint16_t dst_y; /* Translated y coordinate */
} xcb_translate_coordinates_reply_t;
As usual, we need two functions to fill this structure:
xcb_translate_coordinates_cookie_t xcb_translate_coordinates (xcb_connection_t *c,
xcb_window_t src_window,
xcb_window_t dst_window,
int16_t src_x,
int16_t src_y );
xcb_translate_coordinates_reply_t *xcb_translate_coordinates_reply (xcb_connection_t *c,
xcb_translate_coordinates_cookie_t cookie,
xcb_generic_error_t **e );
We use them as follows:
/* assume connection and window */
xcb_get_geometry_reply_t *geom = xcb_get_geometry_reply (connection,
xcb_get_geometry (connection, window),
NULL );
if (!geom) {
return 0;
}
xcb_query_tree_reply_t *tree = xcb_query_tree_reply (connection,
xcb_query_tree (connection, window),
NULL );
if (!tree) {
return 0;
}
xcb_translate_coordinates_cookie_t translateCookie = xcb_translate_coordinates (connection,
window,
tree->parent,
geom->x, geom->y );
xcb_translate_coordinates_reply_t *trans = xcb_translate_coordinates_reply (connection,
translateCookie,
NULL );
if (!trans) {
return 0;
}
/* the translated coordinates are in trans->dst_x and trans->dst_y */
free (trans);
free (tree);
free (geom);
The work is a bit verbose, but XCB is a quite low-level library.
TODO: the utilization of these functions should be a prog, which displays the coordinates of the window.
There is another structure that gives informations about our window:
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 supplies these two functions to fill it:
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 );
You use them as follows:
/* assume connection and window */
xcb_get_window_attributes_cookie_t attributesCookie = xcb_get_window_attributes (connection, window);
xcb_get_window_attributes_reply_t *attributes = xcb_get_window_attributes_reply (connection,
attributesCookie,
NULL );
if (!attributes) {
return 0;
}
/* ...do something with attributes... */
free (attributes);
As for geom, attr has to be freed.