From 802cbc0d75932802ca12056792f6fae757d9a66c Mon Sep 17 00:00:00 2001 From: David Maciejak Date: Sun, 26 Mar 2023 14:35:13 +0800 Subject: [PATCH] Hot corners feature core Add mouse pointer position detection to trigger the corner actions. Screen corners can be assigned an external command to be executed when the mouse pointer is entering those areas. In WPrefs, "Hot Corner Shortcut Preferences" can be used for configuration or by manually adding a "HotCorners" key and value to "YES" in the ~/GNUstep/Defaults/WindowMaker file. Actions are specified by the "HotCornerActions" and are defined as a four entries list ("top left action", "top right action", "bottom left action", "bottom right action"). A screen corner area is a cube shape defined by the "HotCornerEdge" which is a number of pixels from 2 (by default) to 10. To lower the risk of triggering that feature accidentally a "HotCornerDelay" key can be used which is the time before the action is triggered while the pointer is in one of the screen corner. Default value is 250 ms. Hot Corners feature is disabled by default. --- src/WindowMaker.h | 5 +++ src/defaults.c | 46 +++++++++++++++++++++ src/event.c | 101 ++++++++++++++++++++++++++++++++++++++++------ src/screen.h | 3 ++ 4 files changed, 143 insertions(+), 12 deletions(-) diff --git a/src/WindowMaker.h b/src/WindowMaker.h index dffa9167..14392c4d 100644 --- a/src/WindowMaker.h +++ b/src/WindowMaker.h @@ -474,6 +474,11 @@ extern struct WPreferences { char show_clip_title; + char hot_corners; /* let corners execute actions */ + int hot_corner_delay; /* Delay after which the hot corner is triggered */ + int hot_corner_edge; /* Hot corner edge size */ + char *hot_corner_actions[4]; /* Action of each corner */ + struct { #ifdef USE_ICCCM_WMREPLACE unsigned int replace:1; /* replace existing window manager */ diff --git a/src/defaults.c b/src/defaults.c index f5cbf46f..3b848b15 100644 --- a/src/defaults.c +++ b/src/defaults.c @@ -151,6 +151,7 @@ static WDECallbackUpdate setSwPOptions; static WDECallbackUpdate updateUsableArea; static WDECallbackUpdate setModifierKeyLabels; +static WDECallbackUpdate setHotCornerActions; static WDECallbackConvert getCursor; static WDECallbackUpdate setCursor; @@ -525,6 +526,14 @@ WDefaultEntry optionList[] = { {"KeepDockOnPrimaryHead", "NO", NULL, &wPreferences.keep_dock_on_primary_head, getBool, updateDock, NULL, NULL}, + {"HotCorners", "NO", NULL, + &wPreferences.hot_corners, getBool, NULL, NULL, NULL}, + {"HotCornerDelay", "250", (void *)&wPreferences.hot_corner_delay, + &wPreferences.hot_corner_delay, getInt, NULL, NULL, NULL}, + {"HotCornerEdge", "2", (void *)&wPreferences.hot_corner_edge, + &wPreferences.hot_corner_edge, getInt, NULL, NULL, NULL}, + {"HotCornerActions", "(\"None\", \"None\", \"None\", \"None\")", &wPreferences, + NULL, getPropList, setHotCornerActions, NULL, NULL}, /* style options */ @@ -3461,6 +3470,43 @@ static int setModifierKeyLabels(WScreen * scr, WDefaultEntry * entry, void *tdat return 0; } +static int setHotCornerActions(WScreen * scr, WDefaultEntry * entry, void *tdata, void *foo) +{ + WMPropList *array = tdata; + int i; + struct WPreferences *prefs = foo; + + if (!WMIsPLArray(array) || WMGetPropListItemCount(array) != 4) { + wwarning(_("Value for option \"%s\" must be an array of 4 strings"), entry->key); + WMReleasePropList(array); + return 0; + } + + DestroyWindowMenu(scr); + + for (i = 0; i < 4; i++) { + if (prefs->hot_corner_actions[i]) + wfree(prefs->hot_corner_actions[i]); + + if (WMIsPLString(WMGetFromPLArray(array, i))) { + const char *val; + val = WMGetFromPLString(WMGetFromPLArray(array, i)); + if (strcasecmp(val, "NONE") != 0) + prefs->hot_corner_actions[i] = wstrdup(val); + else + prefs->hot_corner_actions[i] = NULL; + } else { + wwarning(_("Invalid argument for option \"%s\" item %d"), entry->key, i); + prefs->hot_corner_actions[i] = NULL; + } + } + + WMReleasePropList(array); + + return 0; +} + + static int setDoubleClick(WScreen *scr, WDefaultEntry *entry, void *tdata, void *foo) { int *value = tdata; diff --git a/src/event.c b/src/event.c index 69f284ac..c843b0c0 100644 --- a/src/event.c +++ b/src/event.c @@ -1928,27 +1928,104 @@ static void handleKeyPress(XEvent * event) } } -static void handleMotionNotify(XEvent * event) -{ - WScreen *scr = wScreenForRootWindow(event->xmotion.root); +#define CORNER_NONE 0 +#define CORNER_TOPLEFT 1 +#define CORNER_TOPRIGHT 2 +#define CORNER_BOTTOMLEFT 3 +#define CORNER_BOTTOMRIGHT 4 - if (wPreferences.scrollable_menus) { +static int get_corner(WMRect rect, WMPoint p) +{ + if (p.x <= (rect.pos.x + wPreferences.hot_corner_edge) && p.y <= (rect.pos.y + wPreferences.hot_corner_edge)) + return CORNER_TOPLEFT; + if (p.x >= (rect.pos.x + rect.size.width - wPreferences.hot_corner_edge) && p.y <= (rect.pos.y + wPreferences.hot_corner_edge)) + return CORNER_TOPRIGHT; + if (p.x <= (rect.pos.x + wPreferences.hot_corner_edge) && p.y >= (rect.pos.y + rect.size.height - wPreferences.hot_corner_edge)) + return CORNER_BOTTOMLEFT; + if (p.x >= (rect.pos.x + rect.size.width - wPreferences.hot_corner_edge) && p.y >= (rect.pos.y + rect.size.height - wPreferences.hot_corner_edge)) + return CORNER_BOTTOMRIGHT; + return CORNER_NONE; +} + +static void hotCornerDelay(void *data) +{ + WScreen *scr = (WScreen *) data; + if (scr->flags.in_hot_corner && wPreferences.hot_corner_actions[scr->flags.in_hot_corner - 1]) + ExecuteShellCommand(scr, wPreferences.hot_corner_actions[scr->flags.in_hot_corner - 1]); + WMDeleteTimerHandler(scr->hot_corner_timer); + scr->hot_corner_timer = NULL; +} + +static void handleMotionNotify(XEvent *event) +{ + if (wPreferences.scrollable_menus || wPreferences.hot_corners) { + WScreen *scr = wScreenForRootWindow(event->xmotion.root); WMPoint p = wmkpoint(event->xmotion.x_root, event->xmotion.y_root); WMRect rect = wGetRectForHead(scr, wGetHeadForPoint(scr, p)); - if (scr->flags.jump_back_pending || - p.x <= (rect.pos.x + 1) || - p.x >= (rect.pos.x + rect.size.width - 2) || - p.y <= (rect.pos.y + 1) || p.y >= (rect.pos.y + rect.size.height - 2)) { - WMenu *menu; + if (wPreferences.hot_corners) { + if (!scr->flags.in_hot_corner) { + scr->flags.in_hot_corner = get_corner(rect, p); + if (scr->flags.in_hot_corner && !scr->hot_corner_timer) + scr->hot_corner_timer = WMAddTimerHandler(wPreferences.hot_corner_delay, hotCornerDelay, scr); + } else { + int out_hot_corner = 0; - menu = wMenuUnderPointer(scr); - if (menu != NULL) - wMenuScroll(menu); + switch (scr->flags.in_hot_corner) { + case CORNER_TOPLEFT: + if ((p.x > (rect.pos.x + wPreferences.hot_corner_edge)) || + (p.y > (rect.pos.y + wPreferences.hot_corner_edge))) + out_hot_corner = 1; + break; + case CORNER_TOPRIGHT: + if ((p.x < (rect.pos.x + rect.size.width - wPreferences.hot_corner_edge)) || + (p.y > (rect.pos.y + wPreferences.hot_corner_edge))) + out_hot_corner = 1; + break; + case CORNER_BOTTOMLEFT: + if ((p.x > (rect.pos.x + wPreferences.hot_corner_edge)) || + (p.y < (rect.pos.y + rect.size.height - wPreferences.hot_corner_edge))) + out_hot_corner = 1; + break; + case CORNER_BOTTOMRIGHT: + if ((p.x < (rect.pos.x + rect.size.width - wPreferences.hot_corner_edge)) || + (p.y < (rect.pos.y + rect.size.height - wPreferences.hot_corner_edge))) + out_hot_corner = 1; + break; + + } + if (out_hot_corner) { + scr->flags.in_hot_corner = CORNER_NONE; + if (scr->hot_corner_timer) { + WMDeleteTimerHandler(scr->hot_corner_timer); + scr->hot_corner_timer = NULL; + } + } + + } + } + + if (wPreferences.scrollable_menus) { + if (scr->flags.jump_back_pending || + p.x <= (rect.pos.x + 1) || + p.x >= (rect.pos.x + rect.size.width - 2) || + p.y <= (rect.pos.y + 1) || p.y >= (rect.pos.y + rect.size.height - 2)) { + WMenu *menu; + + menu = wMenuUnderPointer(scr); + if (menu != NULL) + wMenuScroll(menu); + } } } } +#undef CORNER_NONE +#undef CORNER_TOPLEFT +#undef CORNER_TOPRIGHT +#undef CORNER_BOTTOMLEFT +#undef CORNER_BOTTOMRIGHT + static void handleVisibilityNotify(XEvent * event) { WWindow *wwin; diff --git a/src/screen.h b/src/screen.h index 6b2a5603..20e14a8c 100644 --- a/src/screen.h +++ b/src/screen.h @@ -295,6 +295,8 @@ typedef struct _WScreen { WMHandlerID *autoRaiseTimer; Window autoRaiseWindow; /* window that is scheduled to be * raised */ + /* for hot-corners delay */ + WMHandlerID *hot_corner_timer; /* for window shortcuts */ WMArray *shortcutWindows[MAX_WINDOW_SHORTCUTS]; @@ -323,6 +325,7 @@ typedef struct _WScreen { unsigned int doing_alt_tab:1; unsigned int jump_back_pending:1; unsigned int ignore_focus_events:1; + unsigned int in_hot_corner:3; } flags; } WScreen;