From 63b2b6cbc34b9e34208f3bff96686b9bd3f54521 Mon Sep 17 00:00:00 2001
From: dec05eba <dec05eba@protonmail.com>
Date: Sat, 15 Mar 2025 00:39:37 +0100
Subject: Add region capture option

---
 src/WindowUtils.cpp | 211 ++++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 188 insertions(+), 23 deletions(-)

(limited to 'src/WindowUtils.cpp')

diff --git a/src/WindowUtils.cpp b/src/WindowUtils.cpp
index d588374..49fd65b 100644
--- a/src/WindowUtils.cpp
+++ b/src/WindowUtils.cpp
@@ -1,8 +1,10 @@
 #include "../include/WindowUtils.hpp"
 
-#include <X11/Xlib.h>
 #include <X11/Xatom.h>
 #include <X11/Xutil.h>
+#include <X11/extensions/XInput2.h>
+#include <X11/extensions/Xfixes.h>
+#include <X11/extensions/shapeconst.h>
 
 #include <mglpp/system/Utf8.hpp>
 
@@ -301,6 +303,21 @@ namespace gsr {
         return get_window_name_at_position(dpy, cursor_position, ignore_window);
     }
 
+    void set_window_size_not_resizable(Display *dpy, Window window, int width, int height) {
+        XSizeHints *size_hints = XAllocSizeHints();
+        if(size_hints) {
+            size_hints->width = width;
+            size_hints->height = height;
+            size_hints->min_width = width;
+            size_hints->min_height = height;
+            size_hints->max_width = width;
+            size_hints->max_height = height;
+            size_hints->flags = PSize | PMinSize | PMaxSize;
+            XSetWMNormalHints(dpy, window, size_hints);
+            XFree(size_hints);
+        }
+    }
+
     typedef struct {
         unsigned long flags;
         unsigned long functions;
@@ -348,17 +365,7 @@ namespace gsr {
         XChangeProperty(display, window, net_wm_window_opacity, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&opacity, 1L);
 
         window_set_decorations_visible(display, window, false);
-
-        XSizeHints *size_hints = XAllocSizeHints();
-        size_hints->width = size;
-        size_hints->height = size;
-        size_hints->min_width = size;
-        size_hints->min_height = size;
-        size_hints->max_width = size;
-        size_hints->max_height = size;
-        size_hints->flags = PSize | PMinSize | PMaxSize;
-        XSetWMNormalHints(display, window, size_hints);
-        XFree(size_hints);
+        set_window_size_not_resizable(display, window, size, size);
 
         XMapWindow(display, window);
         XFlush(display);
@@ -412,17 +419,7 @@ namespace gsr {
         XChangeProperty(display, window, net_wm_window_opacity, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&opacity, 1L);
 
         window_set_decorations_visible(display, window, false);
-
-        XSizeHints *size_hints = XAllocSizeHints();
-        size_hints->width = size;
-        size_hints->height = size;
-        size_hints->min_width = size;
-        size_hints->min_height = size;
-        size_hints->max_width = size;
-        size_hints->max_height = size;
-        size_hints->flags = PSize | PMinSize | PMaxSize;
-        XSetWMNormalHints(display, window, size_hints);
-        XFree(size_hints);
+        set_window_size_not_resizable(display, window, size, size);
 
         XMapWindow(display, window);
         XFlush(display);
@@ -531,4 +528,172 @@ namespace gsr {
         mgl_for_each_active_monitor_output(dpy, get_monitors_callback, &monitors);
         return monitors;
     }
+
+    static bool device_is_mouse(const XIDeviceInfo *dev) {
+        for(int i = 0; i < dev->num_classes; ++i) {
+            if(dev->classes[i]->type == XIMasterPointer || dev->classes[i]->type == XISlavePointer)
+                return true;
+        }
+        return false;
+    }
+
+    static void xi_grab_all_mouse_devices(Display *dpy, bool grab) {
+        if(!dpy)
+            return;
+
+        int num_devices = 0;
+        XIDeviceInfo *info = XIQueryDevice(dpy, XIAllDevices, &num_devices);
+        if(!info)
+            return;
+
+        unsigned char mask[XIMaskLen(XI_LASTEVENT)];
+        memset(mask, 0, sizeof(mask));
+        XISetMask(mask, XI_Motion);
+        //XISetMask(mask, XI_RawMotion);
+        XISetMask(mask, XI_ButtonPress);
+        XISetMask(mask, XI_ButtonRelease);
+        XISetMask(mask, XI_KeyPress);
+        XISetMask(mask, XI_KeyRelease);
+
+        for (int i = 0; i < num_devices; ++i) {
+            const XIDeviceInfo *dev = &info[i];
+            if(!device_is_mouse(dev))
+                continue;
+
+            XIEventMask xi_masks;
+            xi_masks.deviceid = dev->deviceid;
+            xi_masks.mask_len = sizeof(mask);
+            xi_masks.mask = mask;
+            if(grab)
+                XIGrabDevice(dpy, dev->deviceid, DefaultRootWindow(dpy), CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, XIOwnerEvents, &xi_masks);
+            else
+                XIUngrabDevice(dpy, dev->deviceid, CurrentTime);
+        }
+
+        XFlush(dpy);
+        XIFreeDeviceInfo(info);
+    }
+
+    void xi_grab_all_mouse_devices(Display *dpy) {
+        xi_grab_all_mouse_devices(dpy, true);
+    }
+
+    void xi_ungrab_all_mouse_devices(Display *dpy) {
+        xi_grab_all_mouse_devices(dpy, false);
+    }
+
+    void xi_warp_all_mouse_devices(Display *dpy, mgl::vec2i position) {
+        if(!dpy)
+            return;
+
+        int num_devices = 0;
+        XIDeviceInfo *info = XIQueryDevice(dpy, XIAllDevices, &num_devices);
+        if(!info)
+            return;
+
+        for (int i = 0; i < num_devices; ++i) {
+            const XIDeviceInfo *dev = &info[i];
+            if(!device_is_mouse(dev))
+                continue;
+
+            XIWarpPointer(dpy, dev->deviceid, DefaultRootWindow(dpy), DefaultRootWindow(dpy), 0, 0, 0, 0, position.x, position.y);
+        }
+
+        XFlush(dpy);
+        XIFreeDeviceInfo(info);
+    }
+
+    void window_set_fullscreen(Display *dpy, Window window, bool fullscreen) {
+        const Atom net_wm_state_atom = XInternAtom(dpy, "_NET_WM_STATE", False);
+        const Atom net_wm_state_fullscreen_atom = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
+
+        XEvent xev;
+        xev.type = ClientMessage;
+        xev.xclient.window = window;
+        xev.xclient.message_type = net_wm_state_atom;
+        xev.xclient.format = 32;
+        xev.xclient.data.l[0] = fullscreen ? 1 : 0;
+        xev.xclient.data.l[1] = net_wm_state_fullscreen_atom;
+        xev.xclient.data.l[2] = 0;
+        xev.xclient.data.l[3] = 1;
+        xev.xclient.data.l[4] = 0;
+
+        if(!XSendEvent(dpy, DefaultRootWindow(dpy), 0, SubstructureRedirectMask | SubstructureNotifyMask, &xev)) {
+            fprintf(stderr, "mgl warning: failed to change window fullscreen state\n");
+            return;
+        }
+
+        XFlush(dpy);
+    }
+
+    bool window_is_fullscreen(Display *display, Window window) {
+        const Atom wm_state_atom = XInternAtom(display, "_NET_WM_STATE", False);
+        const Atom wm_state_fullscreen_atom = XInternAtom(display, "_NET_WM_STATE_FULLSCREEN", False);
+
+        Atom type = None;
+        int format = 0;
+        unsigned long num_items = 0;
+        unsigned long bytes_after = 0;
+        unsigned char *properties = nullptr;
+        if(XGetWindowProperty(display, window, wm_state_atom, 0, 1024, False, XA_ATOM, &type, &format, &num_items, &bytes_after, &properties) < Success) {
+            fprintf(stderr, "Failed to get window wm state property\n");
+            return false;
+        }
+
+        if(!properties)
+            return false;
+
+        bool is_fullscreen = false;
+        Atom *atoms = (Atom*)properties;
+        for(unsigned long i = 0; i < num_items; ++i) {
+            if(atoms[i] == wm_state_fullscreen_atom) {
+                is_fullscreen = true;
+                break;
+            }
+        }
+
+        XFree(properties);
+        return is_fullscreen;
+    }
+
+    #define _NET_WM_STATE_REMOVE  0
+    #define _NET_WM_STATE_ADD     1
+    #define _NET_WM_STATE_TOGGLE  2
+
+    bool set_window_wm_state(Display *dpy, Window window, Atom atom) {
+        const Atom net_wm_state_atom = XInternAtom(dpy, "_NET_WM_STATE", False);
+
+        XClientMessageEvent xclient;
+        memset(&xclient, 0, sizeof(xclient));
+
+        xclient.type = ClientMessage;
+        xclient.window = window;
+        xclient.message_type = net_wm_state_atom;
+        xclient.format = 32;
+        xclient.data.l[0] = _NET_WM_STATE_ADD;
+        xclient.data.l[1] = atom;
+        xclient.data.l[2] = 0;
+        xclient.data.l[3] = 0;
+        xclient.data.l[4] = 0;
+
+        XSendEvent(dpy, DefaultRootWindow(dpy), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent*)&xclient);
+        XFlush(dpy);
+        return true;
+    }
+
+    void make_window_click_through(Display *display, Window window) {
+        XRectangle rect;
+        memset(&rect, 0, sizeof(rect));
+        XserverRegion region = XFixesCreateRegion(display, &rect, 1);
+        XFixesSetWindowShapeRegion(display, window, ShapeInput, 0, 0, region);
+        XFixesDestroyRegion(display, region);
+    }
+
+    bool make_window_sticky(Display *dpy, Window window) {
+        return set_window_wm_state(dpy, window, XInternAtom(dpy, "_NET_WM_STATE_STICKY", False));
+    }
+
+    bool hide_window_from_taskbar(Display *dpy, Window window) {
+        return set_window_wm_state(dpy, window, XInternAtom(dpy, "_NET_WM_STATE_SKIP_TASKBAR", False));
+    }
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2