1
0
mirror of https://github.com/gryf/wmaker.git synced 2026-04-29 12:04:06 +02:00

Compare commits

...

4 Commits

Author SHA1 Message Date
David Maciejak 33fba87ed1 wmaker: add directional window focus
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.
2026-04-03 10:35:39 +01:00
David Maciejak a3c02a22bd NEWS: add new feature descriptions 2026-04-03 10:35:39 +01:00
David Maciejak 0d74066aea wmaker: treat _NET_WM_WINDOW_TYPE property as a list
This patch is treating the _NET_WM_WINDOW_TYPE property as an array
of atoms as defined in X11. Currently, we are only getting the first one,
while the client can set multiple window types ordered from most specific
to least specific.
2026-04-02 08:57:25 +01:00
David Maciejak 26a296db23 wmaker: dynamic vim-like window marking feature
This patch is adding vim-like window marking, like in i3.
A window can be dynamically assigned a mark label.
Then a marked window can be pulled or jumped to.
Or the current focused window can be swapped with a marked window.

The mark label appears in the Window List in between the window
title and the workspace name.

Those new options in WindowMaker conf file are used to control
the actions: MarkSetKey, MarkUnsetKey, MarkBringKey, MarkJumpKey
and MarkSwapKey.

Those actions are set to None by default.
2026-04-02 08:57:25 +01:00
17 changed files with 660 additions and 64 deletions
+174 -1
View File
@@ -1,10 +1,183 @@
NEWS for veteran Window Maker users
-----------------------------------
-- 0.97.0
Directional window focus
------------------------
The keyboard focus can now be moved in a specific direction (left, right,
up, down) relative to the currently focused window, using window centres
for comparison. The shortcuts can be configured in WPrefs under
"Keyboard Shortcuts", or set directly in ~/GNUstep/Defaults/WindowMaker
via "FocusWindowLeftKey", "FocusWindowRightKey", "FocusWindowUpKey" and
"FocusWindowDownKey" (disabled by default).
The existing "CirculateRaise" option ("Raise window when switching focus
with keyboard" in WPrefs) also controls whether windows that are fully
obscured by others are raised when receiving focus this way.
Vim-like window marking
-----------------------
Windows can now be dynamically assigned a mark label (a key or
modifier+key combination) allowing them to be instantly recalled without
searching. This is inspired by vim's mark-and-jump navigation.
The mark label is displayed in the window list menu between the window
title and the workspace name.
Five actions can be configured in ~/GNUstep/Defaults/WindowMaker
(or in WPrefs under "Keyboard Shortcuts")
"MarkSetKey": assign a label to the focused window (e.g. with the
action bound to "Super+M", pressing "Super+M x" marks the focused
window with label 'x').
"MarkUnsetKey": remove the mark from a labeled window.
"MarkJumpKey": focus and raise the window with the given label.
"MarkBringKey": move the marked window to the current workspace and
focus it.
"MarkSwapKey": swap the focused window with the marked window.
Those are disabled by default.
Marks are persistent across sessions and saved in the WMState file.
Multi-key and sticky-chain keybindings
--------------------------------------
Keyboard shortcuts now support multi-key sequences, similar to
Emacs-style prefix bindings (e.g. pressing a leader key followed by
one or more continuation keys).
An optional sticky-chain mode allows a prefix key to remain active
until a cancel key is pressed or a timeout expires, making it possible
to trigger multiple actions in sequence without re-entering the prefix.
New options in ~/GNUstep/Defaults/WindowMaker:
"KeychainTimeoutDelay" is the timeout in milliseconds after which an
active chain expires. Default is 500. Set to 0 to disable the
timeout.
"KeychainCancelKey" sets a key to explicitly cancel an active chain.
(disabled by default).
Configurable modifier key labels
---------------------------------
A new "ModifierKeyShortLabels" option in ~/GNUstep/Defaults/WindowMaker
allows customizing the labels shown for modifier keys in menus and
keyboard shortcut displays. Unicode symbols can be used in place of the
default text labels. For example to use the same symbol as defined on
MacOS:
ModifierKeyShortLabels = (
"\342\207\247",
"\342\214\203",
"\342\214\245",
"\342\207\255",
"\342\207\263",
"\342\214\230",
"\342\207\252",
"\342\227\206",
"\342\214\245"
);
Configurable mouse wheel focus behavior
----------------------------------------
Scrolling the mouse wheel over a window can now optionally move the
keyboard focus to that window. The option is available in WPrefs under
"Expert User Preferences", or by setting "MouseWheelFocusEnable" in
~/GNUstep/Defaults/WindowMaker (NO by default).
Application icons in the window list
-------------------------------------
Application icons can now be shown in the window list menu. This behavior
can be configured by setting the "WindowListAppIcons" option in
~/GNUstep/Defaults/WindowMaker or "Show app icons in window list." in
WPrefs under "Expert User Preferences" (NO by default).
Configurable screenshot filename template
-----------------------------------------
The filename (strftime compatible) format used when saving screenshots
can now be customized by setting the "ScreenshotFileNameTemplate"
option in ~/GNUstep/Defaults/WindowMaker. The default remains the
format introduced in 0.96.0.
JPEG XL image support
---------------------
WRaster can now optionally load JPEG XL images if the libjxl
library is present at build time. The feature can be toggled at
configure time with --enable-jxl/--disable-jxl.
wmiv: archive files and unknown format handling
-----------------------------------------------
wmiv can now open images stored inside archive files (with libarchive).
A new option to silently skip unknown image formats is also available.
It supports ctrl+c shortcut to copy the current image to the clipboard.
Support for _NET_WM_MOVERESIZE
------------------------------
Window Maker now handles the _NET_WM_MOVERESIZE EWMH message, which
allows applications to initiate interactive window moves and resizes
through a standard protocol request (for those applications without
decorations: VS Code, Google Chrome, Steam and Discord, ...).
Cycle windows across all workspaces
-----------------------------------
A new 'Cycle all windows from all workspaces' expert option allows
the Alt-Tab window switcher to include windows from all workspaces,
not only the current one. Which is the "CycleAllWorkspaces" option
in ~/GNUstep/Defaults/WindowMaker (NO by default).
RandR and multi-monitor improvements
------------------------------------
RandR version >= 1.3 is automatically enabled when detected. By default,
it operates in static mode, requiring the screen layout to be defined
manually. A new "HotplugMonitor" option in ~/GNUstep/Defaults/WindowMaker
(NO by default) enables automatic monitor detection on hotplug events,
adding newly connected monitors to the right of the existing layout.
This option is also available in WPrefs expert panel under
"Automatically (de)activate monitors on hotplug events".
Titlebar language button revamp
-------------------------------
When the modelock feature is enabled, the legacy hardcoded language
dropdown icons in the titlebar are replaced by a compact button
displaying the short name of the current XKB locale (those are
basically the layouts displayed via setxkbmap -query).
Clicking the button cycles through up to four keyboard layouts
(matching the four XKB groups). The xkbfile library is required for
this feature.
-- 0.96.0
Hot Corners feature
--------------------------
-------------------
Screen corners can be assigned an external command to be
executed when the mouse pointer is entering those areas.
+12
View File
@@ -103,9 +103,21 @@ 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") },
/* Vim-like Window Marking */
{ "MarkSetKey", N_("Mark window: set mark") },
{ "MarkUnsetKey", N_("Mark window: unset mark") },
{ "MarkBringKey", N_("Mark window: bring marked window here") },
{ "MarkJumpKey", N_("Mark window: jump to marked window") },
{ "MarkSwapKey", N_("Mark window: swap with marked window") },
/* Workspace Related */
{ "WorkspaceMapKey", N_("Open workspace pager") },
{ "NextWorkspaceKey", N_("Switch to next workspace") },
+1
View File
@@ -609,6 +609,7 @@ extern struct wmaker_global_variables {
Atom icon_size;
Atom icon_tile;
Atom mark_key;
} wmaker;
} atom;
+94
View File
@@ -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)
{
+1
View File
@@ -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);
+20
View File
@@ -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,
@@ -823,6 +832,17 @@ WDefaultEntry optionList[] = {
NULL, getKeybind, setKeyGrab, NULL, NULL},
{"PartialCaptureKey", "None", (void *)WKBD_PRINTP,
NULL, getKeybind, setKeyGrab, NULL, NULL},
/* Vim-like Window Marking */
{"MarkSetKey", "None", (void *)WKBD_MARK_SET,
NULL, getKeybind, setKeyGrab, NULL, NULL},
{"MarkUnsetKey", "None", (void *)WKBD_MARK_UNSET,
NULL, getKeybind, setKeyGrab, NULL, NULL},
{"MarkBringKey", "None", (void *)WKBD_MARK_BRING,
NULL, getKeybind, setKeyGrab, NULL, NULL},
{"MarkJumpKey", "None", (void *)WKBD_MARK_JUMP,
NULL, getKeybind, setKeyGrab, NULL, NULL},
{"MarkSwapKey", "None", (void *)WKBD_MARK_SWAP,
NULL, getKeybind, setKeyGrab, NULL, NULL},
#ifdef KEEP_XKB_LOCK_STATUS
{"ToggleKbdModeKey", "None", (void *)WKBD_TOGGLE,
+157
View File
@@ -1471,6 +1471,12 @@ static void wCancelChainTimer(void)
#define ISMAPPED(w) ((w) && !(w)->flags.miniaturized && ((w)->flags.mapped || (w)->flags.shaded))
#define ISFOCUSED(w) ((w) && (w)->flags.focused)
static void startMarkCapture(WScreen *scr, Display *dpy, WMarkCaptureMode mode)
{
scr->flags.mark_capture_mode = mode;
XGrabKeyboard(dpy, scr->root_win, False, GrabModeAsync, GrabModeAsync, CurrentTime);
}
static void dispatchWKBDCommand(int command, WScreen *scr, WWindow *wwin, XEvent *event)
{
short widx;
@@ -1726,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;
@@ -1975,6 +1997,29 @@ static void dispatchWKBDCommand(int command, WScreen *scr, WWindow *wwin, XEvent
}
break;
#endif /* KEEP_XKB_LOCK_STATUS */
case WKBD_MARK_SET:
if (ISMAPPED(wwin) && ISFOCUSED(wwin))
startMarkCapture(scr, dpy, MARK_CAPTURE_SET);
break;
case WKBD_MARK_UNSET:
if (ISMAPPED(wwin) && ISFOCUSED(wwin) && wwin->mark_key_label != NULL)
wWindowUnsetMark(wwin);
break;
case WKBD_MARK_BRING:
startMarkCapture(scr, dpy, MARK_CAPTURE_BRING);
break;
case WKBD_MARK_JUMP:
startMarkCapture(scr, dpy, MARK_CAPTURE_JUMP);
break;
case WKBD_MARK_SWAP:
if (ISMAPPED(wwin) && ISFOCUSED(wwin))
startMarkCapture(scr, dpy, MARK_CAPTURE_SWAP);
break;
}
}
@@ -1990,6 +2035,118 @@ static void handleKeyPress(XEvent * event)
/* ignore CapsLock */
modifiers = event->xkey.state & w_global.shortcut.modifiers_mask;
/* ----------------------------------------------------------- *
* Window mark capture mode *
* *
* We grabbed the keyboard so all keypresses come here until *
* we release the grab. *
* ------------------------------------------------------------ */
if (scr->flags.mark_capture_mode != MARK_CAPTURE_IDLE) {
int capture_mode = scr->flags.mark_capture_mode;
KeySym cap_ksym;
char label[MAX_SHORTCUT_LENGTH];
/* Skip modifier-only keypresses */
cap_ksym = XLookupKeysym(&event->xkey, 0);
if (cap_ksym == NoSymbol || IsModifierKey(cap_ksym))
return;
/* Real key received: exit capture mode and release grab */
scr->flags.mark_capture_mode = MARK_CAPTURE_IDLE;
XUngrabKeyboard(dpy, CurrentTime);
if (!GetCanonicalShortcutLabel(modifiers, cap_ksym, label, sizeof(label)))
wstrlcpy(label, "?", sizeof(label));
if (capture_mode == MARK_CAPTURE_SET) {
WWindow *target = scr->focused_window;
Bool conflict = False;
int i;
/* Conflict check against static wmaker bindings */
for (i = 0; i < WKBD_LAST; i++) {
if (wKeyBindings[i].keycode == event->xkey.keycode && wKeyBindings[i].modifier == modifiers) {
wwarning("window mark: '%s' is already a wmaker binding, mark not assigned", label);
conflict = True;
break;
}
}
/* Conflict check against existing marks on other windows */
if (!conflict) {
WWindow *tw = scr->focused_window;
while (tw) {
if (tw != target && tw->mark_key_label != NULL && strcmp(tw->mark_key_label, label) == 0) {
wwarning("window mark: label '%s' is already used by another window, mark not assigned", label);
conflict = True;
break;
}
tw = tw->prev;
}
}
if (!conflict && target != NULL)
wWindowSetMark(target, label);
} else {
/* Find marked window by label */
WWindow *tw = scr->focused_window;
while (tw) {
if (tw->mark_key_label != NULL && strcmp(tw->mark_key_label, label) == 0)
break;
tw = tw->prev;
}
if (tw == NULL) {
wwarning("window mark: no window labelled '%s'", label);
} else if (capture_mode == MARK_CAPTURE_BRING) {
if (tw->frame->workspace != scr->current_workspace)
wWindowChangeWorkspace(tw, scr->current_workspace);
wMakeWindowVisible(tw);
} else if (capture_mode == MARK_CAPTURE_JUMP) {
wMakeWindowVisible(tw);
} else {
/* MARK_CAPTURE_SWAP: swap position, size and workspace between focused and tw */
WWindow *focused = scr->focused_window;
int fx, fy, fw, fh, tx, ty, tw_w, tw_h;
int f_ws, t_ws;
if (focused == NULL || focused == tw)
return;
/* Snapshot both geometries */
fx = focused->frame_x;
fy = focused->frame_y;
fw = focused->client.width;
fh = focused->client.height;
f_ws = focused->frame->workspace;
tx = tw->frame_x;
ty = tw->frame_y;
tw_w = tw->client.width;
tw_h = tw->client.height;
t_ws = tw->frame->workspace;
/* Swap workspaces first so configure lands in the right one */
if (f_ws != t_ws) {
wWindowChangeWorkspace(focused, t_ws);
wWindowChangeWorkspace(tw, f_ws);
}
/* Swap positions and sizes */
wWindowConfigure(focused, tx, ty, tw_w, tw_h);
wWindowConfigure(tw, fx, fy, fw, fh);
/* Follow origin window: switch view to the workspace it landed in,
* then restore focus to it */
if (f_ws != t_ws)
wWorkspaceChange(scr, t_ws);
wSetFocusTo(scr, focused);
}
}
return;
}
/* ------------------------------------------------------------------ *
* Trie-based key-chain matching *
* *
+9
View File
@@ -77,9 +77,18 @@ enum {
WKBD_WORKSPACEMAP,
WKBD_FOCUSNEXT,
WKBD_FOCUSPREV,
WKBD_FOCUSLEFT,
WKBD_FOCUSRIGHT,
WKBD_FOCUSUP,
WKBD_FOCUSDOWN,
WKBD_GROUPNEXT,
WKBD_GROUPPREV,
WKBD_CENTRAL,
WKBD_MARK_SET,
WKBD_MARK_UNSET,
WKBD_MARK_BRING,
WKBD_MARK_JUMP,
WKBD_MARK_SWAP,
/* window, menu */
WKBD_CLOSE,
+35 -43
View File
@@ -847,6 +847,34 @@ static char *keysymToString(KeySym keysym, unsigned int state)
}
#endif
Bool GetCanonicalShortcutLabel(unsigned int modifiers, KeySym ksym, char *buf, size_t bufsz)
{
static const struct { unsigned int mask; const char *name; } mt[] = {
{ ShiftMask, "Shift+" },
{ ControlMask, "Control+" },
{ Mod1Mask, "Mod1+" },
{ Mod2Mask, "Mod2+" },
{ Mod3Mask, "Mod3+" },
{ Mod4Mask, "Mod4+" },
{ Mod5Mask, "Mod5+" },
{ 0, NULL }
};
const char *kname = XKeysymToString(ksym);
size_t i;
if (!kname)
return False;
buf[0] = '\0';
for (i = 0; mt[i].mask; i++) {
if (modifiers & mt[i].mask)
wstrlcat(buf, mt[i].name, bufsz);
}
wstrlcat(buf, kname, bufsz);
return True;
}
char *GetShortcutString(const char *shortcut)
{
char *buffer = NULL;
@@ -882,50 +910,12 @@ char *GetShortcutString(const char *shortcut)
char *GetShortcutKey(WShortKey key)
{
char buf[MAX_SHORTCUT_LENGTH];
char *wr, *result, *seg;
char *result, *seg;
int step;
void append_string(const char *text)
{
const char *s = text;
while (*s) {
if (wr >= buf + sizeof(buf) - 1)
break;
*wr++ = *s++;
}
}
void append_modifier(int modifier_index, const char *fallback_name)
{
if (wPreferences.modifier_labels[modifier_index])
append_string(wPreferences.modifier_labels[modifier_index]);
else
append_string(fallback_name);
}
Bool build_token(unsigned int mod, KeyCode kcode)
{
const char *kname = XKeysymToString(W_KeycodeToKeysym(dpy, kcode, 0));
if (!kname)
return False;
wr = buf;
if (mod & ControlMask) append_modifier(1, "Control+");
if (mod & ShiftMask) append_modifier(0, "Shift+");
if (mod & Mod1Mask) append_modifier(2, "Mod1+");
if (mod & Mod2Mask) append_modifier(3, "Mod2+");
if (mod & Mod3Mask) append_modifier(4, "Mod3+");
if (mod & Mod4Mask) append_modifier(5, "Mod4+");
if (mod & Mod5Mask) append_modifier(6, "Mod5+");
append_string(kname);
*wr = '\0';
return True;
}
if (!build_token(key.modifier, key.keycode))
if (!GetCanonicalShortcutLabel(key.modifier,
W_KeycodeToKeysym(dpy, key.keycode, 0),
buf, sizeof(buf)))
return NULL;
/* Convert the leader token to its display string */
@@ -938,7 +928,9 @@ char *GetShortcutKey(WShortKey key)
if (key.chain_keycodes[step] == 0)
break;
if (!build_token(key.chain_modifiers[step], key.chain_keycodes[step]))
if (!GetCanonicalShortcutLabel(key.chain_modifiers[step],
W_KeycodeToKeysym(dpy, key.chain_keycodes[step], 0),
buf, sizeof(buf)))
break;
seg = GetShortcutString(buf);
+1
View File
@@ -50,6 +50,7 @@ char *ExpandOptions(WScreen * scr, const char *cmdline);
void ExecuteInputCommand(WScreen *scr, const char *cmdline);
void ExecuteExitCommand(WScreen *scr, long quickmode);
char *GetShortcutString(const char *text);
Bool GetCanonicalShortcutLabel(unsigned int modifiers, KeySym ksym, char *buf, size_t bufsz);
char *GetShortcutKey(WShortKey key);
char *EscapeWM_CLASS(const char *name, const char *class);
char *StrConcatDot(const char *a, const char *b);
+10
View File
@@ -59,6 +59,14 @@ typedef struct WDrawerChain {
struct WDrawerChain *next;
} WDrawerChain;
typedef enum {
MARK_CAPTURE_IDLE = 0,
MARK_CAPTURE_SET = 1,
MARK_CAPTURE_BRING = 2,
MARK_CAPTURE_JUMP = 3,
MARK_CAPTURE_SWAP = 4
} WMarkCaptureMode;
/*
* each WScreen is saved into a context associated with its root window
*/
@@ -332,6 +340,8 @@ typedef struct _WScreen {
unsigned int jump_back_pending:1;
unsigned int ignore_focus_events:1;
unsigned int in_hot_corner:3;
unsigned int mark_capture_mode:3; /* one of WMarkCaptureMode */
} flags;
} WScreen;
+20
View File
@@ -97,6 +97,7 @@ static WMPropList *sMaximized;
static WMPropList *sHidden;
static WMPropList *sGeometry;
static WMPropList *sShortcutMask;
static WMPropList *sMarkKey;
static WMPropList *sDock;
static WMPropList *sYes, *sNo;
@@ -118,6 +119,7 @@ static void make_keys(void)
sGeometry = WMCreatePLString("Geometry");
sDock = WMCreatePLString("Dock");
sShortcutMask = WMCreatePLString("ShortcutMask");
sMarkKey = WMCreatePLString("MarkKey");
sYes = WMCreatePLString("Yes");
sNo = WMCreatePLString("No");
@@ -165,6 +167,7 @@ static WMPropList *makeWindowState(WWindow * wwin, WApplication * wapp)
WMPropList *win_state, *cmd, *name, *workspace;
WMPropList *shaded, *miniaturized, *maximized, *hidden, *geometry;
WMPropList *dock, *shortcut;
WMPropList *mark_key_pl = NULL;
if (wwin->orig_main_window != None && wwin->orig_main_window != wwin->client_win)
win = wwin->orig_main_window;
@@ -215,6 +218,9 @@ static WMPropList *makeWindowState(WWindow * wwin, WApplication * wapp)
snprintf(buffer, sizeof(buffer), "%u", mask);
shortcut = WMCreatePLString(buffer);
if (wwin->mark_key_label)
mark_key_pl = WMCreatePLString(wwin->mark_key_label);
win_state = WMCreatePLDictionary(sName, name,
sCommand, cmd,
sWorkspace, workspace,
@@ -224,6 +230,11 @@ static WMPropList *makeWindowState(WWindow * wwin, WApplication * wapp)
sHidden, hidden,
sShortcutMask, shortcut, sGeometry, geometry, NULL);
if (mark_key_pl) {
WMPutInPLDictionary(win_state, sMarkKey, mark_key_pl);
WMReleasePropList(mark_key_pl);
}
WMReleasePropList(name);
WMReleasePropList(cmd);
WMReleasePropList(workspace);
@@ -418,6 +429,13 @@ static WSavedState *getWindowState(WScreen * scr, WMPropList * win_state)
state->window_shortcuts = mask;
}
value = WMGetFromPLDictionary(win_state, sMarkKey);
if (value != NULL && WMIsPLString(value)) {
char *s = WMGetFromPLString(value);
if (s && *s)
state->mark_key = wstrdup(s);
}
value = WMGetFromPLDictionary(win_state, sGeometry);
if (value && WMIsPLString(value)) {
if (!(sscanf(WMGetFromPLString(value), "%ix%i+%i+%i",
@@ -531,6 +549,8 @@ void wSessionRestoreState(WScreen *scr)
} else if ((pid = execCommand(scr, command)) > 0) {
wWindowAddSavedState(instance, class, command, pid, state);
} else {
if (state->mark_key)
wfree(state->mark_key);
wfree(state);
}
+2
View File
@@ -387,6 +387,7 @@ static char *atomNames[] = {
"_WINDOWMAKER_COMMAND",
"_WINDOWMAKER_ICON_SIZE",
"_WINDOWMAKER_ICON_TILE",
"_WINDOWMAKER_MARK_KEY",
GNUSTEP_WM_ATTR_NAME,
GNUSTEP_WM_MINIATURIZE_WINDOW,
@@ -474,6 +475,7 @@ void StartUp(Bool defaultScreenOnly)
w_global.atom.desktop.gtk_object_path = atom[20];
w_global.atom.wm.ignore_focus_events = atom[21];
w_global.atom.wmaker.mark_key = atom[22];
#ifdef USE_DOCK_XDND
wXDNDInitializeAtoms();
+28 -18
View File
@@ -44,6 +44,8 @@
((w)->wm_gnustep_attr->window_level == WMMainMenuWindowLevel || \
(w)->wm_gnustep_attr->window_level == WMSubmenuWindowLevel))
#define MAX_RTEXT_LENGTH (MAX_WORKSPACENAME_WIDTH + MAX_SHORTCUT_LENGTH + 16)
static int initialized = 0;
static void observer(void *self, WMNotification * notif);
static void wsobserver(void *self, WMNotification * notif);
@@ -214,6 +216,26 @@ static int menuIndexForWindow(WMenu * menu, WWindow * wwin, int old_pos)
return idx;
}
static void fillRtext(char *buf, size_t bufsz, WWindow *wwin, WScreen *scr)
{
char *mlbl = wwin->mark_key_label ? GetShortcutString(wwin->mark_key_label) : NULL;
if (IS_OMNIPRESENT(wwin)) {
if (mlbl)
snprintf(buf, bufsz, "[%s] [*]", mlbl);
else
snprintf(buf, bufsz, "[*]");
} else {
if (mlbl)
snprintf(buf, bufsz, "[%s] [%s]", mlbl,
scr->workspaces[wwin->frame->workspace]->name);
else
snprintf(buf, bufsz, "[%s]",
scr->workspaces[wwin->frame->workspace]->name);
}
wfree(mlbl);
}
/*
* Update switch menu
*/
@@ -263,12 +285,8 @@ void UpdateSwitchMenu(WScreen * scr, WWindow * wwin, int action)
entry->icon = switchMenuIconForWindow(scr, wwin);
entry->flags.indicator = 1;
entry->rtext = wmalloc(MAX_WORKSPACENAME_WIDTH + 8);
if (IS_OMNIPRESENT(wwin))
snprintf(entry->rtext, MAX_WORKSPACENAME_WIDTH, "[*]");
else
snprintf(entry->rtext, MAX_WORKSPACENAME_WIDTH, "[%s]",
scr->workspaces[wwin->frame->workspace]->name);
entry->rtext = wmalloc(MAX_RTEXT_LENGTH);
fillRtext(entry->rtext, MAX_RTEXT_LENGTH, wwin, scr);
if (wwin->flags.hidden) {
entry->flags.indicator_type = MI_HIDDEN;
@@ -311,6 +329,8 @@ void UpdateSwitchMenu(WScreen * scr, WWindow * wwin, int action)
t = ShrinkString(scr->menu_entry_font, title, MAX_WINDOWLIST_WIDTH);
entry->text = t;
fillRtext(entry->rtext, MAX_RTEXT_LENGTH, wwin, scr);
wMenuRealize(switchmenu);
checkVisibility = 1;
break;
@@ -322,13 +342,7 @@ void UpdateSwitchMenu(WScreen * scr, WWindow * wwin, int action)
WPixmap *ipix;
int it, ion;
if (IS_OMNIPRESENT(wwin)) {
snprintf(entry->rtext, MAX_WORKSPACENAME_WIDTH, "[*]");
} else {
snprintf(entry->rtext, MAX_WORKSPACENAME_WIDTH,
"[%s]",
scr->workspaces[wwin->frame->workspace]->name);
}
fillRtext(entry->rtext, MAX_RTEXT_LENGTH, wwin, scr);
rt = entry->rtext;
entry->rtext = NULL;
@@ -404,11 +418,7 @@ static void UpdateSwitchMenuWorkspace(WScreen *scr, int workspace)
wwin = (WWindow *) menu->entries[i]->clientdata;
if (wwin->frame->workspace == workspace && !IS_OMNIPRESENT(wwin)) {
if (IS_OMNIPRESENT(wwin))
snprintf(menu->entries[i]->rtext, MAX_WORKSPACENAME_WIDTH, "[*]");
else
snprintf(menu->entries[i]->rtext, MAX_WORKSPACENAME_WIDTH, "[%s]",
scr->workspaces[wwin->frame->workspace]->name);
fillRtext(menu->entries[i]->rtext, MAX_RTEXT_LENGTH, wwin, scr);
menu->flags.realized = 0;
}
}
+88 -1
View File
@@ -75,6 +75,7 @@
#include "startup.h"
#include "winmenu.h"
#include "osdep.h"
#include "switchmenu.h"
#ifdef USE_MWM_HINTS
# include "motif.h"
@@ -200,6 +201,10 @@ void wWindowDestroy(WWindow *wwin)
}
}
/* clean up any mark assigned to this window */
if (wwin->mark_key_label != NULL)
wWindowUnsetMark(wwin);
if (wwin->fake_group && wwin->fake_group->retainCount > 0) {
wwin->fake_group->retainCount--;
if (wwin->fake_group->retainCount == 0 && wwin->fake_group->leader != None) {
@@ -465,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;
@@ -1006,8 +1040,17 @@ WWindow *wManageWindow(WScreen *scr, Window window)
}
}
if (wstate != NULL)
/* restore mark: prefer session state, fall back to warm-restart hint */
if (win_state != NULL && win_state->state->mark_key != NULL)
wWindowSetMark(wwin, win_state->state->mark_key);
else if (wstate != NULL && wstate->mark_key != NULL)
wWindowSetMark(wwin, wstate->mark_key);
if (wstate != NULL) {
if (wstate->mark_key != NULL)
wfree(wstate->mark_key);
wfree(wstate);
}
}
/* don't let transients start miniaturized if their owners are not */
@@ -2527,6 +2570,14 @@ void wWindowSaveState(WWindow *wwin)
XChangeProperty(dpy, wwin->client_win, w_global.atom.wmaker.state,
w_global.atom.wmaker.state, 32, PropModeReplace, (unsigned char *)data, 10);
if (wwin->mark_key_label != NULL)
XChangeProperty(dpy, wwin->client_win, w_global.atom.wmaker.mark_key,
XA_STRING, 8, PropModeReplace,
(unsigned char *)wwin->mark_key_label,
strlen(wwin->mark_key_label));
else
XDeleteProperty(dpy, wwin->client_win, w_global.atom.wmaker.mark_key);
}
static int getSavedState(Window window, WSavedState ** state)
@@ -2536,6 +2587,7 @@ static int getSavedState(Window window, WSavedState ** state)
unsigned long nitems_ret;
unsigned long bytes_after_ret;
long *data;
unsigned char *mk_data = NULL;
if (XGetWindowProperty(dpy, window, w_global.atom.wmaker.state, 0, 10,
True, w_global.atom.wmaker.state,
@@ -2563,6 +2615,15 @@ static int getSavedState(Window window, WSavedState ** state)
XFree(data);
(*state)->mark_key = NULL;
if (XGetWindowProperty(dpy, window, w_global.atom.wmaker.mark_key, 0, 256,
True, XA_STRING, &type_ret, &fmt_ret, &nitems_ret, &bytes_after_ret,
&mk_data) == Success && mk_data && nitems_ret > 0 && type_ret == XA_STRING)
(*state)->mark_key = wstrdup((char *)mk_data);
if (mk_data)
XFree(mk_data);
return 1;
}
@@ -2889,6 +2950,9 @@ static void release_wwindowstate(WWindowState *wstate)
if (wstate->command)
wfree(wstate->command);
if (wstate->state && wstate->state->mark_key)
wfree(wstate->state->mark_key);
wfree(wstate->state);
wfree(wstate);
}
@@ -2902,6 +2966,29 @@ void wWindowSetOmnipresent(WWindow *wwin, Bool flag)
WMPostNotificationName(WMNChangedState, wwin, "omnipresent");
}
void wWindowSetMark(WWindow *wwin, const char *label)
{
/* Remove any previous mark first */
if (wwin->mark_key_label != NULL)
wWindowUnsetMark(wwin);
wwin->mark_key_label = wstrdup(label);
UpdateSwitchMenu(wwin->screen_ptr, wwin, ACTION_CHANGE);
}
void wWindowUnsetMark(WWindow *wwin)
{
if (wwin->mark_key_label == NULL)
return;
wfree(wwin->mark_key_label);
wwin->mark_key_label = NULL;
UpdateSwitchMenu(wwin->screen_ptr, wwin, ACTION_CHANGE);
}
static void resizebarMouseDown(WCoreWindow *sender, void *data, XEvent *event)
{
WWindow *wwin = data;
+7
View File
@@ -299,6 +299,7 @@ typedef struct WWindow {
int icon_w, icon_h;
RImage *net_icon_image; /* Window Image */
Atom type;
char *mark_key_label; /* Vim-like Window Marking */
} WWindow;
#define HAS_TITLEBAR(w) (!(WFLAGP((w), no_titlebar) || (w)->flags.fullscreen))
@@ -323,6 +324,7 @@ typedef struct WSavedState {
unsigned int w;
unsigned int h;
unsigned window_shortcuts; /* mask like 1<<shortcut_number */
char *mark_key; /* serialised mark key label */
} WSavedState;
typedef struct WWindowState {
@@ -403,10 +405,15 @@ WMagicNumber wWindowGetSavedState(Window win);
void wWindowDeleteSavedState(WMagicNumber id);
Bool wWindowIsFullyCovered(WWindow *wwin);
Bool wWindowObscuresWindow(WWindow *wwin, WWindow *obscured);
void wWindowSetOmnipresent(WWindow *wwin, Bool flag);
/* Vim-like window marking management */
void wWindowSetMark(WWindow *wwin, const char *label);
void wWindowUnsetMark(WWindow *wwin);
#ifdef XKB_BUTTON_HINT
void wWindowGetLanguageLabel(int group_index, char *label);
#endif
+1 -1
View File
@@ -1535,7 +1535,7 @@ void wNETWMCheckClientHints(WWindow *wwin, int *layer, int *workspace)
XFree(data);
}
if (XGetWindowProperty(dpy, wwin->client_win, net_wm_window_type, 0, 1, False,
if (XGetWindowProperty(dpy, wwin->client_win, net_wm_window_type, 0, 1024L, False,
XA_ATOM, &type_ret, &fmt_ret, &nitems_ret,
&bytes_after_ret, (unsigned char **)&data) == Success && data) {