#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(); } } }