diff --git a/doc/fvwm3_manpage_source.adoc b/doc/fvwm3_manpage_source.adoc index c31a6e4dd..1faa7534d 100644 --- a/doc/fvwm3_manpage_source.adoc +++ b/doc/fvwm3_manpage_source.adoc @@ -4329,6 +4329,50 @@ 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. ++ +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..112e5d715 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,83 @@ void CMD_BusyCursor(F_CMD_ARGS) return; } + +void CMD_CursorBarrier(F_CMD_ARGS) +{ +#if !defined(HAVE_XFIXES) + return; +#endif + + 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; + + /* 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..b58c2c401 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 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',