From 56b8a7455edef2a89114c759d67ca0ac0268775c Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Tue, 29 Oct 2024 23:37:41 -0600 Subject: [PATCH] Add XFixes support and CusorBarrier command. CursorBarrier is a command to create a PointerBarrier using the XFixes extension. This barrier can be used to confine mouse movement to any rectangle on the desktop. This first adds XFixes as an optional dependency, and if XFixes is included, the CursorBarrier option can be used to create PointerBarriers. CursorBarrier can be use to both create and destroy barriers. Current `MAX_BARRIERS` is set at 16, so only up to 16 barriers can be created at a single time. The use of CursorBarrier is documented in the manual page. A default key binding ctrl+shift+D is setup to destroy all barriers to give users a way to escape any jails their cursor is stuck in. --- doc/fvwm3_manpage_source.adoc | 47 ++++++++++ fvwm/commands.h | 2 + fvwm/cursor.c | 159 ++++++++++++++++++++++++++++++++++ fvwm/functable.c | 3 + fvwm/fvwm3.c | 5 ++ meson.build | 7 ++ meson.options | 6 ++ 7 files changed, 229 insertions(+) diff --git a/doc/fvwm3_manpage_source.adoc b/doc/fvwm3_manpage_source.adoc index c31a6e4dd..d4e7c6406 100644 --- a/doc/fvwm3_manpage_source.adoc +++ b/doc/fvwm3_manpage_source.adoc @@ -4329,6 +4329,53 @@ MoveToDesk 0 n === Focus & Mouse Movement +*CursorBarrier* [destroy] [options] [_left_ _top_ _right_ _bottom_]:: + A cursor barrier is a box that the cursor cannot be moved outside of + (unless warped with _CursorMove_ or _WarpToWindow_). The _left_, _top_, + _right_, and _bottom_ values give the percent distance the box is placed + from each of the corresponding edges of the desktop. If the values end + with a _p_, they are interpreted as pixel amounts instead. If the option + _coords_ is given, the values are interpreted as the left/top and + right/bottom coordinates of the box's corners. If the option + _screen RandRname_ is given, the positions are computed relative to the + specified monitor. Use _screen c_ to place the barrier on the current + monitor. ++ +Multiple barriers can be created by calling _CursorBarrier_ multiple times. +This allows creating multiple regions to confine the cursor to (such as each +monitor in a multi-monitor setup), then use _CursorMove_ or _WarpToWindow_ +to move the cursor between the barriers. If the command _destroy_ is included, +all barriers are destroyed allowing free movement of the mouse again. If +_destroy_ is followed by an integer _N_, only that barrier is destroyed +(barriers are numbered in the order they are created starting at 0). If the +integer is negative, this counts backwards, such as "-1" is the most recent +created barrier, "-2" is the second most recent, and so on. When a barrier +is destroyed, this renumbers all barriers after it. In practice this is +best used with _destroy -1_ to destroy the most recent barrier without +affecting any previously created barriers. ++ +By default the key binding ctrl+shift+D will destroy all cursor barriers +by calling _CursorBarrier destroy_. ++ +Here are some examples: ++ +.... +# A barrier 15% from left/right and 10% from top/bottom. +CursorBarrier 15 10 15 10 + +# A barrier to confine the mouse to the current monitor. +CursorBarrier screen c + +# A barrier to confine the mouse to a selected window +Pick CursorBarrier coords $[w.x]p $[w.y]p \ + $[math.+.$[w.x],$[w.width]]p $[math.+.$[w.y],$[w.height]]p + +# Destroy all barriers +CursorBarrier destroy +.... ++ +_CursorBarrier_ only works if fvwm is complied with the XFixes extension. + *CursorMove* _horizontal_[p] _vertical_[p]:: Moves the mouse pointer by _horizontal_ pages in the X direction and _vertical_ pages in the Y direction. Either or both entries may be diff --git a/fvwm/commands.h b/fvwm/commands.h index b02a7444d..a1ef41471 100644 --- a/fvwm/commands.h +++ b/fvwm/commands.h @@ -40,6 +40,7 @@ enum F_CONFIG_LIST, F_COPY_MENU_STYLE, F_CURRENT, + F_CURSOR_BARRIER, F_CURSOR_STYLE, F_DESCHEDULE, F_DESKTOP_CONFIGURATION, @@ -225,6 +226,7 @@ void CMD_ColormapFocus(F_CMD_ARGS); void CMD_Colorset(F_CMD_ARGS); void CMD_CopyMenuStyle(F_CMD_ARGS); void CMD_Current(F_CMD_ARGS); +void CMD_CursorBarrier(F_CMD_ARGS); void CMD_CursorMove(F_CMD_ARGS); void CMD_CursorStyle(F_CMD_ARGS); void CMD_DefaultFont(F_CMD_ARGS); diff --git a/fvwm/cursor.c b/fvwm/cursor.c index 3fa96fad4..4f70c2b86 100644 --- a/fvwm/cursor.c +++ b/fvwm/cursor.c @@ -38,6 +38,81 @@ #include "cursor.h" #include "menus.h" +#ifdef HAVE_XFIXES +#include +#define MAX_BARRIERS 16 +PointerBarrier barriers_l[MAX_BARRIERS]; +PointerBarrier barriers_r[MAX_BARRIERS]; +PointerBarrier barriers_t[MAX_BARRIERS]; +PointerBarrier barriers_b[MAX_BARRIERS]; +int num_barriers = 0; + +void create_barrier(int x1, int y1, int x2, int y2) +{ + if (num_barriers >= MAX_BARRIERS) { + fvwm_debug(__func__, "Too many barriers. Aborting."); + return; + } + + barriers_l[num_barriers] = XFixesCreatePointerBarrier( + dpy, Scr.Root, x1, y1, x1, y2, 0, 0, NULL); + barriers_r[num_barriers] = XFixesCreatePointerBarrier( + dpy, Scr.Root, x2, y1, x2, y2, 0, 0, NULL); + barriers_t[num_barriers] = XFixesCreatePointerBarrier( + dpy, Scr.Root, x1, y1, x2, y1, 0, 0, NULL); + barriers_b[num_barriers] = XFixesCreatePointerBarrier( + dpy, Scr.Root, x1, y2, x2, y2, 0, 0, NULL); + num_barriers++; +} + +void destroy_barrier(int n) +{ + XFixesDestroyPointerBarrier(dpy, barriers_l[n]); + XFixesDestroyPointerBarrier(dpy, barriers_r[n]); + XFixesDestroyPointerBarrier(dpy, barriers_t[n]); + XFixesDestroyPointerBarrier(dpy, barriers_b[n]); +} + +void destroy_all_barriers(void) +{ + int i; + + for (i = 0; i < num_barriers; i++) { + destroy_barrier(i); + } + + num_barriers = 0; +} + +void destroy_barrier_n(int n) +{ + int i; + + if (num_barriers == 0) + return; + + if (n < 0) + n += num_barriers; + if (n < 0 || n >= num_barriers) { + fvwm_debug(__func__, "Invalid barrier number: %d", n); + return; + } + + destroy_barrier(n); + num_barriers--; + for (i = n; i < num_barriers; i++) { + barriers_l[i] = barriers_l[i+1]; + barriers_r[i] = barriers_r[i+1]; + barriers_t[i] = barriers_t[i+1]; + barriers_b[i] = barriers_b[i+1]; + } +} + +#else +#define create_barrier(a, b, c, d) +#define destroy_all_barriers() +#define destroy_barrier_n(a) +#endif /* ---------------------------- local definitions -------------------------- */ /* ---------------------------- local macros ------------------------------- */ @@ -544,3 +619,87 @@ void CMD_BusyCursor(F_CMD_ARGS) return; } + +void CMD_CursorBarrier(F_CMD_ARGS) +{ + int val[4] = {0, 0, 0, 0}; + int x1, y1, x2, y2; + rectangle r = + {0, 0, monitor_get_all_widths(), monitor_get_all_heights()}; + bool is_coords = false; + char *option; + struct monitor *m = NULL; + +#if !defined(HAVE_XFIXES) + SUPPRESS_UNUSED_VAR_WARNING(x1); + SUPPRESS_UNUSED_VAR_WARNING(y1); + SUPPRESS_UNUSED_VAR_WARNING(x2); + SUPPRESS_UNUSED_VAR_WARNING(y2); + return; +#endif + + /* Note, if option is matched, the matched option must be skipped + * with PeekToken(action, &action) to stop infinite loop. + */ + while ((option = PeekToken(action, NULL)) != NULL) { + if (StrEquals(option, "screen")) { + option = PeekToken(action, &action); /* Skip */ + option = PeekToken(action, &action); + if ((m = monitor_resolve_name(option)) == NULL) { + fvwm_debug(__func__, "Invalid screen: %s", + option); + return; + } + r.x = m->si->x; + r.y = m->si->y; + r.width = m->si->w; + r.height = m->si->h; + } else if (StrEquals(option, "destroy")) { + int n; + + option = PeekToken(action, &action); /* Skip */ + option = PeekToken(action, &action); + if (option != NULL && sscanf(option, "%d", &n) == 1) { + destroy_barrier_n(n); + } else { + destroy_all_barriers(); + } + XSync(dpy, False); + return; + } else if (StrEquals(option, "coords")) { + option = PeekToken(action, &action); /* Skip */ + is_coords = true; + } else { + int i; + int unit[4] = {r.width, r.height, r.width, r.height}; + + for (i = 0; i < 4; i++) { + option = PeekToken(action, &action); + if (GetOnePercentArgument( + option, &val[i], &unit[i]) == 0 + || val[i] < 0) { + fvwm_debug(__func__, + "Invalid coordinates."); + return; + } + val[i] = val[i] * unit[i] / 100; + } + break; + } + } + + if (is_coords) { + x1 = r.x + val[0]; + y1 = r.y + val[1]; + x2 = r.x + val[2]; + y2 = r.y + val[3]; + } else { + x1 = r.x + val[0]; + y1 = r.y + val[1]; + x2 = r.x + r.width - val[2]; + y2 = r.y + r.height - val[3]; + } + + create_barrier(x1, y1, x2, y2); + XSync(dpy, False); +} diff --git a/fvwm/functable.c b/fvwm/functable.c index ae018e603..69defde31 100644 --- a/fvwm/functable.c +++ b/fvwm/functable.c @@ -146,6 +146,9 @@ const func_t func_table[] = CMD_ENT("current", CMD_Current, F_CURRENT, 0, 0), /* - Operate on the currently focused window */ + CMD_ENT("cursorbarrier", CMD_CursorBarrier, F_CURSOR_BARRIER, 0, 0), + /* - Manage barriers the cursor cannot moved out of unless warped */ + CMD_ENT("cursormove", CMD_CursorMove, F_MOVECURSOR, 0, 0), /* - Move the cursor pointer non interactively */ diff --git a/fvwm/fvwm3.c b/fvwm/fvwm3.c index 83f45534d..15a99da31 100644 --- a/fvwm/fvwm3.c +++ b/fvwm/fvwm3.c @@ -1250,6 +1250,9 @@ static void setVersionInfo(void) #ifdef HAVE_XFT strlcat(support_str, " XFT,", sizeof(support_str)); #endif +#ifdef HAVE_XFIXES + strlcat(support_str, " XFixes,", sizeof(support_str)); +#endif #ifdef HAVE_NLS strlcat(support_str, " NLS,", sizeof(support_str)); #endif @@ -1320,6 +1323,8 @@ static void SetRCDefaults(void) { "Key Up M A MenuMoveCursor -1", "", "" }, { "Key Down M A MenuMoveCursor 1", "", "" }, { "Mouse 1 MI A MenuSelectItem", "", "" }, + /* Default escape from CusorBarriers */ + { "Key D A CS CursorBarrier destroy", "", "" }, /* don't add anything below */ { RC_DEFAULTS_COMPLETE, "", "" }, { "Read "FVWM_DATADIR"/ConfigFvwmDefaults", "", "" }, diff --git a/meson.build b/meson.build index 68e0e22f4..df88939b1 100644 --- a/meson.build +++ b/meson.build @@ -348,6 +348,12 @@ if xcursor.found() conf.set10('HAVE_XCURSOR', true) endif +xfixes = dependency('xfixes', required: get_option('xfixes')) +if xfixes.found() + all_found_deps += xfixes + conf.set10('HAVE_XFIXES', true) +endif + xkbcommon = dependency('xkbcommon', required: get_option('xkbcommon')) if xkbcommon.found() all_found_deps += xkbcommon @@ -568,6 +574,7 @@ featurevals = { 'Shaped Windows': xext.found() ? xext : false, 'SVG support': librsvg.found() ? librsvg : false, 'Xcursor': xcursor.found() ? xcursor : false, + 'XFixes': xfixes.found() ? xfixes : false, 'xkbcommon': xkbcommon.found() ? xkbcommon : false, 'XPM support': xpm.found() ? xpm : false, 'XRender': xrender.found() ? xrender : false, diff --git a/meson.options b/meson.options index ed9b82852..838e4d8d0 100644 --- a/meson.options +++ b/meson.options @@ -88,6 +88,12 @@ option( value: 'auto', description: 'Enable Xcursor support', ) +option( + 'xfixes', + type: 'feature', + value: 'auto', + description: 'Enable XFixes support', +) option( 'xkbcommon', type: 'feature',