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