From 26a296db23b89296bf136044898658c0c2b410fc Mon Sep 17 00:00:00 2001 From: David Maciejak Date: Mon, 30 Mar 2026 22:24:42 -0400 Subject: [PATCH] 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. --- WPrefs.app/KeyboardShortcuts.c | 7 ++ src/WindowMaker.h | 1 + src/defaults.c | 11 +++ src/event.c | 141 +++++++++++++++++++++++++++++++++ src/keybind.h | 5 ++ src/misc.c | 78 ++++++++---------- src/misc.h | 1 + src/screen.h | 10 +++ src/session.c | 20 +++++ src/startup.c | 2 + src/switchmenu.c | 46 ++++++----- src/window.c | 60 +++++++++++++- src/window.h | 6 ++ 13 files changed, 326 insertions(+), 62 deletions(-) diff --git a/WPrefs.app/KeyboardShortcuts.c b/WPrefs.app/KeyboardShortcuts.c index 7d18a90a..f6750c1b 100644 --- a/WPrefs.app/KeyboardShortcuts.c +++ b/WPrefs.app/KeyboardShortcuts.c @@ -106,6 +106,13 @@ static struct keyOption { { "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") }, diff --git a/src/WindowMaker.h b/src/WindowMaker.h index f979573f..da727b37 100644 --- a/src/WindowMaker.h +++ b/src/WindowMaker.h @@ -609,6 +609,7 @@ extern struct wmaker_global_variables { Atom icon_size; Atom icon_tile; + Atom mark_key; } wmaker; } atom; diff --git a/src/defaults.c b/src/defaults.c index 101e4dc1..0a9da900 100644 --- a/src/defaults.c +++ b/src/defaults.c @@ -823,6 +823,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, diff --git a/src/event.c b/src/event.c index e5422ea4..c3063642 100644 --- a/src/event.c +++ b/src/event.c @@ -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; @@ -1975,6 +1981,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 +2019,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 * * * diff --git a/src/keybind.h b/src/keybind.h index 292cbb71..ae9a5022 100644 --- a/src/keybind.h +++ b/src/keybind.h @@ -80,6 +80,11 @@ enum { 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, diff --git a/src/misc.c b/src/misc.c index 38d6238c..788c1b5d 100644 --- a/src/misc.c +++ b/src/misc.c @@ -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); diff --git a/src/misc.h b/src/misc.h index 5fc15a8f..288a8bb4 100644 --- a/src/misc.h +++ b/src/misc.h @@ -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); diff --git a/src/screen.h b/src/screen.h index c3100b0b..9ecb1253 100644 --- a/src/screen.h +++ b/src/screen.h @@ -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; diff --git a/src/session.c b/src/session.c index 89df5b54..9d66b218 100644 --- a/src/session.c +++ b/src/session.c @@ -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); } diff --git a/src/startup.c b/src/startup.c index ed992901..b0e676b2 100644 --- a/src/startup.c +++ b/src/startup.c @@ -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(); diff --git a/src/switchmenu.c b/src/switchmenu.c index a8c3937f..0f1746f6 100644 --- a/src/switchmenu.c +++ b/src/switchmenu.c @@ -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; } } diff --git a/src/window.c b/src/window.c index 9cb11882..470cfcab 100644 --- a/src/window.c +++ b/src/window.c @@ -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) { @@ -1006,8 +1011,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 +2541,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 +2558,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 +2586,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 +2921,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 +2937,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; diff --git a/src/window.h b/src/window.h index 8b14564e..1229eed3 100644 --- a/src/window.h +++ b/src/window.h @@ -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<