david.m...@gmail.com unread, Apr 3, 2026, 5:18:09 AM (3 days ago) Apr 3
Sign in to reply to author
Sign in to forward
You do not have permission to delete messages in this group
Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message
to Window Maker Development
This patch is adding directional window focus (like in openbox), where the focus can be passed to the current window neighboors with a shortcut key. Comparisons are performed using window centres. New options are FocusWindowLeftKey, FocusWindowRightKey, FocusWindowUpKey, FocusWindowDownKey. Other WM are setting those to SUPER+LEFT/RIGHT/UP/DOWN keys but currently those are not set by default. CirculateRaise option which is described in WPrefs as 'Raise window when switching focus with keyboard' is also used to raise/not raise windows that are fully covered by other windows. --- WPrefs.app/KeyboardShortcuts.c | 5 ++ src/actions.c | 94 ++++++++++++++++++++++++++++++++++ src/actions.h | 1 + src/defaults.c | 9 ++++ src/event.c | 16 ++++++ src/keybind.h | 4 ++ src/window.c | 29 +++++++++++ src/window.h | 1 + 8 files changed, 159 insertions(+) diff --git a/WPrefs.app/KeyboardShortcuts.c b/WPrefs.app/KeyboardShortcuts.c index f6750c1b..6e327729 100644 --- a/WPrefs.app/KeyboardShortcuts.c +++ b/WPrefs.app/KeyboardShortcuts.c @@ -103,6 +103,11 @@ static struct keyOption { { "SelectKey", N_("Select active window") }, { "FocusNextKey", N_("Focus next window") }, { "FocusPrevKey", N_("Focus previous window") }, + /* Directional window focus */ + { "FocusWindowLeftKey", N_("Focus the window to the left") }, + { "FocusWindowRightKey", N_("Focus the window to the right") }, + { "FocusWindowUpKey", N_("Focus the window above") }, + { "FocusWindowDownKey", N_("Focus the window below") }, { "GroupNextKey", N_("Focus next group window") }, { "GroupPrevKey", N_("Focus previous group window") }, diff --git a/src/actions.c b/src/actions.c index 460c9166..1205a67f 100644 --- a/src/actions.c +++ b/src/actions.c @@ -240,6 +240,100 @@ void wSetFocusTo(WScreen *scr, WWindow *wwin) old_scr = scr; } +/* + *---------------------------------------------------------------------- + * wSetFocusToDirection-- + * Moves focus to the nearest window in the given cardinal + * direction (DIRECTION_LEFT, DIRECTION_RIGHT, DIRECTION_UP, + * DIRECTION_DOWN from xinerama.h). + * + * Selection algorithm: candidate windows are scored by + * (primary_distance + perpendicular_offset). A large penalty is + * added when the perpendicular offset exceeds the primary distance + * (i.e. the candidate is more than 45 degrees off-axis). The window + * with the lowest score wins. If no candidate exists in the requested + * direction the call is silently ignored. + *---------------------------------------------------------------------- + */ +void wSetFocusToDirection(WScreen *scr, int direction) +{ + WWindow *focused = scr->focused_window; + WWindow *best = NULL; + WWindow *candidate = NULL; + int my_cx, my_cy; + long best_score = -1; + + if (!focused || !focused->flags.mapped) + return; + + /* centre of the focused window */ + my_cx = focused->frame_x + (int)focused->frame->core->width / 2; + my_cy = focused->frame_y + (int)focused->frame->core->height / 2; + + /* Iterate from most-recently-focused to least-recently-focused */ + for (candidate = focused->prev; candidate != NULL; candidate = candidate->prev) { + int his_cx, his_cy, distance, offset; + long score; + + if (!candidate->flags.mapped) + continue; + if (WFLAGP(candidate, no_focusable)) + continue; + if (!candidate->frame || candidate->frame->workspace != scr->current_workspace) + continue; + + /* ignore fully covered windows if cannot raised them */ + if (!wPreferences.circ_raise && wWindowIsFullyCovered(candidate)) + continue; + + /* relative centre of candidate */ + his_cx = (candidate->frame_x - my_cx) + (int)candidate->frame->core->width / 2; + his_cy = (candidate->frame_y - my_cy) + (int)candidate->frame->core->height / 2; + + switch (direction) { + case DIRECTION_RIGHT: + distance = his_cx; + offset = his_cy < 0 ? -his_cy : his_cy; + break; + case DIRECTION_LEFT: + distance = -his_cx; + offset = his_cy < 0 ? -his_cy : his_cy; + break; + case DIRECTION_DOWN: + distance = his_cy; + offset = his_cx < 0 ? -his_cx : his_cx; + break; + case DIRECTION_UP: + distance = -his_cy; + offset = his_cx < 0 ? -his_cx : his_cx; + break; + default: + continue; + } + + /* candidate must be strictly in the requested direction */ + if (distance <= 0) + continue; + + score = distance + offset; + + /* heavy penalty for windows more than 45 degrees off-axis */ + if (offset > distance) + score += 1000000L; + + if (best_score < 0 || score < best_score) { + best = candidate; + best_score = score; + } + } + + if (best) { + if (wPreferences.circ_raise) + wRaiseFrame(best->frame->core); + wSetFocusTo(scr, best); + } +} + void wShadeWindow(WWindow *wwin) { diff --git a/src/actions.h b/src/actions.h index 16e5e3f6..f9aa0efc 100644 --- a/src/actions.h +++ b/src/actions.h @@ -41,6 +41,7 @@ #define SAVE_GEOMETRY_ALL SAVE_GEOMETRY_X | SAVE_GEOMETRY_Y | SAVE_GEOMETRY_WIDTH | SAVE_GEOMETRY_HEIGHT void wSetFocusTo(WScreen *scr, WWindow *wwin); +void wSetFocusToDirection(WScreen *scr, int direction); int wMouseMoveWindow(WWindow *wwin, XEvent *ev); int wKeyboardMoveResizeWindow(WWindow *wwin); diff --git a/src/defaults.c b/src/defaults.c index 0a9da900..42bc3445 100644 --- a/src/defaults.c +++ b/src/defaults.c @@ -721,6 +721,15 @@ WDefaultEntry optionList[] = { NULL, getKeybind, setKeyGrab, NULL, NULL}, {"FocusPrevKey", "Mod1+Shift+Tab", (void *)WKBD_FOCUSPREV, NULL, getKeybind, setKeyGrab, NULL, NULL}, + /* Directional window focus */ + {"FocusWindowLeftKey", "None", (void *)WKBD_FOCUSLEFT, + NULL, getKeybind, setKeyGrab, NULL, NULL}, + {"FocusWindowRightKey", "None", (void *)WKBD_FOCUSRIGHT, + NULL, getKeybind, setKeyGrab, NULL, NULL}, + {"FocusWindowUpKey", "None", (void *)WKBD_FOCUSUP, + NULL, getKeybind, setKeyGrab, NULL, NULL}, + {"FocusWindowDownKey", "None", (void *)WKBD_FOCUSDOWN, + NULL, getKeybind, setKeyGrab, NULL, NULL}, {"GroupNextKey", "None", (void *)WKBD_GROUPNEXT, NULL, getKeybind, setKeyGrab, NULL, NULL}, {"GroupPrevKey", "None", (void *)WKBD_GROUPPREV, diff --git a/src/event.c b/src/event.c index 61975315..50489b58 100644 --- a/src/event.c +++ b/src/event.c @@ -1732,6 +1732,22 @@ static void dispatchWKBDCommand(int command, WScreen *scr, WWindow *wwin, XEvent StartWindozeCycle(wwin, event, False, False); break; + case WKBD_FOCUSLEFT: + wSetFocusToDirection(scr, DIRECTION_LEFT); + break; + + case WKBD_FOCUSRIGHT: + wSetFocusToDirection(scr, DIRECTION_RIGHT); + break; + + case WKBD_FOCUSUP: + wSetFocusToDirection(scr, DIRECTION_UP); + break; + + case WKBD_FOCUSDOWN: + wSetFocusToDirection(scr, DIRECTION_DOWN); + break; + case WKBD_GROUPNEXT: StartWindozeCycle(wwin, event, True, True); break; diff --git a/src/keybind.h b/src/keybind.h index ae9a5022..25b7aac8 100644 --- a/src/keybind.h +++ b/src/keybind.h @@ -77,6 +77,10 @@ enum { WKBD_WORKSPACEMAP, WKBD_FOCUSNEXT, WKBD_FOCUSPREV, + WKBD_FOCUSLEFT, + WKBD_FOCUSRIGHT, + WKBD_FOCUSUP, + WKBD_FOCUSDOWN, WKBD_GROUPNEXT, WKBD_GROUPPREV, WKBD_CENTRAL, diff --git a/src/window.c b/src/window.c index 470cfcab..857deec8 100644 --- a/src/window.c +++ b/src/window.c @@ -470,6 +470,35 @@ void wWindowSetupInitialAttributes(WWindow *wwin, int *level, int *workspace) wwin->client_flags.no_focusable = 1; } +/* + * Returns True if every pixel of 'wwin' is covered by at least one other + * mapped window on the same workspace, making it invisible to the user + */ +Bool wWindowIsFullyCovered(WWindow *wwin) +{ + WScreen *scr = wwin->screen_ptr; + int cx = wwin->frame_x; + int cy = wwin->frame_y; + int cright = cx + (int)wwin->frame->core->width; + int cbottom = cy + (int)wwin->frame->core->height; + WWindow *w; + + for (w = scr->focused_window; w != NULL; w = w->prev) { + if (w == wwin) + continue; + if (!w->flags.mapped) + continue; + if (!w->frame || w->frame->workspace != scr->current_workspace) + continue; + if (w->frame_x <= cx && + w->frame_y <= cy && + w->frame_x + (int)w->frame->core->width >= cright && + w->frame_y + (int)w->frame->core->height >= cbottom) + return True; + } + return False; +} + Bool wWindowObscuresWindow(WWindow *wwin, WWindow *obscured) { int w1, h1, w2, h2; diff --git a/src/window.h b/src/window.h index 1229eed3..9ccabdeb 100644 --- a/src/window.h +++ b/src/window.h @@ -405,6 +405,7 @@ WMagicNumber wWindowGetSavedState(Window win); void wWindowDeleteSavedState(WMagicNumber id); +Bool wWindowIsFullyCovered(WWindow *wwin); Bool wWindowObscuresWindow(WWindow *wwin, WWindow *obscured); void wWindowSetOmnipresent(WWindow *wwin, Bool flag); -- 2.43.0
0002-wmaker-add-directional-window-focus.patch