diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/config.h | 54 | ||||
| -rw-r--r-- | src/defs.h | 73 | ||||
| -rw-r--r-- | src/parser.c | 240 | ||||
| -rw-r--r-- | src/sxwm.c | 839 |
4 files changed, 961 insertions, 245 deletions
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}, }; @@ -1,13 +1,13 @@ /* See LICENSE for more information on use */ #pragma once #include <X11/Xlib.h> -#define SXWM_VERSION "sxwm ver. 1.6" -#define SXWM_AUTHOR "(C) Abhinav Prasai 2025" -#define SXWM_LICINFO "See LICENSE for more info" +#define SXWM_VERSION "sxwm ver. 1.6" +#define SXWM_AUTHOR "(C) Abhinav Prasai 2025" +#define SXWM_LICINFO "See LICENSE for more info" -#define ALT Mod1Mask -#define SUPER Mod4Mask -#define SHIFT ShiftMask +#define ALT Mod1Mask +#define SUPER Mod4Mask +#define SHIFT ShiftMask #define MARGIN (gaps + BORDER_WIDTH) #define OUT_IN (2 * BORDER_WIDTH) @@ -17,18 +17,26 @@ #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define LENGTH(X) (sizeof X / sizeof X[0]) -#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 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 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 -#define TYPE_FUNC 2 -#define TYPE_CMD 3 +/* 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 \ @@ -40,21 +48,16 @@ "6" "\0"\ "7" "\0"\ "8" "\0"\ - "9" "\0"\ - -typedef enum { - DRAG_NONE, - DRAG_MOVE, - DRAG_RESIZE, - DRAG_SWAP -} DragMode; + "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 { @@ -64,7 +67,7 @@ typedef struct { int type; } Binding; -typedef struct Client{ +typedef struct Client { Window win; int x, y, h, w; int orig_x, orig_y, orig_w, orig_h; @@ -75,7 +78,10 @@ typedef struct Client{ Bool floating; Bool fullscreen; Bool mapped; + pid_t pid; struct Client *next; + struct Client *swallowed; + struct Client *swallower; } Client; typedef struct { @@ -93,9 +99,14 @@ typedef struct { int bindsn; Bool new_win_focus; Bool warp_cursor; + Bool new_win_master; Binding binds[256]; char **should_float[256]; - char *torun[256]; + char **can_swallow[256]; + char **can_be_swallowed[256]; + char **scratchpads[32]; + char **open_in_workspace[256]; + char *torun[256]; } Config; typedef struct { @@ -103,6 +114,12 @@ typedef struct { int w, h; } Monitor; +typedef struct { + Client *client; + Bool enabled; +} Scratchpad; + +extern void centre_window(); extern void close_focused(void); extern void dec_gaps(void); extern void focus_next(void); diff --git a/src/parser.c b/src/parser.c index cb01d78..713a298 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1,14 +1,16 @@ #define _POSIX_C_SOURCE 200809L #include <ctype.h> -#include <limits.h> +#include <linux/limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> +#ifdef __linux__ #include <wordexp.h> +#endif #include <X11/keysym.h> +#include <X11/XF86keysym.h> #include <X11/Xlib.h> - #include "parser.h" #include "defs.h" @@ -19,9 +21,13 @@ static const struct { {"decrease_gaps", dec_gaps}, {"focus_next", focus_next}, {"focus_prev", focus_prev}, + {"focus_next_mon", focus_next_mon}, + {"focus_prev_mon", focus_prev_mon}, {"increase_gaps", inc_gaps}, {"master_next", move_master_next}, {"master_previous", move_master_prev}, + {"move_next_mon", move_next_mon}, + {"move_prev_mon", move_prev_mon}, {"quit", quit}, {"reload_config", reload_config}, {"master_increase", resize_master_add}, @@ -31,6 +37,7 @@ static const struct { {"toggle_floating", toggle_floating}, {"global_floating", toggle_floating_global}, {"fullscreen", toggle_fullscreen}, + {"centre_window", centre_window}, {NULL, NULL}}; static void remap_and_dedupe_binds(Config *cfg) @@ -100,9 +107,6 @@ static unsigned parse_combo(const char *combo, Config *cfg, KeySym *out_ks) } buf[sizeof buf - 1] = '\0'; for (char *tok = strtok(buf, "+"); tok; tok = strtok(NULL, "+")) { - for (char *q = tok; *q; q++) { - *q = tolower((unsigned char)*q); - } if (!strcmp(tok, "mod")) { m |= cfg->modkey; } @@ -119,6 +123,7 @@ static unsigned parse_combo(const char *combo, Config *cfg, KeySym *out_ks) m |= Mod4Mask; } else { + ks = XStringToKeysym(tok); ks = parse_keysym(tok); } } @@ -178,11 +183,9 @@ found: /* Initialize should_float matrix */ for (int j = 0; j < 256; j++) { - cfg->should_float[j] = calloc(1, sizeof(char *)); + cfg->should_float[j] = calloc(2, sizeof(char *)); if (!cfg->should_float[j]) { - fprintf(stderr, "calloc failed\n"); - fclose(f); - return -1; + goto cleanup_file; } } @@ -364,17 +367,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); @@ -401,6 +445,81 @@ found: } torun++; } + else if (!strcmp(key, "can_swallow")) { + char *token = strtok(rest, ","); + int i = 0; + while (token && i < 256) { + char *item = strip_quotes(strip(token)); + if (*item) { + cfg->can_swallow[i] = malloc(2 * sizeof(char *)); + if (!cfg->can_swallow[i]) { + fprintf(stderr, "sxwmrc:%d: malloc failed\n", lineno); + break; + } + cfg->can_swallow[i][0] = strdup(item); + cfg->can_swallow[i][1] = NULL; + i++; + } + token = strtok(NULL, ","); + } + } + else if (!strcmp(key, "can_be_swallowed")) { + char *token = strtok(rest, ","); + int i = 0; + while (token && i < 256) { + char *item = strip_quotes(strip(token)); + if (*item) { + cfg->can_be_swallowed[i] = malloc(2 * sizeof(char *)); + if (!cfg->can_be_swallowed[i]) { + break; + } + cfg->can_be_swallowed[i][0] = strdup(item); + cfg->can_be_swallowed[i][1] = NULL; + i++; + } + token = strtok(NULL, ","); + } + } + else if (!strcmp(key, "new_win_master")) { + cfg->new_win_master = !strcmp(rest, "true") ? True : False; + } + else if (!strcmp(key, "open_in_workspace")) { + char *mid = strchr(rest, ':'); + if (!mid) { + fprintf(stderr, "sxwmrc:%d: open_in_workspace missing workspace number\n", lineno); + continue; + } + *mid = '\0'; + char *class_name = strip(rest); + char *ws_str = strip(mid + 1); + + class_name = strip_quotes(class_name); + + int ws = atoi(ws_str); + if (ws < 1 || ws > NUM_WORKSPACES) { + fprintf(stderr, "sxwmrc:%d: invalid workspace number %d\n", lineno, ws); + continue; + } + + /* find free slot in open_in_workspace */ + int slot = -1; + for (int i = 0; i < 256; i++) { + if (!cfg->open_in_workspace[i]) { + slot = i; + break; + } + } + + if (slot >= 0) { + cfg->open_in_workspace[slot] = malloc(2 * sizeof(char *)); + if (cfg->open_in_workspace[slot]) { + cfg->open_in_workspace[slot][0] = strdup(class_name); /* class name */ + char ws_buf[16]; + snprintf(ws_buf, sizeof(ws_buf), "%d", ws - 1); /* 0-indexed workspace */ + cfg->open_in_workspace[slot][1] = strdup(ws_buf); /* workspace number */ + } + } + } else { fprintf(stderr, "sxwmrc:%d: unknown option '%s'\n", lineno, key); } @@ -419,6 +538,19 @@ cleanup_file: free(cfg->should_float[j][0]); free(cfg->should_float[j]); } + if (cfg->can_swallow[j]) { + free(cfg->can_swallow[j][0]); + free(cfg->can_swallow[j]); + } + if (cfg->can_be_swallowed[j]) { + free(cfg->can_be_swallowed[j][0]); + free(cfg->can_be_swallowed[j]); + } + if (cfg->open_in_workspace[j]) { + free(cfg->open_in_workspace[j][0]); + free(cfg->open_in_workspace[j][1]); + free(cfg->open_in_workspace[j]); + } } for (int i = 0; i < torun; i++) { free(cfg->torun[i]); @@ -468,15 +600,75 @@ KeySym parse_keysym(const char *key) return NoSymbol; } +#ifndef __linux__ +static char **split_cmd(const char *cmd, int *out_argc) +{ + enum { NORMAL, IN_QUOTE } state = NORMAL; + const char *p = cmd; + size_t cap = 8, argc = 0, toklen = 0; + char *token = malloc(strlen(cmd) + 1); + char **argv = malloc(cap * sizeof *argv); + if (!token || !argv) { + goto err; + } + + while (*p) { + if (state == NORMAL && isspace((unsigned char)*p)) { + if (toklen) { + token[toklen] = '\0'; + if (argc + 1 >= cap) { + cap *= 2; + argv = realloc(argv, cap * sizeof *argv); + if (!argv) { + goto err; + } + } + argv[argc++] = strdup(token); + toklen = 0; + } + } + else if (*p == '"') { + state = (state == NORMAL) ? IN_QUOTE : NORMAL; + } + else { + token[toklen++] = *p; + } + p++; + } + + if (toklen) { + token[toklen] = '\0'; + argv[argc++] = strdup(token); + } + argv[argc] = NULL; + *out_argc = argc; + free(token); + return argv; + +err: + if (token) { + free(token); + } + if (argv) { + for (size_t i = 0; i < argc; i++) { + free(argv[i]); + } + free(argv); + } + return NULL; +} +#endif + const char **build_argv(const char *cmd) { +#ifdef __linux__ wordexp_t p; if (wordexp(cmd, &p, 0) != 0 || p.we_wordc == 0) { fprintf(stderr, "sxwm: wordexp failed for cmd: '%s'\n", cmd); return NULL; } - const char **argv = malloc((p.we_wordc + 1) * sizeof(char *)); + const char **argv = malloc((p.we_wordc + 1) * sizeof *argv); if (!argv) { wordfree(&p); return NULL; @@ -486,7 +678,29 @@ const char **build_argv(const char *cmd) argv[i] = strdup(p.we_wordv[i]); } argv[p.we_wordc] = NULL; - wordfree(&p); return argv; +#else + int argc = 0; + char **tmp = split_cmd(cmd, &argc); + if (!tmp) { + return NULL; + } + + const char **argv = malloc((argc + 1) * sizeof *argv); + if (!argv) { + for (int i = 0; i < argc; i++) { + free(tmp[i]); + } + free(tmp); + return NULL; + } + + for (int i = 0; i < argc; i++) { + argv[i] = tmp[i]; + } + argv[argc] = NULL; + free(tmp); + return argv; +#endif } @@ -1,26 +1,27 @@ /* - * See LICENSE for more info + * See LICENSE for more info * - * simple xorg window manager - * sxwm is a user-friendly, easily configurable yet powerful - * tiling window manager inspired by window managers such as - * DWM and i3. + * simple xorg window manager + * sxwm is a user-friendly, easily configurable yet powerful + * tiling window manager inspired by window managers such as + * DWM and i3. * - * The userconfig is designed to be as user-friendly as - * possible, and I hope it is easy to configure even without - * knowledge of C or programming, although most people who - * will use this will probably be programmers :) + * The userconfig is designed to be as user-friendly as + * possible, and I hope it is easy to configure even without + * knowledge of C or programming, although most people who + * will use this will probably be programmers :) * - * (C) Abhinav Prasai 2025 - */ + * (C) Abhinav Prasai 2025 +*/ #include <X11/X.h> #include <err.h> #include <stdio.h> -#include <limits.h> +#include <linux/limits.h> #include <signal.h> #include <stdlib.h> #include <string.h> +#include <strings.h> #include <sys/wait.h> #include <unistd.h> @@ -37,15 +38,17 @@ #include "parser.h" Client *add_client(Window w, int ws); +/* void centre_window(); */ void change_workspace(int ws); int clean_mask(int mask); /* void close_focused(void); */ /* void dec_gaps(void); */ -void startup_exec(void); Window find_toplevel(Window w); /* void focus_next(void); */ /* void focus_prev(void); */ int get_monitor_for(Client *c); +pid_t get_pid(Window w); +int get_workspace_for_window(Window w); void grab_keys(void); void hdl_button(XEvent *xev); void hdl_button_release(XEvent *xev); @@ -61,6 +64,7 @@ void hdl_root_property(XEvent *xev); void hdl_unmap_ntf(XEvent *xev); /* void inc_gaps(void); */ void init_defaults(void); +Bool is_child_proc(pid_t pid1, pid_t pid2); /* void move_master_next(void); */ /* void move_master_prev(void); */ void move_to_workspace(int ws); @@ -69,6 +73,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); */ @@ -78,13 +83,18 @@ void scan_existing_windows(void); void send_wm_take_focus(Window w); void setup(void); void setup_atoms(void); -Bool window_should_float(Window w); +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); +void swallow_window(Client *swallower, Client *swallowed); void swap_clients(Client *a, Client *b); 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); void update_monitors(void); @@ -92,6 +102,7 @@ void update_net_client_list(void); void update_struts(void); void update_workarea(void); void warp_cursor(Client *c); +Bool window_should_float(Window w); int xerr(Display *dpy, XErrorEvent *ee); void xev_case(XEvent *xev); #include "config.h" @@ -127,6 +138,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; @@ -159,16 +173,25 @@ Client *add_client(Window w, int ws) c->win = w; c->next = NULL; c->ws = ws; + c->pid = get_pid(w); + c->swallowed = NULL; + c->swallower = NULL; if (!workspaces[ws]) { workspaces[ws] = c; } else { - Client *tail = workspaces[ws]; - while (tail->next) { - tail = tail->next; + if (user_config.new_win_master) { + c->next = workspaces[ws]; + workspaces[ws] = c; + } + else { + Client *tail = workspaces[ws]; + while (tail->next) { + tail = tail->next; + } + tail->next = c; } - tail->next = c; } open_windows++; @@ -231,6 +254,20 @@ Client *add_client(Window w, int ws) return c; } +void centre_window() +{ + if (!focused || !focused->mapped || !focused->floating) { + return; + } + + int x = mons[focused->mon].x + (mons[focused->mon].w - focused->w) / 2; + int y = mons[focused->mon].y + (mons[focused->mon].h - focused->h) / 2; + + focused->x = x; + focused->y = y; + XMoveWindow(dpy, focused->win, x, y); +} + void change_workspace(int ws) { if (ws >= NUM_WORKSPACES || ws == current_ws) { @@ -293,6 +330,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) { @@ -366,47 +411,73 @@ Window find_toplevel(Window w) void focus_next(void) { - if (!focused || !workspaces[current_ws]) { + if (!workspaces[current_ws]) { return; } - focused = (focused->next ? focused->next : workspaces[current_ws]); - current_monitor = focused->mon; - XSetInputFocus(dpy, focused->win, RevertToPointerRoot, CurrentTime); - XRaiseWindow(dpy, focused->win); - if (user_config.warp_cursor) { - warp_cursor(focused); + Client *start = focused ? focused : workspaces[current_ws]; + Client *c = start; + + /* loop until we find a mapped client or return to start */ + do { + c = c->next ? c->next : workspaces[current_ws]; + } while (!c->mapped && c != start); + + /* this stops invisible windows being detected or focused */ + if (!c->mapped) { + return; } + + focused = c; + current_monitor = c->mon; + XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); + XRaiseWindow(dpy, c->win); + if (user_config.warp_cursor) + warp_cursor(c); update_borders(); } void focus_prev(void) { - if (!focused || !workspaces[current_ws]) { + if (!workspaces[current_ws]) { return; } - Client *p = workspaces[current_ws], *prev = NULL; - while (p && p != focused) { - prev = p; - p = p->next; - } + Client *start = focused ? focused : workspaces[current_ws]; + Client *c = start; - if (!prev) { - while (p->next) { + /* loop until we find a mapped client or return to start */ + do { + Client *p = workspaces[current_ws], *prev = NULL; + while (p && p != c) { + prev = p; p = p->next; } - focused = p; - } - else { - focused = prev; + + if (prev) { + c = prev; + } + else { + /* wrap to tail */ + p = workspaces[current_ws]; + while (p->next) + p = p->next; + c = p; + } + } while (!c->mapped && c != start); + + /* this stops invisible windows being detected or focused */ + if (!c->mapped) { + return; } - current_monitor = focused->mon; - XSetInputFocus(dpy, focused->win, RevertToPointerRoot, CurrentTime); - XRaiseWindow(dpy, focused->win); + focused = c; + current_monitor = c->mon; + + XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); + XRaiseWindow(dpy, c->win); if (user_config.warp_cursor) { - warp_cursor(focused); + warp_cursor(c); } update_borders(); } @@ -592,6 +663,56 @@ int get_monitor_for(Client *c) return 0; } +pid_t get_pid(Window w) +{ + pid_t pid = 0; + Atom actual_type; + int actual_format; + unsigned long nitems, bytes_after; + unsigned char *prop = NULL; + Atom atom_pid = XInternAtom(dpy, "_NET_WM_PID", False); + + if (XGetWindowProperty(dpy, w, atom_pid, 0, 1, False, XA_CARDINAL, &actual_type, &actual_format, &nitems, + &bytes_after, &prop) == Success && + prop) { + if (actual_format == 32 && nitems == 1) { + pid = *(pid_t *)prop; + } + XFree(prop); + } + return pid; +} + +int get_workspace_for_window(Window w) +{ + XClassHint ch; + if (!XGetClassHint(dpy, w, &ch)) { + return current_ws; /* default to current workspace */ + } + + for (int i = 0; i < 256; i++) { + if (!user_config.open_in_workspace[i]) { + break; + } + + char *rule_class = user_config.open_in_workspace[i][0]; + char *rule_ws = user_config.open_in_workspace[i][1]; + + if (rule_class && rule_ws) { + if ((ch.res_class && strcasecmp(ch.res_class, rule_class) == 0) || + (ch.res_name && strcasecmp(ch.res_name, rule_class) == 0)) { + XFree(ch.res_class); + XFree(ch.res_name); + return atoi(rule_ws); + } + } + } + + XFree(ch.res_class); + XFree(ch.res_name); + return current_ws; /* default to current workspace */ +} + void grab_keys(void) { const int guards[] = {0, @@ -607,8 +728,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; } @@ -802,6 +923,21 @@ void hdl_destroy_ntf(XEvent *xev) c = c->next; } if (c) { + if (c->swallower) { + unswallow_window(c); + } + + if (c->swallowed) { + Client *swallowed = c->swallowed; + c->swallowed = NULL; + swallowed->swallower = NULL; + + /* show swallowed window */ + XMapWindow(dpy, swallowed->win); + swallowed->mapped = True; + focused = swallowed; + } + if (focused == c) { if (c->next) { focused = c->next; @@ -859,20 +995,57 @@ 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; } } } +void swallow_window(Client *swallower, Client *swallowed) +{ + if (!swallower || !swallowed || swallower->swallowed || swallowed->swallower) { + return; + } + + XUnmapWindow(dpy, swallower->win); + swallower->mapped = False; + + swallower->swallowed = swallowed; + swallowed->swallower = swallower; + + swallowed->floating = swallower->floating; + if (swallowed->floating) { + swallowed->x = swallower->x; + swallowed->y = swallower->y; + swallowed->w = swallower->w; + swallowed->h = swallower->h; + + if (swallowed->win) { + XMoveResizeWindow(dpy, swallowed->win, swallowed->x, swallowed->y, swallowed->w, swallowed->h); + } + } + + tile(); + update_borders(); +} + void swap_clients(Client *a, Client *b) { if (!a || !b || a == b) { @@ -993,7 +1166,8 @@ void hdl_map_req(XEvent *xev) return; } - Client *c = add_client(w, current_ws); + int target_ws = get_workspace_for_window(w); + Client *c = add_client(w, target_ws); if (!c) { return; } @@ -1028,6 +1202,11 @@ void hdl_map_req(XEvent *xev) XSetWindowBorderWidth(dpy, w, user_config.border_width); } + if (target_ws != current_ws) { + update_net_client_list(); + return; + } + /* map & borders */ update_net_client_list(); if (!global_floating && !c->floating) { @@ -1037,6 +1216,67 @@ void hdl_map_req(XEvent *xev) XRaiseWindow(dpy, w); } + /* check for swallowing opportunities */ + { + XClassHint ch; + Bool can_be_swallowed = False; + + if (XGetClassHint(dpy, w, &ch)) { + /* check if new window can be swallowed */ + for (int i = 0; i < 256; i++) { + if (!user_config.can_be_swallowed[i] || !user_config.can_be_swallowed[i][0]) { + break; + } + + if ((ch.res_class && strcasecmp(ch.res_class, user_config.can_be_swallowed[i][0]) == 0) || + (ch.res_name && strcasecmp(ch.res_name, user_config.can_be_swallowed[i][0]) == 0)) { + can_be_swallowed = True; + break; + } + } + + /* if window can be swallowed look for a potential swallower */ + if (can_be_swallowed) { + for (Client *p = workspaces[current_ws]; p; p = p->next) { + if (p == c || p->swallowed || !p->mapped) { + continue; + } + + XClassHint pch; + Bool can_swallow = False; + + if (XGetClassHint(dpy, p->win, &pch)) { + /* check if this existing window can swallow others */ + for (int i = 0; i < 256; i++) { + if (!user_config.can_swallow[i] || !user_config.can_swallow[i][0]) { + break; + } + + if ((pch.res_class && strcasecmp(pch.res_class, user_config.can_swallow[i][0]) == 0) || + (pch.res_name && strcasecmp(pch.res_name, user_config.can_swallow[i][0]) == 0)) { + can_swallow = True; + break; + } + } + + /* check process relationship */ + if (can_swallow) { + /* we know class matches — swallow now */ + swallow_window(p, c); + XFree(pch.res_class); + XFree(pch.res_name); + break; + } + XFree(pch.res_class); + XFree(pch.res_name); + } + } + } + XFree(ch.res_class); + XFree(ch.res_name); + } + } + XMapWindow(dpy, w); c->mapped = True; @@ -1067,7 +1307,7 @@ void hdl_motion(XEvent *xev) unsigned int mask; XQueryPointer(dpy, root, &root_ret, &child, &rx, &ry, &wx, &wy, &mask); - Client *last_swap_target = NULL; + static Client *last_swap_target = NULL; Client *new_target = NULL; for (Client *c = workspaces[current_ws]; c; c = c->next) { @@ -1105,19 +1345,8 @@ void hdl_motion(XEvent *xev) int outer_w = drag_client->w + 2 * user_config.border_width; int outer_h = drag_client->h + 2 * user_config.border_width; - if (UDIST(nx, 0) <= user_config.snap_distance) { - nx = 0; - } - else if (UDIST(nx + outer_w, scr_width) <= user_config.snap_distance) { - nx = scr_width - outer_w; - } - - if (UDIST(ny, 0) <= user_config.snap_distance) { - ny = 0; - } - else if (UDIST(ny + outer_h, scr_height) <= user_config.snap_distance) { - ny = scr_height - outer_h; - } + nx = snap_coordinate(nx, outer_w, scr_width, user_config.snap_distance); + ny = snap_coordinate(ny, outer_h, scr_height, user_config.snap_distance); if (!drag_client->floating && (UDIST(nx, drag_client->x) > user_config.snap_distance || UDIST(ny, drag_client->y) > user_config.snap_distance)) { @@ -1134,8 +1363,13 @@ void hdl_motion(XEvent *xev) int dy = e->y_root - drag_start_y; int nw = drag_orig_w + dx; int nh = drag_orig_h + dy; - drag_client->w = nw < 20 ? 20 : nw; - drag_client->h = nh < 20 ? 20 : nh; + + int max_w = scr_width - drag_client->x; + int max_h = scr_height - drag_client->y; + + drag_client->w = CLAMP(nw, MIN_WINDOW_SIZE, max_w); + drag_client->h = CLAMP(nh, MIN_WINDOW_SIZE, max_h); + XResizeWindow(dpy, drag_client->win, drag_client->w, drag_client->h); } } @@ -1270,10 +1504,17 @@ void init_defaults(void) default_config.border_foc_col = parse_col("#c0cbff"); default_config.border_ufoc_col = parse_col("#555555"); default_config.border_swap_col = parse_col("#fff4c0"); + for (int i = 0; i < MAX_MONITORS; i++) { default_config.master_width[i] = 50 / 100.0f; } + for (int i = 0; i < 256; i++) { + default_config.can_be_swallowed[i] = NULL; + default_config.can_swallow[i] = NULL; + default_config.open_in_workspace[i] = NULL; + } + default_config.motion_throttle = 60; default_config.resize_master_amt = 5; default_config.resize_stack_amt = 20; @@ -1281,6 +1522,7 @@ void init_defaults(void) default_config.bindsn = 0; default_config.new_win_focus = True; default_config.warp_cursor = True; + default_config.new_win_master = False; if (backup_binds) { for (unsigned long i = 0; i < LENGTH(binds); i++) { @@ -1295,6 +1537,47 @@ void init_defaults(void) user_config = default_config; } +Bool is_child_proc(pid_t parent_pid, pid_t child_pid) +{ + if (parent_pid <= 0 || child_pid <= 0) { + return False; + } + + char path[PATH_MAX]; + FILE *f; + pid_t current_pid = child_pid; + int max_iterations = 20; + + while (current_pid > 1 && max_iterations-- > 0) { + snprintf(path, sizeof(path), "/proc/%d/stat", current_pid); + f = fopen(path, "r"); + if (!f) { + printf("sxwm: could not open %s\n", path); + return False; + } + + int ppid = 0; + if (fscanf(f, "%*d %*s %*c %d", &ppid) != 1) { + printf("sxwm: failed to read ppid from %s\n", path); + fclose(f); + return False; + } + fclose(f); + + if (ppid == parent_pid) { + return True; + } + + if (ppid <= 1) { /* Reached init or kernel */ + printf("sxwm: reached init/kernel, no relationship found\n"); + break; + } + + current_pid = ppid; + } + return False; +} + void move_master_next(void) { if (!workspaces[current_ws] || !workspaces[current_ws]->next) { @@ -1329,7 +1612,8 @@ void move_master_prev(void) return; } - Client *prev = NULL, *cur = workspaces[current_ws]; + Client *prev = NULL; + Client *cur = workspaces[current_ws]; Client *old_focused = focused; while (cur->next) { @@ -1450,38 +1734,88 @@ void quit(void) void reload_config(void) { puts("sxwm: reloading config..."); - memset(&user_config, 0, sizeof(user_config)); + + /* free binding commands without */ for (int i = 0; i < user_config.bindsn; i++) { - free(user_config.binds[i].action.cmd); + if (user_config.binds[i].type == TYPE_CMD && user_config.binds[i].action.cmd) { + free(user_config.binds[i].action.cmd); + } user_config.binds[i].action.cmd = NULL; - user_config.binds[i].action.fn = NULL; user_config.binds[i].type = -1; user_config.binds[i].keysym = 0; user_config.binds[i].mods = 0; } + /* free swallow-related arrays */ + for (int i = 0; i < 256; i++) { + if (user_config.can_swallow[i]) { + if (user_config.can_swallow[i][0]) { + free(user_config.can_swallow[i][0]); + } + free(user_config.can_swallow[i]); + user_config.can_swallow[i] = NULL; + } + if (user_config.can_be_swallowed[i]) { + if (user_config.can_be_swallowed[i][0]) { + free(user_config.can_be_swallowed[i][0]); + } + free(user_config.can_be_swallowed[i]); + user_config.can_be_swallowed[i] = NULL; + } + if (user_config.open_in_workspace[i]) { + if (user_config.open_in_workspace[i][0]) { + free(user_config.open_in_workspace[i][0]); + } + if (user_config.open_in_workspace[i][1]) { + free(user_config.open_in_workspace[i][1]); + } + free(user_config.open_in_workspace[i]); + user_config.open_in_workspace[i] = NULL; + } + } + + /* free should_float arrays */ + for (int i = 0; i < 256; i++) { + if (user_config.should_float[i]) { + if (user_config.should_float[i][0]) { + free(user_config.should_float[i][0]); + } + free(user_config.should_float[i]); + user_config.should_float[i] = NULL; + } + } + + /* free any exec strings */ + for (int i = 0; i < 256; i++) { + if (user_config.torun[i]) { + free(user_config.torun[i]); + user_config.torun[i] = NULL; + } + } + + /* wipe everything else */ + memset(&user_config, 0, sizeof(user_config)); init_defaults(); if (parser(&user_config)) { - fprintf(stderr, "sxrc: error parsing config file\n"); + fprintf(stderr, "sxwmrc: error parsing config file\n"); init_defaults(); } + + /* regrab all key/button bindings */ grab_keys(); XUngrabButton(dpy, AnyButton, AnyModifier, root); - for (int ws = 0; ws < NUM_WORKSPACES; ws++) { for (Client *c = workspaces[ws]; c; c = c->next) { XUngrabButton(dpy, AnyButton, AnyModifier, c->win); } } - XGrabButton(dpy, Button1, user_config.modkey, root, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None); XGrabButton(dpy, Button1, user_config.modkey | ShiftMask, root, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None); XGrabButton(dpy, Button3, user_config.modkey, root, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None); - for (int ws = 0; ws < NUM_WORKSPACES; ws++) { for (Client *c = workspaces[ws]; c; c = c->next) { XGrabButton(dpy, Button1, 0, c->win, False, ButtonPressMask, GrabModeSync, GrabModeAsync, None, None); @@ -1496,12 +1830,28 @@ void reload_config(void) update_client_desktop_properties(); update_net_client_list(); - XSync(dpy, False); + tile(); update_borders(); } +void remove_scratchpad(int n) +{ + if (n < 0 || n >= MAX_SCRATCHPADS || scratchpads[n].client == NULL) { + return; + } + + Client *c = scratchpads[n].client; + + if (c->win) { + XMapWindow(dpy, c->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) */ @@ -1649,6 +1999,7 @@ void setup(void) GrabModeAsync, GrabModeAsync, None, None); XGrabButton(dpy, Button1, user_config.modkey | ShiftMask, root, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None); + XGrabButton(dpy, Button3, user_config.modkey, root, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None); XSync(dpy, False); @@ -1734,6 +2085,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; @@ -1757,66 +2125,123 @@ Bool window_should_float(Window w) return False; } +int snap_coordinate(int pos, int size, int screen_size, int snap_dist) +{ + if (UDIST(pos, 0) <= snap_dist) { + return 0; + } + if (UDIST(pos + size, screen_size) <= snap_dist) { + return screen_size - size; + } + return pos; +} + void spawn(const char **argv) { - int pipe_idx = -1; - for (int i = 0; argv[i]; i++) { + int argc = 0; + while (argv[argc]) + argc++; + + int cmd_count = 1; + for (int i = 0; i < argc; i++) { if (strcmp(argv[i], "|") == 0) { - pipe_idx = i; - break; + cmd_count++; } } - if (pipe_idx < 0) { - if (fork() == 0) { - close(ConnectionNumber(dpy)); - setsid(); - execvp(argv[0], (char *const *)argv); - fprintf(stderr, "sxwm: execvp '%s' failed\n", argv[0]); - exit(EXIT_FAILURE); + char ***commands = malloc(cmd_count * sizeof(char **)); + if (!commands) { + perror("malloc commands"); + return; + } + + int cmd_idx = 0; + int arg_start = 0; + for (int i = 0; i <= argc; i++) { + if (!argv[i] || strcmp(argv[i], "|") == 0) { + int len = i - arg_start; + char **cmd_args = malloc((len + 1) * sizeof(char *)); + if (!cmd_args) { + perror("malloc cmd_args"); + for (int j = 0; j < cmd_idx; j++) { + free(commands[j]); + } + free(commands); + return; + } + for (int j = 0; j < len; j++) { + cmd_args[j] = (char *)argv[arg_start + j]; + } + cmd_args[len] = NULL; + commands[cmd_idx++] = cmd_args; + arg_start = i + 1; } } - else { - ((char **)argv)[pipe_idx] = NULL; - const char **left = argv; - const char **right = argv + pipe_idx + 1; - int fd[2]; - Bool x = pipe(fd); - (void)x; - - pid_t pid1 = fork(); - if (pid1 == 0) { - dup2(fd[1], STDOUT_FILENO); - close(fd[0]); - close(fd[1]); - execvp(left[0], (char *const *)left); - perror("spawn left"); - exit(EXIT_FAILURE); + + int pipes[cmd_count - 1][2]; + for (int i = 0; i < cmd_count - 1; i++) { + if (pipe(pipes[i]) == -1) { + perror("pipe"); + for (int j = 0; j < cmd_count; j++) { + free(commands[j]); + } + free(commands); + return; } + } + + for (int i = 0; i < cmd_count; i++) { + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + for (int k = 0; k < cmd_count - 1; k++) { + close(pipes[k][0]); + close(pipes[k][1]); + } + for (int j = 0; j < cmd_count; j++) { + free(commands[j]); + } + free(commands); + return; + } + if (pid == 0) { + close(ConnectionNumber(dpy)); + + if (i > 0) { + dup2(pipes[i - 1][0], STDIN_FILENO); + } + if (i < cmd_count - 1) { + dup2(pipes[i][1], STDOUT_FILENO); + } - pid_t pid2 = fork(); - if (pid2 == 0) { - dup2(fd[0], STDIN_FILENO); - close(fd[0]); - close(fd[1]); - execvp(right[0], (char *const *)right); - perror("spawn right"); + for (int k = 0; k < cmd_count - 1; k++) { + close(pipes[k][0]); + close(pipes[k][1]); + } + + execvp(commands[i][0], commands[i]); + fprintf(stderr, "sxwm: execvp '%s' failed\n", commands[i][0]); exit(EXIT_FAILURE); } + } + + for (int i = 0; i < cmd_count - 1; i++) { + close(pipes[i][0]); + close(pipes[i][1]); + } - close(fd[0]); - close(fd[1]); - waitpid(pid1, NULL, 0); - waitpid(pid2, NULL, 0); + for (int i = 0; i < cmd_count; i++) { + free(commands[i]); } + free(commands); } void tile(void) { update_struts(); Client *head = workspaces[current_ws]; - int total = 0; + for (Client *c = head; c; c = c->next) { if (c->mapped && !c->floating && !c->fullscreen) { total++; @@ -1837,7 +2262,7 @@ void tile(void) Client *stackers[MAXCLIENTS]; int N = 0; - for (Client *c = head; c; c = c->next) { + for (Client *c = head; c && N < MAXCLIENTS; c = c->next) { if (c->mapped && !c->floating && !c->fullscreen && c->mon == m) { stackers[N++] = c; } @@ -1863,7 +2288,11 @@ void tile(void) .width = MAX(1, master_w - bw2), .height = MAX(1, tile_h - bw2), .border_width = user_config.border_width}; - XConfigureWindow(dpy, c->win, CWX | CWY | CWWidth | CWHeight | CWBorderWidth, &wc); + + if (c->x != wc.x || c->y != wc.y || c->w != wc.width || c->h != wc.height) { + XConfigureWindow(dpy, c->win, CWX | CWY | CWWidth | CWHeight | CWBorderWidth, &wc); + } + c->x = wc.x; c->y = wc.y; c->w = wc.width; @@ -1875,90 +2304,46 @@ void tile(void) continue; } + int bw2 = 2 * user_config.border_width; int num_stack = N - 1; - int idx_focus = -1; - for (int i = 1; i < N; i++) { - if (stackers[i] == focused) { - idx_focus = i; - } - } - + int min_raw = bw2 + 1; + int total_fixed_heights = 0, auto_count = 0; + int heights_final[MAXCLIENTS] = {0}; Bool is_fixed[MAXCLIENTS] = {0}; - int bw2 = 2 * user_config.border_width; + for (int i = 1; i < N; i++) { if (stackers[i]->custom_stack_height > 0) { is_fixed[i] = True; + total_fixed_heights += stackers[i]->custom_stack_height; } - } - if (idx_focus >= 1 && stackers[idx_focus]->custom_stack_height > 0) { - is_fixed[idx_focus] = True; - } - - int total_fixed_heights = 0; - for (int i = 1; i < N; i++) { - if (!is_fixed[i]) { - continue; - } - int h = stackers[i]->custom_stack_height > 0 ? stackers[i]->custom_stack_height : stackers[i]->h + bw2; - total_fixed_heights += h; - } - - int auto_count = 0; - for (int i = 1; i < N; i++) { - if (!is_fixed[i]) { + else { auto_count++; } } int total_vgaps = (num_stack - 1) * gy; int remaining = tile_h - total_fixed_heights - total_vgaps; - int min_raw = bw2 + 1; - int heights_final[MAXCLIENTS] = {0}; - if (auto_count > 0) { - if (remaining >= auto_count * min_raw) { - - int auto_h = remaining / auto_count, used = 0, count = 0; - for (int i = 1; i < N; i++) { - if (!is_fixed[i]) { - count++; - heights_final[i] = (count < auto_count) ? auto_h : remaining - used; - used += auto_h; - } - } - for (int i = 1; i < N; i++) { - if (is_fixed[i]) { - heights_final[i] = stackers[i]->custom_stack_height > 0 ? stackers[i]->custom_stack_height - : stackers[i]->h + bw2; - } + if (auto_count > 0 && remaining >= auto_count * min_raw) { + int auto_h = remaining / auto_count, used = 0, count = 0; + for (int i = 1; i < N; i++) { + if (!is_fixed[i]) { + count++; + heights_final[i] = (count < auto_count) ? auto_h : remaining - used; + used += auto_h; } - } - else { - for (int i = 1; i < N; i++) { - heights_final[i] = is_fixed[i] - ? (stackers[i]->custom_stack_height > 0 ? stackers[i]->custom_stack_height - : stackers[i]->h + bw2) - : min_raw; + else { + heights_final[i] = stackers[i]->custom_stack_height; } } } else { - int sum_raw = 0; for (int i = 1; i < N; i++) { - sum_raw += - stackers[i]->custom_stack_height > 0 ? stackers[i]->custom_stack_height : stackers[i]->h + bw2; - } - int remaining_slack = tile_h - total_vgaps - sum_raw; - for (int i = 1; i < N; i++) { - int base_h = - stackers[i]->custom_stack_height > 0 ? stackers[i]->custom_stack_height : stackers[i]->h + bw2; - - /* only grow the bottom window if it isn’t fixed */ - if (i == N - 1 && remaining_slack > 0 && stackers[i]->custom_stack_height == 0) { - heights_final[i] = base_h + remaining_slack; + if (is_fixed[i]) { + heights_final[i] = stackers[i]->custom_stack_height; } else { - heights_final[i] = base_h; + heights_final[i] = min_raw; } } } @@ -1967,7 +2352,6 @@ void tile(void) for (int i = 1; i < N; i++) { total_height += heights_final[i]; } - int overfill = total_height - tile_h; if (overfill > 0) { /* shrink from top down, excluding bottom */ @@ -1979,30 +2363,33 @@ void tile(void) } /* if its not perfectly filled stretch bottom to absorb remainder */ - int actual_stack_height = total_vgaps; + int actual_height = total_vgaps; for (int i = 1; i < N; i++) { - actual_stack_height += heights_final[i]; + actual_height += heights_final[i]; } - - int shortfall = tile_h - actual_stack_height; + int shortfall = tile_h - actual_height; if (shortfall > 0) { heights_final[N - 1] += shortfall; } int sy = tile_y; - int bw = user_config.border_width; for (int i = 1; i < N; i++) { Client *c = stackers[i]; XWindowChanges wc = {.x = tile_x + master_w + gx, .y = sy, - .width = MAX(1, stack_w - (2 * bw)), - .height = MAX(1, heights_final[i] - (2 * bw)), - .border_width = bw}; - XConfigureWindow(dpy, c->win, CWX | CWY | CWWidth | CWHeight | CWBorderWidth, &wc); + .width = MAX(1, stack_w - (2 * user_config.border_width)), + .height = MAX(1, heights_final[i] - (2 * user_config.border_width)), + .border_width = user_config.border_width}; + + if (c->x != wc.x || c->y != wc.y || c->w != wc.width || c->h != wc.height) { + XConfigureWindow(dpy, c->win, CWX | CWY | CWWidth | CWHeight | CWBorderWidth, &wc); + } + c->x = wc.x; c->y = wc.y; c->w = wc.width; c->h = wc.height; + sy += heights_final[i] + gy; } @@ -2127,6 +2514,86 @@ void toggle_fullscreen(void) } } +void toggle_scratchpad(int n) +{ + if (n < 0 || n >= MAX_SCRATCHPADS || scratchpads[n].client == NULL) { + return; + } + + Client *c = scratchpads[n].client; + + if (c->ws != current_ws) { + /* 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, c->win); + scratchpads[n].enabled = False; + focus_prev(); + if (focused) { + send_wm_take_focus(focused->win); + } + update_borders(); + } + else { + XMapWindow(dpy, c->win); + XRaiseWindow(dpy, c->win); + scratchpads[n].enabled = True; + focused = c; + XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); + send_wm_take_focus(c->win); + if (user_config.warp_cursor) { + warp_cursor(focused); + } + tile(); + update_borders(); + } +} + +void unswallow_window(Client *c) +{ + if (!c || !c->swallower) { + return; + } + + Client *swallower = c->swallower; + + /* unlink windows */ + swallower->swallowed = NULL; + c->swallower = NULL; + + if (swallower->win) { + XMapWindow(dpy, swallower->win); + swallower->mapped = True; + + focused = swallower; + XSetInputFocus(dpy, swallower->win, RevertToPointerRoot, CurrentTime); + XRaiseWindow(dpy, swallower->win); + } + + tile(); + update_borders(); +} + void update_borders(void) { for (Client *c = workspaces[current_ws]; c; c = c->next) { @@ -2266,4 +2733,4 @@ int main(int ac, char **av) printf("sxwm: starting...\n"); run(); return 0; -}
\ No newline at end of file +} |
