├── .gitignore ├── Makefile ├── README.md ├── LICENSE ├── frankenwm.1 ├── config.def.h └── frankenwm.c /.gitignore: -------------------------------------------------------------------------------- 1 | frankenwm 2 | *.o 3 | *.swp 4 | *~ 5 | *.diff 6 | tags 7 | config.h 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | WMNAME = frankenwm 2 | 3 | PREFIX ?= /usr/local 4 | BINDIR ?= ${PREFIX}/bin 5 | MANPREFIX ?= ${PREFIX}/share/man 6 | 7 | INCS = -I. -I. `pkg-config --cflags xcb xcb-aux xcb-icccm xcb-keysyms xcb-ewmh` 8 | LIBS = -lc -lX11 `pkg-config --libs xcb xcb-aux xcb-icccm xcb-keysyms xcb-ewmh` 9 | 10 | CFLAGS += -std=c99 -pedantic -Wall -Wextra ${INCS} ${CPPFLAGS} 11 | LDFLAGS += ${LIBS} 12 | 13 | EXEC = ${WMNAME} 14 | 15 | SRC = ${WMNAME}.c 16 | OBJ = ${SRC:.c=.o} 17 | 18 | ifeq (${DEBUG},0) 19 | CFLAGS += -Os 20 | else 21 | CFLAGS += -g 22 | LDFLAGS += -g 23 | endif 24 | 25 | all: options ${WMNAME} 26 | 27 | options: 28 | @echo ${WMNAME} build options: 29 | @echo "CFLAGS = ${CFLAGS}" 30 | @echo "LDFLAGS = ${LDFLAGS}" 31 | @echo "CC = ${CC}" 32 | 33 | .c.o: 34 | @echo CC $< 35 | @${CC} -c ${CFLAGS} $< 36 | 37 | ${OBJ}: config.h 38 | 39 | config.h: 40 | @echo creating $@ from config.def.h 41 | @cp config.def.h $@ 42 | 43 | ${WMNAME}: ${OBJ} 44 | @echo CC -o $@ 45 | @${CC} -o $@ ${OBJ} ${LDFLAGS} 46 | 47 | clean: 48 | @echo cleaning 49 | @rm -fv ${WMNAME} ${OBJ} ${WMNAME}-${VERSION}.tar.gz 50 | 51 | install: all 52 | @echo installing executable file to ${DESTDIR}${PREFIX}/bin 53 | @install -Dm755 ${WMNAME} ${DESTDIR}${PREFIX}/bin/${WMNAME} 54 | @echo installing manual page to ${DESTDIR}${MANPREFIX}/man.1 55 | @install -Dm644 ${WMNAME}.1 ${DESTDIR}${MANPREFIX}/man1/${WMNAME}.1 56 | 57 | uninstall: 58 | @echo removing executable file from ${DESTDIR}${PREFIX}/bin 59 | @rm -f ${DESTDIR}${PREFIX}/bin/${WMNAME} 60 | @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 61 | @rm -f ${DESTDIR}${MANPREFIX}/man1/${WMNAME}.1 62 | 63 | .PHONY: all options clean install uninstall 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FrankenWM 2 | ========= 3 | 4 | *"monsterwm's bastard child"* or *"not the wm your desktop needs, but the one 5 | it deserves"* 6 | 7 | FrankenWM is a dynamic tiling WM (comparable to dwm or Awesome), featuring the 8 | v-stack, b-stack, grid, fibonacci, dualstack, equal and monocle layouts out of 9 | the box. If you want to, you can add gaps between the windows as well. 10 | 11 | It was once based on monsterwm but has undergone many greater changes including 12 | adding pieces from other WMs (hence the name) and porting all sorts of stuff 13 | from Xlib to XCB. Many of the original monsterwm patches have been ported as 14 | well. 15 | 16 | All settings must be set at compile time by editing `config.h` and it does not 17 | feature a status bar (but supports leaving preconfigured space for one). I 18 | prepared a few scripts for different bars [here][sb]. 19 | 20 | [sb]: https://gist.github.com/sulami/d6a53179d6d7479e0709 21 | 22 | Installation 23 | ------------ 24 | 25 | Note: If you are on Arch Linux, you can use the [AUR][aur]. 26 | 27 | You need xcb and xcb-utils then, copy `config.def.h` as `config.h` and edit to 28 | suit your needs. Build and install. 29 | 30 | $ cp config.def.h config.h 31 | $ $EDITOR config.h 32 | $ make 33 | # make clean install 34 | 35 | The packages in Arch Linux needed for example would be 36 | `libxcb` `xcb-util` `xcb-util-wm` `xcb-util-keysyms` 37 | 38 | [aur]: https://aur.archlinux.org/packages/frankenwm-git/ 39 | 40 | Configuration 41 | ------------- 42 | 43 | Configuration is done by editing `config.h` before compiling FrankenWM. 44 | 45 | Usage 46 | ----- 47 | 48 | I took the time to write a really nice and pretty manpage (man frankenwm, or 49 | man ./[frankenwm.1][man] if you want to read it before installing) covering the 50 | tiling modes and all of the default shortcuts. 51 | 52 | [man]: https://github.com/sulami/frankenwm/blob/master/frankenwm.1 53 | 54 | Bugs 55 | ---- 56 | 57 | You can report bugs and request features here: [FrankenWM GitHub issues][gh] or 58 | [ArchLinux Forums][af] 59 | 60 | [gh]: https://github.com/sulami/FrankenWM/issues 61 | [af]: https://bbs.archlinux.org/viewtopic.php?pid=1470320 62 | 63 | Thanks 64 | ------ 65 | 66 | Parts of FrankenWM come from: 67 | 68 | * [c00kiemonster's][cookiemonster] [monsterwm][monsterwm] 69 | * [cloudef's][cloudef] [monsterwm-xcb][monsterwm-xcb] 70 | * [venam's][venam] [2bwm][twobwm] 71 | 72 | 73 | Inspiration from: 74 | 75 | * [suckless'][suckless] [dwm][dwm] 76 | * [baskerville's][baskerville] [bspwm][bspwm] 77 | 78 | 79 | [cookiemonster]: https://github.com/c00kiemon5ter 80 | [monsterwm]: https://github.com/c00kiemon5ter/monsterwm 81 | [cloudef]: https://github.com/cloudef 82 | [monsterwm-xcb]: https://github.com/cloudef/monsterwm-xcb 83 | [venam]: https://github.com/venam 84 | [twobwm]: https://github.com/venam/2bwm 85 | 86 | [suckless]: http://suckless.org/ 87 | [dwm]: http://dwm.suckless.org/ 88 | [baskerville]: https://github.com/baskerville 89 | [bspwm]: https://github.com/baskerville/bspwm 90 | 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | © 2011 Ivan c00kiemon5ter Kanakarakis 4 | © 2012 Jari Cloudef Vetoniemi 5 | © 2014 Robin sulami Schroer 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a 8 | copy of this software and associated documentation files (the "Software"), 9 | to deal in the Software without restriction, including without limitation 10 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | and/or sell copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 20 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | 25 | --- --- --- --- --- --- --- --- --- --- --- --- --- 26 | Portions of dwm code are included in this project. 27 | Those portions are licensed as follows. 28 | --- --- --- --- --- --- --- --- --- --- --- --- --- 29 | 30 | MIT/X Consortium License 31 | 32 | © 2006-2011 Anselm R Garbe 33 | © 2007-2011 Peter Hartlich 34 | © 2010-2011 Connor Lane Smith 35 | © 2006-2009 Jukka Salmi 36 | © 2007-2009 Premysl Hruby 37 | © 2007-2009 Szabolcs Nagy 38 | © 2007-2009 Christof Musik 39 | © 2009 Mate Nagy 40 | © 2007-2008 Enno Gottox Boland 41 | © 2008 Martin Hurton 42 | © 2008 Neale Pickett 43 | © 2006-2007 Sander van Dijk 44 | 45 | Permission is hereby granted, free of charge, to any person obtaining a 46 | copy of this software and associated documentation files (the "Software"), 47 | to deal in the Software without restriction, including without limitation 48 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 49 | and/or sell copies of the Software, and to permit persons to whom the 50 | Software is furnished to do so, subject to the following conditions: 51 | 52 | The above copyright notice and this permission notice shall be included in 53 | all copies or substantial portions of the Software. 54 | 55 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 56 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 57 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 58 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 59 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 60 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 61 | DEALINGS IN THE SOFTWARE. 62 | 63 | -------------------------------------------------------------------------------- /frankenwm.1: -------------------------------------------------------------------------------- 1 | .TH FRANKENWM 1 frankenwm 2 | .SH NAME 3 | FrankenWM \- monsterwm's bastard child 4 | .SH SYNOPSIS 5 | .B frankenwm 6 | .RB [ \-v ] 7 | .SH DESCRIPTION 8 | FrankenWM is a dynamic tiling window manager pieced together from other WMs and 9 | based on XCB. 10 | .P 11 | .SH MODES 12 | There are currently seven base layouts: 13 | .P 14 | .I Vertical Stack (Tile) 15 | where the master window fill a large space either left or right and the rest of 16 | the windows is stacked on the other side, 17 | 18 | ___________ ___________ 19 | | | 1 | | 1 | | 20 | | M |___| or |___| M | 21 | | | 2 | | 2 | | 22 | |_______|___| |___|_______| 23 | 24 | .I Dualstack 25 | where the master is in the center and the stack is divided onto both the left 26 | and right side, or the top and bottom if inverted, 27 | 28 | ___________ ___________ 29 | | 1| | 3| |__1__|__2__| 30 | |__| M |__| or | M | 31 | | 2| | 4| |___________| 32 | |__|_____|__| |__3__|__4__| 33 | 34 | .I Bottom stack 35 | where the master is on top and the stack is horizontally below it, or if 36 | inverted, the other way around, 37 | 38 | ___________ ___________ 39 | | | |__1__|__2__| 40 | | M | or | | 41 | |___________| | M | 42 | |__1__|__2__| |___________| 43 | 44 | .I Grid mode 45 | where all the windows get sorted in a grid, giving each window roughly the same 46 | space on the screen, 47 | 48 | ___________ 49 | | M | 2 | 50 | |_____|_____| 51 | | 1 | 3 | 52 | |_____|_____| 53 | 54 | .I Equal mode 55 | where all the windows have exactly the same space on the screen, sorting them 56 | in either columns or rows, depending on the inverting status, 57 | 58 | ___________ ___________ 59 | | | | | |_____M_____| 60 | | M | 1 | 2 | or |_____1_____| 61 | | | | | |_____2_____| 62 | |___|___|___| |_____3_____| 63 | 64 | .I Fibonacci mode 65 | where the clients share 50% of the space of the client above them in the stack, 66 | getting smaller going down the stack, 67 | 68 | ___________ ___________ 69 | | | 1 | | | 2| 3| 70 | | M |_____| or | M |__|__| 71 | | | 2| 3| | | 1 | 72 | |_____|__|__| |_____|_____| 73 | 74 | .I Monocle mode 75 | where the clients take up the entire workspace, and additional clients are 76 | hidden behind the current shown window, 77 | 78 | ___________ 79 | | | 80 | | M | 81 | | | 82 | |___________| 83 | 84 | .I Floating mode 85 | where, windows can move and be resized freely in the screen space. Windows 86 | retain their floating status until the user switches to a tiling mode. 87 | 88 | ___________ 89 | || | 1| | 90 | || M |__| | 91 | ||_____| | 92 | |___________| 93 | 94 | When hitting a tiling mode shortcut while being already in this tiling mode, 95 | FrankenWM will catch all floating windows and reset them back to tiling as 96 | well. 97 | 98 | .SH OPTIONS 99 | .TP 100 | .B \-v 101 | prints version information to standard output, then exits. 102 | .SH USAGE 103 | .SS Status bar 104 | frankenwm does not provide a status bar. Consistent with the Unix philosophy, 105 | frankenwm provides information to the status bar or panel of choice via text. 106 | .SS Keyboard and mouse commands 107 | All of 108 | .I frankenwm's 109 | commands can be customized by editing 110 | .I config.h 111 | and recompiling. 112 | 113 | .SH DEFAULT SHORTCUTS 114 | 115 | .SS LAYOUTS 116 | 117 | .TP 118 | .B Mod4\-Shift\-t 119 | Sets tiled layout 120 | .TP 121 | .B Mod4\-Shift\-b 122 | Sets bottom stack layout 123 | .TP 124 | .B Mod4\-Shift\-g 125 | Sets grid layout 126 | .TP 127 | .B Mod4\-Shift\-f 128 | Sets fibonacci layout 129 | .TP 130 | .B Mod4\-Shift\-d 131 | Sets dualstack layout 132 | .TP 133 | .B Mod4\-Shift\-e 134 | Sets equal layout 135 | .TP 136 | .B Mod4\-Shift\-m 137 | Sets monocle layout 138 | .TP 139 | .B Mod4\-Shift\-{z,x} 140 | Cycle through the available tiling modes. Also resets all windows to tiling. 141 | 142 | .SS WINDOW SELECTION 143 | 144 | .TP 145 | .B Mod4\-{j,k} 146 | Focus next/previous window 147 | .TP 148 | .B Mod4\-Shift\-{j,k} 149 | Move the focussed window down/up the stack 150 | .TP 151 | .B Mod4\-Return 152 | Swaps the focused window to/from master area 153 | .TP 154 | .B Mod4\-w 155 | Toggle between master and previously selected stack window 156 | .TP 157 | .B Mod4\-f 158 | Maximize/fullscreen the current window 159 | .TP 160 | .B Mod4\-{m,n} 161 | Push windows to a minimize "stack"/pull them out. The stack is desktop-specific 162 | and the last window minimized gets restored first 163 | .TP 164 | .B Mod4\-Shift\-i 165 | Toggle the inverting status, which enables alternate versions of existing 166 | layout modes 167 | .TP 168 | .B Mod4\-Backspace 169 | Focus the window that raised an urgent hint. If no such window in current 170 | desktop, search other desktops, and focus the desktop and window that raised 171 | the urgent hint 172 | 173 | .SS COMMANDS 174 | 175 | .TP 176 | .B Mod4\-Shift\-Return 177 | Start 178 | .BR xterm (1) 179 | .TP 180 | .B Mod4\-r 181 | Start 182 | .BR dmenu (1) 183 | .TP 184 | .B Mod4\-Shift\-c 185 | Close the focused window 186 | .TP 187 | .B Mod4\-s 188 | Toggle the scratchpad terminal, if enabled. The scratchpad is a custom terminal 189 | that always floats and can be toggled from all displays 190 | .TP 191 | .B Mod4\-Control\-{q} 192 | Quit frankenwm 193 | 194 | .SS SETTINGS 195 | 196 | .TP 197 | .B Mod4\-{l,h} 198 | Increase/decrease master area size 199 | .TP 200 | .B Mod4\-Control\-{o,p} 201 | Decrease/increase useless gap size 202 | .TP 203 | .B Mod4\-Control\-{u,i} 204 | Decrease/increase the borders around windows 205 | .TP 206 | .B Mod4\-Control\-b 207 | Toggles the panel on and off 208 | .TP 209 | .B Mod4\-Control\-s 210 | Toggle display of windows on all desktops 211 | 212 | .SS VIRTUAL DESKTOPS 213 | 214 | .TP 215 | .B Mod4\-{1..n} 216 | Select the nth workspace. By default, 217 | .I frankenwm 218 | is configured with four workspaces 219 | .TP 220 | .B Mod4\-Shift\-{1..n} 221 | Move the focused window to the nth workspace 222 | .TP 223 | .B Mod4\-Shift\-{h,l} 224 | Switch to the next/previous desktop 225 | .TP 226 | .B Mod4\-Control\-{h,l} 227 | Switch to the next/previous desktop and take the current window with you 228 | .TP 229 | .B Mod4\-Control\-Shift-{h,l} 230 | Switch to the next/previous desktop and take all of the windows with you 231 | .TP 232 | .B Mod4\-Tab 233 | Toggles to the last selected desktop 234 | 235 | .SS FLOATING WINDOWS 236 | 237 | .TP 238 | .B Mod4\-c 239 | Center the focussed window in floating mode on the screen 240 | .TP 241 | .B Mod4\-Mod1\-{h,j,k,l} 242 | Move floating windows around 243 | .TP 244 | .B Mod4\-Mod1\-Control\-{h,j,k,l} 245 | Resize floating windows 246 | .TP 247 | .B Mod4\-t 248 | Reset just the active floating window back into tiling 249 | 250 | .SS MOUSE 251 | 252 | .TP 253 | .B Mod4\-Button1 254 | Dragging the mouse will move the selected window 255 | .TP 256 | .B Mod4\-Button3 257 | Dragging the mouse will resize the selected window 258 | 259 | .SH CUSTOMIZATION 260 | .I frankenwm 261 | is customized by copying 262 | .I config.def.h 263 | to 264 | .I config.h 265 | , customizing it and (re)compiling the source code. 266 | .SH SEE ALSO 267 | .BR dmenu (1) 268 | .SH BUGS 269 | .I frankenwm 270 | is under active development. Please report all bugs to the author. 271 | .SH AUTHOR 272 | Robin Schroer 273 | .SH BASED ON WORKS OF 274 | Jari Vetoniemi 275 | Ivan Kanakarakis 276 | 277 | -------------------------------------------------------------------------------- /config.def.h: -------------------------------------------------------------------------------- 1 | /* see LICENSE for copyright and license */ 2 | 3 | #ifndef CONFIG_H 4 | #define CONFIG_H 5 | 6 | /* Button definitions, nothing to edit for you */ 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 | /* EDIT THIS: general settings */ 13 | #define MASTER_SIZE 0.6 /* master-stack ratio */ 14 | #define SHOW_PANEL False /* show panel by default on exec */ 15 | #define TOP_PANEL True /* False means panel is on bottom */ 16 | #define PANEL_HEIGHT 18 /* 0 for no space for panel, thus no panel */ 17 | #define DEFAULT_MODE TILE /* TILE MONOCLE BSTACK GRID FIBONACCI EQUAL */ 18 | #define ATTACH_ASIDE True /* False means new window is master */ 19 | #define FOLLOW_MOUSE False /* Focus the window the mouse just entered */ 20 | #define FOLLOW_WINDOW False /* Follow the window when moved to a different desktop */ 21 | #define CLICK_TO_FOCUS True /* Focus an unfocused window when clicked */ 22 | #define BORDER_WIDTH 2 /* window border width */ 23 | #define SCRATCH_WIDTH 1 /* scratch window border width, 0 to disable */ 24 | #define FOCUS "#cccccc" /* focused window border color */ 25 | #define UNFOCUS "#121212" /* unfocused window border color */ 26 | #define SCRATCH "#cc0000" /* scratchpad border color */ 27 | #define DESKTOPS 10 /* number of desktops - edit DESKTOPCHANGE keys to suit */ 28 | #define DEFAULT_DESKTOP 0 /* the desktop to focus on exec */ 29 | #define MINWSZ 50 /* minimum window size in pixels */ 30 | #define USELESSGAP 8 /* the size of the useless gap in pixels */ 31 | #define GLOBALGAPS True /* use the same gap size on all desktops */ 32 | #define MONOCLE_BORDERS False /* display borders in monocle mode */ 33 | #define INVERT False /* use alternative modes by default */ 34 | #define AUTOCENTER True /* automatically center windows floating by default */ 35 | #define OUTPUT False /* write desktop info to stdout */ 36 | #define OUTPUT_TITLE False /* output the title of the currently active window */ 37 | #define USE_SCRATCHPAD False /* enable the scratchpad functionality */ 38 | #define CLOSE_SCRATCHPAD True /* close scratchpad on quit */ 39 | #define SCRPDNAME "scratchpad" /* the instance of the scratchpad window */ 40 | #define EWMH_TASKBAR True /* False if text (or no) panel/taskbar */ 41 | 42 | /* 43 | * EDIT THIS: applicaton specific rules 44 | * Open applications to specified desktop with specified mode. 45 | * If desktop is negative, then current is assumed. Desktops are 0-indexed. 46 | * If border_width is negative, the default is assumed. 47 | * 48 | * The matching is done via POSIX-ERE-regexes on the class or instance strings 49 | * as reported by xprop(1): 50 | * 51 | * WM_CLASS(STRING) = instance, class 52 | * 53 | * See https://en.wikipedia.org/wiki/Regular_expression#POSIX_extended for regex 54 | * syntax (hint: ".*" matches any string, "^GIMP$" only matches "GIMP", but an 55 | * unanchored "GIMP" also matches "AGIMPE"). 56 | * 57 | * Sadly, this can not be empty (for now), so enter something non-existent if 58 | * you do not wish to use this functionality. 59 | */ 60 | static const AppRule rules[] = { \ 61 | /* class instance desktop follow float border_with */ 62 | { "^GIMP$", ".*", -1, False, True, 0 }, 63 | { "Skype", ".*", 3, False, True, -1 }, 64 | }; 65 | 66 | /* helper for spawning shell commands, usually you don't edit this */ 67 | #define SHCMD(cmd) {.com = (const char*[]){"/bin/sh", "-c", cmd, NULL}} 68 | 69 | /* 70 | * EDIT THIS: commands 71 | * Adjust and add these to the shortcuts below to launch anything you want by 72 | * pressing a key (combination). The last argument should ALWAYS be a null 73 | * pointer. scrpcmd needs to be defined and different from all other commands 74 | * (like the example) so FrankenWM can tell when you want to open a scratchpad 75 | * window. The title of the scratchpad window should also match SCRPDNAME from 76 | * above 77 | */ 78 | static const char *termcmd[] = { "xterm", NULL }; 79 | static const char *menucmd[] = { "dmenu_run", NULL }; 80 | static const char *scrpcmd[] = { "xterm", "-T", "scratchpad", NULL }; 81 | /* static const char *scrpcmd[] = { "urxvt", "-name", "scratchpad", NULL }; */ 82 | 83 | #define DESKTOPCHANGE(K,N) \ 84 | { MOD4, K, change_desktop, {.i = N}}, \ 85 | { MOD4|ShiftMask, K, client_to_desktop, {.i = N}}, 86 | 87 | /* 88 | * EDIT THIS: shortcuts 89 | * By default, all shortcuts use only Mod4 (+ Shift/Control), but you can use 90 | * Mod1 as well if you like to, I just prefer not to. (Update: handling 91 | * floating windows makes more sense when using Mod1 as well, so there's that) 92 | */ 93 | static key keys[] = { 94 | /* modifier key function argument */ 95 | 96 | /* select windows */ 97 | { MOD4, XK_j, next_win, {NULL}}, 98 | { MOD4, XK_k, prev_win, {NULL}}, 99 | /* select the master window, or the previously focussed slave */ 100 | { MOD4, XK_w, focusmaster, {NULL}}, 101 | /* select urgent window */ 102 | { MOD4, XK_BackSpace, focusurgent, {NULL}}, 103 | 104 | /* move windows */ 105 | { MOD4|SHIFT, XK_j, move_down, {NULL}}, 106 | { MOD4|SHIFT, XK_k, move_up, {NULL}}, 107 | /* swap the current window to master */ 108 | { MOD4, XK_Return, swap_master, {NULL}}, 109 | /* maximize the current window */ 110 | { MOD4, XK_f, maximize, {NULL}}, 111 | /* minimize window to queue/pull window from queue */ 112 | { MOD4, XK_m, minimize, {NULL}}, 113 | { MOD4, XK_n, restore, {NULL}}, 114 | /* move the current window to the center of the screen, floating */ 115 | { MOD4, XK_c, centerwindow, {NULL}}, 116 | /* toggles inverted stacking modes (left/top stack) */ 117 | { MOD4|SHIFT, XK_i, invertstack, {NULL}}, 118 | /* show/hide all windows on all desktops */ 119 | { MOD4|CONTROL, XK_s, showhide, {NULL}}, 120 | /* toggle the scratchpad terminal, if enabled */ 121 | { MOD4, XK_s, togglescratchpad, {NULL}}, 122 | 123 | /* move floating windows */ 124 | { MOD4|MOD1, XK_j, float_y, {.i = +10}}, 125 | { MOD4|MOD1, XK_k, float_y, {.i = -10}}, 126 | { MOD4|MOD1, XK_h, float_x, {.i = -10}}, 127 | { MOD4|MOD1, XK_l, float_x, {.i = +10}}, 128 | /* resize floating windows */ 129 | { MOD4|MOD1|CONTROL,XK_j, resize_y, {.i = +10}}, 130 | { MOD4|MOD1|CONTROL,XK_k, resize_y, {.i = -10}}, 131 | { MOD4|MOD1|CONTROL,XK_h, resize_x, {.i = -10}}, 132 | { MOD4|MOD1|CONTROL,XK_l, resize_x, {.i = +10}}, 133 | /* reset the selected floating window to tiling */ 134 | { MOD4, XK_t, tilemize, {NULL}}, 135 | 136 | /* mode selection */ 137 | { MOD4|SHIFT, XK_t, switch_mode, {.i = TILE}}, 138 | { MOD4|SHIFT, XK_m, switch_mode, {.i = MONOCLE}}, 139 | { MOD4|SHIFT, XK_b, switch_mode, {.i = BSTACK}}, 140 | { MOD4|SHIFT, XK_g, switch_mode, {.i = GRID}}, 141 | { MOD4|SHIFT, XK_f, switch_mode, {.i = FIBONACCI}}, 142 | { MOD4|SHIFT, XK_d, switch_mode, {.i = DUALSTACK}}, 143 | { MOD4|SHIFT, XK_e, switch_mode, {.i = EQUAL}}, 144 | { MOD4|SHIFT, XK_z, rotate_mode, {.i = -1}}, 145 | { MOD4|SHIFT, XK_x, rotate_mode, {.i = +1}}, 146 | 147 | /* spawn terminal, dmenu, w/e you want to */ 148 | { MOD4|SHIFT, XK_Return, spawn, {.com = termcmd}}, 149 | { MOD4, XK_r, spawn, {.com = menucmd}}, 150 | /* uncomment to run autostart script */ 151 | /* { MOD1, XK_r, spawn, SHCMD("$HOME/.frankenwm/autostart.sh")}, */ 152 | /* kill current window */ 153 | { MOD4|SHIFT, XK_c, killclient, {NULL}}, 154 | 155 | /* desktop selection */ 156 | DESKTOPCHANGE( XK_1, 0) 157 | DESKTOPCHANGE( XK_2, 1) 158 | DESKTOPCHANGE( XK_3, 2) 159 | DESKTOPCHANGE( XK_4, 3) 160 | DESKTOPCHANGE( XK_5, 4) 161 | DESKTOPCHANGE( XK_6, 5) 162 | DESKTOPCHANGE( XK_7, 6) 163 | DESKTOPCHANGE( XK_8, 7) 164 | DESKTOPCHANGE( XK_9, 8) 165 | DESKTOPCHANGE( XK_0, 9) 166 | /* toggle to last desktop */ 167 | { MOD4, XK_Tab, last_desktop, {NULL}}, 168 | /* jump to the next/previous desktop */ 169 | { MOD4|SHIFT, XK_h, rotate, {.i = -1}}, 170 | { MOD4|SHIFT, XK_l, rotate, {.i = +1}}, 171 | /* jump to the next/previous desktop with just the current window */ 172 | { MOD4|CONTROL, XK_h, rotate_client, {.i = -1}}, 173 | { MOD4|CONTROL, XK_l, rotate_client, {.i = +1}}, 174 | /* jump to the next/previous desktop with all windows */ 175 | { MOD4|CONTROL|SHIFT, XK_h, rotate_filled, {.i = -1}}, 176 | { MOD4|CONTROL|SHIFT, XK_l, rotate_filled, {.i = +1}}, 177 | 178 | /* resize master/first stack window */ 179 | { MOD4, XK_h, resize_master, {.i = -10}}, 180 | { MOD4, XK_l, resize_master, {.i = +10}}, 181 | { MOD4, XK_o, resize_stack, {.i = -10}}, 182 | { MOD4, XK_p, resize_stack, {.i = +10}}, 183 | 184 | /* resize the borders */ 185 | { MOD4|CONTROL, XK_u, adjust_borders, {.i = -1}}, 186 | { MOD4|CONTROL, XK_i, adjust_borders, {.i = +1}}, 187 | /* resize the useless gaps between the windows */ 188 | { MOD4|CONTROL, XK_o, adjust_gaps, {.i = -1}}, 189 | { MOD4|CONTROL, XK_p, adjust_gaps, {.i = +1}}, 190 | /* toggle the panel space */ 191 | { MOD4|CONTROL, XK_b, togglepanel, {NULL}}, 192 | 193 | /* exit */ 194 | { MOD4|CONTROL, XK_q, quit, {.i = 0}}, 195 | }; 196 | 197 | /* EDIT THIS: mouse-based shortcuts */ 198 | static Button buttons[] = { 199 | /* move/resize using the mouse */ 200 | { MOD4, Button1, mousemotion, {.i = MOVE}}, 201 | { MOD4, Button3, mousemotion, {.i = RESIZE}}, 202 | }; 203 | #endif 204 | 205 | -------------------------------------------------------------------------------- /frankenwm.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 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | /* compile with -DDEBUGGING for debugging output */ 23 | #ifdef DEBUGGING 24 | # define DEBUG(x) fprintf(stderr, "%s\n", x); 25 | # define DEBUGP(x, ...) fprintf(stderr, x, ##__VA_ARGS__); 26 | #else 27 | # define DEBUG(x); 28 | # define DEBUGP(x, ...); 29 | #endif 30 | 31 | /* upstream compatility */ 32 | #define XCB_MOVE_RESIZE XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT 33 | #define XCB_MOVE XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y 34 | #define XCB_RESIZE XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT 35 | 36 | static char *WM_NAME = "FrankenWM"; 37 | static char *WM_ATOM_NAME[] = { "WM_PROTOCOLS", "WM_DELETE_WINDOW", "WM_STATE", "WM_TAKE_FOCUS" }; 38 | enum { WM_PROTOCOLS, WM_DELETE_WINDOW, WM_STATE, WM_TAKE_FOCUS, WM_COUNT }; 39 | enum { _NET_WM_STATE_REMOVE, _NET_WM_STATE_ADD, _NET_WM_STATE_TOGGLE }; 40 | 41 | #define LENGTH(x) (sizeof(x)/sizeof(*x)) 42 | #define CLEANMASK(mask) (mask & ~(numlockmask | XCB_MOD_MASK_LOCK)) 43 | #define BUTTONMASK XCB_EVENT_MASK_BUTTON_PRESS|XCB_EVENT_MASK_BUTTON_RELEASE 44 | #define ISFMFTM(c) (c->isfullscreen || c->ismaximized || c->isfloating || c->istransient || c->isminimized || c->type != ewmh->_NET_WM_WINDOW_TYPE_NORMAL) 45 | #define USAGE "usage: frankenwm [-h] [-v]" 46 | /* future enhancements */ 47 | #define MONITORS 1 48 | 49 | enum { RESIZE, MOVE }; 50 | enum { TILE, MONOCLE, BSTACK, GRID, FIBONACCI, DUALSTACK, EQUAL, MODES }; 51 | 52 | /* argument structure to be passed to function by config.h 53 | * com - a command to run 54 | * i - an integer to indicate different states 55 | */ 56 | typedef union { 57 | const char **com; 58 | const int i; 59 | } Arg; 60 | 61 | struct list { 62 | struct node *head; 63 | struct node *tail; 64 | void *master; /* backpointer to the list's owner */ 65 | }; 66 | typedef struct list list; 67 | 68 | struct node { 69 | struct node *prev; 70 | struct node *next; 71 | struct list *parent; 72 | }; 73 | typedef struct node node; 74 | 75 | typedef struct { 76 | int previous_x, previous_y; 77 | int current_x, current_y; 78 | } posxy_t; 79 | 80 | /* 81 | * aliens are unmanaged & selfmapped windows, ie dunst notifications. 82 | * They are always on top of all other windows. 83 | */ 84 | struct alien { 85 | node link; 86 | xcb_window_t win; 87 | xcb_atom_t type; 88 | posxy_t position_info; 89 | }; 90 | typedef struct alien alien; 91 | 92 | /* a key struct represents a combination of 93 | * mod - a modifier mask 94 | * keysym - and the key pressed 95 | * func - the function to be triggered because of the above combo 96 | * arg - the argument to the function 97 | */ 98 | typedef struct { 99 | unsigned int mod; 100 | xcb_keysym_t keysym; 101 | void (*func)(const Arg *); 102 | const Arg arg; 103 | } key; 104 | 105 | /* a button struct represents a combination of 106 | * mask - a modifier mask 107 | * button - and the mouse button pressed 108 | * func - the function to be triggered because of the above combo 109 | * arg - the argument to the function 110 | */ 111 | typedef struct { 112 | unsigned int mask, button; 113 | void (*func)(const Arg *); 114 | const Arg arg; 115 | } Button; 116 | 117 | /* a client is a wrapper to a window that additionally 118 | * holds some properties for that window 119 | * 120 | * link - doubly linked list node 121 | * isurgent - set when the window received an urgent hint 122 | * istransient - set when the window is transient 123 | * isfullscreen - set when the window is fullscreen (not maximized) 124 | * ismaximized - set when the window is maximized (not fullscreen) 125 | * isfloating - set when the window is floating 126 | * win - the window this client is representing 127 | * type - the _NET_WM_WINDOW_TYPE 128 | * dim - the window dimensions when floating 129 | * borderwidth - the border width if not using the global one 130 | * setfocus - True: focus directly, else send wm_take_focus 131 | * 132 | * istransient is separate from isfloating as floating window can be reset 133 | * to their tiling positions, while the transients will always be floating 134 | */ 135 | /* 136 | * Developer reminder: do not forget to update create_client(); 137 | */ 138 | typedef struct { 139 | node link; /* must be first */ 140 | bool isurgent, istransient, isfloating, isfullscreen, ismaximized, isminimized; 141 | xcb_window_t win; 142 | xcb_atom_t type; 143 | unsigned int dim[2]; 144 | posxy_t position_info; 145 | int borderwidth; 146 | bool setfocus; 147 | } client; 148 | 149 | /* properties of each desktop 150 | * current - the currently highlighted window 151 | * prevfocus - the client that previously had focus 152 | * mode - the desktop's tiling layout mode 153 | * growth - growth factor of the first stack window 154 | * master_size - the size of the master window 155 | * showpanel - the visibility status of the panel 156 | */ 157 | typedef struct { 158 | float master_size; 159 | int mode, growth, gaps; 160 | bool showpanel, invert; 161 | } displayinfo; 162 | #define M_MASTER_SIZE (current_display->di.master_size) 163 | #define M_MODE (current_display->di.mode) 164 | #define M_GROWTH (current_display->di.growth) 165 | #define M_GAPS (current_display->di.gaps) 166 | #define M_SHOWPANEL (current_display->di.showpanel) 167 | #define M_INVERT (current_display->di.invert) 168 | 169 | /* properties of each display 170 | * current - the currently highlighted window 171 | */ 172 | typedef struct { 173 | node link; /* must be first */ 174 | list clients; /* must be second */ 175 | client *current, *prevfocus; 176 | list miniq; 177 | displayinfo di; 178 | } display; 179 | #define M_CURRENT (current_display->current) 180 | #define M_PREVFOCUS (current_display->prevfocus) 181 | 182 | typedef struct { 183 | node link; /* must be first */ 184 | list displays; /* must be second */ 185 | unsigned int num; 186 | int ww, wh; 187 | int wy; 188 | } monitor; 189 | 190 | /* desktop */ 191 | typedef struct { 192 | node link; /* must be first */ 193 | list monitors; /* must be second */ 194 | unsigned int num; 195 | } desktop; 196 | 197 | /* lifo for minimized clients */ 198 | typedef struct lifo { 199 | node link; /* must be first */ 200 | client *c; 201 | } lifo; 202 | 203 | /* define behavior of certain applications 204 | * configured in config.h 205 | * class - the class of the window 206 | * instance - the instance of the window 207 | * desktop - what desktop it should be spawned at 208 | * follow - whether to change desktop focus to the specified desktop 209 | */ 210 | typedef struct { 211 | const char *class; 212 | const char *instance; 213 | const int desktop; 214 | const bool follow, floating; 215 | const int border_width; 216 | } AppRule; 217 | 218 | /* function prototypes sorted alphabetically */ 219 | static client *addwindow(xcb_window_t w, xcb_atom_t wtype); 220 | static void adjust_borders(const Arg *arg); 221 | static void adjust_gaps(const Arg *arg); 222 | static void buttonpress(xcb_generic_event_t *e); 223 | static void change_desktop(const Arg *arg); 224 | static bool check_if_window_is_alien(xcb_window_t win, bool *isFloating, xcb_atom_t *wtype); 225 | static bool check_wmproto(xcb_window_t win, xcb_atom_t proto); 226 | static void centerfloating(client *c); 227 | static void centerwindow(); 228 | static void cleanup(void); 229 | static void cleanup_display(void); 230 | static int client_borders(const client *c); 231 | static void client_to_desktop(const Arg *arg); 232 | static void clientmessage(xcb_generic_event_t *e); 233 | static void configurerequest(xcb_generic_event_t *e); 234 | static inline alien *create_alien(xcb_window_t win, xcb_atom_t atom); 235 | static client *create_client(xcb_window_t win, xcb_atom_t wtype); 236 | static bool deletewindow(xcb_window_t w); 237 | static void desktopinfo(void); 238 | static void destroynotify(xcb_generic_event_t *e); 239 | static void dualstack(int hh, int cy); 240 | static void enternotify(xcb_generic_event_t *e); 241 | static void equal(int h, int y); 242 | static void fibonacci(int h, int y); 243 | static client *find_client(xcb_window_t w); 244 | static desktop *find_desktop(unsigned int n); 245 | /* static monitor *find_monitor(unsigned int n); future enhancement */ 246 | static void float_client(client *c); 247 | static void float_x(const Arg *arg); 248 | static void float_y(const Arg *arg); 249 | static void focusmaster(); 250 | static void focusurgent(); 251 | static unsigned int getcolor(char *color); 252 | static void grabbuttons(client *c); 253 | static void grabkeys(void); 254 | static void grid(int h, int y); 255 | static void invertstack(); 256 | static void keypress(xcb_generic_event_t *e); 257 | static void killclient(); 258 | static void last_desktop(); 259 | static void mapnotify(xcb_generic_event_t *e); 260 | static void maprequest(xcb_generic_event_t *e); 261 | static void maximize(); 262 | static void minimize_client(client *c); 263 | static void minimize(); 264 | static void monocle(int h, int y); 265 | static void move_down(); 266 | static void move_up(); 267 | static void mousemotion(const Arg *arg); 268 | static void next_win(); 269 | static void prev_win(); 270 | static void propertynotify(xcb_generic_event_t *e); 271 | static void quit(const Arg *arg); 272 | static void removeclient(client *c); 273 | static void resize_master(const Arg *arg); 274 | static void resize_stack(const Arg *arg); 275 | static void resize_x(const Arg *arg); 276 | static void resize_y(const Arg *arg); 277 | static void restore_client(client *c); 278 | static void restore(); 279 | static bool desktop_populated(desktop *d); 280 | static void rotate(const Arg *arg); 281 | static void rotate_client(const Arg *arg); 282 | static void rotate_filled(const Arg *arg); 283 | static void rotate_mode(const Arg *arg); 284 | static void run(void); 285 | static void select_desktop(int i); 286 | static bool sendevent(xcb_window_t win, xcb_atom_t proto); 287 | static void setmaximize(client *c, bool fullscrn); 288 | void setfullscreen(client *c, bool fullscrn); 289 | static int setup(int default_screen); 290 | static void setup_display(void); 291 | static void setwindefattr(xcb_window_t w); 292 | static void showhide(); 293 | static void sigchld(); 294 | static void spawn(const Arg *arg); 295 | static void stack(int h, int y); 296 | static void swap_master(); 297 | static void switch_mode(const Arg *arg); 298 | static void tile(void); 299 | static void tilemize(); 300 | static void togglepanel(); 301 | static void unfloat_client(client *c); 302 | static void togglescratchpad(); 303 | static void update_current(client *c); 304 | static void unmapnotify(xcb_generic_event_t *e); 305 | static void xerror(xcb_generic_event_t *e); 306 | static alien *wintoalien(list *l, xcb_window_t win); 307 | static client *wintoclient(xcb_window_t w); 308 | 309 | #include "config.h" 310 | 311 | #ifdef EWMH_TASKBAR 312 | typedef struct 313 | { 314 | uint16_t left, right, top, bottom; 315 | // uint16_t left_start_y, left_end_y; 316 | // uint16_t right_start_y, right_end_y; 317 | // uint16_t top_start_x, top_end_x; 318 | // uint16_t bottom_start_x, bottom_end_x; 319 | } strut_t; 320 | 321 | static void Setup_EWMH_Taskbar_Support(void); 322 | static void Cleanup_EWMH_Taskbar_Support(void); 323 | static inline void Update_EWMH_Taskbar_Properties(void); 324 | static void Setup_Global_Strut(void); 325 | static void Cleanup_Global_Strut(void); 326 | static inline void Reset_Global_Strut(void); 327 | static void Update_Global_Strut(void); 328 | 329 | static strut_t gstrut; 330 | #endif /* EWMH_TASKBAR */ 331 | 332 | /* variables */ 333 | static bool running = true, show = true, showscratchpad = false; 334 | static int default_screen, previous_desktop, current_desktop_number, retval; 335 | static int borders; 336 | static unsigned int numlockmask, win_unfocus, win_focus, win_scratch; 337 | static xcb_connection_t *dis; 338 | static xcb_screen_t *screen; 339 | static uint32_t checkwin; 340 | static xcb_atom_t scrpd_atom; 341 | static client *scrpd = NULL; 342 | static list desktops; 343 | static list aliens; 344 | 345 | static desktop *current_desktop = NULL; 346 | 347 | static monitor *current_monitor = NULL; 348 | #define M_WW (current_monitor->ww) 349 | #define M_WH (current_monitor->wh) 350 | #define M_WY (current_monitor->wy) 351 | 352 | static display *current_display = NULL; 353 | #define M_HEAD ((client *)(current_display->clients.head)) 354 | #define M_TAIL ((client *)(current_display->clients.tail)) 355 | 356 | static xcb_ewmh_connection_t *ewmh; 357 | static xcb_atom_t wmatoms[WM_COUNT]; 358 | static regex_t classruleregex[LENGTH(rules)]; 359 | static regex_t instanceruleregex[LENGTH(rules)]; 360 | static xcb_key_symbols_t *keysyms; 361 | 362 | /* events array 363 | * on receival of a new event, call the appropriate function to handle it 364 | */ 365 | static void (*events[XCB_NO_OPERATION])(xcb_generic_event_t *e); 366 | 367 | /* layout array - given the current layout mode, tile the windows 368 | * h (or hh) - avaible height that windows have to expand 369 | * y (or cy) - offset from top to place the windows (reserved by the panel) */ 370 | static void (*layout[MODES])(int h, int y) = { 371 | [TILE] = stack, 372 | [BSTACK] = stack, 373 | [GRID] = grid, 374 | [MONOCLE] = monocle, 375 | [FIBONACCI] = fibonacci, 376 | [DUALSTACK] = dualstack, 377 | [EQUAL] = equal, 378 | }; 379 | 380 | /* 381 | * lowlevel doubly linked list functions 382 | */ 383 | 384 | static node *rem_node(node *n) 385 | { 386 | list *l; 387 | 388 | if (!n) 389 | return NULL; 390 | l = n->parent; 391 | if (l) { 392 | if (n == l->head) { 393 | l->head = l->head->next; 394 | if(l->head) 395 | l->head->prev = NULL; 396 | else 397 | l->tail = NULL; 398 | } 399 | else if (n == l->tail) { 400 | l->tail = l->tail->prev; 401 | l->tail->next = NULL; 402 | } 403 | else { 404 | n->prev->next = n->next; 405 | n->next->prev = n->prev; 406 | } 407 | } 408 | n->prev = n->next = NULL; 409 | n->parent = NULL; 410 | return n; 411 | } 412 | 413 | static void add_head(list *l, node *i) 414 | { 415 | node *o = l->head; 416 | if (o == NULL) { 417 | l->head = i; 418 | l->tail = i; 419 | } 420 | else { 421 | l->head = i; 422 | i->prev = NULL; 423 | i->next = o; 424 | o->prev = i; 425 | } 426 | i->parent = l; 427 | } 428 | 429 | static void add_tail(list *l, node *i) 430 | { 431 | if(l->head == NULL) 432 | add_head(l, i); 433 | else { 434 | node *o = l->tail; 435 | l->tail = i; 436 | o->next = i; 437 | i->prev = o; 438 | i->next = NULL; 439 | } 440 | i->parent = l; 441 | } 442 | 443 | static void insert_node_after(list *l, node *c, node *i) 444 | { 445 | node *n; 446 | if (!c || !(n = c->next)) 447 | add_tail(l, i); 448 | else { 449 | c->next = i; 450 | i->prev = c; 451 | i->next = n; 452 | n->prev = i; 453 | } 454 | i->parent = l; 455 | } 456 | 457 | static void insert_node_before(list *l, node *c, node *i) 458 | { 459 | node *p; 460 | if (!c || !(p = c->prev)) 461 | add_head(l, i); 462 | else { 463 | p->next = i; 464 | i->prev = p; 465 | i->next = c; 466 | c->prev = i; 467 | } 468 | i->parent = l; 469 | } 470 | 471 | /* 472 | * glue functions for doubly linked stuff 473 | */ 474 | static inline bool check_head(list *l) { return (l && l->head) ? True : False; } 475 | 476 | static inline node *get_head(list *l) { return (l) ? l->head : NULL; } 477 | 478 | static inline node *rem_head(list *l) { return (l) ? rem_node(l->head) : NULL; } 479 | 480 | static inline node *get_tail(list *l) { return (l) ? l->tail : NULL; } 481 | 482 | static inline node *get_next(node *n) { return (n) ? n->next : NULL; } 483 | 484 | static inline node *get_prev(node *n) { return (n) ? n->prev : NULL; } 485 | 486 | static inline node *get_node_head(node *n) { return (n && n->parent) ? n->parent->head : NULL; } 487 | 488 | #define M_GETNEXT(c) ((client *)get_next(&c->link)) 489 | #define M_GETPREV(c) ((client *)get_prev(&c->link)) 490 | 491 | /* 492 | * Add an atom to a list of atoms the given property defines. 493 | * This is useful, for example, for manipulating _NET_WM_STATE. 494 | * 495 | */ 496 | void xcb_add_property(xcb_connection_t *con, xcb_window_t win, xcb_atom_t prop, xcb_atom_t atom) 497 | { 498 | xcb_change_property(con, XCB_PROP_MODE_APPEND, win, prop, XCB_ATOM_ATOM, 32, 1, (uint32_t[]){atom}); 499 | } 500 | 501 | /* 502 | * Remove an atom from a list of atoms the given property defines without 503 | * removing any other potentially set atoms. This is useful, for example, for 504 | * manipulating _NET_WM_STATE. 505 | * 506 | */ 507 | void xcb_remove_property(xcb_connection_t *con, xcb_window_t win, xcb_atom_t prop, xcb_atom_t atom) 508 | { 509 | xcb_grab_server(con); 510 | 511 | xcb_get_property_reply_t *reply = 512 | xcb_get_property_reply(con, 513 | xcb_get_property(con, False, win, prop, XCB_GET_PROPERTY_TYPE_ANY, 0, 4096), NULL); 514 | if (reply == NULL || xcb_get_property_value_length(reply) == 0) 515 | goto release_grab; 516 | xcb_atom_t *atoms = xcb_get_property_value(reply); 517 | if (atoms == NULL) { 518 | goto release_grab; 519 | } 520 | 521 | { 522 | int num = 0; 523 | const int current_size = xcb_get_property_value_length(reply) / (reply->format / 8); 524 | xcb_atom_t values[current_size]; 525 | for (int i = 0; i < current_size; i++) { 526 | if (atoms[i] != atom) 527 | values[num++] = atoms[i]; 528 | } 529 | 530 | xcb_change_property(con, XCB_PROP_MODE_REPLACE, win, prop, XCB_ATOM_ATOM, 32, num, values); 531 | } 532 | 533 | release_grab: 534 | if (reply) 535 | free(reply); 536 | xcb_ungrab_server(con); 537 | } 538 | 539 | static bool xcb_check_attribute(xcb_connection_t *con, xcb_window_t win, xcb_atom_t atom) 540 | { 541 | xcb_get_property_reply_t *prop_reply; 542 | if ((prop_reply = xcb_get_property_reply(con, xcb_get_property(dis, 0, win, atom, 543 | XCB_GET_PROPERTY_TYPE_ANY, 0, 0), NULL))) { 544 | xcb_atom_t reply_type = prop_reply->type; 545 | free(prop_reply); 546 | if (reply_type != XCB_NONE) 547 | return True; 548 | } 549 | return False; 550 | } 551 | 552 | /* get screen of display */ 553 | static xcb_screen_t *xcb_screen_of_display(xcb_connection_t *con, int screen) 554 | { 555 | xcb_screen_iterator_t iter; 556 | 557 | iter = xcb_setup_roots_iterator(xcb_get_setup(con)); 558 | for (; iter.rem; --screen, xcb_screen_next(&iter)) 559 | if (screen == 0) 560 | return iter.data; 561 | 562 | return NULL; 563 | } 564 | 565 | /* wrapper to intern atom */ 566 | static inline xcb_atom_t xcb_internatom(xcb_connection_t *con, char *name, uint8_t only_if_exists) 567 | { 568 | xcb_atom_t atom; 569 | xcb_intern_atom_cookie_t cookie; 570 | xcb_intern_atom_reply_t *reply; 571 | 572 | atom = 0; 573 | cookie = xcb_intern_atom(con, only_if_exists, strlen(name), name); 574 | reply = xcb_intern_atom_reply(con, cookie, NULL); 575 | if (reply) { 576 | atom = reply->atom; 577 | free(reply); 578 | } 579 | /* TODO: Handle error */ 580 | 581 | return atom; // may be zero 582 | } 583 | 584 | /* wrapper to move and resize window */ 585 | static inline void xcb_move_resize(xcb_connection_t *con, xcb_window_t win, int x, int y, int w, int h, posxy_t *pi) 586 | { 587 | unsigned int pos[4] = { x, y, w, h }; 588 | 589 | if (pi) { 590 | pi->previous_x = pi->current_x; 591 | pi->previous_y = pi->current_y; 592 | pi->current_x = x; 593 | pi->current_y = y; 594 | } 595 | xcb_configure_window(con, win, XCB_MOVE_RESIZE, pos); 596 | } 597 | 598 | /* wrapper to move window */ 599 | static inline void xcb_move(xcb_connection_t *con, xcb_window_t win, int x, int y, posxy_t *pi) 600 | { 601 | unsigned int pos[2] = { x, y }; 602 | 603 | if (pi) { 604 | pi->previous_x = pi->current_x; 605 | pi->previous_y = pi->current_y; 606 | pi->current_x = x; 607 | pi->current_y = y; 608 | } 609 | xcb_configure_window(con, win, XCB_MOVE, pos); 610 | } 611 | 612 | /* wrapper to resize window */ 613 | static inline void xcb_resize(xcb_connection_t *con, xcb_window_t win, int w, 614 | int h) 615 | { 616 | unsigned int pos[2] = { w, h }; 617 | 618 | xcb_configure_window(con, win, XCB_RESIZE, pos); 619 | } 620 | 621 | /* wrapper to raise window */ 622 | static inline void xcb_raise_window(xcb_connection_t *con, xcb_window_t win) 623 | { 624 | unsigned int arg[1] = { XCB_STACK_MODE_ABOVE }; 625 | 626 | xcb_configure_window(con, win, XCB_CONFIG_WINDOW_STACK_MODE, arg); 627 | } 628 | 629 | /* wrapper to lower window */ 630 | static inline void xcb_lower_window(xcb_connection_t *con, xcb_window_t win) 631 | { 632 | unsigned int arg[1] = { XCB_STACK_MODE_BELOW }; 633 | 634 | xcb_configure_window(con, win, XCB_CONFIG_WINDOW_STACK_MODE, arg); 635 | } 636 | 637 | /* wrapper to set xcb border width */ 638 | static inline void xcb_border_width(xcb_connection_t *con, xcb_window_t win, 639 | int w) 640 | { 641 | unsigned int arg[1] = { w }; 642 | 643 | xcb_configure_window(con, win, XCB_CONFIG_WINDOW_BORDER_WIDTH, arg); 644 | } 645 | 646 | /* wrapper to get xcb keysymbol from keycode */ 647 | static xcb_keysym_t xcb_get_keysym(xcb_keycode_t keycode) 648 | { 649 | xcb_keysym_t keysym; 650 | keysym = xcb_key_symbols_get_keysym(keysyms, keycode, 0); 651 | return keysym; 652 | } 653 | 654 | /* wrapper to get xcb keycodes from keysymbol (caller must free) */ 655 | static xcb_keycode_t *xcb_get_keycodes(xcb_keysym_t keysym) 656 | { 657 | xcb_keycode_t *keycode; 658 | keycode = xcb_key_symbols_get_keycode(keysyms, keysym); 659 | return keycode; 660 | } 661 | 662 | /* retieve RGB color from hex (think of html) */ 663 | static unsigned int xcb_get_colorpixel(char *hex) 664 | { 665 | char strgroups[3][3] = {{hex[1], hex[2], '\0'}, 666 | {hex[3], hex[4], '\0'}, 667 | {hex[5], hex[6], '\0'}}; 668 | unsigned int rgb16[3] = {(strtol(strgroups[0], NULL, 16)), 669 | (strtol(strgroups[1], NULL, 16)), 670 | (strtol(strgroups[2], NULL, 16))}; 671 | 672 | return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2]; 673 | } 674 | 675 | /* wrapper to get atoms using xcb */ 676 | static void xcb_get_atoms(char **names, xcb_atom_t *atoms, unsigned int count) 677 | { 678 | xcb_intern_atom_cookie_t cookies[count]; 679 | xcb_intern_atom_reply_t *reply; 680 | 681 | for (unsigned int i = 0; i < count; i++) 682 | cookies[i] = xcb_intern_atom(dis, 0, strlen(names[i]), names[i]); 683 | 684 | for (unsigned int i = 0; i < count; i++) { 685 | reply = xcb_intern_atom_reply(dis, cookies[i], NULL); 686 | if (!reply) 687 | errx(EXIT_FAILURE, "failed to register %s atom", names[i]); 688 | DEBUGP("%s : %d\n", names[i], reply->atom); 689 | atoms[i] = reply->atom; free(reply); 690 | } 691 | } 692 | 693 | /* wrapper to window get attributes using xcb */ 694 | static void xcb_get_attributes(xcb_window_t *windows, 695 | xcb_get_window_attributes_reply_t **reply, 696 | unsigned int count) 697 | { 698 | xcb_get_window_attributes_cookie_t cookies[count]; 699 | 700 | for (unsigned int i = 0; i < count; i++) 701 | cookies[i] = xcb_get_window_attributes(dis, windows[i]); 702 | for (unsigned int i = 0; i < count; i++) 703 | reply[i] = xcb_get_window_attributes_reply(dis, cookies[i], NULL); 704 | /* TODO: Handle error */ 705 | } 706 | 707 | /* wrapper to get window geometry */ 708 | static inline xcb_get_geometry_reply_t *get_geometry(xcb_window_t win) 709 | { 710 | xcb_get_geometry_reply_t *r; 711 | r = xcb_get_geometry_reply(dis, xcb_get_geometry(dis, win), NULL); 712 | if (!r) 713 | errx(EXIT_FAILURE, "failed to get geometry for window %i", win); 714 | return r; 715 | } 716 | 717 | /* check if other wm exists */ 718 | static int xcb_checkotherwm(void) 719 | { 720 | xcb_generic_error_t *error; 721 | unsigned int values[1] = {XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT| 722 | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY| 723 | XCB_EVENT_MASK_PROPERTY_CHANGE| 724 | XCB_EVENT_MASK_BUTTON_PRESS}; 725 | 726 | error = xcb_request_check(dis, xcb_change_window_attributes_checked(dis, 727 | screen->root, XCB_CW_EVENT_MASK, values)); 728 | if (error) 729 | return 1; 730 | return 0; 731 | } 732 | 733 | /* 734 | static bool window_is_override_redirect(xcb_window_t win) 735 | { 736 | xcb_get_window_attributes_reply_t *attr[1]; 737 | xcb_window_t windows[] = {win}; 738 | bool override = True; 739 | 740 | xcb_get_attributes(windows, attr, 1); 741 | if (attr[0]) { 742 | if (!attr[0]->override_redirect) 743 | override = False; 744 | free(attr[0]); 745 | } 746 | return override; 747 | } 748 | */ 749 | 750 | /* find client in current_display by window id */ 751 | static client *find_client(xcb_window_t w) 752 | { 753 | client *c; 754 | for (c = (client *)get_head(¤t_display->clients); 755 | c && c->win != w; c = (client *)get_next(&c->link)) ; 756 | return c; 757 | } 758 | 759 | /* find desktop by number */ 760 | static desktop *find_desktop(unsigned int n) 761 | { 762 | desktop *d; 763 | for (d = (desktop *)get_head(&desktops); 764 | d && d->num != n; d = (desktop *)get_next(&d->link)) ; 765 | return d; 766 | } 767 | 768 | /* find monitor in current_desktop by number */ 769 | /* 770 | static monitor *find_monitor(unsigned int n) 771 | { 772 | monitor *m; 773 | for (m = (monitor *)get_head(¤t_desktop->monitors); 774 | m && m->num != n; m = (monitor *)get_next(&m->link)) ; 775 | return m; 776 | } 777 | */ 778 | 779 | static void getparents(client *c, display **di, monitor **mo, desktop **de) 780 | { 781 | display *disp; 782 | monitor *moni; 783 | desktop *desk; 784 | list *l; 785 | 786 | /* backpointer mambo-jambo */ 787 | if (!c) return; 788 | l = c->link.parent; 789 | disp = l->master; 790 | l = disp->link.parent; 791 | moni = l->master; 792 | l = moni->link.parent; 793 | desk = l->master; 794 | 795 | if (di) 796 | *di = disp; 797 | if (mo) 798 | *mo = moni; 799 | if (de) 800 | *de = desk; 801 | } 802 | 803 | /* create a new client and add the new window 804 | * window should notify of property change events 805 | */ 806 | client *addwindow(xcb_window_t win, xcb_atom_t wtype) 807 | { 808 | client *c = create_client(win, wtype); 809 | 810 | /* c is valid, else we would not get here */ 811 | if (!check_head(¤t_display->clients)) { 812 | add_head(¤t_display->clients, &c->link); 813 | } 814 | else { 815 | if (!ATTACH_ASIDE) 816 | add_head(¤t_display->clients, &c->link); 817 | else 818 | add_tail(¤t_display->clients, &c->link); 819 | } 820 | DEBUG("client added"); 821 | setwindefattr(win); 822 | return c; 823 | } 824 | 825 | /* change the size of the window borders */ 826 | void adjust_borders(const Arg *arg) 827 | { 828 | if (arg->i > 0 || borders >= -arg->i) 829 | borders += arg->i; 830 | tile(); 831 | update_current(M_CURRENT); 832 | } 833 | 834 | /* change the size of the useless gaps on the fly and re-tile */ 835 | void adjust_gaps(const Arg *arg) 836 | { 837 | int gaps = M_GAPS; 838 | 839 | if (arg->i > 0 || gaps >= -arg->i) 840 | gaps += arg->i; 841 | else 842 | return; 843 | 844 | if (GLOBALGAPS) { 845 | desktop *desk; 846 | for (desk = (desktop *)get_head(&desktops); desk; desk = (desktop *)get_next(&desk->link)) { 847 | monitor *moni; 848 | for (moni = (monitor *)get_head(&desk->monitors); moni; moni = (monitor *)get_next(&moni->link)) { 849 | display *disp; 850 | for (disp = (display *)get_head(&moni->displays); disp; disp = (display *)get_next(&disp->link)) { 851 | disp->di.gaps = gaps; 852 | } 853 | } 854 | } 855 | } 856 | else 857 | M_GAPS = gaps; 858 | 859 | tile(); 860 | } 861 | 862 | /* on the press of a button check to see if there's a binded function to call */ 863 | void buttonpress(xcb_generic_event_t *e) 864 | { 865 | xcb_button_press_event_t *ev = (xcb_button_press_event_t *)e; 866 | DEBUGP("xcb: button press: %d state: %d\n", ev->detail, ev->state); 867 | 868 | client *c = wintoclient(ev->event); 869 | if (!c) { 870 | if(USE_SCRATCHPAD && showscratchpad && scrpd && ev->event == scrpd->win) 871 | c = scrpd; 872 | else 873 | return; 874 | } 875 | 876 | if (CLICK_TO_FOCUS && M_CURRENT != c && ev->detail == XCB_BUTTON_INDEX_1) 877 | update_current(c); 878 | 879 | if (c != scrpd) { 880 | for (unsigned int i = 0; i < LENGTH(buttons); i++) 881 | if (buttons[i].func && buttons[i].button == ev->detail && 882 | CLEANMASK(buttons[i].mask) == CLEANMASK(ev->state)) { 883 | if (M_CURRENT != c) 884 | update_current(c); 885 | buttons[i].func(&(buttons[i].arg)); 886 | } 887 | } 888 | 889 | if (CLICK_TO_FOCUS) { 890 | xcb_allow_events(dis, XCB_ALLOW_REPLAY_POINTER, ev->time); 891 | xcb_flush(dis); 892 | } 893 | } 894 | 895 | /* focus another desktop 896 | * 897 | * to avoid flickering 898 | * first map the new windows 899 | * first the current window and then all other 900 | * then unmap the old windows 901 | * first all others then the current */ 902 | void change_desktop(const Arg *arg) 903 | { 904 | if (arg->i == current_desktop_number || arg->i > DESKTOPS-1) 905 | return; 906 | previous_desktop = current_desktop_number; 907 | select_desktop(arg->i); 908 | if (show) { 909 | if (M_CURRENT && M_CURRENT != scrpd) 910 | xcb_move(dis, M_CURRENT->win, M_CURRENT->position_info.previous_x, M_CURRENT->position_info.previous_y, &M_CURRENT->position_info); 911 | for (client *c = M_HEAD; c; c = M_GETNEXT(c)) { 912 | if (c != M_CURRENT) 913 | xcb_move(dis, c->win, c->position_info.previous_x, c->position_info.previous_y, &c->position_info); 914 | } 915 | } 916 | select_desktop(previous_desktop); 917 | for (client *c = M_HEAD; c; c = M_GETNEXT(c)) { 918 | if (c != M_CURRENT) 919 | xcb_move(dis, c->win, -2 * M_WW, 0, &c->position_info); 920 | } 921 | if (M_CURRENT && M_CURRENT != scrpd) 922 | xcb_move(dis, M_CURRENT->win, -2 * M_WW, 0, &M_CURRENT->position_info); 923 | select_desktop(arg->i); 924 | update_current(M_CURRENT); 925 | desktopinfo(); 926 | xcb_ewmh_set_current_desktop(ewmh, default_screen, arg->i); 927 | } 928 | 929 | static void print_window_type(xcb_window_t w, xcb_atom_t a) 930 | { 931 | char *s; 932 | 933 | if (a == ewmh->_NET_WM_WINDOW_TYPE_DESKTOP) s = "_NET_WM_WINDOW_TYPE_DESKTOP"; 934 | else if (a == ewmh->_NET_WM_WINDOW_TYPE_DOCK) s = "_NET_WM_WINDOW_TYPE_DOCK"; 935 | else if (a == ewmh->_NET_WM_WINDOW_TYPE_TOOLBAR) s = "_NET_WM_WINDOW_TYPE_TOOLBAR"; 936 | else if (a == ewmh->_NET_WM_WINDOW_TYPE_MENU) s = "_NET_WM_WINDOW_TYPE_MENU"; 937 | else if (a == ewmh->_NET_WM_WINDOW_TYPE_UTILITY) s = "_NET_WM_WINDOW_TYPE_UTILITY"; 938 | else if (a == ewmh->_NET_WM_WINDOW_TYPE_SPLASH) s = "_NET_WM_WINDOW_TYPE_SPLASH"; 939 | else if (a == ewmh->_NET_WM_WINDOW_TYPE_DIALOG) s = "_NET_WM_WINDOW_TYPE_DIALOG"; 940 | else if (a == ewmh->_NET_WM_WINDOW_TYPE_DROPDOWN_MENU) s = "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"; 941 | else if (a == ewmh->_NET_WM_WINDOW_TYPE_POPUP_MENU) s = "_NET_WM_WINDOW_TYPE_POPUP_MENU"; 942 | else if (a == ewmh->_NET_WM_WINDOW_TYPE_TOOLTIP) s = "_NET_WM_WINDOW_TYPE_TOOLTIP"; 943 | else if (a == ewmh->_NET_WM_WINDOW_TYPE_NOTIFICATION) s = "_NET_WM_WINDOW_TYPE_NOTIFICATION"; 944 | else if (a == ewmh->_NET_WM_WINDOW_TYPE_COMBO) s = "_NET_WM_WINDOW_TYPE_COMBO"; 945 | else if (a == ewmh->_NET_WM_WINDOW_TYPE_DND) s = "_NET_WM_WINDOW_TYPE_DND"; 946 | else if (a == ewmh->_NET_WM_WINDOW_TYPE_NORMAL) s = "_NET_WM_WINDOW_TYPE_NORMAL"; 947 | else s = "undefined window type"; 948 | 949 | if (w && s) { 950 | DEBUGP("window %x has type %s\n", w, s); 951 | } 952 | } 953 | 954 | /* 955 | * returns: 956 | * True if window is alien 957 | * False if window will be client 958 | */ 959 | static bool check_if_window_is_alien(xcb_window_t win, bool *isFloating, xcb_atom_t *wtype) 960 | { 961 | xcb_get_window_attributes_reply_t *attr[1]; 962 | xcb_window_t windows[] = {win}; 963 | bool isAlien = False; 964 | 965 | if (isFloating) *isFloating = False; 966 | if (wtype) *wtype = ewmh->_NET_WM_WINDOW_TYPE_NORMAL; 967 | 968 | xcb_get_attributes(windows, attr, 1); 969 | if (!attr[0]) /* dead on arrival */ 970 | return True; 971 | 972 | if (attr[0]->override_redirect) { 973 | free(attr[0]); 974 | return True; 975 | } 976 | else 977 | free(attr[0]); 978 | 979 | /* 980 | * check if window type is not _NET_WM_WINDOW_TYPE_NORMAL. 981 | * if yes, then we add it to alien list and map it. 982 | */ 983 | xcb_ewmh_get_atoms_reply_t type; 984 | xcb_atom_t atype = 0; 985 | if (xcb_ewmh_get_wm_window_type_reply(ewmh, 986 | xcb_ewmh_get_wm_window_type(ewmh, 987 | win), &type, NULL) == 1) { 988 | if (wtype) *wtype = type.atoms[0]; 989 | for (unsigned int i = 0; i < type.atoms_len; i++) { 990 | print_window_type(win, type.atoms[i]); 991 | if (type.atoms[i] == ewmh->_NET_WM_WINDOW_TYPE_NORMAL) { 992 | isAlien = False; 993 | break; 994 | } 995 | if (type.atoms[i] == ewmh->_NET_WM_WINDOW_TYPE_DIALOG) { 996 | if (wtype) *wtype = type.atoms[i]; 997 | if (isFloating) *isFloating = True; 998 | isAlien = False; 999 | break; 1000 | } 1001 | else { 1002 | if (atype == 0) 1003 | atype = type.atoms[i]; 1004 | isAlien = True; 1005 | } 1006 | } 1007 | xcb_ewmh_get_atoms_reply_wipe(&type); 1008 | } 1009 | if (isAlien) 1010 | create_alien(win, atype); 1011 | 1012 | return isAlien; 1013 | } 1014 | 1015 | /* 1016 | * place a floating client in the center of the screen 1017 | */ 1018 | static void centerfloating(client *c) 1019 | { 1020 | if (!c || !c->isfloating) 1021 | return; 1022 | 1023 | xcb_get_geometry_reply_t *wa = get_geometry(c->win); 1024 | xcb_raise_window(dis, c->win); 1025 | xcb_move(dis, c->win, ((M_WW - wa->width) / 2) - c->borderwidth, 1026 | ((M_WH - wa->height) / 2) - c->borderwidth, &c->position_info); 1027 | free(wa); 1028 | } 1029 | 1030 | /* 1031 | * centerfloating(); wrapper 1032 | */ 1033 | void centerwindow(void) 1034 | { 1035 | if (!M_CURRENT) 1036 | return; 1037 | 1038 | if (!M_CURRENT->isfloating 1039 | && !M_CURRENT->istransient) { 1040 | float_client(M_CURRENT); 1041 | tile(); 1042 | } 1043 | 1044 | centerfloating(M_CURRENT); 1045 | } 1046 | 1047 | /* remove all windows in all desktops by sending a delete message */ 1048 | void cleanup(void) 1049 | { 1050 | #ifdef EWMH_TASKBAR 1051 | Cleanup_Global_Strut(); 1052 | Cleanup_EWMH_Taskbar_Support(); 1053 | #endif /* EWMH_TASKBAR */ 1054 | 1055 | if(USE_SCRATCHPAD && scrpd) { 1056 | if(CLOSE_SCRATCHPAD) { 1057 | deletewindow(scrpd->win); 1058 | } 1059 | else { 1060 | xcb_border_width(dis, scrpd->win, 0); 1061 | xcb_get_geometry_reply_t *wa = get_geometry(scrpd->win); 1062 | xcb_move(dis, scrpd->win, (M_WW - wa->width) / 2, (M_WH - wa->height) / 2, &scrpd->position_info); 1063 | free(wa); 1064 | } 1065 | free(scrpd); 1066 | scrpd = NULL; 1067 | } 1068 | 1069 | xcb_ewmh_connection_wipe(ewmh); 1070 | 1071 | xcb_delete_property(dis, screen->root, ewmh->_NET_SUPPORTED); 1072 | xcb_destroy_window(dis, checkwin); 1073 | 1074 | free(ewmh); 1075 | 1076 | for (unsigned int i = 0; i < LENGTH(rules); i++) { 1077 | regfree(&classruleregex[i]); 1078 | regfree(&instanceruleregex[i]); 1079 | } 1080 | 1081 | cleanup_display(); 1082 | 1083 | alien *a; 1084 | while ((a = (alien *)rem_head(&aliens))) 1085 | free(a); 1086 | } 1087 | 1088 | static void cleanup_display(void) 1089 | { 1090 | desktop *desk; 1091 | for (desk = (desktop *)rem_head(&desktops); desk; desk = (desktop *)rem_head(&desktops)) { 1092 | monitor *moni; 1093 | for (moni = (monitor *)rem_head(&desk->monitors); moni; moni = (monitor *)rem_head(&desk->monitors)) { 1094 | display *disp; 1095 | for (disp = (display *)rem_head(&moni->displays); disp; disp = (display *)rem_head(&moni->displays)) { 1096 | client *c; 1097 | for (c = (client *)rem_head(&disp->clients); c; c = (client *)rem_head(&disp->clients)) { 1098 | xcb_border_width(dis, c->win, 0); 1099 | free(c); 1100 | } 1101 | for (struct lifo *l = (lifo *)rem_head(&disp->miniq); l; l = (lifo *)rem_head(&disp->miniq)) 1102 | free(l); 1103 | 1104 | free(disp); 1105 | } 1106 | free(moni); 1107 | } 1108 | free(desk); 1109 | } 1110 | } 1111 | 1112 | /* 1113 | * return the border width of a client for calculations 1114 | * 1115 | * this will just return the current global border width if this client does 1116 | * not use a custom one 1117 | */ 1118 | int client_borders(const client *c) 1119 | { 1120 | return c->borderwidth >= 0 ? c->borderwidth : borders; 1121 | } 1122 | 1123 | /* move a client to another desktop 1124 | * 1125 | * remove the current client from the current desktop's client list 1126 | * and add it as last client of the new desktop's client list */ 1127 | void client_to_desktop(const Arg *arg) 1128 | { 1129 | if (!M_CURRENT || arg->i == current_desktop_number || arg->i > DESKTOPS-1) 1130 | return; 1131 | int cd = current_desktop_number; 1132 | client *c = M_CURRENT; 1133 | 1134 | rem_node(&c->link); 1135 | select_desktop(arg->i); 1136 | add_tail(¤t_display->clients, &c->link); 1137 | select_desktop(cd); 1138 | xcb_move(dis, c->win, -2 * M_WW, 0, &c->position_info); 1139 | xcb_ewmh_set_wm_desktop(ewmh, c->win, arg->i); 1140 | 1141 | if (FOLLOW_WINDOW) 1142 | change_desktop(arg); 1143 | else 1144 | update_current(M_PREVFOCUS); 1145 | 1146 | desktopinfo(); 1147 | } 1148 | 1149 | /* 1150 | * Here we take and process client messages. Currently supported messages are: 1151 | * _NET_WM_STATE 1152 | * _NET_CURRENT_DESKTOP 1153 | * _NET_ACTIVE_WINDOW 1154 | * _NET_CLOSE_WINDOW 1155 | * 1156 | * data.data32[0] is the action to be taken 1157 | * data.data32[1] is the property to alter three actions: 1158 | * remove/unset _NET_WM_STATE_REMOVE=0 1159 | * add/set _NET_WM_STATE_ADD=1 1160 | * toggle _NET_WM_STATE_TOGGLE=2 1161 | */ 1162 | void clientmessage(xcb_generic_event_t *e) 1163 | { 1164 | xcb_client_message_event_t *ev = (xcb_client_message_event_t *)e; 1165 | client *c = wintoclient(ev->window); 1166 | 1167 | DEBUG("xcb: client message"); 1168 | 1169 | if (c && ev->type == ewmh->_NET_WM_STATE) { 1170 | if (((unsigned)ev->data.data32[1] == ewmh->_NET_WM_STATE_FULLSCREEN 1171 | || (unsigned)ev->data.data32[2] == ewmh->_NET_WM_STATE_FULLSCREEN)) { 1172 | uint32_t mode = ev->data.data32[0]; 1173 | 1174 | if (mode == _NET_WM_STATE_TOGGLE) 1175 | mode = (c->isfullscreen) ? _NET_WM_STATE_REMOVE : _NET_WM_STATE_ADD; 1176 | setfullscreen(c, mode == _NET_WM_STATE_ADD); 1177 | } 1178 | if (((unsigned)ev->data.data32[1] == ewmh->_NET_WM_STATE_HIDDEN 1179 | || (unsigned)ev->data.data32[2] == ewmh->_NET_WM_STATE_HIDDEN)) { 1180 | switch (ev->data.data32[0]) { 1181 | case _NET_WM_STATE_REMOVE: 1182 | restore_client(c); 1183 | break; 1184 | 1185 | case _NET_WM_STATE_ADD: 1186 | minimize_client(c); 1187 | break; 1188 | 1189 | case _NET_WM_STATE_TOGGLE: 1190 | if (c->isminimized) 1191 | restore_client(c); 1192 | else 1193 | minimize_client(c); 1194 | break; 1195 | } 1196 | } 1197 | } 1198 | else { 1199 | if (ev->type == ewmh->_NET_CURRENT_DESKTOP 1200 | && ev->data.data32[0] < DESKTOPS) 1201 | change_desktop(&(Arg){.i = ev->data.data32[0]}); 1202 | else { 1203 | if (c && ev->type == ewmh->_NET_CLOSE_WINDOW) 1204 | killclient(c); 1205 | else { 1206 | if (ev->type == ewmh->_NET_ACTIVE_WINDOW) { 1207 | if (c) { 1208 | client *t = NULL; 1209 | for (t = M_HEAD; t && t != c; t = M_GETNEXT(t)) 1210 | ; 1211 | if (t) 1212 | update_current(c); 1213 | } 1214 | else { 1215 | if (showscratchpad && scrpd && scrpd->win == ev->window) 1216 | update_current(scrpd); 1217 | } 1218 | } 1219 | else { 1220 | if (c && ev->type == ewmh->_NET_WM_DESKTOP 1221 | && ev->data.data32[0] < DESKTOPS) { 1222 | client_to_desktop(&(Arg){.i = ev->data.data32[0]}); 1223 | } 1224 | } 1225 | } 1226 | } 1227 | } 1228 | } 1229 | 1230 | /* a configure request means that the window requested changes in its geometry 1231 | * state. if the window is maximize, discard and fill the screen else set the 1232 | * appropriate values as requested, and tile the window again so that it fills 1233 | * the gaps that otherwise could have been created 1234 | */ 1235 | void configurerequest(xcb_generic_event_t *e) 1236 | { 1237 | xcb_configure_request_event_t *ev = (xcb_configure_request_event_t *)e; 1238 | client *c = wintoclient(ev->window); 1239 | 1240 | DEBUG("xcb: configure request"); 1241 | 1242 | if (c && c->ismaximized) { 1243 | setmaximize(c, true); 1244 | } else { 1245 | unsigned int v[7]; 1246 | unsigned int i = 0; 1247 | int borders = c ? client_borders(c) : 0; 1248 | if (ev->value_mask & XCB_CONFIG_WINDOW_X) 1249 | v[i++] = ev->x; 1250 | if (ev->value_mask & XCB_CONFIG_WINDOW_Y) { 1251 | int y = ev->y; 1252 | if (c && c->type == ewmh->_NET_WM_WINDOW_TYPE_NORMAL) { 1253 | #ifndef EWMH_TASKBAR 1254 | if (M_SHOWPANEL && TOP_PANEL && y < PANEL_HEIGHT) 1255 | #else 1256 | if (y < M_WY) 1257 | #endif /* EWMH_TASKBAR */ 1258 | y = PANEL_HEIGHT; 1259 | } 1260 | v[i++] = y; 1261 | } 1262 | if (ev->value_mask & XCB_CONFIG_WINDOW_WIDTH) 1263 | v[i++] = (ev->width < M_WW - borders) ? ev->width : M_WW + borders; 1264 | if (ev->value_mask & XCB_CONFIG_WINDOW_HEIGHT) 1265 | v[i++] = (ev->height < M_WH - borders) ? ev->height : M_WH + borders; 1266 | if (ev->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) 1267 | v[i++] = ev->border_width; 1268 | if (ev->value_mask & XCB_CONFIG_WINDOW_SIBLING) 1269 | v[i++] = ev->sibling; 1270 | if (ev->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) 1271 | v[i++] = ev->stack_mode; 1272 | xcb_configure_window(dis, ev->window, ev->value_mask, v); 1273 | } 1274 | tile(); 1275 | } 1276 | 1277 | static inline alien *create_alien(xcb_window_t win, xcb_atom_t atom) 1278 | { 1279 | alien *a; 1280 | if((a = (alien *)calloc(1, sizeof(alien)))) { 1281 | unsigned int values[1] = { XCB_EVENT_MASK_PROPERTY_CHANGE }; 1282 | xcb_change_window_attributes(dis, win, XCB_CW_EVENT_MASK, values); 1283 | a->win = win; 1284 | a->type = atom; 1285 | add_tail(&aliens, &a->link); 1286 | xcb_raise_window(dis, win); 1287 | xcb_map_window(dis, win); 1288 | 1289 | xcb_get_geometry_reply_t *g = get_geometry(a->win); 1290 | a->position_info.previous_x = a->position_info.current_x = g->x; 1291 | a->position_info.previous_y = a->position_info.current_y = g->y; 1292 | free(g); 1293 | } 1294 | return(a); 1295 | } 1296 | 1297 | /* 1298 | * allocate client structure and fill in sane defaults 1299 | * exit FrankenWM if memory allocation fails 1300 | */ 1301 | static client *create_client(xcb_window_t win, xcb_atom_t wtype) 1302 | { 1303 | xcb_icccm_wm_hints_t hints; 1304 | client *c = calloc(1, sizeof(client)); 1305 | if (!c) 1306 | err(EXIT_FAILURE, "cannot allocate client"); 1307 | c->isurgent = False; 1308 | c->istransient = False; 1309 | c->isfullscreen = False; 1310 | c->ismaximized = False; 1311 | c->isfloating = False; 1312 | c->isminimized = False; 1313 | c->win = win; 1314 | c->type = wtype; 1315 | c->dim[0] = c->dim[1] = 0; 1316 | c->borderwidth = -1; /* default: use global border width */ 1317 | c->setfocus = True; /* default: prefer xcb_set_input_focus(); */ 1318 | if (xcb_icccm_get_wm_hints_reply(dis, 1319 | xcb_icccm_get_wm_hints(dis, win), &hints, NULL)) 1320 | c->setfocus = (hints.input) ? True : False; 1321 | 1322 | xcb_get_geometry_reply_t *g = get_geometry(c->win); 1323 | c->position_info.previous_x = c->position_info.current_x = g->x; 1324 | c->position_info.previous_y = c->position_info.current_y = g->y; 1325 | free(g); 1326 | 1327 | return c; 1328 | } 1329 | 1330 | static void create_display(client *c) 1331 | { 1332 | desktop *desk=NULL; 1333 | monitor *moni=NULL; 1334 | display *disp=NULL, *new; 1335 | 1336 | if (!c) 1337 | return; 1338 | getparents(c, &disp, &moni, &desk); /* get the client's display, monitor and desktop. */ 1339 | if (!(new = calloc(1, sizeof(display)))) 1340 | err(EXIT_FAILURE, "cannot allocate new display"); 1341 | new->clients.master = new; /* backpointer */ 1342 | rem_node(&c->link); /* unlink client from its display client list. */ 1343 | for (client *t = M_HEAD; t; t = M_GETNEXT(t)) /* hide current windows */ 1344 | xcb_move(dis, t->win, -2 * M_WW, 0, &t->position_info); 1345 | for (alien *t = (alien *)get_head(&aliens); t; t = (alien *)get_next(&t->link)) /* hide aliens */ 1346 | xcb_move(dis, t->win, -2 * M_WW, 0, &t->position_info); 1347 | add_head(&new->clients, &c->link); /* set client as head in new display */ 1348 | add_head(&moni->displays, &new->link); /* set new display as head. */ 1349 | select_desktop(current_desktop_number); /* update global pointers */ 1350 | memcpy(&new->di, &disp->di, sizeof(displayinfo)); /* copy settings */ 1351 | current_display = new; /* update current display pointer */ 1352 | update_current(NULL); /* update focus and tiling */ 1353 | } 1354 | 1355 | /* close the window */ 1356 | bool deletewindow(xcb_window_t win) 1357 | { 1358 | return sendevent(win, wmatoms[WM_DELETE_WINDOW]); 1359 | } 1360 | 1361 | /* 1362 | * output info about the desktops on standard output stream 1363 | * 1364 | * the info is a list of ':' separated values for each desktop 1365 | * desktop to desktop info is separated by ' ' single spaces 1366 | * the info values are 1367 | * the desktop number/id 1368 | * the desktop's client count 1369 | * the desktop's tiling layout mode/id 1370 | * whether the desktop is the current focused (1) or not (0) 1371 | * whether any client in that desktop has received an urgent hint 1372 | * and the current window's title 1373 | * 1374 | * once the info is collected, immediately flush the stream 1375 | */ 1376 | void desktopinfo(void) 1377 | { 1378 | bool urgent = false; 1379 | int cd = current_desktop_number, n = 0, d = 0, minimized = 0; 1380 | xcb_get_property_cookie_t cookie; 1381 | xcb_ewmh_get_utf8_strings_reply_t wclass; 1382 | wclass.strings = NULL; 1383 | 1384 | if (OUTPUT) { 1385 | if (M_CURRENT) { 1386 | cookie = xcb_ewmh_get_wm_name_unchecked(ewmh, M_CURRENT->win); 1387 | xcb_ewmh_get_wm_name_reply(ewmh, cookie, &wclass, (void *)0); 1388 | } 1389 | 1390 | for (client *c; d < DESKTOPS; d++) { 1391 | for (select_desktop(d), c = M_HEAD, n = 0, urgent = false, minimized = 0; 1392 | c; c = M_GETNEXT(c), ++n) { 1393 | if (c->isurgent) 1394 | urgent = true; 1395 | minimized += c->isminimized; 1396 | } 1397 | fprintf(stdout, "%d:%d:%d:%d:%d:%d ", d, n, M_MODE, 1398 | current_desktop_number == cd, urgent, minimized); 1399 | if (d + 1 == DESKTOPS) 1400 | fprintf(stdout, "%s\n", M_CURRENT && OUTPUT_TITLE && wclass.strings ? 1401 | wclass.strings : ""); 1402 | } 1403 | 1404 | if (wclass.strings) { 1405 | xcb_ewmh_get_utf8_strings_reply_wipe(&wclass); 1406 | } 1407 | 1408 | fflush(stdout); 1409 | if (cd != d - 1) 1410 | select_desktop(cd); 1411 | } 1412 | #ifdef EWMH_TASKBAR 1413 | Update_EWMH_Taskbar_Properties(); 1414 | #endif 1415 | } 1416 | 1417 | static void destroy_display(client *c) 1418 | { 1419 | desktop *desk=NULL; 1420 | monitor *moni=NULL; 1421 | display *disp=NULL, *next; 1422 | 1423 | getparents(c, &disp, &moni, &desk); /* get the client's display, monitor and desktop. */ 1424 | if (!(next = (display *)get_next(&disp->link))) /* cannot destroy the last display. */ 1425 | return; 1426 | for (client *t = (client *)rem_head(&disp->clients); t; t = (client *)rem_head(&disp->clients)) { 1427 | /* relink entire clientlist to the tail of next display clientlist. */ 1428 | add_tail(&next->clients, &t->link); 1429 | } 1430 | for (lifo *t = (lifo *)rem_head(&disp->miniq); t; t = (lifo *)rem_head(&disp->miniq)) { 1431 | /* relink minimized clients to the tail of next display clientlist. */ 1432 | xcb_move(dis, t->c->win, t->c->position_info.previous_x, 1433 | t->c->position_info.previous_y, NULL); 1434 | t->c->position_info.previous_x = t->c->position_info.current_x; 1435 | t->c->position_info.previous_y = t->c->position_info.current_y; 1436 | add_tail(&next->clients, &t->c->link); 1437 | t->c->isminimized = False; 1438 | xcb_remove_property(dis, t->c->win, ewmh->_NET_WM_STATE, ewmh->_NET_WM_STATE_HIDDEN); 1439 | free(t); 1440 | 1441 | } 1442 | rem_node(&disp->link); /* unlink now empty display */ 1443 | select_desktop(current_desktop_number); /* update global pointers */ 1444 | for (alien *t = (alien *)get_head(&aliens); t; t = (alien *)get_next(&t->link)) /* show aliens */ 1445 | xcb_move(dis, t->win, t->position_info.previous_x, t->position_info.previous_y, &t->position_info); 1446 | if (current_display == next) { 1447 | update_current(c); 1448 | } 1449 | free(disp); 1450 | } 1451 | 1452 | /* 1453 | * a destroy notification is received when a window is being closed 1454 | * on receival, remove the appropriate client that held that window 1455 | */ 1456 | void destroynotify(xcb_generic_event_t *e) 1457 | { 1458 | xcb_destroy_notify_event_t *ev = (xcb_destroy_notify_event_t *)e; 1459 | client *c = wintoclient(ev->window); 1460 | 1461 | DEBUG("xcb: destroy notify"); 1462 | 1463 | if (c) { 1464 | if (c->isfullscreen) 1465 | destroy_display(c); 1466 | removeclient(c); 1467 | } 1468 | else if (USE_SCRATCHPAD && scrpd && ev->window == scrpd->win) { 1469 | free(scrpd); 1470 | scrpd = NULL; 1471 | update_current(M_CURRENT); 1472 | } 1473 | else { 1474 | alien *a; 1475 | 1476 | if((a = wintoalien(&aliens, ev->window))) { 1477 | DEBUG("unlink selfmapped window"); 1478 | rem_node(&a->link); 1479 | free(a); 1480 | } 1481 | } 1482 | desktopinfo(); 1483 | } 1484 | 1485 | /* dualstack layout (three-column-layout, tcl in dwm) */ 1486 | void dualstack(int hh, int cy) 1487 | { 1488 | client *c = NULL, *t = NULL; 1489 | int n = 0, z = hh, d = 0, l = 0, r = 0, cb = cy, 1490 | ma = (M_INVERT ? M_WH : M_WW) * MASTER_SIZE + M_MASTER_SIZE; 1491 | 1492 | /* count stack windows and grab first non-floating, non-maximize window */ 1493 | for (t = M_HEAD; t; t = M_GETNEXT(t)) { 1494 | if (!ISFMFTM(t)) { 1495 | if (c) 1496 | ++n; 1497 | else 1498 | c = t; 1499 | } 1500 | } 1501 | 1502 | l = (n - 1) / 2 + 1; /* left stack size */ 1503 | r = n - l; /* right stack size */ 1504 | 1505 | if (!c) { 1506 | return; 1507 | } else if (!n) { 1508 | int borders = client_borders(c); 1509 | xcb_move_resize(dis, c->win, M_GAPS, cy + M_GAPS, 1510 | M_WW - 2 * (borders + M_GAPS), 1511 | hh - 2 * (borders + M_GAPS), &c->position_info); 1512 | return; 1513 | } 1514 | 1515 | /* tile the first non-floating, non-maximize window to cover the master area */ 1516 | int borders = client_borders(c); 1517 | if (current_display->di.invert) 1518 | xcb_move_resize(dis, c->win, M_GAPS, 1519 | cy + (hh - ma) / 2 + M_GAPS, 1520 | M_WW - 2 * (borders + M_GAPS), 1521 | n > 1 ? ma - 2 * M_GAPS - 2 * borders 1522 | : ma + (hh - ma) / 2 - 2 * borders - 2 * M_GAPS, &c->position_info); 1523 | else 1524 | xcb_move_resize(dis, c->win, (M_WW - ma) / 2 + borders + M_GAPS, 1525 | cy + M_GAPS, 1526 | n > 1 ? (ma - 4 * borders - 2 * M_GAPS) 1527 | : (ma + (M_WW - ma) / 2 - 3 * borders - 2 * M_GAPS), 1528 | hh - 2 * (borders + M_GAPS), &c->position_info); 1529 | 1530 | int cx = M_GAPS, 1531 | cw = (M_WW - ma) / 2 - borders - M_GAPS, 1532 | ch = z; 1533 | cy += M_GAPS; 1534 | 1535 | /* tile the non-floating, non-maximize stack windows */ 1536 | for (c = M_GETNEXT(c); c; c = M_GETNEXT(c)) { 1537 | for (d = 0, t = M_HEAD; t != c; t = M_GETNEXT(t), d++); 1538 | if (ISFMFTM(c)) 1539 | continue; 1540 | int borders = client_borders(c); 1541 | if (M_INVERT) { 1542 | if (d == l + 1) /* we are on the -right- bottom stack, reset cy */ 1543 | cx = M_GAPS; 1544 | if (d > 1 && d != l + 1) 1545 | cx += (M_WW - M_GAPS) / (d <= l ? l : r); 1546 | xcb_move_resize(dis, c->win, 1547 | cx, (d <= l) ? cy : cy + (hh - ma) / 2 + ma - M_GAPS, 1548 | (M_WW - M_GAPS) / (d <= l ? l : r) - 2 * borders - M_GAPS, 1549 | (hh - ma) / 2 - 2 * borders - M_GAPS, &c->position_info); 1550 | } else { 1551 | if (d == l + 1) /* we are on the right stack, reset cy */ 1552 | cy = cb + M_GAPS; 1553 | if (d > 1 && d != l + 1) 1554 | cy += (ch - M_GAPS) / (d <= l ? l : r); 1555 | xcb_move_resize(dis, c->win, 1556 | d <= l ? cx : M_WW - cw - 2 * borders - M_GAPS, cy, cw, 1557 | (ch - M_GAPS) / (d <= l ? l : r) - 2 * borders - M_GAPS, &c->position_info); 1558 | } 1559 | } 1560 | } 1561 | 1562 | /* 1563 | * when the mouse enters a window's borders 1564 | * the window, if notifying of such events (EnterWindowMask) 1565 | * will notify the wm and will get focus 1566 | */ 1567 | void enternotify(xcb_generic_event_t *e) 1568 | { 1569 | xcb_enter_notify_event_t *ev = (xcb_enter_notify_event_t *)e; 1570 | 1571 | DEBUG("xcb: enter notify"); 1572 | 1573 | if (!FOLLOW_MOUSE) 1574 | return; 1575 | 1576 | DEBUG("event is valid"); 1577 | 1578 | if(USE_SCRATCHPAD && showscratchpad && scrpd && ev->event == scrpd->win) { 1579 | update_current(scrpd); 1580 | } 1581 | else { 1582 | client *c = wintoclient(ev->event); 1583 | 1584 | if (c 1585 | && ev->mode == XCB_NOTIFY_MODE_NORMAL 1586 | && c != M_CURRENT 1587 | && ev->detail != XCB_NOTIFY_DETAIL_INFERIOR) { 1588 | update_current(c); 1589 | } 1590 | } 1591 | } 1592 | 1593 | /* 1594 | * equal mode 1595 | * tile the windows in rows or columns, givin each window an equal amount of 1596 | * screen space 1597 | * will use rows when inverted and columns otherwise 1598 | */ 1599 | void equal(int h, int y) 1600 | { 1601 | int n = 0, j = -1; 1602 | 1603 | for (client *c = M_HEAD; c; c = M_GETNEXT(c)) { 1604 | if (ISFMFTM(c)) 1605 | continue; 1606 | n++; 1607 | } 1608 | 1609 | for (client *c = M_HEAD; c; c = M_GETNEXT(c)) { 1610 | int borders = client_borders(c); 1611 | if (ISFMFTM(c)) 1612 | continue; 1613 | else 1614 | j++; 1615 | if (M_INVERT) 1616 | xcb_move_resize(dis, c->win, M_GAPS, 1617 | y + h / n * j + (c == M_HEAD ? M_GAPS : 0), 1618 | M_WW - 2 * borders - 2 * M_GAPS, 1619 | h / n - 2 * borders - (c == M_HEAD ? 2 : 1) * M_GAPS, &c->position_info); 1620 | else 1621 | xcb_move_resize(dis, c->win, M_WW / n * j + (c == M_HEAD ? M_GAPS : 0), 1622 | y + M_GAPS, 1623 | M_WW / n - 2 * borders - (c == M_HEAD ? 2 : 1) * M_GAPS, 1624 | h - 2 * borders - 2 * M_GAPS, &c->position_info); 1625 | } 1626 | } 1627 | 1628 | /* 1629 | * fibonacci mode / fibonacci layout 1630 | * tile the windows based on the fibonacci series pattern. 1631 | * arrange windows in such a way that every new window shares 1632 | * half the space of the space taken by the last window 1633 | * inverting changes between right/down and right/up 1634 | */ 1635 | void fibonacci(int h, int y) 1636 | { 1637 | int borders = borders; 1638 | if (M_HEAD) 1639 | borders = client_borders(M_HEAD); 1640 | 1641 | int j = -1, x = M_GAPS, tt = 0, 1642 | cw = M_WW - 2 * M_GAPS - 2 * borders, 1643 | ch = h - 2 * M_GAPS - 2 * borders; 1644 | 1645 | for (client *n, *c = M_HEAD; c; c = M_GETNEXT(c)) { 1646 | int borders = client_borders(c); 1647 | if (ISFMFTM(c)) 1648 | continue; 1649 | else 1650 | j++; 1651 | for (n = M_GETNEXT(c); n; n = M_GETNEXT(n)) 1652 | if (!ISFMFTM(n)) 1653 | break; 1654 | 1655 | /* 1656 | * not the last window in stack ? -> half the client size, and also 1657 | * check if we have too many windows to keep them larger than MINWSZ 1658 | */ 1659 | if (n 1660 | && ch > MINWSZ * 2 + borders + M_GAPS 1661 | && cw > MINWSZ * 2 + borders + M_GAPS) { 1662 | (j & 1) ? (ch = ch / 2 - borders - M_GAPS / 2) 1663 | : (cw = cw / 2 - borders - M_GAPS / 2); 1664 | tt = j; 1665 | } 1666 | 1667 | /* not the master client ? -> shift client right or down (or up) */ 1668 | if (j) { 1669 | (j & 1) ? (x = x + cw + 2 * borders + M_GAPS) 1670 | : (y = M_INVERT ? (y - ch - 2 * borders - M_GAPS) 1671 | : (y + ch + 2 * borders + M_GAPS)); 1672 | 1673 | if (j & 1 && n && M_INVERT) 1674 | y += ch + 2 * borders + M_GAPS; 1675 | } 1676 | 1677 | /* if the window does not fit in the stack, do not jam it in there */ 1678 | if (j <= tt + 1) 1679 | xcb_move_resize(dis, c->win, x, y + M_GAPS, cw, ch, &c->position_info); 1680 | } 1681 | } 1682 | 1683 | /* switch a client from tiling to float and manage everything involved */ 1684 | void float_client(client *c) 1685 | { 1686 | if (!c) 1687 | return; 1688 | 1689 | c->isfloating = true; 1690 | 1691 | if (c->dim[0] && c->dim[1]) { 1692 | if (c->dim[0] < MINWSZ) 1693 | c->dim[0] = MINWSZ; 1694 | if (c->dim[1] < MINWSZ) 1695 | c->dim[1] = MINWSZ; 1696 | 1697 | xcb_resize(dis, c->win, c->dim[0], c->dim[1]); 1698 | } 1699 | } 1700 | 1701 | /* 1702 | * handles x-movement of floating windows 1703 | */ 1704 | void float_x(const Arg *arg) 1705 | { 1706 | xcb_get_geometry_reply_t *r; 1707 | 1708 | if (!arg->i || !M_CURRENT) 1709 | return; 1710 | 1711 | if (!M_CURRENT->isfloating) { 1712 | float_client(M_CURRENT); 1713 | tile(); 1714 | } 1715 | 1716 | r = get_geometry(M_CURRENT->win); 1717 | r->x += arg->i; 1718 | xcb_move(dis, M_CURRENT->win, r->x, r->y, &M_CURRENT->position_info); 1719 | free(r); 1720 | } 1721 | 1722 | /* 1723 | * handles y-movement of floating windows 1724 | */ 1725 | void float_y(const Arg *arg) 1726 | { 1727 | xcb_get_geometry_reply_t *r; 1728 | 1729 | if (!arg->i || !M_CURRENT) 1730 | return; 1731 | 1732 | if (!M_CURRENT->isfloating) { 1733 | float_client(M_CURRENT); 1734 | tile(); 1735 | } 1736 | 1737 | r = get_geometry(M_CURRENT->win); 1738 | r->y += arg->i; 1739 | xcb_move(dis, M_CURRENT->win, r->x, r->y, &M_CURRENT->position_info); 1740 | free(r); 1741 | } 1742 | 1743 | /* 1744 | * focus the (first) master window, or switch back to the slave previously 1745 | * focussed, toggling between them 1746 | */ 1747 | void focusmaster() 1748 | { 1749 | if (!M_HEAD 1750 | || !M_CURRENT 1751 | || (M_CURRENT == M_HEAD && !M_GETNEXT(M_HEAD)) 1752 | || !M_PREVFOCUS 1753 | || M_PREVFOCUS->isminimized) 1754 | return; 1755 | 1756 | /* fix for glitchy toggle behaviour between head and head->next */ 1757 | if (M_CURRENT == M_GETNEXT(M_HEAD)) 1758 | M_PREVFOCUS = M_CURRENT; 1759 | 1760 | if (M_CURRENT == M_HEAD) 1761 | update_current(M_PREVFOCUS); 1762 | else 1763 | update_current(M_HEAD); 1764 | } 1765 | 1766 | /* find and focus the client which received 1767 | * the urgent hint in the current desktop */ 1768 | void focusurgent() 1769 | { 1770 | client *c; 1771 | int cd = current_desktop_number, d = 0; 1772 | 1773 | for (c = M_HEAD; c && !c->isurgent; c = M_GETNEXT(c)); 1774 | if (c) { 1775 | update_current(c); 1776 | return; 1777 | } else { 1778 | for (bool f = false; d < DESKTOPS && !f; d++) { 1779 | for (select_desktop(d), c = M_HEAD; c && !(f = c->isurgent); c = M_GETNEXT(c)) 1780 | ; 1781 | } 1782 | } 1783 | select_desktop(cd); 1784 | if (c) { 1785 | change_desktop(&(Arg){.i = --d}); 1786 | update_current(c); 1787 | } 1788 | } 1789 | 1790 | /* get a pixel with the requested color 1791 | * to fill some window area - borders */ 1792 | unsigned int getcolor(char *color) 1793 | { 1794 | xcb_colormap_t map = screen->default_colormap; 1795 | xcb_alloc_color_reply_t *c; 1796 | unsigned int r, g, b, rgb, pixel; 1797 | 1798 | rgb = xcb_get_colorpixel(color); 1799 | r = rgb >> 16; g = rgb >> 8 & 0xFF; b = rgb & 0xFF; 1800 | c = xcb_alloc_color_reply(dis, xcb_alloc_color(dis, map, r * 257, g * 257, 1801 | b * 257), NULL); 1802 | if (!c) 1803 | errx(EXIT_FAILURE, "error: cannot allocate color '%s'\n", color); 1804 | 1805 | pixel = c->pixel; 1806 | free(c); 1807 | 1808 | return pixel; 1809 | } 1810 | 1811 | /* set the given client to listen to button events (presses / releases) */ 1812 | void grabbuttons(client *c) 1813 | { 1814 | unsigned int modifiers[] = { 0, XCB_MOD_MASK_LOCK, numlockmask, 1815 | numlockmask|XCB_MOD_MASK_LOCK }; 1816 | if (!c) 1817 | return; 1818 | 1819 | xcb_ungrab_button(dis, XCB_BUTTON_INDEX_ANY, c->win, XCB_GRAB_ANY); 1820 | for (unsigned int b = 0; b < LENGTH(buttons); b++) 1821 | for (unsigned int m = 0; m < LENGTH(modifiers); m++) 1822 | if (CLICK_TO_FOCUS) 1823 | xcb_grab_button(dis, 1, c->win, XCB_EVENT_MASK_BUTTON_PRESS, 1824 | XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, 1825 | XCB_WINDOW_NONE, XCB_CURSOR_NONE, 1826 | XCB_BUTTON_INDEX_ANY, XCB_BUTTON_MASK_ANY); 1827 | else 1828 | xcb_grab_button(dis, 1, c->win, XCB_EVENT_MASK_BUTTON_PRESS, 1829 | XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, 1830 | XCB_WINDOW_NONE, XCB_CURSOR_NONE, 1831 | buttons[b].button, 1832 | buttons[b].mask|modifiers[m]); 1833 | } 1834 | /* 1835 | void grabbuttons(client *c) 1836 | { 1837 | if (!c) 1838 | return; 1839 | 1840 | xcb_ungrab_button(dis, XCB_BUTTON_INDEX_ANY, c->win, XCB_GRAB_ANY); 1841 | if (CLICK_TO_FOCUS) 1842 | xcb_grab_button(dis, 1, c->win, XCB_EVENT_MASK_BUTTON_PRESS, 1843 | XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, 1844 | XCB_WINDOW_NONE, XCB_CURSOR_NONE, 1845 | (c == scrpd) ? XCB_BUTTON_INDEX_1 : XCB_BUTTON_INDEX_ANY, 1846 | XCB_BUTTON_MASK_ANY); 1847 | else { 1848 | unsigned int modifiers[] = { 0, XCB_MOD_MASK_LOCK, numlockmask, 1849 | numlockmask|XCB_MOD_MASK_LOCK }; 1850 | 1851 | for (unsigned int b = 0; b < LENGTH(buttons); b++) { 1852 | for (unsigned int m = 0; m < LENGTH(modifiers); m++) { 1853 | xcb_grab_button(dis, 1, c->win, XCB_EVENT_MASK_BUTTON_PRESS, 1854 | XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, 1855 | XCB_WINDOW_NONE, XCB_CURSOR_NONE, 1856 | buttons[b].button, 1857 | buttons[b].mask|modifiers[m]); 1858 | } 1859 | } 1860 | } 1861 | } 1862 | */ 1863 | 1864 | /* the wm should listen to key presses */ 1865 | void grabkeys(void) 1866 | { 1867 | xcb_keycode_t *keycode; 1868 | unsigned int modifiers[] = { 0, XCB_MOD_MASK_LOCK, numlockmask, 1869 | numlockmask|XCB_MOD_MASK_LOCK }; 1870 | 1871 | xcb_ungrab_key(dis, XCB_GRAB_ANY, screen->root, XCB_MOD_MASK_ANY); 1872 | for (unsigned int i = 0; i < LENGTH(keys); i++) { 1873 | keycode = xcb_get_keycodes(keys[i].keysym); 1874 | for (unsigned int k = 0; keycode[k] != XCB_NO_SYMBOL; k++) 1875 | for (unsigned int m = 0; m < LENGTH(modifiers); m++) 1876 | xcb_grab_key(dis, 1, screen->root, keys[i].mod | modifiers[m], 1877 | keycode[k], XCB_GRAB_MODE_ASYNC, 1878 | XCB_GRAB_MODE_ASYNC); 1879 | free(keycode); 1880 | } 1881 | } 1882 | 1883 | /* arrange windows in a grid */ 1884 | void grid(int hh, int cy) 1885 | { 1886 | int n = 0, cols = 0, cn = 0, rn = 0, i = -1; 1887 | 1888 | for (client *c = M_HEAD; c; c = M_GETNEXT(c)) 1889 | if (!ISFMFTM(c)) 1890 | ++n; 1891 | if (!n) 1892 | return; 1893 | for (cols = 0; cols <= n / 2; cols++) 1894 | if (cols * cols >= n) 1895 | break; /* emulate square root */ 1896 | if (n == 5) 1897 | cols = 2; 1898 | 1899 | int rows = n / cols, 1900 | ch = hh - M_GAPS, 1901 | cw = (M_WW - M_GAPS) / (cols ? cols : 1); 1902 | for (client *c = M_HEAD; c; c = M_GETNEXT(c)) { 1903 | int borders = client_borders(c); 1904 | if (ISFMFTM(c)) 1905 | continue; 1906 | else 1907 | ++i; 1908 | if (i / rows + 1 > cols - n % cols) 1909 | rows = n / cols + 1; 1910 | xcb_move_resize(dis, c->win, cn * cw + M_GAPS, 1911 | cy + rn * ch / rows + M_GAPS, 1912 | cw - 2 * borders - M_GAPS, 1913 | ch / rows - 2 * borders - M_GAPS, &c->position_info); 1914 | if (++rn >= rows) { 1915 | rn = 0; 1916 | cn++; 1917 | } 1918 | } 1919 | } 1920 | 1921 | /* invert v-stack left-right */ 1922 | void invertstack() 1923 | { 1924 | M_INVERT = !M_INVERT; 1925 | tile(); 1926 | } 1927 | 1928 | /* on the press of a key check to see if there's a binded function to call */ 1929 | void keypress(xcb_generic_event_t *e) 1930 | { 1931 | xcb_key_press_event_t *ev = (xcb_key_press_event_t *)e; 1932 | xcb_keysym_t keysym = xcb_get_keysym(ev->detail); 1933 | 1934 | DEBUGP("xcb: keypress: code: %d mod: %d\n", ev->detail, ev->state); 1935 | for (unsigned int i = 0; i < LENGTH(keys); i++) 1936 | if (keysym == keys[i].keysym && 1937 | CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) && 1938 | keys[i].func) 1939 | keys[i].func(&keys[i].arg); 1940 | } 1941 | 1942 | /* explicitly kill a client - close the highlighted window 1943 | * send a delete message and remove the client */ 1944 | void killclient() 1945 | { 1946 | if (!M_CURRENT) 1947 | return; 1948 | if (!deletewindow(M_CURRENT->win)) { 1949 | xcb_kill_client(dis, M_CURRENT->win); 1950 | DEBUG("client killed"); 1951 | } 1952 | else { 1953 | DEBUG("client deleted"); 1954 | } 1955 | removeclient(M_CURRENT); 1956 | } 1957 | 1958 | static bool check_wmproto(xcb_window_t win, xcb_atom_t proto) 1959 | { 1960 | xcb_icccm_get_wm_protocols_reply_t reply; 1961 | bool got = false; 1962 | 1963 | if (xcb_icccm_get_wm_protocols_reply(dis, 1964 | xcb_icccm_get_wm_protocols(dis, win, wmatoms[WM_PROTOCOLS]), &reply, NULL)) { 1965 | /* TODO: Handle error? */ 1966 | unsigned int n; 1967 | for (n = 0; n != reply.atoms_len; ++n) 1968 | if ((got = reply.atoms[n] == proto)) 1969 | break; 1970 | xcb_icccm_get_wm_protocols_reply_wipe(&reply); 1971 | } 1972 | return got; 1973 | } 1974 | 1975 | /* focus the previously focused desktop */ 1976 | void last_desktop() 1977 | { 1978 | change_desktop(&(Arg){.i = previous_desktop}); 1979 | } 1980 | 1981 | void mapnotify(xcb_generic_event_t *e) 1982 | { 1983 | xcb_map_notify_event_t *ev = (xcb_map_notify_event_t *)e; 1984 | 1985 | DEBUG("xcb: map notify"); 1986 | 1987 | if (wintoclient(ev->window) || (scrpd && scrpd->win == ev->window)) 1988 | return; 1989 | 1990 | xcb_window_t wins[] = {ev->window}; 1991 | xcb_get_window_attributes_reply_t *attr[1]; 1992 | 1993 | xcb_get_attributes(wins, attr, 1); 1994 | if (!attr[0]) 1995 | return; /* dead on arrival */ 1996 | if (attr[0]->override_redirect) { 1997 | free(attr[0]); 1998 | return; 1999 | } 2000 | else 2001 | free(attr[0]); 2002 | 2003 | if (wintoalien(&aliens, ev->window)) { 2004 | DEBUG("alien window already in list"); 2005 | return; 2006 | } 2007 | 2008 | xcb_ewmh_get_atoms_reply_t type; 2009 | if (xcb_ewmh_get_wm_window_type_reply(ewmh, 2010 | xcb_ewmh_get_wm_window_type(ewmh, 2011 | ev->window), &type, NULL) == 1) { 2012 | create_alien(ev->window, type.atoms[0]); 2013 | xcb_ewmh_get_atoms_reply_wipe(&type); 2014 | DEBUG("caught a new selfmapped window"); 2015 | } 2016 | else { 2017 | DEBUG("alien has no _NET_WM_WINDOW_TYPE property"); 2018 | } 2019 | } 2020 | 2021 | /* a map request is received when a window wants to display itself 2022 | * if the window has override_redirect flag set then it should not be handled 2023 | * by the wm. if the window already has a client then there is nothing to do. 2024 | * 2025 | * get the window class and name instance and try to match against an app rule. 2026 | * create a client for the window, that client will always be current. 2027 | * check for transient state, and maximize state and the appropriate values. 2028 | * if the desktop in which the window was spawned is the current desktop then 2029 | * display the window, else, if set, focus the new desktop. 2030 | */ 2031 | void maprequest(xcb_generic_event_t *e) 2032 | { 2033 | xcb_map_request_event_t *ev = (xcb_map_request_event_t *)e; 2034 | xcb_window_t transient = 0; 2035 | xcb_get_property_reply_t *prop_reply; 2036 | xcb_icccm_get_wm_class_reply_t wclass; 2037 | xcb_atom_t wtype = ewmh->_NET_WM_WINDOW_TYPE_NORMAL; 2038 | client *c; 2039 | bool isFloating = False; 2040 | 2041 | DEBUG("xcb: map request"); 2042 | 2043 | if ((c = wintoclient(ev->window))) { 2044 | if (!find_client(c->win)) { /* client is on different display */ 2045 | rem_node(&c->link); 2046 | add_tail(¤t_display->clients, &c->link); 2047 | } 2048 | xcb_map_window(dis, c->win); 2049 | update_current(c); 2050 | return; 2051 | } 2052 | 2053 | if (check_if_window_is_alien(ev->window, &isFloating, &wtype)) 2054 | return; 2055 | 2056 | xcb_remove_property(dis, ev->window, ewmh->_NET_WM_STATE, ewmh->_NET_WM_STATE_HIDDEN); 2057 | 2058 | DEBUG("event is valid"); 2059 | 2060 | bool follow = false; 2061 | int cd = current_desktop_number, newdsk = current_desktop_number, border_width = -1; 2062 | 2063 | if (xcb_icccm_get_wm_class_reply(dis, xcb_icccm_get_wm_class_unchecked(dis, ev->window), &wclass, NULL)) { 2064 | char *instance_name = wclass.instance_name; 2065 | char *class_name = wclass.class_name; 2066 | DEBUGP("class,inst: %s,%s\n", class_name, instance_name); 2067 | 2068 | if (!strcmp(instance_name, SCRPDNAME)) { 2069 | scrpd = create_client(ev->window, wtype); 2070 | setwindefattr(scrpd->win); 2071 | grabbuttons(scrpd); 2072 | 2073 | xcb_move(dis, scrpd->win, -2 * M_WW, 0, &scrpd->position_info); 2074 | xcb_map_window(dis, scrpd->win); 2075 | xcb_icccm_get_wm_class_reply_wipe(&wclass); 2076 | 2077 | if (scrpd_atom) 2078 | xcb_change_property(dis, XCB_PROP_MODE_REPLACE, scrpd->win, scrpd_atom, 2079 | XCB_ATOM_WINDOW, 32, 1, &scrpd->win); 2080 | return; 2081 | } 2082 | 2083 | for (unsigned int i = 0; i < LENGTH(classruleregex); i++) 2084 | if (!regexec(&classruleregex[i], &class_name[0], 0, NULL, 0) && 2085 | !regexec(&instanceruleregex[i], &instance_name[0], 0, NULL, 0)) { 2086 | follow = rules[i].follow; 2087 | newdsk = (rules[i].desktop < 0 || 2088 | rules[i].desktop >= DESKTOPS) ? current_desktop_number 2089 | : rules[i].desktop; 2090 | isFloating = rules[i].floating; 2091 | border_width = rules[i].border_width; 2092 | break; 2093 | } 2094 | 2095 | xcb_icccm_get_wm_class_reply_wipe(&wclass); 2096 | } 2097 | 2098 | if (cd != newdsk) 2099 | select_desktop(newdsk); 2100 | c = addwindow(ev->window, wtype); 2101 | 2102 | xcb_icccm_get_wm_transient_for_reply(dis, 2103 | xcb_icccm_get_wm_transient_for_unchecked(dis, ev->window), 2104 | &transient, NULL); /* TODO: error handling */ 2105 | c->istransient = transient ? true : false; 2106 | c->isfloating = isFloating || c->istransient; 2107 | c->borderwidth = border_width; 2108 | 2109 | prop_reply = xcb_get_property_reply(dis, xcb_get_property_unchecked( 2110 | dis, 0, ev->window, ewmh->_NET_WM_STATE, 2111 | XCB_ATOM_ATOM, 0, 1), NULL); 2112 | /* TODO: error handling */ 2113 | if (prop_reply) { 2114 | if (prop_reply->format == 32) { 2115 | xcb_atom_t *v = xcb_get_property_value(prop_reply); 2116 | for (unsigned int i = 0; i < prop_reply->value_len; i++) { 2117 | DEBUGP("%d : %d\n", i, v[i]); 2118 | if (v[i] == ewmh->_NET_WM_STATE_FULLSCREEN) 2119 | setfullscreen(c, True); 2120 | } 2121 | } 2122 | free(prop_reply); 2123 | } 2124 | 2125 | DEBUGP("transient: %d\n", c->istransient); 2126 | DEBUGP("floating: %d\n", c->isfloating); 2127 | 2128 | int wmdsk = cd; 2129 | bool visible = True; 2130 | xcb_move(dis, c->win, -2 * M_WW, 0, &c->position_info); 2131 | xcb_map_window(dis, c->win); 2132 | if (cd != newdsk) { 2133 | visible = False; 2134 | rem_node(&c->link); 2135 | select_desktop(newdsk); 2136 | add_tail(¤t_display->clients, &c->link); 2137 | select_desktop(cd); 2138 | wmdsk = newdsk; 2139 | if (follow) { 2140 | visible = True; 2141 | change_desktop(&(Arg){.i = newdsk}); 2142 | } 2143 | } 2144 | if (visible && show) { 2145 | xcb_move(dis, c->win, c->position_info.previous_x, 2146 | c->position_info.previous_y, NULL); 2147 | c->position_info.previous_x = c->position_info.current_x; 2148 | c->position_info.previous_y = c->position_info.current_y; 2149 | update_current(c); 2150 | if (c->isfloating && AUTOCENTER) 2151 | centerfloating(c); 2152 | } 2153 | xcb_ewmh_set_wm_desktop(ewmh, c->win, wmdsk); 2154 | grabbuttons(c); 2155 | desktopinfo(); 2156 | } 2157 | 2158 | /* maximize the current window, or if we are maximized, tile() */ 2159 | void maximize() 2160 | { 2161 | if (!M_CURRENT) 2162 | return; 2163 | 2164 | setmaximize(M_CURRENT, !M_CURRENT->ismaximized); 2165 | } 2166 | 2167 | /* push the current client down the miniq and minimize the window */ 2168 | void minimize_client(client *c) 2169 | { 2170 | lifo *new; 2171 | 2172 | if (!c || c->isfullscreen) 2173 | return; 2174 | 2175 | new = calloc(1, sizeof(lifo)); 2176 | if (!new) 2177 | return; 2178 | 2179 | new->c = c; 2180 | add_head(¤t_display->miniq, &new->link); 2181 | 2182 | new->c->isminimized = true; 2183 | xcb_move(dis, new->c->win, -2 * M_WW, 0, &new->c->position_info); 2184 | xcb_add_property(dis, new->c->win, ewmh->_NET_WM_STATE, ewmh->_NET_WM_STATE_HIDDEN); 2185 | 2186 | client *t = M_HEAD; 2187 | while (t) { 2188 | if (t && !t->isminimized) 2189 | break; 2190 | t = M_GETNEXT(t); 2191 | } 2192 | if (t) 2193 | update_current(t); 2194 | 2195 | tile(); 2196 | } 2197 | 2198 | /* minimize_client(); wrapper */ 2199 | void minimize() 2200 | { 2201 | minimize_client(M_CURRENT); 2202 | } 2203 | 2204 | /* grab the pointer and get it's current position 2205 | * all pointer movement events will be reported until it's ungrabbed 2206 | * until the mouse button has not been released, 2207 | * grab the interesting events - button press/release and pointer motion 2208 | * and on on pointer movement resize or move the window under the curson. 2209 | * if the received event is a map request or a configure request call the 2210 | * appropriate handler, and stop listening for other events. 2211 | * Ungrab the poitner and event handling is passed back to run() function. 2212 | * Once a window has been moved or resized, it's marked as floating. */ 2213 | void mousemotion(const Arg *arg) 2214 | { 2215 | xcb_get_geometry_reply_t *geometry; 2216 | xcb_query_pointer_reply_t *pointer; 2217 | xcb_grab_pointer_reply_t *grab_reply; 2218 | int mx, my, winx, winy, winw, winh, xw, yh; 2219 | 2220 | if (!M_CURRENT || M_CURRENT->isfullscreen) 2221 | return; 2222 | geometry = get_geometry(M_CURRENT->win); 2223 | winx = geometry->x; winy = geometry->y; 2224 | winw = geometry->width; winh = geometry->height; 2225 | free(geometry); 2226 | 2227 | pointer = xcb_query_pointer_reply(dis, 2228 | xcb_query_pointer(dis, screen->root), 0); 2229 | if (!pointer) 2230 | return; 2231 | mx = pointer->root_x; 2232 | my = pointer->root_y; 2233 | 2234 | grab_reply = xcb_grab_pointer_reply(dis, xcb_grab_pointer(dis, 0, 2235 | screen->root, 2236 | BUTTONMASK|XCB_EVENT_MASK_BUTTON_MOTION|XCB_EVENT_MASK_POINTER_MOTION, 2237 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_NONE, XCB_NONE, 2238 | XCB_CURRENT_TIME), NULL); 2239 | 2240 | if (!grab_reply || grab_reply->status != XCB_GRAB_STATUS_SUCCESS) { 2241 | free(grab_reply); 2242 | return; 2243 | } else { 2244 | free(grab_reply); 2245 | } 2246 | 2247 | if (M_CURRENT->ismaximized) 2248 | setmaximize(M_CURRENT, False); 2249 | if (!M_CURRENT->isfloating) 2250 | float_client(M_CURRENT); 2251 | update_current(M_CURRENT); 2252 | 2253 | xcb_generic_event_t *e = NULL; 2254 | xcb_motion_notify_event_t *ev = NULL; 2255 | bool ungrab = false; 2256 | do { 2257 | xcb_flush(dis); 2258 | while (!(e = xcb_wait_for_event(dis))) 2259 | xcb_flush(dis); 2260 | switch (e->response_type & ~0x80) { 2261 | case XCB_CONFIGURE_REQUEST: 2262 | case XCB_MAP_REQUEST: 2263 | events[e->response_type & ~0x80](e); 2264 | break; 2265 | case XCB_MOTION_NOTIFY: 2266 | ev = (xcb_motion_notify_event_t *)e; 2267 | xw = (arg->i == MOVE ? winx : winw) + ev->root_x - mx; 2268 | yh = (arg->i == MOVE ? winy : winh) + ev->root_y - my; 2269 | if (arg->i == RESIZE) xcb_resize(dis, M_CURRENT->win, 2270 | xw > MINWSZ ? xw : winw, 2271 | yh > MINWSZ ? yh : winh); 2272 | else if (arg->i == MOVE) xcb_move(dis, M_CURRENT->win, xw, yh, &M_CURRENT->position_info); 2273 | xcb_flush(dis); 2274 | break; 2275 | case XCB_KEY_PRESS: 2276 | case XCB_KEY_RELEASE: 2277 | case XCB_BUTTON_PRESS: 2278 | case XCB_BUTTON_RELEASE: 2279 | ungrab = true; 2280 | } 2281 | if (e) 2282 | free(e); 2283 | } while (!ungrab && M_CURRENT); 2284 | DEBUG("xcb: ungrab"); 2285 | xcb_ungrab_pointer(dis, XCB_CURRENT_TIME); 2286 | 2287 | free(pointer); 2288 | } 2289 | 2290 | /* each window should cover all the available screen space */ 2291 | void monocle(int hh, int cy) 2292 | { 2293 | unsigned int b = MONOCLE_BORDERS ? 2 * client_borders(M_CURRENT) : 0; 2294 | 2295 | for (client *c = M_HEAD; c; c = M_GETNEXT(c)) 2296 | if (!ISFMFTM(c)) 2297 | xcb_move_resize(dis, c->win, M_GAPS, cy + M_GAPS, 2298 | M_WW - 2 * M_GAPS - b, hh - 2 * M_GAPS - b, &c->position_info); 2299 | } 2300 | 2301 | /* move the current client, to current->next 2302 | * and current->next to current client's position */ 2303 | void move_down() 2304 | { 2305 | if (!M_CURRENT || !M_GETNEXT(M_CURRENT)) 2306 | return; 2307 | client *c = M_CURRENT; 2308 | client *n = M_GETNEXT(c); 2309 | list *l = c->link.parent; 2310 | rem_node(&c->link); 2311 | insert_node_after(l, &n->link, &c->link); 2312 | tile(); 2313 | } 2314 | 2315 | /* move the current client, to the previous from current and 2316 | * the previous from current to current client's position */ 2317 | void move_up() 2318 | { 2319 | if (!M_CURRENT || !M_GETPREV(M_CURRENT)) 2320 | return; 2321 | client *c = M_CURRENT; 2322 | client *p = M_GETPREV(c); 2323 | list *l = c->link.parent; 2324 | rem_node(&c->link); 2325 | insert_node_before(l, &p->link, &c->link); 2326 | tile(); 2327 | } 2328 | 2329 | /* cyclic focus the next window 2330 | * if the window is the last on stack, focus head */ 2331 | void next_win() 2332 | { 2333 | client *t = M_CURRENT; 2334 | 2335 | if (!M_CURRENT || !M_GETNEXT(M_HEAD)) 2336 | return; 2337 | 2338 | while (1) { 2339 | if (!M_GETNEXT(t)) 2340 | t = M_HEAD; 2341 | else 2342 | t = M_GETNEXT(t); 2343 | if (!t->isminimized) 2344 | break; 2345 | if (t == M_CURRENT) 2346 | break; 2347 | } 2348 | 2349 | M_PREVFOCUS = M_CURRENT; 2350 | update_current(t); 2351 | } 2352 | 2353 | /* cyclic focus the previous window 2354 | * if the window is the head, focus the last stack window */ 2355 | void prev_win() 2356 | { 2357 | client *t = M_CURRENT; 2358 | 2359 | if (!M_CURRENT || !M_GETNEXT(M_HEAD)) 2360 | return; 2361 | 2362 | for (;;) { 2363 | if(!(t = M_GETPREV(t))) 2364 | t = M_TAIL; 2365 | if (!t->isminimized) 2366 | break; 2367 | if (t == M_CURRENT) 2368 | break; 2369 | } 2370 | 2371 | M_PREVFOCUS = M_CURRENT; 2372 | update_current(t); 2373 | } 2374 | 2375 | /* property notify is called when one of the window's properties 2376 | * is changed, such as an urgent hint is received 2377 | */ 2378 | void propertynotify(xcb_generic_event_t *e) 2379 | { 2380 | xcb_property_notify_event_t *ev = (xcb_property_notify_event_t *)e; 2381 | xcb_icccm_wm_hints_t wmh; 2382 | client *c; 2383 | 2384 | DEBUG("xcb: property notify"); 2385 | 2386 | #ifdef EWMH_TASKBAR 2387 | if (ev->atom == ewmh->_NET_WM_STRUT 2388 | || ev->atom == ewmh->_NET_WM_STRUT_PARTIAL) { 2389 | tile(); 2390 | return; 2391 | } 2392 | #endif /* EWMH_TASKBAR */ 2393 | 2394 | c = wintoclient(ev->window); 2395 | if (!c) 2396 | return; 2397 | else if (xcb_icccm_get_wm_hints_reply(dis, 2398 | xcb_icccm_get_wm_hints(dis, ev->window), 2399 | &wmh, NULL)) 2400 | /* TODO: error handling */ 2401 | c->isurgent = xcb_icccm_wm_hints_get_urgency(&wmh); 2402 | else if (ev->atom != XCB_ICCCM_WM_ALL_HINTS) 2403 | return; 2404 | 2405 | DEBUG("xcb: got hint!"); 2406 | desktopinfo(); 2407 | } 2408 | 2409 | /* to quit just stop receiving events 2410 | * run() is stopped and control is back to main() 2411 | */ 2412 | void quit(const Arg *arg) 2413 | { 2414 | retval = arg->i; 2415 | running = false; 2416 | } 2417 | 2418 | /* remove the specified client 2419 | * 2420 | * note, the removing client can be on any desktop, 2421 | * we must return back to the current focused desktop. 2422 | * if c was the previously focused, prevfocus must be updated 2423 | * else if c was the current one, current must be updated. */ 2424 | void removeclient(client *c) 2425 | { 2426 | int nd = 0, cd = current_desktop_number; 2427 | 2428 | if (!c) 2429 | return; 2430 | rem_node(&c->link); 2431 | if (c == M_PREVFOCUS) 2432 | M_PREVFOCUS = M_GETPREV(M_CURRENT); 2433 | if (c == M_CURRENT || !M_GETNEXT(M_HEAD)) 2434 | update_current(M_PREVFOCUS); 2435 | free(c); 2436 | c = NULL; 2437 | if (cd == nd - 1) 2438 | tile(); 2439 | else 2440 | select_desktop(cd); 2441 | } 2442 | 2443 | /* resize the master window - check for boundary size limits 2444 | * the size of a window can't be less than MINWSZ 2445 | */ 2446 | void resize_master(const Arg *arg) 2447 | { 2448 | int msz = (M_MODE == BSTACK ? M_WH : M_WW) * MASTER_SIZE + M_MASTER_SIZE + arg->i; 2449 | 2450 | if (msz < MINWSZ || (M_MODE == BSTACK ? M_WH : M_WW) - msz < MINWSZ) 2451 | return; 2452 | M_MASTER_SIZE += arg->i; 2453 | tile(); 2454 | } 2455 | 2456 | /* resize the first stack window - no boundary checks */ 2457 | void resize_stack(const Arg *arg) 2458 | { 2459 | M_GROWTH += arg->i; 2460 | tile(); 2461 | } 2462 | 2463 | /* 2464 | * resize floating windows in x-dimension (and float them if not already) 2465 | */ 2466 | void resize_x(const Arg *arg) 2467 | { 2468 | xcb_get_geometry_reply_t *r; 2469 | 2470 | if (!arg->i || !M_CURRENT) 2471 | return; 2472 | 2473 | if (!M_CURRENT->isfloating) { 2474 | float_client(M_CURRENT); 2475 | tile(); 2476 | } 2477 | 2478 | r = get_geometry(M_CURRENT->win); 2479 | if (r->width + arg->i < MINWSZ || r->width + arg->i <= 0) 2480 | return; 2481 | 2482 | r->width += arg->i; 2483 | xcb_move_resize(dis, M_CURRENT->win, r->x, r->y, r->width, r->height, &M_CURRENT->position_info); 2484 | free(r); 2485 | } 2486 | 2487 | /* 2488 | * resize floating windows in y-dimension (and float them if not already) 2489 | */ 2490 | void resize_y(const Arg *arg) 2491 | { 2492 | xcb_get_geometry_reply_t *r; 2493 | 2494 | if (!arg->i || !M_CURRENT) 2495 | return; 2496 | 2497 | if (!M_CURRENT->isfloating) { 2498 | float_client(M_CURRENT); 2499 | tile(); 2500 | } 2501 | 2502 | r = get_geometry(M_CURRENT->win); 2503 | if (r->height + arg->i < MINWSZ || r->height + arg->i <= 0) 2504 | return; 2505 | 2506 | r->height += arg->i; 2507 | xcb_move_resize(dis, M_CURRENT->win, r->x, r->y, r->width, r->height, &M_CURRENT->position_info); 2508 | free(r); 2509 | } 2510 | 2511 | /* get (the last) client from the current miniq and restore it */ 2512 | void restore_client(client *c) 2513 | { 2514 | lifo *t; 2515 | 2516 | if (!check_head(¤t_display->miniq)) 2517 | return; 2518 | 2519 | if (c == NULL) 2520 | t = (lifo *)get_head(¤t_display->miniq); 2521 | else 2522 | for (t = (lifo *)get_head(¤t_display->miniq); t && t->c != c; t = (lifo *)get_next(&t->link)) ; 2523 | if (!t) 2524 | return; 2525 | else 2526 | rem_node(&t->link); 2527 | 2528 | t->c->isminimized = false; 2529 | xcb_remove_property(dis, t->c->win, ewmh->_NET_WM_STATE, ewmh->_NET_WM_STATE_HIDDEN); 2530 | 2531 | /* 2532 | * if our window is floating, center it to move it back onto the visible 2533 | * screen, TODO: save geometry in some way to restore it where it was 2534 | * before minimizing, TODO: fix it to use centerwindow() instead of copying 2535 | * half of it 2536 | */ 2537 | if (t->c->isfloating) 2538 | centerfloating(t->c); 2539 | tile(); 2540 | update_current(t->c); 2541 | free(t); 2542 | } 2543 | 2544 | /* return true if desktop has clients */ 2545 | bool desktop_populated(desktop *d) 2546 | { 2547 | monitor *moni; 2548 | for (moni = (monitor *)get_head(&d->monitors); moni; moni = (monitor *)get_next(&moni->link)) { 2549 | display *disp; 2550 | for (disp = (display *)get_head(&moni->displays); disp; disp = (display *)get_next(&disp->link)) { 2551 | if(get_head(&disp->clients)) { 2552 | return true; 2553 | } 2554 | } 2555 | } 2556 | return false; 2557 | } 2558 | 2559 | /* restore_client(); wrapper */ 2560 | void restore() 2561 | { 2562 | restore_client(NULL); 2563 | } 2564 | 2565 | /* jump and focus the next or previous desktop */ 2566 | void rotate(const Arg *arg) 2567 | { 2568 | change_desktop(&(Arg) 2569 | {.i = (DESKTOPS + current_desktop_number + arg->i) % DESKTOPS}); 2570 | } 2571 | 2572 | /* jump and focus the next or previous desktop 2573 | * and take the current client with us. */ 2574 | void rotate_client(const Arg *arg) 2575 | { 2576 | int i = (DESKTOPS + current_desktop_number + arg->i) % DESKTOPS; 2577 | 2578 | client_to_desktop(&(Arg){.i = i}); 2579 | change_desktop(&(Arg){.i = i}); 2580 | } 2581 | 2582 | /* jump and focus the next or previous desktop that has clients */ 2583 | void rotate_filled(const Arg *arg) 2584 | { 2585 | desktop *newdesk; 2586 | 2587 | if (arg->i > 0) { /* forward */ 2588 | for (newdesk = (desktop *)get_next(¤t_desktop->link);;) { 2589 | if (!newdesk) { 2590 | newdesk = (desktop *)get_head(&desktops); 2591 | } else { 2592 | if (newdesk == current_desktop || desktop_populated(newdesk)) break; 2593 | newdesk = (desktop *)get_next(&newdesk->link); 2594 | } 2595 | } 2596 | } 2597 | else { 2598 | for (newdesk = (desktop *)get_prev(¤t_desktop->link);;) { 2599 | if (!newdesk) { 2600 | newdesk = (desktop *)get_tail(&desktops); 2601 | } else { 2602 | if (newdesk == current_desktop || desktop_populated(newdesk)) break; 2603 | newdesk = (desktop *)get_prev(&newdesk->link); 2604 | } 2605 | } 2606 | } 2607 | 2608 | if (newdesk) 2609 | change_desktop(&(Arg){.i = newdesk->num}); 2610 | } 2611 | 2612 | /* 2613 | * main event loop - on receival of an event call the appropriate event 2614 | * handler 2615 | */ 2616 | 2617 | #ifdef DEBUGGING 2618 | static char *xcb_event_str(xcb_generic_event_t *ev) 2619 | { 2620 | switch(ev->response_type & ~0x80) { 2621 | case XCB_KEY_PRESS: return "XCB_KEY_PRESS"; 2622 | case XCB_KEY_RELEASE: return "XCB_KEY_RELEASE"; 2623 | case XCB_BUTTON_PRESS: return "XCB_BUTTON_PRESS"; 2624 | case XCB_BUTTON_RELEASE: return "XCB_BUTTON_RELEASE"; 2625 | case XCB_MOTION_NOTIFY: return "XCB_MOTION_NOTIFY"; 2626 | case XCB_ENTER_NOTIFY: return "XCB_ENTER_NOTIFY"; 2627 | case XCB_LEAVE_NOTIFY: return "XCB_LEAVE_NOTIFY"; 2628 | case XCB_FOCUS_IN: return "XCB_FOCUS_IN"; 2629 | case XCB_FOCUS_OUT: return "XCB_FOCUS_OUT"; 2630 | case XCB_KEYMAP_NOTIFY: return "XCB_KEYMAP_NOTIFY"; 2631 | case XCB_EXPOSE: return "XCB_EXPOSE"; 2632 | case XCB_GRAPHICS_EXPOSURE: return "XCB_GRAPHICS_EXPOSURE"; 2633 | case XCB_NO_EXPOSURE: return "XCB_NO_EXPOSURE"; 2634 | case XCB_VISIBILITY_NOTIFY: return "XCB_VISIBILITY_NOTIFY"; 2635 | case XCB_CREATE_NOTIFY: return "XCB_CREATE_NOTIFY"; 2636 | case XCB_DESTROY_NOTIFY: return "XCB_DESTROY_NOTIFY"; 2637 | case XCB_UNMAP_NOTIFY: return "XCB_UNMAP_NOTIFY "; 2638 | case XCB_MAP_NOTIFY: return "XCB_MAP_NOTIFY"; 2639 | case XCB_MAP_REQUEST: return "XCB_MAP_REQUEST "; 2640 | case XCB_REPARENT_NOTIFY: return "XCB_REPARENT_NOTIFY"; 2641 | case XCB_CONFIGURE_NOTIFY: return "XCB_CONFIGURE_NOTIFY"; 2642 | case XCB_CONFIGURE_REQUEST: return "XCB_CONFIGURE_REQUEST"; 2643 | case XCB_GRAVITY_NOTIFY: return "XCB_GRAVITY_NOTIFY"; 2644 | case XCB_RESIZE_REQUEST: return "XCB_RESIZE_REQUEST"; 2645 | default: return "undefined event"; 2646 | } 2647 | } 2648 | #endif /* DEBUGGING */ 2649 | 2650 | void run(void) 2651 | { 2652 | xcb_generic_event_t *ev; 2653 | 2654 | while(running) { 2655 | xcb_flush(dis); 2656 | if (xcb_connection_has_error(dis)) 2657 | err(EXIT_FAILURE, "error: X11 connection got interrupted\n"); 2658 | if ((ev = xcb_wait_for_event(dis))) { 2659 | if (events[ev->response_type & ~0x80]) { 2660 | events[ev->response_type & ~0x80](ev); 2661 | } else { 2662 | DEBUGP("xcb: unimplemented event: %s\n", xcb_event_str(ev)); 2663 | } 2664 | free(ev); 2665 | } 2666 | } 2667 | } 2668 | 2669 | /* set the specified desktop's properties */ 2670 | void select_desktop(int i) 2671 | { 2672 | if (i < 0 || i >= DESKTOPS) 2673 | return; 2674 | current_desktop = find_desktop(i); 2675 | if (!current_desktop) 2676 | current_desktop = (desktop *)get_head(&desktops); 2677 | current_desktop_number = current_desktop->num; 2678 | current_monitor = (monitor *)get_head(¤t_desktop->monitors); 2679 | current_display = (display *)get_head(¤t_monitor->displays); 2680 | } 2681 | 2682 | static bool sendevent(xcb_window_t win, xcb_atom_t proto) 2683 | { 2684 | bool got = check_wmproto(win, proto); 2685 | if (got) { 2686 | xcb_client_message_event_t ev = {0}; 2687 | 2688 | ev.response_type = XCB_CLIENT_MESSAGE; 2689 | ev.window = win; 2690 | ev.format = 32; 2691 | ev.sequence = 0; 2692 | ev.type = wmatoms[WM_PROTOCOLS]; 2693 | ev.data.data32[0] = proto; 2694 | ev.data.data32[1] = XCB_CURRENT_TIME; 2695 | xcb_send_event(dis, 0, win, XCB_EVENT_MASK_NO_EVENT, (char *)&ev); 2696 | } 2697 | 2698 | return got; 2699 | } 2700 | 2701 | /* set or unset maximize state of client */ 2702 | void setmaximize(client *c, bool maximize) 2703 | { 2704 | DEBUGP("xcb: set maximize: %d\n", maximize); 2705 | 2706 | if (!c || c->isfullscreen) 2707 | return; 2708 | 2709 | int borders = client_borders(c); 2710 | borders = (!M_GETNEXT(M_HEAD) || 2711 | (M_MODE == MONOCLE && !ISFMFTM(c) && !MONOCLE_BORDERS) 2712 | ) ? 0 : borders; 2713 | xcb_border_width(dis, c->win, borders); 2714 | 2715 | if (maximize) { 2716 | xcb_move_resize(dis, c->win, M_GAPS, M_WY + M_GAPS, 2717 | M_WW - 2 * (borders + M_GAPS), 2718 | M_WH - 2 * (borders + M_GAPS), &c->position_info); 2719 | c->ismaximized = True; 2720 | } 2721 | else 2722 | c->ismaximized = False; 2723 | 2724 | update_current(c); 2725 | } 2726 | 2727 | /* get numlock modifier using xcb */ 2728 | int setup_keyboard(void) 2729 | { 2730 | xcb_get_modifier_mapping_reply_t *reply; 2731 | xcb_keycode_t *modmap; 2732 | xcb_keycode_t *numlock; 2733 | 2734 | if (!(keysyms = xcb_key_symbols_alloc(dis))) 2735 | return -1; 2736 | 2737 | reply = xcb_get_modifier_mapping_reply(dis, 2738 | xcb_get_modifier_mapping_unchecked(dis), NULL); 2739 | /* TODO: error checking */ 2740 | if (!reply) 2741 | return -1; 2742 | 2743 | modmap = xcb_get_modifier_mapping_keycodes(reply); 2744 | if (!modmap) { 2745 | free(reply); 2746 | return -1; 2747 | } 2748 | 2749 | numlock = xcb_get_keycodes(XK_Num_Lock); 2750 | if (numlock) { 2751 | for (unsigned int i = 0; i < 8; i++) { 2752 | for (unsigned int j = 0; j < reply->keycodes_per_modifier; j++) { 2753 | xcb_keycode_t keycode = 2754 | modmap[i * reply->keycodes_per_modifier + 2755 | j]; 2756 | if (keycode == XCB_NO_SYMBOL) 2757 | continue; 2758 | for (unsigned int n = 0; numlock[n] != XCB_NO_SYMBOL; n++) { 2759 | if (numlock[n] == keycode) { 2760 | DEBUGP("xcb: found num-lock %d\n", 1 << i); 2761 | numlockmask = 1 << i; 2762 | break; 2763 | } 2764 | } 2765 | } 2766 | } 2767 | free(numlock); 2768 | } 2769 | free(reply); 2770 | 2771 | 2772 | return 0; 2773 | } 2774 | 2775 | /* set or unset fullscreen state of client */ 2776 | void setfullscreen(client *c, bool fullscrn) 2777 | { 2778 | DEBUGP("xcb: set fullscreen: %d\n", fullscrn); 2779 | 2780 | if (fullscrn) { 2781 | long data[] = { ewmh->_NET_WM_STATE_FULLSCREEN }; 2782 | c->isfullscreen = True; 2783 | xcb_border_width(dis, c->win, 0); 2784 | xcb_move_resize(dis, c->win, 0, 0, screen->width_in_pixels, screen->height_in_pixels, &c->position_info); 2785 | xcb_change_property(dis, XCB_PROP_MODE_REPLACE, 2786 | c->win, ewmh->_NET_WM_STATE, 2787 | XCB_ATOM_ATOM, 32, True, data); 2788 | create_display(c); 2789 | } 2790 | else { 2791 | c->isfullscreen = False; 2792 | xcb_border_width(dis, c->win, 2793 | (!M_GETNEXT(M_HEAD) || 2794 | (M_MODE == MONOCLE && !ISFMFTM(c) && !MONOCLE_BORDERS) 2795 | ) ? 0 : client_borders(c)); 2796 | xcb_remove_property(dis, c->win, ewmh->_NET_WM_STATE, ewmh->_NET_WM_STATE_FULLSCREEN); 2797 | destroy_display(c); 2798 | } 2799 | update_current(c); 2800 | } 2801 | 2802 | /* set initial values 2803 | * root window - screen height/width - atoms - xerror handler 2804 | * set masks for reporting events handled by the wm 2805 | * and propagate the suported net atoms 2806 | */ 2807 | int setup(int default_screen) 2808 | { 2809 | xcb_intern_atom_cookie_t *cookie; 2810 | 2811 | sigchld(); 2812 | screen = xcb_screen_of_display(dis, default_screen); 2813 | if (!screen) 2814 | err(EXIT_FAILURE, "error: cannot aquire screen\n"); 2815 | setup_display(); 2816 | select_desktop(0); /* initialize global pointers */ 2817 | 2818 | #ifdef EWMH_TASKBAR 2819 | Reset_Global_Strut(); /* struts are not yet ready. */ 2820 | #endif /* EWMH_TASKBAR */ 2821 | borders = BORDER_WIDTH; 2822 | 2823 | aliens.head = NULL; 2824 | aliens.tail = NULL; 2825 | 2826 | win_focus = getcolor(FOCUS); 2827 | win_unfocus = getcolor(UNFOCUS); 2828 | win_scratch = getcolor(SCRATCH); 2829 | 2830 | /* setup keyboard */ 2831 | if (setup_keyboard() == -1) 2832 | err(EXIT_FAILURE, "error: failed to setup keyboard\n"); 2833 | 2834 | /* set up atoms for dialog/notification windows */ 2835 | xcb_get_atoms(WM_ATOM_NAME, wmatoms, WM_COUNT); 2836 | 2837 | /* check if another wm is running */ 2838 | if (xcb_checkotherwm()) 2839 | err(EXIT_FAILURE, "error: other wm is running\n"); 2840 | 2841 | /* initialize apprule regexes */ 2842 | for (unsigned int i = 0; i < LENGTH(rules); i++) 2843 | if (regcomp(&classruleregex[i], rules[i].class, 0) || 2844 | regcomp(&instanceruleregex[i], rules[i].instance, 0)) 2845 | err(EXIT_FAILURE, "error: failed to compile rule regexes\n"); 2846 | 2847 | /* initialize EWMH */ 2848 | ewmh = calloc(1, sizeof(xcb_ewmh_connection_t)); 2849 | if (!ewmh) 2850 | err(EXIT_FAILURE, "error: failed to set ewmh atoms\n"); 2851 | cookie = xcb_ewmh_init_atoms(dis, ewmh); 2852 | xcb_ewmh_init_atoms_replies(ewmh, cookie, (void *)0); 2853 | 2854 | /* set EWMH atoms */ 2855 | xcb_atom_t net_atoms[] = { ewmh->_NET_SUPPORTED, 2856 | #ifdef EWMH_TASKBAR 2857 | ewmh->_NET_CLIENT_LIST, 2858 | ewmh->_NET_WM_STRUT, 2859 | ewmh->_NET_WM_STRUT_PARTIAL, 2860 | #endif /* EWMH_TASKBAR */ 2861 | ewmh->_NET_WM_STATE_FULLSCREEN, 2862 | ewmh->_NET_WM_STATE, 2863 | ewmh->_NET_SUPPORTING_WM_CHECK, 2864 | ewmh->_NET_ACTIVE_WINDOW, 2865 | ewmh->_NET_NUMBER_OF_DESKTOPS, 2866 | ewmh->_NET_CURRENT_DESKTOP, 2867 | ewmh->_NET_DESKTOP_GEOMETRY, 2868 | ewmh->_NET_DESKTOP_VIEWPORT, 2869 | ewmh->_NET_WORKAREA, 2870 | ewmh->_NET_SHOWING_DESKTOP, 2871 | ewmh->_NET_CLOSE_WINDOW, 2872 | ewmh->_NET_WM_DESKTOP, 2873 | ewmh->_NET_WM_WINDOW_TYPE }; 2874 | 2875 | xcb_ewmh_coordinates_t viewports[2] = {{ 0, 0 }}; 2876 | /* TODO: calculate workarea properly by substracting optional panel space */ 2877 | xcb_ewmh_geometry_t workarea[2] = {{ 0, 0, M_WW, M_WH }}; 2878 | 2879 | /* functionless window required by the EWMH standard */ 2880 | uint32_t noevents = 0; 2881 | checkwin = xcb_generate_id(dis); 2882 | xcb_create_window(dis, 0, checkwin, screen->root, 0, 0, 1, 1, 0, 2883 | XCB_WINDOW_CLASS_INPUT_ONLY, 0, XCB_CW_EVENT_MASK, &noevents); 2884 | xcb_ewmh_set_wm_name(ewmh, checkwin, sizeof(WM_NAME)-1, WM_NAME); 2885 | 2886 | xcb_ewmh_set_supported(ewmh, default_screen, LENGTH(net_atoms), net_atoms); 2887 | xcb_ewmh_set_supporting_wm_check(ewmh, screen->root, checkwin); 2888 | xcb_ewmh_set_number_of_desktops(ewmh, default_screen, DESKTOPS); 2889 | xcb_ewmh_set_current_desktop(ewmh, default_screen, DEFAULT_DESKTOP); 2890 | xcb_ewmh_set_desktop_geometry(ewmh, default_screen, M_WW, M_WH); 2891 | xcb_ewmh_set_desktop_viewport(ewmh, default_screen, 1, viewports); 2892 | xcb_ewmh_set_workarea(ewmh, default_screen, 1, workarea); 2893 | xcb_ewmh_set_showing_desktop(ewmh, default_screen, 0); 2894 | 2895 | xcb_change_property(dis, XCB_PROP_MODE_REPLACE, screen->root, 2896 | ewmh->_NET_SUPPORTED, XCB_ATOM_ATOM, 32, 2897 | LENGTH(net_atoms), net_atoms); 2898 | 2899 | if (USE_SCRATCHPAD && !CLOSE_SCRATCHPAD) 2900 | scrpd_atom = xcb_internatom(dis, SCRPDNAME, 0); 2901 | else 2902 | scrpd_atom = 0; 2903 | 2904 | grabkeys(); 2905 | 2906 | /* set events */ 2907 | for (unsigned int i = 0; i < XCB_NO_OPERATION; i++) 2908 | events[i] = NULL; 2909 | events[0] = xerror; 2910 | events[XCB_BUTTON_PRESS] = buttonpress; 2911 | events[XCB_CLIENT_MESSAGE] = clientmessage; 2912 | events[XCB_CONFIGURE_REQUEST] = configurerequest; 2913 | events[XCB_DESTROY_NOTIFY] = destroynotify; 2914 | events[XCB_ENTER_NOTIFY] = enternotify; 2915 | events[XCB_KEY_PRESS] = keypress; 2916 | events[XCB_MAP_NOTIFY] = mapnotify; 2917 | events[XCB_MAP_REQUEST] = maprequest; 2918 | events[XCB_PROPERTY_NOTIFY] = propertynotify; 2919 | events[XCB_UNMAP_NOTIFY] = unmapnotify; 2920 | 2921 | /* grab existing windows */ 2922 | xcb_query_tree_reply_t *reply; 2923 | 2924 | reply = xcb_query_tree_reply(dis, xcb_query_tree(dis, screen->root), 0); 2925 | if (reply) { 2926 | int len = xcb_query_tree_children_length(reply); 2927 | xcb_window_t *children = xcb_query_tree_children(reply); 2928 | uint32_t cd = current_desktop_number; 2929 | for (int i = 0; i < len; i++) { 2930 | xcb_atom_t wtype = ewmh->_NET_WM_WINDOW_TYPE_NORMAL; 2931 | xcb_get_window_attributes_reply_t *attr; 2932 | 2933 | // if (window_is_override_redirect(children[i])) 2934 | if (check_if_window_is_alien(children[i], NULL, &wtype)) 2935 | continue; 2936 | 2937 | attr = xcb_get_window_attributes_reply(dis, 2938 | xcb_get_window_attributes(dis, children[i]), NULL); 2939 | if (!attr) 2940 | continue; 2941 | /* ignore windows in override redirect mode or with input only 2942 | * class as we won't see them */ 2943 | if (!attr->override_redirect 2944 | && attr->_class != XCB_WINDOW_CLASS_INPUT_ONLY) { 2945 | uint32_t dsk = cd; 2946 | 2947 | if (scrpd_atom && !scrpd) { 2948 | if (xcb_check_attribute(dis, children[i], scrpd_atom)) { 2949 | scrpd = create_client(children[i], wtype); 2950 | setwindefattr(scrpd->win); 2951 | grabbuttons(scrpd); 2952 | xcb_move(dis, scrpd->win, -2 * M_WW, 0, &scrpd->position_info); 2953 | showscratchpad = False; 2954 | continue; 2955 | } 2956 | } 2957 | 2958 | xcb_get_property_reply_t *prop_reply; 2959 | bool isHidden = False, doMinimize = False; 2960 | prop_reply = xcb_get_property_reply(dis, xcb_get_property_unchecked( 2961 | dis, 0, children[i], ewmh->_NET_WM_STATE, 2962 | XCB_ATOM_ATOM, 0, 1), NULL); 2963 | /* TODO: error handling */ 2964 | if (prop_reply) { 2965 | if (prop_reply->format == 32) { 2966 | xcb_atom_t *v = xcb_get_property_value(prop_reply); 2967 | for (unsigned int i = 0; i < prop_reply->value_len; i++) { 2968 | DEBUGP("%d : %d\n", i, v[i]); 2969 | if (v[i] == ewmh->_NET_WM_STATE_HIDDEN) 2970 | isHidden = True; 2971 | } 2972 | } 2973 | free(prop_reply); 2974 | } 2975 | 2976 | /* 2977 | * case 1: window has no desktop property and is unmapped --> ignore 2978 | * case 2: window has no desktop property and is mapped --> add desktop property and append to client list. 2979 | * case 3: window has current desktop property and is unmapped --> map and append to client list. 2980 | * case 4: window has desktop and hidden property -> move to miniq and append to client list. 2981 | * case 5: window has current desktop property and is mapped --> append to client list. 2982 | * case 6: window has different desktop property and is unmapped -> append to client list. 2983 | * case 7: window has different desktop property and is mapped -> unmap and append to client list. 2984 | * case 8: window has desktop property > DESKTOPS -> move window to last desktop 2985 | * case 9: window has desktop property = -1 -> TODO: sticky window support. 2986 | */ 2987 | bool case7 = False; 2988 | if (!(xcb_ewmh_get_wm_desktop_reply(ewmh, 2989 | xcb_ewmh_get_wm_desktop(ewmh, children[i]), &dsk, NULL))) { 2990 | if (attr->map_state == XCB_MAP_STATE_UNMAPPED) 2991 | continue; /* case 1 */ 2992 | else 2993 | xcb_ewmh_set_wm_desktop(ewmh, children[i], dsk = cd); /* case 2 */ 2994 | } 2995 | else { 2996 | if (isHidden) 2997 | doMinimize = True; /* case 4 */ 2998 | if ((int)dsk > DESKTOPS-1) 2999 | xcb_ewmh_set_wm_desktop(ewmh, children[i], dsk = DESKTOPS-1); /* case 8 */ 3000 | if (dsk == cd) { 3001 | if (attr->map_state == XCB_MAP_STATE_UNMAPPED) { 3002 | if (wtype == ewmh->_NET_WM_WINDOW_TYPE_NORMAL) 3003 | xcb_map_window(dis, children[i]); /* case 3 */ 3004 | else 3005 | continue; /* ignore _NET_WM_WINDOW_TYPE_DIALOG windows */ 3006 | } 3007 | else 3008 | { ; } /* case 5 */ 3009 | } 3010 | else { /* different desktop */ 3011 | if (attr->map_state == XCB_MAP_STATE_UNMAPPED) 3012 | { ; } /* case 6 */ 3013 | else 3014 | case7 = True; /* case 7 */ 3015 | } 3016 | } 3017 | 3018 | /* sane defaults */ 3019 | xcb_remove_property(dis, children[i], ewmh->_NET_WM_STATE, ewmh->_NET_WM_STATE_FULLSCREEN); 3020 | xcb_remove_property(dis, children[i], ewmh->_NET_WM_STATE, ewmh->_NET_WM_STATE_HIDDEN); 3021 | 3022 | if (cd != dsk) 3023 | select_desktop(dsk); 3024 | client *c = addwindow(children[i], wtype); 3025 | 3026 | if (doMinimize) 3027 | minimize_client(c); 3028 | if (case7) 3029 | xcb_move(dis, c->win, -2 * M_WW, 0, &c->position_info); 3030 | grabbuttons(c); 3031 | if (cd != dsk) { 3032 | xcb_move(dis, c->win, -2 * M_WW, 0, &c->position_info); 3033 | select_desktop(cd); 3034 | } 3035 | } 3036 | free(attr); 3037 | } 3038 | free(reply); 3039 | } 3040 | 3041 | change_desktop(&(Arg){.i = DEFAULT_DESKTOP}); 3042 | switch_mode(&(Arg){.i = DEFAULT_MODE}); 3043 | 3044 | /* open the scratchpad terminal if enabled */ 3045 | if (USE_SCRATCHPAD && !scrpd) 3046 | spawn(&(Arg){.com = scrpcmd}); 3047 | 3048 | #ifdef EWMH_TASKBAR 3049 | Setup_EWMH_Taskbar_Support(); 3050 | Setup_Global_Strut(); 3051 | #endif 3052 | 3053 | return 0; 3054 | } 3055 | 3056 | static void setup_display(void) 3057 | { 3058 | desktops.head = desktops.tail = NULL; 3059 | for (int d = 0; d < DESKTOPS; d++) { 3060 | desktop *desk; 3061 | if (!(desk = calloc(1, sizeof(desktop)))) 3062 | err(EXIT_FAILURE, "cannot allocate desktop"); 3063 | add_tail(&desktops, &desk->link); 3064 | desk->monitors.master = desk; 3065 | desk->num = d; 3066 | 3067 | for (int m = 0; m < MONITORS; m++) { 3068 | monitor *moni; 3069 | display *disp; 3070 | if (!(moni = calloc(1, sizeof(monitor)))) 3071 | err(EXIT_FAILURE, "cannot allocate monitor"); 3072 | add_tail(&desk->monitors, &moni->link); 3073 | moni->displays.master = moni; 3074 | 3075 | /* TODO: multi monitor support */ 3076 | moni->num = m; 3077 | moni->ww = screen->width_in_pixels; 3078 | moni->wh = screen->height_in_pixels; 3079 | #ifndef EWMH_TASKBAR 3080 | moni->wh -= PANEL_HEIGHT; 3081 | #endif /* EWMH_TASKBAR */ 3082 | 3083 | /* each monitor gets 1 default display. */ 3084 | if (!(disp = calloc(1, sizeof(display)))) 3085 | err(EXIT_FAILURE, "cannot allocate display"); 3086 | add_tail(&moni->displays, &disp->link); 3087 | disp->clients.master = disp; 3088 | 3089 | /* disp->di.master_size = MASTER_SIZE; */ 3090 | disp->di.gaps = USELESSGAP; 3091 | disp->di.mode = DEFAULT_MODE; 3092 | disp->di.showpanel = SHOW_PANEL; 3093 | disp->di.invert = INVERT; 3094 | 3095 | /* Pivot monitor support */ 3096 | if (moni->wh > moni->ww) { 3097 | if (disp->di.mode == TILE) 3098 | disp->di.mode = BSTACK; 3099 | else { 3100 | if (disp->di.mode == BSTACK) 3101 | disp->di.mode = TILE; 3102 | } 3103 | } 3104 | } 3105 | } 3106 | current_desktop = (desktop *)get_head(&desktops); 3107 | current_monitor = (monitor *)get_head(¤t_desktop->monitors); 3108 | current_display = (display *)get_head(¤t_monitor->displays); 3109 | } 3110 | 3111 | /* 3112 | * set default window attributes 3113 | */ 3114 | static void setwindefattr(xcb_window_t w) 3115 | { 3116 | unsigned int values[1] = {XCB_EVENT_MASK_PROPERTY_CHANGE| 3117 | (FOLLOW_MOUSE ? XCB_EVENT_MASK_ENTER_WINDOW : 0)}; 3118 | if (w) xcb_change_window_attributes(dis, w, XCB_CW_EVENT_MASK, values); 3119 | } 3120 | 3121 | /* 3122 | * toggle visibility of all windows in all desktops 3123 | */ 3124 | void showhide(void) 3125 | { 3126 | if ((show = !show)) { 3127 | for (client *c = (client *)get_node_head(&M_HEAD->link); c; c = M_GETNEXT(c)) 3128 | xcb_move(dis, c->win, c->position_info.previous_x, c->position_info.previous_y, &c->position_info); 3129 | tile(); 3130 | xcb_ewmh_set_showing_desktop(ewmh, default_screen, 1); 3131 | } else { 3132 | for (client *c = (client *)get_node_head(&M_HEAD->link); c; c = M_GETNEXT(c)) 3133 | xcb_move(dis, c->win, -2 * M_WW, 0, &c->position_info); 3134 | xcb_ewmh_set_showing_desktop(ewmh, default_screen, 0); 3135 | } 3136 | } 3137 | 3138 | void sigchld() 3139 | { 3140 | if (signal(SIGCHLD, sigchld) == SIG_ERR) 3141 | err(EXIT_FAILURE, "cannot install SIGCHLD handler"); 3142 | while (0 < waitpid(-1, NULL, WNOHANG)); 3143 | } 3144 | 3145 | /* execute a command, save the child pid if we start the scratchpad */ 3146 | void spawn(const Arg *arg) 3147 | { 3148 | if (fork()) 3149 | return; 3150 | if (dis) 3151 | close(screen->root); 3152 | setsid(); 3153 | execvp((char *)arg->com[0], (char **)arg->com); 3154 | err(EXIT_SUCCESS, "error: execvp %s", (char *)arg->com[0]); 3155 | } 3156 | 3157 | /* arrange windows in normal or bottom stack tile */ 3158 | void stack(int hh, int cy) 3159 | { 3160 | client *c = NULL, *t = NULL; bool b = M_MODE == BSTACK; 3161 | int n = 0, d = 0, z = b ? M_WW : hh, 3162 | ma = (M_MODE == BSTACK ? M_WH : M_WW) * MASTER_SIZE + M_MASTER_SIZE; 3163 | 3164 | /* count stack windows and grab first non-floating, non-maximize window */ 3165 | for (t = M_HEAD; t; t = M_GETNEXT(t)) { 3166 | if (!ISFMFTM(t)) { 3167 | if (c) 3168 | ++n; 3169 | else 3170 | c = t; 3171 | } 3172 | } 3173 | 3174 | /* 3175 | * if there is only one window, it should cover the available screen space 3176 | * if there is only one stack window (n == 1) then we don't care about 3177 | * growth if more than one stack windows (n > 1) on screen then adjustments 3178 | * may be needed 3179 | * - d is the num of pixels than remain when spliting 3180 | * the available width/height to the number of windows 3181 | * - z is the clients' height/width 3182 | * 3183 | * ---------- -. --------------------. 3184 | * | |----| --|--> growth `}--> first client will get 3185 | * | | | | | (z+d) height/width 3186 | * | |----| }--> screen height - hh --' 3187 | * | | | }-|--> client height - z 3188 | * ---------- -' 3189 | * 3190 | * -> piece of art by c00kiemon5ter o.O om nom nom nom nom 3191 | * 3192 | * what we do is, remove the growth from the screen height : (z - 3193 | * growth) and then divide that space with the windows on the stack : 3194 | * (z - growth)/n so all windows have equal height/width (z) 3195 | * : growth is left out and will later be added to the first's client 3196 | * height/width before that, there will be cases when the num of 3197 | * windows is not perfectly divided with then available screen 3198 | * height/width (ie 100px scr. height, and 3 windows) so we get that 3199 | * remaining space and merge growth to it (d) : (z - growth) % n + 3200 | * growth finally we know each client's height, and how many pixels 3201 | * should be added to the first stack window so that it satisfies 3202 | * growth, and doesn't create gaps 3203 | * on the bottom of the screen. 3204 | */ 3205 | if (!c) { 3206 | return; 3207 | } else if (!n) { 3208 | int borders = client_borders(c); 3209 | xcb_move_resize(dis, c->win, M_GAPS, cy + M_GAPS, 3210 | M_WW - 2 * (borders + M_GAPS), 3211 | hh - 2 * (borders + M_GAPS), &c->position_info); 3212 | return; 3213 | } else if (n > 1) { 3214 | d = (z - M_GROWTH) % n + M_GROWTH; z = (z - M_GROWTH) / n; 3215 | } 3216 | 3217 | /* tile the first non-floating, non-maximize window to cover the master area */ 3218 | int borders = client_borders(c); 3219 | if (b) 3220 | xcb_move_resize(dis, c->win, M_GAPS, 3221 | M_INVERT ? (cy + hh - ma + M_GAPS) : (cy + M_GAPS), 3222 | M_WW - 2 * (borders + M_GAPS), 3223 | ma - 2 * (borders + M_GAPS), &c->position_info); 3224 | else 3225 | xcb_move_resize(dis, c->win, M_INVERT ? (M_WW - ma + M_GAPS) : M_GAPS, 3226 | cy + M_GAPS, 3227 | ma - 2 * (borders + M_GAPS), 3228 | hh - 2 * (borders + M_GAPS), &c->position_info); 3229 | 3230 | /* tile the next non-floating, non-maximize (first) stack window with growth|d */ 3231 | for (c = M_GETNEXT(c); c && ISFMFTM(c); c = M_GETNEXT(c)); 3232 | borders = client_borders(c); 3233 | int cx = b ? 0 : (M_INVERT ? M_GAPS : ma), 3234 | cw = (b ? hh : M_WW) - 2 * borders - ma - M_GAPS, 3235 | ch = z - 2 * borders - M_GAPS; 3236 | if (b) 3237 | xcb_move_resize(dis, c->win, cx += M_GAPS, cy += M_INVERT ? M_GAPS : ma, 3238 | ch - M_GAPS + d, cw, &c->position_info); 3239 | else 3240 | xcb_move_resize(dis, c->win, cx, cy += M_GAPS, cw, ch - M_GAPS + d, &c->position_info); 3241 | 3242 | /* tile the rest of the non-floating, non-maximize stack windows */ 3243 | for (b ? (cx += z + d - M_GAPS) : (cy += z + d - M_GAPS), 3244 | c = M_GETNEXT(c); c; c = M_GETNEXT(c)) { 3245 | if (ISFMFTM(c)) 3246 | continue; 3247 | if (b) { 3248 | xcb_move_resize(dis, c->win, cx, cy, ch, cw, &c->position_info); cx += z; 3249 | } else { 3250 | xcb_move_resize(dis, c->win, cx, cy, cw, ch, &c->position_info); cy += z; 3251 | } 3252 | } 3253 | } 3254 | 3255 | /* swap master window with current or 3256 | * if current is head swap with next 3257 | * if current is not head, then head 3258 | * is behind us, so move_up until we 3259 | * are the head */ 3260 | void swap_master() 3261 | { 3262 | if (!M_CURRENT || !M_GETNEXT(M_HEAD)) 3263 | return; 3264 | if (M_CURRENT == M_HEAD) { 3265 | client *c = M_GETNEXT(M_HEAD); 3266 | rem_node(&c->link); 3267 | add_head(¤t_display->clients, &c->link); 3268 | } 3269 | else { 3270 | client *c = M_CURRENT; 3271 | rem_node(&c->link); 3272 | add_head(¤t_display->clients, &c->link); 3273 | } 3274 | update_current(M_HEAD); 3275 | } 3276 | 3277 | /* switch the tiling mode and reset all floating windows */ 3278 | void switch_mode(const Arg *arg) 3279 | { 3280 | if (!show) 3281 | showhide(); 3282 | if (M_MODE == arg->i) 3283 | for (client *c = M_HEAD; c; c = M_GETNEXT(c)) 3284 | unfloat_client(c); 3285 | M_MODE = arg->i; 3286 | tile(); 3287 | update_current(M_CURRENT); 3288 | desktopinfo(); 3289 | } 3290 | 3291 | 3292 | /* cycle the tiling mode and reset all floating windows */ 3293 | void rotate_mode(const Arg *arg) 3294 | { 3295 | if (!show) 3296 | showhide(); 3297 | M_MODE = (M_MODE + arg->i + MODES) % MODES; 3298 | tile(); 3299 | update_current(M_CURRENT); 3300 | desktopinfo(); 3301 | } 3302 | 3303 | /* tile all windows of current desktop - call the handler tiling function */ 3304 | void tile(void) 3305 | { 3306 | desktopinfo(); 3307 | if (!M_HEAD) 3308 | return; /* nothing to arange */ 3309 | #ifndef EWMH_TASKBAR 3310 | layout[M_GETNEXT(M_HEAD) ? M_MODE : MONOCLE](M_WH + (M_SHOWPANEL ? 0 : PANEL_HEIGHT), 3311 | (TOP_PANEL && M_SHOWPANEL ? PANEL_HEIGHT : 0)); 3312 | #else 3313 | Update_Global_Strut(); 3314 | layout[M_GETNEXT(M_HEAD) ? M_MODE : MONOCLE](M_WH, M_WY); 3315 | #endif /* EWMH_TASKBAR */ 3316 | } 3317 | 3318 | /* reset the active window from floating to tiling, if not already */ 3319 | void tilemize() 3320 | { 3321 | if (!M_CURRENT || !M_CURRENT->isfloating) 3322 | return; 3323 | unfloat_client(M_CURRENT); 3324 | update_current(M_CURRENT); 3325 | } 3326 | 3327 | /* toggle visibility state of the panel */ 3328 | void togglepanel() 3329 | { 3330 | M_SHOWPANEL = !M_SHOWPANEL; 3331 | tile(); 3332 | } 3333 | 3334 | /* 3335 | * Toggle the scratchpad terminal, also attempt to reopen it if it is 3336 | * not present. 3337 | */ 3338 | void togglescratchpad() 3339 | { 3340 | if (!USE_SCRATCHPAD) { 3341 | return; 3342 | } else if (!scrpd) { 3343 | spawn(&(Arg){.com = scrpcmd}); 3344 | showscratchpad = false; 3345 | if (!scrpd) 3346 | return; 3347 | } 3348 | 3349 | showscratchpad = !showscratchpad; 3350 | 3351 | if (showscratchpad) { 3352 | xcb_get_geometry_reply_t *wa = get_geometry(scrpd->win); 3353 | xcb_move(dis, scrpd->win, (M_WW - wa->width) / 2, (M_WH - wa->height) / 2, &scrpd->position_info); 3354 | free(wa); 3355 | update_current(scrpd); 3356 | xcb_raise_window(dis, scrpd->win); 3357 | } else { 3358 | xcb_move(dis, scrpd->win, -2 * M_WW, 0, &scrpd->position_info); 3359 | if(M_CURRENT == scrpd) { 3360 | if(!M_PREVFOCUS) 3361 | update_current(M_HEAD); 3362 | else 3363 | update_current(M_PREVFOCUS->isminimized ? M_HEAD : M_PREVFOCUS); 3364 | } 3365 | } 3366 | } 3367 | 3368 | /* tile a floating client and save its size for re-floating */ 3369 | void unfloat_client(client *c) 3370 | { 3371 | if (!c) 3372 | return; 3373 | 3374 | c->isfloating = false; 3375 | 3376 | xcb_get_geometry_reply_t *r = get_geometry(c->win); 3377 | c->dim[0] = r->width; 3378 | c->dim[1] = r->height; 3379 | free(r); 3380 | } 3381 | 3382 | static inline bool on_current_desktop(client *c) { 3383 | client *p; 3384 | for (p = M_HEAD; p && p != c; p = M_GETNEXT(p)) 3385 | ; 3386 | return (p != NULL); 3387 | } 3388 | 3389 | /* windows that request to unmap should lose their 3390 | * client, so no invisible windows exist on screen 3391 | */ 3392 | void unmapnotify(xcb_generic_event_t *e) 3393 | { 3394 | xcb_unmap_notify_event_t *ev = (xcb_unmap_notify_event_t *)e; 3395 | client *c = wintoclient(ev->window); 3396 | 3397 | DEBUG("xcb: unmap notify"); 3398 | 3399 | if (c && on_current_desktop(c)) { 3400 | if (c->isfullscreen) 3401 | destroy_display(c); 3402 | removeclient(c); 3403 | desktopinfo(); 3404 | } 3405 | } 3406 | 3407 | /* 3408 | * highlight borders and set active window and input focus 3409 | * if given current is NULL then delete the active window property 3410 | * 3411 | * stack order by client properties, top to bottom: 3412 | * - current when floating or transient 3413 | * - floating or trancient windows 3414 | * - current when tiled 3415 | * - current when maximized 3416 | * - maximized windows 3417 | * - tiled windows 3418 | * 3419 | * a window should have borders in any case, except if 3420 | * - the window is the only window on screen 3421 | * - the window is maximized 3422 | * - the mode is MONOCLE and the window is not floating or transient 3423 | * and MONOCLE_BORDERS is set to false 3424 | */ 3425 | static inline void nada(void) 3426 | { 3427 | xcb_delete_property(dis, screen->root, ewmh->_NET_ACTIVE_WINDOW); 3428 | xcb_set_input_focus(dis, XCB_INPUT_FOCUS_POINTER_ROOT, screen->root, XCB_CURRENT_TIME); 3429 | M_PREVFOCUS = M_CURRENT = NULL; 3430 | } 3431 | void update_current(client *newfocus) // newfocus may be NULL 3432 | { 3433 | if(!M_HEAD && USE_SCRATCHPAD && !showscratchpad) { // empty desktop. no clients, no scratchpad. 3434 | nada(); 3435 | return; 3436 | } 3437 | 3438 | if(!newfocus) { 3439 | if(M_PREVFOCUS) 3440 | M_CURRENT = M_PREVFOCUS; 3441 | else 3442 | M_CURRENT = M_HEAD; 3443 | M_PREVFOCUS = M_GETPREV(M_CURRENT); // get previous client in list, may be NULL 3444 | } 3445 | else { 3446 | if(newfocus == M_PREVFOCUS) { 3447 | M_CURRENT = M_PREVFOCUS; 3448 | M_PREVFOCUS = M_GETPREV(M_CURRENT); // get previous client in list, may be NULL 3449 | } 3450 | else { 3451 | M_PREVFOCUS = M_CURRENT; 3452 | M_CURRENT = newfocus; 3453 | } 3454 | } 3455 | 3456 | if(!M_CURRENT && (USE_SCRATCHPAD && showscratchpad && scrpd)) // focus scratchpad, if visible 3457 | M_CURRENT = scrpd; 3458 | 3459 | if(!M_CURRENT) { // there is really really really nothing to focus. 3460 | nada(); 3461 | return; 3462 | } 3463 | 3464 | for (client *c = M_HEAD; c; c = M_GETNEXT(c)) { 3465 | if (!c->isfullscreen) { 3466 | xcb_change_window_attributes(dis, c->win, XCB_CW_BORDER_PIXEL, 3467 | (c == M_CURRENT ? &win_focus : &win_unfocus)); 3468 | xcb_border_width(dis, c->win, ((!MONOCLE_BORDERS && !M_GETNEXT(M_HEAD)) 3469 | || (M_MODE == MONOCLE && !ISFMFTM(c) && !MONOCLE_BORDERS) 3470 | ) ? 0 : client_borders(c)); 3471 | } 3472 | } 3473 | 3474 | if (USE_SCRATCHPAD && SCRATCH_WIDTH && showscratchpad && scrpd) { 3475 | xcb_change_window_attributes(dis, scrpd->win, XCB_CW_BORDER_PIXEL, 3476 | (M_CURRENT == scrpd ? &win_scratch : &win_unfocus)); 3477 | xcb_border_width(dis, scrpd->win, SCRATCH_WIDTH); 3478 | 3479 | } 3480 | 3481 | tile(); 3482 | 3483 | for (client *c = M_HEAD; c; c = M_GETNEXT(c)) { 3484 | if (c->isfullscreen) { 3485 | // xcb_border_width(dis, c->win, 0); 3486 | xcb_lower_window(dis, c->win); 3487 | // xcb_move_resize(dis, c->win, 0, 0, 3488 | // screen->width_in_pixels, screen->height_in_pixels, &c->position_info); 3489 | break; 3490 | } 3491 | } 3492 | 3493 | client *rl = NULL; 3494 | for (client *c = M_HEAD; c; c = M_GETNEXT(c)) { 3495 | if (c->ismaximized || c->isfloating || c->istransient || c->type != ewmh->_NET_WM_WINDOW_TYPE_NORMAL) { 3496 | if (c == M_CURRENT) { 3497 | rl = c; 3498 | continue; 3499 | } 3500 | } 3501 | xcb_raise_window(dis, c->win); 3502 | } 3503 | if(rl) 3504 | xcb_raise_window(dis, rl->win); 3505 | 3506 | if (USE_SCRATCHPAD && showscratchpad && scrpd) 3507 | xcb_raise_window(dis, scrpd->win); 3508 | 3509 | if (check_head(&aliens)) { 3510 | alien *a; 3511 | for (a=(alien *)get_head(&aliens); a; a=(alien *)get_next(&a->link)) { 3512 | if (M_CURRENT->isfullscreen 3513 | && a->type != ewmh->_NET_WM_WINDOW_TYPE_NOTIFICATION) 3514 | continue; 3515 | xcb_raise_window(dis, a->win); 3516 | } 3517 | } 3518 | 3519 | if (M_CURRENT) { 3520 | if (M_CURRENT->setfocus) { 3521 | xcb_change_property(dis, XCB_PROP_MODE_REPLACE, screen->root, 3522 | ewmh->_NET_ACTIVE_WINDOW, XCB_ATOM_WINDOW, 32, 1, 3523 | &M_CURRENT->win); 3524 | xcb_set_input_focus(dis, XCB_INPUT_FOCUS_POINTER_ROOT, M_CURRENT->win, 3525 | XCB_CURRENT_TIME); 3526 | DEBUG("xcb_set_input_focus();"); 3527 | } 3528 | else { 3529 | sendevent(M_CURRENT->win, wmatoms[WM_TAKE_FOCUS]); 3530 | DEBUG("send WM_TAKE_FOCUS"); 3531 | } 3532 | } 3533 | } 3534 | 3535 | static alien *wintoalien(list *l, xcb_window_t win) 3536 | { 3537 | alien *t; 3538 | if (!l || !win) 3539 | return NULL; 3540 | 3541 | for (t=(alien *)get_head(l); t; t=(alien *)get_next((node *)t)) { 3542 | if (t->win == win) 3543 | break; 3544 | } 3545 | return t; 3546 | } 3547 | 3548 | /* find to which client the given window belongs to */ 3549 | client *wintoclient(xcb_window_t win) 3550 | { 3551 | desktop *desk; 3552 | for (desk = (desktop *)get_head(&desktops); desk; desk = (desktop *)get_next(&desk->link)) { 3553 | monitor *moni; 3554 | for (moni = (monitor *)get_head(&desk->monitors); moni; moni = (monitor *)get_next(&moni->link)) { 3555 | display *disp; 3556 | for (disp = (display *)get_head(&moni->displays); disp; disp = (display *)get_next(&disp->link)) { 3557 | client *c; 3558 | for (c = (client *)get_head(&disp->clients); c; c = (client *)get_next(&c->link)) { 3559 | if (c->win == win) { 3560 | return c; 3561 | } 3562 | } 3563 | } 3564 | } 3565 | } 3566 | return NULL; 3567 | } 3568 | 3569 | void xerror(xcb_generic_event_t *e) 3570 | { 3571 | #ifdef DEBUGGING 3572 | xcb_generic_error_t *error = (xcb_generic_error_t *)e; 3573 | DEBUGP("X error: %i, %i:%i [%i]\n", error->error_code, 3574 | (int)error->major_code, (int)error->minor_code, 3575 | (int)error->resource_id); 3576 | #else 3577 | if(e){;} /* silencing gcc warning */ 3578 | #endif /* DEBUGGING */ 3579 | } 3580 | 3581 | static void ungrab_focus(void) 3582 | { 3583 | Display * dpy; 3584 | 3585 | 3586 | // if use xcb_set_input_focus(dis, XCB_INPUT_FOCUS_POINTER_ROOT, screen->root, XCB_CURRENT_TIME); 3587 | // then the focus gets frozen to one window, and there's no way to set focus to different window. 3588 | // if set focus to PointerRoot, then focus follows mouse after quitting the window manager. 3589 | // TODO: convert to xcb 3590 | 3591 | if ((dpy = XOpenDisplay(0x0))) { 3592 | XSetInputFocus(dpy, PointerRoot, RevertToNone, CurrentTime); 3593 | XCloseDisplay(dpy); 3594 | } 3595 | 3596 | } 3597 | int main(int argc, char *argv[]) 3598 | { 3599 | setvbuf(stdout, NULL, _IOLBF, 0); 3600 | setvbuf(stderr, NULL, _IOLBF, 0); 3601 | if (argc == 2 && argv[1][0] == '-') switch (argv[1][1]) { 3602 | case 'v': 3603 | errx(EXIT_SUCCESS, 3604 | "FrankenWM - by sulami (thanks to c00kiemon5ter and Cloudef)"); 3605 | case 'h': 3606 | errx(EXIT_SUCCESS, "%s", USAGE); 3607 | default: 3608 | errx(EXIT_FAILURE, "%s", USAGE); 3609 | } else if (argc != 1) { 3610 | errx(EXIT_FAILURE, "%s", USAGE); 3611 | } 3612 | if (xcb_connection_has_error((dis = xcb_connect(NULL, &default_screen)))) 3613 | errx(EXIT_FAILURE, "error: cannot open display\n"); 3614 | DEBUG("connected to display"); 3615 | if (setup(default_screen) != -1) { 3616 | desktopinfo(); /* zero out every desktop on (re)start */ 3617 | run(); 3618 | } 3619 | cleanup(); 3620 | xcb_aux_sync(dis); 3621 | xcb_disconnect(dis); 3622 | ungrab_focus(); 3623 | return retval; 3624 | } 3625 | 3626 | #ifdef EWMH_TASKBAR 3627 | /* 3628 | * Optional EWMH Taskbar (Panel) Support functions 3629 | */ 3630 | 3631 | static void Setup_EWMH_Taskbar_Support(void) 3632 | { 3633 | /* 3634 | * initial _NET_CLIENT_LIST property 3635 | */ 3636 | Update_EWMH_Taskbar_Properties(); 3637 | 3638 | xcb_change_property(dis, XCB_PROP_MODE_REPLACE, screen->root, 3639 | ewmh->_NET_DESKTOP_NAMES, ewmh->UTF8_STRING, 8, 0, NULL); 3640 | } 3641 | 3642 | static void Cleanup_EWMH_Taskbar_Support(void) 3643 | { 3644 | /* 3645 | * set _NET_CLIENT_LIST property to zero 3646 | */ 3647 | xcb_window_t empty = 0; 3648 | xcb_change_property(dis, XCB_PROP_MODE_REPLACE, 3649 | screen->root, ewmh->_NET_CLIENT_LIST, 3650 | XCB_ATOM_WINDOW, 32, 0, &empty); 3651 | } 3652 | 3653 | static inline void Update_EWMH_Taskbar_Properties(void) 3654 | { 3655 | /* 3656 | * update _NET_CLIENT_LIST property, may be empty 3657 | */ 3658 | 3659 | xcb_window_t *wins; 3660 | client *c; 3661 | int num=0; 3662 | 3663 | if(showscratchpad && scrpd) 3664 | num++; 3665 | for (c = M_HEAD; c; c = M_GETNEXT(c)) 3666 | num++; 3667 | 3668 | if((wins = (xcb_window_t *)calloc(num, sizeof(xcb_window_t)))) 3669 | { 3670 | num = 0; 3671 | if(showscratchpad && scrpd) 3672 | wins[num++] = scrpd->win; 3673 | for (c = M_HEAD; c; c = M_GETNEXT(c)) 3674 | wins[num++] = c->win; 3675 | xcb_change_property(dis, XCB_PROP_MODE_REPLACE, 3676 | screen->root, ewmh->_NET_CLIENT_LIST, 3677 | XCB_ATOM_WINDOW, 32, num, wins); 3678 | xcb_flush(dis); 3679 | free(wins); 3680 | DEBUGP("update _NET_CLIENT_LIST property (%d entries)\n", num); 3681 | } 3682 | } 3683 | #endif /* EWMH_TASKBAR */ 3684 | 3685 | 3686 | #ifdef EWMH_TASKBAR 3687 | static void Setup_Global_Strut(void) 3688 | { 3689 | Update_Global_Strut(); 3690 | } 3691 | 3692 | static void Cleanup_Global_Strut(void) 3693 | { 3694 | } 3695 | 3696 | static inline void Reset_Global_Strut(void) 3697 | { 3698 | gstrut.left = gstrut.right = gstrut.top = gstrut.bottom = 0; 3699 | M_WW = screen->width_in_pixels; 3700 | M_WH = screen->height_in_pixels; 3701 | M_WY = 0; 3702 | } 3703 | 3704 | static void Update_Global_Strut(void) 3705 | { 3706 | /* TODO(?): Struts for each desktop. */ 3707 | 3708 | Reset_Global_Strut(); 3709 | 3710 | /* grab existing windows */ 3711 | xcb_query_tree_reply_t *reply = xcb_query_tree_reply(dis, 3712 | xcb_query_tree(dis, screen->root), 0); 3713 | if (reply) { 3714 | int len = xcb_query_tree_children_length(reply); 3715 | xcb_window_t *children = xcb_query_tree_children(reply); 3716 | bool gstrut_modified = False; 3717 | for (int i = 0; i < len; i++) { 3718 | xcb_get_window_attributes_reply_t *attr = xcb_get_window_attributes_reply(dis, 3719 | xcb_get_window_attributes(dis, children[i]), NULL); 3720 | if (!attr) 3721 | continue; 3722 | 3723 | if (!(attr->map_state == XCB_MAP_STATE_UNMAPPED)) { 3724 | void *data; 3725 | xcb_get_property_reply_t *strut_r; 3726 | /* 3727 | * Read newer _NET_WM_STRUT_PARTIAL property first. Only the first 4 values. 3728 | * Fall back to older _NET_WM_STRUT property. 3729 | */ 3730 | strut_r = xcb_get_property_reply(dis, 3731 | xcb_get_property_unchecked(dis, false, children[i], 3732 | ewmh->_NET_WM_STRUT_PARTIAL, XCB_ATOM_CARDINAL, 0, 4), NULL); 3733 | if (strut_r->type == XCB_NONE) { 3734 | strut_r = xcb_get_property_reply(dis, 3735 | xcb_get_property_unchecked(dis, false, children[i], 3736 | ewmh->_NET_WM_STRUT, XCB_ATOM_CARDINAL, 0, 4), NULL); 3737 | } 3738 | if(strut_r && strut_r->value_len && (data = xcb_get_property_value(strut_r))) 3739 | { 3740 | uint32_t *strut = data; 3741 | if (gstrut.top < strut[2]) { 3742 | gstrut.top = strut[2]; 3743 | gstrut_modified = True; 3744 | } 3745 | if (gstrut.bottom < strut[3]) { 3746 | gstrut.bottom = strut[3]; 3747 | gstrut_modified = True; 3748 | } 3749 | } 3750 | } 3751 | free(attr); 3752 | } 3753 | free(reply); 3754 | if (gstrut_modified) { 3755 | M_WW = screen->width_in_pixels; 3756 | M_WH = screen->height_in_pixels; 3757 | M_WH -= gstrut.top; 3758 | M_WH -= gstrut.bottom; 3759 | M_WY = gstrut.top; 3760 | } 3761 | } 3762 | } 3763 | #endif /* EWMH_TASKBAR */ 3764 | 3765 | /* vim: set ts=4 sw=4 expandtab :*/ 3766 | --------------------------------------------------------------------------------