sowm

An itsy bitsy floating window manager (220~ sloc!).
git clone git://mfeller.io/sowm.git
Log | Files | Refs | README | LICENSE

commit 95b696bdc2088ec5077c32c1bb61bca0aeade90e
Author: Dylan Araps <dylan.araps@gmail.com>
Date:   Fri, 11 Oct 2019 14:48:34 +0300

docs: update

Diffstat:
ALICENCE_DWM | 32++++++++++++++++++++++++++++++++
AMakefile | 20++++++++++++++++++++
AREADME.md | 11+++++++++++
Acatwm.c | 674+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aconfig.h | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 831 insertions(+), 0 deletions(-)

diff --git a/LICENCE_DWM b/LICENCE_DWM @@ -0,0 +1,32 @@ +MIT/X Consortium License + +© 2006-2011 Anselm R Garbe <anselm@garbe.us> +© 2007-2011 Peter Hartlich <sgkkr at hartlich dot com> +© 2010-2011 Connor Lane Smith <cls@lubutu.com> +© 2006-2009 Jukka Salmi <jukka at salmi dot ch> +© 2007-2009 Premysl Hruby <dfenze at gmail dot com> +© 2007-2009 Szabolcs Nagy <nszabolcs at gmail dot com> +© 2007-2009 Christof Musik <christof at sendfax dot de> +© 2009 Mate Nagy <mnagy at port70 dot net> +© 2007-2008 Enno Gottox Boland <gottox at s01 dot de> +© 2008 Martin Hurton <martin dot hurton at gmail dot com> +© 2008 Neale Pickett <neale dot woozle dot org> +© 2006-2007 Sander van Dijk <a dot h dot vandijk at gmail dot com> + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile @@ -0,0 +1,20 @@ +CFLAGS+= -Wall +LDADD+= -lX11 +LDFLAGS= +EXEC=catwm + +PREFIX?= /usr +BINDIR?= $(PREFIX)/bin + +CC=gcc + +all: $(EXEC) + +catwm: catwm.o + $(CC) $(LDFLAGS) -Os -o $@ $+ $(LDADD) + +install: all + install -Dm 755 catwm $(DESTDIR)$(BINDIR)/catwm + +clean: + rm -f catwm *.o diff --git a/README.md b/README.md @@ -0,0 +1,11 @@ +# catwm + +``` + + /\___/\ + ( o o ) Made by cat... + ( =^= ) + ( ) ... for cat! + ( ) + ( ))))))________________ Cute And Tiny Window Manager +``` diff --git a/catwm.c b/catwm.c @@ -0,0 +1,674 @@ + /* + * /\___/\ + * ( o o ) Made by cat... + * ( =^= ) + * ( ) ... for cat! + * ( ) + * ( ))))))________________ Cute And Tiny Window Manager + * ______________________________________________________________________________ + * + * Copyright (c) 2010, Rinaldini Julien, julien.rinaldini@heig-vd.ch + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +#include <X11/Xlib.h> +#include <X11/keysym.h> +#include <X11/XF86keysym.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <signal.h> +#include <sys/wait.h> + +#define TABLENGTH(X) (sizeof(X)/sizeof(*X)) + +typedef union { + const char** com; + const int i; +} Arg; + +// Structs +struct key { + unsigned int mod; + KeySym keysym; + void (*function)(const Arg arg); + const Arg arg; +}; + +typedef struct client client; +struct client{ + // Prev and next client + client *next; + client *prev; + + // The window + Window win; +}; + +typedef struct desktop desktop; +struct desktop{ + int master_size; + int mode; + client *head; + client *current; +}; + +// Functions +static void add_window(Window w); +static void change_desktop(const Arg arg); +static void client_to_desktop(const Arg arg); +static void configurenotify(XEvent *e); +static void configurerequest(XEvent *e); +static void decrease(); +static void destroynotify(XEvent *e); +static void die(const char* e); +static unsigned long getcolor(const char* color); +static void grabkeys(); +static void increase(); +static void keypress(XEvent *e); +static void kill_client(); +static void maprequest(XEvent *e); +static void move_down(); +static void move_up(); +static void next_desktop(); +static void next_win(); +static void prev_desktop(); +static void prev_win(); +static void quit(); +static void remove_window(Window w); +static void save_desktop(int i); +static void select_desktop(int i); +static void send_kill_signal(Window w); +static void setup(); +static void sigchld(int unused); +static void spawn(const Arg arg); +static void start(); +//static void swap(); +static void swap_master(); +static void switch_mode(); +static void tile(); +static void update_current(); + +// Include configuration file (need struct key) +#include "config.h" + +// Variable +static Display *dis; +static int bool_quit; +static int current_desktop; +static int master_size; +static int mode; +static int sh; +static int sw; +static int screen; +static unsigned int win_focus; +static unsigned int win_unfocus; +static Window root; +static client *head; +static client *current; + +// Events array +static void (*events[LASTEvent])(XEvent *e) = { + [KeyPress] = keypress, + [MapRequest] = maprequest, + [DestroyNotify] = destroynotify, + [ConfigureNotify] = configurenotify, + [ConfigureRequest] = configurerequest +}; + +// Desktop array +static desktop desktops[10]; + +void add_window(Window w) { + client *c,*t; + + if(!(c = (client *)calloc(1,sizeof(client)))) + die("Error calloc!"); + + if(head == NULL) { + c->next = NULL; + c->prev = NULL; + c->win = w; + head = c; + } + else { + for(t=head;t->next;t=t->next); + + c->next = NULL; + c->prev = t; + c->win = w; + + t->next = c; + } + + current = c; +} + +void change_desktop(const Arg arg) { + client *c; + + if(arg.i == current_desktop) + return; + + // Unmap all window + if(head != NULL) + for(c=head;c;c=c->next) + XUnmapWindow(dis,c->win); + + // Save current "properties" + save_desktop(current_desktop); + + // Take "properties" from the new desktop + select_desktop(arg.i); + + // Map all windows + if(head != NULL) + for(c=head;c;c=c->next) + XMapWindow(dis,c->win); + + tile(); + update_current(); +} + +void client_to_desktop(const Arg arg) { + client *tmp = current; + int tmp2 = current_desktop; + + if(arg.i == current_desktop || current == NULL) + return; + + // Add client to desktop + select_desktop(arg.i); + add_window(tmp->win); + save_desktop(arg.i); + + // Remove client from current desktop + select_desktop(tmp2); + remove_window(current->win); + + tile(); + update_current(); +} + +void configurenotify(XEvent *e) { + // Do nothing for the moment +} + +void configurerequest(XEvent *e) { + // Paste from DWM, thx again \o/ + XConfigureRequestEvent *ev = &e->xconfigurerequest; + XWindowChanges wc; + wc.x = ev->x; + wc.y = ev->y; + wc.width = ev->width; + wc.height = ev->height; + wc.border_width = ev->border_width; + wc.sibling = ev->above; + wc.stack_mode = ev->detail; + XConfigureWindow(dis, ev->window, ev->value_mask, &wc); +} + +void decrease() { + if(master_size > 50) { + master_size -= 10; + tile(); + } +} + +void destroynotify(XEvent *e) { + int i=0; + client *c; + XDestroyWindowEvent *ev = &e->xdestroywindow; + + // Uber (and ugly) hack ;) + for(c=head;c;c=c->next) + if(ev->window == c->win) + i++; + + // End of the hack + if(i == 0) + return; + + remove_window(ev->window); + tile(); + update_current(); +} + +void die(const char* e) { + fprintf(stdout,"catwm: %s\n",e); + exit(1); +} + +unsigned long getcolor(const char* color) { + XColor c; + Colormap map = DefaultColormap(dis,screen); + + if(!XAllocNamedColor(dis,map,color,&c,&c)) + die("Error parsing color!"); + + return c.pixel; +} + +void grabkeys() { + int i; + KeyCode code; + + // For each shortcuts + for(i=0;i<TABLENGTH(keys);++i) { + if((code = XKeysymToKeycode(dis,keys[i].keysym))) { + XGrabKey(dis,code,keys[i].mod,root,True,GrabModeAsync,GrabModeAsync); + } + } +} + +void increase() { + if(master_size < sw-50) { + master_size += 10; + tile(); + } +} + +void keypress(XEvent *e) { + int i; + XKeyEvent ke = e->xkey; + KeySym keysym = XKeycodeToKeysym(dis,ke.keycode,0); + + for(i=0;i<TABLENGTH(keys);++i) { + if(keys[i].keysym == keysym && keys[i].mod == ke.state) { + keys[i].function(keys[i].arg); + } + } +} + +void kill_client() { + if(current != NULL) { + //send delete signal to window + XEvent ke; + ke.type = ClientMessage; + ke.xclient.window = current->win; + ke.xclient.message_type = XInternAtom(dis, "WM_PROTOCOLS", True); + ke.xclient.format = 32; + ke.xclient.data.l[0] = XInternAtom(dis, "WM_DELETE_WINDOW", True); + ke.xclient.data.l[1] = CurrentTime; + XSendEvent(dis, current->win, False, NoEventMask, &ke); + send_kill_signal(current->win); + } +} + +void maprequest(XEvent *e) { + XMapRequestEvent *ev = &e->xmaprequest; + + // For fullscreen mplayer (and maybe some other program) + client *c; + for(c=head;c;c=c->next) + if(ev->window == c->win) { + XMapWindow(dis,ev->window); + return; + } + + add_window(ev->window); + XMapWindow(dis,ev->window); + tile(); + update_current(); +} + +void move_down() { + Window tmp; + if(current == NULL || current->next == NULL || current->win == head->win || current->prev == NULL) { + return; + } + tmp = current->win; + current->win = current->next->win; + current->next->win = tmp; + //keep the moved window activated + next_win(); + tile(); + update_current(); +} + +void move_up() { + Window tmp; + if(current == NULL || current->prev == head || current->win == head->win) { + return; + } + tmp = current->win; + current->win = current->prev->win; + current->prev->win = tmp; + prev_win(); + tile(); + update_current(); +} + +void next_desktop() { + int tmp = current_desktop; + if(tmp== 9) + tmp = 0; + else + tmp++; + + Arg a = {.i = tmp}; + change_desktop(a); +} + +void next_win() { + client *c; + + if(current != NULL && head != NULL) { + if(current->next == NULL) + c = head; + else + c = current->next; + + current = c; + update_current(); + } +} + +void prev_desktop() { + int tmp = current_desktop; + if(tmp == 0) + tmp = 9; + else + tmp--; + + Arg a = {.i = tmp}; + change_desktop(a); +} + +void prev_win() { + client *c; + + if(current != NULL && head != NULL) { + if(current->prev == NULL) + for(c=head;c->next;c=c->next); + else + c = current->prev; + + current = c; + update_current(); + } +} + +void quit() { + Window root_return, parent; + Window *children; + int i; + unsigned int nchildren; + XEvent ev; + + /* + * if a client refuses to terminate itself, + * we kill every window remaining the brutal way. + * Since we're stuck in the while(nchildren > 0) { ... } loop + * we can't exit through the main method. + * This all happens if MOD+q is pushed a second time. + */ + if(bool_quit == 1) { + XUngrabKey(dis, AnyKey, AnyModifier, root); + XDestroySubwindows(dis, root); + fprintf(stdout, "catwm: Thanks for using!\n"); + XCloseDisplay(dis); + die("forced shutdown"); + } + + bool_quit = 1; + XQueryTree(dis, root, &root_return, &parent, &children, &nchildren); + for(i = 0; i < nchildren; i++) { + send_kill_signal(children[i]); + } + //keep alive until all windows are killed + while(nchildren > 0) { + XQueryTree(dis, root, &root_return, &parent, &children, &nchildren); + XNextEvent(dis,&ev); + if(events[ev.type]) + events[ev.type](&ev); + } + + XUngrabKey(dis,AnyKey,AnyModifier,root); + fprintf(stdout,"catwm: Thanks for using!\n"); +} + +void remove_window(Window w) { + client *c; + + // CHANGE THIS UGLY CODE + for(c=head;c;c=c->next) { + + if(c->win == w) { + if(c->prev == NULL && c->next == NULL) { + free(head); + head = NULL; + current = NULL; + return; + } + + if(c->prev == NULL) { + head = c->next; + c->next->prev = NULL; + current = c->next; + } + else if(c->next == NULL) { + c->prev->next = NULL; + current = c->prev; + } + else { + c->prev->next = c->next; + c->next->prev = c->prev; + current = c->prev; + } + + free(c); + return; + } + } +} + +void save_desktop(int i) { + desktops[i].master_size = master_size; + desktops[i].mode = mode; + desktops[i].head = head; + desktops[i].current = current; +} + +void select_desktop(int i) { + head = desktops[i].head; + current = desktops[i].current; + master_size = desktops[i].master_size; + mode = desktops[i].mode; + current_desktop = i; +} + +void send_kill_signal(Window w) { + XEvent ke; + ke.type = ClientMessage; + ke.xclient.window = w; + ke.xclient.message_type = XInternAtom(dis, "WM_PROTOCOLS", True); + ke.xclient.format = 32; + ke.xclient.data.l[0] = XInternAtom(dis, "WM_DELETE_WINDOW", True); + ke.xclient.data.l[1] = CurrentTime; + XSendEvent(dis, w, False, NoEventMask, &ke); +} + +void setup() { + // Install a signal + sigchld(0); + + // Screen and root window + screen = DefaultScreen(dis); + root = RootWindow(dis,screen); + + // Screen width and height + sw = XDisplayWidth(dis,screen); + sh = XDisplayHeight(dis,screen); + + // Colors + win_focus = getcolor(FOCUS); + win_unfocus = getcolor(UNFOCUS); + + // Shortcuts + grabkeys(); + + // Vertical stack + mode = 0; + + // For exiting + bool_quit = 0; + + // List of client + head = NULL; + current = NULL; + + // Master size + master_size = sw*MASTER_SIZE; + + // Set up all desktop + int i; + for(i=0;i<TABLENGTH(desktops);++i) { + desktops[i].master_size = master_size; + desktops[i].mode = mode; + desktops[i].head = head; + desktops[i].current = current; + } + + // Select first dekstop by default + const Arg arg = {.i = 1}; + current_desktop = arg.i; + change_desktop(arg); + + // To catch maprequest and destroynotify (if other wm running) + XSelectInput(dis,root,SubstructureNotifyMask|SubstructureRedirectMask); +} + +void sigchld(int unused) { + // Again, thx to dwm ;) + if(signal(SIGCHLD, sigchld) == SIG_ERR) + die("Can't install SIGCHLD handler"); + while(0 < waitpid(-1, NULL, WNOHANG)); +} + +void spawn(const Arg arg) { + if(fork() == 0) { + if(fork() == 0) { + if(dis) + close(ConnectionNumber(dis)); + + setsid(); + execvp((char*)arg.com[0],(char**)arg.com); + } + exit(0); + } +} + +void start() { + XEvent ev; + + // Main loop, just dispatch events (thx to dwm ;) + while(!bool_quit && !XNextEvent(dis,&ev)) { + if(events[ev.type]) + events[ev.type](&ev); + } +} + +void swap_master() { + Window tmp; + + if(head != NULL && current != NULL && current != head && mode == 0) { + tmp = head->win; + head->win = current->win; + current->win = tmp; + current = head; + + tile(); + update_current(); + } +} + +void switch_mode() { + mode = (mode == 0) ? 1:0; + tile(); + update_current(); +} + +void tile() { + client *c; + int n = 0; + int y = 0; + + // If only one window + if(head != NULL && head->next == NULL) { + XMoveResizeWindow(dis,head->win,0,0,sw-2,sh-2); + } + else if(head != NULL) { + switch(mode) { + case 0: + // Master window + XMoveResizeWindow(dis,head->win,0,0,master_size-2,sh-2); + + // Stack + for(c=head->next;c;c=c->next) ++n; + for(c=head->next;c;c=c->next) { + XMoveResizeWindow(dis,c->win,master_size,y,sw-master_size-2,(sh/n)-2); + y += sh/n; + } + break; + case 1: + for(c=head;c;c=c->next) { + XMoveResizeWindow(dis,c->win,0,0,sw,sh); + } + break; + default: + break; + } + } +} + +void update_current() { + client *c; + + for(c=head;c;c=c->next) + if(current == c) { + // "Enable" current window + XSetWindowBorderWidth(dis,c->win,1); + XSetWindowBorder(dis,c->win,win_focus); + XSetInputFocus(dis,c->win,RevertToParent,CurrentTime); + XRaiseWindow(dis,c->win); + } + else + XSetWindowBorder(dis,c->win,win_unfocus); +} + +int main(int argc, char **argv) { + // Open display + if(!(dis = XOpenDisplay(NULL))) + die("Cannot open display!"); + + // Setup env + setup(); + + // Start wm + start(); + + // Close display + XCloseDisplay(dis); + + return 0; +} + diff --git a/config.h b/config.h @@ -0,0 +1,94 @@ + /* + * /\___/\ + * ( o o ) Made by cat... + * ( =^= ) + * ( ) ... for cat! + * ( ) + * ( ))))))________________ Cute And Tiny Window Manager + * ______________________________________________________________________________ + * + * Copyright (c) 2010, Rinaldini Julien, julien.rinaldini@heig-vd.ch + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef CONFIG_H +#define CONFIG_H + +// Mod (Mod1 == alt) and master size +#define MOD Mod1Mask +#define MASTER_SIZE 0.6 + +// Colors +#define FOCUS "rgb:bc/57/66" +#define UNFOCUS "rgb:88/88/88" + +const char* dmenucmd[] = {"dmenu_run",NULL}; +const char* urxvtcmd[] = {"urxvt",NULL}; +const char* lockcmd[] = {"slock",NULL}; +const char* next[] = {"ncmpcpp","next",NULL}; +const char* prev[] = {"ncmpcpp","prev",NULL}; +const char* toggle[] = {"ncmpcpp","toggle",NULL }; +const char* voldown[] = {"amixer","set","PCM","5\%-",NULL}; +const char* volup[] = {"amixer","set","PCM","5\%+",NULL}; + +// Avoid multiple paste +#define DESKTOPCHANGE(K,N) \ + { MOD, K, change_desktop, {.i = N}}, \ + { MOD|ShiftMask, K, client_to_desktop, {.i = N}}, + +// Shortcuts +static struct key keys[] = { + // MOD KEY FUNCTION ARGS + { MOD, XK_h, decrease, {NULL}}, + { MOD, XK_l, increase, {NULL}}, + { MOD, XK_x, kill_client, {NULL}}, + { MOD, XK_j, next_win, {NULL}}, + { MOD, XK_Tab, next_win, {NULL}}, + { MOD, XK_k, prev_win, {NULL}}, + { MOD|ShiftMask, XK_j, move_up, {NULL}}, + { MOD|ShiftMask, XK_k, move_down, {NULL}}, + { MOD, XK_Return, swap_master, {NULL}}, + { MOD, XK_space, switch_mode, {NULL}}, + { MOD, XK_c, spawn, {.com = lockcmd}}, + { 0, XF86XK_AudioNext, spawn, {.com = next}}, + { 0, XF86XK_AudioPrev, spawn, {.com = prev}}, + { 0, XF86XK_AudioPlay, spawn, {.com = toggle}}, + { 0, XF86XK_AudioLowerVolume, spawn, {.com = voldown}}, + { 0, XF86XK_AudioRaiseVolume, spawn, {.com = volup}}, + { MOD, XK_p, spawn, {.com = dmenucmd}}, + { MOD|ShiftMask, XK_Return, spawn, {.com = urxvtcmd}}, + { MOD, XK_Right, next_desktop, {NULL}}, + { MOD, XK_Left, prev_desktop, {NULL}}, + DESKTOPCHANGE( XK_0, 0) + DESKTOPCHANGE( XK_1, 1) + DESKTOPCHANGE( XK_2, 2) + DESKTOPCHANGE( XK_3, 3) + DESKTOPCHANGE( XK_4, 4) + DESKTOPCHANGE( XK_5, 5) + DESKTOPCHANGE( XK_6, 6) + DESKTOPCHANGE( XK_7, 7) + DESKTOPCHANGE( XK_8, 8) + DESKTOPCHANGE( XK_9, 9) + { MOD, XK_q, quit, {NULL}} +}; + +#endif +