forked from RepoMirrors/bemenu
225 lines
7.0 KiB
C
225 lines
7.0 KiB
C
#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 :*/
|