diff options
Diffstat (limited to 'lib/renderers/x11/window.c')
-rw-r--r-- | lib/renderers/x11/window.c | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/lib/renderers/x11/window.c b/lib/renderers/x11/window.c new file mode 100644 index 0000000..0b4c94a --- /dev/null +++ b/lib/renderers/x11/window.c @@ -0,0 +1,224 @@ +#include "x11.h" + +#include <stdlib.h> +#include <cairo-xlib.h> +#include <X11/extensions/Xinerama.h> + +static void +destroy_buffer(struct buffer *buffer) +{ + bm_cairo_destroy(&buffer->cairo); + memset(buffer, 0, sizeof(struct buffer)); +} + +static bool +create_buffer(struct window *window, struct buffer *buffer, int32_t width, int32_t height) +{ + cairo_surface_t *surf; + if (!(surf = cairo_xlib_surface_create(window->display, window->drawable, DefaultVisual(window->display, window->screen), width, height))) + goto fail; + + cairo_xlib_surface_set_size(surf, width, height); + + if (!bm_cairo_create_for_surface(&buffer->cairo, surf)) { + cairo_surface_destroy(surf); + goto fail; + } + + buffer->width = width; + buffer->height = height; + buffer->created = true; + return true; + +fail: + destroy_buffer(buffer); + return false; +} + +static struct buffer* +next_buffer(struct window *window) +{ + assert(window); + + struct buffer *buffer = &window->buffer; + + if (!buffer) + return NULL; + + if (window->width != buffer->width || window->height != buffer->height) + destroy_buffer(buffer); + + if (!buffer->created && !create_buffer(window, buffer, window->width, window->height)) + return NULL; + + return buffer; +} + +void +bm_x11_window_render(struct window *window, const struct bm_menu *menu) +{ + assert(window && menu); + uint32_t oldw = window->width, oldh = window->height; + + struct buffer *buffer; + for (int32_t tries = 0; tries < 2; ++tries) { + if (!(buffer = next_buffer(window))) { + fprintf(stderr, "could not get next buffer"); + exit(EXIT_FAILURE); + } + + if (!window->notify.render) + break; + + cairo_push_group(buffer->cairo.cr); + struct cairo_paint_result result; + window->notify.render(&buffer->cairo, buffer->width, buffer->height, window->max_height, menu, &result); + window->displayed = result.displayed; + cairo_pop_group_to_source(buffer->cairo.cr); + + if (window->height == result.height) + break; + + window->height = result.height; + destroy_buffer(buffer); + } + + if (oldw != window->width || oldh != window->height) { + if (window->bottom) { + XMoveResizeWindow(window->display, window->drawable, window->x, window->max_height - window->height, window->width, window->height); + } else { + XResizeWindow(window->display, window->drawable, window->width, window->height); + } + } + + if (buffer->created) { + cairo_paint(buffer->cairo.cr); + cairo_surface_flush(buffer->cairo.surface); + } +} + +void +bm_x11_window_key_press(struct window *window, XKeyEvent *ev) +{ + KeySym keysym = NoSymbol; + XmbLookupString(window->xic, ev, NULL, 0, &keysym, NULL); + window->mods = 0; + if (ev->state & ControlMask) window->mods |= MOD_CTRL; + if (ev->state & ShiftMask) window->mods |= MOD_SHIFT; + window->keysym = keysym; +} + +void +bm_x11_window_destroy(struct window *window) +{ + assert(window); + destroy_buffer(&window->buffer); + + if (window->display && window->drawable) + XDestroyWindow(window->display, window->drawable); +} + +void +bm_x11_window_set_monitor(struct window *window, uint32_t monitor) +{ + if (window->monitor == monitor) + return; + + Window root = DefaultRootWindow(window->display); + + { + /* xinerama logic straight from dmenu */ +#define INTERSECT(x,y,w,h,r) (fmax(0, fmin((x)+(w),(r).x_org+(r).width) - fmax((x),(r).x_org)) * fmax(0, fmin((y)+(h),(r).y_org+(r).height) - fmax((y),(r).y_org))) + + int32_t n; + XineramaScreenInfo *info; + if ((info = XineramaQueryScreens(window->display, &n))) { + int32_t x, y, a, j, di, i = 0, area = 0; + uint32_t du; + Window w, pw, dw, *dws; + XWindowAttributes wa; + + XGetInputFocus(window->display, &w, &di); + if (monitor > 0) + i = ((int32_t)monitor > n ? n : (int32_t)monitor) - 1; + + if (monitor == 0 && w != root && w != PointerRoot && w != None) { + /* find top-level window containing current input focus */ + do { + if (XQueryTree(window->display, (pw = w), &dw, &w, &dws, &du) && dws) + XFree(dws); + } while(w != root && w != pw); + + /* find xinerama screen with which the window intersects most */ + if (XGetWindowAttributes(window->display, pw, &wa)) { + for (j = 0; j < n; j++) + if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) { + area = a; + i = j; + } + } + } + + /* no focused window is on screen, so use pointer location instead */ + if (monitor == 0 && !area && XQueryPointer(window->display, root, &dw, &dw, &x, &y, &di, &di, &du)) { + for (i = 0; i < n; i++) { + if (INTERSECT(x, y, 1, 1, info[i])) + break; + } + } + + window->x = info[i].x_org; + window->y = info[i].y_org + (window->bottom ? info[i].height - window->height : 0); + window->width = info[i].width; + window->max_height = info[i].height; + XFree(info); + } else { + window->max_height = DisplayHeight(window->display, window->screen); + window->x = 0; + window->y = (window->bottom ? window->max_height - window->height : 0); + window->width = DisplayWidth(window->display, window->screen); + } + +#undef INTERSECT + } + + window->monitor = monitor; + XMoveResizeWindow(window->display, window->drawable, window->x, window->y, window->width, window->height); + XFlush(window->display); +} + +void +bm_x11_window_set_bottom(struct window *window, bool bottom) +{ + if (window->bottom == bottom) + return; + + window->bottom = bottom; + bm_x11_window_set_monitor(window, window->monitor); +} + +bool +bm_x11_window_create(struct window *window, Display *display) +{ + assert(window); + + window->display = display; + window->screen = DefaultScreen(display); + window->width = window->height = 1; + window->monitor = -1; + + XSetWindowAttributes wa = { + .override_redirect = True, + .event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask + }; + + window->drawable = XCreateWindow(display, DefaultRootWindow(display), 0, 0, window->width, window->height, 0, DefaultDepth(display, window->screen), CopyFromParent, DefaultVisual(display, window->screen), CWOverrideRedirect | CWBackPixel | CWEventMask, &wa); + + XSelectInput(display, window->drawable, ButtonPressMask | KeyPressMask); + XMapRaised(display, window->drawable); + window->xim = XOpenIM(display, NULL, NULL, NULL); + window->xic = XCreateIC(window->xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, window->drawable, XNFocusWindow, window->drawable, NULL); + return true; +} + +/* vim: set ts=8 sw=4 tw=0 :*/ |