diff options
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | Makefile | 3 | ||||
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | xseam.c | 189 |
4 files changed, 196 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f7b696 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +xseam +compile_commands.json diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c6efa75 --- /dev/null +++ b/Makefile @@ -0,0 +1,3 @@ +all: + cc -O2 -Wall -Wextra -o xseam xseam.c -lX11 -lXfixes -lXrandr -lXi + diff --git a/README.md b/README.md new file mode 100644 index 0000000..0e041fe --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# xseam +utility that maps cursor position between monitors of different resolution height. @@ -0,0 +1,189 @@ +#include <X11/Xlib.h> +#include <X11/extensions/Xfixes.h> +#include <X11/extensions/Xrandr.h> +#include <X11/extensions/XInput2.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define INSET 2 +typedef struct { int x, y, w, h; } RECT; + +typedef struct { + PointerBarrier barrier; + int seam_x; + RECT left; + RECT right; +} SEAM; + +Display *dpy; +Window root; +int xi_opcode; +int randr_event_base, randr_error_base; +int xfixes_event_base, xfixes_error_base; + +SEAM *seams = 0; +int nseams = 0; +int seam_cap = 0; + +int clamp( int v, int lo, int hi ) { + return v < lo ? lo : v > hi ? hi : v; +} + +int yranges_overlap( RECT *a, RECT *b ) { + return a->y < b->y + b->h && b->y < a->y + a->h; +} + +void destroy_seams() { + for( int i = 0; i < nseams; i++ ) { + if( seams[i].barrier ) + XFixesDestroyPointerBarrier( dpy, seams[i].barrier ); + } + nseams = 0; +} + +void add_seam( int seam_x, RECT L, RECT R ) { + int y_top = L.y < R.y ? L.y : R.y; + int y_bot = (L.y + L.h) > (R.y + R.h) ? (L.y + L.h) : (R.y + R.h); + + PointerBarrier b = XFixesCreatePointerBarrier( dpy, root, seam_x, y_top, seam_x, y_bot, 0, 0, 0 ); + if( !b ) + return; + + if( nseams == seam_cap ) { + seam_cap = seam_cap ? seam_cap * 2 : 8; + seams = realloc( seams, seam_cap * sizeof( *seams ) ); + } + seams[nseams].barrier = b; + seams[nseams].seam_x = seam_x; + seams[nseams].left = L; + seams[nseams].right = R; + nseams++; +} + +void rebuild() { + destroy_seams(); + + int n = 0; + XRRMonitorInfo *mon = XRRGetMonitors( dpy, root, 1, &n ); + if( !mon || n < 2 ) { + if( mon ) XRRFreeMonitors( mon ); + XFlush( dpy ); + fprintf( stderr, "xseam: %d monitor(s), nothing to map\n", n ); + return; + } + + RECT *r = calloc( n, sizeof( *r ) ); + for( int i = 0; i < n; i++ ) { + r[i].x = mon[i].x; r[i].y = mon[i].y; + r[i].w = mon[i].width; r[i].h = mon[i].height; + } + + for( int i = 0; i < n; i++ ) { + for( int j = 0; j < n; j++ ) { + if( i == j ) continue; + if( r[i].x + r[i].w == r[j].x && yranges_overlap( &r[i], &r[j] ) ) + add_seam( r[j].x, r[i], r[j] ); + } + } + + free( r ); + XRRFreeMonitors( mon ); + XFlush( dpy ); + fprintf( stderr, "xseam: %d monitors, %d seam(s) mapped\n", n, nseams); +} + +void handle_barrier_hit( XIBarrierEvent *ev ) { + SEAM *s = 0; + for( int i = 0; i < nseams; i++ ) { + if( seams[i].barrier == ev->barrier ) { s = &seams[i]; break; } + } + if( !s ) + return; + + double dx = ev->dx; + if( dx == 0.0 ) + return; + + int dest_x, dest_y; + if( dx < 0.0 ) { + double yr = ev->root_y; + double yl = s->left.y + (yr - s->right.y) * (double)s->left.h / (double)s->right.h; + dest_x = s->seam_x - INSET; + dest_y = clamp( (int)(yl + 0.5), s->left.y, s->left.y + s->left.h - 1 ); + } else { + double yl = ev->root_y; + double yr = s->right.y + (yl - s->left.y) * (double)s->right.h / (double)s->left.h; + dest_x = s->seam_x + INSET; + dest_y = clamp( (int)(yr + 0.5), s->right.y, s->right.y + s->right.h - 1 ); + } + + XWarpPointer( dpy, None, root, 0, 0, 0, 0, dest_x, dest_y ); + XIBarrierReleasePointer( dpy, ev->deviceid, ev->barrier, ev->eventid ); + XFlush( dpy ); +} + +int main() { + dpy = XOpenDisplay( 0 ); + if( !dpy ) { fprintf( stderr, "xseam: cannot open display\n" ); return 1; } + root = DefaultRootWindow( dpy ); + int ev, err; + + if( !XFixesQueryExtension( dpy, &xfixes_event_base, &xfixes_error_base ) ) { + fprintf( stderr, "xseam: XFixes missing\n" ); + return 1; + } + if( !XQueryExtension( dpy, "XInputExtension", &xi_opcode, &ev, &err ) ) { + fprintf( stderr, "xseam: XInput2 missing\n" ); + return 1; + } + if( !XRRQueryExtension( dpy, &randr_event_base, &randr_error_base ) ) { + fprintf( stderr, "xseam: RandR missing\n" ); + return 1; + } + + XRRSelectInput( dpy, root, RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask | RROutputChangeNotifyMask ); + + unsigned char m[XIMaskLen(XI_BarrierHit)]; + memset( m, 0, sizeof(m) ); + XISetMask( m, XI_BarrierHit ); + XIEventMask mask; + mask.deviceid = XIAllMasterDevices; + mask.mask_len = sizeof(m); + mask.mask = m; + XISelectEvents( dpy, root, &mask, 1 ); + + rebuild(); + XSync( dpy, 0 ); + + for( ;; ) { + XEvent ev; + XNextEvent( dpy, &ev ); + + if( ev.type == GenericEvent && ev.xcookie.extension == xi_opcode ) { + if( XGetEventData( dpy, &ev.xcookie ) ) { + if( ev.xcookie.evtype == XI_BarrierHit ) + handle_barrier_hit( (XIBarrierEvent * )ev.xcookie.data ); + XFreeEventData( dpy, &ev.xcookie ); + } + } + else if( ev.type == randr_event_base + RRScreenChangeNotify ) { + XRRUpdateConfiguration( &ev ); + while( XPending( dpy ) ) { + XEvent pk; + XPeekEvent( dpy, &pk ); + if( pk.type == randr_event_base + RRScreenChangeNotify + || pk.type == randr_event_base + RRNotify ) { + XNextEvent( dpy, &pk ); + if( pk.type == randr_event_base + RRScreenChangeNotify ) + XRRUpdateConfiguration( &pk ); + } else break; + } + rebuild(); + } + else if( ev.type == randr_event_base + RRNotify ) { + rebuild(); + } + } +} |
