[PATCH 2/2] wmaker: add directional window focus

Skip to first unread message

david.m...@gmail.com

unread,
Apr 3, 2026, 5:18:09 AM (3 days ago) Apr 3
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
Reply all
Reply to author
Forward
0 new messages