├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── config.h ├── monsterwm.1 └── monsterwm.c /.gitignore: -------------------------------------------------------------------------------- 1 | monsterwm 2 | *.o 3 | *.swp 4 | *~ 5 | *.diff 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | © 2011 Ivan c00kiemon5ter Kanakarakis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. 22 | 23 | --- --- --- --- --- --- --- --- --- --- --- --- --- 24 | Portions of dwm code are included in this project. 25 | Those portions are licensed as follows. 26 | --- --- --- --- --- --- --- --- --- --- --- --- --- 27 | 28 | MIT/X Consortium License 29 | 30 | © 2006-2011 Anselm R Garbe 31 | © 2007-2011 Peter Hartlich 32 | © 2010-2011 Connor Lane Smith 33 | © 2006-2009 Jukka Salmi 34 | © 2007-2009 Premysl Hruby 35 | © 2007-2009 Szabolcs Nagy 36 | © 2007-2009 Christof Musik 37 | © 2009 Mate Nagy 38 | © 2007-2008 Enno Gottox Boland 39 | © 2008 Martin Hurton 40 | © 2008 Neale Pickett 41 | © 2006-2007 Sander van Dijk 42 | 43 | Permission is hereby granted, free of charge, to any person obtaining a 44 | copy of this software and associated documentation files (the "Software"), 45 | to deal in the Software without restriction, including without limitation 46 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 47 | and/or sell copies of the Software, and to permit persons to whom the 48 | Software is furnished to do so, subject to the following conditions: 49 | 50 | The above copyright notice and this permission notice shall be included in 51 | all copies or substantial portions of the Software. 52 | 53 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 54 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 55 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 56 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 57 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 58 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 59 | DEALINGS IN THE SOFTWARE. 60 | 61 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for monsterwm - see LICENSE for license and copyright information 2 | 3 | VERSION = cookies-git 4 | WMNAME = monsterwm 5 | 6 | PREFIX ?= /usr/local 7 | BINDIR ?= ${PREFIX}/bin 8 | MANPREFIX = ${PREFIX}/share/man 9 | 10 | X11INC = -I/usr/X11R6/include 11 | X11LIB = -L/usr/X11R6/lib -lX11 12 | XINERAMALIB = -lXinerama 13 | 14 | INCS = -I. -I/usr/include ${X11INC} 15 | LIBS = -L/usr/lib -lc ${X11LIB} ${XINERAMALIB} 16 | 17 | CFLAGS = -std=c99 -pedantic -Wall -Wextra ${INCS} -DVERSION=\"${VERSION}\" 18 | LDFLAGS = ${LIBS} 19 | 20 | CC = cc 21 | EXEC = ${WMNAME} 22 | 23 | SRC = ${WMNAME}.c 24 | OBJ = ${SRC:.c=.o} 25 | 26 | all: CFLAGS += -Os 27 | all: LDFLAGS += -s 28 | all: options ${WMNAME} 29 | 30 | debug: CFLAGS += -O0 -g 31 | debug: options ${WMNAME} 32 | 33 | options: 34 | @echo ${WMNAME} build options: 35 | @echo "CFLAGS = ${CFLAGS}" 36 | @echo "LDFLAGS = ${LDFLAGS}" 37 | @echo "CC = ${CC}" 38 | 39 | .c.o: 40 | @echo CC $< 41 | @${CC} -c ${CFLAGS} $< 42 | 43 | ${OBJ}: config.h 44 | 45 | config.h: 46 | @echo creating $@ from config.def.h 47 | @cp config.def.h $@ 48 | 49 | ${WMNAME}: ${OBJ} 50 | @echo CC -o $@ 51 | @${CC} -o $@ ${OBJ} ${LDFLAGS} 52 | 53 | clean: 54 | @echo cleaning 55 | @rm -fv ${WMNAME} ${OBJ} ${WMNAME}-${VERSION}.tar.gz 56 | 57 | install: all 58 | @echo installing executable file to ${DESTDIR}${PREFIX}/bin 59 | @install -Dm755 ${WMNAME} ${DESTDIR}${PREFIX}/bin/${WMNAME} 60 | @echo installing manual page to ${DESTDIR}${MANPREFIX}/man.1 61 | @install -Dm644 ${WMNAME}.1 ${DESTDIR}${MANPREFIX}/man1/${WMNAME}.1 62 | 63 | uninstall: 64 | @echo removing executable file from ${DESTDIR}${PREFIX}/bin 65 | @rm -f ${DESTDIR}${PREFIX}/bin/${WMNAME} 66 | @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 67 | @rm -f ${DESTDIR}${MANPREFIX}/man1/${WMNAME}.1 68 | 69 | .PHONY: all options clean install uninstall 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | monsterwm 2 | ========= 3 | 4 | → tiny and monstrous! 5 | --------------------- 6 | 7 | **monsterwm** is a minimal, lightweight, tiny but monstrous dynamic tiling window manager. 8 | It provides a set of different layout modes (see below), including floating mode support. 9 | Each virtual desktop has its own properties, unaffected by other desktops' or monitors' settings. 10 | 11 | ***Attention : Since monsterwm uses xinerama, if xinerama breaks down, monsterwm will break too.*** 12 | ***Unplug a monitor that already defined in xorg.conf will cause xinerama crash.*** 13 | ***Be careful with the xorg.conf !*** 14 | 15 | 16 | Modes 17 | ----- 18 | 19 | Monsterwm allows opening the new window as master or 20 | opening the window at the bottom of the stack (attach\_aside) 21 | 22 | --- 23 | 24 | *Common tiling mode:* 25 | 26 | -------------- 27 | | | W | 28 | | |___| 29 | | Master | | 30 | | |___| 31 | | | | 32 | -------------- 33 | 34 | --- 35 | 36 | *Bottom Stack (bstack) tiling mode:* 37 | 38 | ------------- 39 | | | 40 | | Master | 41 | |-----------| 42 | | W | | | 43 | ------------- 44 | 45 | --- 46 | 47 | *Grid tiling mode:* 48 | 49 | ------------- 50 | | | | | 51 | |---|---|---| 52 | | | | | 53 | |---|---|---| 54 | | | | | 55 | ------------- 56 | 57 | one can have as many windows he wants. 58 | `GRID` layout automatically manages the rows and columns. 59 | 60 | --- 61 | 62 | *Monocle mode* (aka fullscreen) 63 | 64 | ------------- 65 | | | 66 | | | 67 | | | 68 | | | 69 | ------------- 70 | 71 | `MONOCLE` layout presents one window at a time in fullscreen mode. 72 | One can decide whether full screen window have borders or not, have 73 | gaps or not, covers panel or not. 74 | 75 | --- 76 | 77 | *floating mode* 78 | 79 | ------------- 80 | | | | 81 | |--' .---. | 82 | | | | | 83 | | | | | 84 | ------`---'-- 85 | 86 | In floating mode one can freely move and resize windows in the screen space. 87 | Changing desktops, adding or removing floating windows, does not affect the 88 | floating status of the windows. Windows will revert to their tiling mode 89 | position once the user selects a tiling mode. 90 | To enter the floating mode, either change the layout to `FLOAT`, or 91 | enabled it by moving or resizing a window with the mouse, the window 92 | is then marked as being in floating mode. 93 | 94 | --- 95 | 96 | All shortcuts are accessible via the keyboard and the mouse, and defined in `config.h` file. 97 | 98 | All desktops store their settings independently. 99 | 100 | * The window W at the top of the stack can be resized on a per desktop basis. 101 | * Changing a tiling mode or window size on one desktop doesn't affect the other desktops. 102 | * toggling the panel in one desktop does not affect the state of the panel in other desktops. 103 | 104 | 105 | Panel - Statusbar 106 | ----------------- 107 | 108 | The user can define an empty space (by default 18px) on the bottom or top(default) of the 109 | screen, to be used by a panel. The panel is toggleable, but will be visible if no windows 110 | are on the screen. 111 | 112 | Monsterwm does not provide a panel and/or statusbar itself. Instead it adheres 113 | to the [UNIX philosophy][unix] and outputs information about the existent 114 | desktop, the number of windows on each, the mode of each desktop, the current 115 | desktop and urgent hints whenever needed. The user can use whatever tool or 116 | panel suits him best (dzen2, conky, w/e), to process and display that information. 117 | 118 | To disable the panel completely set `PANEL_HEIGHT` to zero `0`. 119 | The `SHOW_PANELL` setting controls whether the panel is visible on startup, 120 | it does not control whether there is a panel or not. 121 | 122 | [unix]: http://en.wikipedia.org/wiki/Unix_philosophy 123 | 124 | Here is a list of minimal and lightweight panels: 125 | 126 | * [`bar`](https://github.com/LemonBoy/bar) 127 | * [`some_sorta_bar`](https://github.com/moetunes/Some_sorta_bar) 128 | * [`bipolarbar`](https://github.com/moetunes/bipolarbar) 129 | * [`mopag`](https://github.com/c00kiemon5ter/mopag) (pager) 130 | 131 | You can find an examples configurations of panels [here](https://gist.github.com/1905427). 132 | You can actually parse monsterwm's output with any language you want, 133 | build anything you want, and display the information however you like. 134 | Do not be limited by those examples. 135 | 136 | 137 | Installation 138 | ------------ 139 | 140 | You need Xlib and xinerama, then, 141 | edit `config.h` to suit your needs. 142 | Build and install. 143 | 144 | $ $EDITOR config.h 145 | $ make 146 | # sudo make clean install 147 | 148 | 149 | Patches 150 | ------- 151 | 152 | Some extensions to the code are supported in the form of patches. 153 | See other branches for the patch and code. 154 | Easiest way to apply a patch, is to `git merge` that branch. 155 | 156 | Currently: 157 | 158 | * ~~[centerwindow] : center new floating windows on the screen and center any window with a shortcut~~ **Already patched** 159 | * [fibonacci] : adds fibonacci layout mode 160 | * ~~[initlayouts] : define initial layouts for every desktop~~ **Already patched** 161 | * ~~[monocleborders] : adds borders to the monocle layout~~ **Already patched** 162 | * [nmaster] : adds nmaster layout - multiple master windows for BSTACK and TILE layouts 163 | * [rectangle] : draws only a rectangle when moving/resizing windows to keep resources low (ie through an ssh forwarded session) 164 | * [showhide] : adds a function to show and hide all windows on all desktops 165 | * ~~[uselessgaps] : adds gaps around every window on screen~~ **Already patched** 166 | * [warpcursor] : cursors follows and is placed in the center of the current window 167 | * [windowtitles] : along with the rest desktop info, output the title of the current window 168 | 169 | [centerwindow]: https://github.com/c00kiemon5ter/monsterwm/tree/centerwindow 170 | [fibonacci]: https://github.com/c00kiemon5ter/monsterwm/tree/fibonacci 171 | [initlayouts]: https://github.com/c00kiemon5ter/monsterwm/tree/initlayouts 172 | [monocleborders]: https://github.com/c00kiemon5ter/monsterwm/tree/monocleborders 173 | [nmaster]: https://github.com/c00kiemon5ter/monsterwm/tree/nmaster 174 | [rectangle]: https://github.com/c00kiemon5ter/monsterwm/tree/rectangle 175 | [showhide]: https://github.com/c00kiemon5ter/monsterwm/tree/showhide 176 | [uselessgaps]: https://github.com/c00kiemon5ter/monsterwm/tree/uselessgaps 177 | [warpcursor]: https://github.com/c00kiemon5ter/monsterwm/tree/warpcursor 178 | [windowtitles]: https://github.com/c00kiemon5ter/monsterwm/tree/windowtitles 179 | 180 | 181 | Bugs 182 | ---- 183 | 184 | For any bug or request please [fill an issue][bug]. 185 | 186 | [bug]: https://github.com/A1phaZer0/monsterwm-xinerama/issues 187 | 188 | 189 | 190 | License 191 | ------- 192 | 193 | Licensed under MIT/X Consortium License, see [LICENSE][law] file for more copyright and license information. 194 | 195 | [law]: https://raw.github.com/c00kiemon5ter/monsterwm/master/LICENSE 196 | 197 | Thanks 198 | ------ 199 | 200 | * [the suckless team](http://suckless.org/) 201 | * [moetunes](https://github.com/moetunes) 202 | * [pyknite](https://github.com/pyknite) 203 | * [richo4](https://github.com/richo4) 204 | * [Cloudef](https://github.com/Cloudef) 205 | * [jasonwryan](https://github.com/jasonwryan) 206 | * [LemonBoy](https://github.com/LemonBoy) 207 | * [djura-san](https://github.com/djura-san) 208 | * [prasinoulhs](https://github.com/prasinoulhs) 209 | * [mil](https://github.com/mil) 210 | * [dnuux](https://github.com/dnuux) 211 | * Matus Telgarsky 212 | 213 | Screenshots 214 | ------ 215 | ![](http://i.imgur.com/Jv5KpxV.png) 216 | ![](http://i.imgur.com/7qK5Zyj.png) 217 | ![](http://i.imgur.com/ooxHBmQ.png) 218 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | /* see LICENSE for copyright and license */ 2 | 3 | #ifndef CONFIG_H 4 | #define CONFIG_H 5 | 6 | /** modifiers **/ 7 | #define MOD1 Mod1Mask /* ALT key */ 8 | #define MOD4 Mod4Mask /* Super/Windows key */ 9 | #define CONTROL ControlMask /* Control key */ 10 | #define SHIFT ShiftMask /* Shift key */ 11 | 12 | /** generic settings **/ 13 | #define MASTER_SIZE 0.50 14 | #define SHOW_PANEL True /* show panel by default on exec */ 15 | #define TOP_PANEL True /* False means panel is on bottom */ 16 | #define PANEL_HEIGHT 24 /* 0 for no space for panel, thus no panel */ 17 | #define DEFAULT_MODE FLOAT /* initial layout/mode: TILE MONOCLE BSTACK GRID FLOAT */ 18 | #define ATTACH_ASIDE True /* False means new window is master */ 19 | #define FOLLOW_WINDOW False /* follow the window when moved to a different desktop */ 20 | #define FOLLOW_MONITOR True /* follow the window when moved to a different monitor */ 21 | #define FOLLOW_MOUSE False /* focus the window the mouse just entered */ 22 | #define CLICK_TO_FOCUS True /* focus an unfocused window when clicked */ 23 | #define FOCUS_BUTTON Button3 /* mouse button to be used along with CLICK_TO_FOCUS */ 24 | #define BORDER_WIDTH 4 /* window border width */ 25 | #define FOCUS "#CC6666" /* focused window border color */ 26 | #define UNFOCUS "#373B41" /* unfocused window border color */ 27 | #define INFOCUS "#5F819D" /* focused window border color on unfocused monitor */ 28 | #define MINWSZ 50 /* minimum window size in pixels */ 29 | #define DEFAULT_MONITOR 0 /* the monitor to focus initially */ 30 | #define DEFAULT_DESKTOP 0 /* the desktop to focus initially */ 31 | #define DESKTOPS 4 /* number of desktops - edit DESKTOPCHANGE keys to suit */ 32 | #define USELESSGAP 8 /* the size of the useless gap in pixels */ 33 | #define COVER_PANEL False /* full screen window covers panel or not */ 34 | #define MONOCLE_GAP False /* useless gap in monocle mode 35 | * set MONOCLE_BORDER to **True** before using this*/ 36 | #define MONOCLE_BORDER False /* window border in monocle mode */ 37 | 38 | 39 | struct ml { 40 | int m; /* monitor that the desktop in on */ 41 | int d; /* desktop which properties follow */ 42 | struct { 43 | int mode; /* layout mode for desktop d of monitor m */ 44 | int masz; /* incread or decrease master area in px */ 45 | Bool sbar; /* whether or not to show panel on desktop d */ 46 | } dl; 47 | }; 48 | 49 | /** 50 | * define initial values for each monitor and dekstop properties 51 | * 52 | * in the example below: 53 | * - the first desktop (0) on the first monitor (0) will have 54 | * tile layout, with its master area increased by 50px and 55 | * the panel will be visible. 56 | * - the third desktop (2) on the second monitor (1) will have 57 | * grid layout, with no changes to its master area and 58 | * the panel will be hidden. 59 | */ 60 | static const struct ml init[] = { \ 61 | /* monitor desktop mode masz sbar */ 62 | { 0, 0, { TILE, 50, True } }, 63 | { 1, 2, { GRID, 0, False } }, 64 | }; 65 | 66 | /** 67 | * open applications to specified monitor and desktop 68 | * with the specified properties. 69 | * if monitor is negative, then current is assumed 70 | * if desktop is negative, then current is assumed 71 | */ 72 | static const AppRule rules[] = { \ 73 | /* class monitor desktop follow float */ 74 | { "MPlayer", 0, 3, True, False }, 75 | { "Gimp", 1, 0, False, True }, 76 | }; 77 | 78 | /* helper for spawning shell commands */ 79 | #define SHCMD(cmd) {.com = (const char*[]){"/bin/sh", "-c", cmd, NULL}} 80 | 81 | /** 82 | * custom commands 83 | * must always end with ', NULL };' 84 | */ 85 | static const char *termcmd[] = { "urxvt", NULL }; 86 | 87 | #define MONITORCHANGE(K,N) \ 88 | { MOD4, K, change_monitor, {.i = N}}, \ 89 | { MOD4|ShiftMask, K, client_to_monitor, {.i = N}}, 90 | 91 | #define DESKTOPCHANGE(K,N) \ 92 | { MOD1, K, change_desktop, {.i = N}}, \ 93 | { MOD1|ShiftMask, K, client_to_desktop, {.i = N}}, 94 | 95 | /** 96 | * keyboard shortcuts 97 | */ 98 | static Key keys[] = { 99 | /* modifier key function argument */ 100 | { MOD1, XK_b, togglepanel, {NULL}}, 101 | { MOD1, XK_BackSpace, focusurgent, {NULL}}, 102 | { MOD1|SHIFT, XK_c, killclient, {NULL}}, 103 | { MOD1, XK_j, next_win, {NULL}}, 104 | { MOD1, XK_k, prev_win, {NULL}}, 105 | { MOD1, XK_h, resize_master, {.i = -10}}, /* decrease size in px */ 106 | { MOD1, XK_l, resize_master, {.i = +10}}, /* increase size in px */ 107 | { MOD1, XK_o, resize_stack, {.i = -10}}, /* shrink size in px */ 108 | { MOD1, XK_p, resize_stack, {.i = +10}}, /* grow size in px */ 109 | { MOD1|CONTROL, XK_h, rotate, {.i = -1}}, 110 | { MOD1|CONTROL, XK_l, rotate, {.i = +1}}, 111 | { MOD1|SHIFT, XK_h, rotate_filled, {.i = -1}}, 112 | { MOD1|SHIFT, XK_l, rotate_filled, {.i = +1}}, 113 | { MOD1, XK_Tab, last_desktop, {NULL}}, 114 | { MOD1, XK_Return, swap_master, {NULL}}, 115 | { MOD1|SHIFT, XK_j, move_down, {NULL}}, 116 | { MOD1|SHIFT, XK_k, move_up, {NULL}}, 117 | { MOD1|SHIFT, XK_t, switch_mode, {.i = TILE}}, 118 | { MOD1|SHIFT, XK_m, switch_mode, {.i = MONOCLE}}, 119 | { MOD1|SHIFT, XK_b, switch_mode, {.i = BSTACK}}, 120 | { MOD1|SHIFT, XK_g, switch_mode, {.i = GRID}}, 121 | { MOD1|SHIFT, XK_f, switch_mode, {.i = FLOAT}}, 122 | { MOD1|CONTROL, XK_r, quit, {.i = 0}}, /* quit with exit value 0 */ 123 | { MOD1|CONTROL, XK_q, quit, {.i = 1}}, /* quit with exit value 1 */ 124 | { MOD1|SHIFT, XK_Return, spawn, {.com = termcmd}}, 125 | { MOD4, XK_j, moveresize, {.v = (int []){ 0, 25, 0, 0 }}}, /* move down */ 126 | { MOD4, XK_k, moveresize, {.v = (int []){ 0, -25, 0, 0 }}}, /* move up */ 127 | { MOD4, XK_l, moveresize, {.v = (int []){ 25, 0, 0, 0 }}}, /* move right */ 128 | { MOD4, XK_h, moveresize, {.v = (int []){ -25, 0, 0, 0 }}}, /* move left */ 129 | { MOD4|SHIFT, XK_j, moveresize, {.v = (int []){ 0, 0, 0, 25 }}}, /* height grow */ 130 | { MOD4|SHIFT, XK_k, moveresize, {.v = (int []){ 0, 0, 0, -25 }}}, /* height shrink */ 131 | { MOD4|SHIFT, XK_l, moveresize, {.v = (int []){ 0, 0, 25, 0 }}}, /* width grow */ 132 | { MOD4|SHIFT, XK_h, moveresize, {.v = (int []){ 0, 0, -25, 0 }}}, /* width shrink */ 133 | DESKTOPCHANGE( XK_F1, 0) 134 | DESKTOPCHANGE( XK_F2, 1) 135 | DESKTOPCHANGE( XK_F3, 2) 136 | DESKTOPCHANGE( XK_F4, 3) 137 | MONITORCHANGE( XK_F1, 0) 138 | MONITORCHANGE( XK_F2, 1) 139 | }; 140 | 141 | /** 142 | * mouse shortcuts 143 | */ 144 | static Button buttons[] = { 145 | { MOD1, Button1, mousemotion, {.i = MOVE}}, 146 | { MOD1, Button3, mousemotion, {.i = RESIZE}}, 147 | }; 148 | #endif 149 | 150 | /* vim: set expandtab ts=4 sts=4 sw=4 : */ 151 | -------------------------------------------------------------------------------- /monsterwm.1: -------------------------------------------------------------------------------- 1 | .TH MONSTERWM 1 monsterwm 2 | .SH NAME 3 | monsterwm \- minimal and dynamic tiling window manager 4 | .SH SYNOPSIS 5 | .B monsterwm 6 | .RB [ \-v ] 7 | .SH DESCRIPTION 8 | .I monsterwm 9 | is a minimal, lightweight, tiny but monstrous, dynamic tiling window manager. 10 | .P 11 | .SH MODES 12 | .I monsterwm 13 | comes with four tiling layouts by default plus the floating mode. 14 | It allows the usual method of tiling window managers, with the new window as 15 | the master window, but also provides the ability to have the new window opened 16 | as the last window (at the bottom) of the stack. 17 | .P 18 | the available modes: 19 | .TP 20 | .B Tile stack 21 | the stack clients are tiled on the side of master. 22 | .TP 23 | .B Bottom stack 24 | the stack clients are tiled beneath the master. 25 | .TP 26 | .B Grid mode 27 | clients are tiled in a grid, equaly sharing and dividing the screen space 28 | .TP 29 | .B Monocle mode 30 | also known as fullscreen or max mode, where the clients take up the entire 31 | screen space. Other clients are hidden behind the current shown window. 32 | On this layout, fullscreen clients don't need and don't have borders. 33 | You can change that behavior with the 34 | .I monocleborders 35 | patch, in the corresponding branch. 36 | .TP 37 | .B Floating mode 38 | windows can move and be resized freely in the screen space, like on a stacking 39 | window manager. Windows retain their floating status until the user switches 40 | to a tiling mode. 41 | .SH OPTIONS 42 | .TP 43 | .B \-v 44 | prints version information to standard output, then exits. 45 | .SH USAGE 46 | .SS Status bar 47 | .P 48 | .I monsterwm 49 | does not provide a status bar. Consistent with the Unix philosophy, 50 | .I monsterwm 51 | provides information to the status bar or panel of choice via ouputing 52 | text with information about the state of the windows. 53 | .P 54 | the available settings in 55 | .I config.h 56 | for the panel/status bar, are: 57 | .TP 58 | .B SHOW_PANEL 59 | whether the panel should be visible or hidden by default 60 | .TP 61 | .B TOP_PANEL 62 | whether the panel should be on top or bottom of the screen 63 | .TP 64 | .B PANEL_HEIGHT 65 | how much space should be left for use by the panel. Set to 66 | .B 0 67 | to disable the panel completely. 68 | .SS Keyboard and mouse commands 69 | All of 70 | .I monsterwm's 71 | commands can be customized by editing 72 | .I config.h 73 | and recompiling. 74 | .P 75 | The default keyboard-bindings include: 76 | .TP 77 | .B Mod1\-b 78 | Toggles the panel on and off. 79 | .TP 80 | .B Mod1\-Backspace 81 | Focus the window with an urgent hint. 82 | Focus the appropriate desktop if needed. 83 | .TP 84 | .B Mod1\-Shift\-c 85 | Close focused window. 86 | .TP 87 | .B Mod1\-j 88 | Focus next window. 89 | .TP 90 | .B Mod1\-k 91 | Focus previous window. 92 | .TP 93 | .B Mod1\-l 94 | Increase master area size. 95 | .TP 96 | .B Mod1\-h 97 | Decrease master area size. 98 | .TP 99 | .B Mod1\-o 100 | Shrink the size of the first stack window. 101 | .TP 102 | .B Mod1\-p 103 | Grow the size of the first stack window. 104 | .TP 105 | .B Mod1\-Ctrl\-h 106 | focus the previous desktop. 107 | .TP 108 | .B Mod1\-Ctrl\-l 109 | focus the next desktop. 110 | .TP 111 | .B Mod1\-Shift\-h 112 | focus the previous desktop that has windows open. 113 | .TP 114 | .B Mod1\-Shift\-l 115 | focus the next desktop that has windows open. 116 | .TP 117 | .B Mod1\-Tab 118 | Toggles to the last selected desktop. 119 | .TP 120 | .B Mod1\-Return 121 | Swaps the focused window to/from master area (tiled layouts only). 122 | .TP 123 | .B Mod1\-Shift\-j 124 | Move the focussed window down the stack 125 | .TP 126 | .B Mod1\-Shift\-k 127 | Move the focussed window up the stack 128 | .TP 129 | .B Mod1\-Shift\-t 130 | Sets tiled layout. 131 | .TP 132 | .B Mod1\-Shift\-m 133 | Sets monocle layout. 134 | .TP 135 | .B Mod1\-Shift\-b 136 | Sets bottom stack layout 137 | .TP 138 | .B Mod1\-Shift\-g 139 | Sets grid layout 140 | .TP 141 | .B Mod1\-Shift\-f 142 | Sets float layout 143 | .TP 144 | .B Mod1\-Shift\-r 145 | Quit with exit value 0 (usefull for restarts of the wm). 146 | .TP 147 | .B Mod1\-Shift\-q 148 | Quit with exit value 1 (differentiate quit from restart). 149 | .TP 150 | .B Mod1\-Shift\-Return 151 | Start 152 | .BR xterm (1). 153 | .TP 154 | .B Mod4\-v 155 | Start 156 | .BR dmenu (1). 157 | .TP 158 | .B MOD4\-{Down,Up,Right,Left} Arrow 159 | move the current window to the corresponding direction. 160 | .TP 161 | .B MOD4\-Shift\-{Down,Up,Right,Left} Arrow 162 | resize the current window to the corresponding direction. 163 | .TP 164 | .B Mod1\-F{1..n} 165 | Move to the nth workspace. By default, 166 | .I monsterwm 167 | is configured with four workspaces. 168 | The setting in 169 | .I config.h 170 | .B FOLLOW_WINDOW 171 | defines whether the focus should change on 172 | the new desktop, where the window moved to. 173 | .TP 174 | .B Mod1\-Shift\-F{1..n} 175 | Move focused window to nth workspace. 176 | .P 177 | The default mouse-bindings include: 178 | .TP 179 | .B Mod1\-Button1 180 | Dragging the mouse will move the selected window 181 | .TP 182 | .B Mod1\-Button3 183 | Dragging the mouse will resize the selected window 184 | .TP 185 | .B Mod4\-Button3 186 | will bring up 187 | .I dmenu 188 | .SS Customization 189 | .I monsterwm 190 | is customized by copying 191 | .I config.def.h 192 | to 193 | .I config.h 194 | and (re)compiling the source code. 195 | .P 196 | settings among others covered above include: 197 | .TP 198 | .B MASTER_SIZE 199 | set the size of the master area that 200 | will be used by the master window 201 | .TP 202 | .B DEFAULT_MODE 203 | set the default tiling mode to be active on startup 204 | .TP 205 | .B ATTACH_ASIDE 206 | whether new stack clients should spawn as the master window, 207 | or the last stack window 208 | .TP 209 | .B FOLLOW_MOUSE 210 | whether to focus the window the mouse just entered 211 | .TP 212 | .B FOLLOW_WINDOW 213 | whether to follow the window to the new desktop where it moved 214 | .TP 215 | .B CLICK_TO_FOCUS 216 | whether an action on a window (eg clicking, or scrolling) 217 | will give the window focus. Disabling this gives the user 218 | the ability to, for example, look up things on a web browser 219 | but not lose focus from the terminal etc. 220 | .TP 221 | .B BORDER_WIDTH 222 | the width of the borders the windows have 223 | .TP 224 | .B FOCUS / UNFOCUS 225 | the colors for the borders of focused and unfocused windows 226 | .TP 227 | .B DESKTOPS 228 | the number of desktops to use 229 | .TP 230 | .B DEFAULT_DESKTOP 231 | which desktop to focus by default 232 | .TP 233 | .B MINWSZ 234 | the minimum window size allowed. Prevents over resizing with 235 | the mouse or keyboard (eg resizing the master area) 236 | .P 237 | users can set 238 | .B rules 239 | on applications, by matching their 240 | .B class 241 | or 242 | .B instance 243 | name. The rules can specify on which 244 | .B desktop 245 | the application should start (or 246 | .B -1 247 | to signify the current desktop), whether the 248 | .B focus 249 | should change to that desktop, when the application starts 250 | and whether the application should start on 251 | .B floating 252 | or tiled mode. 253 | .SH SEE ALSO 254 | .BR dmenu (1) 255 | .SH BUGS 256 | .I monsterwm 257 | is under active development. Please report all bugs to the author. 258 | .SH AUTHOR 259 | Ivan c00kiemon5ter Kanakarakis 260 | 261 | 262 | -------------------------------------------------------------------------------- /monsterwm.c: -------------------------------------------------------------------------------- 1 | /* see license for copyright and license */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #define LENGTH(x) (sizeof(x)/sizeof(*x)) 18 | #define CLEANMASK(mask) (mask & ~(numlockmask | LockMask)) 19 | #define BUTTONMASK ButtonPressMask|ButtonReleaseMask 20 | #define ISFFT(c) (c->isfull || c->isfloat || c->istrans) 21 | #define ROOTMASK SubstructureRedirectMask|ButtonPressMask|SubstructureNotifyMask|PropertyChangeMask 22 | 23 | enum { RESIZE, MOVE }; 24 | enum { TILE, MONOCLE, BSTACK, GRID, FLOAT, MODES }; 25 | enum { WM_PROTOCOLS, WM_DELETE_WINDOW, WM_COUNT }; 26 | enum { NET_SUPPORTED, NET_FULLSCREEN, NET_WM_STATE, NET_ACTIVE, NET_COUNT }; 27 | 28 | /** 29 | * argument structure to be passed to function by config.h 30 | * com - function pointer ~ the command to run 31 | * i - an integer to indicate different states 32 | * v - any type argument 33 | */ 34 | typedef union { 35 | const char** com; 36 | const int i; 37 | const void *v; 38 | } Arg; 39 | 40 | /** 41 | * a key struct represents a combination of 42 | * mod - a modifier mask 43 | * keysym - and the key pressed 44 | * func - the function to be triggered because of the above combo 45 | * arg - the argument to the function 46 | */ 47 | typedef struct { 48 | unsigned int mod; 49 | KeySym keysym; 50 | void (*func)(const Arg *); 51 | const Arg arg; 52 | } Key; 53 | 54 | /** 55 | * a button struct represents a combination of 56 | * mask - a modifier mask 57 | * button - and the mouse button pressed 58 | * func - the function to be triggered because of the above combo 59 | * arg - the argument to the function 60 | */ 61 | typedef struct { 62 | unsigned int mask, button; 63 | void (*func)(const Arg *); 64 | const Arg arg; 65 | } Button; 66 | 67 | /** 68 | * define behavior of certain applications 69 | * configured in config.h 70 | * 71 | * class - the class or name of the instance 72 | * desktop - what desktop it should be spawned at 73 | * follow - whether to change desktop focus to the specified desktop 74 | */ 75 | typedef struct { 76 | const char *class; 77 | const int monitor; 78 | const int desktop; 79 | const Bool follow, floating; 80 | } AppRule; 81 | 82 | /* exposed function prototypes sorted alphabetically */ 83 | static void change_desktop(const Arg *arg); 84 | static void change_monitor(const Arg *arg); 85 | static void centerwindow(); 86 | static void client_to_desktop(const Arg *arg); 87 | static void client_to_monitor(const Arg *arg); 88 | static void focusurgent(); 89 | static void killclient(); 90 | static void last_desktop(); 91 | static void move_down(); 92 | static void move_up(); 93 | static void moveresize(const Arg *arg); 94 | static void mousemotion(const Arg *arg); 95 | static void next_win(); 96 | static void prev_win(); 97 | static void quit(const Arg *arg); 98 | static void resize_master(const Arg *arg); 99 | static void resize_stack(const Arg *arg); 100 | static void rotate(const Arg *arg); 101 | static void rotate_filled(const Arg *arg); 102 | static void spawn(const Arg *arg); 103 | static void swap_master(); 104 | static void switch_mode(const Arg *arg); 105 | static void togglepanel(); 106 | 107 | #include "config.h" 108 | 109 | /** 110 | * a client is a wrapper to a window that additionally 111 | * holds some properties for that window 112 | * 113 | * next - the client after this one, or NULL if the current is the last client 114 | * isurgn - set when the window received an urgent hint 115 | * isfull - set when the window is fullscreen 116 | * isfloat - set when the window is floating 117 | * istrans - set when the window is transient 118 | * win - the window this client is representing 119 | * 120 | * istrans is separate from isfloat as floating windows can be reset to 121 | * their tiling positions, while the transients will always be floating 122 | */ 123 | typedef struct Client { 124 | struct Client *next; 125 | Bool isurgn, isfull, isfloat, istrans; 126 | Window win; 127 | } Client; 128 | 129 | /** 130 | * properties of each desktop 131 | * 132 | * masz - the size of the master area 133 | * sasz - additional size of the first stack window area 134 | * mode - the desktop's tiling layout mode 135 | * head - the start of the client list 136 | * curr - the currently highlighted window 137 | * prev - the client that previously had focus 138 | * sbar - the visibility status of the panel/statusbar 139 | */ 140 | typedef struct { 141 | int mode, masz, sasz; 142 | Client *head, *curr, *prev; 143 | Bool sbar; 144 | } Desktop; 145 | 146 | /** 147 | * properties of each monitor 148 | * 149 | * wx, wy - the starting position of the monitor area 150 | * wh, ww - the width and height of the monitor 151 | * currdeskidx - the current desktop 152 | * desktops - the desktops handled by the monitor 153 | */ 154 | typedef struct Monitor { 155 | int x, y, h, w, currdeskidx, prevdeskidx; 156 | Desktop desktops[DESKTOPS]; 157 | } Monitor; 158 | 159 | /* hidden function prototypes sorted alphabetically */ 160 | static Client* addwindow(Window w, Desktop *d); 161 | static void buttonpress(XEvent *e); 162 | static void canclefullscreen(const Desktop *d, Monitor *m); 163 | static void cleanup(void); 164 | static void clientmessage(XEvent *e); 165 | static void configurerequest(XEvent *e); 166 | static void deletewindow(Window w); 167 | static void desktopinfo(void); 168 | static void destroynotify(XEvent *e); 169 | static void enternotify(XEvent *e); 170 | static void focus(Client *c, Desktop *d, Monitor *m); 171 | static void focusin(XEvent *e); 172 | static unsigned long getcolor(const char* color, const int screen); 173 | static void grabbuttons(Client *c); 174 | static void grabkeys(void); 175 | static void grid(int x, int y, int w, int h, const Desktop *d); 176 | static void keypress(XEvent *e); 177 | static void maprequest(XEvent *e); 178 | static void monocle(int x, int y, int w, int h, const Desktop *d); 179 | static Client* prevclient(Client *c, Desktop *d); 180 | static void propertynotify(XEvent *e); 181 | static void removeclient(Client *c, Desktop *d, Monitor *m); 182 | static void run(void); 183 | static void setfullscreen(Client *c, Desktop *d, Monitor *m, Bool fullscrn); 184 | static void setup(void); 185 | static void sigchld(int sig); 186 | static void stack(int x, int y, int w, int h, const Desktop *d); 187 | static void tile(Desktop *d, Monitor *m); 188 | static void unmapnotify(XEvent *e); 189 | static Bool wintoclient(Window w, Client **c, Desktop **d, Monitor **m); 190 | static int xerror(Display *dis, XErrorEvent *ee); 191 | static int xerrorstart(Display *dis, XErrorEvent *ee); 192 | 193 | /** 194 | * global variables 195 | * 196 | * running - whether the wm is accepting and processing more events 197 | * wh - screen height 198 | * ww - screen width 199 | * dis - the display aka dpy 200 | * root - the root window 201 | * wmatoms - array holding atoms for ICCCM support 202 | * netatoms - array holding atoms for EWMH support 203 | * desktops - array of managed desktops 204 | * currdeskidx - which desktop is currently active 205 | */ 206 | static Bool running = True; 207 | static int nmonitors, currmonidx, retval; 208 | static unsigned int numlockmask, win_focus, win_unfocus, win_infocus; 209 | static Display *dis; 210 | static Window root; 211 | static Atom wmatoms[WM_COUNT], netatoms[NET_COUNT]; 212 | static Monitor *monitors; 213 | 214 | /** 215 | * array of event handlers 216 | * 217 | * when a new event is received, 218 | * call the appropriate handler function 219 | */ 220 | static void (*events[LASTEvent])(XEvent *e) = { 221 | [KeyPress] = keypress, [EnterNotify] = enternotify, 222 | [MapRequest] = maprequest, [ClientMessage] = clientmessage, 223 | [ButtonPress] = buttonpress, [DestroyNotify] = destroynotify, 224 | [UnmapNotify] = unmapnotify, [PropertyNotify] = propertynotify, 225 | [ConfigureRequest] = configurerequest, [FocusIn] = focusin, 226 | }; 227 | 228 | /** 229 | * array of layout handlers 230 | * 231 | * x - the start position in the x axis to place clients 232 | * y - the start position in the y axis to place clients 233 | * w - available width that windows have to expand 234 | * h - available height that windows have to expand 235 | * d - the desktop to tile its clients 236 | */ 237 | static void (*layout[MODES])(int x, int y, int w, int h, const Desktop *d) = { 238 | [TILE] = stack, [BSTACK] = stack, [GRID] = grid, [MONOCLE] = monocle, 239 | }; 240 | 241 | /** 242 | * add the given window to the given desktop 243 | * 244 | * create a new client to hold the new window 245 | * 246 | * if there is no head at the given desktop 247 | * add the window as the head 248 | * otherwise if ATTACH_ASIDE is not set, 249 | * add the window as the last client 250 | * otherwise add the window as head 251 | */ 252 | Client* addwindow(Window w, Desktop *d) { 253 | Client *c = NULL, *t = prevclient(d->head, d); 254 | if (!(c = (Client *)calloc(1, sizeof(Client)))) err(EXIT_FAILURE, "cannot allocate client"); 255 | if (!d->head) d->head = c; 256 | else if (!ATTACH_ASIDE) { c->next = d->head; d->head = c; } 257 | else if (t) t->next = c; else d->head->next = c; 258 | 259 | XSelectInput(dis, (c->win = w), PropertyChangeMask|FocusChangeMask|(FOLLOW_MOUSE?EnterWindowMask:0)); 260 | return c; 261 | } 262 | 263 | /** 264 | * on the press of a key binding (see grabkeys) 265 | * call the appropriate handler 266 | */ 267 | void buttonpress(XEvent *e) { 268 | Monitor *m = NULL; Desktop *d = NULL; Client *c = NULL; 269 | Bool w = wintoclient(e->xbutton.window, &c, &d, &m); 270 | 271 | int cm = 0; while (m != &monitors[cm] && cm < nmonitors) ++cm; 272 | 273 | if (w && CLICK_TO_FOCUS && e->xbutton.button == FOCUS_BUTTON && (c != d->curr || cm != currmonidx)) { 274 | if (cm != currmonidx) change_monitor(&(Arg){.i = cm}); 275 | focus(c, d, m); 276 | } 277 | 278 | for (unsigned int i = 0; i < LENGTH(buttons); i++) 279 | if (CLEANMASK(buttons[i].mask) == CLEANMASK(e->xbutton.state) && 280 | buttons[i].func && buttons[i].button == e->xbutton.button) { 281 | if (w && cm != currmonidx) change_monitor(&(Arg){.i = cm}); 282 | if (w && c != d->curr) focus(c, d, m); 283 | buttons[i].func(&(buttons[i].arg)); 284 | } 285 | } 286 | 287 | /** 288 | * focus another desktop 289 | * 290 | * to avoid flickering (esp. monocle mode): 291 | * first map the new windows 292 | * first the current window and then all other 293 | * then unmap the old windows 294 | * first all others then the current 295 | */ 296 | void change_desktop(const Arg *arg) { 297 | Monitor *m = &monitors[currmonidx]; 298 | if (arg->i == m->currdeskidx || arg->i < 0 || arg->i >= DESKTOPS) return; 299 | Desktop *d = &m->desktops[(m->prevdeskidx = m->currdeskidx)], *n = &m->desktops[(m->currdeskidx = arg->i)]; 300 | if (n->curr) XMapWindow(dis, n->curr->win); 301 | for (Client *c = n->head; c; c = c->next) XMapWindow(dis, c->win); 302 | XChangeWindowAttributes(dis, root, CWEventMask, &(XSetWindowAttributes){.do_not_propagate_mask = SubstructureNotifyMask}); 303 | for (Client *c = d->head; c; c = c->next) if (c != d->curr) XUnmapWindow(dis, c->win); 304 | if (d->curr) XUnmapWindow(dis, d->curr->win); 305 | XChangeWindowAttributes(dis, root, CWEventMask, &(XSetWindowAttributes){.event_mask = ROOTMASK}); 306 | if (n->head) { tile(n, m); focus(n->curr, n, m); } 307 | desktopinfo(); 308 | } 309 | 310 | /** 311 | * focus another monitor 312 | */ 313 | void change_monitor(const Arg *arg) { 314 | if (arg->i == currmonidx || arg->i < 0 || arg->i >= nmonitors) return; 315 | Monitor *m = &monitors[currmonidx], *n = &monitors[(currmonidx = arg->i)]; 316 | focus(m->desktops[m->currdeskidx].curr, &m->desktops[m->currdeskidx], m); 317 | focus(n->desktops[n->currdeskidx].curr, &n->desktops[n->currdeskidx], n); 318 | desktopinfo(); 319 | } 320 | 321 | /** 322 | * place the current window in the center of the screen floating 323 | */ 324 | void centerwindow(void) { 325 | XWindowAttributes wa; 326 | Monitor *m = &monitors[currmonidx]; 327 | Desktop *d = &m->desktops[m->currdeskidx]; 328 | int border_width; 329 | if (!d->curr || !XGetWindowAttributes(dis, d->curr->win, &wa)) return; 330 | if (!d->curr->isfloat && !d->curr->istrans) { d->curr->isfloat = True; tile(d, m); } 331 | XRaiseWindow(dis, d->curr->win); 332 | if (d->mode == MONOCLE) 333 | border_width = MONOCLE_BORDER?BORDER_WIDTH:0; 334 | else 335 | border_width = BORDER_WIDTH; 336 | XMoveWindow(dis, d->curr->win, m->x + (m->w - wa.width)/2 - border_width, 337 | m->y + (m->h - wa.height - (TOP_PANEL && d->sbar ? PANEL_HEIGHT : 0))/2 - border_width 338 | + (TOP_PANEL && d->sbar ? PANEL_HEIGHT : 0)); 339 | } 340 | 341 | /** 342 | * remove all windows in all desktops by sending a delete window message 343 | */ 344 | void cleanup(void) { 345 | Window root_return, parent_return, *children; 346 | unsigned int nchildren; 347 | 348 | XUngrabKey(dis, AnyKey, AnyModifier, root); 349 | XQueryTree(dis, root, &root_return, &parent_return, &children, &nchildren); 350 | for (unsigned int i = 0; i < nchildren; i++) deletewindow(children[i]); 351 | if (children) XFree(children); 352 | XSync(dis, False); 353 | free(monitors); 354 | } 355 | 356 | /** 357 | * move the current focused client to another desktop 358 | * 359 | * add the current client as the last on the new desktop 360 | * then remove it from the current desktop 361 | */ 362 | void client_to_desktop(const Arg *arg) { 363 | Monitor *m = &monitors[currmonidx]; Desktop *d = &m->desktops[m->currdeskidx], *n = NULL; 364 | if (arg->i == m->currdeskidx || arg->i < 0 || arg->i >= DESKTOPS || !d->curr) return; 365 | 366 | Client *c = d->curr, *p = prevclient(d->curr, d), 367 | *l = prevclient(m->desktops[arg->i].head, (n = &m->desktops[arg->i])); 368 | 369 | /* unlink current client from current desktop */ 370 | if (d->head == c || !p) d->head = c->next; else p->next = c->next; 371 | c->next = NULL; 372 | XChangeWindowAttributes(dis, root, CWEventMask, &(XSetWindowAttributes){.do_not_propagate_mask = SubstructureNotifyMask}); 373 | if (XUnmapWindow(dis, c->win)) focus(d->prev, d, m); 374 | XChangeWindowAttributes(dis, root, CWEventMask, &(XSetWindowAttributes){.event_mask = ROOTMASK}); 375 | if (!(c->isfloat || c->istrans) || (d->head && !d->head->next)) tile(d, m); 376 | 377 | /* link client to new desktop and make it the current */ 378 | focus(l ? (l->next = c):n->head ? (n->head->next = c):(n->head = c), n, m); 379 | 380 | if (FOLLOW_WINDOW) change_desktop(arg); else desktopinfo(); 381 | } 382 | 383 | /** 384 | * move the current focused client to another monitor 385 | * 386 | * add the current client as the last on the new monitor's current desktop 387 | * then remove it from the current monitor's current desktop 388 | * 389 | * removing the client means unlinking it and unmapping it. 390 | * add the client means linking it as the last client, and 391 | * mapping it. mapping must happen after the client has been 392 | * unmapped from the current monitor's current desktop. 393 | */ 394 | void client_to_monitor(const Arg *arg) { 395 | Monitor *cm = &monitors[currmonidx], *nm = NULL; 396 | Desktop *cd = &cm->desktops[cm->currdeskidx], *nd = NULL; 397 | if (arg->i == currmonidx || arg->i < 0 || arg->i >= nmonitors || !cd->curr) return; 398 | 399 | nd = &monitors[arg->i].desktops[(nm = &monitors[arg->i])->currdeskidx]; 400 | Client *c = cd->curr, *p = prevclient(c, cd), *l = prevclient(nd->head, nd); 401 | 402 | /* unlink current client from current monitor's current desktop */ 403 | if (cd->head == c || !p) cd->head = c->next; else p->next = c->next; 404 | c->next = NULL; 405 | focus(cd->prev, cd, cm); 406 | if (!(c->isfloat || c->istrans) || (cd->head && !cd->head->next)) tile(cd, cm); 407 | 408 | /* reset floating and fullscreen state */ 409 | if (ISFFT(c)) c->isfloat = c->isfull = False; 410 | 411 | /* link to new monitor's current desktop */ 412 | focus(l ? (l->next = c):nd->head ? (nd->head->next = c):(nd->head = c), nd, nm); 413 | tile(nd, nm); 414 | 415 | if (FOLLOW_MONITOR) change_monitor(arg); else desktopinfo(); 416 | } 417 | 418 | /** 419 | * receive and process client messages 420 | * 421 | * check if window wants to change its state to fullscreen, 422 | * or if the window want to become active/focused 423 | * 424 | * to change the state of a mapped window, a client MUST 425 | * send a _NET_WM_STATE client message to the root window 426 | * message_type must be _NET_WM_STATE 427 | * data.l[0] is the action to be taken 428 | * data.l[1] is the property to alter three actions: 429 | * - remove/unset _NET_WM_STATE_REMOVE=0 430 | * - add/set _NET_WM_STATE_ADD=1, 431 | * - toggle _NET_WM_STATE_TOGGLE=2 432 | * 433 | * to request to become active, a client should send a 434 | * message of _NET_ACTIVE_WINDOW type. when such a message 435 | * is received and a client holding that window exists, 436 | * the window becomes the current active focused window 437 | * on its desktop. 438 | */ 439 | void clientmessage(XEvent *e) { 440 | Monitor *m = NULL; Desktop *d = NULL; Client *c = NULL; 441 | if (!wintoclient(e->xclient.window, &c, &d, &m)) return; 442 | 443 | if (e->xclient.message_type == netatoms[NET_WM_STATE] && ( 444 | (unsigned)e->xclient.data.l[1] == netatoms[NET_FULLSCREEN] 445 | || (unsigned)e->xclient.data.l[2] == netatoms[NET_FULLSCREEN])) { 446 | setfullscreen(c, d, m, (e->xclient.data.l[0] == 1 || (e->xclient.data.l[0] == 2 && !c->isfull))); 447 | if (!(c->isfloat || c->istrans) || !d->head->next) tile(d, m); 448 | } else if (e->xclient.message_type == netatoms[NET_ACTIVE]) focus(c, d, m); 449 | } 450 | 451 | /** 452 | * configure a window's size, position, border width, and stacking order. 453 | * 454 | * windows usually have a prefered size (width, height) and position (x, y), 455 | * and sometimes borer with (border_width) and stacking order (above, detail). 456 | * a configure request attempts to reconfigure those properties for a window. 457 | * 458 | * we don't really care about those values, because a tiling wm will impose 459 | * its own values for those properties. 460 | * however the requested values must be set initially for some windows, 461 | * otherwise the window will misbehave or even crash (see gedit, geany, gvim). 462 | * 463 | * some windows depend on the number of columns and rows to set their 464 | * size, and not on pixels (terminals, consoles, some editors etc). 465 | * normally those clients when tiled and respecting the prefered size 466 | * will create gaps around them (window_hints). 467 | * however, clients are tiled to match the wm's prefered size, 468 | * not respecting those prefered values. 469 | * 470 | * some windows implement window manager functions themselves. 471 | * that is windows explicitly steal focus, or manage subwindows, 472 | * or move windows around w/o the window manager's help, etc.. 473 | * to disallow this behavior, we 'tile()' the desktop to which 474 | * the window that sent the configure request belongs. 475 | */ 476 | void configurerequest(XEvent *e) { 477 | XConfigureRequestEvent *ev = &e->xconfigurerequest; 478 | XWindowChanges wc = { ev->x, ev->y, ev->width, ev->height, ev->border_width, ev->above, ev->detail }; 479 | if (XConfigureWindow(dis, ev->window, ev->value_mask, &wc)) XSync(dis, False); 480 | Monitor *m = NULL; Desktop *d = NULL; Client *c = NULL; 481 | if (wintoclient(ev->window, &c, &d, &m)) tile(d, m); 482 | } 483 | 484 | /** 485 | * clients receiving a WM_DELETE_WINDOW message should behave as if 486 | * the user selected "delete window" from a hypothetical menu and 487 | * also perform any confirmation dialog with the user. 488 | */ 489 | void deletewindow(Window w) { 490 | XEvent ev = { .type = ClientMessage }; 491 | ev.xclient.window = w; 492 | ev.xclient.format = 32; 493 | ev.xclient.message_type = wmatoms[WM_PROTOCOLS]; 494 | ev.xclient.data.l[0] = wmatoms[WM_DELETE_WINDOW]; 495 | ev.xclient.data.l[1] = CurrentTime; 496 | XSendEvent(dis, w, False, NoEventMask, &ev); 497 | } 498 | 499 | /** 500 | * output info about the desktops on standard output stream 501 | * 502 | * the information is formatted as a space separated line 503 | * where each token contains information about a desktop. 504 | * each token is a formatted as ':' separated string of values. 505 | * the values are: 506 | * - the desktop number/id 507 | * - the desktop's client count 508 | * - the desktop's tiling layout mode/id 509 | * - whether the desktop is the current focused (1) or not (0) 510 | * - whether any client in that desktop has received an urgent hint 511 | * 512 | * once the info is collected, immediately flush the stream 513 | */ 514 | void desktopinfo(void) { 515 | Monitor *m = NULL; 516 | Client *c = NULL; 517 | Bool urgent = False; 518 | 519 | for (int cm = 0; cm < nmonitors; cm++) 520 | for (int cd = 0, w = 0; cd < DESKTOPS; cd++, w = 0, urgent = False) { 521 | for (m = &monitors[cm], c = m->desktops[cd].head; c; urgent |= c->isurgn, ++w, c = c->next); 522 | printf("%d:%d:%d:%d:%d:%d:%d ", cm, cm == currmonidx, cd, w, m->desktops[cd].mode, cd == m->currdeskidx, urgent); 523 | } 524 | 525 | printf("\n"); 526 | fflush(stdout); 527 | } 528 | 529 | /** 530 | * generated whenever a client application destroys a window 531 | * 532 | * a destroy notification is received when a window is being closed 533 | * on receival, remove the client that held that window 534 | */ 535 | void destroynotify(XEvent *e) { 536 | Monitor *m = NULL; Desktop *d = NULL; Client *c = NULL; 537 | if (wintoclient(e->xdestroywindow.window, &c, &d, &m)) removeclient(c, d, m); 538 | } 539 | 540 | /** 541 | * when the mouse enters a window's borders, that window, 542 | * if has set notifications of such events (EnterWindowMask) 543 | * will notify that the pointer entered its region 544 | * and will get focus if FOLLOW_MOUSE is set in the config. 545 | */ 546 | void enternotify(XEvent *e) { 547 | Monitor *m = NULL; Desktop *d = NULL; Client *c = NULL, *p = NULL; 548 | 549 | if (!FOLLOW_MOUSE || (e->xcrossing.mode != NotifyNormal && e->xcrossing.detail == NotifyInferior) 550 | || !wintoclient(e->xcrossing.window, &c, &d, &m) || e->xcrossing.window == d->curr->win) return; 551 | 552 | if (m != &monitors[currmonidx]) for (int cm = 0; cm < nmonitors; cm++) 553 | if (m == &monitors[cm]) change_monitor(&(Arg){.i = cm}); 554 | 555 | if ((p = d->prev)) 556 | XChangeWindowAttributes(dis, p->win, CWEventMask, &(XSetWindowAttributes){.do_not_propagate_mask = EnterWindowMask}); 557 | focus(c, d, m); 558 | if (p) XChangeWindowAttributes(dis, p->win, CWEventMask, &(XSetWindowAttributes){.event_mask = EnterWindowMask}); 559 | } 560 | 561 | /** 562 | * 1. set current/active/focused and previously focused client 563 | * in other words, manage curr and prev references 564 | * 2. restack clients 565 | * 3. highlight borders and set active window property 566 | * 4. give input focus to the current/active/focused client 567 | */ 568 | void focus(Client *c, Desktop *d, Monitor *m) { 569 | /* update references to prev and curr, 570 | * previously focused and currently focused clients. 571 | * 572 | * if there are no clients (!head) or the new client 573 | * is NULL, then delete the _NET_ACTIVE_WINDOW property 574 | * 575 | * if the new client is the prev client then 576 | * - either the current client was removed 577 | * and thus focus(prev) was called 578 | * - or the previous from current is prev 579 | * ie, two consecutive clients were focused 580 | * and then prev_win() was called, to focus 581 | * the previous from current client, which 582 | * happens to be prev (curr == c->next). 583 | * (below: h:head p:prev c:curr) 584 | * 585 | * [h]->[p]->[c]->NULL ===> [h|p]->[c]->NULL 586 | * ^ remove current 587 | * 588 | * [h]->[p]->[c]->NULL ===> [h]->[c]->[p]->NULL 589 | * ^ prev_win swaps prev and curr 590 | * 591 | * in the first case we need to update prev reference, 592 | * choice here is to set it to the previous from the 593 | * new current client. 594 | * the second case is handled as any other case, the 595 | * current client is now the previously focused (prev = curr) 596 | * and the new current client is now curr (curr = c) 597 | * 598 | * references should only change when the current 599 | * client is different from the one given to focus. 600 | * 601 | * the new client should never be NULL, except if, 602 | * there is no other client on the workspace (!head). 603 | * prev and curr always point to different clients. 604 | * 605 | * NOTICE: remove client can remove any client, 606 | * not just the current (curr). Thus, if prev is 607 | * removed, its reference needs to be updated. 608 | * That is handled by removeclient() function. 609 | * All other reference changes for curr and prev 610 | * should and are handled here. 611 | */ 612 | if (!d->head || !c) { /* no clients - no active window - nothing to do */ 613 | XDeleteProperty(dis, root, netatoms[NET_ACTIVE]); 614 | d->curr = d->prev = NULL; 615 | return; 616 | } else if (d->prev == c && d->curr != c->next) { d->prev = prevclient((d->curr = c), d); 617 | } else if (d->curr != c) { d->prev = d->curr; d->curr = c; } 618 | 619 | /* restack clients 620 | * 621 | * stack order is based on client properties. 622 | * from top to bottom: 623 | * - current when floating or transient 624 | * - floating or trancient windows 625 | * - current when tiled 626 | * - current when fullscreen 627 | * - fullscreen windows 628 | * - tiled windows 629 | * 630 | * num of n:all fl:fullscreen ft:floating/transient windows 631 | */ 632 | int n = 0, fl = 0, ft = 0; 633 | for (c = d->head; c; c = c->next, ++n) if (ISFFT(c)) { fl++; if (!c->isfull) ft++; } 634 | Window w[n]; 635 | w[(d->curr->isfloat || d->curr->istrans) ? 0:ft] = d->curr->win; 636 | for (fl += !ISFFT(d->curr) ? 1:0, c = d->head; c; c = c->next) { 637 | XSetWindowBorder(dis, c->win, (c != d->curr) ? win_unfocus:(m == &monitors[currmonidx]) ? win_focus:win_infocus); 638 | /* windows always get borders, except if fullscreen */ 639 | XSetWindowBorderWidth(dis, c->win, c->isfull ? 0:BORDER_WIDTH); 640 | if (c != d->curr) w[c->isfull ? --fl:ISFFT(c) ? --ft:--n] = c->win; 641 | if (CLICK_TO_FOCUS || c == d->curr) grabbuttons(c); 642 | } 643 | XRestackWindows(dis, w, LENGTH(w)); 644 | 645 | XSetInputFocus(dis, d->curr->win, RevertToPointerRoot, CurrentTime); 646 | XChangeProperty(dis, root, netatoms[NET_ACTIVE], XA_WINDOW, 32, 647 | PropModeReplace, (unsigned char *)&d->curr->win, 1); 648 | 649 | XSync(dis, False); 650 | } 651 | 652 | /** 653 | * dont give focus to any client except current. 654 | * some apps explicitly call XSetInputFocus (see 655 | * tabbed, chromium), resulting in loss of input 656 | * focuse (mouse/kbd) from the current focused 657 | * client. 658 | * 659 | * this gives focus back to the current selected 660 | * client, by the user, through the wm. 661 | */ 662 | void focusin(XEvent *e) { 663 | Monitor *m = &monitors[currmonidx]; Desktop *d = &m->desktops[m->currdeskidx]; 664 | if (d->curr && d->curr->win != e->xfocus.window) focus(d->curr, d, m); 665 | } 666 | 667 | /** 668 | * find and focus the first client that received an urgent hint 669 | * first look in the current desktop then on other desktops 670 | */ 671 | void focusurgent(void) { 672 | Monitor *m = &monitors[currmonidx]; 673 | Client *c = NULL; 674 | int d = -1; 675 | for (c = m->desktops[m->currdeskidx].head; c && !c->isurgn; c = c->next); 676 | while (!c && d < DESKTOPS-1) for (c = m->desktops[++d].head; c && !c->isurgn; c = c->next); 677 | if (c) { if (d != -1) change_desktop(&(Arg){.i = d}); focus(c, &m->desktops[m->currdeskidx], m); } 678 | } 679 | 680 | /** 681 | * get a pixel with the requested color to 682 | * fill some window area (such as borders) 683 | */ 684 | unsigned long getcolor(const char* color, const int screen) { 685 | XColor c; Colormap map = DefaultColormap(dis, screen); 686 | if (!XAllocNamedColor(dis, map, color, &c, &c)) err(EXIT_FAILURE, "cannot allocate color"); 687 | return c.pixel; 688 | } 689 | 690 | /** 691 | * register button bindings to be notified of 692 | * when they occur. 693 | * the wm listens to those button bindings and 694 | * calls an appropriate handler when a binding 695 | * occurs (see buttonpress). 696 | */ 697 | void grabbuttons(Client *c) { 698 | Monitor *cm = &monitors[currmonidx]; 699 | unsigned int b, m, modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; 700 | 701 | for (m = 0; CLICK_TO_FOCUS && m < LENGTH(modifiers); m++) 702 | if (c != cm->desktops[cm->currdeskidx].curr) XGrabButton(dis, FOCUS_BUTTON, modifiers[m], 703 | c->win, False, BUTTONMASK, GrabModeAsync, GrabModeAsync, None, None); 704 | else XUngrabButton(dis, FOCUS_BUTTON, modifiers[m], c->win); 705 | 706 | for (b = 0, m = 0; b < LENGTH(buttons); b++, m = 0) while (m < LENGTH(modifiers)) 707 | XGrabButton(dis, buttons[b].button, buttons[b].mask|modifiers[m++], c->win, 708 | False, BUTTONMASK, GrabModeAsync, GrabModeAsync, None, None); 709 | } 710 | 711 | /** 712 | * register key bindings to be notified of 713 | * when they occur. 714 | * the wm listens to those key bindings and 715 | * calls an appropriate handler when a binding 716 | * occurs (see keypressed). 717 | */ 718 | void grabkeys(void) { 719 | KeyCode code; 720 | XUngrabKey(dis, AnyKey, AnyModifier, root); 721 | unsigned int k, m, modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; 722 | 723 | for (k = 0, m = 0; k < LENGTH(keys); k++, m = 0) 724 | while ((code = XKeysymToKeycode(dis, keys[k].keysym)) && m < LENGTH(modifiers)) 725 | XGrabKey(dis, code, keys[k].mod|modifiers[m++], root, True, GrabModeAsync, GrabModeAsync); 726 | } 727 | 728 | /** 729 | * grid mode / grid layout 730 | * arrange windows in a grid aka fair 731 | */ 732 | void grid(int x, int y, int w, int h, const Desktop *d) { 733 | int n = 0, cols = 0, cn = 0, rn = 0, i = -1; 734 | canclefullscreen(d, &monitors[currmonidx]); 735 | for (Client *c = d->head; c; c = c->next) if (!ISFFT(c)) ++n; 736 | for (cols = 0; cols <= n/2; cols++) if (cols*cols >= n) break; /* emulate square root */ 737 | if (n == 0) return; else if (n == 5) cols = 2; 738 | 739 | int rows = n/cols, ch = h - USELESSGAP, cw = (w - USELESSGAP)/(cols ? cols:1); 740 | for (Client *c = d->head; c; c = c->next) { 741 | if (ISFFT(c)) continue; else ++i; 742 | if (i/rows + 1 > cols - n%cols) rows = n/cols + 1; 743 | XMoveResizeWindow(dis, c->win, x + cn*cw + USELESSGAP, y + rn*ch/rows + USELESSGAP, 744 | cw - 2*BORDER_WIDTH - USELESSGAP, ch/rows - 2*BORDER_WIDTH - USELESSGAP); 745 | if (++rn >= rows) { rn = 0; cn++; } 746 | } 747 | } 748 | 749 | /** 750 | * on the press of a key binding (see grabkeys) 751 | * call the appropriate handler 752 | */ 753 | void keypress(XEvent *e) { 754 | KeySym keysym = XkbKeycodeToKeysym(dis, e->xkey.keycode, 0, 0); 755 | for (unsigned int i = 0; i < LENGTH(keys); i++) 756 | if (keysym == keys[i].keysym && CLEANMASK(keys[i].mod) == CLEANMASK(e->xkey.state)) 757 | if (keys[i].func) keys[i].func(&keys[i].arg); 758 | } 759 | 760 | /** 761 | * explicitly kill the current client - close the highlighted window 762 | * if the client accepts WM_DELETE_WINDOW requests send a delete message 763 | * otherwise forcefully kill and remove the client 764 | */ 765 | void killclient(void) { 766 | Monitor *m = &monitors[currmonidx]; 767 | Desktop *d = &m->desktops[m->currdeskidx]; 768 | if (!d->curr) return; 769 | 770 | Atom *prot = NULL; int n = -1; 771 | if (XGetWMProtocols(dis, d->curr->win, &prot, &n)) 772 | while(--n >= 0 && prot[n] != wmatoms[WM_DELETE_WINDOW]); 773 | if (n < 0) { XKillClient(dis, d->curr->win); removeclient(d->curr, d, m); } 774 | else deletewindow(d->curr->win); 775 | if (prot) XFree(prot); 776 | } 777 | 778 | /** 779 | * focus the previously focused desktop 780 | */ 781 | void last_desktop(void) { 782 | change_desktop(&(Arg){.i = monitors[currmonidx].prevdeskidx}); 783 | } 784 | 785 | /** 786 | * a map request is received when a window wants to display itself. 787 | * if the window has override_redirect flag set, 788 | * then it should not be handled by the wm. 789 | * if the window already has a client then there is nothing to do. 790 | * 791 | * match window class and/or install name against an app rule. 792 | * create a new client for the window and add it to the appropriate desktop. 793 | * set the floating, transient and fullscreen state of the client. 794 | * if the desktop in which the window is to be spawned is the current desktop 795 | * then display/map the window, else, if follow is set, focus the new desktop. 796 | */ 797 | void maprequest(XEvent *e) { 798 | Monitor *m = NULL; Desktop *d = NULL; Client *c = NULL; 799 | Window w = e->xmaprequest.window; 800 | XWindowAttributes wa = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 801 | if (wintoclient(w, &c, &d, &m) || (XGetWindowAttributes(dis, w, &wa) && wa.override_redirect)) return; 802 | 803 | XClassHint ch = {0, 0}; 804 | Bool follow = False, floating = False; 805 | int newmon = currmonidx, newdsk = monitors[currmonidx].currdeskidx; 806 | 807 | if (XGetClassHint(dis, w, &ch)) for (unsigned int i = 0; i < LENGTH(rules); i++) 808 | if (strstr(ch.res_class, rules[i].class) || strstr(ch.res_name, rules[i].class)) { 809 | if (rules[i].monitor >= 0 && rules[i].monitor < nmonitors) newmon = rules[i].monitor; 810 | if (rules[i].desktop >= 0 && rules[i].desktop < DESKTOPS) newdsk = rules[i].desktop; 811 | follow = rules[i].follow, floating = rules[i].floating; 812 | break; 813 | } 814 | if (ch.res_class) XFree(ch.res_class); 815 | if (ch.res_name) XFree(ch.res_name); 816 | 817 | c = addwindow(w, (d = &(m = &monitors[newmon])->desktops[newdsk])); /* from now on, use c->win */ 818 | c->istrans = XGetTransientForHint(dis, c->win, &w); 819 | if ((c->isfloat = (floating || d->mode == FLOAT)) && !c->istrans) 820 | //XMoveWindow(dis, c->win, m->x + (m->w - wa.width)/2, m->y + (m->h - wa.height)/2); 821 | XMoveWindow(dis, c->win, m->x + (m->w - wa.width)/2 - BORDER_WIDTH, 822 | m->y + (m->h - wa.height - (TOP_PANEL && d->sbar ? PANEL_HEIGHT : 0))/2 - BORDER_WIDTH 823 | + (TOP_PANEL && d->sbar ? PANEL_HEIGHT : 0)); 824 | 825 | int i; unsigned long l; unsigned char *state = NULL; Atom a; 826 | if (XGetWindowProperty(dis, c->win, netatoms[NET_WM_STATE], 0L, sizeof a, 827 | False, XA_ATOM, &a, &i, &l, &l, &state) == Success && state) 828 | setfullscreen(c, d, m, (*(Atom *)state == netatoms[NET_FULLSCREEN])); 829 | if (state) XFree(state); 830 | 831 | if (m->currdeskidx == newdsk) { if (!ISFFT(c)) tile(d, m); XMapWindow(dis, c->win); } 832 | if (follow) { change_monitor(&(Arg){.i = newmon}); change_desktop(&(Arg){.i = newdsk}); } 833 | focus(c, d, m); 834 | 835 | if (!follow) desktopinfo(); 836 | } 837 | 838 | /** 839 | * handle resize and positioning of a window with the pointer. 840 | * 841 | * grab the pointer and get it's current position. 842 | * now, all pointer movement events will be reported until it is ungrabbed. 843 | * 844 | * while the mouse is pressed, grab interesting events (see button press, 845 | * button release, pointer motion). 846 | * on on pointer movement resize or move the window under the curson. 847 | * also handle map requests and configure requests. 848 | * 849 | * finally, on ButtonRelease, ungrab the poitner. 850 | * event handling is passed back to run() function. 851 | * 852 | * once a window has been moved or resized, it's marked as floating. 853 | */ 854 | void mousemotion(const Arg *arg) { 855 | Monitor *m = &monitors[currmonidx]; Desktop *d = &m->desktops[m->currdeskidx]; 856 | XWindowAttributes wa; 857 | XEvent ev; 858 | 859 | if (!d->curr || !XGetWindowAttributes(dis, d->curr->win, &wa)) return; 860 | 861 | if (arg->i == RESIZE) XWarpPointer(dis, d->curr->win, d->curr->win, 0, 0, 0, 0, --wa.width, --wa.height); 862 | int rx, ry, c, xw, yh; unsigned int v; Window w; 863 | if (!XQueryPointer(dis, root, &w, &w, &rx, &ry, &c, &c, &v) || w != d->curr->win) return; 864 | 865 | if (XGrabPointer(dis, root, False, BUTTONMASK|PointerMotionMask, GrabModeAsync, 866 | GrabModeAsync, None, None, CurrentTime) != GrabSuccess) return; 867 | 868 | if (!d->curr->isfloat && !d->curr->istrans) { d->curr->isfloat = True; tile(d, m); focus(d->curr, d, m); } 869 | XRaiseWindow(dis, d->curr->win); 870 | 871 | do { 872 | XMaskEvent(dis, BUTTONMASK|PointerMotionMask|SubstructureRedirectMask, &ev); 873 | if (ev.type == MotionNotify) { 874 | xw = (arg->i == MOVE ? wa.x:wa.width) + ev.xmotion.x - rx; 875 | yh = (arg->i == MOVE ? wa.y:wa.height) + ev.xmotion.y - ry; 876 | if (arg->i == RESIZE) XResizeWindow(dis, d->curr->win, 877 | xw > MINWSZ ? xw:wa.width, yh > MINWSZ ? yh:wa.height); 878 | else if (arg->i == MOVE) XMoveWindow(dis, d->curr->win, xw, yh); 879 | } else if (ev.type == ConfigureRequest || ev.type == MapRequest) events[ev.type](&ev); 880 | } while (ev.type != ButtonRelease); 881 | 882 | XUngrabPointer(dis, CurrentTime); 883 | } 884 | 885 | /** 886 | * monocle aka max aka fullscreen mode/layout 887 | * each window should cover all the available screen space 888 | */ 889 | void monocle(int x, int y, int w, int h, const Desktop *d) { 890 | int gap_width = MONOCLE_GAP?USELESSGAP:0; 891 | int border_width = MONOCLE_BORDER?BORDER_WIDTH:0; 892 | if (d->mode != MONOCLE) { 893 | canclefullscreen(d, &monitors[currmonidx]); 894 | for (Client *c = d->head; c; c = c->next) { 895 | if (!(c->isfloat || c->istrans)) 896 | XMoveResizeWindow(dis, c->win, x + USELESSGAP, y + USELESSGAP, w - 2*USELESSGAP - 2*BORDER_WIDTH, h - 2*USELESSGAP - 2*BORDER_WIDTH); 897 | } 898 | } else { 899 | for (Client *c = d->head; c; c = c->next) 900 | if (!c->istrans) { 901 | XChangeProperty(dis, c->win, netatoms[NET_WM_STATE], XA_ATOM, 32, PropModeReplace, (unsigned char*) 902 | ((c->isfull = !(MONOCLE_GAP || MONOCLE_BORDER)) ? &netatoms[NET_FULLSCREEN]:0), !(MONOCLE_GAP || MONOCLE_BORDER)); 903 | XMoveResizeWindow(dis, c->win, x + gap_width, y - (COVER_PANEL?(TOP_PANEL && d->sbar ? PANEL_HEIGHT:0):0) + gap_width, 904 | w - 2*gap_width - 2*border_width, h + (COVER_PANEL?(TOP_PANEL && d->sbar ? PANEL_HEIGHT:0):0) - 2*gap_width - 2*border_width); 905 | XSetWindowBorderWidth(dis, c->win, (MONOCLE_BORDER?BORDER_WIDTH:0)); 906 | } 907 | } 908 | } 909 | 910 | /** 911 | * swap positions of current and next from current clients 912 | */ 913 | void move_down(void) { 914 | Desktop *d = &monitors[currmonidx].desktops[monitors[currmonidx].currdeskidx]; 915 | if (!d->curr || !d->head->next) return; 916 | /* p is previous, c is current, n is next, if current is head n is last */ 917 | Client *p = prevclient(d->curr, d), *n = (d->curr->next) ? d->curr->next:d->head; 918 | /* 919 | * if c is head, swapping with n should update head to n 920 | * [c]->[n]->.. ==> [n]->[c]->.. 921 | * ^head ^head 922 | * 923 | * else there is a previous client and p->next should be what's after c 924 | * ..->[p]->[c]->[n]->.. ==> ..->[p]->[n]->[c]->.. 925 | */ 926 | if (d->curr == d->head) d->head = n; else p->next = d->curr->next; 927 | /* 928 | * if c is the last client, c will be the current head 929 | * [n]->..->[p]->[c]->NULL ==> [c]->[n]->..->[p]->NULL 930 | * ^head ^head 931 | * else c will take the place of n, so c-next will be n->next 932 | * ..->[p]->[c]->[n]->.. ==> ..->[p]->[n]->[c]->.. 933 | */ 934 | d->curr->next = (d->curr->next) ? n->next:n; 935 | /* 936 | * if c was swapped with n then they now point to the same ->next. n->next should be c 937 | * ..->[p]->[c]->[n]->.. ==> ..->[p]->[n]->.. ==> ..->[p]->[n]->[c]->.. 938 | * [c]-^ 939 | * 940 | * else c is the last client and n is head, 941 | * so c will be move to be head, no need to update n->next 942 | * [n]->..->[p]->[c]->NULL ==> [c]->[n]->..->[p]->NULL 943 | * ^head ^head 944 | */ 945 | if (d->curr->next == n->next) n->next = d->curr; else d->head = d->curr; 946 | if (!d->curr->isfloat && !d->curr->istrans) tile(d, &monitors[currmonidx]); 947 | } 948 | 949 | /** 950 | * swap positions of current and previous from current clients 951 | */ 952 | void move_up(void) { 953 | Desktop *d = &monitors[currmonidx].desktops[monitors[currmonidx].currdeskidx]; 954 | if (!d->curr || !d->head->next) return; 955 | /* p is previous from current or last if current is head */ 956 | Client *pp = NULL, *p = prevclient(d->curr, d); 957 | /* pp is previous from p, or null if current is head and thus p is last */ 958 | if (p->next) for (pp = d->head; pp && pp->next != p; pp = pp->next); 959 | /* 960 | * if p has a previous client then the next client should be current (current is c) 961 | * ..->[pp]->[p]->[c]->.. ==> ..->[pp]->[c]->[p]->.. 962 | * 963 | * if p doesn't have a previous client, then p might be head, so head must change to c 964 | * [p]->[c]->.. ==> [c]->[p]->.. 965 | * ^head ^head 966 | * if p is not head, then c is head (and p is last), so the new head is next of c 967 | * [c]->[n]->..->[p]->NULL ==> [n]->..->[p]->[c]->NULL 968 | * ^head ^last ^head ^last 969 | */ 970 | if (pp) pp->next = d->curr; else d->head = (d->curr == d->head) ? d->curr->next:d->curr; 971 | /* 972 | * next of p should be next of c 973 | * ..->[pp]->[p]->[c]->[n]->.. ==> ..->[pp]->[c]->[p]->[n]->.. 974 | * except if c was head (now c->next is head), so next of p should be c 975 | * [c]->[n]->..->[p]->NULL ==> [n]->..->[p]->[c]->NULL 976 | * ^head ^last ^head ^last 977 | */ 978 | p->next = (d->curr->next == d->head) ? d->curr:d->curr->next; 979 | /* 980 | * next of c should be p 981 | * ..->[pp]->[p]->[c]->[n]->.. ==> ..->[pp]->[c]->[p]->[n]->.. 982 | * except if c was head (now c->next is head), so c is must be last 983 | * [c]->[n]->..->[p]->NULL ==> [n]->..->[p]->[c]->NULL 984 | * ^head ^last ^head ^last 985 | */ 986 | d->curr->next = (d->curr->next == d->head) ? NULL:p; 987 | if (!d->curr->isfloat && !d->curr->istrans) tile(d, &monitors[currmonidx]); 988 | } 989 | 990 | /** 991 | * move and resize a window with the keyboard 992 | */ 993 | void moveresize(const Arg *arg) { 994 | Monitor *m = &monitors[currmonidx]; Desktop *d = &m->desktops[m->currdeskidx]; 995 | XWindowAttributes wa; 996 | if (!d->curr || !XGetWindowAttributes(dis, d->curr->win, &wa)) return; 997 | if (!d->curr->isfloat && !d->curr->istrans) { d->curr->isfloat = True; tile(d, m); focus(d->curr, d, m); } 998 | XRaiseWindow(dis, d->curr->win); 999 | XMoveResizeWindow(dis, d->curr->win, wa.x + ((int *)arg->v)[0], wa.y + ((int *)arg->v)[1], 1000 | wa.width + ((int *)arg->v)[2], wa.height + ((int *)arg->v)[3]); 1001 | } 1002 | 1003 | /** 1004 | * cyclic focus the next window 1005 | * if the window is the last on stack, focus head 1006 | */ 1007 | void next_win(void) { 1008 | Desktop *d = &monitors[currmonidx].desktops[monitors[currmonidx].currdeskidx]; 1009 | if (d->curr && d->head->next) focus(d->curr->next ? d->curr->next:d->head, d, &monitors[currmonidx]); 1010 | } 1011 | 1012 | /** 1013 | * get the previous client from the given 1014 | * if no such client, return NULL 1015 | */ 1016 | Client* prevclient(Client *c, Desktop *d) { 1017 | Client *p = NULL; 1018 | if (c && d->head && d->head->next) for (p = d->head; p->next && p->next != c; p = p->next); 1019 | return p; 1020 | } 1021 | 1022 | /** 1023 | * cyclic focus the previous window 1024 | * if the window is head, focus the last stack window 1025 | */ 1026 | void prev_win(void) { 1027 | Desktop *d = &monitors[currmonidx].desktops[monitors[currmonidx].currdeskidx]; 1028 | if (d->curr && d->head->next) focus(prevclient(d->curr, d), d, &monitors[currmonidx]); 1029 | } 1030 | 1031 | /** 1032 | * set unrgent hint for a window 1033 | */ 1034 | void propertynotify(XEvent *e) { 1035 | Monitor *m = NULL; Desktop *d = NULL; Client *c = NULL; 1036 | if (e->xproperty.atom != XA_WM_HINTS || !wintoclient(e->xproperty.window, &c, &d, &m)) return; 1037 | 1038 | XWMHints *wmh = XGetWMHints(dis, c->win); 1039 | Desktop *cd = &monitors[currmonidx].desktops[monitors[currmonidx].currdeskidx]; 1040 | c->isurgn = (c != cd->curr && wmh && (wmh->flags & XUrgencyHint)); 1041 | 1042 | if (wmh) XFree(wmh); 1043 | desktopinfo(); 1044 | } 1045 | 1046 | /** 1047 | * to quit just stop receiving events 1048 | * run is stopped and control is back to main 1049 | */ 1050 | void quit(const Arg *arg) { 1051 | retval = arg->i; 1052 | running = False; 1053 | } 1054 | 1055 | /** 1056 | * remove the specified client from the given desktop 1057 | * 1058 | * if c was the previous client, previous must be updated. 1059 | * if c was the current client, current must be updated. 1060 | */ 1061 | void removeclient(Client *c, Desktop *d, Monitor *m) { 1062 | Client **p = NULL; 1063 | for (p = &d->head; *p && (*p != c); p = &(*p)->next); 1064 | if (!*p) return; else *p = c->next; 1065 | if (c == d->prev && !(d->prev = prevclient(d->curr, d))) d->prev = d->head; 1066 | if (c == d->curr || (d->head && !d->head->next)) focus(d->prev, d, m); 1067 | if (!(c->isfloat || c->istrans) || (d->head && !d->head->next)) tile(d, m); 1068 | free(c); 1069 | desktopinfo(); 1070 | } 1071 | 1072 | /** 1073 | * resize the master size 1074 | * we should check for window size limits for both master and 1075 | * stack clients. the size of a window can't be less than MINWSZ 1076 | */ 1077 | void resize_master(const Arg *arg) { 1078 | Monitor *m = &monitors[currmonidx]; 1079 | Desktop *d = &m->desktops[m->currdeskidx]; 1080 | int msz = (d->mode == BSTACK ? m->h:m->w) * MASTER_SIZE + (d->masz += arg->i); 1081 | if (msz >= MINWSZ && (d->mode == BSTACK ? m->h:m->w) - msz >= MINWSZ) tile(d, m); 1082 | else d->masz -= arg->i; /* reset master area size */ 1083 | } 1084 | 1085 | /** 1086 | * resize the first stack window 1087 | */ 1088 | void resize_stack(const Arg *arg) { 1089 | monitors[currmonidx].desktops[monitors[currmonidx].currdeskidx].sasz += arg->i; 1090 | tile(&monitors[currmonidx].desktops[monitors[currmonidx].currdeskidx], &monitors[currmonidx]); 1091 | } 1092 | 1093 | /** 1094 | * jump and focus the next or previous desktop 1095 | */ 1096 | void rotate(const Arg *arg) { 1097 | change_desktop(&(Arg){.i = (DESKTOPS + monitors[currmonidx].currdeskidx + arg->i) % DESKTOPS}); 1098 | } 1099 | 1100 | /** 1101 | * jump and focus the next non-empty desktop 1102 | */ 1103 | void rotate_filled(const Arg *arg) { 1104 | Monitor *m = &monitors[currmonidx]; 1105 | int n = arg->i; 1106 | while (n < DESKTOPS && !m->desktops[(DESKTOPS + m->currdeskidx + n) % DESKTOPS].head) (n += arg->i); 1107 | change_desktop(&(Arg){.i = (DESKTOPS + m->currdeskidx + n) % DESKTOPS}); 1108 | } 1109 | 1110 | /** 1111 | * main event loop 1112 | * on receival of an event call the appropriate handler 1113 | */ 1114 | void run(void) { 1115 | XEvent ev; 1116 | while(running && !XNextEvent(dis, &ev)) if (events[ev.type]) events[ev.type](&ev); 1117 | } 1118 | 1119 | /** 1120 | * set the fullscreen state of a client 1121 | * 1122 | * if a client gets fullscreen resize it 1123 | * to cover all screen space. 1124 | * the border should be zero (0). 1125 | * 1126 | * if a client is reset from fullscreen, 1127 | * the border should be BORDER_WIDTH. 1128 | */ 1129 | void setfullscreen(Client *c, Desktop *d, Monitor *m, Bool fullscrn) { 1130 | if (fullscrn != c->isfull) XChangeProperty(dis, c->win, 1131 | netatoms[NET_WM_STATE], XA_ATOM, 32, PropModeReplace, (unsigned char*) 1132 | ((c->isfull = fullscrn) ? &netatoms[NET_FULLSCREEN]:0), fullscrn); 1133 | if (fullscrn) XMoveResizeWindow(dis, c->win, m->x, m->y, m->w, m->h); 1134 | XSetWindowBorderWidth(dis, c->win, (c->isfull || !d->head->next ? 0:BORDER_WIDTH)); 1135 | } 1136 | 1137 | /** 1138 | * cancle fullscreen state 1139 | * if layout is float, resize window, reset window border, 1140 | * otherwise just reset window border waiting corresponding 1141 | * layout function to resize window. 1142 | */ 1143 | 1144 | void canclefullscreen(const Desktop *d, Monitor *m) 1145 | { 1146 | for (Client *c = d->head; c; c = c->next) 1147 | if ((d->mode == FLOAT || c->isfloat) && c->isfull) { 1148 | XChangeProperty(dis, c->win, netatoms[NET_WM_STATE], XA_ATOM, 32, PropModeReplace, (unsigned char*) 1149 | ((c->isfull = False) ? &netatoms[NET_FULLSCREEN]:0), False); 1150 | XMoveResizeWindow(dis, c->win, m->x + USELESSGAP, m->y + USELESSGAP + (TOP_PANEL && d->sbar ? PANEL_HEIGHT:0), 1151 | m->w - 2*USELESSGAP - 2*BORDER_WIDTH, m->h - 2*USELESSGAP - 2*BORDER_WIDTH - (TOP_PANEL && d->sbar ? PANEL_HEIGHT:0)); 1152 | XSetWindowBorderWidth(dis, c->win, BORDER_WIDTH); 1153 | } else { 1154 | XChangeProperty(dis, c->win, netatoms[NET_WM_STATE], XA_ATOM, 32, PropModeReplace, (unsigned char*) 1155 | ((c->isfull = False) ? &netatoms[NET_FULLSCREEN]:0), False); 1156 | XSetWindowBorderWidth(dis, c->win, BORDER_WIDTH); 1157 | } 1158 | } 1159 | 1160 | 1161 | /** 1162 | * set initial values 1163 | */ 1164 | void setup(void) { 1165 | sigchld(0); 1166 | 1167 | /* screen and root window */ 1168 | const int screen = DefaultScreen(dis); 1169 | root = RootWindow(dis, screen); 1170 | 1171 | /* initialize monitors and desktops */ 1172 | XineramaScreenInfo *info = XineramaQueryScreens(dis, &nmonitors); 1173 | 1174 | if (!nmonitors || !info) 1175 | errx(EXIT_FAILURE, "Xinerama is not active"); 1176 | if (!(monitors = calloc(nmonitors, sizeof(Monitor)))) 1177 | err(EXIT_FAILURE, "cannot allocate monitors"); 1178 | 1179 | for (int m = 0; m < nmonitors; m++) { 1180 | monitors[m] = (Monitor){ .x = info[m].x_org, .y = info[m].y_org, 1181 | .w = info[m].width, .h = info[m].height }; 1182 | for (unsigned int d = 0; d < DESKTOPS; d++) 1183 | monitors[m].desktops[d] = (Desktop){ .mode = DEFAULT_MODE, .sbar = SHOW_PANEL }; 1184 | } 1185 | XFree(info); 1186 | 1187 | /* init values for each monitor and desktop */ 1188 | for (unsigned int i = 0, m = init[0].m, d = init[0].d; i < LENGTH(init); i++, m = init[i].m, d = init[i].d) { 1189 | monitors[m].desktops[d].mode = init[i].dl.mode; 1190 | monitors[m].desktops[d].sbar = init[i].dl.sbar; 1191 | monitors[m].desktops[d].masz = init[i].dl.masz; 1192 | } 1193 | 1194 | /* get color for focused and unfocused client borders */ 1195 | win_focus = getcolor(FOCUS, screen); 1196 | win_unfocus = getcolor(UNFOCUS, screen); 1197 | win_infocus = getcolor(INFOCUS, screen); 1198 | 1199 | /* set numlockmask */ 1200 | XModifierKeymap *modmap = XGetModifierMapping(dis); 1201 | for (int k = 0; k < 8; k++) for (int j = 0; j < modmap->max_keypermod; j++) 1202 | if (modmap->modifiermap[modmap->max_keypermod*k + j] == XKeysymToKeycode(dis, XK_Num_Lock)) 1203 | numlockmask = (1 << k); 1204 | XFreeModifiermap(modmap); 1205 | 1206 | /* set up atoms for dialog/notification windows */ 1207 | wmatoms[WM_PROTOCOLS] = XInternAtom(dis, "WM_PROTOCOLS", False); 1208 | wmatoms[WM_DELETE_WINDOW] = XInternAtom(dis, "WM_DELETE_WINDOW", False); 1209 | netatoms[NET_SUPPORTED] = XInternAtom(dis, "_NET_SUPPORTED", False); 1210 | netatoms[NET_WM_STATE] = XInternAtom(dis, "_NET_WM_STATE", False); 1211 | netatoms[NET_ACTIVE] = XInternAtom(dis, "_NET_ACTIVE_WINDOW", False); 1212 | netatoms[NET_FULLSCREEN] = XInternAtom(dis, "_NET_WM_STATE_FULLSCREEN", False); 1213 | 1214 | /* propagate EWMH support */ 1215 | XChangeProperty(dis, root, netatoms[NET_SUPPORTED], XA_ATOM, 32, 1216 | PropModeReplace, (unsigned char *)netatoms, NET_COUNT); 1217 | 1218 | /* set the appropriate error handler 1219 | * try an action that will cause an error if another wm is active 1220 | * wait until events are processed to process the error from the above action 1221 | * if all is good set the generic error handler */ 1222 | XSetErrorHandler(xerrorstart); 1223 | /* set masks for reporting events handled by the wm */ 1224 | XSelectInput(dis, root, ROOTMASK); 1225 | XSync(dis, False); 1226 | XSetErrorHandler(xerror); 1227 | XSync(dis, False); 1228 | 1229 | grabkeys(); 1230 | if (DEFAULT_DESKTOP >= 0 && DEFAULT_DESKTOP < DESKTOPS) change_desktop(&(Arg){.i = DEFAULT_DESKTOP}); 1231 | if (DEFAULT_MONITOR >= 0 && DEFAULT_MONITOR < nmonitors) change_monitor(&(Arg){.i = DEFAULT_MONITOR}); 1232 | } 1233 | 1234 | void sigchld(__attribute__((unused)) int sig) { 1235 | if (signal(SIGCHLD, sigchld) != SIG_ERR) while(0 < waitpid(-1, NULL, WNOHANG)); 1236 | else err(EXIT_FAILURE, "cannot install SIGCHLD handler"); 1237 | } 1238 | 1239 | /** 1240 | * execute a command 1241 | */ 1242 | void spawn(const Arg *arg) { 1243 | if (fork()) return; 1244 | if (dis) close(ConnectionNumber(dis)); 1245 | setsid(); 1246 | execvp((char*)arg->com[0], (char**)arg->com); 1247 | err(EXIT_SUCCESS, "execvp %s", (char *)arg->com[0]); 1248 | } 1249 | 1250 | /** 1251 | * tile or common tiling aka v-stack mode/layout 1252 | * bstack or bottom stack aka h-stack mode/layout 1253 | */ 1254 | void stack(int x, int y, int w, int h, const Desktop *d) { 1255 | Client *c = NULL, *t = NULL; Bool b = (d->mode == BSTACK); 1256 | int n = 0, p = 0, z = (b ? w:h), ma = (b ? h:w) * MASTER_SIZE + d->masz; 1257 | 1258 | canclefullscreen(d, &monitors[currmonidx]); 1259 | /* count stack windows and grab first non-floating, non-fullscreen window */ 1260 | for (t = d->head; t; t = t->next) if (!ISFFT(t)) { if (c) ++n; else c = t; } 1261 | 1262 | /* if there is only one window (c && !n), it should cover the available screen space 1263 | * if there is only one stack window, then we don't care about growth 1264 | * if more than one stack windows (n > 1) adjustments may be needed. 1265 | * 1266 | * - p is the num of pixels than remain when spliting the 1267 | * available width/height to the number of windows 1268 | * - z is each client's height/width 1269 | * 1270 | * ---------- --. ----------------------. 1271 | * | |----| }--|--> sasz }--> first client will have 1272 | * | | 1s | | | z+p+sasz height/width. 1273 | * | M |----|-. }--> screen height (h) ---' 1274 | * | | 2s | }--|--> client height (z) two stack clients on tile mode 1275 | * -----------' -' ::: ascii art by c00kiemon5ter 1276 | * 1277 | * what we do is, remove the sasz from the screen height/width and then 1278 | * divide that space with the windows on the stack so all windows have 1279 | * equal height/width: z = (z - sasz)/n 1280 | * 1281 | * sasz was left out (subtrackted), to later be added to the first client 1282 | * height/width. before we do that, there will be cases when the num of 1283 | * windows cannot be perfectly divided with the available screen height/width. 1284 | * for example: 100px scr. height, and 3 stack windows: 100/3 = 33,3333.. 1285 | * so we get that remaining space and merge it to sasz: p = (z - sasz) % n + sasz 1286 | * 1287 | * in the end, we know each client's height/width (z), and how many pixels 1288 | * should be added to the first stack client (p) so that it satisfies sasz, 1289 | * and also, does not result in gaps created on the bottom of the screen. 1290 | */ 1291 | if (c && !n) XMoveResizeWindow(dis, c->win, x + USELESSGAP, y + USELESSGAP, 1292 | w - 2*(BORDER_WIDTH + USELESSGAP), h - 2*(BORDER_WIDTH + USELESSGAP)); 1293 | if (!c || !n) return; else if (n > 1) { p = (z - d->sasz)%n + d->sasz; z = (z - d->sasz)/n; } 1294 | 1295 | /* tile the first non-floating, non-fullscreen window to cover the master area */ 1296 | if (b) XMoveResizeWindow(dis, c->win, x + USELESSGAP, y + USELESSGAP, 1297 | w - 2*(BORDER_WIDTH + USELESSGAP), ma - 2*(BORDER_WIDTH + USELESSGAP)); 1298 | else XMoveResizeWindow(dis, c->win, x + USELESSGAP, y + USELESSGAP, 1299 | ma - 2*(BORDER_WIDTH + USELESSGAP), h - 2*(BORDER_WIDTH + USELESSGAP)); 1300 | 1301 | /* tile the next non-floating, non-fullscreen (and first) stack window adding p */ 1302 | for (c = c->next; c && ISFFT(c); c = c->next); 1303 | int ch = z - 2*BORDER_WIDTH - USELESSGAP, cw = (b ? h:w) - 2*BORDER_WIDTH - ma - USELESSGAP; 1304 | if (b) XMoveResizeWindow(dis, c->win, x += USELESSGAP, y += ma, ch - USELESSGAP + p, cw); 1305 | else XMoveResizeWindow(dis, c->win, x += ma, y += USELESSGAP, cw, ch - USELESSGAP + p); 1306 | 1307 | /* tile the rest of the non-floating, non-fullscreen stack windows */ 1308 | for (b ? (x += z+p-USELESSGAP):(y += z+p-USELESSGAP), c = c->next; c; c = c->next) { 1309 | if (ISFFT(c)) continue; 1310 | if (b) { XMoveResizeWindow(dis, c->win, x, y, ch, cw); x += z; } 1311 | else { XMoveResizeWindow(dis, c->win, x, y, cw, ch); y += z; } 1312 | } 1313 | } 1314 | 1315 | /** 1316 | * swap master window with current. 1317 | * if current is head swap with next 1318 | * if current is not head, then head 1319 | * is behind us, so move_up until we 1320 | * are the head 1321 | */ 1322 | void swap_master(void) { 1323 | Desktop *d = &monitors[currmonidx].desktops[monitors[currmonidx].currdeskidx]; 1324 | if (!d->curr || !d->head->next) return; 1325 | if (d->curr == d->head) move_down(); 1326 | else while (d->curr != d->head) move_up(); 1327 | focus(d->head, d, &monitors[currmonidx]); 1328 | } 1329 | 1330 | /** 1331 | * switch tiling mode/layout 1332 | * 1333 | * if mode is reselected reset all floating clients 1334 | * if mode is FLOAT set all clients floating 1335 | */ 1336 | void switch_mode(const Arg *arg) { 1337 | Desktop *d = &monitors[currmonidx].desktops[monitors[currmonidx].currdeskidx]; 1338 | if (d->mode != arg->i) d->mode = arg->i; 1339 | else if (d->mode != FLOAT) for (Client *c = d->head; c; c = c->next) c->isfloat = False; 1340 | if (d->head) { tile(d, &monitors[currmonidx]); focus(d->curr, d, &monitors[currmonidx]); } 1341 | desktopinfo(); 1342 | } 1343 | 1344 | /** 1345 | * tile clients of the given desktop with the desktop's mode/layout 1346 | * call the tiling handler fucntion taking account the panel height 1347 | */ 1348 | void tile(Desktop *d, Monitor *m) { 1349 | if (!d->head || d->mode == FLOAT) { 1350 | canclefullscreen(d, &monitors[currmonidx]); 1351 | return; /* nothing to arange */ 1352 | } 1353 | layout[d->head->next ? d->mode:MONOCLE](m->x, m->y + (TOP_PANEL && d->sbar ? PANEL_HEIGHT:0), 1354 | m->w, m->h - (d->sbar ? PANEL_HEIGHT:0), d); 1355 | } 1356 | 1357 | /** 1358 | * toggle visibility state of the panel/bar 1359 | */ 1360 | void togglepanel(void) { 1361 | Monitor *m = &monitors[currmonidx]; 1362 | m->desktops[m->currdeskidx].sbar = !m->desktops[m->currdeskidx].sbar; 1363 | tile(&m->desktops[m->currdeskidx], m); 1364 | } 1365 | 1366 | /** 1367 | * windows that request to unmap should lose their client 1368 | * so invisible windows do not exist on screen 1369 | */ 1370 | void unmapnotify(XEvent *e) { 1371 | Monitor *m = NULL; Desktop *d = NULL; Client *c = NULL; 1372 | if (wintoclient(e->xunmap.window, &c, &d, &m)) removeclient(c, d, m); 1373 | } 1374 | 1375 | /** 1376 | * find to which client and desktop the given window belongs to 1377 | */ 1378 | Bool wintoclient(Window w, Client **c, Desktop **d, Monitor **m) { 1379 | for (int cm = 0; cm < nmonitors && !*c; cm++) 1380 | for (int cd = 0; cd < DESKTOPS && !*c; cd++) 1381 | for (*m = &monitors[cm], *d = &(*m)->desktops[cd], *c = (*d)->head; *c && (*c)->win != w; *c = (*c)->next); 1382 | return (*c != NULL); 1383 | } 1384 | 1385 | /** 1386 | * There's no way to check accesses to destroyed windows, 1387 | * thus those cases are ignored (especially on UnmapNotify's). 1388 | */ 1389 | int xerror(__attribute__((unused)) Display *dis, XErrorEvent *ee) { 1390 | if ((ee->error_code == BadAccess && (ee->request_code == X_GrabKey 1391 | || ee->request_code == X_GrabButton)) 1392 | || (ee->error_code == BadMatch && (ee->request_code == X_SetInputFocus 1393 | || ee->request_code == X_ConfigureWindow)) 1394 | || (ee->error_code == BadDrawable && (ee->request_code == X_PolyFillRectangle 1395 | || ee->request_code == X_CopyArea || ee->request_code == X_PolySegment 1396 | || ee->request_code == X_PolyText8)) 1397 | || ee->error_code == BadWindow) return 0; 1398 | err(EXIT_FAILURE, "xerror: request: %d code: %d", ee->request_code, ee->error_code); 1399 | } 1400 | 1401 | /** 1402 | * error handler function to display an appropriate error message 1403 | * when the window manager initializes (see setup - XSetErrorHandler) 1404 | */ 1405 | int xerrorstart(__attribute__((unused)) Display *dis, __attribute__((unused)) XErrorEvent *ee) { 1406 | errx(EXIT_FAILURE, "xerror: another window manager is already running"); 1407 | } 1408 | 1409 | int main(int argc, char *argv[]) { 1410 | if (argc == 2 && !strncmp(argv[1], "-v", 3)) 1411 | errx(EXIT_SUCCESS, "version: %s - by c00kiemon5ter >:3 omnomnomnom", VERSION); 1412 | else if (argc != 1) errx(EXIT_FAILURE, "usage: man monsterwm"); 1413 | if (!(dis = XOpenDisplay(NULL))) errx(EXIT_FAILURE, "cannot open display"); 1414 | setup(); 1415 | desktopinfo(); /* zero out every desktop on (re)start */ 1416 | run(); 1417 | cleanup(); 1418 | XCloseDisplay(dis); 1419 | return retval; 1420 | } 1421 | 1422 | /* vim: set expandtab ts=4 sts=4 sw=4 : */ 1423 | --------------------------------------------------------------------------------