From ef874921b5d8317c41af93d4ce1bbed67c808e82 Mon Sep 17 00:00:00 2001 From: aura Date: Tue, 30 Jun 2026 22:39:41 +0200 Subject: push code --- xseam.c | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 xseam.c (limited to 'xseam.c') diff --git a/xseam.c b/xseam.c new file mode 100644 index 0000000..b672849 --- /dev/null +++ b/xseam.c @@ -0,0 +1,189 @@ +#include +#include +#include +#include + +#include +#include +#include + +#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(); + } + } +} -- cgit v1.2.3