tAdd monitoring of events on root window - xmenu - drop-down menu for X11
(HTM) git clone git://git.z3bra.org/xmenu.git
(DIR) Log
(DIR) Files
(DIR) Refs
---
(DIR) commit b02590ceeb90f07ab4ffe23bfa71ae1c8d096e9a
(DIR) parent 61c19bc23c3509c5b1e32afe95f335516fd39d32
(HTM) Author: Willy Goiffon <dev@z3bra.org>
Date: Wed, 20 Nov 2019 18:49:06 +0100
Add monitoring of events on root window
This will let the menu run as a daemon watching click events on the root
window and pop the menu on such events.
This saves a click compared to firing the application from an external
programming that's listenning for events.
Diffstat:
M xmenu.c | 150 ++++++++++++++++++++++++-------
1 file changed, 117 insertions(+), 33 deletions(-)
---
(DIR) diff --git a/xmenu.c b/xmenu.c
t@@ -15,6 +15,10 @@
#define MAX(a,b) (((a)>(b))?(a):(b))
#define MIN(a,b) (((a)<(b))?(a):(b))
+struct geom {
+ int x, y, w, h;
+};
+
int verbose = 0;
int current = -1;
xcb_connection_t *dpy;
t@@ -24,13 +28,12 @@ xcb_window_t wid;
size_t nent = 0;
size_t maxwidth = 0;
size_t maxheight = 0;
-int width = 0;
-int height = 0;
+struct geom menu = { 0, 0, 0, 0 };
void
usage(FILE *fd, char *name)
{
- fprintf(fd, "usage: %s [-h]\n", name);
+ fprintf(fd, "usage: %s [-hd]\n", name);
}
int
t@@ -63,8 +66,8 @@ drawentries()
size_t i, w, h;
for (i = 0; i < nent; i++) {
- w = (width - xft_txtw(entries[i])) / 2;
- h = (height/nent) * (i + 0.5) - maxheight/2 + (maxheight - xft_txth(entries[i]));
+ w = (menu.w - xft_txtw(entries[i])) / 2;
+ h = (menu.h/nent) * (i + 0.5) - maxheight/2 + (maxheight - xft_txth(entries[i]));
xft_drawtext(dpy, wid, w, h, foreground, entries[i]);
}
t@@ -72,7 +75,7 @@ drawentries()
}
int
-popwindow(int w, int h)
+popwindow(int x, int y, int w, int h)
{
int mask, val[4];
t@@ -84,14 +87,13 @@ popwindow(int w, int h)
| XCB_EVENT_MASK_KEY_PRESS
| XCB_EVENT_MASK_ENTER_WINDOW
| XCB_EVENT_MASK_LEAVE_WINDOW
- | XCB_EVENT_MASK_BUTTON_PRESS
| XCB_EVENT_MASK_BUTTON_RELEASE
| XCB_EVENT_MASK_POINTER_MOTION
| XCB_EVENT_MASK_STRUCTURE_NOTIFY;
wid = xcb_generate_id(dpy);
xcb_create_window(dpy, screen->root_depth, wid, screen->root,
- 0, 0, w, h, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
+ x, y, w, h, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
screen->root_visual, mask, val);
xcb_map_window(dpy, wid);
t@@ -103,10 +105,10 @@ popwindow(int w, int h)
int
eventloop()
{
- int loop = 1;
xcb_generic_event_t *ev;
+ xcb_motion_notify_event_t *e;
- while(loop) {
+ for (;;) {
int last = current;
ev = xcb_wait_for_event(dpy);
t@@ -114,18 +116,48 @@ eventloop()
break;
switch(ev->response_type & ~0x80) {
+
+ /*
+ * pressing escape (keycode 9) clears the selection and
+ * closes the menu
+ */
case XCB_KEY_PRESS:
- if (((xcb_key_press_event_t *)ev)->detail == 9)
- return -1;
+ if (((xcb_key_press_event_t *)ev)->detail == 9) {
+ current = -1;
+ return 0;
+ }
break;
- case XCB_BUTTON_RELEASE:
- if (((xcb_button_release_event_t *)ev)->event == wid)
- return current;
+
+ /*
+ * button press events MUST be caught on root window
+ * only to avoid popup loops
+ */
+ case XCB_BUTTON_PRESS:
+ menu.x = ((xcb_button_press_event_t *)ev)->root_x;
+ menu.y = ((xcb_button_press_event_t *)ev)->root_y;
+ popwindow(menu.x, menu.y, menu.w, menu.h);
break;
+
+ /*
+ * releasing the mouse button closes the menu while
+ * keeping the current selection active. In other words,
+ * it validates the current selection (if any)
+ */
+ case XCB_BUTTON_RELEASE:
+ return 0;
+ break; /* NOTREACHED */
+
+ /*
+ * make sure to keep track of window geometry changes
+ * to calculate cursor position correctly
+ */
case XCB_CONFIGURE_NOTIFY:
- width = ((xcb_configure_notify_event_t *)ev)->width;
- height = ((xcb_configure_notify_event_t *)ev)->height;
+ menu.x = ((xcb_configure_notify_event_t *)ev)->x;
+ menu.y = ((xcb_configure_notify_event_t *)ev)->y;
+ menu.w = ((xcb_configure_notify_event_t *)ev)->width;
+ menu.h = ((xcb_configure_notify_event_t *)ev)->height;
break;
+
case XCB_EXPOSE:
/*
* TODO: clean after someone mess up current entry
t@@ -139,17 +171,30 @@ eventloop()
*
* Damn punks.
*/
- if (last >= 0)
- hilight(wid, 0, (height/nent) * last, width, height/nent);
+ if (last >= 0 && last != current)
+ hilight(wid, 0, (menu.h/nent) * last, menu.w, menu.h/nent);
drawentries();
if (current >= 0)
- hilight(wid, 0, (height/nent) * current, width, height/nent);
+ hilight(wid, 0, (menu.h/nent) * current, menu.w, menu.h/nent);
break;
+
+ /*
+ * moving the cursor outside the menu clears the current
+ * selection, so that you can "cancel" the menu by releasing
+ * the mouse outside the menu
+ */
case XCB_LEAVE_NOTIFY:
- hilight(wid, 0, (height/nent) * current, width, height/nent);
+ if (current >= 0)
+ hilight(wid, 0, (menu.h/nent) * current, menu.w, menu.h/nent);
current = -1;
break;
+
+ /*
+ * calculate current item based on cursor position
+ * relative to the root window and current window dimensions
+ */
case XCB_MOTION_NOTIFY:
+ e = (xcb_motion_notify_event_t *)ev;
/*
* in some special cases, MOTION events can be
* reported in the window when the pointer is
t@@ -160,13 +205,21 @@ eventloop()
* cases, so we must check first that the pointer
* is actually inside the window
*/
- if (((xcb_motion_notify_event_t *)ev)->event_x > width
- || ((xcb_motion_notify_event_t *)ev)->event_y > height)
+ if (e->root_x > menu.x + menu.w
+ || e->root_y > menu.y + menu.h
+ || e->root_x < menu.x
+ || e->root_y < menu.y)
break;
- current = nent * (((xcb_motion_notify_event_t *)ev)->event_y * 1.0 / height);
- hilight(wid, 0, (height/nent) * last, width, height/nent);
- hilight(wid, 0, (height/nent) * current, width, height/nent);
+ current = nent * ((e->root_y - menu.y) * 1.0 / menu.h);
+
+ /* don't bother redrawing selection that didn't change */
+ if (last == current)
+ break;
+ if (last >= 0)
+ hilight(wid, 0, (menu.h/nent) * last, menu.w, menu.h/nent);
+ if (current >= 0)
+ hilight(wid, 0, (menu.h/nent) * current, menu.w, menu.h/nent);
break;
}
t@@ -180,11 +233,14 @@ eventloop()
int
main(int argc, char *argv[])
{
- int selected = 0;
+ int dflag = 0;
long dpi;
char *argv0;
ARGBEGIN {
+ case 'd':
+ dflag = 1;
+ break;
case 'h':
usage(stdout, argv0);
return 0;
t@@ -216,15 +272,43 @@ main(int argc, char *argv[])
maxheight = MAX(xft_txth(entries[nent]), maxheight);
}
- width = maxwidth * 1.2;
- height = maxheight * nent * 2.5;
+ menu.w = maxwidth * 1.5;
+ menu.h = maxheight * nent * 2;
+
+ /*
+ * daemon mode will catch mouse clicks on the root window and
+ * pop the menu everytime it is clicked.
+ * selection happens instantly and is validated upon releasing
+ * the mouse button while hovering an item
+ */
+ if (dflag) {
+ int val[] = {
+ XCB_EVENT_MASK_BUTTON_PRESS
+ | XCB_EVENT_MASK_BUTTON_RELEASE
+ | XCB_EVENT_MASK_POINTER_MOTION
+ };
+
+ xcb_change_window_attributes(dpy, screen->root, XCB_CW_EVENT_MASK, val);
+ xcb_flush(dpy);
+
+ for (;;) {
+ current = -1;
+ eventloop();
+ if (current >= 0) {
+ printf("%s\n", entries[current]);
+ fflush(stdout);
+ }
- popwindow(width, height);
- selected = eventloop();
+ xcb_destroy_window(dpy, wid);
+ }
+ }
- if (selected >= 0)
- printf("%s\n", entries[selected]);
+ popwindow(menu.x, menu.y, menu.w, menu.h);
+ eventloop();
+ if (current >= 0)
+ printf("%s\n", entries[current]);
+ xcb_destroy_window(dpy, wid);
xft_unload();
xcb_disconnect(dpy);
return 0;