summaryrefslogtreecommitdiff
path: root/src/sxwm.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sxwm.c')
-rw-r--r--src/sxwm.c451
1 files changed, 430 insertions, 21 deletions
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) {