├── Makefile ├── README.md ├── TODO ├── config.h ├── maxwelm.c └── statusbar.sh /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS+= -Wall 2 | LDADD+= -lX11 -lm 3 | LDFLAGS= 4 | EXEC=maxwelm 5 | 6 | PREFIX?= /usr 7 | BINDIR?= $(PREFIX)/bin 8 | 9 | CC=gcc 10 | 11 | all: $(EXEC) 12 | 13 | maxwelm: maxwelm.o 14 | $(CC) $(LDFLAGS) -Os -o $@ $+ $(LDADD) 15 | 16 | install: all 17 | install -Dm 755 maxwelm $(DESTDIR)$(BINDIR)/maxwelm 18 | 19 | clean: 20 | rm -f maxwelm *.o 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # maxwelm (Maximizing Window Element Manager) 2 | 3 | maxwelm is written by Zach Sisco ``, 2016. 4 | 5 | Maxwelm is a simple window manager based off of [catwm](https://github.com/pyknite/catwm), my previous window manager [ShMOW](https://github.com/zsisco/ShMOW), and bits of [dwm](http://dwm.suckless.org) and [TinyWM](https://github.com/mackstann/tinywm) when I get stuck. 6 | The main idea is to have a window manager suitable for small screens (and slower machines) that minimizes clutter and maximizes precious screen real estate. 7 | 8 | ####Features 9 | - All windows are maximized on creation (like monocle mode in dwm or other tiling window managers) 10 | - Windows can be moved and resized with the mouse or through hotkeys 11 | - Status bar at the top of the screen displays current desktop number, focused window name, and custom status text 12 | - Virtual desktops 13 | 14 | ####Configuration 15 | - All configuration is done in `config.h` (mostly key bindings, colors, and custom commands) 16 | - Custom status text is set by `xsetroot -name $status` (similar to dwm) where `$status` is a string variable. A sample shell script is provided -- `statusbar.sh`. It may use programs not installed on your machine; change it to output whatever you like. 17 | 18 | ####Dependencies 19 | - Xlib. 20 | - dmenu (optional). 21 | 22 | ####Installation 23 | - `$ vi config.h` 24 | - `# make install clean` 25 | 26 | ####Functionality and default hotkeys: 27 | ``` 28 | Alt + Button1, drag: interactive window move 29 | Alt + Button3, drag: interactive window resize 30 | Alt + h/j/k/l: move window (left/down/up/right) 31 | Alt + Shift + h/j/k/l: resize window (left/down/up/right) 32 | Alt + m: toggle maximize for focused window 33 | Alt + Shift + w: close focused window 34 | Alt + Tab: focus next window 35 | Alt + Shift + Tab: focus previous window 36 | Alt + (0 - 9) focus virtual desktop (0 - 9) 37 | Alt + Shift + (0 - 9): move focused window to virtual desktop (0 - 9) 38 | Alt + Enter: spawn terminal 39 | Alt + p: spawn dmenu 40 | Alt + Control + t: quit maxwelm 41 | ``` 42 | 43 | ####Screenshots 44 | ![floating windows](http://i.imgur.com/qpB3On5.png "floating windows") 45 | 46 | ![maximized window](http://i.imgur.com/o1xECpD.png "maximized window") 47 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | To do: 2 | [X] break main into functions 3 | [X] grab keys and key config 4 | [X] switch to event-driven loop 5 | [X] vi-hotkeys for resizing window 6 | [X] vi-hotkeys for moving window 7 | [X] get window name 8 | [X] window name update function 9 | [X] put windows in linked list, test this 10 | |--[X] add 11 | |--[X] remove 12 | |--[X] next 13 | |--[X] prev 14 | [X] kill client 15 | [X] rewrite keypress() for linked list windows (i.e. current) 16 | [X] current/focus window? 17 | mouse-focus no longer functional, focus only through tabbing 18 | can still interactive move/resize and raise window (no focus) with mouse 19 | [X] add_window() add AFTER CURRENT client in list 20 | [X] auto maximize windows 21 | [X] change spawn location of windows (MapRequest, by virtue of item above) 22 | [X] virtual desktops 23 | [X] fun colors, config different LIGHT colors for desktops and borders 24 | [X] top bar 25 | |--[X] draw bar on screen 26 | |--[X] show desktop number 27 | |--[X] show focused window name 28 | |--[X] custom status text 29 | |--[X] PropertyNotify 30 | |--[X] get Atoms and stuff 31 | [X] graceful quit WM 32 | [X] Alt + m - toggle. save old x and y 33 | [X] BUG: moving windows between desktops crashes drawbar() 34 | [X] BUG: new/private firefox windows are misplaced for maximized 35 | [X] prevent moving windows above status bar 36 | [ ] BUG: frequent WM crashing with firefox...can't quite diagnose 37 | firefox creates a window that isn't kept track off 38 | [ ] enhancements: 39 | |--[ ] display all window titles on bar? or display number of windows on desktop? 40 | |--[ ] "snap to" side-by-side windows? 41 | | |--[ ] split screen with tabbable windows? 42 | |--[ ] mod + shift + m - full screen (max and disregard borders, tool bars) 43 | |--[ ] transient windows (dialog, popups) 44 | [ ] re-write in XCB? 45 | [ ] modularity considerations: 46 | | maybe it's better design and fits more with the unix philosophy to remove aspects of maxwelm that are NOT window management? 47 | |--[ ] remove the status bar in favor of a "real" bar program like bar, dzen, conky? 48 | | this can work so long as maxwelm send the right info. 49 | |------[ ] test maxwelm with lemonboy bar 50 | |--[ ] remove keybindings, hand that off to a real keybinding program (sxhkd) 51 | [ ] think about workflow on small displays and how maxwelm can differentiate itself from other WM's 52 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | /* Mod (Mod1 == alt) */ 5 | #define MOD Mod1Mask 6 | 7 | /* Colors */ 8 | #define UNFOCUS "rgb:1c/1c/1c" 9 | static char* focuscolors[10] = { /* initialize 10 colors */ 10 | "rgb:cc/cc/c6", 11 | "rgb:9a/cc/79", 12 | "rgb:79/9a/cc", 13 | "rgb:9a/79/cc", 14 | "rgb:cb/8d/c4", 15 | "rgb:cc/79/9a", 16 | "rgb:cc/9a/79", 17 | "rgb:d6/aa/55", 18 | "rgb:e4/d5/61", 19 | "rgb:89/9c/a1", 20 | }; 21 | 22 | /* 23 | * Status bar script command name. 24 | * If there is none, change it to "" 25 | */ 26 | #define STATUS_BAR_SCRIPT "statusbar.sh" 27 | 28 | /* 29 | * If STATUS_BAR_SCRIPT is not in your PATH, 30 | * change statusbarcmd[] to: 31 | * statusbarcmd[] = {"./path/to/script/"STATUS_BAR_SCRIPT, NULL}; 32 | */ 33 | const char* statusbarcmd[] = {STATUS_BAR_SCRIPT, NULL}; /* do not remove this line */ 34 | const char* killstatusbarcmd[] = {"killall", STATUS_BAR_SCRIPT}; /* do not remove this line */ 35 | const char* dmenucmd[] = {"dmenu_run",NULL}; /* replace with another launcher program */ 36 | const char* termcmd[] = {"urxvt",NULL}; /* replace with another terminal emulator */ 37 | 38 | static struct key keys[] = { 39 | /*MOD KEY FUNCTION ARGS */ 40 | { MOD, XK_m, max_win, {NULL}}, 41 | { MOD|ShiftMask, XK_w, close_win, {NULL}}, 42 | { MOD, XK_Tab, next_win, {NULL}}, 43 | { MOD|ShiftMask, XK_Tab, prev_win, {NULL}}, 44 | { MOD|ControlMask, XK_t, quit_wm, {NULL}}, 45 | /* run commands */ 46 | { MOD, XK_p, spawn, {.com = dmenucmd}}, 47 | { MOD, XK_Return, spawn, {.com = termcmd}}, 48 | /* move/resize */ 49 | { MOD, XK_h, move_win, {.dir = LEFT}}, 50 | { MOD|ShiftMask, XK_h, resize_win, {.dir = LEFT}}, 51 | { MOD, XK_j, move_win, {.dir = DOWN}}, 52 | { MOD|ShiftMask, XK_j, resize_win, {.dir = DOWN}}, 53 | { MOD, XK_k, move_win, {.dir = UP}}, 54 | { MOD|ShiftMask, XK_k, resize_win, {.dir = UP}}, 55 | { MOD, XK_l, move_win, {.dir = RIGHT}}, 56 | { MOD|ShiftMask, XK_l, resize_win, {.dir = RIGHT}}, 57 | /* virtual desktops */ 58 | { MOD, XK_0, change_desktop, {.i = 0}}, 59 | { MOD|ShiftMask, XK_0, client_to_desktop, {.i = 0}}, 60 | { MOD, XK_1, change_desktop, {.i = 1}}, 61 | { MOD|ShiftMask, XK_1, client_to_desktop, {.i = 1}}, 62 | { MOD, XK_2, change_desktop, {.i = 2}}, 63 | { MOD|ShiftMask, XK_2, client_to_desktop, {.i = 2}}, 64 | { MOD, XK_3, change_desktop, {.i = 3}}, 65 | { MOD|ShiftMask, XK_3, client_to_desktop, {.i = 3}}, 66 | { MOD, XK_4, change_desktop, {.i = 4}}, 67 | { MOD|ShiftMask, XK_4, client_to_desktop, {.i = 4}}, 68 | { MOD, XK_5, change_desktop, {.i = 5}}, 69 | { MOD|ShiftMask, XK_5, client_to_desktop, {.i = 5}}, 70 | { MOD, XK_6, change_desktop, {.i = 6}}, 71 | { MOD|ShiftMask, XK_6, client_to_desktop, {.i = 6}}, 72 | { MOD, XK_7, change_desktop, {.i = 7}}, 73 | { MOD|ShiftMask, XK_7, client_to_desktop, {.i = 7}}, 74 | { MOD, XK_8, change_desktop, {.i = 8}}, 75 | { MOD|ShiftMask, XK_8, client_to_desktop, {.i = 8}}, 76 | { MOD, XK_9, change_desktop, {.i = 9}}, 77 | { MOD|ShiftMask, XK_9, client_to_desktop, {.i = 9}} 78 | }; 79 | 80 | #endif 81 | 82 | -------------------------------------------------------------------------------- /maxwelm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * maxwelm - Maximizing Window element Manager 3 | * 4 | * Maxwelm is written by Zach Sisco , 2016. 5 | * 6 | * Borrowed interactive pointer move/resize code from TinyWM. 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #define MAX(a, b) ((a) > (b) ? (a) : (b)) 22 | #define LENGTH(X) (sizeof(X) / sizeof(*X)) 23 | #define RESIZER 20 24 | #define TOPBAR 15 25 | #define WINBORDER 1 26 | 27 | enum direction {LEFT, DOWN, UP, RIGHT}; 28 | 29 | typedef union { 30 | const char** com; 31 | const int i; 32 | const enum direction dir; 33 | } Arg; 34 | 35 | struct key { 36 | unsigned int mod; 37 | KeySym keysym; 38 | void (*function)(const Arg arg); 39 | const Arg arg; 40 | }; 41 | 42 | struct client { 43 | struct client *next; 44 | struct client *prev; 45 | 46 | Window win; 47 | char name[256]; 48 | int old_x; 49 | int old_y; 50 | int old_w; 51 | int old_h; 52 | }; 53 | 54 | struct desktop { 55 | struct client *head; 56 | struct client *current; 57 | }; 58 | 59 | /* declare functions */ 60 | static void add_window(Window w); 61 | static void buttonpress(XEvent *ev); 62 | static void buttonrelease(XEvent *ev); 63 | static void change_desktop(const Arg arg); 64 | static void cleanup(); 65 | static void client_to_desktop(const Arg arg); 66 | static void close_win(); 67 | static void configurerequest(XEvent *e); 68 | static void destroynotify(XEvent *ev); 69 | static void drawbar(); 70 | static unsigned long getcolor(const char* color); 71 | static Bool gettextprop(Window w, Atom atom, char *text, unsigned int size); 72 | static void grabinput(); 73 | static void keypress(XEvent *ev); 74 | static void maprequest(XEvent *ev); 75 | static void max_win(); 76 | static void motionnotify(XEvent *ev); 77 | static void move_win(const Arg arg); 78 | static void next_win(); 79 | static void prev_win(); 80 | static void propertynotify(XEvent *ev); 81 | static void quit_wm(); 82 | static void remove_window(Window w); 83 | static void resize_win(const Arg arg); 84 | static void run(); 85 | static void save_desktop(int d); 86 | static void select_desktop(int d); 87 | static void send_kill_signal(Window w); 88 | static GC setcolor(const char* col); 89 | static void setup(); 90 | static void sigchld(int unused); 91 | static void spawn(const Arg arg); 92 | static void update_all_titles(); 93 | static void update_all_windows(); 94 | static void update_status(void); 95 | static void update_title(struct client *c); 96 | 97 | /* variables */ 98 | static XWindowAttributes attr; 99 | static Colormap cmap; 100 | static XColor color; 101 | static unsigned int color_light; 102 | static unsigned int color_dark; 103 | static struct client *current; 104 | static unsigned int currentdesktop; 105 | static struct desktop desktops[10]; 106 | static Display *dpy; 107 | static GC gc; 108 | static void (*handler[LASTEvent]) (XEvent *) = { 109 | [ButtonPress] = buttonpress, 110 | [ButtonRelease] = buttonrelease, 111 | [ConfigureRequest] = configurerequest, 112 | [DestroyNotify] = destroynotify, 113 | [KeyPress] = keypress, 114 | [MapRequest] = maprequest, 115 | [MotionNotify] = motionnotify, 116 | [PropertyNotify] = propertynotify 117 | }; 118 | static struct client *head; 119 | static int maxwin_h; 120 | static int maxwin_w; 121 | static Atom NetWMName; 122 | static Window root; 123 | static Bool running = True; 124 | static int screen; 125 | static int screen_w; 126 | static int screen_h; 127 | static XButtonEvent start; 128 | static char status_text[256]; 129 | 130 | /* include config here to use structs defined above */ 131 | #include "config.h" 132 | 133 | void add_window(Window new_win) 134 | { 135 | struct client *newclient, *tmp; 136 | 137 | if (!(newclient = (struct client *)calloc(1, sizeof(struct client)))) { 138 | fprintf(stderr, "calloc error!\n"); 139 | exit(1); 140 | } 141 | 142 | static XWindowAttributes wa; 143 | XGetWindowAttributes(dpy, new_win, &wa); 144 | newclient->old_x = wa.x; 145 | newclient->old_y = wa.y; 146 | newclient->old_w = wa.width; 147 | newclient->old_h = wa.height; 148 | 149 | newclient->win = new_win; 150 | 151 | if (head == NULL) { 152 | newclient->next = NULL; 153 | newclient->prev = NULL; 154 | head = newclient; 155 | } else { 156 | for (tmp = head; tmp->next; tmp = tmp->next) 157 | if (tmp == current) 158 | break; 159 | 160 | if (tmp->next != NULL) 161 | tmp->next->prev = newclient; 162 | 163 | newclient->next = tmp->next; 164 | newclient->prev = tmp; 165 | 166 | tmp->next = newclient; 167 | } 168 | 169 | update_title(newclient); 170 | 171 | current = newclient; 172 | } 173 | 174 | void buttonpress(XEvent *ev) 175 | { 176 | if (ev->xbutton.subwindow != None) { 177 | XGetWindowAttributes(dpy, ev->xbutton.subwindow, &attr); 178 | start = ev->xbutton; 179 | } 180 | } 181 | 182 | void buttonrelease(XEvent *ev) 183 | { 184 | start.subwindow = None; 185 | } 186 | 187 | void change_desktop(const Arg arg) 188 | { 189 | int d = arg.i; 190 | struct client *c; 191 | 192 | if (d == currentdesktop) 193 | return; 194 | 195 | if (head != NULL) 196 | for (c = head; c; c = c->next) 197 | XUnmapWindow(dpy, c->win); 198 | 199 | save_desktop(currentdesktop); 200 | 201 | select_desktop(d); 202 | 203 | if (head != NULL) 204 | for (c = head; c; c = c->next) 205 | XMapWindow(dpy, c->win); 206 | 207 | color_light = getcolor(focuscolors[currentdesktop]); 208 | update_all_windows(); 209 | drawbar(); 210 | } 211 | 212 | void cleanup() 213 | { 214 | int i; 215 | struct client *c; 216 | fprintf(stdout, "\ncleanup!\n\tremoving all windows\n"); 217 | for (i = 0; i < 10; i++) { 218 | if (head != NULL) 219 | for (c = head; c; c = c->next) 220 | XUnmapWindow(dpy, c->win); 221 | save_desktop(currentdesktop); 222 | select_desktop(i); 223 | fprintf(stdout, "\tcleaning desktop %d ...\n", i); 224 | c = current; 225 | while (c != NULL) { 226 | remove_window(c->win); 227 | XUnmapWindow(dpy, c->win); 228 | update_all_windows(); 229 | c = current; 230 | } 231 | fprintf(stdout, "\tdone cleaning desktop %d\n", i); 232 | desktops[i].head = NULL; 233 | desktops[i].current = NULL; 234 | } 235 | 236 | fprintf(stdout, "\n\tkilling status bar script\n"); 237 | const Arg killarg = {.com = killstatusbarcmd}; 238 | spawn(killarg); 239 | 240 | XUngrabKey(dpy, AnyKey, AnyModifier, root); 241 | XFreeGC(dpy, gc); 242 | XSync(dpy, False); 243 | XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); 244 | fprintf(stdout, "cleanup! done\n"); 245 | } 246 | 247 | void client_to_desktop(const Arg arg) 248 | { 249 | int d = arg.i; 250 | struct client *movec = current; 251 | int orig_desktop = currentdesktop; 252 | 253 | if (d == currentdesktop || current == NULL) 254 | return; 255 | 256 | save_desktop(orig_desktop); 257 | select_desktop(d); 258 | add_window(movec->win); 259 | save_desktop(d); 260 | 261 | select_desktop(orig_desktop); 262 | if (movec->win != None) 263 | XUnmapWindow(dpy, movec->win); 264 | remove_window(movec->win); 265 | 266 | update_all_windows(); 267 | drawbar(); 268 | } 269 | 270 | void close_win() 271 | { 272 | if (current != NULL) { 273 | /* send delete signal to window */ 274 | XEvent ke; 275 | ke.type = ClientMessage; 276 | ke.xclient.window = current->win; 277 | ke.xclient.message_type = XInternAtom(dpy, "WM_PROTOCOLS", True); 278 | ke.xclient.format = 32; 279 | ke.xclient.data.l[0] = XInternAtom(dpy, "WM_DELETE_WINDOW", True); 280 | ke.xclient.data.l[1] = CurrentTime; 281 | XSendEvent(dpy, current->win, False, NoEventMask, &ke); 282 | send_kill_signal(current->win); 283 | } 284 | } 285 | 286 | void configurerequest(XEvent *e) { 287 | /* Paste from dwm */ 288 | XConfigureRequestEvent *ev = &e->xconfigurerequest; 289 | XWindowChanges wc; 290 | wc.x = ev->x; 291 | wc.y = ev->y; 292 | wc.width = ev->width; 293 | wc.height = ev->height; 294 | wc.border_width = ev->border_width; 295 | wc.sibling = ev->above; 296 | wc.stack_mode = ev->detail; 297 | XConfigureWindow(dpy, ev->window, ev->value_mask, &wc); 298 | } 299 | 300 | void destroynotify(XEvent *ev) 301 | { 302 | int i = 0; 303 | struct client *c; 304 | XDestroyWindowEvent *dstr = &ev->xdestroywindow; 305 | 306 | for (c = head; c; c = c->next) { 307 | if (dstr->window == c->win) { 308 | i++; 309 | } 310 | } 311 | 312 | if (i == 0) 313 | return; 314 | 315 | remove_window(dstr->window); 316 | update_all_titles(); 317 | update_all_windows(); 318 | drawbar(); 319 | } 320 | 321 | void drawbar() 322 | { 323 | fprintf(stdout, "\n\tdrawbar->\n"); 324 | 325 | int status_w = strlen(status_text) * 6; /* right side of bar */ 326 | int bar_w = screen_w - status_w; /* left side of bar */ 327 | 328 | Window win = XCreateSimpleWindow(dpy, root, 0, 0, screen_w, TOPBAR, 0, color_light, color_light); 329 | fprintf(stdout, "\t create simple window\n"); 330 | XMapWindow(dpy, win); 331 | fprintf(stdout, "\t map window\n"); 332 | XDrawRectangle(dpy, win, setcolor(focuscolors[currentdesktop]), 0, 0, screen_w, TOPBAR - 1); 333 | fprintf(stdout, "\t draw rectangle\n"); 334 | XFillRectangle(dpy, win, setcolor(focuscolors[currentdesktop]), 0, 0, screen_w, TOPBAR); 335 | fprintf(stdout, "\t fill rectangle\n"); 336 | 337 | /* draw status text area */ 338 | XDrawRectangle(dpy, win, setcolor(UNFOCUS), screen_w - status_w, 0, status_w, TOPBAR - 1); 339 | fprintf(stdout, "\t draw rectangle\n"); 340 | XFillRectangle(dpy, win, setcolor(UNFOCUS), screen_w - status_w, 0, status_w, TOPBAR); 341 | fprintf(stdout, "\t fill rectangle\n"); 342 | XDrawString(dpy, win, setcolor(focuscolors[currentdesktop]), screen_w - status_w + 1, TOPBAR - 3, status_text, strlen(status_text)); 343 | fprintf(stdout, "\t draw status text\n"); 344 | 345 | /* load current window name */ 346 | if (current != NULL) { 347 | update_title(current); 348 | fprintf(stdout, "\n\t update title\n"); 349 | } 350 | 351 | /* get count of open windows */ 352 | struct client *tmp; 353 | int totalwin = 0; 354 | int currentwin = 0; 355 | for (tmp = head; tmp; tmp = tmp->next) { 356 | totalwin = totalwin + 1; 357 | if (tmp == current) 358 | currentwin = totalwin; 359 | } 360 | 361 | 362 | char barbuffer[bar_w]; 363 | 364 | /* draw desktop number and window name */ 365 | snprintf(barbuffer, bar_w, "[D:%d|W:%d/%d] [%s]", currentdesktop, currentwin, totalwin, (current == NULL ? "" : current->name)); 366 | XDrawString(dpy, win, setcolor(UNFOCUS), 5, TOPBAR - 3, barbuffer, strlen(barbuffer)); 367 | fprintf(stdout, "\t draw bar text\n"); 368 | 369 | fprintf(stdout, "\tdrawbar<-\n\n"); 370 | } 371 | 372 | unsigned long getcolor(const char* color) 373 | { 374 | XColor c; 375 | Colormap map = DefaultColormap(dpy, screen); 376 | 377 | if(!XAllocNamedColor(dpy, map, color, &c, &c)) { 378 | fprintf(stderr, "Error parsing color!"); 379 | exit(1); 380 | } 381 | 382 | return c.pixel; 383 | } 384 | 385 | Bool gettextprop(Window w, Atom atom, char *text, unsigned int size) { 386 | char **list = NULL; 387 | int n; 388 | XTextProperty name; 389 | 390 | if (!text || size == 0) 391 | return False; 392 | text[0] = '\0'; 393 | XGetTextProperty(dpy, w, &name, atom); 394 | if (!name.nitems) 395 | return False; 396 | if (name.encoding == XA_STRING) { 397 | strncpy(text, (char *)name.value, size - 1); 398 | } else { 399 | if (XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success && n > 0 && *list) { 400 | strncpy(text, *list, size - 1); 401 | XFreeStringList(list); 402 | } 403 | } 404 | text[size - 1] = '\0'; 405 | XFree(name.value); 406 | return True; 407 | } 408 | 409 | void grabinput() 410 | { 411 | int i; 412 | KeyCode code; 413 | 414 | for (i = 0; i < LENGTH(keys); ++i) { 415 | if ((code = XKeysymToKeycode(dpy, keys[i].keysym))) { 416 | XGrabKey(dpy, code, keys[i].mod, root, True, GrabModeAsync, GrabModeAsync); 417 | } 418 | } 419 | 420 | XGrabButton(dpy, 1, MOD, root, True, 421 | ButtonPressMask|ButtonReleaseMask|PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None); 422 | XGrabButton(dpy, 3, MOD, root, True, 423 | ButtonPressMask|ButtonReleaseMask|PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None); 424 | } 425 | 426 | 427 | void keypress(XEvent *ev) 428 | { 429 | KeySym keysym = XkbKeycodeToKeysym(dpy, ev->xkey.keycode, 0, 0); 430 | int i; 431 | for (i = 0; i < LENGTH(keys); ++i) { 432 | if (keys[i].keysym == keysym && keys[i].mod == ev->xkey.state) { 433 | keys[i].function(keys[i].arg); 434 | break; 435 | } 436 | } 437 | } 438 | 439 | void maprequest(XEvent *ev) 440 | { 441 | static XWindowAttributes wa; 442 | XMapRequestEvent *mapev = &ev->xmaprequest; 443 | 444 | if (!XGetWindowAttributes(dpy, mapev->window, &wa)) 445 | return; 446 | if (wa.override_redirect) /* for popups/dialogs */ 447 | return; 448 | 449 | /* Map window and maximize, true to name */ 450 | XMapWindow(dpy, mapev->window); 451 | add_window(mapev->window); 452 | max_win(); 453 | update_all_titles(); 454 | update_all_windows(); 455 | drawbar(); 456 | } 457 | 458 | void max_win() 459 | { 460 | if (current != NULL && current->win != None) { 461 | static XWindowAttributes wa; 462 | XGetWindowAttributes(dpy, current->win, &wa); 463 | if (wa.width == maxwin_w && wa.height == maxwin_h 464 | && wa.x == 0 && wa.y == TOPBAR) { 465 | XMoveResizeWindow(dpy, current->win, current->old_x, 466 | current->old_y, current->old_w, current->old_h); 467 | } else { 468 | current->old_x = wa.x; 469 | current->old_y = wa.y; 470 | current->old_w = wa.width; 471 | current->old_h = wa.height; 472 | XMoveResizeWindow(dpy, current->win, 0, TOPBAR, maxwin_w, maxwin_h); 473 | } 474 | } 475 | } 476 | 477 | void motionnotify(XEvent *ev) 478 | { 479 | if (start.subwindow != None) { 480 | int xdiff = ev->xbutton.x_root - start.x_root; 481 | int ydiff = ev->xbutton.y_root - start.y_root; 482 | 483 | XMoveResizeWindow(dpy, start.subwindow, 484 | attr.x + (start.button==1 ? xdiff : 0), 485 | attr.y + (start.button==1 ? ydiff : 0), 486 | MAX(1, attr.width + (start.button==3 ? xdiff : 0)), 487 | MAX(1, attr.height + (start.button==3 ? ydiff : 0))); 488 | } 489 | } 490 | 491 | void move_win(const Arg arg) 492 | { 493 | static int x, y; 494 | enum direction dir = arg.dir; 495 | 496 | if (current != NULL && current->win != None) { 497 | XGetWindowAttributes(dpy, current->win, &attr); 498 | switch (dir) { 499 | case LEFT: 500 | x = MAX(1, attr.x - RESIZER); 501 | y = attr.y; 502 | break; 503 | case DOWN: 504 | x = attr.x; 505 | y = (screen_h - attr.height < attr.y + RESIZER ? screen_h - attr.height : attr.y + RESIZER); 506 | break; 507 | case UP: 508 | x = attr.x; 509 | y = MAX(TOPBAR, attr.y - RESIZER); 510 | break; 511 | case RIGHT: 512 | x = (screen_w - attr.width < attr.x + RESIZER ? screen_w - attr.width : attr.x + RESIZER); 513 | y = attr.y; 514 | break; 515 | default: 516 | break; 517 | } 518 | XMoveWindow(dpy, current->win, x, y); 519 | } 520 | } 521 | 522 | void next_win() 523 | { 524 | struct client *c; 525 | 526 | if (current != NULL && head != NULL) { 527 | if (current->next == NULL) 528 | c = head; 529 | else 530 | c = current->next; 531 | 532 | current = c; 533 | update_all_windows(); 534 | drawbar(); 535 | } 536 | } 537 | 538 | void prev_win() 539 | { 540 | struct client *c; 541 | 542 | if (current != NULL && head != NULL) { 543 | if (current->prev == NULL) 544 | for (c = head; c->next; c = c->next); 545 | else 546 | c = current->prev; 547 | 548 | current = c; 549 | update_all_windows(); 550 | drawbar(); 551 | } 552 | } 553 | 554 | void propertynotify(XEvent *ev) 555 | { 556 | fprintf(stdout, "\nPropertyNotify\n"); 557 | XPropertyEvent *propev = &ev->xproperty; 558 | 559 | if ((propev->window == root) && (propev->atom == XA_WM_NAME)) { 560 | fprintf(stdout, "\tupdate status\n"); 561 | update_status(); 562 | drawbar(); 563 | } else if (propev->state == PropertyDelete) { 564 | return; /*ignore*/ 565 | } 566 | } 567 | 568 | void quit_wm() 569 | { 570 | running = False; 571 | fprintf(stdout, "\nquitting maxwelm...\n"); 572 | } 573 | 574 | void remove_window(Window w) 575 | { 576 | struct client *c; 577 | 578 | for (c = head; c != NULL; c = c->next) { 579 | if (c->win == w) { 580 | if (c->prev == NULL && c->next == NULL) { 581 | free(head); 582 | head = NULL; 583 | current = NULL; 584 | return; 585 | } else if (c->prev == NULL) { 586 | head = c->next; 587 | c->next->prev = NULL; 588 | current = c->next; 589 | } else if (c->next == NULL) { 590 | c->prev->next = NULL; 591 | current = c->prev; 592 | } else { 593 | c->prev->next = c->next; 594 | c->next->prev = c->prev; 595 | current = c->prev; 596 | } 597 | 598 | free(c); 599 | return; 600 | } 601 | } 602 | } 603 | 604 | void resize_win(const Arg arg) 605 | { 606 | static int w, h; 607 | enum direction dir = arg.dir; 608 | 609 | if (current != NULL && current->win != None) { 610 | XGetWindowAttributes(dpy, current->win, &attr); 611 | switch (dir) { 612 | case LEFT: 613 | w = MAX(1, attr.width - RESIZER); 614 | h = attr.height; 615 | break; 616 | case DOWN: 617 | w = attr.width; 618 | h = (screen_h - attr.y < attr.height + RESIZER ? screen_h - attr.y : attr.height + RESIZER); 619 | break; 620 | case UP: 621 | w = attr.width; 622 | h = MAX(1, attr.height - RESIZER); 623 | break; 624 | case RIGHT: 625 | w = (screen_w - attr.x < attr.width + RESIZER ? screen_w - attr.x : attr.width + RESIZER); 626 | h = attr.height; 627 | break; 628 | default: 629 | break; 630 | } 631 | XResizeWindow(dpy, current->win, w, h); 632 | } 633 | } 634 | 635 | void run() 636 | { 637 | XEvent ev; 638 | 639 | start.subwindow = None; 640 | 641 | XSync(dpy, False); 642 | 643 | /* Credit to dwm for the O(1)-time event loop */ 644 | while (running && !XNextEvent(dpy, &ev)) 645 | if (handler[ev.type]) 646 | handler[ev.type](&ev); /* call handler */ 647 | } 648 | 649 | void save_desktop(int d) 650 | { 651 | desktops[d].head = head; 652 | desktops[d].current = current; 653 | } 654 | 655 | void select_desktop(int d) 656 | { 657 | head = desktops[d].head; 658 | current = desktops[d].current; 659 | currentdesktop = d; 660 | } 661 | 662 | void send_kill_signal(Window w) 663 | { 664 | XEvent ke; 665 | ke.type = ClientMessage; 666 | ke.xclient.window = w; 667 | ke.xclient.message_type = XInternAtom(dpy, "WM_PROTOCOLS", True); 668 | ke.xclient.format = 32; 669 | ke.xclient.data.l[0] = XInternAtom(dpy, "WM_DELETE_WINDOW", True); 670 | ke.xclient.data.l[1] = CurrentTime; 671 | XSendEvent(dpy, w, False, NoEventMask, &ke); 672 | } 673 | 674 | GC setcolor(const char* col) 675 | { 676 | XAllocNamedColor(dpy, cmap, col, &color, &color); 677 | XSetForeground(dpy, gc, color.pixel); 678 | return gc; 679 | } 680 | 681 | void setup() 682 | { 683 | sigchld(0); 684 | 685 | screen = DefaultScreen(dpy); 686 | screen_w = XDisplayWidth(dpy, screen); 687 | screen_h = XDisplayHeight(dpy, screen); 688 | root = RootWindow(dpy,screen); 689 | 690 | maxwin_w = screen_w - (2 * WINBORDER); 691 | maxwin_h = screen_h - TOPBAR - (2 * WINBORDER); 692 | 693 | grabinput(); 694 | 695 | head = NULL; 696 | current = NULL; 697 | 698 | /* EWMH */ 699 | NetWMName = XInternAtom(dpy, "_NET_WM_NAME", False); 700 | 701 | int i; 702 | for (i = 0; i < 10; i++) { 703 | desktops[i].head = head; 704 | desktops[i].current = current; 705 | } 706 | 707 | /* Select first desktop as default */ 708 | const Arg arg = {.i = 1}; 709 | currentdesktop = arg.i; 710 | change_desktop(arg); 711 | 712 | /* init color stuff */ 713 | color_light = getcolor(focuscolors[currentdesktop]); 714 | color_dark = getcolor(UNFOCUS); 715 | cmap = DefaultColormap(dpy, screen); 716 | XGCValues val; 717 | val.font = XLoadFont(dpy, "fixed"); 718 | gc = XCreateGC(dpy, root, GCFont, &val); 719 | 720 | /* init status bar text */ 721 | strncpy(status_text, "maxwelm\0", sizeof(status_text)); 722 | const Arg statusarg = {.com = statusbarcmd}; 723 | spawn(statusarg); 724 | 725 | drawbar(); 726 | 727 | XSelectInput(dpy,root,SubstructureNotifyMask|SubstructureRedirectMask|PropertyChangeMask); 728 | } 729 | 730 | /* Credit to dwm for this */ 731 | void sigchld(int unused) 732 | { 733 | if (signal(SIGCHLD, sigchld) == SIG_ERR) { 734 | fprintf(stderr, "Can't install SIGCHLD handler"); 735 | exit(1); 736 | } 737 | while (0 < waitpid(-1, NULL, WNOHANG)); 738 | } 739 | 740 | void spawn(const Arg arg) 741 | { 742 | if (fork() == 0) { 743 | if (fork() == 0) { 744 | if (dpy) 745 | close(ConnectionNumber(dpy)); 746 | 747 | setsid(); 748 | execvp((char*)arg.com[0], (char**)arg.com); 749 | } 750 | exit(0); 751 | } 752 | } 753 | 754 | void update_all_titles() 755 | { 756 | struct client *tmp; 757 | tmp = head; 758 | if (head == NULL) 759 | return; 760 | 761 | fprintf(stdout, "\n"); 762 | update_title(tmp); 763 | while (tmp->next) { 764 | fprintf(stdout, " -> "); 765 | tmp = tmp->next; 766 | update_title(tmp); 767 | } 768 | fprintf(stdout, "\n"); 769 | } 770 | 771 | void update_all_windows() 772 | { 773 | struct client *c; 774 | 775 | for (c = head; c; c = c->next) { 776 | if (current == c) { 777 | XSetWindowBorderWidth(dpy, c->win, 1); 778 | XSetWindowBorder(dpy, c->win, color_light); 779 | XSetInputFocus(dpy, c->win, RevertToParent, CurrentTime); 780 | XRaiseWindow(dpy, c->win); 781 | } else { 782 | XSetWindowBorder(dpy, c->win, color_dark); 783 | } 784 | } 785 | } 786 | 787 | void update_status(void) { 788 | if(!gettextprop(root, XA_WM_NAME, status_text, sizeof(status_text))) 789 | strcpy(status_text, "maxwelm"); 790 | } 791 | 792 | void update_title(struct client *c) 793 | { 794 | static const char broken[] = "broken"; 795 | 796 | if (!gettextprop(c->win, NetWMName, c->name, sizeof c->name)) 797 | gettextprop(c->win, XA_WM_NAME, c->name, sizeof c->name); 798 | if (c->name[0] == '\0') /* hack to mark broken Clients */ 799 | strcpy(c->name, broken); 800 | fprintf(stdout, "[%d|%s]", currentdesktop, c->name); 801 | } 802 | 803 | int main(void) 804 | { 805 | if(!(dpy = XOpenDisplay(0x0))) return 1; 806 | 807 | setup(); 808 | 809 | run(); 810 | 811 | cleanup(); 812 | 813 | XCloseDisplay(dpy); 814 | 815 | return 0; 816 | } 817 | 818 | 819 | -------------------------------------------------------------------------------- /statusbar.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | 4 | battery() { 5 | if acpi -a |grep off-line > /dev/null; then 6 | echo "BAT $(acpi -b |awk 'sub(/,/,"") {print $4}') " 7 | fi 8 | } 9 | 10 | clock() { 11 | date '+%I:%M' 12 | } 13 | 14 | cpuload() { 15 | iostat -c | awk 'BEGIN {sum=0.0f} {sum+=$1} END {print sum}' 16 | } 17 | 18 | memram() { 19 | FREE_DATA=`free -m | grep Mem` 20 | CURRENT=`echo $FREE_DATA | cut -f3 -d' '` 21 | TOTAL=`echo $FREE_DATA | cut -f2 -d' '` 22 | echo "scale = 2; $CURRENT/$TOTAL*100" | bc 23 | } 24 | 25 | wifi() { 26 | ISUP="$(ip link |grep wlp3s0 |awk '{print $9}')" 27 | if [ "$ISUP" != "UP" ]; then 28 | echo -n "WIFI " ; echo -n $ISUP ; echo "! " 29 | fi 30 | } 31 | 32 | while :; do 33 | buf=" " 34 | buf="${buf}$(battery)" 35 | buf="${buf}$(wifi)" 36 | buf="${buf}CPU[$(cpuload)%] " 37 | buf="${buf}RAM[$(memram)%] " 38 | buf="${buf}[$(clock)]" 39 | xsetroot -name "$buf"; 40 | #echo $buf 41 | sleep 2 42 | done 43 | --------------------------------------------------------------------------------