tAdd detailed comments to explain how the WM works - glazier - window management experiments
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) Submodules
(DIR) README
(DIR) LICENSE
---
(DIR) commit f34353b39ee245b298bef4288cc31d7379fc88f5
(DIR) parent 1e0105946562c0933f2a57d7d0d805ff7aa215da
(HTM) Author: Willy Goiffon <dev@z3bra.org>
Date: Sun, 27 Oct 2019 09:16:52 +0100
Add detailed comments to explain how the WM works
Diffstat:
M glazier.c | 179 ++++++++++++++++++++++---------
1 file changed, 131 insertions(+), 48 deletions(-)
---
(DIR) diff --git a/glazier.c b/glazier.c
t@@ -35,7 +35,7 @@ void usage(char *);
static int takeover();
static int adopt(xcb_window_t);
static int inflate(xcb_window_t, int);
-static int outline(xcb_drawable_t, int, int, int, int, int);
+static int outline(xcb_drawable_t, int, int, int, int);
static int ev_callback(xcb_generic_event_t *);
/* XCB events callbacks */
t@@ -108,7 +108,14 @@ usage(char *name)
fprintf(stderr, "usage: %s [-vh]\n", name);
}
-static int
+/*
+ * Every window that shouldn't be ignored (override_redirect) is adoped
+ * by the WM when it is created, or when the WM is started.
+ * When a window is created, it is centered on the cursor, before it
+ * gets mapped on screen. Windows that are already visible are not moved.
+ * Some events are also registered by the WM for these windows.
+ */
+int
adopt(xcb_window_t wid)
{
int x, y, w, h;
t@@ -123,14 +130,17 @@ adopt(xcb_window_t wid)
wm_teleport(wid, MAX(0, x - w/2), MAX(0, y - h/2), w, h);
}
- wm_reg_window_event(wid, XCB_EVENT_MASK_ENTER_WINDOW
+ return wm_reg_window_event(wid, XCB_EVENT_MASK_ENTER_WINDOW
| XCB_EVENT_MASK_FOCUS_CHANGE
| XCB_EVENT_MASK_STRUCTURE_NOTIFY);
-
- return 0;
}
-static int
+/*
+ * Inflating a window will grow it both vertically and horizontally in
+ * all 4 directions, thus making it look like it is inflating.
+ * The window can be "deflated" by providing a negative `step` value.
+ */
+int
inflate(xcb_window_t wid, int step)
{
int x, y, w, h;
t@@ -140,13 +150,16 @@ inflate(xcb_window_t wid, int step)
w = wm_get_attribute(wid, ATTR_W) + step;
h = wm_get_attribute(wid, ATTR_H) + step;
- wm_teleport(wid, x, y, w, h);
- wm_restack(wid, XCB_STACK_MODE_ABOVE);
-
- return 0;
+ return wm_teleport(wid, x, y, w, h);
}
-static int
+/*
+ * When the WM is started, it will take control of the existing windows.
+ * This means registering events on them and setting the borders if they
+ * are mapped. This function is only supposed to run once at startup,
+ * as the callback functions will take control of new windows
+ */
+int
takeover()
{
int i, n;
t@@ -167,12 +180,21 @@ takeover()
wm_set_border(border, border_color, wid);
}
-
return n;
}
-static int
-outline(xcb_drawable_t wid, int x, int y, int w, int h, int clear)
+/*
+ * Draws a rectangle selection on the screen.
+ * The trick here is to invert the color on the selection, so that
+ * redrawing the same rectangle will "clear" it.
+ * This function is used to dynamically draw a region for moving/resizing
+ * a window using the cursor. As such, we need to make sure that whenever
+ * we draw a rectangle, we clear out the last drawn one by redrawing
+ * the latest coordinates again, so we have to save them from one call to
+ * the other.
+ */
+int
+outline(xcb_drawable_t wid, int x, int y, int w, int h)
{
int mask, val[3];
static int X = 0, Y = 0, W = 0, H = 0;
t@@ -193,11 +215,6 @@ outline(xcb_drawable_t wid, int x, int y, int w, int h, int clear)
r.height = H;
xcb_poly_rectangle(conn, wid, gc, 1, &r);
- if (clear) {
- X = Y = W = H = 0;
- return 0;
- }
-
/* draw rectangle and save its coordinates for later removal */
X = r.x = x;
Y = r.y = y;
t@@ -208,7 +225,11 @@ outline(xcb_drawable_t wid, int x, int y, int w, int h, int clear)
return 0;
}
-static int
+/*
+ * Callback used for all events that are not explicitely registered.
+ * This is not at all necessary, and used for debugging purposes.
+ */
+int
cb_default(xcb_generic_event_t *ev)
{
if (verbose && XEV(ev)) {
t@@ -220,7 +241,13 @@ cb_default(xcb_generic_event_t *ev)
return 0;
}
-static int
+/*
+ * XCB_CREATE_NOTIFY is the first event triggered by new windows, and
+ * is used to prepare the window for use by the WM.
+ * The attribute `override_redirect` allow windows to specify that they
+ * shouldn't be handled by the WM.
+ */
+int
cb_create(xcb_generic_event_t *ev)
{
xcb_create_notify_event_t *e;
t@@ -238,7 +265,14 @@ cb_create(xcb_generic_event_t *ev)
return 0;
}
-static int
+/*
+ * XCB_MAP_REQUEST is triggered by a window that wants to be mapped on
+ * screen. This is then the responsibility of the WM to map it on screen
+ * and eventually decorate it. This event require that the WM register
+ * XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT on the root window to intercept
+ * map requests.
+ */
+int
cb_mapreq(xcb_generic_event_t *ev)
{
xcb_map_request_event_t *e;
t@@ -255,7 +289,19 @@ cb_mapreq(xcb_generic_event_t *ev)
return 0;
}
-static int
+/*
+ * The WM grabs XCB_BUTTON_PRESS events when the modifier is held.
+ * Once pressed, we'll grab the pointer entirely (without modifiers)
+ * and wait for motion/release events.
+ * The special mouse buttons 4/5 (scroll up/down) are treated especially,
+ * as they do not trigger any "release" event.
+ *
+ * This function must also save the window ID where the mouse press
+ * occured so we know which window to move/resize, even if the focus
+ * changes to another window.
+ * For similar reasons, we must save the cursor position.
+ */
+int
cb_mouse_press(xcb_generic_event_t *ev)
{
int mask;
t@@ -266,7 +312,7 @@ cb_mouse_press(xcb_generic_event_t *ev)
/* ignore some motion events if they happen too often */
if (e->time - lasttime < 8)
- return 0;
+ return -1;
if (verbose)
fprintf(stderr, "%s 0x%08x %d\n", XEV(e), e->event, e->detail);
t@@ -303,13 +349,18 @@ cb_mouse_press(xcb_generic_event_t *ev)
cursor.b = 0;
break;
default:
- return 1;
+ return -1;
}
return 0;
}
-static int
+/*
+ * When XCB_BUTTON_RELEASE is triggered, this will "commit" any
+ * move/resize initiated on a previous mouse press.
+ * This will also ungrab the mouse pointer.
+ */
+int
cb_mouse_release(xcb_generic_event_t *ev)
{
int x, y, w, h;
t@@ -359,12 +410,28 @@ cb_mouse_release(xcb_generic_event_t *ev)
wm_restack(curwid, XCB_STACK_MODE_ABOVE);
/* clear last drawn rectangle to avoid leaving artefacts */
- outline(scrn->root, 0, 0, 0, 0, 1);
+ outline(scrn->root, 0, 0, 0, 0);
return 0;
}
-static int
+/*
+ * When the pointer is grabbed, every move triggers a XCB_MOTION_NOTIFY.
+ * Events are reported for every single move by 1 pixel.
+ *
+ * This can spam a huge lot of events, and treating them all can be
+ * resource hungry and make the interface feels laggy.
+ * To get around this, we must ignore some of these events. This is done
+ * by using the `time` attribute, and only processing new events every
+ * X milliseconds.
+ *
+ * This callback is different from the others because it does not uses
+ * the ID of the window that reported the event, but an ID previously
+ * saved in cb_mouse_press().
+ * This makes sense as we want to move the last window we clicked on,
+ * and not the window we are moving over.
+ */
+int
cb_motion(xcb_generic_event_t *ev)
{
int x, y, w, h;
t@@ -381,7 +448,8 @@ cb_motion(xcb_generic_event_t *ev)
return -1;
if (verbose)
- fprintf(stderr, "%s 0x%08x %d,%d\n", XEV(e), curwid, e->root_x, e->root_y);
+ fprintf(stderr, "%s 0x%08x %d,%d\n", XEV(e), curwid,
+ e->root_x, e->root_y);
lasttime = e->time;
t@@ -391,7 +459,7 @@ cb_motion(xcb_generic_event_t *ev)
y = e->root_y - cursor.y;
w = wm_get_attribute(curwid, ATTR_W);
h = wm_get_attribute(curwid, ATTR_H);
- outline(scrn->root, x, y, w, h, 0);
+ outline(scrn->root, x, y, w, h);
break;
case XCB_BUTTON_MASK_2:
if (cursor.x > e->root_x) {
t@@ -408,24 +476,28 @@ cb_motion(xcb_generic_event_t *ev)
y = cursor.y;
h = e->root_y - y;
}
- outline(scrn->root, x, y, w, h, 0);
+ outline(scrn->root, x, y, w, h);
break;
case XCB_BUTTON_MASK_3:
x = wm_get_attribute(curwid, ATTR_X);
y = wm_get_attribute(curwid, ATTR_Y);
w = e->root_x - x;
h = e->root_y - y;
- outline(scrn->root, x, y, w, h, 0);
+ outline(scrn->root, x, y, w, h);
break;
default:
return -1;
}
-
return 0;
}
-static int
+/*
+ * Each time the pointer moves from one window to another, an
+ * XCB_ENTER_NOTIFY event is fired. This is used to switch input focus
+ * between windows to follow where the pointer is.
+ */
+int
cb_enter(xcb_generic_event_t *ev)
{
xcb_enter_notify_event_t *e;
t@@ -438,12 +510,15 @@ cb_enter(xcb_generic_event_t *ev)
if (verbose)
fprintf(stderr, "%s 0x%08x\n", XEV(e), e->event);
- wm_set_focus(e->event);
-
- return 0;
+ return wm_set_focus(e->event);
}
-static int
+/*
+ * Whenever the input focus change from one window to another, both an
+ * XCB_FOCUS_OUT and XCB_FOCUS_IN are fired.
+ * This is the occasion to change the border color to represent focus.
+ */
+int
cb_focus(xcb_generic_event_t *ev)
{
xcb_focus_in_event_t *e;
t@@ -455,17 +530,23 @@ cb_focus(xcb_generic_event_t *ev)
switch(e->response_type & ~0x80) {
case XCB_FOCUS_IN:
- wm_set_border(-1, border_color_active, e->event);
- break;
+ return wm_set_border(-1, border_color_active, e->event);
+ break; /* NOTREACHED */
case XCB_FOCUS_OUT:
- wm_set_border(-1, border_color, e->event);
- break;
+ return wm_set_border(-1, border_color, e->event);
+ break; /* NOTREACHED */
}
- return 0;
+ return -1;
}
-static int
+/*
+ * XCB_CONFIGURE_REQUEST is triggered by every window that wants to
+ * change its attributes like size, stacking order or border.
+ * These must now be handled by the WM because of the
+ * XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT registration.
+ */
+int
cb_configreq(xcb_generic_event_t *ev)
{
int x, y, w, h, b;
t@@ -495,7 +576,11 @@ cb_configreq(xcb_generic_event_t *ev)
return 0;
}
-static int
+/*
+ * This functions uses the ev_callback_t structure to call out a specific
+ * callback function for each EVENT fired.
+ */
+int
ev_callback(xcb_generic_event_t *ev)
{
uint8_t i;
t@@ -563,7 +648,5 @@ main (int argc, char *argv[])
free(ev);
}
- wm_kill_xcb();
-
- return 0;
+ return wm_kill_xcb();
}
\ No newline at end of file