diff options
| -rw-r--r-- | CHANGELOG.md | 23 | ||||
| -rw-r--r-- | CONTRIBUTIONS.md | 90 | ||||
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | default_sxwmrc | 3 | ||||
| -rw-r--r-- | src/defs.h | 2 | ||||
| -rw-r--r-- | src/parser.c | 14 | ||||
| -rw-r--r-- | src/sxwm.c | 154 | ||||
| -rw-r--r-- | sxwm.1 | 28 | ||||
| -rw-r--r-- | sxwm.desktop | 2 |
9 files changed, 250 insertions, 70 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 1db9d69..0c24308 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,17 +2,32 @@ All notable changes to this project will be documented in this file. -#### v1.6 (current) +#### v1.6 (git) - **NEW**: True multi-monitor support +- **NEW**: Vertical stack resizing +- **NEW**: Mouse warping +- **NEW**: Floating window rules +- **NEW**: Ctrl key as modifier +- **NEW**: Focus window on creation +- **NEW**: Copy config to `/usr/local/share/sxwmrc` +- **CHANGE**: Renamed `focus_previous` to `focus_prev` +- **CHANGE**: Invalid sample config +- **CHANGE**: Parser `$HOME` searching order. XDG Compliance +- **FIXED**: Improved parsing now supporting commands with `"` and `'` +- **FIXED**: (mouse warping) Switching to master doesn't automatically shift cursor to it +- **FIXED**: `ctrl` and `shift` key works as a modifier +- **FIXED**: Fixed build error (#64). +- **FIXED**: Removed debug logs +- **FIXED**: Fixed new window getting interrupted by mouse +- **FIXED**: Fixed `should_float` segfalt - **FIXED**: Invisible windows of minimized programs - **FIXED**: Zombie processes spawned from apps -- **FIXED**: Invalid sample config - **FIXED**: Undefined behaviour in `parse_col` -#### v1.5 +#### v1.5 (current) - **NEW**: Using XCursor instead of cursor font && new logo. -- **FIXED**: Proper bind resetting on refresh config. && Multi-arg binds now work due to new and improved spawn function - **CHANGE**: No longer using INIT_WORKSPACE macro, proper workspace handling. New sxwmrc +- **FIXED**: Proper bind resetting on refresh config. && Multi-arg binds now work due to new and improved spawn function #### v1.4 - **CHANGE**: Added motion throttle && master width general options diff --git a/CONTRIBUTIONS.md b/CONTRIBUTIONS.md new file mode 100644 index 0000000..9955680 --- /dev/null +++ b/CONTRIBUTIONS.md @@ -0,0 +1,90 @@ +# Contributing to sxwm + +Firstly, thanks for taking the time to contribute to sxwm! I appreciate your interest in improving the project. + +## Code Style and Formatting + +There is an included clangd formatting file with this inside, but it wont do everything for you. + +### Indentation +Indentation must always be **tabs** not spaces. + +### Blocks +- All blocks of C code must be encased with curly braces `{}` +- Blocks must also be seperated, each statement on a new line +- Statements must be formatted with a space between the keyword and the brackets + +**Example**: +``` c +if (x) { + y(); +} +else { + z(); +} +``` + +### Comments +Comments must be like this to keep the look consistent across the whole program. +`/* this is a comment */` + +### Function Declarations +They must look like this +``` c +void function_x(void) +{ +} + +void function_y(int y) +{ +} +``` + +### Variable Naming +Variables and function names must be in `snake_case` +``` c +void function_a(void); +void function_b(void); +``` +``` c +int some_variable = 2; +float other_variable = 5; +``` + +## Build + +- Make sure `make` succeeds with no warnings on your system +- Don't commit any build artifacts or backup files +- Test your changes on **multiple monitors (Xephyr)** + +## File Layouts + +- Please try to not make a new C file or header unless it is necissary. Most of the codebase is kept in the `sxwm.c` file. + +## Submitting Changes + +- Open a pull request with a **clear report** of the change(s) +- Keep commits having one purpose per commit +- If you fix a bug, please describe how to reproduce it +- If adding a feature, make sure it is consistent with sxwm’s minimalist philosophy +- Please don't keep multiple changes in one PR. Open a new one for that + +## Documentation + +- If applicable, **update `sxwm.1`, `README.md` and `default_sxwmrc`** + +## Respect the Existing Structure + +- Before large changes, open an issue discussion +- Please don’t change for style unless it improves clarity or fixes a problem + +## Testing + +- [x] Builds with no warnings +- [x] Tested to work fully +- [x] All changes are explained in the PR +- [x] Configuration reload works (if relevant) + +--- + +**Happy hacking!** @@ -40,7 +40,7 @@ - **Live Config Reload**: Change your config and reload instantly with a keybind. - **Easy Configuration**: Human-friendly `sxwmrc` file, no C required. - **Master-Stack Layout**: DWM-inspired productive workflow. -- **Mouse Support**: Move, swap, resize, and focus windows with the mouse. +- **Mouse Support**: Move, swap, and resize windows with the mouse. - **Zero Dependencies**: Only `libX11` and `Xinerama` required. - **Lightweight**: Single C file, minimal headers, compiles in seconds. - **Bar Friendly**: Works great with [sxbar](https://github.com/uint23/sxbar). @@ -305,5 +305,5 @@ exec sxwm --- <p align="center"> - <em>Contributions welcome! Open issues or submit PRs.</em> + <em>Contributions welcome, Please read CONTRIBUTIONS.md for more info!</em> </p> diff --git a/default_sxwmrc b/default_sxwmrc index 35e6f6c..6efede7 100644 --- a/default_sxwmrc +++ b/default_sxwmrc @@ -8,9 +8,10 @@ gaps : 10 border_width : 1 master_width : 60 # Percentage of screen width resize_master_amount : 1 +resize_stack_amt : 20 snap_distance : 5 motion_throttle : 60 # Set to screen refresh rate for smoothest motions -should_float : st +should_float : "pcmanfm" new_win_focus : true warp_cursor : true @@ -1,7 +1,7 @@ /* See LICENSE for more information on use */ #pragma once #include <X11/Xlib.h> -#define SXWM_VERSION "sxwm ver. 1.5" +#define SXWM_VERSION "sxwm ver. 1.6" #define SXWM_AUTHOR "(C) Abhinav Prasai 2025" #define SXWM_LICINFO "See LICENSE for more info" diff --git a/src/parser.c b/src/parser.c index 8e598d0..3803f10 100644 --- a/src/parser.c +++ b/src/parser.c @@ -135,7 +135,7 @@ int parser(Config *cfg) return -1; } - // Determine config file path + /* determine config file path */ const char *xdg_config_home = getenv("XDG_CONFIG_HOME"); if (xdg_config_home) { snprintf(path, sizeof path, "%s/sxwmrc", xdg_config_home); @@ -159,7 +159,7 @@ int parser(Config *cfg) goto found; } - // Nothing found + /* Nothing found */ fprintf(stderr, "sxwmrc: no configuration file found\n"); return -1; @@ -174,7 +174,7 @@ found:; int lineno = 0; int should_floatn = 0; - // Initialize should_float matrix + /* Initialize should_float matrix */ for (int j = 0; j < 256; j++) { cfg->should_float[j] = calloc(256, sizeof(char *)); if (!cfg->should_float[j]) { @@ -278,11 +278,13 @@ found:; /* store each comma separated value in a seperate row */ while (comma && should_floatn < 256) { comma = strip(comma); - if (*comma == '"') + if (*comma == '"') { comma++; + } char *end = comma + strlen(comma) - 1; - if (*end == '"') + if (*end == '"') { *end = '\0'; + } /* store each programs name in its own row at index 0 */ cfg->should_float[should_floatn][0] = strdup(comma); @@ -446,4 +448,4 @@ const char **build_argv(const char *cmd) wordfree(&p); return argv; -}
\ No newline at end of file +} @@ -157,8 +157,9 @@ Client *add_client(Window w, int ws) } else { Client *tail = workspaces[ws]; - while (tail->next) + while (tail->next) { tail = tail->next; + } tail->next = c; } @@ -213,24 +214,27 @@ Client *add_client(Window w, int ws) void change_workspace(int ws) { - if (ws >= NUM_WORKSPACES || ws == current_ws) + if (ws >= NUM_WORKSPACES || ws == current_ws) { return; + } in_ws_switch = True; XGrabServer(dpy); /* unmap those still marked mapped */ for (Client *c = workspaces[current_ws]; c; c = c->next) { - if (c->mapped) + if (c->mapped) { XUnmapWindow(dpy, c->win); + } } current_ws = ws; /* map those still marked mapped */ for (Client *c = workspaces[current_ws]; c; c = c->next) { - if (c->mapped) + if (c->mapped) { XMapWindow(dpy, c->win); + } } tile(); @@ -265,7 +269,7 @@ void close_focused(void) Atom *protos; int n; if (XGetWMProtocols(dpy, focused->win, &protos, &n) && protos) { - for (int i = 0; i < n; i++) + for (int i = 0; i < n; i++) { if (protos[i] == atom_wm_delete) { XEvent ev = {.xclient = {.type = ClientMessage, .window = focused->win, @@ -277,6 +281,7 @@ void close_focused(void) XFree(protos); return; } + } XUnmapWindow(dpy, focused->win); XFree(protos); } @@ -321,8 +326,9 @@ void focus_prev(void) } if (!prev) { - while (p->next) + while (p->next) { p = p->next; + } focused = p; } else { @@ -341,8 +347,9 @@ int get_monitor_for(Client *c) { int cx = c->x + c->w / 2, cy = c->y + c->h / 2; for (int i = 0; i < monsn; i++) { - if (cx >= (int)mons[i].x && cx < mons[i].x + mons[i].w && cy >= (int)mons[i].y && cy < mons[i].y + mons[i].h) + if (cx >= (int)mons[i].x && cx < mons[i].x + mons[i].w && cy >= (int)mons[i].y && cy < mons[i].y + mons[i].h) { return i; + } } return 0; } @@ -363,15 +370,18 @@ void grab_keys(void) 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))) + (b->type == TYPE_MWKSP && b->mods != (user_config.modkey | ShiftMask))) { continue; + } KeyCode kc = XKeysymToKeycode(dpy, b->keysym); - if (!kc) + if (!kc) { continue; + } - for (size_t g = 0; g < sizeof guards / sizeof *guards; g++) + for (size_t g = 0; g < sizeof guards / sizeof *guards; g++) { XGrabKey(dpy, kc, b->mods | guards[g], root, True, GrabModeAsync, GrabModeAsync); + } } } @@ -519,11 +529,13 @@ void hdl_config_req(XEvent *xev) XConfigureRequestEvent *e = &xev->xconfigurerequest; Client *c = NULL; - for (int ws = 0; ws < NUM_WORKSPACES && !c; ws++) - for (c = workspaces[ws]; c; c = c->next) + for (int ws = 0; ws < NUM_WORKSPACES && !c; ws++) { + for (c = workspaces[ws]; c; c = c->next) { if (c->win == e->window) { break; } + } + } if (!c || c->floating || c->fullscreen) { /* allow client to configure itself */ @@ -612,8 +624,9 @@ void hdl_keypress(XEvent *xev) break; case TYPE_FUNC: - if (b->action.fn) + if (b->action.fn) { b->action.fn(); + } break; case TYPE_CWKSP: change_workspace(b->action.ws); @@ -638,10 +651,12 @@ void swap_clients(Client *a, Client *b) Client **head = &workspaces[current_ws]; Client **pa = head, **pb = head; - while (*pa && *pa != a) + while (*pa && *pa != a) { pa = &(*pa)->next; - while (*pb && *pb != b) + } + while (*pb && *pb != b) { pb = &(*pb)->next; + } if (!*pa || !*pb) { return; @@ -724,12 +739,14 @@ void hdl_map_req(XEvent *xev) } Client *c = add_client(w, current_ws); - if (!c) + if (!c) { return; + } Window tr; - if (!should_float && XGetTransientForHint(dpy, w, &tr)) + if (!should_float && XGetTransientForHint(dpy, w, &tr)) { should_float = True; + } XSizeHints sh; long sup; if (!should_float && XGetWMNormalHints(dpy, w, &sh, &sup) && (sh.flags & PMinSize) && (sh.flags & PMaxSize) && @@ -758,10 +775,12 @@ void hdl_map_req(XEvent *xev) /* map & borders */ update_net_client_list(); - if (!global_floating && !c->floating) + if (!global_floating && !c->floating) { tile(); - else if (c->floating) + } + else if (c->floating) { XRaiseWindow(dpy, w); + } if (user_config.new_win_focus) { focused = c; @@ -773,10 +792,11 @@ void hdl_map_req(XEvent *xev) } XMapWindow(dpy, w); - for (Client *c = workspaces[current_ws]; c; c = c->next) - if (c->win == w) + for (Client *c = workspaces[current_ws]; c; c = c->next) { + if (c->win == w) { c->mapped = True; - + } + } update_borders(); } @@ -914,8 +934,9 @@ void update_struts(void) Window root_ret, parent_ret, *children; unsigned int nchildren; - if (!XQueryTree(dpy, root, &root_ret, &parent_ret, &children, &nchildren)) + if (!XQueryTree(dpy, root, &root_ret, &parent_ret, &children, &nchildren)) { return; + } for (unsigned int i = 0; i < nchildren; i++) { Window w = children[i]; @@ -927,8 +948,9 @@ void update_struts(void) if (XGetWindowProperty(dpy, w, atom_wm_window_type, 0, 4, False, XA_ATOM, &actual_type, &actual_format, &nitems, &bytes_after, (unsigned char **)&types) != Success || - !types) + !types) { continue; + } Bool is_dock = False; for (unsigned long j = 0; j < nitems; j++) { @@ -938,8 +960,9 @@ void update_struts(void) } } XFree(types); - if (!is_dock) + if (!is_dock) { continue; + } long *str = NULL; Atom actual; @@ -982,8 +1005,9 @@ 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++) + for (int i = 0; i < MAX_MONITORS; i++) { default_config.master_width[i] = 50 / 100.0f; + } default_config.motion_throttle = 60; default_config.resize_master_amt = 5; @@ -1078,8 +1102,9 @@ void move_to_workspace(int ws) XUnmapWindow(dpy, focused->win); /* remove from current list */ Client **pp = &workspaces[current_ws]; - while (*pp && *pp != focused) + while (*pp && *pp != focused) { pp = &(*pp)->next; + } if (*pp) { *pp = focused->next; } @@ -1212,8 +1237,9 @@ void resize_master_sub(void) void resize_stack_add(void) { - if (!focused || focused->floating || focused == workspaces[current_ws]) + if (!focused || focused->floating || focused == workspaces[current_ws]) { return; + } int bw2 = 2 * user_config.border_width; int raw_cur = (focused->custom_stack_height > 0) ? focused->custom_stack_height : (focused->h + bw2); @@ -1225,8 +1251,9 @@ void resize_stack_add(void) void resize_stack_sub(void) { - if (!focused || focused->floating || focused == workspaces[current_ws]) + if (!focused || focused->floating || focused == workspaces[current_ws]) { return; + } int bw2 = 2 * user_config.border_width; int raw_cur = (focused->custom_stack_height > 0) ? focused->custom_stack_height : (focused->h + bw2); @@ -1490,14 +1517,18 @@ void tile(void) Client *head = workspaces[current_ws]; int total = 0; - for (Client *c = head; c; c = c->next) - if (c->mapped && !c->floating && !c->fullscreen) + for (Client *c = head; c; c = c->next) { + if (c->mapped && !c->floating && !c->fullscreen) { total++; + } + } if (total == 1) { - for (Client *c = head; c; c = c->next) - if (!c->floating && c->fullscreen) + for (Client *c = head; c; c = c->next) { + if (!c->floating && c->fullscreen) { return; + } + } } for (int m = 0; m < monsn; m++) { @@ -1506,12 +1537,15 @@ void tile(void) Client *stackers[MAXCLIENTS]; int N = 0; - for (Client *c = head; c; c = c->next) - if (c->mapped && !c->floating && !c->fullscreen && c->mon == m) + for (Client *c = head; c; c = c->next) { + if (c->mapped && !c->floating && !c->fullscreen && c->mon == m) { stackers[N++] = c; + } + } - if (N == 0) + if (N == 0) { continue; + } int gx = user_config.gaps, gy = user_config.gaps; int tile_x = mon_x + gx, tile_y = mon_y + gy; @@ -1543,30 +1577,38 @@ void tile(void) int num_stack = N - 1; int idx_focus = -1; - for (int i = 1; i < N; i++) - if (stackers[i] == focused) + for (int i = 1; i < N; i++) { + if (stackers[i] == focused) { idx_focus = i; + } + } 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) + for (int i = 1; i < N; i++) { + if (stackers[i]->custom_stack_height > 0) { is_fixed[i] = True; - if (idx_focus >= 1 && stackers[idx_focus]->custom_stack_height > 0) + } + } + 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]) + 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]) + for (int i = 1; i < N; i++) { + if (!is_fixed[i]) { auto_count++; + } + } int total_vgaps = (num_stack - 1) * gy; int remaining = tile_h - total_fixed_heights - total_vgaps; @@ -1575,6 +1617,7 @@ void tile(void) 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]) { @@ -1583,17 +1626,20 @@ void tile(void) used += auto_h; } } - for (int i = 1; i < N; i++) - if (is_fixed[i]) + 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; + } + } } else { - for (int i = 1; i < N; i++) + 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 { @@ -1607,7 +1653,7 @@ void tile(void) 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 + /* 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; } @@ -1618,12 +1664,13 @@ void tile(void) } int total_height = total_vgaps; - for (int i = 1; i < N; i++) + 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 + /* shrink from top down, excluding bottom */ for (int i = 1; i < N - 1 && overfill > 0; i++) { int shrink = MIN(overfill, heights_final[i] - min_raw); heights_final[i] -= shrink; @@ -1631,10 +1678,11 @@ void tile(void) } } - // if its not perfectly filled stretch bottom to absorb remainder + /* if its not perfectly filled stretch bottom to absorb remainder */ int actual_stack_height = total_vgaps; - for (int i = 1; i < N; i++) + for (int i = 1; i < N; i++) { actual_stack_height += heights_final[i]; + } int shortfall = tile_h - actual_stack_height; if (shortfall > 0) { @@ -1901,4 +1949,4 @@ int main(int ac, char **av) printf("sxwm: starting...\n"); run(); return 0; -}
\ No newline at end of file +} @@ -1,4 +1,4 @@ -.TH SXWM 1 "May 2025" "sxwm 1.5" "User Commands" +.TH sxwm 1 "June 2025" "sxwm 1.6" "User Commands" .SH NAME sxwm \- minimal, fast, and configurable tiling window manager for X11 @@ -15,7 +15,7 @@ Nine workspaces with full bar support. Live configuration reload without restart. Human-friendly configuration file requiring no recompilation. DWM-style master-stack layout. -Mouse support for moving, resizing, focusing, and swapping windows. +Mouse support for moving, resizing, and swapping windows. Depends only on libX11 and Xinerama. Extremely lightweight (single C file). Multi-monitor support via Xinerama. @@ -73,6 +73,14 @@ Target updates per second for mouse drag operations (move, resize, swap). Defaul .B should_float Lets you change which windows should float by default when opening them. Default is st +.TP +.B new_win_focus +Whether newly opened windows should automatically gain focus. Default is true. + +.TP +.B warp_cursor +If true, warps the mouse cursor to the center of newly focused windows. Default is true. + .SH KEYBINDINGS Keybindings associate key combinations with actions, either running external commands or internal sxwm functions. @@ -155,6 +163,14 @@ Increases the width allocated to the master area. Decreases the width allocated to the master area. .TP +.B stack_increase +Increases the height of stack window. + +.TP +.B stack_decrease +Decreases the height of stack window. + +.TP .B toggle_floating Toggles the floating state of the focused window. @@ -210,6 +226,14 @@ Focus next or previous window. Move window up or down in the master stack. .TP +.B MOD + Shift + h / l +Increase or decrease master size. + +.TP +.B MOD + Ctrl h / l +Increase or decrease stack height. + +.TP .B MOD + Space Toggle floating mode for focused window. diff --git a/sxwm.desktop b/sxwm.desktop index f841c2b..ef08338 100644 --- a/sxwm.desktop +++ b/sxwm.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Name=Sxwm +Name=sxwm Comment=Simple Xorg Window Manager Exec=sxwm TryExec=sxwm |
