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.c126
-rw-r--r--src/sxwm.c451
4 files changed, 633 insertions, 71 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 3fc2856..79cf29b 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -1,6 +1,6 @@
#define _POSIX_C_SOURCE 200809L
#include <ctype.h>
-#include <limits.h>
+#include <linux/limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -9,6 +9,7 @@
#include <wordexp.h>
#endif
#include <X11/keysym.h>
+#include <X11/XF86keysym.h>
#include <X11/Xlib.h>
#include "parser.h"
#include "defs.h"
@@ -36,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)
@@ -367,17 +369,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);
@@ -404,6 +447,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);
}
@@ -574,4 +692,4 @@ const char **build_argv(const char *cmd)
free(tmp);
return argv;
#endif
-} \ No newline at end of file
+}
diff --git a/src/sxwm.c b/src/sxwm.c
index 6efa3d0..7c789de 100644
--- a/src/sxwm.c
+++ b/src/sxwm.c
@@ -12,15 +12,16 @@
* will use this will probably be programmers :)
*
* (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>
@@ -36,9 +37,8 @@
#include "defs.h"
#include "parser.h"
-#define MIN_WINDOW_SIZE 20
-
Client *add_client(Window w, int ws);
+/* void centre_window(); */
void change_workspace(int ws);
int clean_mask(int mask);
/* void close_focused(void); */
@@ -47,6 +47,8 @@ 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);
@@ -62,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);
@@ -70,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); */
@@ -79,14 +83,18 @@ 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);
+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);
@@ -130,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;
@@ -162,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++;
@@ -234,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) {
@@ -296,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) {
@@ -621,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,
@@ -636,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;
}
@@ -831,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;
@@ -888,20 +995,54 @@ 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;
+ 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) {
@@ -1022,7 +1163,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;
}
@@ -1057,6 +1199,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) {
@@ -1066,6 +1213,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;
@@ -1293,10 +1501,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;
@@ -1304,6 +1519,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++) {
@@ -1318,6 +1534,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) {
@@ -1475,8 +1732,11 @@ void reload_config(void)
{
puts("sxwm: reloading 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;
@@ -1484,14 +1744,62 @@ void reload_config(void)
user_config.binds[i].mods = 0;
}
- memset(&user_config, 0, sizeof(user_config));
+ /* 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++) {
@@ -1499,16 +1807,12 @@ void reload_config(void)
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);
@@ -1523,12 +1827,23 @@ void reload_config(void)
update_client_desktop_properties();
update_net_client_list();
-
XSync(dpy, False);
+
tile();
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) */
@@ -1676,6 +1991,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);
@@ -1761,6 +2077,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;
@@ -2177,6 +2510,82 @@ 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;
+ focus_prev();
+ send_wm_take_focus(focused->win);
+ update_borders();
+ }
+ 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) {
+ return;
+ }
+
+ Client *swallower = c->swallower;
+
+ /* unlink windows */
+ swallower->swallowed = NULL;
+ c->swallower = NULL;
+
+ 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) {