From cc234a66f020256ca2cd2a0a189fda00195bb8f0 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Tue, 24 Jun 2025 00:20:36 +0100 Subject: add scratchpads there are now i3 like scratchpads. all default binds added, mans updated --- CHANGELOG.md | 1 + README.md | 38 +++++++++++++++----- default_sxwmrc | 16 +++++++++ src/config.h | 54 ++++++++++++++++++---------- src/defs.h | 26 ++++++++++---- src/parser.c | 45 +++++++++++++++++++++-- src/sxwm.c | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- sxwm.1 | 50 +++++++++++++++++++++++--- 8 files changed, 297 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff3fe90..3ec0b4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file. - **NEW**: Can click on a window to set focus to it - **NEW**: Window swallowing - **NEW**: New windows can now open as master window +- **NEW**: Scratchpads - **CHANGE**: Renamed `focus_previous` to `focus_prev` - **CHANGE**: Invalid sample config - **CHANGE**: Parser `$HOME` searching order. XDG Compliance diff --git a/README.md b/README.md index 47028e8..0d26eeb 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,8 @@ Allows user to use backup keybinds with `sxwm` - **Tiling & Floating**: Switch seamlessly between layouts. - **Workspaces**: 9 workspaces, fully integrated with your bar. +- **Scratchpads**: Floating windows you can summon/hide instantly. +- **Window Swallowing**: Native window swallowing support. - **Live Config Reload**: Change your config and reload instantly with a keybind. - **Easy Configuration**: Human-friendly `sxwmrc` file, no C required. - **Master-Stack Layout**: DWM-inspired productive workflow. @@ -92,23 +94,30 @@ The file uses a `key : value` format. Lines starting with `#` are ignored. ### Syntax +- **Modifiers**: `mod`, `shift`, `ctrl`, `alt`, `super` +- **Key**: Case-insensitive keysym (e.g., `Return`, `q`, `1`) +- **Action**: Either an external command (in quotes) or internal function. +- **move**: Move to that worspace +- **swap**: Swap window to that workspace +- **n**: Workspace / Scratchpad number +- **create**: Creates a scratchpad on that slot +- **toggle**: toggles the visibility of that scratchpad +- **remove**: Removes the scratchpad on that slot + ```sh bind : modifier + modifier + ... + key : action ``` -- **Modifiers**: `mod`, `shift`, `ctrl`, `alt`, `super` -- **Key**: Case-insensitive keysym (e.g., `Return`, `q`, `1`) -- **Action**: Either an external command (in quotes) or internal function. +```sh +scratchpad : modifier + ... + key : create n +scratchpad : modifier + ... + key : toggle n +scratchpad : modifier + ... + key : remove n +``` ```sh workspace : modifier + modifier + ... + key : move n workspace : modifier + modifier + ... + key : swap n ``` -- **Modifiers**: `mod`, `shift`, `ctrl`, `alt`, `super` -- **Key**: Case-insensitive keysym (e.g., `Return`, `q`, `1`) -- **move**: Move to that worspace -- **swap**: Swap window to that workspace -- **n**: Workspace number ### Available Functions @@ -141,6 +150,11 @@ bind : mod + Return : "st" # Close window bind : mod + shift + q : close_window +# Scratchpads +scratchpad : mod + ctrl + Return : create 1 +scratchpad : mod + shift + b : toggle 2 +scratchpad : mod + alt + b : remove 2 + # Switch workspace workspace : mod + 3 : move 3 # Move window to workspace @@ -171,6 +185,14 @@ workspace : mod + shift + 5 : swap 5 | `MOD` + `Shift` + `e` | Quit sxwm | | `MOD` + `r` | Reload configuration | +### Scratchpads + +| Combo | Action | +| ---------------------------------- | -------------------------------- | +| `MOD` + `Alt` + `1–4` | Create scratchpad 1–5 | +| `MOD` + `Ctrl` + `1–4` | Toggle scratchpad 1–5 | +| `MOD` + `Alt` + `Shift` + `1–4` | Remove scratchpad 1–5 | + ### Workspaces | Combo | Action | diff --git a/default_sxwmrc b/default_sxwmrc index 746912c..7128526 100644 --- a/default_sxwmrc +++ b/default_sxwmrc @@ -69,6 +69,22 @@ call : mod + shift + f : fullscreen # Reload Config call : mod + r : reload_config +# Scratchpads +scratchpad : mod + alt + 1 : create 1 +scratchpad : mod + alt + 2 : create 2 +scratchpad : mod + alt + 3 : create 3 +scratchpad : mod + alt + 4 : create 4 + +scratchpad : mod + ctrl + 1 : toggle 1 +scratchpad : mod + ctrl + 2 : toggle 2 +scratchpad : mod + ctrl + 3 : toggle 3 +scratchpad : mod + ctrl + 4 : toggle 4 + +scratchpad : mod + alt + shift + 1 : remove 1 +scratchpad : mod + alt + shift + 2 : remove 2 +scratchpad : mod + alt + shift + 3 : remove 3 +scratchpad : mod + alt + shift + 4 : remove 4 + # Workspaces (1-9) workspace : mod + 1 : move 1 workspace : mod + shift + 1 : swap 1 diff --git a/src/config.h b/src/config.h index 3c0f0bc..e257da3 100644 --- a/src/config.h +++ b/src/config.h @@ -40,22 +40,40 @@ const Binding binds[] = { {Mod4Mask, XK_r, {.fn = reload_config}, TYPE_FUNC}, - {Mod4Mask, XK_1, {.ws = 0}, TYPE_CWKSP}, - {Mod4Mask | ShiftMask, XK_1, {.ws = 0}, TYPE_MWKSP}, - {Mod4Mask, XK_2, {.ws = 1}, TYPE_CWKSP}, - {Mod4Mask | ShiftMask, XK_2, {.ws = 1}, TYPE_MWKSP}, - {Mod4Mask, XK_3, {.ws = 2}, TYPE_CWKSP}, - {Mod4Mask | ShiftMask, XK_3, {.ws = 2}, TYPE_MWKSP}, - {Mod4Mask, XK_4, {.ws = 3}, TYPE_CWKSP}, - {Mod4Mask | ShiftMask, XK_4, {.ws = 3}, TYPE_MWKSP}, - {Mod4Mask, XK_5, {.ws = 4}, TYPE_CWKSP}, - {Mod4Mask | ShiftMask, XK_5, {.ws = 4}, TYPE_MWKSP}, - {Mod4Mask, XK_6, {.ws = 5}, TYPE_CWKSP}, - {Mod4Mask | ShiftMask, XK_6, {.ws = 5}, TYPE_MWKSP}, - {Mod4Mask, XK_7, {.ws = 6}, TYPE_CWKSP}, - {Mod4Mask | ShiftMask, XK_7, {.ws = 6}, TYPE_MWKSP}, - {Mod4Mask, XK_8, {.ws = 7}, TYPE_CWKSP}, - {Mod4Mask | ShiftMask, XK_8, {.ws = 7}, TYPE_MWKSP}, - {Mod4Mask, XK_9, {.ws = 8}, TYPE_CWKSP}, - {Mod4Mask | ShiftMask, XK_9, {.ws = 8}, TYPE_MWKSP}, + {Mod4Mask | Mod1Mask, XK_1, {.sp = 0}, TYPE_SP_CREATE}, + {Mod4Mask | Mod1Mask, XK_2, {.sp = 1}, TYPE_SP_CREATE}, + {Mod4Mask | Mod1Mask, XK_3, {.sp = 2}, TYPE_SP_CREATE}, + {Mod4Mask | Mod1Mask, XK_4, {.sp = 3}, TYPE_SP_CREATE}, + {Mod4Mask | Mod1Mask, XK_5, {.sp = 4}, TYPE_SP_CREATE}, + + {Mod4Mask | ControlMask, XK_1, {.sp = 0}, TYPE_SP_TOGGLE}, + {Mod4Mask | ControlMask, XK_2, {.sp = 1}, TYPE_SP_TOGGLE}, + {Mod4Mask | ControlMask, XK_3, {.sp = 2}, TYPE_SP_TOGGLE}, + {Mod4Mask | ControlMask, XK_4, {.sp = 3}, TYPE_SP_TOGGLE}, + {Mod4Mask | ControlMask, XK_5, {.sp = 4}, TYPE_SP_TOGGLE}, + + {Mod4Mask | Mod1Mask | ShiftMask, XK_1, {.sp = 0}, TYPE_SP_REMOVE}, + {Mod4Mask | Mod1Mask | ShiftMask, XK_2, {.sp = 1}, TYPE_SP_REMOVE}, + {Mod4Mask | Mod1Mask | ShiftMask, XK_3, {.sp = 2}, TYPE_SP_REMOVE}, + {Mod4Mask | Mod1Mask | ShiftMask, XK_4, {.sp = 3}, TYPE_SP_REMOVE}, + {Mod4Mask | Mod1Mask | ShiftMask, XK_5, {.sp = 4}, TYPE_SP_REMOVE}, + + {Mod4Mask, XK_1, {.ws = 0}, TYPE_WS_CHANGE}, + {Mod4Mask | ShiftMask, XK_1, {.ws = 0}, TYPE_WS_MOVE}, + {Mod4Mask, XK_2, {.ws = 1}, TYPE_WS_CHANGE}, + {Mod4Mask | ShiftMask, XK_2, {.ws = 1}, TYPE_WS_MOVE}, + {Mod4Mask, XK_3, {.ws = 2}, TYPE_WS_CHANGE}, + {Mod4Mask | ShiftMask, XK_3, {.ws = 2}, TYPE_WS_MOVE}, + {Mod4Mask, XK_4, {.ws = 3}, TYPE_WS_CHANGE}, + {Mod4Mask | ShiftMask, XK_4, {.ws = 3}, TYPE_WS_MOVE}, + {Mod4Mask, XK_5, {.ws = 4}, TYPE_WS_CHANGE}, + {Mod4Mask | ShiftMask, XK_5, {.ws = 4}, TYPE_WS_MOVE}, + {Mod4Mask, XK_6, {.ws = 5}, TYPE_WS_CHANGE}, + {Mod4Mask | ShiftMask, XK_6, {.ws = 5}, TYPE_WS_MOVE}, + {Mod4Mask, XK_7, {.ws = 6}, TYPE_WS_CHANGE}, + {Mod4Mask | ShiftMask, XK_7, {.ws = 6}, TYPE_WS_MOVE}, + {Mod4Mask, XK_8, {.ws = 7}, TYPE_WS_CHANGE}, + {Mod4Mask | ShiftMask, XK_8, {.ws = 7}, TYPE_WS_MOVE}, + {Mod4Mask, XK_9, {.ws = 8}, TYPE_WS_CHANGE}, + {Mod4Mask | ShiftMask, XK_9, {.ws = 8}, TYPE_WS_MOVE}, }; diff --git a/src/defs.h b/src/defs.h index ae60c94..18c7e2b 100644 --- a/src/defs.h +++ b/src/defs.h @@ -20,15 +20,23 @@ #define UDIST(a, b) abs((int)(a) - (int)(b)) #define CLAMP(x, lo, hi) (((x) < (lo)) ? (lo) : ((x) > (hi)) ? (hi) : (x)) #define MAXCLIENTS 99 +#define MAX_SCRATCHPADS 20 #define MIN_WINDOW_SIZE 20 #define BIND(mod, key, cmdstr) {(mod), XK_##key, {cmdstr}, False} #define CALL(mod, key, fnptr) {(mod), XK_##key, {.fn = fnptr}, True} #define CMD(name, ...) const char *name[] = {__VA_ARGS__, NULL} -#define TYPE_CWKSP 0 -#define TYPE_MWKSP 1 +/* workspaces */ +#define TYPE_FUNC 2 +#define TYPE_WS_CHANGE 0 +#define TYPE_WS_MOVE 1 +/* fn/cmd */ #define TYPE_FUNC 2 #define TYPE_CMD 3 +/* scratchpads*/ +#define TYPE_SP_REMOVE 4 +#define TYPE_SP_TOGGLE 5 +#define TYPE_SP_CREATE 6 #define NUM_WORKSPACES 9 #define WORKSPACE_NAMES \ @@ -43,13 +51,13 @@ "9" "\0" typedef enum { DRAG_NONE, DRAG_MOVE, DRAG_RESIZE, DRAG_SWAP } DragMode; - typedef void (*EventHandler)(XEvent *); typedef union { const char **cmd; void (*fn)(void); - int ws; + int ws; /* workspace */ + int sp; /* scratchpad */ } Action; typedef struct { @@ -95,7 +103,8 @@ typedef struct { Binding binds[256]; char **should_float[256]; char **can_swallow[256]; - char **can_be_swallowed[256]; + char **can_be_swallowed[256]; + char **scratchpads[32]; char *torun[256]; } Config; @@ -104,6 +113,11 @@ typedef struct { int w, h; } Monitor; +typedef struct { + Client *client; + Bool enabled; +} Scratchpad; + extern void close_focused(void); extern void dec_gaps(void); extern void focus_next(void); @@ -124,4 +138,4 @@ extern void resize_stack_add(void); extern void resize_stack_sub(void); extern void toggle_floating(void); extern void toggle_floating_global(void); -extern void toggle_fullscreen(void); +extern void toggle_fullscreen(void); \ No newline at end of file diff --git a/src/parser.c b/src/parser.c index c8f748d..b230fa6 100644 --- a/src/parser.c +++ b/src/parser.c @@ -368,17 +368,58 @@ found: int n; if (sscanf(act, "move %d", &n) == 1 && n >= 1 && n <= NUM_WORKSPACES) { - b->type = TYPE_CWKSP; + b->type = TYPE_WS_CHANGE; b->action.ws = n - 1; } else if (sscanf(act, "swap %d", &n) == 1 && n >= 1 && n <= NUM_WORKSPACES) { - b->type = TYPE_MWKSP; + b->type = TYPE_WS_MOVE; b->action.ws = n - 1; } else { fprintf(stderr, "sxwmrc:%d: invalid workspace action '%s'\n", lineno, act); } } + else if (!strcmp(key, "scratchpad")) { + char *mid = strchr(rest, ':'); + if (!mid) { + fprintf(stderr, "sxwmrc:%d: scratchpad missing action\n", lineno); + continue; + } + *mid = '\0'; + char *combo = strip(rest); + char *act = strip(mid + 1); + + KeySym ks; + unsigned mods = parse_combo(combo, cfg, &ks); + if (ks == NoSymbol) { + fprintf(stderr, "sxwmrc:%d: bad key in '%s'\n", lineno, combo); + continue; + } + + Binding *b = alloc_bind(cfg, mods, ks); + if (!b) { + fputs("sxwm: too many binds\n", stderr); + goto cleanup_file; + } + + int padnum = -1; + + if (sscanf(act, "create %d", &padnum) == 1 && padnum >= 1 && padnum <= MAX_SCRATCHPADS) { + b->type = TYPE_SP_CREATE; + b->action.sp = padnum - 1; + } + else if (sscanf(act, "toggle %d", &padnum) == 1 && padnum >= 1 && padnum <= MAX_SCRATCHPADS) { + b->type = TYPE_SP_TOGGLE; + b->action.sp = padnum - 1; + } + else if (sscanf(act, "remove %d", &padnum) == 1 && padnum >= 1 && padnum <= MAX_SCRATCHPADS) { + b->type = TYPE_SP_REMOVE; + b->action.sp = padnum - 1; + } + else { + fprintf(stderr, "sxwmrc:%d: invalid scratchpad action '%s'\n", lineno, act); + } + } else if (!strcmp(key, "exec")) { if (torun >= 256) { fprintf(stderr, "sxwmrc:%d: too many exec commands\n", lineno); diff --git a/src/sxwm.c b/src/sxwm.c index 7b35b09..8e9b2c4 100644 --- a/src/sxwm.c +++ b/src/sxwm.c @@ -71,6 +71,7 @@ int other_wm_err(Display *dpy, XErrorEvent *ee); /* long parse_col(const char *hex); */ /* void quit(void); */ /* void reload_config(void); */ +void remove_scratchpad(int n); /* void resize_master_add(void); */ /* void resize_master_sub(void); */ /* void resize_stack_add(void); */ @@ -80,6 +81,7 @@ void scan_existing_windows(void); void send_wm_take_focus(Window w); void setup(void); void setup_atoms(void); +void set_win_scratchpad(int n); int snap_coordinate(int pos, int size, int screen_size, int snap_dist); void spawn(const char **argv); void startup_exec(void); @@ -89,6 +91,7 @@ void tile(void); /* void toggle_floating(void); */ /* void toggle_floating_global(void); */ /* void toggle_fullscreen(void); */ +void toggle_scratchpad(int n); void unswallow_window(Client *c); void update_borders(void); void update_client_desktop_properties(void); @@ -133,6 +136,9 @@ Display *dpy; Window root; Window wm_check_win; Monitor *mons = NULL; +Scratchpad scratchpads[MAX_SCRATCHPADS]; +int scratchpad_count = 0; +int current_scratchpad = 0; int monsn = 0; int current_monitor = 0; Bool global_floating = False; @@ -308,6 +314,14 @@ void close_focused(void) return; } + for (int i = 0; i < MAX_SCRATCHPADS; i++) { + if (scratchpads[i].client == focused) { + scratchpads[i].client = NULL; + scratchpads[i].enabled = False; + break; + } + } + Atom *protos; int n; if (XGetWMProtocols(dpy, focused->win, &protos, &n) && protos) { @@ -668,8 +682,8 @@ void grab_keys(void) for (int i = 0; i < user_config.bindsn; i++) { Binding *b = &user_config.binds[i]; - if ((b->type == TYPE_CWKSP && b->mods != user_config.modkey) || - (b->type == TYPE_MWKSP && b->mods != (user_config.modkey | ShiftMask))) { + if ((b->type == TYPE_WS_CHANGE && b->mods != user_config.modkey) || + (b->type == TYPE_WS_MOVE && b->mods != (user_config.modkey | ShiftMask))) { continue; } @@ -935,14 +949,23 @@ void hdl_keypress(XEvent *xev) b->action.fn(); } break; - case TYPE_CWKSP: + case TYPE_WS_CHANGE: change_workspace(b->action.ws); update_net_client_list(); break; - case TYPE_MWKSP: + case TYPE_WS_MOVE: move_to_workspace(b->action.ws); update_net_client_list(); break; + case TYPE_SP_REMOVE: + remove_scratchpad(b->action.sp); + break; + case TYPE_SP_TOGGLE: + toggle_scratchpad(b->action.sp); + break; + case TYPE_SP_CREATE: + set_win_scratchpad(b->action.sp); + break; } return; } @@ -1747,6 +1770,17 @@ void reload_config(void) update_borders(); } +void remove_scratchpad(int n) +{ + if (scratchpads[n].client == NULL) { + return; + } + + XMapWindow(dpy, scratchpads[n].client->win); + scratchpads[n].client = NULL; + scratchpads[n].enabled = False; +} + void resize_master_add(void) { /* pick the monitor of the focused window (or 0 if none) */ @@ -1980,6 +2014,23 @@ void setup_atoms(void) update_workarea(); } +void set_win_scratchpad(int n) +{ + if (focused == NULL) { + return; + } + + Client *pad_client = focused; + if (scratchpads[n].client != NULL) { + XMapWindow(dpy, scratchpads[n].client->win); + scratchpads[n].enabled = False; + scratchpads[n].client = NULL; + } + scratchpads[n].client = pad_client; + XUnmapWindow(dpy, scratchpads[n].client->win); + scratchpads[n].enabled = False; +} + Bool window_should_float(Window w) { XClassHint ch; @@ -2346,6 +2397,56 @@ void toggle_fullscreen(void) } } +void toggle_scratchpad(int n) +{ + if (scratchpads[n].client == NULL) { + return; + } + + if (scratchpads[n].client->ws != current_ws) { + Client *c = scratchpads[n].client; + + /* unlink from old workspace */ + Client **pp = &workspaces[c->ws]; + while (*pp && *pp != c) { + pp = &(*pp)->next; + } + if (*pp) { + *pp = c->next; + } + + /* link to new workspace */ + c->next = workspaces[current_ws]; + workspaces[current_ws] = c; + + c->ws = current_ws; + c->mon = get_monitor_for(c); + + tile(); + update_borders(); + update_client_desktop_properties(); + update_net_client_list(); + } + + if (scratchpads[n].enabled) { + XUnmapWindow(dpy, scratchpads[n].client->win); + scratchpads[n].enabled = False; + } + else { + XMapWindow(dpy, scratchpads[n].client->win); + XRaiseWindow(dpy, scratchpads[n].client->win); + scratchpads[n].enabled = True; + focused = scratchpads[n].client; + XSetInputFocus(dpy, focused->win, RevertToPointerRoot, CurrentTime); + send_wm_take_focus(scratchpads[n].client->win); + if (user_config.warp_cursor) { + warp_cursor(focused); + } + tile(); + update_borders(); + } +} + void unswallow_window(Client *c) { if (!c || !c->swallower) { @@ -2508,4 +2609,4 @@ int main(int ac, char **av) printf("sxwm: starting...\n"); run(); return 0; -} +} \ No newline at end of file diff --git a/sxwm.1 b/sxwm.1 index fabab4d..889a6ae 100644 --- a/sxwm.1 +++ b/sxwm.1 @@ -13,6 +13,8 @@ sxwm is a lightweight and efficient tiling window manager for X11, designed to b .SH FEATURES Tiling and floating layouts. Nine workspaces with full bar support. +Scratchpads for instant access to floating windows. +Native windwo swallowing. Live configuration reload without restart. Human-friendly configuration file requiring no recompilation. DWM-style master-stack layout. @@ -120,19 +122,28 @@ Keybindings associate key combinations with actions, either running external com They follow this syntax: .TP -.B bind : modifier + modifier + ... + key : action +.B bind : modifier + ... + key : action Modifiers can be mod, shift, ctrl, alt, or super. The key is the final key name (e.g., Return, q, 1, equal, space). Actions can be either a quoted external command or an internal function name. .TP -.B workspace : modifier + modifier + ... + key : move n +.B workspace : modifier + ... + key : move n .TP -.B workspace : modifier + modifier + ... + key : swap n +.B workspace : modifier + ... + key : swap n For workspace switching and moving windows. n is the workspace number (1-9). +.TP +.B scratchpad : modifier + ... + key : create n +.TP +.B scratchpad : modifier + ... + key : toggle n +.TP +.B scratchpad : modifier + ... + key : remove n + +For scratchpad management. create assigns the focused window to scratchpad n, toggle shows/hides scratchpad n, and remove returns the scratchpad window to normal tiling. n is the scratchpad number (1-5). + Example bindings: .TP @@ -151,6 +162,18 @@ Go to workspace 3. .B workspace : mod + shift + 5 : swap 5 Move selected window to workspace 5. +.TP +.B scratchpad : mod + alt + 1 : create 1 +Assign focused window to scratchpad 1. + +.TP +.B scratchpad : mod + ctrl + 1 : toggle 1 +Show or hide scratchpad 1. + +.TP +.B scratchpad : mod + alt + shift + 1 : remove 1 +Remove window from scratchpad 1 and return to normal tiling. + .SH AVAILABLE FUNCTIONS The following internal functions are available for keybindings: @@ -305,6 +328,18 @@ Switch to workspace 1 through 9. .B MOD + Shift + 1 to 9 Move focused window to workspace 1 through 9. +.TP +.B MOD + Alt + 1 to 4 +Create scratchpad 1 through 4. + +.TP +.B MOD + Ctrl + 1 to 4 +Toggle scratchpad 1 through 4. + +.TP +.B MOD + Alt + Shift + 1 to 4 +Remove scratchpad 1 through 4. + .TP .B MOD + j / k Focus next or previous window. @@ -386,9 +421,14 @@ bind : mod + d : "rofi -show drun" workspace : mod + 1 : move 1 workspace : mod + shift + 1 : swap 1 +# Scratchpad management +scratchpad : mod + alt + 1 : create 1 +scratchpad : mod + ctrl + 1 : toggle 1 +scratchpad : mod + alt + shift + 1 : remove 1 + # Window swallowing -can_swallow : Alacritty -can_be_swallowed : sxiv, mpv, zathura +can_swallow : "st" +can_be_swallowed : "sxiv", "mpv", "zathura" .fi .RE -- cgit v1.2.3