diff options
Diffstat (limited to 'src/sxwm.c')
| -rw-r--r-- | src/sxwm.c | 451 |
1 files changed, 430 insertions, 21 deletions
@@ -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) { |
