diff --git a/cmenu/popup.c b/cmenu/popup.c new file mode 100644 index 0000000..1b87c23 --- /dev/null +++ b/cmenu/popup.c @@ -0,0 +1,361 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include + +#include +#include + +static int get_active_window(Display *dpy, Window root, Window *active) +{ + Atom prop = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", True); + + if (!prop) + return 0; + + Atom actual; + int format; + unsigned long nitems; + unsigned long bytes_after; + unsigned char *data = NULL; + + int status = XGetWindowProperty( + dpy, + root, + prop, + 0, + 1, + False, + XA_WINDOW, + &actual, + &format, + &nitems, + &bytes_after, + &data + ); + + if (status != Success || !data || nitems == 0) { + if (data) + XFree(data); + return 0; + } + + *active = *(Window *)data; + + XFree(data); + return 1; +} + +static int get_window_center( + Display *dpy, + Window window, + int *cx, + int *cy +) +{ + Window root; + Window child; + + int x; + int y; + + unsigned int width; + unsigned int height; + unsigned int border; + unsigned int depth; + + if (!XGetGeometry( + dpy, + window, + &root, + &x, + &y, + &width, + &height, + &border, + &depth)) + return 0; + + int root_x; + int root_y; + + if (!XTranslateCoordinates( + dpy, + window, + root, + 0, + 0, + &root_x, + &root_y, + &child)) + return 0; + + *cx = root_x + (int)width / 2; + *cy = root_y + (int)height / 2; + + return 1; +} + +int main(void) +{ + Display *dpy = XOpenDisplay(NULL); + + if (!dpy) { + fprintf(stderr, "popup: cannot open X display\n"); + return EXIT_FAILURE; + } + + int screen = DefaultScreen(dpy); + Window root = RootWindow(dpy, screen); + + /* + * ------------------------------------------------------------ + * Find the currently focused window. + * ------------------------------------------------------------ + */ + + Window active = None; + + int cursor_x = DisplayWidth(dpy, screen) / 2; + int cursor_y = DisplayHeight(dpy, screen) / 2; + + if (get_active_window(dpy, root, &active)) { + get_window_center( + dpy, + active, + &cursor_x, + &cursor_y + ); + } + + /* + * ------------------------------------------------------------ + * Find the monitor containing the active window. + * ------------------------------------------------------------ + */ + + int monitor_x = 0; + int monitor_y = 0; + + int monitor_width = DisplayWidth(dpy, screen); + int monitor_height = DisplayHeight(dpy, screen); + + int monitor_count = 0; + + XRRMonitorInfo *monitors = + XRRGetMonitors( + dpy, + root, + True, + &monitor_count + ); + + if (monitors) { + + for (int i = 0; i < monitor_count; i++) { + + XRRMonitorInfo *m = &monitors[i]; + + if (cursor_x >= m->x && + cursor_x < m->x + (int)m->width && + cursor_y >= m->y && + cursor_y < m->y + (int)m->height) { + + monitor_x = m->x; + monitor_y = m->y; + + monitor_width = m->width; + monitor_height = m->height; + + break; + } + } + + XRRFreeMonitors(monitors); + } + + /* + * ------------------------------------------------------------ + * Desired geometry. + * + * Half the monitor width and half the monitor height, + * centered on that monitor. + * ------------------------------------------------------------ + */ + + int width = monitor_width / 2; + int height = monitor_height / 2; + + int x = monitor_x + (monitor_width - width) / 2; + int y = monitor_y + (monitor_height - height) / 2; + + /* + * ------------------------------------------------------------ + * Create the window. + * + * override_redirect means the window manager does not manage + * this window. This prevents it from moving/resizing it. + * ------------------------------------------------------------ + */ + + XSetWindowAttributes attributes; + + attributes.override_redirect = True; + attributes.background_pixel = + BlackPixel(dpy, screen); + + attributes.event_mask = + KeyPressMask | + FocusChangeMask | + StructureNotifyMask; + + Window window = XCreateWindow( + dpy, + root, + + x, + y, + + width, + height, + + 0, + + CopyFromParent, + InputOutput, + CopyFromParent, + + CWOverrideRedirect | + CWBackPixel | + CWEventMask, + + &attributes + ); + + if (!window) { + fprintf(stderr, "popup: XCreateWindow failed\n"); + XCloseDisplay(dpy); + return EXIT_FAILURE; + } + + /* + * ------------------------------------------------------------ + * Map the window. + * ------------------------------------------------------------ + */ + + XMapRaised(dpy, window); + XFlush(dpy); + + /* + * ------------------------------------------------------------ + * Wait until the X server has actually mapped the window. + * + * Calling XSetInputFocus() before this can result in: + * + * BadMatch (invalid parameter attributes) + * + * ------------------------------------------------------------ + */ + + for (;;) { + + XEvent event; + + XNextEvent(dpy, &event); + + if (event.type == MapNotify && + event.xmap.window == window) { + break; + } + } + + /* + * ------------------------------------------------------------ + * Now that the window is viewable, take keyboard focus. + * ------------------------------------------------------------ + */ + + XSetInputFocus( + dpy, + window, + RevertToParent, + CurrentTime + ); + + XRaiseWindow(dpy, window); + + XFlush(dpy); + + /* + * ------------------------------------------------------------ + * Event loop. + * + * Escape: + * exit + * + * FocusOut: + * exit + * + * The window is override-redirect, so the WM won't resize or + * reposition it. + * ------------------------------------------------------------ + */ + + for (;;) { + + XEvent event; + + XNextEvent(dpy, &event); + + switch (event.type) { + + case KeyPress: + { + KeySym key = + XLookupKeysym( + &event.xkey, + 0 + ); + + if (key == XK_Escape) { + + XDestroyWindow( + dpy, + window + ); + + XCloseDisplay(dpy); + + return EXIT_SUCCESS; + } + + break; + } + + case FocusOut: + { + /* + * Any loss of keyboard focus causes the popup + * to immediately terminate. + */ + + XDestroyWindow( + dpy, + window + ); + + XCloseDisplay(dpy); + + return EXIT_SUCCESS; + } + + default: + break; + } + } + + return EXIT_SUCCESS; +}