├── .gitignore ├── Makefile ├── sowm.h ├── LICENSE.md ├── config.def.h ├── README.md └── sowm.c /.gitignore: -------------------------------------------------------------------------------- 1 | sowm 2 | sowm.o 3 | config.h 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS += -std=c99 -Wall -Wextra -pedantic -Wold-style-declaration 2 | CFLAGS += -Wmissing-prototypes -Wno-unused-parameter 3 | PREFIX ?= /usr 4 | BINDIR ?= $(PREFIX)/bin 5 | CC ?= gcc 6 | 7 | all: sowm 8 | 9 | config.h: 10 | cp config.def.h config.h 11 | 12 | sowm: sowm.c sowm.h config.h Makefile 13 | $(CC) -O3 $(CFLAGS) -o $@ $< -lX11 $(LDFLAGS) 14 | 15 | install: all 16 | install -Dm755 sowm $(DESTDIR)$(BINDIR)/sowm 17 | 18 | uninstall: 19 | rm -f $(DESTDIR)$(BINDIR)/sowm 20 | 21 | clean: 22 | rm -f sowm *.o 23 | 24 | .PHONY: all install uninstall clean 25 | -------------------------------------------------------------------------------- /sowm.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define win (client *t=0, *c=list; c && t!=list->prev; t=c, c=c->next) 4 | #define ws_save(W) ws_list[W] = list 5 | #define ws_sel(W) list = ws_list[ws = W] 6 | #define MAX(a, b) ((a) > (b) ? (a) : (b)) 7 | 8 | #define win_size(W, gx, gy, gw, gh) \ 9 | XGetGeometry(d, W, &(Window){0}, gx, gy, gw, gh, \ 10 | &(unsigned int){0}, &(unsigned int){0}) 11 | 12 | // Taken from DWM. Many thanks. https://git.suckless.org/dwm 13 | #define mod_clean(mask) (mask & ~(numlock|LockMask) & \ 14 | (ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask)) 15 | 16 | typedef struct { 17 | const char** com; 18 | const int i; 19 | const Window w; 20 | } Arg; 21 | 22 | struct key { 23 | unsigned int mod; 24 | KeySym keysym; 25 | void (*function)(const Arg arg); 26 | const Arg arg; 27 | }; 28 | 29 | typedef struct client { 30 | struct client *next, *prev; 31 | int f, wx, wy; 32 | unsigned int ww, wh; 33 | Window w; 34 | } client; 35 | 36 | void button_press(XEvent *e); 37 | void button_release(XEvent *e); 38 | void configure_request(XEvent *e); 39 | void input_grab(Window root); 40 | void key_press(XEvent *e); 41 | void map_request(XEvent *e); 42 | void mapping_notify(XEvent *e); 43 | void notify_destroy(XEvent *e); 44 | void notify_enter(XEvent *e); 45 | void notify_motion(XEvent *e); 46 | void run(const Arg arg); 47 | void win_add(Window w); 48 | void win_center(const Arg arg); 49 | void win_del(Window w); 50 | void win_fs(const Arg arg); 51 | void win_focus(client *c); 52 | void win_kill(const Arg arg); 53 | void win_prev(const Arg arg); 54 | void win_next(const Arg arg); 55 | void win_to_ws(const Arg arg); 56 | void ws_go(const Arg arg); 57 | 58 | static int xerror() { return 0; } 59 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | - © 2019- Dylan Araps 4 | - © 2006-2011 Anselm R Garbe 5 | - © 2007-2011 Peter Hartlich 6 | - © 2010-2011 Connor Lane Smith 7 | - © 2006-2009 Jukka Salmi 8 | - © 2007-2009 Premysl Hruby 9 | - © 2007-2009 Szabolcs Nagy 10 | - © 2007-2009 Christof Musik 11 | - © 2009 Mate Nagy 12 | - © 2007-2008 Enno Gottox Boland 13 | - © 2008 Martin Hurton 14 | - © 2008 Neale Pickett 15 | - © 2006-2007 Sander van Dijk 16 | 17 | Permission is hereby granted, free of charge, to any person obtaining a 18 | copy of this software and associated documentation files (the "Software"), 19 | to deal in the Software without restriction, including without limitation 20 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 21 | and/or sell copies of the Software, and to permit persons to whom the 22 | Software is furnished to do so, subject to the following conditions: 23 | 24 | The above copyright notice and this permission notice shall be included in 25 | all copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 30 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 32 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 33 | DEALINGS IN THE SOFTWARE. 34 | -------------------------------------------------------------------------------- /config.def.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | #define MOD Mod4Mask 5 | 6 | const char* menu[] = {"dmenu_run", 0}; 7 | const char* term[] = {"st", 0}; 8 | const char* scrot[] = {"scr", 0}; 9 | const char* briup[] = {"bri", "10", "+", 0}; 10 | const char* bridown[] = {"bri", "10", "-", 0}; 11 | const char* voldown[] = {"amixer", "sset", "Master", "5%-", 0}; 12 | const char* volup[] = {"amixer", "sset", "Master", "5%+", 0}; 13 | const char* volmute[] = {"amixer", "sset", "Master", "toggle", 0}; 14 | const char* colors[] = {"bud", "/home/goldie/Pictures/Wallpapers", 0}; 15 | 16 | static struct key keys[] = { 17 | {MOD, XK_q, win_kill, {0}}, 18 | {MOD, XK_c, win_center, {0}}, 19 | {MOD, XK_f, win_fs, {0}}, 20 | 21 | {Mod1Mask, XK_Tab, win_next, {0}}, 22 | {Mod1Mask|ShiftMask, XK_Tab, win_prev, {0}}, 23 | 24 | {MOD, XK_d, run, {.com = menu}}, 25 | {MOD, XK_w, run, {.com = colors}}, 26 | {MOD, XK_p, run, {.com = scrot}}, 27 | {MOD, XK_Return, run, {.com = term}}, 28 | 29 | {0, XF86XK_AudioLowerVolume, run, {.com = voldown}}, 30 | {0, XF86XK_AudioRaiseVolume, run, {.com = volup}}, 31 | {0, XF86XK_AudioMute, run, {.com = volmute}}, 32 | {0, XF86XK_MonBrightnessUp, run, {.com = briup}}, 33 | {0, XF86XK_MonBrightnessDown, run, {.com = bridown}}, 34 | 35 | {MOD, XK_1, ws_go, {.i = 1}}, 36 | {MOD|ShiftMask, XK_1, win_to_ws, {.i = 1}}, 37 | {MOD, XK_2, ws_go, {.i = 2}}, 38 | {MOD|ShiftMask, XK_2, win_to_ws, {.i = 2}}, 39 | {MOD, XK_3, ws_go, {.i = 3}}, 40 | {MOD|ShiftMask, XK_3, win_to_ws, {.i = 3}}, 41 | {MOD, XK_4, ws_go, {.i = 4}}, 42 | {MOD|ShiftMask, XK_4, win_to_ws, {.i = 4}}, 43 | {MOD, XK_5, ws_go, {.i = 5}}, 44 | {MOD|ShiftMask, XK_5, win_to_ws, {.i = 5}}, 45 | {MOD, XK_6, ws_go, {.i = 6}}, 46 | {MOD|ShiftMask, XK_6, win_to_ws, {.i = 6}}, 47 | }; 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sowm (*~~Simple~~ Shitty Opinionated Window Manager*) 2 | 3 | 4 | 5 | An itsy bitsy floating window manager (*220~ sloc!*). 6 | 7 | - Floating only. 8 | - Fullscreen toggle. 9 | - Window centering. 10 | - Mix of mouse and keyboard workflow. 11 | - Focus with cursor. 12 | - Rounded corners (*[through patch](https://github.com/dylanaraps/sowm/pull/58)*) 13 | - Titlebars (*[through patch](https://github.com/dylanaraps/sowm/pull/57)*) 14 | 15 | 16 | 17 | - Alt-Tab window focusing. 18 | - All windows die on exit. 19 | - No window borders. 20 | - [No ICCCM](https://web.archive.org/web/20190617214524/https://raw.githubusercontent.com/kfish/xsel/1a1c5edf0dc129055f7764c666da2dd468df6016/rant.txt). 21 | - No EWMH. 22 | - etc etc etc 23 | 24 | 25 |
26 | 27 | Patches available here: https://github.com/dylanaraps/sowm/pulls 28 | 29 | ## Default Keybindings 30 | 31 | **Window Management** 32 | 33 | | combo | action | 34 | | -------------------------- | -----------------------| 35 | | `Mouse` | focus under cursor | 36 | | `MOD4` + `Left Mouse` | move window | 37 | | `MOD4` + `Right Mouse` | resize window | 38 | | `MOD4` + `f` | maximize toggle | 39 | | `MOD4` + `c` | center window | 40 | | `MOD4` + `q` | kill window | 41 | | `MOD4` + `1-6` | desktop swap | 42 | | `MOD4` + `Shift` +`1-6` | send window to desktop | 43 | | `MOD1` + `TAB` (*alt-tab*) | focus cycle | 44 | 45 | **Programs** 46 | 47 | | combo | action | program | 48 | | ------------------------ | ---------------- | -------------- | 49 | | `MOD4` + `Return` | terminal | `st` | 50 | | `MOD4` + `d` | dmenu | `dmenu_run` | 51 | | `MOD4` + `p` | scrot | `scr` | 52 | | `MOD4` + `w` | wallpaper cycler | `bud` | 53 | | `XF86_AudioLowerVolume` | volume down | `amixer` | 54 | | `XF86_AudioRaiseVolume` | volume up | `amixer` | 55 | | `XF86_AudioMute` | volume toggle | `amixer` | 56 | | `XF86_MonBrightnessUp` | brightness up | `bri` | 57 | | `XF86_MonBrightnessDown` | brightness down | `bri` | 58 | 59 | 60 | ## Dependencies 61 | 62 | - `xlib` (*usually `libX11`*). 63 | 64 | 65 | ## Installation 66 | 67 | 1) Copy `config.def.h` to `config.h` and modify it to suit your needs. 68 | 2) Run `make` to build `sowm`. 69 | 3) Copy it to your path or run `make install`. 70 | - `DESTDIR` and `PREFIX` are supported. 71 | 4) (Optional) Apply patch with `git apply patches/patch-name` 72 | - In case of applying multiple patches, it has to be done **manually**. 73 | 74 | If you are using GDM, save the following to `/usr/share/xsessions/sowm.desktop`. It is still recommended to start `sowm` from `.xinitrc` or through 75 | [your own xinit implementation](https://github.com/dylanaraps/bin/blob/dfd9a9ff4555efb1cc966f8473339f37d13698ba/x). 76 | 77 | ``` 78 | [Desktop Entry] 79 | Name=sowm 80 | Comment=This session runs sowm as desktop manager 81 | Exec=sowm 82 | Type=Application 83 | ``` 84 | 85 | 86 | ## Thanks 87 | 88 | - [2bwm](https://github.com/venam/2bwm) 89 | - [SmallWM](https://github.com/adamnew123456/SmallWM) 90 | - [berry](https://github.com/JLErvin/berry) 91 | - [catwm](https://github.com/pyknite/catwm) 92 | - [dminiwm](https://github.com/moetunes/dminiwm) 93 | - [dwm](https://dwm.suckless.org) 94 | - [monsterwm](https://github.com/c00kiemon5ter/monsterwm) 95 | - [openbox](https://github.com/danakj/openbox) 96 | - [possum-wm](https://github.com/duckinator/possum-wm) 97 | - [swm](https://github.com/dcat/swm) 98 | - [tinywm](http://incise.org/tinywm.html) 99 | -------------------------------------------------------------------------------- /sowm.c: -------------------------------------------------------------------------------- 1 | // sowm - An itsy bitsy floating window manager. 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "sowm.h" 12 | 13 | static client *list = {0}, *ws_list[10] = {0}, *cur; 14 | static int ws = 1, sw, sh, wx, wy, numlock = 0; 15 | static unsigned int ww, wh; 16 | 17 | static Display *d; 18 | static XButtonEvent mouse; 19 | static Window root; 20 | 21 | static void (*events[LASTEvent])(XEvent *e) = { 22 | [ButtonPress] = button_press, 23 | [ButtonRelease] = button_release, 24 | [ConfigureRequest] = configure_request, 25 | [KeyPress] = key_press, 26 | [MapRequest] = map_request, 27 | [MappingNotify] = mapping_notify, 28 | [DestroyNotify] = notify_destroy, 29 | [EnterNotify] = notify_enter, 30 | [MotionNotify] = notify_motion 31 | }; 32 | 33 | #include "config.h" 34 | 35 | void win_focus(client *c) { 36 | cur = c; 37 | XSetInputFocus(d, cur->w, RevertToParent, CurrentTime); 38 | } 39 | 40 | void notify_destroy(XEvent *e) { 41 | win_del(e->xdestroywindow.window); 42 | 43 | if (list) win_focus(list->prev); 44 | } 45 | 46 | void notify_enter(XEvent *e) { 47 | while(XCheckTypedEvent(d, EnterNotify, e)); 48 | 49 | for win if (c->w == e->xcrossing.window) win_focus(c); 50 | } 51 | 52 | void notify_motion(XEvent *e) { 53 | if (!mouse.subwindow || cur->f) return; 54 | 55 | while(XCheckTypedEvent(d, MotionNotify, e)); 56 | 57 | int xd = e->xbutton.x_root - mouse.x_root; 58 | int yd = e->xbutton.y_root - mouse.y_root; 59 | 60 | XMoveResizeWindow(d, mouse.subwindow, 61 | wx + (mouse.button == 1 ? xd : 0), 62 | wy + (mouse.button == 1 ? yd : 0), 63 | MAX(1, ww + (mouse.button == 3 ? xd : 0)), 64 | MAX(1, wh + (mouse.button == 3 ? yd : 0))); 65 | } 66 | 67 | void key_press(XEvent *e) { 68 | KeySym keysym = XkbKeycodeToKeysym(d, e->xkey.keycode, 0, 0); 69 | 70 | for (unsigned int i=0; i < sizeof(keys)/sizeof(*keys); ++i) 71 | if (keys[i].keysym == keysym && 72 | mod_clean(keys[i].mod) == mod_clean(e->xkey.state)) 73 | keys[i].function(keys[i].arg); 74 | } 75 | 76 | void button_press(XEvent *e) { 77 | if (!e->xbutton.subwindow) return; 78 | 79 | win_size(e->xbutton.subwindow, &wx, &wy, &ww, &wh); 80 | XRaiseWindow(d, e->xbutton.subwindow); 81 | mouse = e->xbutton; 82 | } 83 | 84 | void button_release(XEvent *e) { 85 | mouse.subwindow = 0; 86 | } 87 | 88 | void win_add(Window w) { 89 | client *c; 90 | 91 | if (!(c = (client *) calloc(1, sizeof(client)))) 92 | exit(1); 93 | 94 | c->w = w; 95 | 96 | if (list) { 97 | list->prev->next = c; 98 | c->prev = list->prev; 99 | list->prev = c; 100 | c->next = list; 101 | 102 | } else { 103 | list = c; 104 | list->prev = list->next = list; 105 | } 106 | 107 | ws_save(ws); 108 | } 109 | 110 | void win_del(Window w) { 111 | client *x = 0; 112 | 113 | for win if (c->w == w) x = c; 114 | 115 | if (!list || !x) return; 116 | if (x->prev == x) list = 0; 117 | if (list == x) list = x->next; 118 | if (x->next) x->next->prev = x->prev; 119 | if (x->prev) x->prev->next = x->next; 120 | 121 | free(x); 122 | ws_save(ws); 123 | } 124 | 125 | void win_kill(const Arg arg) { 126 | if (cur) XKillClient(d, cur->w); 127 | } 128 | 129 | void win_center(const Arg arg) { 130 | if (!cur) return; 131 | 132 | win_size(cur->w, &(int){0}, &(int){0}, &ww, &wh); 133 | XMoveWindow(d, cur->w, (sw - ww) / 2, (sh - wh) / 2); 134 | } 135 | 136 | void win_fs(const Arg arg) { 137 | if (!cur) return; 138 | 139 | if ((cur->f = cur->f ? 0 : 1)) { 140 | win_size(cur->w, &cur->wx, &cur->wy, &cur->ww, &cur->wh); 141 | XMoveResizeWindow(d, cur->w, 0, 0, sw, sh); 142 | 143 | } else { 144 | XMoveResizeWindow(d, cur->w, cur->wx, cur->wy, cur->ww, cur->wh); 145 | } 146 | } 147 | 148 | void win_to_ws(const Arg arg) { 149 | int tmp = ws; 150 | 151 | if (arg.i == tmp) return; 152 | 153 | ws_sel(arg.i); 154 | win_add(cur->w); 155 | ws_save(arg.i); 156 | 157 | ws_sel(tmp); 158 | win_del(cur->w); 159 | XUnmapWindow(d, cur->w); 160 | ws_save(tmp); 161 | 162 | if (list) win_focus(list); 163 | } 164 | 165 | void win_prev(const Arg arg) { 166 | if (!cur) return; 167 | 168 | XRaiseWindow(d, cur->prev->w); 169 | win_focus(cur->prev); 170 | } 171 | 172 | void win_next(const Arg arg) { 173 | if (!cur) return; 174 | 175 | XRaiseWindow(d, cur->next->w); 176 | win_focus(cur->next); 177 | } 178 | 179 | void ws_go(const Arg arg) { 180 | int tmp = ws; 181 | 182 | if (arg.i == ws) return; 183 | 184 | ws_save(ws); 185 | ws_sel(arg.i); 186 | 187 | for win XMapWindow(d, c->w); 188 | 189 | ws_sel(tmp); 190 | 191 | for win XUnmapWindow(d, c->w); 192 | 193 | ws_sel(arg.i); 194 | 195 | if (list) win_focus(list); else cur = 0; 196 | } 197 | 198 | void configure_request(XEvent *e) { 199 | XConfigureRequestEvent *ev = &e->xconfigurerequest; 200 | 201 | XConfigureWindow(d, ev->window, ev->value_mask, &(XWindowChanges) { 202 | .x = ev->x, 203 | .y = ev->y, 204 | .width = ev->width, 205 | .height = ev->height, 206 | .sibling = ev->above, 207 | .stack_mode = ev->detail 208 | }); 209 | } 210 | 211 | void map_request(XEvent *e) { 212 | Window w = e->xmaprequest.window; 213 | 214 | XSelectInput(d, w, StructureNotifyMask|EnterWindowMask); 215 | win_size(w, &wx, &wy, &ww, &wh); 216 | win_add(w); 217 | cur = list->prev; 218 | 219 | if (wx + wy == 0) win_center((Arg){0}); 220 | 221 | XMapWindow(d, w); 222 | win_focus(list->prev); 223 | } 224 | 225 | void mapping_notify(XEvent *e) { 226 | XMappingEvent *ev = &e->xmapping; 227 | 228 | if (ev->request == MappingKeyboard || ev->request == MappingModifier) { 229 | XRefreshKeyboardMapping(ev); 230 | input_grab(root); 231 | } 232 | } 233 | 234 | void run(const Arg arg) { 235 | if (fork()) return; 236 | if (d) close(ConnectionNumber(d)); 237 | 238 | setsid(); 239 | execvp((char*)arg.com[0], (char**)arg.com); 240 | } 241 | 242 | void input_grab(Window root) { 243 | unsigned int i, j, modifiers[] = {0, LockMask, numlock, numlock|LockMask}; 244 | XModifierKeymap *modmap = XGetModifierMapping(d); 245 | KeyCode code; 246 | 247 | for (i = 0; i < 8; i++) 248 | for (int k = 0; k < modmap->max_keypermod; k++) 249 | if (modmap->modifiermap[i * modmap->max_keypermod + k] 250 | == XKeysymToKeycode(d, 0xff7f)) 251 | numlock = (1 << i); 252 | 253 | XUngrabKey(d, AnyKey, AnyModifier, root); 254 | 255 | for (i = 0; i < sizeof(keys)/sizeof(*keys); i++) 256 | if ((code = XKeysymToKeycode(d, keys[i].keysym))) 257 | for (j = 0; j < sizeof(modifiers)/sizeof(*modifiers); j++) 258 | XGrabKey(d, code, keys[i].mod | modifiers[j], root, 259 | True, GrabModeAsync, GrabModeAsync); 260 | 261 | for (i = 1; i < 4; i += 2) 262 | for (j = 0; j < sizeof(modifiers)/sizeof(*modifiers); j++) 263 | XGrabButton(d, i, MOD | modifiers[j], root, True, 264 | ButtonPressMask|ButtonReleaseMask|PointerMotionMask, 265 | GrabModeAsync, GrabModeAsync, 0, 0); 266 | 267 | XFreeModifiermap(modmap); 268 | } 269 | 270 | int main(void) { 271 | XEvent ev; 272 | 273 | if (!(d = XOpenDisplay(0))) exit(1); 274 | 275 | signal(SIGCHLD, SIG_IGN); 276 | XSetErrorHandler(xerror); 277 | 278 | int s = DefaultScreen(d); 279 | root = RootWindow(d, s); 280 | sw = XDisplayWidth(d, s); 281 | sh = XDisplayHeight(d, s); 282 | 283 | XSelectInput(d, root, SubstructureRedirectMask); 284 | XDefineCursor(d, root, XCreateFontCursor(d, 68)); 285 | input_grab(root); 286 | 287 | while (1 && !XNextEvent(d, &ev)) // 1 && will forever be here. 288 | if (events[ev.type]) events[ev.type](&ev); 289 | } 290 | --------------------------------------------------------------------------------