diff options
Diffstat (limited to 'src/WindowSelector.cpp')
-rw-r--r-- | src/WindowSelector.cpp | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/src/WindowSelector.cpp b/src/WindowSelector.cpp new file mode 100644 index 0000000..f04d600 --- /dev/null +++ b/src/WindowSelector.cpp @@ -0,0 +1,229 @@ +#include "../include/WindowSelector.hpp" +#include "../include/WindowUtils.hpp" + +#include <stdio.h> +#include <string.h> + +#include <X11/extensions/shape.h> +#include <X11/cursorfont.h> +#include <X11/keysym.h> + +namespace gsr { + static const int rectangle_border_size = 2; + + static int max_int(int a, int b) { + return a >= b ? a : b; + } + + static void set_region_rectangle(Display *dpy, Window window, int x, int y, int width, int height, int border_size) { + if(width < 0) { + x += width; + width = abs(width); + } + + if(height < 0) { + y += height; + height = abs(height); + } + + XRectangle rectangles[] = { + { + (short)max_int(0, x), (short)max_int(0, y), + (unsigned short)max_int(0, border_size), (unsigned short)max_int(0, height) + }, // Left + { + (short)max_int(0, x + width - border_size), (short)max_int(0, y), + (unsigned short)max_int(0, border_size), (unsigned short)max_int(0, height) + }, // Right + { + (short)max_int(0, x + border_size), (short)max_int(0, y), + (unsigned short)max_int(0, width - border_size*2), (unsigned short)max_int(0, border_size) + }, // Top + { + (short)max_int(0, x + border_size), (short)max_int(0, y + height - border_size), + (unsigned short)max_int(0, width - border_size*2), (unsigned short)max_int(0, border_size) + }, // Bottom + }; + XShapeCombineRectangles(dpy, window, ShapeBounding, 0, 0, rectangles, 4, ShapeSet, Unsorted); + XFlush(dpy); + } + + static unsigned long mgl_color_to_x11_color(mgl::Color color) { + if(color.a == 0) + return 0; + return ((uint32_t)color.a << 24) | (((uint32_t)color.r * color.a / 0xFF) << 16) | (((uint32_t)color.g * color.a / 0xFF) << 8) | ((uint32_t)color.b * color.a / 0xFF); + } + + static Window get_cursor_window(Display *dpy) { + Window root_window = None; + Window window = None; + int dummy_i; + unsigned int dummy_u; + mgl::vec2i root_pos; + XQueryPointer(dpy, DefaultRootWindow(dpy), &root_window, &window, &root_pos.x, &root_pos.y, &dummy_i, &dummy_i, &dummy_u); + return window; + } + + static void get_window_geometry(Display *dpy, Window window, mgl::vec2i &pos, mgl::vec2i &size) { + Window root_window; + int x = 0; + int y = 0; + unsigned int w = 0; + unsigned int h = 0; + unsigned int dummy_border, dummy_depth; + XGetGeometry(dpy, window, &root_window, &x, &y, &w, &h, &dummy_border, &dummy_depth); + pos.x = x; + pos.y = y; + size.x = w; + size.y = h; + } + + WindowSelector::WindowSelector() { + + } + + WindowSelector::~WindowSelector() { + stop(); + } + + bool WindowSelector::start(mgl::Color border_color) { + if(dpy) + return false; + + const unsigned long border_color_x11 = mgl_color_to_x11_color(border_color); + dpy = XOpenDisplay(nullptr); + if(!dpy) { + fprintf(stderr, "Error: WindowSelector::start: failed to connect to the X11 server\n"); + return false; + } + + const Window cursor_window = get_cursor_window(dpy); + mgl::vec2i cursor_window_pos, cursor_window_size; + get_window_geometry(dpy, cursor_window, cursor_window_pos, cursor_window_size); + + XVisualInfo vinfo; + memset(&vinfo, 0, sizeof(vinfo)); + XMatchVisualInfo(dpy, DefaultScreen(dpy), 32, TrueColor, &vinfo); + border_window_colormap = XCreateColormap(dpy, DefaultRootWindow(dpy), vinfo.visual, AllocNone); + + XSetWindowAttributes window_attr; + window_attr.background_pixel = border_color_x11; + window_attr.border_pixel = 0; + window_attr.override_redirect = true; + window_attr.event_mask = StructureNotifyMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask; + window_attr.colormap = border_window_colormap; + + Screen *screen = XDefaultScreenOfDisplay(dpy); + border_window = XCreateWindow(dpy, DefaultRootWindow(dpy), 0, 0, XWidthOfScreen(screen), XHeightOfScreen(screen), 0, + vinfo.depth, InputOutput, vinfo.visual, CWBackPixel | CWBorderPixel | CWOverrideRedirect | CWEventMask | CWColormap, &window_attr); + if(!border_window) { + fprintf(stderr, "Error: WindowSelector::start: failed to create region window\n"); + stop(); + return false; + } + set_window_size_not_resizable(dpy, border_window, XWidthOfScreen(screen), XHeightOfScreen(screen)); + if(cursor_window && cursor_window != DefaultRootWindow(dpy)) + set_region_rectangle(dpy, border_window, cursor_window_pos.x, cursor_window_pos.y, cursor_window_size.x, cursor_window_size.y, rectangle_border_size); + else + set_region_rectangle(dpy, border_window, 0, 0, 0, 0, 0); + make_window_click_through(dpy, border_window); + XMapWindow(dpy, border_window); + + crosshair_cursor = XCreateFontCursor(dpy, XC_crosshair); + XGrabPointer(dpy, DefaultRootWindow(dpy), True, PointerMotionMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask, GrabModeAsync, GrabModeAsync, None, crosshair_cursor, CurrentTime); + XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, GrabModeAsync, CurrentTime); + XFlush(dpy); + + selected = false; + canceled = false; + selected_window = None; + return true; + } + + void WindowSelector::stop() { + if(!dpy) + return; + + XUngrabPointer(dpy, CurrentTime); + XUngrabKeyboard(dpy, CurrentTime); + + if(border_window_colormap) { + XFreeColormap(dpy, border_window_colormap); + border_window_colormap = 0; + } + + if(border_window) { + XDestroyWindow(dpy, border_window); + border_window = 0; + } + + if(crosshair_cursor) { + XFreeCursor(dpy, crosshair_cursor); + crosshair_cursor = None; + } + + XFlush(dpy); + XCloseDisplay(dpy); + dpy = nullptr; + } + + bool WindowSelector::is_started() const { + return dpy != nullptr; + } + + bool WindowSelector::failed() const { + return !dpy; + } + + bool WindowSelector::poll_events() { + if(!dpy || selected) + return false; + + XEvent xev; + while(XPending(dpy)) { + XNextEvent(dpy, &xev); + + if(xev.type == MotionNotify) { + const Window motion_window = xev.xmotion.subwindow; + mgl::vec2i motion_window_pos, motion_window_size; + get_window_geometry(dpy, motion_window, motion_window_pos, motion_window_size); + if(motion_window && motion_window != DefaultRootWindow(dpy)) + set_region_rectangle(dpy, border_window, motion_window_pos.x, motion_window_pos.y, motion_window_size.x, motion_window_size.y, rectangle_border_size); + else + set_region_rectangle(dpy, border_window, 0, 0, 0, 0, 0); + XFlush(dpy); + } else if(xev.type == ButtonRelease && xev.xbutton.button == Button1) { + selected_window = xev.xbutton.subwindow; + const Window clicked_window_real = window_get_target_window_child(dpy, selected_window); + if(clicked_window_real) + selected_window = clicked_window_real; + selected = true; + + stop(); + break; + } else if(xev.type == KeyRelease && XKeycodeToKeysym(dpy, xev.xkey.keycode, 0) == XK_Escape) { + canceled = true; + selected = false; + stop(); + break; + } + } + return true; + } + + bool WindowSelector::take_selection() { + const bool result = selected; + selected = false; + return result; + } + + bool WindowSelector::take_canceled() { + const bool result = canceled; + canceled = false; + return result; + } + + Window WindowSelector::get_selection() const { + return selected_window; + } +}
\ No newline at end of file |