summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/config.h54
-rw-r--r--src/defs.h73
-rw-r--r--src/parser.c240
-rw-r--r--src/sxwm.c839
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},
};
diff --git a/src/defs.h b/src/defs.h
index 74af271..5796214 100644
--- a/src/defs.h
+++ b/src/defs.h
@@ -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
}
diff --git a/src/sxwm.c b/src/sxwm.c
index 68008a2..1819d36 100644
--- a/src/sxwm.c
+++ b/src/sxwm.c
@@ -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
+}