├── LICENSE ├── Makefile ├── README.md ├── config.h ├── config.mk ├── examples ├── Xresources ├── sxhkd ├── unminimize.sh └── wmr ├── shod.1 ├── shod.c ├── shod.h └── theme.xpm /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | © 2020 phillbush 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include config.mk 2 | 3 | SRCS = ${PROG}.c 4 | OBJS = ${SRCS:.c=.o} 5 | 6 | all: ${PROG} 7 | 8 | ${PROG}: ${OBJS} 9 | ${CC} -o $@ ${OBJS} ${LDFLAGS} 10 | 11 | ${OBJS}: shod.h config.h theme.xpm 12 | 13 | .c.o: 14 | ${CC} ${CFLAGS} -c $< 15 | 16 | clean: 17 | -rm ${OBJS} ${PROG} 18 | 19 | install: all 20 | install -D -m 755 ${PROG} ${DESTDIR}${PREFIX}/bin/${PROG} 21 | install -D -m 644 ${PROG}.1 ${DESTDIR}${MANPREFIX}/man1/${PROG}.1 22 | 23 | uninstall: 24 | rm -f ${DESTDIR}/${PREFIX}/bin/${PROG} 25 | rm -f ${DESTDIR}/${MANPREFIX}/man1/${PROG}.1 26 | 27 | .PHONY: all clean install uninstall 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Development of shod (or, as I call it now, shod1) is stagnant in favor 2 | of [shod2](https://github.com/phillbush/shod2), and may be discontinued 3 | in the future.** 4 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | struct Config config = { 2 | /* font, in the old X Logical Font Description style */ 3 | .font = "-misc-fixed-medium-r-semicondensed--13-120-75-75-c-60-iso8859-1", 4 | 5 | /* gaps */ 6 | .gapinner = 0, /* gap between windows */ 7 | .gapouter = 0, /* gap between window and screen edges */ 8 | 9 | /* behavior of general windows */ 10 | .hidetitle = 0, /* whether to hide title bar */ 11 | 12 | /* behavior of tiled windows */ 13 | .ignoregaps = 0, /* whether to ignore outer gaqps when a single window is tiled */ 14 | .ignoretitle = 0, /* whether to ignore title bar when a single window is tiled */ 15 | .ignoreborders = 0, /* whether to ignore borders when a single window is tiled */ 16 | .mergeborders = 0, /* whether to merge borders of tiled windows */ 17 | 18 | /* whether to ignore requests from indirect sources, accept only direct requests */ 19 | .ignoreindirect = 0, 20 | 21 | /* mouse control (these configuration cannot be set via X resources) */ 22 | .modifier = Mod1Mask, /* modifier pressed with mouse button */ 23 | .focusbuttons = 1, /* bit mask of mouse buttons that focus windows */ 24 | .raisebuttons = 1, /* bit mask of mouse buttons that raise windows */ 25 | 26 | /* number of desktops */ 27 | .ndesktops = 10, 28 | 29 | /* notification placement */ 30 | .notifgravity = "NE", /* where in the screen to place notification windows */ 31 | .notifgap = 3, /* gap between notification windows */ 32 | }; 33 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | #program name 2 | PROG = shod 3 | 4 | # paths 5 | PREFIX = /usr/local 6 | MANPREFIX = ${PREFIX}/share/man 7 | 8 | LOCALINC = /usr/local/include 9 | LOCALLIB = /usr/local/lib 10 | 11 | X11INC = /usr/X11R6/include 12 | X11LIB = /usr/X11R6/lib 13 | 14 | # includes and libs 15 | INCS = -I${LOCALINC} -I${X11INC} 16 | LIBS = -L${LOCALLIB} -L${X11LIB} -lX11 -lXinerama -lXpm 17 | 18 | # flags 19 | CFLAGS = -g -O0 -Wall -Wextra ${INCS} ${CPPFLAGS} 20 | LDFLAGS = ${LIBS} 21 | 22 | # compiler and linker 23 | CC = cc 24 | -------------------------------------------------------------------------------- /examples/Xresources: -------------------------------------------------------------------------------- 1 | shod.gapOuter: 7 2 | shod.gapInner: 7 3 | -------------------------------------------------------------------------------- /examples/sxhkd: -------------------------------------------------------------------------------- 1 | # Start terminal 2 | mod1 + Return 3 | xterm 4 | 5 | # Killing windows 6 | mod1 + shift + q 7 | wmctrl -c :ACTIVE: 8 | 9 | # Workspace 10 | mod1 + {1,2,3,4,5,6,7,8,9,0} 11 | wmctrl -s {0,1,2,3,4,5,6,7,8,9} 12 | mod1 + shift + {1,2,3,4,5,6,7,8,9} 13 | wmctrl -r :ACITVE: -t {0,1,2,3,4,5,6,7,8,9} 14 | 15 | # Resize/move windows with wmr 16 | mod1 + {c, v, shift + c, shift + v} 17 | wmr 0 0 {-25 0, 0 -25, +25 0, 0 +25} 18 | mod1 + shift + {h, j, k, l} 19 | wmr {-10 0, 0 10, 0 -10, 10 0} 0 0 20 | 21 | # Change window status to sticky/above/below/minimized/fullscreen/maximized 22 | mod1 + shift + {s, a, b, z, f} 23 | wmctrl -r :ACTIVE: -b toggle,{sticky,above,below,hidden,fullscreen} 24 | mod1 + shift + t 25 | wmctrl -r :ACTIVE: -b toggle,maximized_vert,maximized_horz 26 | 27 | # Call the unminimize.sh script 28 | mod1 + shift + u 29 | unminimize.sh 30 | -------------------------------------------------------------------------------- /examples/unminimize.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | lsw() { 4 | xprop -notype -f "_NET_CLIENT_LIST" 0x ' $0+\n' -root "_NET_CLIENT_LIST" |\ 5 | cut -d' ' -f2- |\ 6 | sed 's/, */\ 7 | /g' 8 | } 9 | 10 | ishidden() { 11 | xprop -notype -f "_NET_WM_STATE" 32a ' $0+\n' -id "$1" "_NET_WM_STATE" |\ 12 | cut -d' ' -f2- |\ 13 | sed 's/, */\ 14 | /g' | grep -q "_NET_WM_STATE_HIDDEN" 15 | } 16 | 17 | printname() { 18 | name="$(xprop -notype -f "_NET_WM_NAME" 8s ' $0+\n' -id "$1" "_NET_WM_NAME" 2>/dev/null)" 19 | [ "$(echo $name)" = "_NET_WM_NAME: not found." ] && name="$(xprop -notype -f "WM_NAME" 8s ' $0+\n' -id "$1" "WM_NAME" 2>/dev/null)" 20 | 21 | echo $name |\ 22 | cut -d' ' -f2- |\ 23 | sed 's/, */\ 24 | /g' 25 | } 26 | 27 | for win in $(lsw) 28 | do 29 | ishidden $win && printf "%s: " $win && printname $win 30 | done |\ 31 | dmenu -i -l 8 -p "unhide window:" |\ 32 | cut -d: -f1 |\ 33 | xargs wmctrl -b toggle,hidden -ir 34 | -------------------------------------------------------------------------------- /examples/wmr: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # wmr: move and resize window relative to its current position and size 3 | 4 | set -e 5 | 6 | usage() { 7 | echo "usage: wmr x y w h" >&2 8 | exit 1 9 | } 10 | 11 | [ $# -ne 4 ] && usage 12 | eval $(xdotool getactivewindow getwindowgeometry --shell) 13 | xadd=$1 14 | yadd=$2 15 | wadd=$3 16 | hadd=$4 17 | X=$(( X + xadd )) 18 | Y=$(( Y + yadd )) 19 | WIDTH=$(( WIDTH + wadd )) 20 | HEIGHT=$(( HEIGHT + hadd )) 21 | wmctrl -r :ACTIVE: -e 0,$X,$Y,$WIDTH,$HEIGHT 22 | -------------------------------------------------------------------------------- /shod.1: -------------------------------------------------------------------------------- 1 | .TH SHOD 1 2 | .SH NAME 3 | .B shod 4 | \- hybrid window manager 5 | .SH SYNOPSIS 6 | .B shod 7 | .SH DESCRIPTION 8 | .B shod 9 | is a hybrid (tiling and floating) reparenting X11 window manager which supports tabs, multiple monitors, themes, and window rules. 10 | .B shod 11 | sets no keybindings; 12 | reads no configuration other than X resources; 13 | and works only via mouse with a given key modifier (Alt by default) 14 | and by responding to client messages with EWMH hints 15 | (so it is needed 16 | .IR wmctrl (1) 17 | to control shod). 18 | .PP 19 | .B shod 20 | maintains one monitor for each physical monitor found by 21 | .IR Xinerama (1). 22 | One of the monitors is the focused one, where new windows go to when they are created. 23 | Each monitor contains a set of desktops. 24 | One of the desktops of a monitor is the focused desktop for that monitor. 25 | .PP 26 | Most windows are displayed in frames (containers with borders, titlebars and buttons; each window is a tab in a frame). 27 | But some windows are special and are displayed in different ways. 28 | .SS Frames 29 | Windows are displayed in frames. 30 | A frame contains borders (four edges and four corners), 31 | tabs (one for each client in the window), 32 | two buttons (a minimize and a close button), 33 | the content (which is the window of the selected tab), 34 | and an optional dialog window centered on the content. 35 | The buttons and the tabs of a frame together are called the title bar of that frame. 36 | Windows can be grouped into the same frame with tabs. 37 | One of the tabs is the selected tab. 38 | Only the contents of the selected tab of a frame is shown in that frame. 39 | .PP 40 | Each frame has one of the following four states: 41 | .TP 42 | Minimized 43 | Minimized frames belong to no monitor or desktop. 44 | Minimized frames are not shown on the screen. 45 | When a minimized frame is unminimized, 46 | it becomes a normal frame on the focused desktop of the focused monitor. 47 | .TP 48 | Sticky 49 | Sticky frames belong only to a monitor; but they do not belong to any desktop. 50 | Instead, they are \(lqsticked\(rq to the monitor, and they appear on the screen 51 | no matter which desktop is focused on that monitor. 52 | .TP 53 | Tiled 54 | Tiled frames belong to a desktop and its monitor. 55 | The monitor is split up horizontally into columns. 56 | Each column handles arbitrary frames and arranges them vertically in a nonoverlapping way. 57 | They can then be moved and resized between and within columns at will. 58 | (This tiling style is the same used by the old wmii(1) window manager and the acme(1) editor). 59 | .TP 60 | Normal 61 | Normal frames belong to a desktop and its monitor. 62 | Normal frames, along with sticky frames, are called floating frames. 63 | Normal frames appear only when its desktop is the focused one for its monitor. 64 | .PP 65 | A non\-minimized frame can also be made fullscreen or shaded, but not both. 66 | A floating (sticky or normal) frame can also be raised above other floating windows 67 | or lowered below other floating frames. 68 | .PP 69 | When a new frame spawns, it is set as 70 | .BR Normal , 71 | and it is placed in an unoccupied region of the monitor. 72 | The first frame in a monitor is centered on the monitor. 73 | .PP 74 | The frame stacking order is the following (from bottom-to-top): 75 | .IP \(bu 2 76 | Tiled frames appear below any other frames. 77 | .IP \(bu 2 78 | Lowered floating frames appear on top of tiled frames. 79 | .IP \(bu 2 80 | Regular floating frames appear on top of lowered floating frames. 81 | .IP \(bu 2 82 | Raised floating frames appear on top of regular floating frames. 83 | .IP \(bu 2 84 | Fullscreen frames appear on top of raised floating frames. 85 | .PP 86 | .B shod 87 | sets the property 88 | .B _SHOD_TAB_GROUP 89 | on each window to the ID of the window of the focused tab of its frame. 90 | All the windows in the focused frame have the 91 | .B _NET_WM_STATE_FOCUSED 92 | atom set in the 93 | .B _NET_WM_STATE 94 | property. 95 | .PP 96 | The initial state of a window and whether it will be displayed in a new frame 97 | or in a new tab of an existing frame is specified by the values of the 98 | .BR *desktop , 99 | .BR *state , 100 | and 101 | .B *autotab 102 | X resources. 103 | .PP 104 | Transient/dialog windows are not mapped into a new frame or a new tab of an existing frame; 105 | instead, they are centered on the content of the window it is a transient for. 106 | This behavior is the same as sheets in macOS window system. 107 | .SS Prompt 108 | A window of type 109 | .B _NET_WM_WINDOW_TYPE_PROMPT 110 | (called prompt window) 111 | is mapped on the top of the focused monitor. 112 | This window will stay focused and mapped until be closed or a mouse 113 | button is pressed outside that window. This is an EWMH extension, 114 | only used by 115 | .IR xprompt (1). 116 | .PP 117 | .B shod 118 | does not change the size of the prompt window. 119 | However, shod changes its position. 120 | .SS Desktop windows 121 | Windows of type 122 | .B _NET_WM_WINDOW_TYPE_DESKTOP 123 | (called desktop windows) 124 | are mapped bellow all other windows and are stacked on the order they are spawned. 125 | Desktop windows cannot be manipulated. 126 | Desktop windows have no decoration around them. 127 | .PP 128 | Desktop windows indicates a desktop feature. 129 | That includes windows such as 130 | .IR conky (1) 131 | and windows that manage desktop icons. 132 | .PP 133 | .B shod 134 | does not change the size nor the position of desktop windows. 135 | .SS Notification windows 136 | Windows of type 137 | .B _NET_WM_WINDOW_TYPE_NOTIFICATION 138 | (called notifications windows) 139 | are popped up on the top right corner, one above another. 140 | Notification windows cannot be manipulated. 141 | Notification windows have a decoration around them; 142 | this decoration is the same as the borders of the active frame 143 | (or, for urgent notifications, the same as the borders of an urgent frame). 144 | .PP 145 | An example of a notification window would be a bubble appearing with informative text such as 146 | "Your laptop is running out of power" etc. 147 | .PP 148 | The screen corner where notification windows pop up can be changed with the 149 | .B shod.notification.gravity 150 | X resource. 151 | .PP 152 | .B shod 153 | does not change the size of notification windows. 154 | However, shod changes its position. 155 | .SH USAGE 156 | There are several ways 157 | .B shod 158 | can manage windows. 159 | See the section 160 | .B EXAMPLES 161 | for examples on how to use 162 | .IR wmctrl (1) 163 | for controlling shod. 164 | .SS Managing windows with the mouse 165 | The main method for managing windows is the mouse. 166 | .PP 167 | The buttons set with the 168 | .B shod.focusButtons 169 | X resource are used to focus a frame when clicking on it. 170 | If no button is specified, the focus follows the mouse pointer. 171 | .PP 172 | The buttons set with the 173 | .B shod.raiseButtons 174 | X resource are used to raise a frame when clicking on it. 175 | .PP 176 | The modifier set with the 177 | .B shod.modifier 178 | X resource is the modifier key. 179 | Pressing the modifier key and dragging a frame with the Button 1 180 | (the left mouse button) will move that frame. 181 | Pressing the modifier key and dragging a frame with the button 3 182 | (the right mouse button) will resize that frame. 183 | .PP 184 | Resizing a frame can also be performed by dragging the frame border with the Button 1, 185 | without pressing any modifier. 186 | Moving a frame can also be performed by dragging the frame border with the button 3, 187 | without pressing any modifier. 188 | Moving a frame can also be performed by dragging a tab of the frame with the Button 1, 189 | without pressing any modifier. 190 | .PP 191 | A tab can be detached from its frame by dragging it with the Button 3. 192 | A detached tab, while being dragged with the Button 3, 193 | can be attached into another frame by dropping it into another frame's title bar. 194 | Double clicking on a tab shades or unshades the frame. 195 | .PP 196 | Each frame has two buttons on its title bar. 197 | Clicking with the mouse Button 1 on the left button (called the minimize button) 198 | minimizes the frame. 199 | Clicking with the mouse Button 1 on the right button (called the close button) 200 | closes the window on the focused tab, and, if it was the last tab on the frame, deletes the frame. 201 | .PP 202 | When a normal frame is moved from one monitor to another, 203 | that frame moves from the desktop it is in to the focused desktop 204 | of the monitor it is moved to. 205 | .SS Managing windows with state client messages 206 | The 207 | .IR wmctrl (1) 208 | utility can be used to send client messages to the window manager, 209 | and 210 | .B shod 211 | respects those messages in different ways. 212 | For example, running the following command toggles the sticky option 213 | on the active frame. 214 | .IP 215 | .EX 216 | $ wmctrl -r :ACTIVE: -b toggle,sticky 217 | .EE 218 | .IP \(bu 2 219 | Setting the 220 | .B sticky 221 | state on a window, sticks the window's frame on the monitor. 222 | .IP \(bu 2 223 | Setting both the 224 | .BR maximize_vert " and " maximize_horz 225 | states on a window, tiles the window's frame. 226 | For more information on manipulating tiled windows, see the section 227 | .B Managing tiled windows 228 | below. 229 | .IP \(bu 2 230 | Setting the 231 | .B hidden 232 | state on a window, 233 | minimizes the window's frame 234 | (it won't be displayed on any desktop or on any monitor). 235 | .IP \(bu 2 236 | Setting the 237 | .B fullscreen 238 | state on a window, makes the content of the window's frame be maximized to fit the entire screen. 239 | .IP \(bu 2 240 | Setting the 241 | .B above 242 | state on a floating window, raises the window's frame above all other floating frames. 243 | .IP \(bu 2 244 | Setting the 245 | .B below 246 | state on a floating window, lowers the window's frame below all other floating frames. 247 | .IP \(bu 2 248 | Setting the 249 | .B shaded 250 | state on a window, collapses the window's frame into its title bar. 251 | .PP 252 | All other client messages are ignored. 253 | .SS Managing windows with other EWMH client messages 254 | .B shod 255 | acts upon other EWMH client messages sent to windows and to the root window. 256 | Most client messages can be sent via 257 | .IR wmctrl (1) 258 | with a specific option. 259 | The options and the messages they send are specified below. 260 | .IP \(bu 2 261 | A message sent with the 262 | .BI \-s " NUMBER" 263 | option of 264 | .IR wmctrl (1) 265 | makes 266 | .B shod 267 | changes the desktop. 268 | That is, 269 | hide the windows on the current desktop and show the windows on a new desktop. 270 | If the desktop is on another monitor, 271 | .B shod 272 | instead moves the pointer to that monitor and focus a window on it. 273 | .IP \(bu 2 274 | A message sent with the 275 | .B \-k on 276 | or 277 | .B \-k off 278 | options of 279 | .IR wmctrl (1) 280 | makes 281 | .B shod 282 | show or hide the desktop, respectively. 283 | .IP \(bu 2 284 | A message sent with the 285 | .BI \-a " WINDOW" 286 | option of 287 | .IR wmctrl (1) 288 | makes 289 | .B shod 290 | change the active frame. 291 | That is, focus and raise the frame of the specified window. 292 | .IP \(bu 2 293 | A message sent with the 294 | .BI \-c " WINDOW" 295 | option of 296 | .IR wmctrl (1) 297 | makes 298 | .B shod 299 | close gently the specified window. 300 | .IP \(bu 2 301 | A message sent with the 302 | .BI \-e " POSITION" 303 | option of 304 | .IR wmctrl (1) 305 | makes 306 | .B shod 307 | change the position and geometry of the frame of the specified window. 308 | .IP \(bu 2 309 | A message sent with the 310 | .BI \-t " NUMBER" 311 | option of 312 | .IR wmctrl (1) 313 | makes 314 | shod 315 | move the frame of the specified window to the specified desktop. 316 | .SS Managing windows with configure request events 317 | .B shod 318 | acts upon configure request events sent to windows by resizing and moving their frames 319 | just as if the user have resized or moved them with the mouse. 320 | .SS Managing tiled windows 321 | When a window is maximized, its frame is tiled by 322 | .BR shod . 323 | A tiled frame behaves differently of regular frames. 324 | Tiled frames are organized in columns. 325 | Each tiled frame ocupies a row in a column. 326 | .PP 327 | In order to move a tiled frame from one column to another 328 | just move the frame left or right with 329 | .IR wmctrl (1) 330 | or with the mouse. 331 | This will move the frame from its current column to the column to its 332 | left or right, or it will create a new column, if needed. 333 | .PP 334 | In order to move a tiled frame up or down a column, 335 | just move the frame up or down with 336 | .IR wmctrl (1) 337 | or with the mouse. 338 | .PP 339 | Resizing a tiled frame with 340 | .IR wmctrl (1) 341 | or with the mouse 342 | will change the size of the frame, the size of the column it is in, 343 | and the size of the neighboring frames. 344 | .SH ENVIRONMENT 345 | The following environment variables affect the execution of 346 | .B shod 347 | .TP 348 | .B DISPLAY 349 | The display to start 350 | .B shod 351 | on. 352 | .SH RESOURCES 353 | .B shod 354 | understands the following X resources. 355 | .SS WM Requests 356 | These options control how WM requests are handled. 357 | .TP 358 | .B shod.ignoreIndirect 359 | Window management requests (such as to send a window to a given desktop) 360 | can be originated from one of two different sources: 361 | by the application (indirect request) or by the user (direct request). 362 | Applications requesting themselves to maximize their windows 363 | or send their windows to a given desktop can be an annoyance. 364 | If this resource is set to 365 | .BR true , 366 | indirect requests are ignored. 367 | .SS Mouse behavior 368 | These resources specify the mouse buttons that control windows 369 | and the keyboard modifier that can be used with mouse buttons. 370 | .TP 371 | .B shod.focusButtons 372 | Which mouse buttons can be used to focus a window when clicking on it. 373 | .I buttons 374 | is a string of numbers 1 to 5 (corresponding to mouse buttons 1 to 5). 375 | For example, setting this resource to 376 | .B 13 377 | makes windows be focused when clicking on them with the mouse buttons 1 and 3 378 | (the left and right mouse buttons, respectively). 379 | If this is set to a blank string, no mouse button is used to focus window, 380 | and 381 | .B shod 382 | uses the focus-follow-cursor focusing style. 383 | By default, focus follows mouse click on button 1. 384 | .TP 385 | .B shod.modifier 386 | Which modifier, from 387 | .B Mod1 388 | to 389 | .B Mod5 390 | is used to move and resize windows with the mouse pointer. 391 | .TP 392 | .B shod.raiseButtons 393 | Which mouse buttons can be used to raise a window when clicking on it. 394 | .I buttons 395 | is a string of numbers 1 to 5 (corresponding to mouse buttons 1 to 5). 396 | For example, setting this resource to 397 | .B 13 398 | makes windows be raised when clicking on them with the mouse buttons 1 and 3 399 | (the left and right mouse buttons, respectively). 400 | By default, raise occurs on mouse click on button 1. 401 | .SS General appearance 402 | These resources control the appearance of frames and whether the titlebar is visible. 403 | .TP 404 | .B shod.font 405 | The font in the X Logical Font Description of the text in the title bar. 406 | .TP 407 | .B shod.theme 408 | Path to a .xpm file containing the border decorations. 409 | The x_hotspot is interpreted as the width of the border for that decoration. 410 | The y_hotspot is interpreted as the width of the buttons for that decoration. 411 | The size of the corner is calculated as the sum of the width of the border and the width of the buttons. 412 | The height of the title bar (and its tabs) is equal to the width of the buttons. 413 | The .xpm file contains in it nine squares representing all the possible decoration states for a frame. 414 | A sample .xpm file is distributed with shod. 415 | .TP 416 | .B shod.hideTitle 417 | If set to \(lqtrue\(rq, the title bars of frames with a single tab are hidden. 418 | .SS Tiling appearance 419 | These resources control the appearance and spacement between tiled frames. 420 | .TP 421 | .B shod.gapOuter 422 | The gap in pixels between the sides of the monitor and the frames. 423 | .TP 424 | .B shod.gapInner 425 | The gap in pixels between the tiled frames. 426 | .TP 427 | .B shod.ignoreGaps 428 | If set to \(lqtrue\(rq, a single tiled frame ingores the gaps. 429 | .TP 430 | .B shod.ignoreTitle 431 | If set to \(lqtrue\(rq, a single tiled frame ingores the title bar. 432 | .TP 433 | .B shod.ignoreBorders 434 | If set to \(lqtrue\(rq, a single tiled frame does not have borders. 435 | .TP 436 | .B shod.mergeBorders 437 | If set to \(lqtrue\(rq, the borders of adjacent tiled frames are merged into a single border. 438 | .SS Window rules 439 | These resources control how a new window is managed. 440 | They are described according to a group 441 | .RB ( role , 442 | .BR class , 443 | .BR instance , 444 | or 445 | .BR title ) 446 | and to the description of the group. 447 | User-placed windows ignore rules. 448 | Groups and descriptions are case sensitive. 449 | See the examples for more information. 450 | .TP 451 | .B shod.GROUP.DESCRIPTION.autotab 452 | Controls whether a new window should be tabbed with the focused window if they have the same class. 453 | If set to 454 | .BR floating , 455 | auto tabbing occurs if the focused window is floating. 456 | If set to 457 | .BR tilingAlways , 458 | auto tabbing occurs if the focused window is tiled. 459 | If set to 460 | .BR tilingMulti , 461 | auto tabbing occurs if the focused window is tiled 462 | and there is at least two tiled windows. 463 | If set to 464 | .BR always , 465 | auto tabbing occurs if the focused window has a visible title bar. 466 | If set to 467 | .BR off , 468 | auto tabbing does not occur. 469 | .TP 470 | .B shod.GROUP.DESCRIPTION.desktop 471 | Controls in which desktop a new window should be mapped on. 472 | .TP 473 | .B shod.GROUP.DESCRIPTION.state 474 | Controls the initial state of a new window. 475 | If set to 476 | .BR normal , 477 | the window begins in normal state (the default). 478 | If set to 479 | .BR sticky , 480 | the window begins sticked on the screen. 481 | If set to 482 | .BR tiled , 483 | the window begins tiled. 484 | If set to 485 | .BR minimized , 486 | the window begins minimized. 487 | .TP 488 | .B shod.GROUP.DESCRIPTION.position 489 | TODO 490 | .SS Notification control 491 | This resource control the placement of notification windows. 492 | .TP 493 | .B shod.notification.gravity 494 | Specify the gravity, that is, where in the screen to display notification windows. 495 | The gravity can be "NE" for NorthEastGravity (display on the top right corner of the screen); 496 | "SE" for SouthEastGravity (display on the bottom right corner of the screen); 497 | "C" for CenterGravity (display on the center of the screen); etc. 498 | .TP 499 | .B shod.notification.gap 500 | The gap between notifications. 501 | .SH EXAMPLES 502 | The following is a sample configuration for X resources. 503 | It must be placed in 504 | .B $HOME/.Xresources 505 | or 506 | .B $HOME/.Xdefaults 507 | or other file called by 508 | .IR xrdb (1). 509 | This example makes shod uses a 7 pixels wide gap around and between tiled windows. 510 | It also sets three window rules: 511 | windows with the 512 | .B "browser" 513 | role should be mapped on the second desktop; 514 | windows of class 515 | .B "Zathura" 516 | should be mapped tiled; 517 | and windows of class 518 | .B "XTerm" 519 | should be tabbed with other windows of the same class. 520 | .IP 521 | .EX 522 | shod.gapOuter: 7 523 | shod.gapInner: 7 524 | shod.role.browser.desktop: 2 525 | shod.class.Zathura.state: tiled 526 | shod.class.XTerm.autotab: always 527 | .EE 528 | .PP 529 | The following is a sample configuration for 530 | .IR sxhkd (1), 531 | a program that binds keypresses (or key releases) to commands. 532 | This example uses 533 | .IR wmctrl (1) 534 | for sending EWMH hints to 535 | .BR shod. 536 | It uses 537 | .IR wmr (1) 538 | (a script shown below) for moving and resizing windows, respectively. 539 | .IP 540 | .EX 541 | # Start terminal 542 | mod1 + Return 543 | xterm 544 | 545 | # Killing windows 546 | mod1 + shift + q 547 | wmctrl -c :ACTIVE: 548 | 549 | # Workspace 550 | mod1 + {1,2,3,4,5,6,7,8,9,0} 551 | wmctrl -s {0,1,2,3,4,5,6,7,8,9} 552 | mod1 + shift + {1,2,3,4,5,6,7,8,9} 553 | wmctrl -r :ACITVE: -t {0,1,2,3,4,5,6,7,8,9} 554 | 555 | # Resize/move windows with wmr 556 | mod1 + {c, v, shift + c, shift + v} 557 | wmr 0 0 {-25 0, 0 -25, +25 0, 0 +25} 558 | mod1 + shift + {h, j, k, l} 559 | wmr {-10 0, 0 10, 0 -10, 10 0} 0 0 560 | 561 | # Change window status to sticky/above/below/minimized/fullscreen/maximized 562 | mod1 + shift + {s, a, b, z, f} 563 | wmctrl -r :ACTIVE: -b toggle,{sticky,above,below,hidden,fullscreen} 564 | mod1 + shift + t 565 | wmctrl -r :ACTIVE: -b toggle,maximized_vert,maximized_horz 566 | 567 | # Call the unminimize.sh script 568 | mod1 + shift + u 569 | unminimize.sh 570 | .EE 571 | .PP 572 | The previous example binds the following keys to the following commands: 573 | .TP 574 | .B Mod4 + Enter 575 | Spawns a terminal emulator window. 576 | .TP 577 | .B Mod4 + Shift + Q 578 | Gently closes the active windows. 579 | .TP 580 | .B Mod4 + 581 | Go to the N-th desktop. 582 | .TP 583 | .B Mod4 + Shift + 584 | Send active window to the N-th desktop. 585 | .TP 586 | .B Mod4 + C 587 | Shrink the active window horizontally by 25 pixels. 588 | .TP 589 | .B Mod4 + Shift + C 590 | Expand the active window horizontally by 25 pixels. 591 | .TP 592 | .B Mod4 + V 593 | Shrink the active window vertically by 25 pixels. 594 | .TP 595 | .B Mod4 + Shift + V 596 | Expand the active window vertically by 25 pixels. 597 | .TP 598 | .B Mod4 + Shift + H 599 | Move the active window 10 pixels to the left. 600 | .TP 601 | .B Mod4 + Shift + J 602 | Move the active window 10 pixels down. 603 | .TP 604 | .B Mod4 + Shift + K 605 | Move the active window 10 pixels up. 606 | .TP 607 | .B Mod4 + Shift + L 608 | Move the active window 10 pixels to the right. 609 | .TP 610 | .B Mod4 + Shift + S 611 | Make the active window sticky; 612 | or make it normal if it was sticky. 613 | .TP 614 | .B Mod4 + Shift + A 615 | Raise the active window above the others; 616 | or move it to its normal place if it was already above others. 617 | .TP 618 | .B Mod4 + Shift + B 619 | Lower the active window below the others; 620 | or move it to its normal place if it was already below others. 621 | .TP 622 | .B Mod4 + Shift + Z 623 | Hide the active window. 624 | .TP 625 | .B Mod4 + Shift + F 626 | Make the active window fullscreen; 627 | or make it normal if it was already fullscreen. 628 | .TP 629 | .B Mod4 + Shift + T 630 | Tile the active window; 631 | or make it floating if it was already tiled. 632 | .TP 633 | .B Mod4 + Shift + U 634 | Call the unminimize.sh script (see below). 635 | .PP 636 | The following is a sample script for 637 | .IR dmenu (1). 638 | This script lists the minimized windows and unminimizes the selected one. 639 | This script uses 640 | .IR xprop (1) 641 | to obtain the X properties set by 642 | .BR shod. 643 | .IP 644 | .EX 645 | #!/bin/sh 646 | 647 | lsw() { 648 | xprop -notype -f "_NET_CLIENT_LIST" 0x \(aq $0+\en\(aq -root "_NET_CLIENT_LIST" |\e 649 | cut -d\(aq \(aq -f2- |\e 650 | sed \(aqs/, */\e 651 | /g\(aq 652 | } 653 | 654 | ishidden() { 655 | xprop -notype -f "_NET_WM_STATE" 32a \(aq $0+\en\(aq -id "$1" "_NET_WM_STATE" |\e 656 | cut -d\(aq \(aq -f2- |\e 657 | sed \(aqs/, */\e 658 | /g\(aq | grep -q "_NET_WM_STATE_HIDDEN" 659 | } 660 | 661 | printname() { 662 | name="$(xprop -notype -f "_NET_WM_NAME" 8s \(aq $0+\en\(aq -id "$1" "_NET_WM_NAME" 2>/dev/null)" 663 | [ "$(echo $name)" = "_NET_WM_NAME: not found." ] && \e 664 | name="$(xprop -notype -f "WM_NAME" 8s \(aq $0+\en\(aq -id "$1" "WM_NAME" 2>/dev/null)" 665 | 666 | echo $name |\e 667 | cut -d\(aq \(aq -f2- |\e 668 | sed \(aqs/, */\e 669 | /g\(aq 670 | } 671 | 672 | for win in $(lsw) 673 | do 674 | ishidden $win && printf "%s: " $win && printname $win 675 | done |\e 676 | dmenu -i -l 8 -p "unminimize window:" |\e 677 | cut -d: -f1 |\e 678 | xargs wmctrl -b toggle,hidden -ir 679 | .EE 680 | .PP 681 | The following script moves and resize the active window by a relative amount of pixels. 682 | .IP 683 | .EX 684 | #!/bin/sh 685 | # wmr: move and resize window relative to its current position and size 686 | 687 | set -e 688 | 689 | usage() { 690 | echo "usage: wmr x y w h" >&2 691 | exit 1 692 | } 693 | 694 | [ $# -ne 4 ] && usage 695 | eval $(xdotool getactivewindow getwindowgeometry --shell) 696 | xadd=$1 697 | yadd=$2 698 | wadd=$3 699 | hadd=$4 700 | X=$(( X + xadd )) 701 | Y=$(( Y + yadd )) 702 | WIDTH=$(( WIDTH + wadd )) 703 | HEIGHT=$(( HEIGHT + hadd )) 704 | wmctrl -r :ACTIVE: -e 0,$X,$Y,$WIDTH,$HEIGHT 705 | .EE 706 | .SH SEE ALSO 707 | .IR dmenu (1), 708 | .IR sxhkd (1), 709 | .IR wmctrl (1), 710 | .IR xprompt (1) 711 | .SH BUGS 712 | .RI XSizeHints (3) 713 | are ignored. 714 | Size hints make no sense in a tiled and tabbed window manager. 715 | They only make sense when the size of a single frame depends only on a single window, 716 | and a single window dictates the size of a single frame. 717 | When the size of a frame depends on the size of other frames (as in the tiled situation), 718 | or when a set of windows must have the same size (as in a tabbed situation), 719 | it makes no sense to constrain the size of a frame based on the size hints of a single window, 720 | because the relation from windows to frames is no more one-to-one. 721 | .PP 722 | Naming things is hard. 723 | In the context of X Window System, a "window" can mean 724 | the UI object the user sees on the screen, 725 | or the Xlib object the programmer manipulates on the code. 726 | Usually, there is one window (Xlib object) to one window (UI object), 727 | but since 728 | .B shod 729 | is a tabbed window manager, 730 | there can exist more than one window (Xlib object) in a single window (UI object). 731 | To help on that, this manual uses the term "frame" to call windows (UI object), 732 | and the term "window" to the other sense. 733 | But, for historical reasons, the code uses the term "client" to refer to the UI object (frames). 734 | .PP 735 | EWMH hints (and other properties) may not be updated when they should. 736 | This is a bug and should be reported. 737 | -------------------------------------------------------------------------------- /shod.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 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 "shod.h" 18 | #include "theme.xpm" 19 | 20 | /* X stuff */ 21 | static Display *dpy; 22 | static Window root; 23 | static XrmDatabase xdb; 24 | static GC gc; 25 | static char *xrm; 26 | static int depth; 27 | static int screen, screenw, screenh; 28 | static int (*xerrorxlib)(Display *, XErrorEvent *); 29 | static Atom atoms[AtomLast]; 30 | 31 | /* visual */ 32 | static struct Decor decor[STYLE_LAST][DECOR_LAST]; 33 | static XFontSet fontset; 34 | static Cursor cursor[CURSOR_LAST]; 35 | static int edge; /* size of the decoration edge */ 36 | static int corner; /* size of the decoration corner */ 37 | static int border; /* size of the decoration border */ 38 | static int center; /* size of the decoration center */ 39 | static int button; /* size of the title bar and the buttons */ 40 | static int minsize; /* minimum size of a window */ 41 | 42 | /* dummy windows */ 43 | static Window wmcheckwin; 44 | static Window focuswin; /* dummy window to get focus when no other window has focus */ 45 | static Window layerwin[LayerLast]; /* dummy windows used to restack clients */ 46 | 47 | /* windows, desktops, monitors */ 48 | static struct Client *clients; 49 | static struct Client *focuslist; 50 | static struct Client *prevfocused; 51 | static struct Client *focused; 52 | static struct Client *raiselist; 53 | static struct Client *raised; 54 | static struct Monitor *selmon; 55 | static struct Monitor *mons; 56 | static struct Monitor *lastmon; 57 | static struct Notification *notifications; 58 | static int showingdesk; 59 | static XSetWindowAttributes clientswa = { 60 | .event_mask = EnterWindowMask | SubstructureNotifyMask | ExposureMask 61 | | SubstructureRedirectMask | ButtonPressMask | FocusChangeMask 62 | | PointerMotionMask 63 | }; 64 | 65 | /* other variables */ 66 | volatile sig_atomic_t running = 1; 67 | 68 | /* include default configuration */ 69 | #include "config.h" 70 | 71 | /* show usage and exit */ 72 | static void 73 | usage(void) 74 | { 75 | (void)fprintf(stderr, "usage: shod [-f buttons] [-m modifier] [-r buttons]\n"); 76 | exit(1); 77 | } 78 | 79 | /* get maximum */ 80 | static int 81 | max(int x, int y) 82 | { 83 | return x > y ? x : y; 84 | } 85 | 86 | /* get minimum */ 87 | static int 88 | min(int x, int y) 89 | { 90 | return x < y ? x : y; 91 | } 92 | 93 | /* call malloc checking for error */ 94 | static void * 95 | emalloc(size_t size) 96 | { 97 | void *p; 98 | 99 | if ((p = malloc(size)) == NULL) 100 | err(1, "malloc"); 101 | return p; 102 | } 103 | 104 | /* call strndup checking for error */ 105 | static char * 106 | estrndup(const char *s, size_t maxlen) 107 | { 108 | char *p; 109 | 110 | if ((p = strndup(s, maxlen)) == NULL) 111 | err(1, "strndup"); 112 | return p; 113 | } 114 | 115 | /* call calloc checking for error */ 116 | static void * 117 | ecalloc(size_t nmemb, size_t size) 118 | { 119 | void *p; 120 | 121 | if ((p = calloc(nmemb, size)) == NULL) 122 | err(1, "malloc"); 123 | return p; 124 | } 125 | 126 | /* get atom property from window */ 127 | static Atom 128 | getatomprop(Window win, Atom prop) 129 | { 130 | int di; 131 | unsigned long dl; 132 | unsigned char *p = NULL; 133 | Atom da, atom = None; 134 | 135 | if (XGetWindowProperty(dpy, win, prop, 0L, sizeof atom, False, XA_ATOM, 136 | &da, &di, &dl, &dl, &p) == Success && p) { 137 | atom = *(Atom *)p; 138 | XFree(p); 139 | } 140 | return atom; 141 | } 142 | 143 | /* get array of unsigned longs from window */ 144 | unsigned long * 145 | getcardinalprop(Window win, Atom atom, unsigned long size) 146 | { 147 | unsigned char *prop_ret = NULL; 148 | unsigned long *values = NULL; 149 | Atom da; /* dummy */ 150 | int di; /* dummy */ 151 | unsigned long dl; /* dummy */ 152 | int status; 153 | 154 | status = XGetWindowProperty(dpy, win, atom, 0, size, False, 155 | XA_CARDINAL, &da, &di, &dl, &dl, 156 | (unsigned char **)&prop_ret); 157 | 158 | if (status == Success && prop_ret) 159 | values = (unsigned long *)prop_ret; 160 | 161 | return values; 162 | } 163 | 164 | /* get window name */ 165 | char * 166 | getwinname(Window win) 167 | { 168 | XTextProperty tprop; 169 | char **list = NULL; 170 | char *name = NULL; 171 | unsigned char *p = NULL; 172 | unsigned long size, dl; 173 | int di; 174 | Atom da; 175 | 176 | if (XGetWindowProperty(dpy, win, atoms[NetWMName], 0L, NAMEMAXLEN, False, atoms[Utf8String], 177 | &da, &di, &size, &dl, &p) == Success && p) { 178 | name = estrndup((char *)p, NAMEMAXLEN); 179 | XFree(p); 180 | } else if (XGetWMName(dpy, win, &tprop) && 181 | XmbTextPropertyToTextList(dpy, &tprop, &list, &di) == Success && 182 | di > 0 && list && *list) { 183 | name = estrndup(*list, NAMEMAXLEN); 184 | XFreeStringList(list); 185 | XFree(tprop.value); 186 | } 187 | return name; 188 | } 189 | 190 | /* parse buttons string */ 191 | static unsigned int 192 | parsebuttons(const char *s) 193 | { 194 | const char *origs; 195 | unsigned int buttons; 196 | 197 | origs = s; 198 | buttons = 0; 199 | while (*s != '\0') { 200 | if (*s < '1' || *s > '5') 201 | errx(1, "improper buttons string %s", origs); 202 | buttons |= 1 << (*s - '1'); 203 | s++; 204 | } 205 | return buttons; 206 | } 207 | 208 | /* parse modifier string */ 209 | static unsigned int 210 | parsemodifier(const char *s) 211 | { 212 | if (strcasecmp(s, "Mod1") == 0) 213 | return Mod1Mask; 214 | else if (strcasecmp(s, "Mod2") == 0) 215 | return Mod2Mask; 216 | else if (strcasecmp(s, "Mod3") == 0) 217 | return Mod3Mask; 218 | else if (strcasecmp(s, "Mod4") == 0) 219 | return Mod4Mask; 220 | else if (strcasecmp(s, "Mod5") == 0) 221 | return Mod5Mask; 222 | else 223 | errx(1, "improper modifier string %s", s); 224 | return 0; 225 | } 226 | 227 | /* get configuration from X resources */ 228 | static void 229 | getresources(void) 230 | { 231 | XrmValue xval; 232 | long n; 233 | char *type; 234 | 235 | if (XrmGetResource(xdb, "shod.gapInner", "*", &type, &xval) == True) 236 | if ((n = strtol(xval.addr, NULL, 10)) > 0) 237 | config.gapinner = n; 238 | if (XrmGetResource(xdb, "shod.gapOuter", "*", &type, &xval) == True) 239 | if ((n = strtol(xval.addr, NULL, 10)) > 0) 240 | config.gapouter = n; 241 | if (XrmGetResource(xdb, "shod.hideTitle", "*", &type, &xval) == True) 242 | config.hidetitle = (strcasecmp(xval.addr, "true") == 0 || 243 | strcasecmp(xval.addr, "on") == 0); 244 | if (XrmGetResource(xdb, "shod.ignoreGaps", "*", &type, &xval) == True) 245 | config.ignoregaps = (strcasecmp(xval.addr, "true") == 0 || 246 | strcasecmp(xval.addr, "on") == 0); 247 | if (XrmGetResource(xdb, "shod.ignoreTitle", "*", &type, &xval) == True) 248 | config.ignoretitle = (strcasecmp(xval.addr, "true") == 0 || 249 | strcasecmp(xval.addr, "on") == 0); 250 | if (XrmGetResource(xdb, "shod.ignoreBorders", "*", &type, &xval) == True) 251 | config.ignoreborders = (strcasecmp(xval.addr, "true") == 0 || 252 | strcasecmp(xval.addr, "on") == 0); 253 | if (XrmGetResource(xdb, "shod.mergeBorders", "*", &type, &xval) == True) 254 | config.mergeborders = (strcasecmp(xval.addr, "true") == 0 || 255 | strcasecmp(xval.addr, "on") == 0); 256 | if (XrmGetResource(xdb, "shod.ignoreIndirect", "*", &type, &xval) == True) 257 | config.ignoreindirect = (strcasecmp(xval.addr, "true") == 0 || 258 | strcasecmp(xval.addr, "on") == 0); 259 | if (XrmGetResource(xdb, "shod.theme", "*", &type, &xval) == True) 260 | config.theme_path = xval.addr; 261 | if (XrmGetResource(xdb, "shod.font", "*", &type, &xval) == True) 262 | config.font = xval.addr; 263 | if (XrmGetResource(xdb, "shod.notification.gravity", "*", &type, &xval) == True) 264 | config.notifgravity = xval.addr; 265 | if (XrmGetResource(xdb, "shod.notification.gap", "*", &type, &xval) == True) 266 | if ((n = strtol(xval.addr, NULL, 10)) > 0) 267 | config.notifgap = n; 268 | if (XrmGetResource(xdb, "shod.modifier", "*", &type, &xval) == True) 269 | config.modifier = parsemodifier(xval.addr); 270 | if (XrmGetResource(xdb, "shod.focusButtons", "*", &type, &xval) == True) 271 | config.focusbuttons = parsebuttons(xval.addr); 272 | if (XrmGetResource(xdb, "shod.raiseButtons", "*", &type, &xval) == True) 273 | config.raisebuttons = parsebuttons(xval.addr); 274 | } 275 | 276 | /* get configuration from command-line */ 277 | static void 278 | getoptions(int argc, char *argv[]) 279 | { 280 | int ch; 281 | 282 | while ((ch = getopt(argc, argv, "f:m:r:")) != -1) { 283 | switch (ch) { 284 | case 'f': 285 | config.focusbuttons = parsebuttons(optarg); 286 | break; 287 | case 'm': 288 | config.modifier = parsemodifier(optarg); 289 | break; 290 | case 'r': 291 | config.raisebuttons = parsebuttons(optarg); 292 | break; 293 | default: 294 | usage(); 295 | break; 296 | } 297 | } 298 | argc -= optind; 299 | argv += optind; 300 | if (argc != 0) { 301 | usage(); 302 | } 303 | } 304 | 305 | /* get window's WM_STATE property */ 306 | static long 307 | getstate(Window w) 308 | { 309 | long result = -1; 310 | unsigned char *p = NULL; 311 | unsigned long n, extra; 312 | Atom da; 313 | int di; 314 | 315 | if (XGetWindowProperty(dpy, w, atoms[WMState], 0L, 2L, False, atoms[WMState], 316 | &da, &di, &n, &extra, (unsigned char **)&p) != Success) 317 | return -1; 318 | if (n != 0) 319 | result = *p; 320 | XFree(p); 321 | return result; 322 | } 323 | 324 | /* get desktop pointer from desktop index */ 325 | static struct Desktop * 326 | getdesk(long int n) 327 | { 328 | if (n < 0 || n >= config.ndesktops) 329 | return NULL; 330 | return &selmon->desks[n]; 331 | } 332 | 333 | /* error handler */ 334 | static int 335 | xerror(Display *dpy, XErrorEvent *e) 336 | { 337 | /* stolen from berry, which stole from katriawm, which stole from dwm lol */ 338 | 339 | /* There's no way to check accesses to destroyed windows, thus those 340 | * cases are ignored (especially on UnmapNotify's). Other types of 341 | * errors call Xlibs default error handler, which may call exit. */ 342 | if (e->error_code == BadWindow || 343 | (e->request_code == X_SetInputFocus && e->error_code == BadMatch) || 344 | (e->request_code == X_PolyText8 && e->error_code == BadDrawable) || 345 | (e->request_code == X_PolyFillRectangle && e->error_code == BadDrawable) || 346 | (e->request_code == X_PolySegment && e->error_code == BadDrawable) || 347 | (e->request_code == X_ConfigureWindow && e->error_code == BadMatch) || 348 | (e->request_code == X_GrabButton && e->error_code == BadAccess) || 349 | (e->request_code == X_GrabKey && e->error_code == BadAccess) || 350 | (e->request_code == X_CopyArea && e->error_code == BadDrawable) || 351 | (e->request_code == 139 && e->error_code == BadDrawable) || 352 | (e->request_code == 139 && e->error_code == 143)) 353 | return 0; 354 | 355 | errx(1, "Fatal request. Request code=%d, error code=%d", e->request_code, e->error_code); 356 | return xerrorxlib(dpy, e); 357 | } 358 | 359 | /* stop running */ 360 | static void 361 | siginthandler(int signo) 362 | { 363 | (void)signo; 364 | running = 0; 365 | } 366 | 367 | /* initialize signals */ 368 | static void 369 | initsignal(void) 370 | { 371 | struct sigaction sa; 372 | 373 | /* remove zombies, we may inherit children when exec'ing shod in .xinitrc */ 374 | sa.sa_handler = SIG_IGN; 375 | sa.sa_flags = 0; 376 | sigemptyset(&sa.sa_mask); 377 | if (sigaction(SIGCHLD, &sa, NULL) == -1) 378 | err(1, "sigaction"); 379 | 380 | /* set running to 0 */ 381 | sa.sa_handler = siginthandler; 382 | sa.sa_flags = 0; 383 | sigemptyset(&sa.sa_mask); 384 | if (sigaction(SIGINT, &sa, NULL) == -1) 385 | err(1, "sigaction"); 386 | } 387 | 388 | /* create dummy windows used for controlling focus and the layer of clients */ 389 | static void 390 | initdummywindows(void) 391 | { 392 | XSetWindowAttributes swa; 393 | int i; 394 | 395 | swa.do_not_propagate_mask = NoEventMask; 396 | swa.event_mask = KeyPressMask; 397 | wmcheckwin = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0); 398 | focuswin = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, 399 | CopyFromParent, CopyFromParent, CopyFromParent, 400 | CWDontPropagate | CWEventMask, &swa); 401 | for (i = 0; i < LayerLast; i++) 402 | layerwin[i] = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0); 403 | } 404 | 405 | /* initialize font set */ 406 | static void 407 | initfontset(void) 408 | { 409 | char **dp, *ds; 410 | int di; 411 | 412 | if ((fontset = XCreateFontSet(dpy, config.font, &dp, &di, &ds)) == NULL) 413 | errx(1, "XCreateFontSet: could not create fontset"); 414 | XFreeStringList(dp); 415 | } 416 | 417 | /* initialize cursors */ 418 | static void 419 | initcursors(void) 420 | { 421 | cursor[CURSOR_NORMAL] = XCreateFontCursor(dpy, XC_left_ptr); 422 | cursor[CURSOR_MOVE] = XCreateFontCursor(dpy, XC_fleur); 423 | cursor[CURSOR_NW] = XCreateFontCursor(dpy, XC_top_left_corner); 424 | cursor[CURSOR_NE] = XCreateFontCursor(dpy, XC_top_right_corner); 425 | cursor[CURSOR_SW] = XCreateFontCursor(dpy, XC_bottom_left_corner); 426 | cursor[CURSOR_SE] = XCreateFontCursor(dpy, XC_bottom_right_corner); 427 | cursor[CURSOR_N] = XCreateFontCursor(dpy, XC_top_side); 428 | cursor[CURSOR_S] = XCreateFontCursor(dpy, XC_bottom_side); 429 | cursor[CURSOR_W] = XCreateFontCursor(dpy, XC_left_side); 430 | cursor[CURSOR_E] = XCreateFontCursor(dpy, XC_right_side); 431 | cursor[CURSOR_PIRATE] = XCreateFontCursor(dpy, XC_pirate); 432 | } 433 | 434 | /* initialize atom arrays */ 435 | static void 436 | initatoms(void) 437 | { 438 | char *atomnames[AtomLast] = { 439 | [Utf8String] = "UTF8_STRING", 440 | 441 | [WMDeleteWindow] = "WM_DELETE_WINDOW", 442 | [WMWindowRole] = "WM_WINDOW_ROLE", 443 | [WMTakeFocus] = "WM_TAKE_FOCUS", 444 | [WMProtocols] = "WM_PROTOCOLS", 445 | [WMState] = "WM_STATE", 446 | 447 | [NetSupported] = "_NET_SUPPORTED", 448 | [NetClientList] = "_NET_CLIENT_LIST", 449 | [NetClientListStacking] = "_NET_CLIENT_LIST_STACKING", 450 | [NetNumberOfDesktops] = "_NET_NUMBER_OF_DESKTOPS", 451 | [NetCurrentDesktop] = "_NET_CURRENT_DESKTOP", 452 | [NetActiveWindow] = "_NET_ACTIVE_WINDOW", 453 | [NetWorkarea] = "_NET_WORKAREA", 454 | [NetSupportingWMCheck] = "_NET_SUPPORTING_WM_CHECK", 455 | [NetShowingDesktop] = "_NET_SHOWING_DESKTOP", 456 | [NetCloseWindow] = "_NET_CLOSE_WINDOW", 457 | [NetMoveresizeWindow] = "_NET_MOVERESIZE_WINDOW", 458 | [NetWMMoveresize] = "_NET_WM_MOVERESIZE", 459 | [NetRequestFrameExtents] = "_NET_REQUEST_FRAME_EXTENTS", 460 | [NetWMName] = "_NET_WM_NAME", 461 | [NetWMWindowType] = "_NET_WM_WINDOW_TYPE", 462 | [NetWMWindowTypeDesktop] = "_NET_WM_WINDOW_TYPE_DESKTOP", 463 | [NetWMWindowTypeDock] = "_NET_WM_WINDOW_TYPE_DOCK", 464 | [NetWMWindowTypeToolbar] = "_NET_WM_WINDOW_TYPE_TOOLBAR", 465 | [NetWMWindowTypeMenu] = "_NET_WM_WINDOW_TYPE_MENU", 466 | [NetWMWindowTypeSplash] = "_NET_WM_WINDOW_TYPE_SPLASH", 467 | [NetWMWindowTypePrompt] = "_NET_WM_WINDOW_TYPE_PROMPT", 468 | [NetWMWindowTypeDialog] = "_NET_WM_WINDOW_TYPE_DIALOG", 469 | [NetWMWindowTypeUtility] = "_NET_WM_WINDOW_TYPE_UTILITY", 470 | [NetWMWindowTypeNotification] = "_NET_WM_WINDOW_TYPE_NOTIFICATION", 471 | [NetWMState] = "_NET_WM_STATE", 472 | [NetWMStateSticky] = "_NET_WM_STATE_STICKY", 473 | [NetWMStateMaximizedVert] = "_NET_WM_STATE_MAXIMIZED_VERT", 474 | [NetWMStateMaximizedHorz] = "_NET_WM_STATE_MAXIMIZED_HORZ", 475 | [NetWMStateShaded] = "_NET_WM_STATE_SHADED", 476 | [NetWMStateHidden] = "_NET_WM_STATE_HIDDEN", 477 | [NetWMStateFullscreen] = "_NET_WM_STATE_FULLSCREEN", 478 | [NetWMStateAbove] = "_NET_WM_STATE_ABOVE", 479 | [NetWMStateBelow] = "_NET_WM_STATE_BELOW", 480 | [NetWMStateFocused] = "_NET_WM_STATE_FOCUSED", 481 | [NetWMStateDemandsAttention] = "_NET_WM_STATE_DEMANDS_ATTENTION", 482 | [NetWMAllowedActions] = "_NET_WM_ALLOWED_ACTIONS", 483 | [NetWMActionMove] = "_NET_WM_ACTION_MOVE", 484 | [NetWMActionResize] = "_NET_WM_ACTION_RESIZE", 485 | [NetWMActionMinimize] = "_NET_WM_ACTION_MINIMIZE", 486 | [NetWMActionStick] = "_NET_WM_ACTION_STICK", 487 | [NetWMActionMaximizeHorz] = "_NET_WM_ACTION_MAXIMIZE_HORZ", 488 | [NetWMActionMaximizeVert] = "_NET_WM_ACTION_MAXIMIZE_VERT", 489 | [NetWMActionFullscreen] = "_NET_WM_ACTION_FULLSCREEN", 490 | [NetWMActionChangeDesktop] = "_NET_WM_ACTION_CHANGE_DESKTOP", 491 | [NetWMActionClose] = "_NET_WM_ACTION_CLOSE", 492 | [NetWMActionAbove] = "_NET_WM_ACTION_ABOVE", 493 | [NetWMActionBelow] = "_NET_WM_ACTION_BELOW", 494 | [NetWMStrut] = "_NET_WM_STRUT", 495 | [NetWMStrutPartial] = "_NET_WM_STRUT_PARTIAL", 496 | [NetWMUserTime] = "_NET_WM_USER_TIME", 497 | [NetWMStateAttention] = "_NET_WM_STATE_DEMANDS_ATTENTION", 498 | [NetWMDesktop] = "_NET_WM_DESKTOP", 499 | [NetFrameExtents] = "_NET_FRAME_EXTENTS", 500 | [NetDesktopViewport] = "_NET_DESKTOP_VIEWPORT", 501 | [ShodTabGroup] = "_SHOD_TAB_GROUP" 502 | }; 503 | 504 | XInternAtoms(dpy, atomnames, AtomLast, False, atoms); 505 | } 506 | 507 | /* initialize gravity and direction values for notifications */ 508 | static void 509 | initnotif(void) 510 | { 511 | if (config.notifgravity == NULL || strcmp(config.notifgravity, "NE") == 0) { 512 | config.gravity = NorthEastGravity; 513 | config.direction = DownWards; 514 | } else if (strcmp(config.notifgravity, "NW") == 0) { 515 | config.gravity = NorthWestGravity; 516 | config.direction = DownWards; 517 | } else if (strcmp(config.notifgravity, "SW") == 0) { 518 | config.gravity = SouthWestGravity; 519 | config.direction = UpWards; 520 | } else if (strcmp(config.notifgravity, "SE") == 0) { 521 | config.gravity = SouthEastGravity; 522 | config.direction = UpWards; 523 | } else if (strcmp(config.notifgravity, "N") == 0) { 524 | config.gravity = NorthGravity; 525 | config.direction = DownWards; 526 | } else if (strcmp(config.notifgravity, "W") == 0) { 527 | config.gravity = WestGravity; 528 | config.direction = DownWards; 529 | } else if (strcmp(config.notifgravity, "C") == 0) { 530 | config.gravity = CenterGravity; 531 | config.direction = DownWards; 532 | } else if (strcmp(config.notifgravity, "E") == 0) { 533 | config.gravity = EastGravity; 534 | config.direction = DownWards; 535 | } else if (strcmp(config.notifgravity, "S") == 0) { 536 | config.gravity = SouthGravity; 537 | config.direction = UpWards; 538 | } else { 539 | errx(1, "unknown gravity %s", config.notifgravity); 540 | } 541 | } 542 | 543 | /* set up root window */ 544 | static void 545 | initroot(void) 546 | { 547 | XSetWindowAttributes swa; 548 | 549 | /* Select SubstructureRedirect events on root window */ 550 | swa.cursor = cursor[CURSOR_NORMAL]; 551 | swa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask 552 | | SubstructureRedirectMask 553 | | SubstructureNotifyMask 554 | | StructureNotifyMask 555 | | ButtonPressMask; 556 | XChangeWindowAttributes(dpy, root, CWEventMask | CWCursor, &swa); 557 | 558 | /* Set focus to root window */ 559 | XSetInputFocus(dpy, root, RevertToParent, CurrentTime); 560 | } 561 | 562 | /* check whether window was placed by the user */ 563 | static int 564 | isuserplaced(Window win) 565 | { 566 | XSizeHints size; 567 | long dl; 568 | 569 | return (XGetWMNormalHints(dpy, win, &size, &dl) && (size.flags & USPosition)); 570 | } 571 | 572 | /* check whether window is urgent */ 573 | static int 574 | isurgent(Window win) 575 | { 576 | XWMHints *wmh; 577 | int ret; 578 | 579 | ret = 0; 580 | if ((wmh = XGetWMHints(dpy, win)) != NULL) { 581 | ret = wmh->flags & XUrgencyHint; 582 | XFree(wmh); 583 | } 584 | return ret; 585 | } 586 | 587 | /* clear window urgency */ 588 | static void 589 | tabclearurgency(struct Tab *t) 590 | { 591 | XWMHints wmh = {0}; 592 | 593 | XSetWMHints(dpy, t->win, &wmh); 594 | t->isurgent = 0; 595 | } 596 | 597 | /* create and copy pixmap */ 598 | static Pixmap 599 | copypixmap(Pixmap src, int sx, int sy, int w, int h) 600 | { 601 | Pixmap pix; 602 | 603 | pix = XCreatePixmap(dpy, root, w, h, depth); 604 | XCopyArea(dpy, src, pix, gc, sx, sy, w, h, 0, 0); 605 | return pix; 606 | } 607 | 608 | /* initialize decoration pixmap */ 609 | static void 610 | settheme(void) 611 | { 612 | XGCValues val; 613 | XpmAttributes xa; 614 | XImage *img; 615 | Pixmap pix; 616 | struct Decor *d; 617 | unsigned int size; /* size of each square in the .xpm file */ 618 | unsigned int x, y; 619 | unsigned int i, j; 620 | int status; 621 | 622 | memset(&xa, 0, sizeof xa); 623 | if (config.theme_path) /* if the we have specified a file, read it instead */ 624 | status = XpmReadFileToImage(dpy, config.theme_path, &img, NULL, &xa); 625 | else /* else use the default theme */ 626 | status = XpmCreateImageFromData(dpy, theme, &img, NULL, &xa); 627 | if (status != XpmSuccess) 628 | errx(1, "could not load theme"); 629 | 630 | /* create Pixmap from XImage */ 631 | pix = XCreatePixmap(dpy, root, img->width, img->height, img->depth); 632 | val.foreground = 1; 633 | val.background = 0; 634 | XChangeGC(dpy, gc, GCForeground | GCBackground, &val); 635 | XPutImage(dpy, pix, gc, img, 0, 0, 0, 0, img->width, img->height); 636 | 637 | /* check whether the theme has the correct proportions and hotspots */ 638 | size = 0; 639 | if (xa.valuemask & (XpmSize | XpmHotspot) && 640 | xa.width % 3 == 0 && xa.height % 3 == 0 && xa.height == xa.width && 641 | (xa.width / 3) % 2 == 1 && (xa.height / 3) % 2 == 1 && 642 | xa.x_hotspot < ((xa.width / 3) - 1) / 2) { 643 | size = xa.width / 3; 644 | border = xa.x_hotspot; 645 | button = xa.y_hotspot; 646 | corner = border + button; 647 | edge = (size - 1) / 2 - corner; 648 | center = size - border * 2; 649 | minsize = max(1, center); 650 | } 651 | if (size == 0) { 652 | XDestroyImage(img); 653 | XFreePixmap(dpy, pix); 654 | errx(1, "theme in wrong format"); 655 | } 656 | 657 | /* destroy pixmap into decoration parts and copy them into the decor array */ 658 | y = 0; 659 | for (i = 0; i < STYLE_LAST; i++) { 660 | x = 0; 661 | for (j = 0; j < DECOR_LAST; j++) { 662 | d = &decor[i][j]; 663 | d->bl = copypixmap(pix, x + border, y + border, button, button); 664 | d->tl = copypixmap(pix, x + border + button, y + border, edge, button); 665 | d->t = copypixmap(pix, x + border + button + edge, y + border, 1, button); 666 | d->tr = copypixmap(pix, x + border + button + edge + 1, y + border, edge, button); 667 | d->br = copypixmap(pix, x + border + button + 2 * edge + 1, y + border, button, button); 668 | d->nw = copypixmap(pix, x, y, corner, corner); 669 | d->nf = copypixmap(pix, x + corner, y, edge, border); 670 | d->n = copypixmap(pix, x + corner + edge, y, 1, border); 671 | d->nl = copypixmap(pix, x + corner + edge + 1, y, edge, border); 672 | d->ne = copypixmap(pix, x + size - corner, y, corner, corner); 673 | d->wf = copypixmap(pix, x, y + corner, border, edge); 674 | d->w = copypixmap(pix, x, y + corner + edge, border, 1); 675 | d->wl = copypixmap(pix, x, y + corner + edge + 1, border, edge); 676 | d->ef = copypixmap(pix, x + size - border, y + corner, border, edge); 677 | d->e = copypixmap(pix, x + size - border, y + corner + edge, border, 1); 678 | d->el = copypixmap(pix, x + size - border, y + corner + edge + 1, border, edge); 679 | d->sw = copypixmap(pix, x, y + size - corner, corner, corner); 680 | d->sf = copypixmap(pix, x + corner, y + size - border, edge, border); 681 | d->s = copypixmap(pix, x + corner + edge, y + size - border, 1, border); 682 | d->sl = copypixmap(pix, x + corner + edge + 1, y + size - border, edge, border); 683 | d->se = copypixmap(pix, x + size - corner, y + size - corner, corner, corner); 684 | d->fg = XGetPixel(img, x + size / 2, y + corner + edge); 685 | d->bg = XGetPixel(img, x + size / 2, y + border + button / 2); 686 | x += size; 687 | } 688 | y += size; 689 | } 690 | 691 | XDestroyImage(img); 692 | XFreePixmap(dpy, pix); 693 | } 694 | 695 | /* get next focused client after old on selected monitor and desktop */ 696 | static struct Client * 697 | getnextfocused(struct Client *old) 698 | { 699 | struct Client *c; 700 | 701 | if (old != NULL) { 702 | if (old->state == Tiled) { 703 | if (old->row->prev) { 704 | return old->row->prev->c; 705 | } else if (old->row->next) { 706 | return old->row->next->c; 707 | } else if (old->row->col->prev) { 708 | return old->row->col->prev->row->c; 709 | } else if (old->row->col->next) { 710 | return old->row->col->next->row->c; 711 | } 712 | } else if (old->state == Normal || old->state == Sticky) { 713 | for (c = focuslist; c; c = c->fnext) { 714 | if (c != old && ((c->state == Sticky && c->mon == selmon) || 715 | (c->state == Normal && c->desk == selmon->seldesk))) { 716 | return c; 717 | } 718 | } 719 | } 720 | } 721 | for (c = focuslist; c; c = c->fnext) { 722 | if (c != old && ((c->state == Sticky && c->mon == selmon) || 723 | ((c->state == Normal || c->state == Tiled) && 724 | c->desk == selmon->seldesk))) { 725 | return c; 726 | } 727 | } 728 | return NULL; 729 | } 730 | 731 | static void 732 | icccmwmstate(Window win, int state) 733 | { 734 | long data[2]; 735 | 736 | data[0] = state; 737 | data[1] = None; 738 | 739 | XChangeProperty(dpy, win, atoms[WMState], atoms[WMState], 32, 740 | PropModeReplace, (unsigned char *)&data, 2); 741 | } 742 | 743 | static void 744 | icccmdeletestate(Window win) 745 | { 746 | XDeleteProperty(dpy, win, atoms[WMState]); 747 | } 748 | 749 | static void 750 | shodgroup(struct Client *c) 751 | { 752 | struct Tab *t; 753 | struct Transient *trans; 754 | Window win; 755 | 756 | if (c == NULL) 757 | return; 758 | win = (c->seltab ? c->seltab->win : None); 759 | for (t = c->tabs; t; t = t->next) { 760 | XChangeProperty(dpy, t->win, atoms[ShodTabGroup], XA_WINDOW, 32, PropModeReplace, (unsigned char *)&win, 1); 761 | for (trans = t->trans; trans; trans = trans->next) { 762 | XChangeProperty(dpy, trans->win, atoms[ShodTabGroup], XA_WINDOW, 32, PropModeReplace, (unsigned char *)&win, 1); 763 | } 764 | } 765 | } 766 | 767 | static void 768 | ewmhinit(void) 769 | { 770 | unsigned long data[2]; 771 | 772 | /* set window and property that indicates that the wm is ewmh compliant */ 773 | XChangeProperty(dpy, wmcheckwin, atoms[NetSupportingWMCheck], XA_WINDOW, 32, PropModeReplace, (unsigned char *)&wmcheckwin, 1); 774 | XChangeProperty(dpy, wmcheckwin, atoms[NetWMName], atoms[Utf8String], 8, PropModeReplace, (unsigned char *) "shod", strlen("shod")); 775 | XChangeProperty(dpy, root, atoms[NetSupportingWMCheck], XA_WINDOW, 32, PropModeReplace, (unsigned char *)&wmcheckwin, 1); 776 | 777 | /* set properties that the window manager supports */ 778 | XChangeProperty(dpy, root, atoms[NetSupported], XA_ATOM, 32, PropModeReplace, (unsigned char *)atoms, AtomLast); 779 | XDeleteProperty(dpy, root, atoms[NetClientList]); 780 | 781 | /* set number of desktops */ 782 | XChangeProperty(dpy, root, atoms[NetNumberOfDesktops], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&config.ndesktops, 1); 783 | 784 | /* This wm does not support viewports */ 785 | data[0] = data[1] = 0; 786 | XChangeProperty(dpy, root, atoms[NetDesktopViewport], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)data, 2); 787 | } 788 | 789 | static void 790 | ewmhsetallowedactions(Window win) 791 | { 792 | XChangeProperty(dpy, win, atoms[NetWMAllowedActions], XA_ATOM, 32, PropModeReplace, (unsigned char *)&atoms[NetWMActionMove], 11); 793 | /* 794 | * 11 is the number of actions supported, and NetWMActionMove is the 795 | * first of them. See the EWMH atoms enumeration in shod.h for more 796 | * information. 797 | */ 798 | } 799 | 800 | static void 801 | ewmhsetactivewindow(Window w) 802 | { 803 | XChangeProperty(dpy, root, atoms[NetActiveWindow], XA_WINDOW, 32, PropModeReplace, (unsigned char *)&w, 1); 804 | } 805 | 806 | static void 807 | ewmhsetcurrentdesktop(unsigned long n) 808 | { 809 | XChangeProperty(dpy, root, atoms[NetCurrentDesktop], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&n, 1); 810 | } 811 | 812 | static void 813 | ewmhsetframeextents(Window win, int b, int t) 814 | { 815 | unsigned long data[4]; 816 | 817 | data[0] = data[1] = data[3] = b; 818 | data[2] = b + t; 819 | 820 | XChangeProperty(dpy, win, atoms[NetFrameExtents], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&data, 4); 821 | } 822 | 823 | static void 824 | ewmhsetshowingdesktop(int n) 825 | { 826 | XChangeProperty(dpy, root, atoms[NetShowingDesktop], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&n, 1); 827 | } 828 | 829 | static void 830 | ewmhsetstate(struct Client *c) 831 | { 832 | struct Transient *trans; 833 | struct Tab *t; 834 | Atom data[6]; 835 | int n = 0; 836 | 837 | if (c == NULL) 838 | return; 839 | if (c == focused) 840 | data[n++] = atoms[NetWMStateFocused]; 841 | if (c->isfullscreen) 842 | data[n++] = atoms[NetWMStateFullscreen]; 843 | if (c->isshaded) 844 | data[n++] = atoms[NetWMStateShaded]; 845 | switch (c->state) { 846 | case Tiled: 847 | data[n++] = atoms[NetWMStateMaximizedVert]; 848 | data[n++] = atoms[NetWMStateMaximizedHorz]; 849 | break; 850 | case Sticky: 851 | data[n++] = atoms[NetWMStateSticky]; 852 | break; 853 | case Minimized: 854 | data[n++] = atoms[NetWMStateHidden]; 855 | break; 856 | default: 857 | break; 858 | } 859 | if (c->layer > 0) 860 | data[n++] = atoms[NetWMStateAbove]; 861 | else if (c->layer < 0) 862 | data[n++] = atoms[NetWMStateBelow]; 863 | for (t = c->tabs; t; t = t->next) { 864 | XChangeProperty(dpy, t->win, atoms[NetWMState], XA_ATOM, 32, PropModeReplace, (unsigned char *)data, n); 865 | for (trans = t->trans; trans; trans = trans->next) { 866 | XChangeProperty(dpy, trans->win, atoms[NetWMState], XA_ATOM, 32, PropModeReplace, (unsigned char *)data, n); 867 | } 868 | } 869 | } 870 | 871 | static void 872 | ewmhsetdesktop(Window win, long d) 873 | { 874 | XChangeProperty(dpy, win, atoms[NetWMDesktop], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&d, 1); 875 | } 876 | 877 | static void 878 | ewmhsetwmdesktop(struct Client *c) 879 | { 880 | struct Tab *t; 881 | struct Transient *trans; 882 | 883 | for (t = c->tabs; t; t = t->next) { 884 | if (c->state == Sticky || c->state == Minimized) { 885 | ewmhsetdesktop(t->win, 0xFFFFFFFF); 886 | } else { 887 | ewmhsetdesktop(t->win, c->desk->n); 888 | } 889 | for (trans = t->trans; trans; trans = trans->next) { 890 | if (c->state == Sticky || c->state == Minimized) { 891 | ewmhsetdesktop(trans->win, 0xFFFFFFFF); 892 | } else { 893 | ewmhsetdesktop(trans->win, c->desk->n); 894 | } 895 | } 896 | } 897 | } 898 | 899 | static void 900 | ewmhsetworkarea(int screenw, int screenh) 901 | { 902 | unsigned long data[4]; 903 | 904 | data[0] = 0; 905 | data[1] = 0; 906 | data[2] = screenw; 907 | data[3] = screenh; 908 | 909 | XChangeProperty(dpy, root, atoms[NetWorkarea], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&data, 4); 910 | } 911 | 912 | static void 913 | ewmhsetclients(void) 914 | { 915 | struct Client *c; 916 | struct Tab *t; 917 | struct Transient *trans; 918 | Window *wins = NULL; 919 | size_t i = 0, nwins = 0; 920 | 921 | for (c = clients; c; c = c->next) { 922 | for (t = c->tabs; t; t = t->next) { 923 | for (trans = t->trans; trans; trans = trans->next) { 924 | nwins++; 925 | } 926 | nwins++; 927 | } 928 | } 929 | if (nwins) 930 | wins = ecalloc(nwins, sizeof *wins); 931 | for (c = clients; c; c = c->next) { 932 | for (t = c->tabs; t; t = t->next) { 933 | wins[i++] = t->win; 934 | for (trans = t->trans; trans; trans = trans->next) { 935 | wins[i++] = trans->win; 936 | } 937 | } 938 | } 939 | XChangeProperty(dpy, root, atoms[NetClientList], XA_WINDOW, 32, 940 | PropModeReplace, (unsigned char *)wins, i); 941 | free(wins); 942 | } 943 | 944 | static void 945 | ewmhsetclientsstacking(void) 946 | { 947 | struct Client *c, *last; 948 | struct Tab *t; 949 | struct Transient *trans; 950 | Window *wins = NULL; 951 | size_t i = 0, nwins = 0; 952 | 953 | last = NULL; 954 | for (c = raiselist; c; c = c->rnext) { 955 | last = c; 956 | for (t = c->tabs; t; t = t->next) { 957 | for (trans = t->trans; trans; trans = trans->next) { 958 | nwins++; 959 | } 960 | nwins++; 961 | } 962 | } 963 | if (nwins) 964 | wins = ecalloc(nwins, sizeof *wins); 965 | for (c = last; c; c = c->rprev) { 966 | for (t = c->tabs; t; t = t->next) { 967 | if (c->state == Tiled && !c->isfullscreen) { 968 | wins[i++] = t->win; 969 | for (trans = t->trans; trans; trans = trans->next) { 970 | wins[i++] = trans->win; 971 | } 972 | } 973 | } 974 | } 975 | for (c = last; c; c = c->rprev) { 976 | for (t = c->tabs; t; t = t->next) { 977 | if (c->state != Tiled && !c->isfullscreen && c->layer < 0) { 978 | wins[i++] = t->win; 979 | for (trans = t->trans; trans; trans = trans->next) { 980 | wins[i++] = trans->win; 981 | } 982 | } 983 | } 984 | } 985 | for (c = last; c; c = c->rprev) { 986 | for (t = c->tabs; t; t = t->next) { 987 | if (c->state != Tiled && !c->isfullscreen && c->layer == 0) { 988 | wins[i++] = t->win; 989 | for (trans = t->trans; trans; trans = trans->next) { 990 | wins[i++] = trans->win; 991 | } 992 | } 993 | } 994 | } 995 | for (c = last; c; c = c->rprev) { 996 | for (t = c->tabs; t; t = t->next) { 997 | if (c->state != Tiled && !c->isfullscreen && c->layer > 0) { 998 | wins[i++] = t->win; 999 | for (trans = t->trans; trans; trans = trans->next) { 1000 | wins[i++] = trans->win; 1001 | } 1002 | } 1003 | } 1004 | } 1005 | for (c = last; c; c = c->rprev) { 1006 | for (t = c->tabs; t; t = t->next) { 1007 | if (c->isfullscreen) { 1008 | wins[i++] = t->win; 1009 | for (trans = t->trans; trans; trans = trans->next) { 1010 | wins[i++] = trans->win; 1011 | } 1012 | } 1013 | } 1014 | } 1015 | XChangeProperty(dpy, root, atoms[NetClientListStacking], XA_WINDOW, 32, 1016 | PropModeReplace, (unsigned char *)wins, i); 1017 | free(wins); 1018 | } 1019 | 1020 | /* get pointer to client, tab or transient structure given a window */ 1021 | static struct Winres 1022 | getwin(Window win) 1023 | { 1024 | struct Winres res; 1025 | struct Client *c; 1026 | struct Tab *t; 1027 | struct Transient *trans; 1028 | struct Notification *n; 1029 | 1030 | res.n = NULL; 1031 | res.c = NULL; 1032 | res.t = NULL; 1033 | res.trans = NULL; 1034 | for (n = notifications; n; n = n->next) { 1035 | if (win == n->frame || win == n->win) { 1036 | res.n = n; 1037 | goto done; 1038 | } 1039 | } 1040 | for (c = clients; c; c = c->next) { 1041 | if (win == c->frame || win == c->curswin) { 1042 | res.c = c; 1043 | goto done; 1044 | } 1045 | for (t = c->tabs; t; t = t->next) { 1046 | if (win == t->win || win == t->frame || win == t->title) { 1047 | res.c = c; 1048 | res.t = t; 1049 | goto done; 1050 | } 1051 | for (trans = t->trans; trans; trans = trans->next) { 1052 | if (win == trans->win || win == trans->frame) { 1053 | res.c = c; 1054 | res.t = t; 1055 | res.trans = trans; 1056 | goto done; 1057 | } 1058 | } 1059 | } 1060 | } 1061 | done: 1062 | return res; 1063 | } 1064 | 1065 | /* get monitor given coordinates */ 1066 | static struct Monitor * 1067 | getmon(int x, int y) 1068 | { 1069 | struct Monitor *mon; 1070 | 1071 | for (mon = mons; mon; mon = mon->next) 1072 | if (x >= mon->mx && x <= mon->mx + mon->mw && 1073 | y >= mon->my && y <= mon->my + mon->mh) 1074 | return mon; 1075 | return NULL; 1076 | } 1077 | 1078 | /* get focused fullscreen window in given monitor and desktop */ 1079 | static struct Client * 1080 | getfullscreen(struct Monitor *mon, struct Desktop *desk) 1081 | { 1082 | struct Client *c; 1083 | 1084 | for (c = focuslist; c; c = c->fnext) 1085 | if (c->isfullscreen && 1086 | (((c->state == Normal || c->state == Tiled) && c->desk == desk) || 1087 | (c->state == Sticky && c->mon == mon))) 1088 | return c; 1089 | return NULL; 1090 | } 1091 | 1092 | /* compute position and width of tabs of a client */ 1093 | static void 1094 | calctabs(struct Client *c) 1095 | { 1096 | struct Transient *trans; 1097 | struct Tab *t; 1098 | int i, x; 1099 | 1100 | x = 0; 1101 | for (i = 0, t = c->tabs; t; t = t->next, i++) { 1102 | t->w = max(1, ((i + 1) * (c->w - 2 * button) / c->ntabs) - (i * (c->w - 2 * button) / c->ntabs)); 1103 | t->x = x; 1104 | x += t->w; 1105 | for (trans = t->trans; trans; trans = trans->next) { 1106 | trans->w = max(minsize, min(trans->maxw, c->w - 2 * border)); 1107 | trans->h = max(minsize, min(trans->maxh, (c->isshaded ? c->saveh : c->h) - 2 * border)); 1108 | trans->x = c->w / 2 - trans->w / 2; 1109 | trans->y = c->h / 2 - trans->h / 2; 1110 | } 1111 | } 1112 | } 1113 | 1114 | /* focus a tab */ 1115 | static void 1116 | tabfocus(struct Tab *t) 1117 | { 1118 | if (t == NULL) 1119 | return; 1120 | t->c->seltab = t; 1121 | if (t->isurgent) 1122 | tabclearurgency(t); 1123 | XRaiseWindow(dpy, t->frame); 1124 | if (t->c->isshaded) { 1125 | XSetInputFocus(dpy, t->c->frame, RevertToParent, CurrentTime); 1126 | } else if (t->trans) { 1127 | XRaiseWindow(dpy, t->trans->frame); 1128 | XSetInputFocus(dpy, t->trans->win, RevertToParent, CurrentTime); 1129 | ewmhsetactivewindow(t->trans->win); 1130 | } else { 1131 | XSetInputFocus(dpy, t->win, RevertToParent, CurrentTime); 1132 | ewmhsetactivewindow(t->win); 1133 | } 1134 | shodgroup(t->c); 1135 | } 1136 | 1137 | /* get tab decoration style */ 1138 | static int 1139 | tabgetstyle(struct Tab *t) 1140 | { 1141 | if (t->isurgent) 1142 | return URGENT; 1143 | if (t->c == focused) 1144 | return FOCUSED; 1145 | return UNFOCUSED; 1146 | } 1147 | 1148 | /* delete transient window from tab */ 1149 | static void 1150 | transdel(struct Transient *trans) 1151 | { 1152 | struct Tab *t; 1153 | 1154 | t = trans->t; 1155 | if (trans->next) 1156 | trans->next->prev = trans->prev; 1157 | if (trans->prev) 1158 | trans->prev->next = trans->next; 1159 | else 1160 | t->trans = trans->next; 1161 | shodgroup(t->c); 1162 | if (trans->pix != None) 1163 | XFreePixmap(dpy, trans->pix); 1164 | icccmdeletestate(trans->win); 1165 | XReparentWindow(dpy, trans->win, root, 0, 0); 1166 | XDestroyWindow(dpy, trans->frame); 1167 | tabfocus(t); 1168 | free(trans); 1169 | } 1170 | 1171 | /* decorate transient window */ 1172 | static void 1173 | transdecorate(struct Transient *trans) 1174 | { 1175 | XGCValues val; 1176 | int transw, transh; 1177 | int style; 1178 | 1179 | style = tabgetstyle(trans->t); 1180 | transw = trans->w + 2 * border; 1181 | transh = trans->h + 2 * border; 1182 | 1183 | if (trans->pw != transw || trans->ph != transh || trans->pix == None) { 1184 | if (trans->pix != None) 1185 | XFreePixmap(dpy, trans->pix); 1186 | trans->pix = XCreatePixmap(dpy, trans->frame, transw, transh, depth); 1187 | } 1188 | trans->pw = transw; 1189 | trans->ph = transh; 1190 | 1191 | val.fill_style = FillTiled; 1192 | val.tile = decor[style][TRANSIENT].w; 1193 | val.ts_x_origin = 0; 1194 | val.ts_y_origin = 0; 1195 | XChangeGC(dpy, gc, GCFillStyle | GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); 1196 | XFillRectangle(dpy, trans->pix, gc, 0, border, border, trans->h + border); 1197 | 1198 | val.tile = decor[style][TRANSIENT].e; 1199 | val.ts_x_origin = border + trans->w; 1200 | val.ts_y_origin = 0; 1201 | XChangeGC(dpy, gc, GCFillStyle | GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); 1202 | XFillRectangle(dpy, trans->pix, gc, border + trans->w, border, border, trans->h + border); 1203 | 1204 | val.tile = decor[style][TRANSIENT].n; 1205 | val.ts_x_origin = 0; 1206 | val.ts_y_origin = 0; 1207 | XChangeGC(dpy, gc, GCFillStyle | GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); 1208 | XFillRectangle(dpy, trans->pix, gc, border, 0, transw, border); 1209 | 1210 | val.tile = decor[style][TRANSIENT].s; 1211 | val.ts_x_origin = 0; 1212 | val.ts_y_origin = border + trans->h; 1213 | XChangeGC(dpy, gc, GCFillStyle | GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); 1214 | XFillRectangle(dpy, trans->pix, gc, border, border + trans->h, transw, border); 1215 | 1216 | XCopyArea(dpy, decor[style][TRANSIENT].nw, trans->pix, gc, 0, 0, corner, corner, 0, 0); 1217 | XCopyArea(dpy, decor[style][TRANSIENT].ne, trans->pix, gc, 0, 0, corner, corner, transw - corner, 0); 1218 | XCopyArea(dpy, decor[style][TRANSIENT].sw, trans->pix, gc, 0, 0, corner, corner, 0, transh - corner); 1219 | XCopyArea(dpy, decor[style][TRANSIENT].se, trans->pix, gc, 0, 0, corner, corner, transw - corner, transh - corner); 1220 | 1221 | val.fill_style = FillSolid; 1222 | val.foreground = decor[style][TRANSIENT].bg; 1223 | XChangeGC(dpy, gc, GCFillStyle | GCForeground, &val); 1224 | XFillRectangle(dpy, trans->pix, gc, border, border, trans->w, trans->h); 1225 | 1226 | XCopyArea(dpy, trans->pix, trans->frame, gc, 0, 0, transw, transh, 0, 0); 1227 | } 1228 | 1229 | /* detach tab from client */ 1230 | static void 1231 | tabdetach(struct Tab *t, int x, int y) 1232 | { 1233 | t->winw = t->c->fw; 1234 | t->winh = t->c->fh; 1235 | if (t->c->seltab == t) { 1236 | if (t->prev) { 1237 | t->c->seltab = t->prev; 1238 | } else { 1239 | t->c->seltab = t->next; 1240 | } 1241 | } 1242 | t->c->ntabs--; 1243 | t->ignoreunmap = IGNOREUNMAP; 1244 | XReparentWindow(dpy, t->title, root, x, y); 1245 | if (t->next) 1246 | t->next->prev = t->prev; 1247 | if (t->prev) 1248 | t->prev->next = t->next; 1249 | else 1250 | t->c->tabs = t->next; 1251 | t->next = NULL; 1252 | t->prev = NULL; 1253 | calctabs(t->c); 1254 | } 1255 | 1256 | /* move a detached tab */ 1257 | static void 1258 | tabmove(struct Tab *t, int x, int y) 1259 | { 1260 | XMoveWindow(dpy, t->title, x, y); 1261 | } 1262 | 1263 | /* delete tab from client */ 1264 | static void 1265 | tabdel(struct Tab *t) 1266 | { 1267 | struct Client *c; 1268 | 1269 | c = t->c; 1270 | while (t->trans) 1271 | transdel(t->trans); 1272 | tabdetach(t, 0, 0); 1273 | shodgroup(c); 1274 | if (t->pix != None) 1275 | XFreePixmap(dpy, t->pix); 1276 | icccmdeletestate(t->win); 1277 | XReparentWindow(dpy, t->win, root, c->x, c->y); 1278 | XDestroyWindow(dpy, t->title); 1279 | XDestroyWindow(dpy, t->frame); 1280 | free(t->name); 1281 | free(t->class); 1282 | free(t); 1283 | } 1284 | 1285 | /* update tab title */ 1286 | static void 1287 | tabupdatetitle(struct Tab *t) 1288 | { 1289 | free(t->name); 1290 | t->name = getwinname(t->win); 1291 | } 1292 | 1293 | /* update tab class */ 1294 | static void 1295 | tabupdateclass(struct Tab *t) 1296 | { 1297 | XClassHint chint; 1298 | 1299 | if (XGetClassHint(dpy, t->win, &chint)) { 1300 | free(t->class); 1301 | t->class = (chint.res_class != NULL && chint.res_class[0] != '\0') 1302 | ? estrndup(chint.res_class, NAMEMAXLEN) 1303 | : NULL; 1304 | XFree(chint.res_class); 1305 | XFree(chint.res_name); 1306 | } 1307 | } 1308 | 1309 | /* add tab into client */ 1310 | static struct Tab * 1311 | tabadd(Window win, char *name, char *class, int ignoreunmap) 1312 | { 1313 | struct Tab *t; 1314 | 1315 | t = emalloc(sizeof *t); 1316 | t->prev = NULL; 1317 | t->next = NULL; 1318 | t->c = NULL; 1319 | t->trans = NULL; 1320 | t->title = None; 1321 | t->name = name; 1322 | t->class = class; 1323 | t->ignoreunmap = ignoreunmap; 1324 | t->isurgent = isurgent(win); 1325 | t->pix = None; 1326 | t->pw = 0; 1327 | t->frame = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, 1328 | CopyFromParent, CopyFromParent, CopyFromParent, 1329 | CWEventMask, &clientswa); 1330 | t->win = win; 1331 | XReparentWindow(dpy, t->win, t->frame, 0, 0); 1332 | icccmwmstate(win, NormalState); 1333 | ewmhsetallowedactions(win); 1334 | return t; 1335 | } 1336 | 1337 | /* decorate tab */ 1338 | static void 1339 | tabdecorate(struct Tab *t, int pressed) 1340 | { 1341 | XGCValues val; 1342 | XRectangle box, dr; 1343 | struct Decor *d; 1344 | size_t len; 1345 | int style; 1346 | int x, y; 1347 | 1348 | style = tabgetstyle(t); 1349 | if (t->c && t != t->c->seltab) 1350 | d = &decor[style][TAB_UNFOCUSED]; 1351 | else if (t->c && pressed == FrameTitle) 1352 | d = &decor[style][TAB_PRESSED]; 1353 | else 1354 | d = &decor[style][TAB_FOCUSED]; 1355 | if (t->pw != t->w || t->pix == None) { 1356 | if (t->pix != None) 1357 | XFreePixmap(dpy, t->pix); 1358 | t->pix = XCreatePixmap(dpy, t->title, t->w, button, depth); 1359 | } 1360 | t->pw = t->w; 1361 | val.tile = d->t; 1362 | val.ts_x_origin = 0; 1363 | val.ts_y_origin = 0; 1364 | val.fill_style = FillTiled; 1365 | XChangeGC(dpy, gc, GCTile | GCTileStipYOrigin | GCTileStipXOrigin | GCFillStyle, &val); 1366 | XCopyArea(dpy, d->tl, t->pix, gc, 0, 0, edge, button, 0, 0); 1367 | XFillRectangle(dpy, t->pix, gc, edge, 0, t->w - edge, button); 1368 | XCopyArea(dpy, d->tr, t->pix, gc, 0, 0, edge, button, t->w - edge, 0); 1369 | if (t->name != NULL) { 1370 | len = strlen(t->name); 1371 | val.fill_style = FillSolid; 1372 | val.foreground = d->fg; 1373 | XChangeGC(dpy, gc, GCFillStyle | GCForeground, &val); 1374 | XmbTextExtents(fontset, t->name, len, &dr, &box); 1375 | x = (t->w - box.width) / 2 - box.x; 1376 | y = (button - box.height) / 2 - box.y; 1377 | XmbDrawString(dpy, t->pix, fontset, gc, x, y, t->name, len); 1378 | } 1379 | val.foreground = d->bg; 1380 | val.fill_style = FillSolid; 1381 | XChangeGC(dpy, gc, GCFillStyle | GCForeground, &val); 1382 | XFillRectangle(dpy, t->frame, gc, 0, 0, t->c->w, t->c->h); 1383 | XCopyArea(dpy, t->pix, t->title, gc, 0, 0, t->w, button, 0, 0); 1384 | } 1385 | 1386 | /* notify window of configuration changing */ 1387 | static void 1388 | notify(Window win, int x, int y, int w, int h) 1389 | { 1390 | XConfigureEvent ce; 1391 | 1392 | ce.type = ConfigureNotify; 1393 | ce.display = dpy; 1394 | ce.x = x; 1395 | ce.y = y; 1396 | ce.width = w; 1397 | ce.height = h; 1398 | ce.border_width = 0; 1399 | ce.above = None; 1400 | ce.override_redirect = False; 1401 | ce.event = win; 1402 | ce.window = win; 1403 | XSendEvent(dpy, win, False, StructureNotifyMask, (XEvent *)&ce); 1404 | } 1405 | 1406 | /* get decoration style (and state) of client */ 1407 | static int 1408 | clientgetstyle(struct Client *c) 1409 | { 1410 | struct Tab *t; 1411 | 1412 | if (c == focused) 1413 | return FOCUSED; 1414 | for (t = c->tabs; t; t = t->next) 1415 | if (t->isurgent) 1416 | return URGENT; 1417 | return UNFOCUSED; 1418 | } 1419 | 1420 | /* check if client is visible */ 1421 | static int 1422 | clientisvisible(struct Client *c) 1423 | { 1424 | if (c == NULL || c->state == Minimized) 1425 | return 0; 1426 | if (c->state == Sticky || (c->desk && c->desk == c->desk->mon->seldesk)) 1427 | return 1; 1428 | return 0; 1429 | } 1430 | 1431 | /* notify client of configuration changing */ 1432 | static void 1433 | clientnotify(struct Client *c) 1434 | { 1435 | struct Tab *t; 1436 | struct Transient *trans; 1437 | 1438 | if (c == NULL) 1439 | return; 1440 | for (t = c->tabs; t; t = t->next) { 1441 | notify(t->win, c->x, c->y, c->w, c->isshaded ? c->saveh : c->h); 1442 | for (trans = t->trans; trans; trans = trans->next) { 1443 | notify(trans->win, trans->x, trans->y, trans->w, trans->h); 1444 | } 1445 | } 1446 | } 1447 | 1448 | /* draw decoration on the frame window */ 1449 | static void 1450 | clientdecorate(struct Client *c, int decorateall, enum Octant octant, int region) 1451 | { 1452 | XGCValues val; 1453 | struct Tab *t; 1454 | struct Decor *d; /* unpressed decoration */ 1455 | struct Decor *dp; /* pressed decoration */ 1456 | int style; 1457 | int origin; 1458 | int w, h; 1459 | int fullw, fullh; 1460 | int j; 1461 | 1462 | if (c == NULL) 1463 | return; 1464 | style = clientgetstyle(c); 1465 | j = UNPRESSED; 1466 | if ((c->state & Tiled) && config.mergeborders) 1467 | j = MERGE_BORDERS; 1468 | d = &decor[style][j]; 1469 | dp = (octant && j == 0) ? &decor[style][PRESSED] : d; 1470 | origin = c->b - border; 1471 | fullw = c->w + c->b * 2; 1472 | fullh = c->h + c->b * 2 + c->t; 1473 | w = fullw - corner * 2 - origin * 2; 1474 | h = fullh - corner * 2 - origin * 2; 1475 | val.fill_style = FillTiled; 1476 | XChangeGC(dpy, gc, GCFillStyle, &val); 1477 | 1478 | if (c->pw != fullw || c->ph != fullh || c->pix == None) { 1479 | if (c->pix != None) 1480 | XFreePixmap(dpy, c->pix); 1481 | c->pix = XCreatePixmap(dpy, c->frame, fullw, fullh, depth); 1482 | } 1483 | c->pw = fullw; 1484 | c->ph = fullh; 1485 | 1486 | /* draw borders */ 1487 | if (w > 0) { 1488 | val.tile = (octant == N) ? dp->n : d->n; 1489 | val.ts_x_origin = origin; 1490 | val.ts_y_origin = origin; 1491 | XChangeGC(dpy, gc, GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); 1492 | XFillRectangle(dpy, c->pix, gc, origin + corner, 0, w, c->b); 1493 | 1494 | val.tile = (octant == S) ? dp->s : d->s; 1495 | val.ts_x_origin = origin; 1496 | val.ts_y_origin = fullh - c->b; 1497 | XChangeGC(dpy, gc, GCTile | GCTileStipYOrigin | GCTileStipXOrigin , &val); 1498 | XFillRectangle(dpy, c->pix, gc, origin + corner, fullh - c->b, w, c->b); 1499 | } 1500 | 1501 | if (h > 0) { 1502 | val.tile = (octant == W) ? dp->w : d->w; 1503 | val.ts_x_origin = origin; 1504 | val.ts_y_origin = origin; 1505 | XChangeGC(dpy, gc, GCTile | GCTileStipYOrigin | GCTileStipXOrigin , &val); 1506 | XFillRectangle(dpy, c->pix, gc, 0, origin + corner, c->b, h); 1507 | 1508 | val.tile = (octant == E) ? dp->e : d->e; 1509 | val.ts_x_origin = fullw - c->b; 1510 | val.ts_y_origin = origin; 1511 | XChangeGC(dpy, gc, GCTile | GCTileStipYOrigin | GCTileStipXOrigin , &val); 1512 | XFillRectangle(dpy, c->pix, gc, fullw - c->b, origin + corner, c->b, h); 1513 | } 1514 | 1515 | /* draw corners and border ends */ 1516 | XCopyArea(dpy, (octant == N) ? dp->nf : d->nf, c->pix, gc, 0, 0, edge, border, origin + corner, origin); 1517 | XCopyArea(dpy, (octant == W) ? dp->wf : d->wf, c->pix, gc, 0, 0, border, edge, origin, origin + corner); 1518 | XCopyArea(dpy, (octant == N) ? dp->nl : d->nl, c->pix, gc, 0, 0, edge, border, origin + corner + w - edge, origin); 1519 | XCopyArea(dpy, (octant == E) ? dp->ef : d->ef, c->pix, gc, 0, 0, border, edge, origin + border + c->w, origin + corner); 1520 | XCopyArea(dpy, (octant == S) ? dp->sf : d->sf, c->pix, gc, 0, 0, edge, border, origin + corner, origin + border + c->t + c->h); 1521 | XCopyArea(dpy, (octant == W) ? dp->wl : d->wl, c->pix, gc, 0, 0, border, edge, origin, origin + corner + h - edge); 1522 | XCopyArea(dpy, (octant == S) ? dp->sl : d->sl, c->pix, gc, 0, 0, edge, border, origin + corner + w - edge, origin + border + c->t + c->h); 1523 | XCopyArea(dpy, (octant == E) ? dp->el : d->el, c->pix, gc, 0, 0, border, edge, origin + border + c->w, origin + corner + h - edge); 1524 | XCopyArea(dpy, (octant == NW || (octant == SW && c->isshaded)) ? dp->nw : d->nw, c->pix, gc, 0, corner/2, corner, corner/2+1, origin, origin + corner/2); 1525 | XCopyArea(dpy, (octant == NE || (octant == SE && c->isshaded)) ? dp->ne : d->ne, c->pix, gc, 0, corner/2, corner, corner/2+1, fullw - corner - origin, origin + corner/2); 1526 | XCopyArea(dpy, (octant == SW || (octant == NW && c->isshaded)) ? dp->sw : d->sw, c->pix, gc, 0, 0, corner, corner/2, origin, fullh - corner - origin); 1527 | XCopyArea(dpy, (octant == SE || (octant == NE && c->isshaded)) ? dp->se : d->se, c->pix, gc, 0, 0, corner, corner/2, fullw - corner - origin, fullh - corner - origin); 1528 | XCopyArea(dpy, (octant == NW || (octant == SW && c->isshaded)) ? dp->nw : d->nw, c->pix, gc, 0, 0, corner, corner/2, origin, origin); 1529 | XCopyArea(dpy, (octant == NE || (octant == SE && c->isshaded)) ? dp->ne : d->ne, c->pix, gc, 0, 0, corner, corner/2, fullw - corner - origin, origin); 1530 | XCopyArea(dpy, (octant == SW || (octant == NW && c->isshaded)) ? dp->sw : d->sw, c->pix, gc, 0, corner/2, corner, corner/2+1, origin, fullh - corner - origin + corner/2); 1531 | XCopyArea(dpy, (octant == SE || (octant == NE && c->isshaded)) ? dp->se : d->se, c->pix, gc, 0, corner/2, corner, corner/2+1, fullw - corner - origin, fullh - corner - origin + corner/2); 1532 | 1533 | /* draw background */ 1534 | val.foreground = d->bg; 1535 | val.fill_style = FillSolid; 1536 | XChangeGC(dpy, gc, GCFillStyle | GCForeground, &val); 1537 | XFillRectangle(dpy, c->pix, gc, c->b, c->b, c->w, c->h + c->t); 1538 | 1539 | /* draw title and buttons */ 1540 | if (c->t > 0) { 1541 | dp = region ? &decor[style][PRESSED] : &decor[style][UNPRESSED]; 1542 | XCopyArea(dpy, (region == FrameButtonLeft) ? dp->bl : d->bl, c->pix, gc, 0, 0, button, button, c->b, c->b); 1543 | XCopyArea(dpy, (region == FrameButtonRight) ? dp->br : d->br, c->pix, gc, 0, 0, button, button, fullw - button - c->b, c->b); 1544 | } 1545 | if (decorateall) { 1546 | for (t = c->tabs; t; t = t->next) { 1547 | if (t->trans) { 1548 | transdecorate(t->trans); 1549 | } 1550 | if (c->t > 0) { 1551 | tabdecorate(t, FrameNone); 1552 | } 1553 | } 1554 | } 1555 | XCopyArea(dpy, c->pix, c->frame, gc, 0, 0, fullw, fullh, 0, 0); 1556 | } 1557 | 1558 | /* set client border width */ 1559 | static void 1560 | clientborderwidth(struct Client *c, int border) 1561 | { 1562 | if (c == NULL) 1563 | return; 1564 | c->b = border; 1565 | } 1566 | 1567 | /* set client title bar width */ 1568 | static void 1569 | clienttitlewidth(struct Client *c, int title) 1570 | { 1571 | if (c == NULL) 1572 | return; 1573 | c->t = title; 1574 | } 1575 | 1576 | /* move and resize tabs within frame */ 1577 | static void 1578 | clientretab(struct Client *c) 1579 | { 1580 | struct Transient *trans; 1581 | struct Tab *t; 1582 | int transx, transy, transw, transh; 1583 | int i; 1584 | 1585 | for (i = 0, t = c->tabs; t; t = t->next, i++) { 1586 | if (c->isshaded) { 1587 | XMoveResizeWindow(dpy, t->frame, c->b, 2 * c->b + c->t, c->w, c->saveh); 1588 | } else { 1589 | XMoveResizeWindow(dpy, t->frame, c->b, c->b + c->t, c->w, c->h); 1590 | } 1591 | for (trans = t->trans; trans; trans = trans->next) { 1592 | transx = trans->x - border; 1593 | transy = trans->y - border; 1594 | transw = trans->w + 2 * border; 1595 | transh = trans->h + 2 * border; 1596 | XMoveResizeWindow(dpy, trans->frame, transx, transy, transw, transh); 1597 | XMoveResizeWindow(dpy, trans->win, border, border, trans->w, trans->h); 1598 | if (trans->pw != transw || trans->ph != transh) { 1599 | transdecorate(trans); 1600 | } 1601 | } 1602 | XResizeWindow(dpy, t->win, c->w, (c->isshaded ? c->saveh : c->h)); 1603 | if (c->t > 0) { 1604 | XMapWindow(dpy, t->title); 1605 | XMoveResizeWindow(dpy, t->title, c->b + button + t->x, c->b, t->w, c->t); 1606 | } else { 1607 | XUnmapWindow(dpy, t->title); 1608 | } 1609 | if (t->pw != t->w) { 1610 | tabdecorate(t, 0); 1611 | } 1612 | } 1613 | clientnotify(c); 1614 | } 1615 | 1616 | /* commit floating client size and position */ 1617 | static void 1618 | clientmoveresize(struct Client *c) 1619 | { 1620 | int x, y, w, h; /* frame geometry */ 1621 | 1622 | if (c == NULL) 1623 | return; 1624 | x = c->x - c->b; 1625 | y = c->y - c->b - c->t; 1626 | w = WIDTH(c); 1627 | h = HEIGHT(c); 1628 | calctabs(c); 1629 | XMoveResizeWindow(dpy, c->frame, x, y, w, h); 1630 | XMoveResizeWindow(dpy, c->curswin, 0, 0, w, h); 1631 | clientretab(c); 1632 | if (c->pw != w || c->ph != h) { 1633 | clientdecorate(c, 0, 0, FrameNone); 1634 | } 1635 | } 1636 | 1637 | /* check if desktop is visible */ 1638 | static int 1639 | deskisvisible(struct Desktop *desk) 1640 | { 1641 | return desk->mon->seldesk == desk; 1642 | } 1643 | 1644 | /* allocate array of desktops for monitor */ 1645 | static struct Desktop * 1646 | desksadd(struct Monitor *mon) 1647 | { 1648 | struct Desktop *desks; 1649 | int i; 1650 | 1651 | desks = ecalloc(config.ndesktops, sizeof *desks); 1652 | for (i = 0; i < config.ndesktops; i++) { 1653 | desks[i].mon = mon; 1654 | desks[i].n = i; 1655 | } 1656 | return desks; 1657 | } 1658 | 1659 | /* tile all clients in desktop */ 1660 | static void 1661 | desktile(struct Desktop *desk) 1662 | { 1663 | struct Monitor *mon; 1664 | struct Column *col; 1665 | struct Row *row; 1666 | int recol = 0, rerow = 0; /* whether to resize columns and rows */ 1667 | int sumw, sumh; 1668 | int ncols, nrows, nshaded; 1669 | int x, y, w, h; 1670 | int b, g, t; /* border and gaps for tiled windows */ 1671 | 1672 | mon = desk->mon; 1673 | b = (config.mergeborders ? (border + 1) / 2 : border); 1674 | g = (config.mergeborders ? 0 : config.gapinner); 1675 | t = (config.hidetitle ? 0 : button); 1676 | 1677 | /* get number of columns and sum their widths with borders and gaps applied */ 1678 | sumw = 0; 1679 | for (ncols = 0, col = desk->col; col; col = col->next, ncols++) 1680 | sumw += col->w + b * 2; 1681 | sumw += g * (ncols - 1); 1682 | if (ncols == 0) 1683 | return; 1684 | 1685 | /* decide whether to recalculate columns widths */ 1686 | recol = (sumw != mon->gw); 1687 | 1688 | w = (mon->gw - g * (ncols - 1) - b * 2 * ncols) / ncols; 1689 | x = mon->gx; 1690 | for (col = desk->col; col; col = col->next) { 1691 | /* the last column gets the remaining width */ 1692 | if (!col->next) 1693 | w = mon->gw + mon->gx - x - 2 * b; 1694 | 1695 | /* get number of clients in current column and sum their heights */ 1696 | sumh = 0; 1697 | for (nshaded = nrows = 0, row = col->row; row; row = row->next, nrows++) { 1698 | sumh += row->c->h + b * 2 + t; 1699 | if (row->c->isshaded) 1700 | nshaded++; 1701 | } 1702 | sumh += g * (nrows - 1); 1703 | 1704 | /* decide whether to recalculate client heights */ 1705 | rerow = (sumh != mon->gh); 1706 | 1707 | if (nshaded < nrows) 1708 | h = (mon->gh - g * (nrows - nshaded) - b * 2 * nrows - t * nrows) / (nrows - nshaded); 1709 | else 1710 | h = 0; 1711 | y = mon->gy; 1712 | 1713 | for (row = col->row; row; row = row->next) { 1714 | if (row->c->isfullscreen) 1715 | continue; 1716 | 1717 | /* the last client gets the remaining height */ 1718 | if (!row->next) 1719 | h = mon->gh + mon->gy - y - t - 2 * b; 1720 | 1721 | /* set new width and heights */ 1722 | if (recol || !col->next) 1723 | col->w = w; 1724 | if (rerow || !row->next) 1725 | row->h = h; 1726 | 1727 | /* if there is only one tiled window, borders or gaps can be ignored */ 1728 | clientborderwidth(row->c, b); 1729 | if (!row->c->isshaded) 1730 | clienttitlewidth(row->c, (config.hidetitle ? 0 : button)); 1731 | if (nrows == 1 && ncols == 1) { 1732 | if (config.ignoreborders) { 1733 | col->w += 2 * b; 1734 | row->h += 2 * b - t; 1735 | clientborderwidth(row->c, 0); 1736 | } 1737 | if (config.ignoregaps) { 1738 | x = mon->wx; 1739 | y = mon->wy; 1740 | col->w = mon->ww - ((!config.ignoreborders) ? 2 * b : 0); 1741 | row->h = mon->wh - ((!config.ignoreborders) ? 2 * b : 0) - t; 1742 | } 1743 | if (config.ignoretitle && !row->c->isshaded && row->c->ntabs == 1) { 1744 | clienttitlewidth(row->c, 0); 1745 | row->h += t; 1746 | } 1747 | } 1748 | 1749 | if (row->c->isshaded) 1750 | row->h = 0; 1751 | 1752 | row->c->x = x + row->c->b; 1753 | row->c->y = y + row->c->b + row->c->t; 1754 | row->c->w = col->w; 1755 | row->c->h = row->h; 1756 | if (clientisvisible(row->c)) { 1757 | clientmoveresize(row->c); 1758 | } 1759 | 1760 | y += row->h + g + b * 2 + t; 1761 | } 1762 | 1763 | x += col->w + g + b * 2; 1764 | } 1765 | } 1766 | 1767 | /* allocate column in desktop */ 1768 | static struct Column * 1769 | coladd(struct Desktop *desk, int end) 1770 | { 1771 | struct Column *col, *lastcol; 1772 | 1773 | col = emalloc(sizeof *col); 1774 | col->desk = desk; 1775 | col->prev = NULL; 1776 | col->next = NULL; 1777 | col->row = NULL; 1778 | col->w = 0; 1779 | if (desk->col == NULL) { 1780 | desk->col = col; 1781 | } else if (end) { 1782 | for (lastcol = desk->col; lastcol->next; lastcol = lastcol->next) 1783 | ; 1784 | lastcol->next = col; 1785 | col->prev = lastcol; 1786 | } else { 1787 | if (desk->col) 1788 | desk->col->prev = col; 1789 | col->next = desk->col; 1790 | desk->col = col; 1791 | } 1792 | return col; 1793 | } 1794 | 1795 | /* deallocate column */ 1796 | static void 1797 | coldel(struct Column *col) 1798 | { 1799 | if (col->next) 1800 | col->next->prev = col->prev; 1801 | if (col->prev) 1802 | col->prev->next = col->next; 1803 | else 1804 | col->desk->col = col->next; 1805 | free(col); 1806 | } 1807 | 1808 | /* allocate row in column */ 1809 | static struct Row * 1810 | rowadd(struct Column *col) 1811 | { 1812 | struct Row *row, *lastrow; 1813 | 1814 | row = emalloc(sizeof *row); 1815 | row->prev = NULL; 1816 | row->next = NULL; 1817 | row->col = col; 1818 | row->c = None; 1819 | row->h = 0; 1820 | if (col->row == NULL) { 1821 | col->row = row; 1822 | } else { 1823 | for (lastrow = col->row; lastrow->next; lastrow = lastrow->next) 1824 | ; 1825 | lastrow->next = row; 1826 | row->prev = lastrow; 1827 | } 1828 | return row; 1829 | } 1830 | 1831 | /* deallocate row */ 1832 | static void 1833 | rowdel(struct Row *row) 1834 | { 1835 | if (row->next) 1836 | row->next->prev = row->prev; 1837 | if (row->prev) 1838 | row->prev->next = row->next; 1839 | else 1840 | row->col->row = row->next; 1841 | if (row->col->row == NULL) 1842 | coldel(row->col); 1843 | free(row); 1844 | } 1845 | 1846 | /* get the retion position x,y (relative to frame, not the content) is in the frame */ 1847 | static int 1848 | frameregion(struct Client *c, Window win, int x, int y) 1849 | { 1850 | struct Tab *t; 1851 | 1852 | for (t = c->tabs; t; t = t->next) 1853 | if (win == t->title) 1854 | return FrameTitle; 1855 | if (win != c->frame) 1856 | return FrameNone; 1857 | if (x >= c->b && x < c->b + button && y >= c->b && y < c->b + button) 1858 | return FrameButtonLeft; 1859 | if (x >= c->b + c->w - button && x < c->b + c->w && y >= c->b && y < c->b + button) 1860 | return FrameButtonRight; 1861 | if (x >= c->b + button && y >= c->b && x < c->b + c->w - button && y < c->b + button) 1862 | return FrameTitle; 1863 | if (x < c->b || y < c->b || x >= c->b + c->w || y >= c->b + c->h) 1864 | return FrameBorder; 1865 | return FrameNone; 1866 | } 1867 | 1868 | /* get in which corner or side of window the cursor is in */ 1869 | static enum Octant 1870 | frameoctant(struct Client *c, Window win, int x, int y) 1871 | { 1872 | double tan; 1873 | int wm, hm; 1874 | int w, h; 1875 | int b; 1876 | 1877 | if (c == NULL || c->state == Minimized) 1878 | return SE; 1879 | if (win == c->frame || win == c->curswin) { 1880 | x -= c->b; 1881 | y -= c->b + c->t; 1882 | } 1883 | w = c->w; 1884 | h = c->h; 1885 | b = corner - c->b; 1886 | if (x >= w - b && y >= h - b) 1887 | return SE; 1888 | if (x >= w - b && y <= 0) 1889 | return NE; 1890 | if (x <= b && y >= h - b) 1891 | return SW; 1892 | if (x <= b && y <= 0) 1893 | return NW; 1894 | if (x < 0) 1895 | return W; 1896 | if (y < 0) 1897 | return N; 1898 | if (x >= w) 1899 | return E; 1900 | if (y >= h) 1901 | return S; 1902 | wm = w / 2; 1903 | hm = h / 2; 1904 | if (c->state == Tiled) { 1905 | tan = (double)h/w; 1906 | if (tan == 0.0) 1907 | tan = 1.0; 1908 | if (y >= hm) { 1909 | h = y - hm; 1910 | w = h / tan; 1911 | if (x < wm - w) { 1912 | return W; 1913 | } if (x > wm + w) { 1914 | return E; 1915 | } else { 1916 | return S; 1917 | } 1918 | } else { 1919 | h = hm - y; 1920 | w = h / tan; 1921 | if (x < wm - w) { 1922 | return W; 1923 | } if (x > wm + w) { 1924 | return E; 1925 | } else { 1926 | return N; 1927 | } 1928 | } 1929 | return N; 1930 | } else { 1931 | if (x >= wm && y >= hm) { 1932 | return SE; 1933 | } 1934 | if (x >= wm && y <= hm) { 1935 | return NE; 1936 | } 1937 | if (x <= wm && y >= hm) { 1938 | return SW; 1939 | } 1940 | if (x <= wm && y <= hm) { 1941 | return NW; 1942 | } 1943 | } 1944 | return SE; 1945 | } 1946 | 1947 | /* find best position to place a client on screen */ 1948 | static void 1949 | clientplace(struct Client *c, struct Desktop *desk) 1950 | { 1951 | struct Monitor *mon; 1952 | struct Client *tmp; 1953 | int grid[DIV][DIV] = {{0}, {0}}; 1954 | int lowest; 1955 | int i, j, k, w, h; 1956 | int subx, suby; /* position of the larger subregion */ 1957 | int subw, subh; /* larger subregion width and height */ 1958 | int origw, origh; 1959 | 1960 | if (desk == NULL || c == NULL || c->state == Tiled || c->isfullscreen || c->state == Minimized) 1961 | return; 1962 | 1963 | mon = desk->mon; 1964 | 1965 | /* if window is bigger than monitor, resize it while maintaining proportion */ 1966 | origw = c->fw + 2 * c->b; 1967 | origh = c->fh + 2 * c->b + c->t; 1968 | w = min(origw, mon->gw); 1969 | h = min(origh, mon->gh); 1970 | if (origw * h > origh * w) { 1971 | h = (origh * w) / origw; 1972 | w = (origw * h) / origh; 1973 | } else { 1974 | w = (origw * h) / origh; 1975 | h = (origh * w) / origw; 1976 | } 1977 | c->fw = max(minsize, w - (2 * c->b)); 1978 | c->fh = max(minsize, h - (2 * c->b + c->t)); 1979 | 1980 | /* if the user placed the window, we should not re-place it */ 1981 | if (c->isuserplaced) 1982 | return; 1983 | 1984 | /* increment cells of grid a window is in */ 1985 | for (tmp = clients; tmp; tmp = tmp->next) { 1986 | if (tmp != c && ((tmp->state == Sticky && tmp->mon == mon) || (tmp->state == Normal && tmp->desk == desk))) { 1987 | for (i = 0; i < DIV; i++) { 1988 | for (j = 0; j < DIV; j++) { 1989 | int ha, hb, wa, wb; 1990 | int ya, yb, xa, xb; 1991 | ha = mon->gy + (mon->gh * i)/DIV; 1992 | hb = mon->gy + (mon->gh * (i + 1))/DIV; 1993 | wa = mon->gx + (mon->gw * j)/DIV; 1994 | wb = mon->gx + (mon->gw * (j + 1))/DIV; 1995 | ya = tmp->fy; 1996 | yb = tmp->fy + tmp->fh + 2 * tmp->b + tmp->t; 1997 | xa = tmp->fx; 1998 | xb = tmp->fx + tmp->fw + 2 * tmp->b; 1999 | if (ya <= hb && ha <= yb && xa <= wb && wa <= xb) { 2000 | if (ya < ha && yb > hb) 2001 | grid[i][j]++; 2002 | if (xa < wa && xb > wb) 2003 | grid[i][j]++; 2004 | grid[i][j]++; 2005 | } 2006 | } 2007 | } 2008 | } 2009 | } 2010 | 2011 | /* find biggest region in grid with less windows in it */ 2012 | lowest = INT_MAX; 2013 | subx = suby = 0; 2014 | subw = subh = 0; 2015 | for (i = 0; i < DIV; i++) { 2016 | for (j = 0; j < DIV; j++) { 2017 | if (grid[i][j] > lowest) 2018 | continue; 2019 | else if (grid[i][j] < lowest) { 2020 | lowest = grid[i][j]; 2021 | subw = subh = 0; 2022 | } 2023 | for (w = 0; j+w < DIV && grid[i][j + w] == lowest; w++) 2024 | ; 2025 | for (h = 1; i+h < DIV && grid[i + h][j] == lowest; h++) { 2026 | for (k = 0; k < w && grid[i + h][j + k] == lowest; k++) 2027 | ; 2028 | if (k < w) 2029 | break; 2030 | } 2031 | if (k < w) 2032 | h--; 2033 | if (w * h > subw * subh) { 2034 | subw = w; 2035 | subh = h; 2036 | suby = i; 2037 | subx = j; 2038 | } 2039 | } 2040 | } 2041 | subx = subx * mon->gw / DIV; 2042 | suby = suby * mon->gh / DIV; 2043 | subw = subw * mon->gw / DIV; 2044 | subh = subh * mon->gh / DIV; 2045 | c->fx = min(mon->gx + mon->gw - c->fw - c->b, max(mon->gx + c->b, mon->gx + subx + subw / 2 - c->fw / 2)); 2046 | c->fy = min(mon->gy + mon->gh - c->fh - c->b, max(mon->gy + c->t + c->b, mon->gy + suby + subh / 2 - c->fh / 2)); 2047 | } 2048 | 2049 | /* apply size constraints */ 2050 | static void 2051 | clientapplysize(struct Client *c) 2052 | { 2053 | if (c == NULL) 2054 | return; 2055 | c->w = max(c->fw, minsize); 2056 | if (!c->isshaded) 2057 | c->h = max(c->fh, minsize); 2058 | c->x = c->fx; 2059 | c->y = c->fy; 2060 | } 2061 | 2062 | /* remove client from the focus list */ 2063 | static void 2064 | clientdelfocus(struct Client *c) 2065 | { 2066 | if (c->fnext) { 2067 | c->fnext->fprev = c->fprev; 2068 | } 2069 | if (c->fprev) { 2070 | c->fprev->fnext = c->fnext; 2071 | } else if (focuslist == c) { 2072 | focuslist = c->fnext; 2073 | } 2074 | } 2075 | 2076 | /* put client on beginning of focus list */ 2077 | static void 2078 | clientaddfocus(struct Client *c) 2079 | { 2080 | if (c == NULL || c->state == Minimized) 2081 | return; 2082 | clientdelfocus(c); 2083 | c->fnext = focuslist; 2084 | c->fprev = NULL; 2085 | if (focuslist) 2086 | focuslist->fprev = c; 2087 | focuslist = c; 2088 | } 2089 | 2090 | /* remove client from the raise list */ 2091 | static void 2092 | clientdelraise(struct Client *c) 2093 | { 2094 | if (c->rnext) { 2095 | c->rnext->rprev = c->rprev; 2096 | } 2097 | if (c->rprev) { 2098 | c->rprev->rnext = c->rnext; 2099 | } else if (raiselist == c) { 2100 | raiselist = c->rnext; 2101 | } 2102 | } 2103 | 2104 | /* put client on beginning of raise list */ 2105 | static void 2106 | clientaddraise(struct Client *c) 2107 | { 2108 | clientdelraise(c); 2109 | c->rnext = raiselist; 2110 | c->rprev = NULL; 2111 | if (raiselist) 2112 | raiselist->rprev = c; 2113 | raiselist = c; 2114 | } 2115 | 2116 | /* raise client */ 2117 | static void 2118 | clientraise(struct Client *c) 2119 | { 2120 | Window wins[2]; 2121 | 2122 | if (c == NULL || c->state == Minimized) 2123 | return; 2124 | clientaddraise(c); 2125 | wins[1] = c->frame; 2126 | if (c->isfullscreen) 2127 | wins[0] = layerwin[LayerFullscreen]; 2128 | else if (c->state == Tiled) 2129 | wins[0] = layerwin[LayerTiled]; 2130 | else if (c->layer < 0) 2131 | wins[0] = layerwin[LayerBelow]; 2132 | else if (c->layer > 0) 2133 | wins[0] = layerwin[LayerAbove]; 2134 | else 2135 | wins[0] = layerwin[LayerTop]; 2136 | XRestackWindows(dpy, wins, sizeof wins); 2137 | ewmhsetclientsstacking(); 2138 | } 2139 | 2140 | /* hide client */ 2141 | static void 2142 | clienthide(struct Client *c, int hide) 2143 | { 2144 | struct Tab *t; 2145 | 2146 | if (c == NULL) 2147 | return; 2148 | c->ishidden = hide; 2149 | if (hide) { 2150 | XUnmapWindow(dpy, c->frame); 2151 | for (t = c->tabs; t; t = t->next) { 2152 | icccmwmstate(t->win, IconicState); 2153 | } 2154 | } else { 2155 | XMapWindow(dpy, c->frame); 2156 | for (t = c->tabs; t; t = t->next) { 2157 | icccmwmstate(t->win, NormalState); 2158 | } 2159 | } 2160 | } 2161 | 2162 | /* hide all windows and show the desktop */ 2163 | static void 2164 | clientshowdesk(int show) 2165 | { 2166 | struct Client *c; 2167 | 2168 | showingdesk = show; 2169 | for (c = clients; c; c = c->next) { 2170 | if (c->state != Minimized && 2171 | ((c->state == Sticky && c->mon == selmon) || 2172 | (c->state != Sticky && c->desk == selmon->seldesk))) { 2173 | clienthide(c, show); 2174 | } 2175 | } 2176 | ewmhsetshowingdesktop(show); 2177 | } 2178 | 2179 | /* send client to desktop, raise it and optionally place it */ 2180 | static void 2181 | clientsendtodesk(struct Client *c, struct Desktop *desk, int place) 2182 | { 2183 | if (c == NULL || desk == NULL || c->desk == desk || c->state == Minimized) 2184 | return; 2185 | c->desk = desk; 2186 | c->mon = desk->mon; 2187 | if (place) { 2188 | clientplace(c, c->desk); 2189 | clientapplysize(c); 2190 | if (clientisvisible(c)) { 2191 | clientmoveresize(c); 2192 | } 2193 | clienthide(c, !clientisvisible(c)); 2194 | } 2195 | clientraise(c); 2196 | ewmhsetwmdesktop(c); 2197 | } 2198 | 2199 | /* stick a client to the monitor */ 2200 | static int 2201 | clientstick(struct Client *c, int stick) 2202 | { 2203 | if (stick != REMOVE && c->state != Sticky) { 2204 | c->state = Sticky; 2205 | c->desk = NULL; 2206 | } else if (stick != ADD && c->state == Sticky) { 2207 | c->state = Normal; 2208 | clientsendtodesk(c, c->mon->seldesk, 0); 2209 | } else { 2210 | return 0; 2211 | } 2212 | return 1; 2213 | } 2214 | 2215 | /* (un)tile client; return whether state change occurred */ 2216 | static int 2217 | clienttile(struct Client *c, int tile) 2218 | { 2219 | struct Desktop *desk; 2220 | struct Column *col; 2221 | struct Row *row; 2222 | 2223 | desk = c->desk; 2224 | if (tile != REMOVE && c->state != Tiled) { 2225 | clientstick(c, REMOVE); 2226 | if (desk->col == NULL || desk->col->next == NULL) { 2227 | col = coladd(desk, 1); 2228 | } else { 2229 | for (col = desk->col; col->next; col = col->next) 2230 | ; 2231 | } 2232 | row = rowadd(col); 2233 | row->c = c; 2234 | c->row = row; 2235 | c->layer = 0; 2236 | c->state = Tiled; 2237 | } else if (tile != ADD && c->state == Tiled) { 2238 | clientstick(c, REMOVE); 2239 | c->state = Normal; 2240 | rowdel(c->row); 2241 | clientplace(c, c->desk); 2242 | clientborderwidth(c, border); 2243 | clienttitlewidth(c, (config.hidetitle ? 0 : button)); 2244 | clientapplysize(c); 2245 | if (clientisvisible(c)) { 2246 | clientmoveresize(c); 2247 | } 2248 | } else { 2249 | return 0; 2250 | } 2251 | clientraise(c); 2252 | if (desk == desk->mon->seldesk) { 2253 | desktile(desk); 2254 | } 2255 | return 1; 2256 | } 2257 | 2258 | /* minimize client; remember to focus another client when minimizing */ 2259 | static int 2260 | clientminimize(struct Client *c, int minimize) 2261 | { 2262 | if (minimize != REMOVE && c->state != Minimized) { 2263 | clientstick(c, REMOVE); 2264 | clienttile(c, REMOVE); 2265 | c->desk = NULL; 2266 | c->mon = NULL; 2267 | c->state = Minimized; 2268 | clienthide(c, 1); 2269 | } else if (minimize != ADD && c->state == Minimized) { 2270 | c->state = Normal; 2271 | clientsendtodesk(c, selmon->seldesk, 1); 2272 | /* no need to call clienthide(c, 0) here for clientsendtodesk already calls it */ 2273 | } else { 2274 | return 0; 2275 | } 2276 | return 1; 2277 | } 2278 | 2279 | /* raise client above others */ 2280 | static int 2281 | clientabove(struct Client *c, int above) 2282 | { 2283 | if (above != REMOVE && c->layer != 1) 2284 | c->layer = 1; 2285 | else if (above != ADD && c->layer != 0) 2286 | c->layer = 0; 2287 | else 2288 | return 0; 2289 | clientraise(c); 2290 | return 1; 2291 | } 2292 | 2293 | /* lower client below others */ 2294 | static int 2295 | clientbelow(struct Client *c, int below) 2296 | { 2297 | if (below != REMOVE && c->layer != -1) 2298 | c->layer = -1; 2299 | else if (below != ADD && c->layer != 0) 2300 | c->layer = 0; 2301 | else 2302 | return 0; 2303 | clientraise(c); 2304 | return 1; 2305 | } 2306 | 2307 | /* shade client */ 2308 | static int 2309 | clientshade(struct Client *c, int shade) 2310 | { 2311 | if (shade != REMOVE && !c->isshaded) { 2312 | clienttitlewidth(c, button); 2313 | if (config.hidetitle) 2314 | c->y += button; 2315 | c->isshaded = 1; 2316 | c->saveh = c->h; 2317 | c->h = 0; 2318 | } else if (shade != ADD && c->isshaded) { 2319 | clienttitlewidth(c, (config.hidetitle ? 0 : button)); 2320 | if (config.hidetitle) 2321 | c->y -= button; 2322 | c->isshaded = 0; 2323 | c->h = c->saveh; 2324 | } else { 2325 | return 0; 2326 | } 2327 | if (clientisvisible(c)) 2328 | clientmoveresize(c); 2329 | if (c->state == Tiled) /* retile for the window shape has changed */ 2330 | desktile(c->desk); 2331 | return 1; 2332 | } 2333 | 2334 | /* make client fullscreen */ 2335 | static int 2336 | clientfullscreen(struct Client *c, int fullscreen) 2337 | { 2338 | if (fullscreen != REMOVE && !c->isfullscreen) { 2339 | clientstick(c, REMOVE); 2340 | clientshade(c, REMOVE); 2341 | c->isfullscreen = 1; 2342 | clientborderwidth(c, 0); 2343 | clienttitlewidth(c, 0); 2344 | c->x = c->mon->mx; 2345 | c->y = c->mon->my; 2346 | c->w = c->mon->mw; 2347 | c->h = c->mon->mh; 2348 | if (clientisvisible(c)) { 2349 | clientmoveresize(c); 2350 | } 2351 | } else if (fullscreen != ADD && c->isfullscreen) { 2352 | c->isfullscreen = 0; 2353 | clienttitlewidth(c, (config.hidetitle ? 0 : button)); 2354 | clientborderwidth(c, border); 2355 | if (c->state == Tiled) { 2356 | desktile(c->desk); 2357 | } else if (clientisvisible(c)) { 2358 | clientapplysize(c); 2359 | clientmoveresize(c); 2360 | } 2361 | } else { 2362 | return 0; 2363 | } 2364 | clientraise(c); 2365 | return 1; 2366 | } 2367 | 2368 | /* focus client */ 2369 | static int 2370 | clientfocus(struct Client *c) 2371 | { 2372 | struct Client *fullscreen; 2373 | 2374 | prevfocused = focused; 2375 | if (c == NULL) { 2376 | focused = NULL; 2377 | if (prevfocused) 2378 | clientdecorate(prevfocused, 1, 0, FrameNone); 2379 | XSetInputFocus(dpy, focuswin, RevertToParent, CurrentTime); 2380 | ewmhsetactivewindow(None); 2381 | } else if ((fullscreen = getfullscreen(c->mon, c->desk)) == NULL || 2382 | fullscreen == c) { /* we should not focus a client below a fullscreen client */ 2383 | focused = c; 2384 | if (prevfocused) 2385 | clientdecorate(prevfocused, 1, 0, FrameNone); 2386 | if (c->mon) 2387 | selmon = c->mon; 2388 | if (c->state != Sticky && c->state != Minimized) 2389 | selmon->seldesk = c->desk; 2390 | clientaddfocus(c); 2391 | clientdecorate(c, 1, 0, FrameNone); 2392 | if (c->state == Minimized) 2393 | clientminimize(c, 0); 2394 | } else { 2395 | return 0; 2396 | } 2397 | return 1; 2398 | } 2399 | 2400 | /* resize client x and y pixels out of octant o */ 2401 | static void 2402 | clientincrresize(struct Client *c, enum Octant o, int x, int y) 2403 | { 2404 | int origw, origh; 2405 | 2406 | if (c == NULL || c->state == Minimized || c->isfullscreen) 2407 | return; 2408 | if (c->state == Tiled) { 2409 | if (o & N && c->row->prev) { 2410 | if (c->row->prev->h - y < minsize) 2411 | y = c->row->prev->h - minsize; 2412 | if (c->row->h + y < minsize) 2413 | return; 2414 | c->row->h += y; 2415 | c->row->prev->h -= y; 2416 | } 2417 | if (o & S && c->row->next) { 2418 | if (c->row->next->h - y < minsize) 2419 | y = c->row->next->h - minsize; 2420 | if (c->row->h + y < minsize) 2421 | return; 2422 | c->row->h += y; 2423 | c->row->next->h -= y; 2424 | } 2425 | if (o & W && c->row->col->prev) { 2426 | if (c->row->col->prev->w - x < minsize) 2427 | x = c->row->col->prev->w - minsize; 2428 | if (c->row->col->w + x < minsize) 2429 | return; 2430 | c->row->col->w += x; 2431 | c->row->col->prev->w -= x; 2432 | } 2433 | if (o & E && c->row->col->next) { 2434 | if (c->row->col->next->w - x < minsize) 2435 | x = c->row->col->next->w - minsize; 2436 | if (c->row->col->w + x < minsize) 2437 | return; 2438 | c->row->col->w += x; 2439 | c->row->col->next->w -= x; 2440 | } 2441 | desktile(c->desk); 2442 | } else { 2443 | if (c->fw + x < minsize || c->fh + y < minsize) 2444 | return; 2445 | origw = c->w; 2446 | origh = c->h; 2447 | c->fw += x; 2448 | if (!c->isshaded) 2449 | c->fh += y; 2450 | clientapplysize(c); 2451 | if (o & N) 2452 | c->fy -= c->h - origh; 2453 | if (o & W) 2454 | c->fx -= c->w - origw; 2455 | c->x = c->fx; 2456 | c->y = c->fy; 2457 | clientmoveresize(c); 2458 | } 2459 | } 2460 | 2461 | /* move client x pixels to the right and y pixels down */ 2462 | static void 2463 | clientincrmove(struct Client *c, int x, int y) 2464 | { 2465 | struct Monitor *monto; 2466 | struct Column *col = NULL; 2467 | struct Row *row = NULL; 2468 | int tmp; 2469 | 2470 | if (c == NULL || c->state == Minimized || c->isfullscreen) 2471 | return; 2472 | if (c->state == Tiled) { 2473 | row = c->row; 2474 | tmp = row->h; 2475 | if (x > 0) { 2476 | if (row->col->next) 2477 | col = row->col->next; 2478 | else if (!(row->col->row == row && row->next == NULL)) 2479 | col = coladd(row->col->desk, 1); 2480 | if (col) { 2481 | rowdel(row); 2482 | row = rowadd(col); 2483 | row->c = c; 2484 | row->h = tmp; 2485 | c->row = row; 2486 | } 2487 | } 2488 | if (x < 0) { 2489 | if (row->col->prev) 2490 | col = row->col->prev; 2491 | else if (!(row->col->row == row && row->next == NULL)) 2492 | col = coladd(row->col->desk, 0); 2493 | if (col) { 2494 | rowdel(row); 2495 | row = rowadd(col); 2496 | row->c = c; 2497 | c->row = row; 2498 | } 2499 | } 2500 | if (y < 0) { 2501 | if (row->prev) { 2502 | row->c = row->prev->c; 2503 | row->c->row = row; 2504 | row->prev->c = c; 2505 | row->prev->c->row = row->prev; 2506 | row->h = row->prev->h; 2507 | row->prev->h = tmp; 2508 | } 2509 | } 2510 | if (y > 0) { 2511 | if (row->next) { 2512 | row->c = row->next->c; 2513 | row->c->row = row; 2514 | row->next->c = c; 2515 | row->next->c->row = row->next; 2516 | row->h = row->next->h; 2517 | row->next->h = tmp; 2518 | } 2519 | } 2520 | desktile(c->desk); 2521 | } else { 2522 | c->fx += x; 2523 | c->fy += y; 2524 | c->x = c->fx; 2525 | c->y = c->fy; 2526 | clientmoveresize(c); 2527 | if (c->state != Sticky) { 2528 | monto = getmon(c->fx + c->fw / 2, c->fy + c->fh / 2); 2529 | if (monto && monto != c->mon) { 2530 | clientsendtodesk(c, monto->seldesk, 0); 2531 | } 2532 | } 2533 | } 2534 | } 2535 | 2536 | /* add client */ 2537 | static struct Client * 2538 | clientadd(int x, int y, int w, int h, int isuserplaced) 2539 | { 2540 | struct Client *c; 2541 | 2542 | c = emalloc(sizeof *c); 2543 | c->fprev = c->fnext = NULL; 2544 | c->rprev = c->rnext = NULL; 2545 | c->mon = NULL; 2546 | c->desk = NULL; 2547 | c->row = NULL; 2548 | c->isfullscreen = 0; 2549 | c->isuserplaced = isuserplaced; 2550 | c->isshaded = 0; 2551 | c->ishidden = 0; 2552 | c->state = Normal; 2553 | c->layer = 0; 2554 | c->pw = c->ph = 0; 2555 | c->x = c->fx = x; 2556 | c->y = c->fy = y; 2557 | c->w = c->fw = w; 2558 | c->h = c->fh = h; 2559 | c->saveh = c->h; 2560 | c->b = border; 2561 | c->t = (config.hidetitle ? 0 : button); 2562 | c->seltab = NULL; 2563 | c->tabs = NULL; 2564 | c->ntabs = 0; 2565 | c->prev = NULL; 2566 | c->pix = None; 2567 | c->frame = XCreateWindow(dpy, root, c->x - c->b, c->y - c->b, 2568 | c->w + c->b * 2, c->h + c->b * 2 + c->t, 0, 2569 | CopyFromParent, CopyFromParent, CopyFromParent, 2570 | CWEventMask, &clientswa); 2571 | c->curswin = XCreateWindow(dpy, c->frame, 0, 0, c->w + c->b * 2, c->h + c->b * 2 + c->t, 0, 2572 | CopyFromParent, InputOnly, CopyFromParent, 0, NULL); 2573 | if (clients) 2574 | clients->prev = c; 2575 | c->next = clients; 2576 | clients = c; 2577 | XMapWindow(dpy, c->curswin); 2578 | return c; 2579 | } 2580 | 2581 | /* delete client */ 2582 | static void 2583 | clientdel(struct Client *c) 2584 | { 2585 | clientdelfocus(c); 2586 | clientdelraise(c); 2587 | if (focused == c) 2588 | focused = NULL; 2589 | if (raised == c) 2590 | raised = NULL; 2591 | if (c->next) 2592 | c->next->prev = c->prev; 2593 | if (c->prev) 2594 | c->prev->next = c->next; 2595 | else 2596 | clients = c->next; 2597 | if (c->state == Tiled) { 2598 | rowdel(c->row); 2599 | } 2600 | if (c->state == Tiled) 2601 | desktile(c->desk); 2602 | while (c->tabs) 2603 | tabdel(c->tabs); 2604 | if (c->pix != None) 2605 | XFreePixmap(dpy, c->pix); 2606 | XDestroyWindow(dpy, c->frame); 2607 | XDestroyWindow(dpy, c->curswin); 2608 | free(c); 2609 | } 2610 | 2611 | /* configure client size and position */ 2612 | static void 2613 | clientconfigure(struct Client *c, unsigned int valuemask, XWindowChanges *wc) 2614 | { 2615 | int x, y, w, h; 2616 | 2617 | if (c == NULL || c->state == Minimized || c->isfullscreen) 2618 | return; 2619 | if (c->state == Tiled) { 2620 | x = y = w = h = 0; 2621 | if (valuemask & CWX) 2622 | x = wc->x - c->x; 2623 | if (valuemask & CWY) 2624 | y = wc->y - c->y; 2625 | if (valuemask & CWWidth) 2626 | w = wc->width - c->w; 2627 | if (valuemask & CWHeight) 2628 | h = wc->height - c->h; 2629 | if (x || y) 2630 | clientincrmove(c, x, y); 2631 | if (w || h) 2632 | clientincrresize(c, SW, w, h); 2633 | } else { 2634 | if (valuemask & CWX) 2635 | c->fx = wc->x; 2636 | if (valuemask & CWY) 2637 | c->fy = wc->y; 2638 | if (valuemask & CWWidth) 2639 | c->fw = wc->width; 2640 | if (valuemask & CWHeight) 2641 | c->fh = wc->height; 2642 | clientapplysize(c); 2643 | if (clientisvisible(c)) { 2644 | clientmoveresize(c); 2645 | } 2646 | } 2647 | } 2648 | 2649 | /* check if new client size is ok */ 2650 | static int 2651 | clientvalidsize(struct Client *c, enum Octant o, int dx, int dy) 2652 | { 2653 | if (dy != 0) { 2654 | if (c->state == Tiled) { 2655 | if (o & N) { 2656 | return c->row->prev && 2657 | c->row->prev->h - dy >= minsize && 2658 | c->row->h + dy >= minsize; 2659 | } else if (o & S) { 2660 | return c->row->next && 2661 | c->row->next->h - dy >= minsize && 2662 | c->row->h + dy >= minsize; 2663 | } 2664 | } else { 2665 | return c->h + dy >= minsize; 2666 | } 2667 | } else if (dx != 0) { 2668 | if (c->state == Tiled) { 2669 | if (o & W) { 2670 | return c->row->col->prev && 2671 | c->row->col->prev->w - dx >= minsize && 2672 | c->row->col->w + dx >= minsize; 2673 | } else if (o & E) { 2674 | return c->row->col->next && 2675 | c->row->col->next->w - dx >= minsize && 2676 | c->row->col->w + dx >= minsize; 2677 | } 2678 | } else { 2679 | return c->w + dx >= minsize; 2680 | } 2681 | } 2682 | return 0; 2683 | } 2684 | 2685 | /* call function to change client state and set corresponding ewmh properties */ 2686 | static void 2687 | clientstate(struct Client *c, int state, long int flag) 2688 | { 2689 | struct Client *f; 2690 | 2691 | switch (state) { 2692 | case ABOVE: 2693 | if (c == NULL || c->state == Minimized || c->state == Tiled || c->isfullscreen) 2694 | break; 2695 | if (clientabove(c, flag)) 2696 | ewmhsetstate(c); 2697 | break; 2698 | case BELOW: 2699 | if (c == NULL || c->state == Minimized || c->state == Tiled || c->isfullscreen) 2700 | break; 2701 | if (clientbelow(c, flag)) 2702 | ewmhsetstate(c); 2703 | break; 2704 | case STICK: 2705 | if (c == NULL || c->state == Minimized || c->state == Tiled || c->isfullscreen) 2706 | break; 2707 | if (clientstick(c, flag)) { 2708 | ewmhsetwmdesktop(c); 2709 | ewmhsetstate(c); 2710 | } 2711 | break; 2712 | case MAXIMIZE: 2713 | if (c == NULL || c->state == Minimized || c->isfullscreen) 2714 | break; 2715 | if (clienttile(c, flag)) { 2716 | ewmhsetwmdesktop(c); 2717 | ewmhsetstate(c); 2718 | } 2719 | break; 2720 | case SHADE: 2721 | if (c == NULL || c->state == Minimized || c->isfullscreen) 2722 | break; 2723 | if (clientshade(c, flag)) { 2724 | tabfocus(c->seltab); 2725 | ewmhsetstate(c); 2726 | } 2727 | break; 2728 | case FULLSCREEN: 2729 | if (c == NULL || c->state == Minimized) 2730 | break; 2731 | if (clientfullscreen(c, flag)) { 2732 | tabfocus(c->seltab); 2733 | ewmhsetstate(c); 2734 | } 2735 | break; 2736 | case HIDE: 2737 | if (c == NULL) 2738 | break; 2739 | if (clientminimize(c, flag)) { 2740 | f = (c->state == Minimized) ? getnextfocused(c) : c; 2741 | clientfocus(f); 2742 | if (f != NULL) 2743 | tabfocus(f->seltab); 2744 | ewmhsetwmdesktop(c); 2745 | ewmhsetstate(c); 2746 | } 2747 | break; 2748 | case FOCUS: 2749 | if (c != NULL && c->state == Minimized) 2750 | break; 2751 | if (clientfocus(c)) { 2752 | ewmhsetcurrentdesktop(selmon->seldesk->n); 2753 | ewmhsetstate(c); 2754 | if (c != NULL) { 2755 | tabfocus(c->seltab); 2756 | } 2757 | } 2758 | break; 2759 | } 2760 | if (focused != prevfocused) { 2761 | ewmhsetstate(prevfocused); 2762 | } 2763 | } 2764 | 2765 | /* add tab into client w*/ 2766 | static void 2767 | clienttab(struct Client *c, struct Tab *t, int pos) 2768 | { 2769 | struct Client *oldc; 2770 | struct Tab *tmp, *prev; 2771 | int i; 2772 | 2773 | oldc = t->c; 2774 | t->c = c; 2775 | c->seltab = t; 2776 | c->ntabs++; 2777 | if (pos == 0 || c->tabs == NULL) { 2778 | t->prev = NULL; 2779 | t->next = c->tabs; 2780 | if (c->tabs) 2781 | c->tabs->prev = t; 2782 | c->tabs = t; 2783 | } else { 2784 | for (i = 0, prev = tmp = c->tabs; tmp && (pos < 0 || i < pos); tmp = tmp->next, i++) 2785 | prev = tmp; 2786 | if (prev->next) 2787 | prev->next->prev = t; 2788 | t->next = prev->next; 2789 | t->prev = prev; 2790 | prev->next = t; 2791 | } 2792 | calctabs(c); 2793 | if (t->title == None) { 2794 | t->title = XCreateWindow(dpy, c->frame, c->b + button + t->x, c->b, t->w, button, 0, 2795 | CopyFromParent, CopyFromParent, CopyFromParent, 2796 | CWEventMask, &clientswa); 2797 | } else { 2798 | XReparentWindow(dpy, t->title, c->frame, c->b, c->b); 2799 | } 2800 | XReparentWindow(dpy, t->frame, c->frame, c->b, c->b + c->t); 2801 | XMapWindow(dpy, t->title); 2802 | XMapWindow(dpy, t->frame); 2803 | XMapSubwindows(dpy, t->frame); 2804 | if (oldc) { /* deal with the frame this tab came from */ 2805 | if (oldc->ntabs == 0) { 2806 | clientdel(oldc); 2807 | } else if (oldc->state == Tiled) { 2808 | desktile(oldc->desk); 2809 | } 2810 | } 2811 | if (clientisvisible(c)) 2812 | clientstate(c, FOCUS, ADD); 2813 | ewmhsetframeextents(t->win, c->b, c->t); 2814 | ewmhsetclients(); 2815 | ewmhsetclientsstacking(); 2816 | } 2817 | 2818 | /* change desktop */ 2819 | static void 2820 | deskchange(struct Desktop *desk) 2821 | { 2822 | struct Client *c; 2823 | int cursorx, cursory; 2824 | Window da, db; /* dummy variables */ 2825 | int dx, dy; /* dummy variables */ 2826 | unsigned int du; /* dummy variable */ 2827 | 2828 | if (desk == NULL || desk == selmon->seldesk) 2829 | return; 2830 | if (!deskisvisible(desk)) { 2831 | /* hide clients of previous current desktop */ 2832 | for (c = clients; c; c = c->next) { 2833 | if (c->desk == desk->mon->seldesk) { 2834 | clienthide(c, 1); 2835 | } 2836 | } 2837 | 2838 | /* unhide clientsof new current desktop */ 2839 | for (c = clients; c; c = c->next) { 2840 | if (c->desk == desk) { 2841 | if (!c->isfullscreen && c->state == Normal) { 2842 | clientapplysize(c); 2843 | clientmoveresize(c); 2844 | } 2845 | clienthide(c, 0); 2846 | } 2847 | } 2848 | } 2849 | 2850 | /* if changing focus to a new monitor and the cursor isn't there, warp it */ 2851 | XQueryPointer(dpy, root, &da, &db, &cursorx, &cursory, &dx, &dy, &du); 2852 | if (desk->mon != selmon && desk->mon != getmon(cursorx, cursory)) { 2853 | XWarpPointer(dpy, None, root, 0, 0, 0, 0, desk->mon->mx + desk->mon->mw / 2, 2854 | desk->mon->my + desk->mon->mh / 2); 2855 | } 2856 | 2857 | /* update current desktop */ 2858 | selmon = desk->mon; 2859 | selmon->seldesk = desk; 2860 | if (showingdesk) 2861 | clientshowdesk(0); 2862 | ewmhsetcurrentdesktop(desk->n); 2863 | desktile(desk); 2864 | 2865 | /* focus client on the new current desktop */ 2866 | clientstate(getnextfocused(NULL), FOCUS, ADD); 2867 | } 2868 | 2869 | /* configure transient window */ 2870 | static void 2871 | transconfigure(struct Transient *trans, unsigned int valuemask, XWindowChanges *wc) 2872 | { 2873 | if (trans == NULL) 2874 | return; 2875 | if (valuemask & CWWidth) { 2876 | trans->maxw = wc->width; 2877 | } 2878 | if (valuemask & CWHeight) { 2879 | trans->maxh = wc->height; 2880 | } 2881 | if (clientisvisible(trans->t->c)) { 2882 | clientretab(trans->t->c); 2883 | } 2884 | } 2885 | 2886 | /* check if there is a titlebar of a client under cursor; return client */ 2887 | static struct Client * 2888 | getclientbytitle(int x, int y, int *pos) 2889 | { 2890 | struct Client *c; 2891 | 2892 | for (c = clients; c; c = c->next) { 2893 | if (clientisvisible(c) && y >= c->y - c->t - c->b && y < c->y && x >= c->x && x < c->x + c->w) { 2894 | *pos = (1 + (2 * c->ntabs * (x - c->x)) / c->w) / 2; 2895 | return c; 2896 | } 2897 | } 2898 | return NULL; 2899 | } 2900 | 2901 | /* check whether to place new window in tab rather than in new frame*/ 2902 | static int 2903 | tabwindow(const char *class, int autotab) 2904 | { 2905 | /* auto tab should be anabled */ 2906 | if (autotab == NoAutoTab) 2907 | return 0; 2908 | 2909 | /* there should be a focused frame */ 2910 | if (!focused) 2911 | return 0; 2912 | 2913 | /* focused frame must be unshaded with title bar visible */ 2914 | if (focused->isfullscreen || focused->state == Minimized) 2915 | return 0; 2916 | 2917 | /* classes must match */ 2918 | if (!class || !focused->seltab->class || strcmp(class, focused->seltab->class) != 0) 2919 | return 0; 2920 | 2921 | switch (autotab) { 2922 | case TabFloating: 2923 | return focused->state != Tiled; 2924 | case TabTilingAlways: 2925 | return focused->state == Tiled; 2926 | case TabTilingMulti: 2927 | return focused->state == Tiled && (focused->desk->col->next || focused->desk->col->row->next); 2928 | case TabAlways: 2929 | return 1; 2930 | } 2931 | return 0; 2932 | } 2933 | 2934 | /* update tab urgency */ 2935 | static void 2936 | tabupdateurgency(struct Tab *t, int isurgent) 2937 | { 2938 | int prev; 2939 | 2940 | prev = t->isurgent; 2941 | t->isurgent = isurgent; 2942 | if (t->isurgent && t->c == focused && t == t->c->seltab) { 2943 | tabclearurgency(t); 2944 | } 2945 | if (prev != t->isurgent) { 2946 | clientdecorate(t->c, 1, 0, FrameNone); 2947 | } 2948 | } 2949 | 2950 | /* call the proper decorate function */ 2951 | static void 2952 | decorate(struct Winres *res) 2953 | { 2954 | int fullw, fullh; 2955 | 2956 | if (res->n) { 2957 | XCopyArea(dpy, res->n->pix, res->n->frame, gc, 0, 0, res->n->w, res->n->h, 0, 0); 2958 | } else if (res->trans) { 2959 | fullw = res->trans->w + 2 * border; 2960 | fullh = res->trans->h + 2 * border; 2961 | XCopyArea(dpy, res->trans->pix, res->trans->frame, gc, 0, 0, fullw, fullh, 0, 0); 2962 | } else if (res->t) { 2963 | XCopyArea(dpy, res->t->pix, res->t->title, gc, 0, 0, res->t->w, button, 0, 0); 2964 | } else if (res->c) { 2965 | fullw = WIDTH(res->c); 2966 | fullh = HEIGHT(res->c); 2967 | XCopyArea(dpy, res->c->pix, res->c->frame, gc, 0, 0, fullw, fullh, 0, 0); 2968 | } 2969 | } 2970 | 2971 | /* send a WM_DELETE message to client */ 2972 | static void 2973 | windowclose(Window win) 2974 | { 2975 | XEvent ev; 2976 | 2977 | ev.type = ClientMessage; 2978 | ev.xclient.window = win; 2979 | ev.xclient.message_type = atoms[WMProtocols]; 2980 | ev.xclient.format = 32; 2981 | ev.xclient.data.l[0] = atoms[WMDeleteWindow]; 2982 | ev.xclient.data.l[1] = CurrentTime; 2983 | 2984 | /* 2985 | * communicate with the given Client, kindly telling it to 2986 | * close itself and terminate any associated processes using 2987 | * the WM_DELETE_WINDOW protocol 2988 | */ 2989 | XSendEvent(dpy, win, False, NoEventMask, &ev); 2990 | } 2991 | 2992 | /* select window input events, grab mouse button presses, and clear its border */ 2993 | static void 2994 | preparewin(Window win) 2995 | { 2996 | XSelectInput(dpy, win, EnterWindowMask | StructureNotifyMask 2997 | | PropertyChangeMask | FocusChangeMask); 2998 | XGrabButton(dpy, AnyButton, AnyModifier, win, False, ButtonPressMask, 2999 | GrabModeSync, GrabModeSync, None, None); 3000 | XSetWindowBorderWidth(dpy, win, 0); 3001 | } 3002 | 3003 | /* check if event is related to the prompt or its frame */ 3004 | static Bool 3005 | promptvalidevent(Display *dpy, XEvent *ev, XPointer arg) 3006 | { 3007 | struct Prompt *prompt; 3008 | 3009 | (void)dpy; 3010 | prompt = (struct Prompt *)arg; 3011 | switch(ev->type) { 3012 | case DestroyNotify: 3013 | if (ev->xdestroywindow.window == prompt->win) 3014 | return True; 3015 | break; 3016 | case UnmapNotify: 3017 | if (ev->xunmap.window == prompt->win) 3018 | return True; 3019 | break; 3020 | case ConfigureRequest: 3021 | if (ev->xconfigurerequest.window == prompt->win) 3022 | return True; 3023 | break; 3024 | case Expose: 3025 | case ButtonPress: 3026 | return True; 3027 | } 3028 | return False; 3029 | } 3030 | 3031 | /* decorate prompt frame */ 3032 | static void 3033 | promptdecorate(Window frame, int w, int h) 3034 | { 3035 | XGCValues val; 3036 | 3037 | val.fill_style = FillSolid; 3038 | val.foreground = decor[FOCUSED][2].bg; 3039 | XChangeGC(dpy, gc, GCFillStyle | GCForeground, &val); 3040 | XFillRectangle(dpy, frame, gc, border, border, w, h); 3041 | 3042 | val.fill_style = FillTiled; 3043 | val.tile = decor[FOCUSED][2].w; 3044 | val.ts_x_origin = 0; 3045 | val.ts_y_origin = 0; 3046 | XChangeGC(dpy, gc, GCFillStyle | GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); 3047 | XFillRectangle(dpy, frame, gc, 0, 0, border, h + border); 3048 | 3049 | val.fill_style = FillTiled; 3050 | val.tile = decor[FOCUSED][2].e; 3051 | val.ts_x_origin = border + w; 3052 | val.ts_y_origin = 0; 3053 | XChangeGC(dpy, gc, GCFillStyle | GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); 3054 | XFillRectangle(dpy, frame, gc, border + w, 0, border, h + border); 3055 | 3056 | val.fill_style = FillTiled; 3057 | val.tile = decor[FOCUSED][2].s; 3058 | val.ts_x_origin = 0; 3059 | val.ts_y_origin = border + h; 3060 | XChangeGC(dpy, gc, GCFillStyle | GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); 3061 | XFillRectangle(dpy, frame, gc, border, h, w + 2 * border, border); 3062 | 3063 | XCopyArea(dpy, decor[FOCUSED][2].sw, frame, gc, 0, 0, corner, corner, 0, h + border - corner); 3064 | XCopyArea(dpy, decor[FOCUSED][2].se, frame, gc, 0, 0, corner, corner, w + 2 * border - corner, h + border - corner); 3065 | } 3066 | 3067 | /* calculate position and size of prompt window and the size of its frame */ 3068 | static void 3069 | promptcalcgeom(int *x, int *y, int *w, int *h, int *fw, int *fh) 3070 | { 3071 | *w = min(*w, selmon->ww - border * 2); 3072 | *h = min(*h, selmon->wh - border); 3073 | *x = selmon->wx + (selmon->ww - *w) / 2 - border; 3074 | *y = 0; 3075 | *fw = *w + border * 2; 3076 | *fh = *h + border; 3077 | } 3078 | 3079 | /* get tab given window is a transient for */ 3080 | static struct Tab * 3081 | gettransfor(Window win) 3082 | { 3083 | struct Winres res; 3084 | Window tmpwin; 3085 | 3086 | if (XGetTransientForHint(dpy, win, &tmpwin)) { 3087 | res = getwin(tmpwin); 3088 | return res.t; 3089 | } 3090 | return NULL; 3091 | } 3092 | 3093 | /* get window role, class, name, etc and return window rules */ 3094 | static struct Rules 3095 | getrules(Window win, char **name, char **class) 3096 | { 3097 | static char *prefixes[LAST_PREFIX] = { 3098 | [TITLE] = "shod.title.", 3099 | [INSTANCE] = "shod.instance.", 3100 | [CLASS] = "shod.class.", 3101 | [ROLE] = "shod.role.", 3102 | }; 3103 | static char *suffixes[LAST_SUFFIX] = { 3104 | [DESKTOP] = ".desktop", 3105 | [STATE] = ".state", 3106 | [AUTOTAB] = ".autoTab", 3107 | [POSITION] = ".position", 3108 | }; 3109 | struct Rules rules = { 3110 | .desk = -1, 3111 | .state = Normal, 3112 | .autotab = NoAutoTab, 3113 | .x = -1, 3114 | .y = -1, 3115 | .w = -1, 3116 | .h = -1 3117 | }; 3118 | XClassHint chint; 3119 | XrmValue xval; 3120 | Atom ad; 3121 | size_t len; 3122 | unsigned long ld; 3123 | long n; 3124 | int i, j, id; 3125 | unsigned char *p; 3126 | char *s, *t; 3127 | char *type; 3128 | 3129 | *class = *name = NULL; 3130 | for (i = 0; i < LAST_PREFIX; i++) { 3131 | switch (i) { 3132 | case TITLE: 3133 | if ((*name = getwinname(win)) == NULL) 3134 | continue; 3135 | t = *name; 3136 | break; 3137 | case INSTANCE: 3138 | if (!XGetClassHint(dpy, win, &chint)) 3139 | continue; 3140 | t = chint.res_name; 3141 | XFree(chint.res_class); 3142 | break; 3143 | case CLASS: 3144 | if (!XGetClassHint(dpy, win, &chint)) 3145 | continue; 3146 | t = chint.res_class; 3147 | *class = (t != NULL && *t != '\0') ? estrndup(t, NAMEMAXLEN) : NULL; 3148 | XFree(chint.res_name); 3149 | break; 3150 | case ROLE: 3151 | if (XGetWindowProperty(dpy, win, atoms[WMWindowRole], 0, NAMEMAXLEN, False, 3152 | XA_STRING, &ad, &id, &ld, &ld, &p) != Success) 3153 | continue; 3154 | t = (char *)p; 3155 | break; 3156 | default: 3157 | errx(1, "getrules"); 3158 | break; 3159 | } 3160 | if (t == NULL || *t == '\0') 3161 | continue; 3162 | len = strlen(t) + RULEMINSIZ; 3163 | s = emalloc(len); 3164 | for (j = 0; j < LAST_SUFFIX; j++) { 3165 | if (j == DESKTOP && rules.desk > -1) 3166 | continue; 3167 | else if (j == STATE && rules.state != Normal) 3168 | continue; 3169 | else if (j == AUTOTAB && rules.autotab != NoAutoTab) 3170 | continue; 3171 | else if (j == POSITION && rules.x > -1) 3172 | continue; 3173 | snprintf(s, len, "%s%s%s", prefixes[i], t, suffixes[j]); 3174 | if (XrmGetResource(xdb, s, "*", &type, &xval) != True) 3175 | continue; 3176 | switch (j) { 3177 | case DESKTOP: 3178 | if ((n = strtol(xval.addr, NULL, 10)) > 0 && n <= config.ndesktops) 3179 | rules.desk = n - 1; 3180 | break; 3181 | case STATE: 3182 | if (strcasecmp(xval.addr, "sticky") == 0) 3183 | rules.state = Sticky; 3184 | else if (strcasecmp(xval.addr, "tiled") == 0) 3185 | rules.state = Tiled; 3186 | else if (strcasecmp(xval.addr, "minimized") == 0) 3187 | rules.state = Minimized; 3188 | break; 3189 | case AUTOTAB: 3190 | if (strcasecmp(xval.addr, "floating") == 0) 3191 | rules.autotab = TabFloating; 3192 | else if (strcasecmp(xval.addr, "tilingAlways") == 0) 3193 | rules.autotab = TabTilingAlways; 3194 | else if (strcasecmp(xval.addr, "tilingMulti") == 0) 3195 | rules.autotab = TabTilingMulti; 3196 | else if (strcasecmp(xval.addr, "always") == 0) 3197 | rules.autotab = TabAlways; 3198 | break; 3199 | case POSITION: 3200 | // TODO 3201 | break; 3202 | default: 3203 | errx(1, "getrules"); 3204 | break; 3205 | } 3206 | } 3207 | free(s); 3208 | switch (i) { 3209 | case TITLE: 3210 | break; 3211 | case INSTANCE: 3212 | case CLASS: 3213 | XFree(t); 3214 | break; 3215 | case ROLE: 3216 | XFree(p); 3217 | break; 3218 | } 3219 | } 3220 | return rules; 3221 | } 3222 | 3223 | /* add notification window */ 3224 | static void 3225 | notifadd(Window win, int w, int h) 3226 | { 3227 | static XSetWindowAttributes swa = { 3228 | .event_mask = SubstructureNotifyMask | SubstructureRedirectMask 3229 | }; 3230 | struct Notification *n; 3231 | 3232 | n = emalloc(sizeof *n); 3233 | n->w = w + 2 * border; 3234 | n->h = h + 2 * border; 3235 | n->pw = n->ph = 0; 3236 | n->prev = NULL; 3237 | n->next = notifications; 3238 | n->pix = None; 3239 | n->win = win; 3240 | n->frame = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, 3241 | CopyFromParent, CopyFromParent, CopyFromParent, 3242 | CWEventMask, &swa); 3243 | if (notifications) 3244 | notifications->prev = n; 3245 | notifications = n; 3246 | XReparentWindow(dpy, n->win, n->frame, 0, 0); 3247 | XMapWindow(dpy, n->win); 3248 | } 3249 | 3250 | /* decorate notification */ 3251 | static void 3252 | notifdecorate(struct Notification *n, int style) 3253 | { 3254 | XGCValues val; 3255 | int w, h; 3256 | 3257 | if (n->pw != n->w || n->ph != n->h || n->pix == None) { 3258 | if (n->pix != None) 3259 | XFreePixmap(dpy, n->pix); 3260 | n->pix = XCreatePixmap(dpy, n->frame, n->w, n->h, depth); 3261 | } 3262 | n->pw = n->w; 3263 | n->ph = n->h; 3264 | 3265 | w = n->w - 2 * border; 3266 | h = n->h - 2 * border; 3267 | 3268 | val.fill_style = FillTiled; 3269 | val.tile = decor[style][TRANSIENT].w; 3270 | val.ts_x_origin = 0; 3271 | val.ts_y_origin = 0; 3272 | XChangeGC(dpy, gc, GCFillStyle | GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); 3273 | XFillRectangle(dpy, n->pix, gc, 0, border, border, h); 3274 | 3275 | val.tile = decor[style][TRANSIENT].e; 3276 | val.ts_x_origin = border + w; 3277 | val.ts_y_origin = 0; 3278 | XChangeGC(dpy, gc, GCFillStyle | GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); 3279 | XFillRectangle(dpy, n->pix, gc, border + w, border, border, h); 3280 | 3281 | val.tile = decor[style][TRANSIENT].n; 3282 | val.ts_x_origin = 0; 3283 | val.ts_y_origin = 0; 3284 | XChangeGC(dpy, gc, GCFillStyle | GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); 3285 | XFillRectangle(dpy, n->pix, gc, border, 0, w, border); 3286 | 3287 | val.tile = decor[style][TRANSIENT].s; 3288 | val.ts_x_origin = 0; 3289 | val.ts_y_origin = border + h; 3290 | XChangeGC(dpy, gc, GCFillStyle | GCTile | GCTileStipYOrigin | GCTileStipXOrigin, &val); 3291 | XFillRectangle(dpy, n->pix, gc, border, border + h, w, border); 3292 | 3293 | XCopyArea(dpy, decor[style][TRANSIENT].nw, n->pix, gc, 0, 0, corner, corner, 0, 0); 3294 | XCopyArea(dpy, decor[style][TRANSIENT].ne, n->pix, gc, 0, 0, corner, corner, n->w - corner, 0); 3295 | XCopyArea(dpy, decor[style][TRANSIENT].sw, n->pix, gc, 0, 0, corner, corner, 0, n->h - corner); 3296 | XCopyArea(dpy, decor[style][TRANSIENT].se, n->pix, gc, 0, 0, corner, corner, n->w - corner, n->h - corner); 3297 | 3298 | val.fill_style = FillSolid; 3299 | val.foreground = decor[style][TRANSIENT].bg; 3300 | XChangeGC(dpy, gc, GCFillStyle | GCForeground, &val); 3301 | XFillRectangle(dpy, n->pix, gc, border, border, w, h); 3302 | 3303 | XCopyArea(dpy, n->pix, n->frame, gc, 0, 0, n->w, n->h, 0, 0); 3304 | } 3305 | 3306 | /* place notifications */ 3307 | static void 3308 | notifplace(void) 3309 | { 3310 | struct Notification *n; 3311 | int x, y, h; 3312 | 3313 | h = 0; 3314 | for (n = notifications; n; n = n->next) { 3315 | x = mons->gx; 3316 | y = mons->gy; 3317 | switch (config.gravity) { 3318 | case NorthWestGravity: 3319 | break; 3320 | case NorthGravity: 3321 | x += (mons->gw - n->w) / 2; 3322 | break; 3323 | case NorthEastGravity: 3324 | x += mons->gw - n->w; 3325 | break; 3326 | case WestGravity: 3327 | y += (mons->gh - n->h) / 2; 3328 | break; 3329 | case CenterGravity: 3330 | x += (mons->gw - n->w) / 2; 3331 | y += (mons->gh - n->h) / 2; 3332 | break; 3333 | case EastGravity: 3334 | x += mons->gw - n->w; 3335 | y += (mons->gh - n->h) / 2; 3336 | break; 3337 | case SouthWestGravity: 3338 | y += mons->gh - n->h; 3339 | break; 3340 | case SouthGravity: 3341 | x += (mons->gw - n->w) / 2; 3342 | y += mons->gh - n->h; 3343 | break; 3344 | case SouthEastGravity: 3345 | x += mons->gw - n->w; 3346 | y += mons->gh - n->h; 3347 | break; 3348 | } 3349 | 3350 | if (config.direction == DownWards) 3351 | y += h; 3352 | else 3353 | y -= h; 3354 | h += n->h + config.notifgap + border * 2; 3355 | 3356 | XMoveResizeWindow(dpy, n->frame, x, y, n->w, n->h); 3357 | XMoveResizeWindow(dpy, n->win, border, border, n->w - 2 * border, n->h - 2 * border); 3358 | XMapWindow(dpy, n->frame); 3359 | notify(n->win, x + border, y + border, n->w - 2 * border, n->h - 2 * border); 3360 | if (n->pw != n->w || n->ph != n->h) { 3361 | notifdecorate(n, FOCUSED); 3362 | } 3363 | } 3364 | } 3365 | 3366 | /* delete notification */ 3367 | static void 3368 | notifdel(struct Notification *n) 3369 | { 3370 | if (n->next) 3371 | n->next->prev = n->prev; 3372 | if (n->prev) 3373 | n->prev->next = n->next; 3374 | else 3375 | notifications = n->next; 3376 | if (n->pix != None) 3377 | XFreePixmap(dpy, n->pix); 3378 | XDestroyWindow(dpy, n->frame); 3379 | free(n); 3380 | notifplace(); 3381 | } 3382 | 3383 | /* add notification window into notification queue; and update notification placement */ 3384 | static void 3385 | managenotif(Window win, int w, int h) 3386 | { 3387 | notifadd(win, w, h); 3388 | notifplace(); 3389 | } 3390 | 3391 | /* map prompt, give it focus, wait for it to close, then revert focus to previously focused window */ 3392 | static void 3393 | manageprompt(Window win, int w, int h) 3394 | { 3395 | struct Prompt prompt; 3396 | struct Winres res; 3397 | XEvent ev; 3398 | int x, y, fw, fh; 3399 | 3400 | promptcalcgeom(&x, &y, &w, &h, &fw, &fh); 3401 | prompt.frame = XCreateWindow(dpy, root, x, y, fw, fh, 0, 3402 | CopyFromParent, CopyFromParent, CopyFromParent, 3403 | CWEventMask, &clientswa); 3404 | XReparentWindow(dpy, win, prompt.frame, border, 0); 3405 | XMapWindow(dpy, win); 3406 | XMapWindow(dpy, prompt.frame); 3407 | XSetInputFocus(dpy, win, RevertToParent, CurrentTime); 3408 | prompt.win = win; 3409 | while (!XIfEvent(dpy, &ev, promptvalidevent, (XPointer)&prompt)) { 3410 | switch(ev.type) { 3411 | case Expose: 3412 | if (ev.xexpose.count == 0) { 3413 | if (ev.xexpose.window == prompt.frame) { 3414 | promptdecorate(prompt.frame, w, h); 3415 | } else { 3416 | res = getwin(ev.xexpose.window); 3417 | decorate(&res); 3418 | } 3419 | } 3420 | break; 3421 | case DestroyNotify: 3422 | case UnmapNotify: 3423 | goto done; 3424 | break; 3425 | case ConfigureRequest: 3426 | w = ev.xconfigurerequest.width; 3427 | h = ev.xconfigurerequest.height; 3428 | promptcalcgeom(&x, &y, &w, &h, &fw, &fh); 3429 | XMoveResizeWindow(dpy, prompt.frame, x, y, fw, fh); 3430 | XMoveResizeWindow(dpy, win, border, 0, w, h); 3431 | break; 3432 | case ButtonPress: 3433 | if (ev.xbutton.window != win && ev.xbutton.window != prompt.frame) 3434 | windowclose(win); 3435 | XAllowEvents(dpy, ReplayPointer, CurrentTime); 3436 | break; 3437 | } 3438 | } 3439 | done: 3440 | XReparentWindow(dpy, win, root, 0, 0); 3441 | XDestroyWindow(dpy, prompt.frame); 3442 | clientstate(focused, FOCUS, ADD); 3443 | } 3444 | 3445 | /* create client for tab */ 3446 | static void 3447 | manageclient(struct Client *c, struct Tab *t, struct Rules *rules, struct Desktop *desk) 3448 | { 3449 | int focus = 1; /* whether to focus window */ 3450 | int map = 1; 3451 | 3452 | clienttab(c, t, 0); 3453 | if (rules != NULL && rules->state == Minimized) { 3454 | clientminimize(c, 1); 3455 | map = focus = 0; 3456 | } else if (rules != NULL && rules->state == Sticky) { 3457 | c->desk = desk; 3458 | clientplace(c, desk); 3459 | clientstick(c, 1); 3460 | clientapplysize(c); 3461 | focus = (getfullscreen(selmon, NULL) == NULL); 3462 | } else if (rules != NULL && rules->state == Tiled) { 3463 | clientsendtodesk(c, desk, 0); 3464 | clienttile(c, 1); 3465 | desktile(desk); 3466 | focus = (getfullscreen(desk->mon, desk) == NULL && c->desk == c->desk->mon->seldesk); 3467 | focus = (focus && c->desk == selmon->seldesk); 3468 | } else { 3469 | clientsendtodesk(c, desk, 0); 3470 | clientplace(c, c->desk); 3471 | clientapplysize(c); 3472 | focus = (getfullscreen(desk->mon, desk) == NULL && c->desk == c->desk->mon->seldesk); 3473 | focus = (focus && c->desk == selmon->seldesk); 3474 | } 3475 | if (map) { 3476 | clientraise(c); 3477 | clientmoveresize(c); 3478 | clienthide(c, 0); 3479 | } 3480 | if (focus) { 3481 | clientstate(c, FOCUS, ADD); 3482 | } else { 3483 | clientdecorate(c, 1, 0, FrameNone); 3484 | clientaddfocus(c); 3485 | clientaddfocus(focused); 3486 | } 3487 | clientnotify(c); 3488 | } 3489 | 3490 | /* add transient window into tab */ 3491 | static void 3492 | managetrans(struct Tab *t, Window win, int maxw, int maxh, int ignoreunmap) 3493 | { 3494 | struct Transient *trans; 3495 | 3496 | trans = emalloc(sizeof *trans); 3497 | trans->prev = NULL; 3498 | trans->next = NULL; 3499 | trans->t = t; 3500 | trans->x = 0; 3501 | trans->y = 0; 3502 | trans->w = 0; 3503 | trans->h = 0; 3504 | trans->pix = None; 3505 | trans->pw = trans->ph = 0; 3506 | trans->maxw = maxw; 3507 | trans->maxh = maxh; 3508 | trans->ignoreunmap = ignoreunmap; 3509 | trans->frame = XCreateWindow(dpy, root, 0, 0, maxw, maxh, 0, 3510 | CopyFromParent, CopyFromParent, CopyFromParent, 3511 | CWEventMask, &clientswa); 3512 | trans->win = win; 3513 | XReparentWindow(dpy, trans->frame, t->frame, 0, 0); 3514 | XReparentWindow(dpy, trans->win, trans->frame, 0, 0); 3515 | if (t->trans) 3516 | t->trans->prev = trans; 3517 | trans->next = t->trans; 3518 | t->trans = trans; 3519 | icccmwmstate(win, NormalState); 3520 | if (clientisvisible(t->c)) { 3521 | clientdecorate(t->c, 1, 0, FrameNone); 3522 | clientmoveresize(t->c); 3523 | } 3524 | XMapRaised(dpy, trans->frame); 3525 | XMapRaised(dpy, trans->win); 3526 | tabfocus(t); 3527 | } 3528 | 3529 | /* map desktop window (windows displaying icons, for example) */ 3530 | static void 3531 | managedesktop(Window win) 3532 | { 3533 | Window wins[2] = {win, layerwin[LayerDesktop]}; 3534 | 3535 | XRestackWindows(dpy, wins, sizeof wins); 3536 | XMapWindow(dpy, win); 3537 | } 3538 | 3539 | /* map dock window (docks, panels, bars, etc) */ 3540 | static void 3541 | managedock(Window win) 3542 | { 3543 | Window wins[2] = {win, layerwin[LayerBars]}; 3544 | 3545 | XRestackWindows(dpy, wins, sizeof wins); 3546 | XMapWindow(dpy, win); 3547 | } 3548 | 3549 | /* delete tab (and its client if it is the only tab) */ 3550 | static void 3551 | unmanage(struct Tab *t) 3552 | { 3553 | struct Client *c, *f; 3554 | 3555 | c = t->c; 3556 | tabdel(t); 3557 | calctabs(c); 3558 | if (c->ntabs == 0) { 3559 | f = getnextfocused(c); 3560 | c->isfullscreen = 0; /* clientfocus would refuse to focus f if c is fullscreen */ 3561 | if (clientisvisible(c)) 3562 | clientstate(f, FOCUS, ADD); 3563 | clientdel(c); 3564 | } else { 3565 | clientdecorate(c, 1, 0, FrameNone); 3566 | clientmoveresize(c); 3567 | if (c == focused) { 3568 | tabfocus(c->seltab); 3569 | } 3570 | } 3571 | } 3572 | 3573 | /* call one of the manage- functions */ 3574 | static void 3575 | manage(Window win, XWindowAttributes *wa, int ignoreunmap) 3576 | { 3577 | struct Winres res; 3578 | struct Client *c; 3579 | struct Tab *t; 3580 | struct Tab *transfor; 3581 | struct Rules rules; 3582 | Atom prop; 3583 | int placed; 3584 | char *name, *class; 3585 | 3586 | res = getwin(win); 3587 | if (res.c != NULL) 3588 | return; 3589 | prop = getatomprop(win, atoms[NetWMWindowType]); 3590 | transfor = gettransfor(win); 3591 | if (prop == atoms[NetWMWindowTypeDesktop]) { 3592 | managedesktop(win); 3593 | } else if (prop == atoms[NetWMWindowTypeDock]) { 3594 | managedock(win); 3595 | } else if (prop == atoms[NetWMWindowTypeNotification]) { 3596 | preparewin(win); 3597 | managenotif(win, wa->width, wa->height); 3598 | } else if (prop == atoms[NetWMWindowTypePrompt] && !ignoreunmap) { 3599 | preparewin(win); 3600 | manageprompt(win, wa->width, wa->height); 3601 | } else if (transfor != NULL) { 3602 | preparewin(win); 3603 | managetrans(transfor, win, wa->width, wa->height, ignoreunmap); 3604 | } else { 3605 | preparewin(win); 3606 | placed = isuserplaced(win); 3607 | rules = getrules(win, &name, &class); 3608 | t = tabadd(win, name, class, ignoreunmap); 3609 | if (!placed && tabwindow(class, rules.autotab)) { 3610 | clienttab(focused, t, -1); 3611 | clientdecorate(focused, 1, 0, FrameNone); 3612 | clientmoveresize(focused); 3613 | if (focused->state == Tiled) 3614 | desktile(focused->desk); 3615 | ewmhsetwmdesktop(focused); 3616 | } else { 3617 | c = clientadd(wa->x, wa->y, wa->width, wa->height, placed); 3618 | manageclient(c, t, &rules, (rules.desk >= 0 ? &selmon->desks[rules.desk] : selmon->seldesk)); 3619 | } 3620 | } 3621 | } 3622 | 3623 | /* scan for already existing windows and adopt them */ 3624 | static void 3625 | scan(void) 3626 | { 3627 | unsigned int i, num; 3628 | Window d1, d2, transwin, *wins = NULL; 3629 | XWindowAttributes wa; 3630 | 3631 | if (XQueryTree(dpy, root, &d1, &d2, &wins, &num)) { 3632 | for (i = 0; i < num; i++) { 3633 | if (!XGetWindowAttributes(dpy, wins[i], &wa) 3634 | || wa.override_redirect || XGetTransientForHint(dpy, wins[i], &d1)) 3635 | continue; 3636 | if (wa.map_state == IsViewable || getstate(wins[i]) == IconicState) { 3637 | manage(wins[i], &wa, IGNOREUNMAP); 3638 | } 3639 | } 3640 | for (i = 0; i < num; i++) { /* now the transients */ 3641 | if (!XGetWindowAttributes(dpy, wins[i], &wa)) 3642 | continue; 3643 | if (XGetTransientForHint(dpy, wins[i], &transwin) && 3644 | (wa.map_state == IsViewable || getstate(wins[i]) == IconicState)) { 3645 | manage(wins[i], &wa, IGNOREUNMAP); 3646 | } 3647 | } 3648 | if (wins) { 3649 | XFree(wins); 3650 | } 3651 | } 3652 | } 3653 | 3654 | /* map and hide focus window */ 3655 | static void 3656 | mapfocuswin(void) 3657 | { 3658 | XMoveWindow(dpy, focuswin, -1, 0); 3659 | XMapWindow(dpy, focuswin); 3660 | } 3661 | 3662 | /* draw outline while resizing */ 3663 | static void 3664 | outlinedraw(struct Outline *outline) 3665 | { 3666 | static struct Outline oldoutline = {0, 0, 0, 0, 0, 0}; 3667 | XGCValues val; 3668 | XRectangle rects[4]; 3669 | 3670 | val.function = GXinvert; 3671 | val.subwindow_mode = IncludeInferiors; 3672 | val.foreground = 1; 3673 | val.fill_style = FillSolid; 3674 | XChangeGC(dpy, gc, GCFunction | GCSubwindowMode | GCForeground | GCFillStyle, &val); 3675 | if (oldoutline.w != 0 && oldoutline.h != 0) { 3676 | rects[0].x = oldoutline.x + 1; 3677 | rects[0].y = oldoutline.y; 3678 | rects[0].width = oldoutline.w - 2; 3679 | rects[0].height = 1; 3680 | rects[1].x = oldoutline.x; 3681 | rects[1].y = oldoutline.y; 3682 | rects[1].width = 1; 3683 | rects[1].height = oldoutline.h; 3684 | rects[2].x = oldoutline.x + 1; 3685 | rects[2].y = oldoutline.y + oldoutline.h - 1; 3686 | rects[2].width = oldoutline.w - 2; 3687 | rects[2].height = 1; 3688 | rects[3].x = oldoutline.x + oldoutline.w - 1; 3689 | rects[3].y = oldoutline.y; 3690 | rects[3].width = 1; 3691 | rects[3].height = oldoutline.h; 3692 | XFillRectangles(dpy, root, gc, rects, 4); 3693 | } 3694 | if (outline->w != 0 && outline->h != 0) { 3695 | rects[0].x = outline->x + 1; 3696 | rects[0].y = outline->y; 3697 | rects[0].width = outline->w - 2; 3698 | rects[0].height = 1; 3699 | rects[1].x = outline->x; 3700 | rects[1].y = outline->y; 3701 | rects[1].width = 1; 3702 | rects[1].height = outline->h; 3703 | rects[2].x = outline->x + 1; 3704 | rects[2].y = outline->y + outline->h - 1; 3705 | rects[2].width = outline->w - 2; 3706 | rects[2].height = 1; 3707 | rects[3].x = outline->x + outline->w - 1; 3708 | rects[3].y = outline->y; 3709 | rects[3].width = 1; 3710 | rects[3].height = outline->h; 3711 | XFillRectangles(dpy, root, gc, rects, 4); 3712 | } 3713 | oldoutline = *outline; 3714 | val.function = GXcopy; 3715 | val.subwindow_mode = ClipByChildren; 3716 | XChangeGC(dpy, gc, GCFunction | GCSubwindowMode, &val); 3717 | } 3718 | 3719 | /* check if monitor geometry is unique */ 3720 | static int 3721 | monisuniquegeom(XineramaScreenInfo *unique, size_t n, XineramaScreenInfo *info) 3722 | { 3723 | while (n--) 3724 | if (unique[n].x_org == info->x_org && unique[n].y_org == info->y_org 3725 | && unique[n].width == info->width && unique[n].height == info->height) 3726 | return 0; 3727 | return 1; 3728 | } 3729 | 3730 | /* add monitor */ 3731 | static void 3732 | monadd(XineramaScreenInfo *info) 3733 | { 3734 | struct Monitor *mon; 3735 | 3736 | mon = emalloc(sizeof *mon); 3737 | mon->prev = NULL; 3738 | mon->next = NULL; 3739 | mon->mx = mon->wx = info->x_org; 3740 | mon->my = mon->wy = info->y_org; 3741 | mon->mw = mon->ww = info->width; 3742 | mon->mh = mon->wh = info->height; 3743 | mon->gx = mon->wx + config.gapouter; 3744 | mon->gy = mon->wy + config.gapouter; 3745 | mon->gw = mon->ww - config.gapouter * 2; 3746 | mon->gh = mon->wh - config.gapouter * 2; 3747 | mon->desks = desksadd(mon); 3748 | mon->seldesk = &mon->desks[0]; 3749 | if (lastmon) { 3750 | lastmon->next = mon; 3751 | mon->prev = lastmon; 3752 | } else { 3753 | mons = mon; 3754 | } 3755 | lastmon = mon; 3756 | } 3757 | 3758 | /* delete monitor and set monitor of clients on it to NULL */ 3759 | static void 3760 | mondel(struct Monitor *mon) 3761 | { 3762 | struct Client *c; 3763 | 3764 | if (mon->next) 3765 | mon->next->prev = mon->prev; 3766 | else 3767 | lastmon = mon->prev; 3768 | if (mon->prev) 3769 | mon->prev->next = mon->next; 3770 | else 3771 | mons = mon->next; 3772 | for (c = clients; c; c = c->next) { 3773 | if (c->mon == mon) { 3774 | c->mon = NULL; 3775 | c->desk = NULL; 3776 | } 3777 | } 3778 | free(mon->desks); 3779 | free(mon); 3780 | } 3781 | 3782 | /* update the list of monitors */ 3783 | static void 3784 | monupdate(void) 3785 | { 3786 | XineramaScreenInfo *info = NULL; 3787 | XineramaScreenInfo *unique = NULL; 3788 | struct Monitor *mon; 3789 | struct Monitor *tmp; 3790 | struct Client *c, *focus; 3791 | int delselmon = 0; 3792 | int del, add; 3793 | int i, j, n; 3794 | int moncount; 3795 | 3796 | info = XineramaQueryScreens(dpy, &n); 3797 | unique = ecalloc(n, sizeof *unique); 3798 | 3799 | /* only consider unique geometries as separate screens */ 3800 | for (i = 0, j = 0; i < n; i++) 3801 | if (monisuniquegeom(unique, j, &info[i])) 3802 | memcpy(&unique[j++], &info[i], sizeof *unique); 3803 | XFree(info); 3804 | moncount = j; 3805 | 3806 | /* look for monitors that do not exist anymore and delete them */ 3807 | mon = mons; 3808 | while (mon) { 3809 | del = 1; 3810 | for (i = 0; i < moncount; i++) { 3811 | if (unique[i].x_org == mon->mx && unique[i].y_org == mon->my && 3812 | unique[i].width == mon->mw && unique[i].height == mon->mh) { 3813 | del = 0; 3814 | break; 3815 | } 3816 | } 3817 | tmp = mon; 3818 | mon = mon->next; 3819 | if (del) { 3820 | if (tmp == selmon) 3821 | delselmon = 1; 3822 | mondel(tmp); 3823 | } 3824 | } 3825 | 3826 | /* look for new monitors and add them */ 3827 | for (i = 0; i < moncount; i++) { 3828 | add = 1; 3829 | for (mon = mons; mon; mon = mon->next) { 3830 | if (unique[i].x_org == mon->mx && unique[i].y_org == mon->my && 3831 | unique[i].width == mon->mw && unique[i].height == mon->mh) { 3832 | add = 0; 3833 | break; 3834 | } 3835 | } 3836 | if (add) { 3837 | monadd(&unique[i]); 3838 | } 3839 | } 3840 | if (delselmon) 3841 | selmon = mons; 3842 | 3843 | /* update monitor number */ 3844 | for (i = 0, mon = mons; mon; mon = mon->next, i++) 3845 | mon->n = i; 3846 | 3847 | /* send clients with do not belong to a window to selected desktop */ 3848 | focus = NULL; 3849 | for (c = clients; c; c = c->next) { 3850 | if (c->state != Minimized && c->mon == NULL) { 3851 | c->state = Normal; 3852 | c->layer = 0; 3853 | focus = c; 3854 | clientsendtodesk(c, selmon->seldesk, 1); 3855 | } 3856 | } 3857 | if (focus != NULL) /* if a client changed desktop, focus it */ 3858 | clientstate(focus, FOCUS, ADD); 3859 | 3860 | free(unique); 3861 | } 3862 | 3863 | /* press button with mouse */ 3864 | static void 3865 | mousebutton(struct Client *c, int region) 3866 | { 3867 | struct Winres res; 3868 | XEvent ev; 3869 | int released = region; 3870 | 3871 | XGrabPointer(dpy, c->frame, False, ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, 3872 | (region == FrameButtonRight) ? cursor[CURSOR_PIRATE] : cursor[CURSOR_NORMAL], CurrentTime); 3873 | clientdecorate(c, 0, 0, region); /* draw pressed button */ 3874 | while (!XMaskEvent(dpy, ButtonReleaseMask | ExposureMask, &ev)) { 3875 | switch(ev.type) { 3876 | case Expose: 3877 | if (ev.xexpose.count == 0) { 3878 | res = getwin(ev.xexpose.window); 3879 | decorate(&res); 3880 | } 3881 | break; 3882 | case ButtonRelease: 3883 | released = frameregion(c, ev.xbutton.window, ev.xbutton.x, ev.xbutton.y); 3884 | goto done; 3885 | } 3886 | } 3887 | done: 3888 | clientdecorate(c, 0, 0, FrameNone); /* draw pressed button */ 3889 | if (released == region) { 3890 | switch (released) { 3891 | case FrameButtonLeft: 3892 | clientstate(c, HIDE, ADD); 3893 | break; 3894 | case FrameButtonRight: 3895 | if (c->seltab != NULL) 3896 | windowclose(c->seltab->trans != NULL ? c->seltab->trans->win : c->seltab->win); 3897 | break; 3898 | } 3899 | } 3900 | XUngrabPointer(dpy, CurrentTime); 3901 | } 3902 | 3903 | /* detach tab from window with mouse */ 3904 | static void 3905 | mouseretab(struct Tab *t, int xroot, int yroot, int x, int y) 3906 | { 3907 | struct Monitor *mon; 3908 | struct Client *c; 3909 | struct Winres res; 3910 | XEvent ev; 3911 | int pos; 3912 | 3913 | tabdetach(t, xroot - x, yroot - y); 3914 | tabfocus(t->c->seltab); 3915 | clientretab(t->c); 3916 | XGrabPointer(dpy, t->title, False, ButtonReleaseMask | Button3MotionMask, GrabModeAsync, GrabModeAsync, None, cursor[CURSOR_NORMAL], CurrentTime); 3917 | while (!XMaskEvent(dpy, ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ExposureMask, &ev)) { 3918 | switch(ev.type) { 3919 | case Expose: 3920 | if (ev.xexpose.count == 0) { 3921 | if (ev.xexpose.window == t->title || (t->trans && ev.xexpose.window == t->trans->frame)) { 3922 | tabdecorate(t, FrameNone); 3923 | } else { 3924 | res = getwin(ev.xexpose.window); 3925 | decorate(&res); 3926 | } 3927 | } 3928 | break; 3929 | case ButtonRelease: 3930 | xroot = ev.xbutton.x_root; 3931 | yroot = ev.xbutton.y_root; 3932 | XUnmapWindow(dpy, t->title); 3933 | goto done; 3934 | case MotionNotify: 3935 | tabmove(t, ev.xmotion.x_root - x, ev.xmotion.y_root - y); 3936 | break; 3937 | } 3938 | } 3939 | done: 3940 | if ((c = getclientbytitle(xroot, yroot, &pos)) != NULL) { 3941 | clienttab(c, t, pos); 3942 | } else { 3943 | mon = getmon(xroot, yroot); 3944 | c = clientadd(xroot, yroot, t->winw, t->winh, 0); 3945 | manageclient(c, t, NULL, mon->seldesk); 3946 | } 3947 | clientretab(c); 3948 | clientdecorate(c, 1, 0, FrameNone); 3949 | XUngrabPointer(dpy, CurrentTime); 3950 | ewmhsetwmdesktop(c); 3951 | } 3952 | 3953 | /* move frame with mouse */ 3954 | static void 3955 | mousemove(struct Client *c, struct Tab *t, int xroot, int yroot, enum Octant octant, int region) 3956 | { 3957 | struct Winres res; 3958 | XEvent ev; 3959 | int x = 0, y = 0; 3960 | 3961 | XGrabPointer(dpy, c->frame, False, 3962 | ButtonReleaseMask | Button1MotionMask | Button3MotionMask, 3963 | GrabModeAsync, GrabModeAsync, None, cursor[CURSOR_MOVE], CurrentTime); 3964 | if (t != NULL && region == FrameTitle) 3965 | tabdecorate(t, region); 3966 | else 3967 | clientdecorate(c, 0, octant, region); 3968 | while (!XMaskEvent(dpy, ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ExposureMask, &ev)) { 3969 | switch(ev.type) { 3970 | case Expose: 3971 | if (ev.xexpose.count == 0) { 3972 | res = getwin(ev.xexpose.window); 3973 | decorate(&res); 3974 | } 3975 | break; 3976 | case ButtonRelease: 3977 | goto done; 3978 | case MotionNotify: 3979 | if (c->state == Tiled) { 3980 | if (ev.xmotion.x_root > c->x + c->w + (c->row->col->next ? c->b + config.gapinner : 0)) 3981 | x = +1; 3982 | else if (ev.xmotion.x_root < c->x - (c->row->col->prev ? c->b + config.gapinner : 0)) 3983 | x = -1; 3984 | else 3985 | x = 0; 3986 | if (c->row->next && ev.xmotion.y_root > c->y + c->h + HEIGHT(c->row->next->c) / 2 + config.gapinner) 3987 | y = +1; 3988 | else if (c->row->prev && ev.xmotion.y_root < c->y - HEIGHT(c->row->prev->c) / 2) 3989 | y = -1; 3990 | else 3991 | y = 0; 3992 | } else { 3993 | x = ev.xmotion.x_root - xroot; 3994 | y = ev.xmotion.y_root - yroot; 3995 | } 3996 | clientincrmove(c, x, y); 3997 | xroot = ev.xmotion.x_root; 3998 | yroot = ev.xmotion.y_root; 3999 | break; 4000 | } 4001 | } 4002 | done: 4003 | clientdecorate(c, 1, 0, FrameNone); /* draw pressed region */ 4004 | XUngrabPointer(dpy, CurrentTime); 4005 | } 4006 | 4007 | /* resize frame with mouse */ 4008 | static void 4009 | mouseresize(struct Client *c, int xroot, int yroot, enum Octant octant) 4010 | { 4011 | struct Outline outline; 4012 | struct Winres res; 4013 | XEvent ev; 4014 | Cursor curs = None; 4015 | int x, y, dx, dy; 4016 | 4017 | if (c->isfullscreen || (c->state == Tiled && 4018 | ((c->row->col->next == NULL && octant & E) || (c->row->col->prev == NULL && octant & W) || 4019 | (c->row->prev == NULL && octant & N) || (c->row->next == NULL && octant & S)))) { 4020 | return; 4021 | } 4022 | outline.x = c->x - c->b; 4023 | outline.y = c->y - c->b - c->t; 4024 | outline.w = WIDTH(c); 4025 | outline.h = HEIGHT(c); 4026 | outline.diffx = 0; 4027 | outline.diffy = 0; 4028 | switch (octant) { 4029 | case NW: 4030 | curs = c->isshaded ? cursor[CURSOR_W] : cursor[CURSOR_NW]; 4031 | break; 4032 | case NE: 4033 | curs = c->isshaded ? cursor[CURSOR_E] : cursor[CURSOR_NE]; 4034 | break; 4035 | case SW: 4036 | curs = c->isshaded ? cursor[CURSOR_W] : cursor[CURSOR_SW]; 4037 | break; 4038 | case SE: 4039 | curs = c->isshaded ? cursor[CURSOR_E] : cursor[CURSOR_SE]; 4040 | break; 4041 | case N: 4042 | curs = cursor[CURSOR_N]; 4043 | break; 4044 | case S: 4045 | curs = cursor[CURSOR_S]; 4046 | break; 4047 | case W: 4048 | curs = cursor[CURSOR_W]; 4049 | break; 4050 | case E: 4051 | curs = cursor[CURSOR_E]; 4052 | break; 4053 | case C: 4054 | curs = cursor[CURSOR_NORMAL]; 4055 | } 4056 | if (octant & W) 4057 | x = xroot - c->x + c->b; 4058 | else if (octant & E) 4059 | x = c->x + c->w + c->b - xroot; 4060 | else 4061 | x = 0; 4062 | if (octant & N) 4063 | y = yroot - c->y + c->b + c->t; 4064 | else if (octant & S) 4065 | y = c->y + c->h + c->b - yroot; 4066 | else 4067 | y = 0; 4068 | XGrabPointer(dpy, c->frame, False, 4069 | ButtonReleaseMask | PointerMotionMask, 4070 | GrabModeAsync, GrabModeAsync, None, curs, CurrentTime); 4071 | clientdecorate(c, 0, octant, FrameNone); /* draw pressed region */ 4072 | while (!XMaskEvent(dpy, ButtonReleaseMask | PointerMotionMask | ExposureMask, &ev)) { 4073 | switch(ev.type) { 4074 | case Expose: 4075 | if (ev.xexpose.count == 0) { 4076 | res = getwin(ev.xexpose.window); 4077 | decorate(&res); 4078 | } 4079 | break; 4080 | case ButtonRelease: 4081 | goto done; 4082 | case MotionNotify: 4083 | if (x > outline.w) 4084 | x = 0; 4085 | if (y > outline.h) 4086 | y = 0; 4087 | if (octant & W && 4088 | ((ev.xmotion.x_root < xroot && x > ev.xmotion.x_root - outline.x) || 4089 | (ev.xmotion.x_root > xroot && x < ev.xmotion.x_root - outline.x))) { 4090 | dx = xroot - ev.xmotion.x_root; 4091 | if (clientvalidsize(c, octant, outline.diffx + dx, 0)) { 4092 | outline.x -= dx; 4093 | outline.w += dx; 4094 | outline.diffx += dx; 4095 | } 4096 | } else if (octant & E && 4097 | ((ev.xmotion.x_root > xroot && x > outline.x + outline.w - ev.xmotion.x_root) || 4098 | (ev.xmotion.x_root < xroot && x < outline.x + outline.w - ev.xmotion.x_root))) { 4099 | dx = ev.xmotion.x_root - xroot; 4100 | if (clientvalidsize(c, octant, outline.diffx + dx, 0)) { 4101 | outline.w += dx; 4102 | outline.diffx += dx; 4103 | } 4104 | } 4105 | if (octant & N && 4106 | ((ev.xmotion.y_root < yroot && y > ev.xmotion.y_root - outline.y) || 4107 | (ev.xmotion.y_root > yroot && y < ev.xmotion.y_root - outline.y))) { 4108 | dy = yroot - ev.xmotion.y_root; 4109 | if (clientvalidsize(c, octant, 0, outline.diffy + dy)) { 4110 | outline.y -= dy; 4111 | outline.h += dy; 4112 | outline.diffy += dy; 4113 | } 4114 | } else if (octant & S && 4115 | ((ev.xmotion.y_root > yroot && outline.y + outline.h - ev.xmotion.y_root < y) || 4116 | (ev.xmotion.y_root < yroot && outline.y + outline.h - ev.xmotion.y_root > y))) { 4117 | dy = ev.xmotion.y_root - yroot; 4118 | if (clientvalidsize(c, octant, 0, outline.diffy + dy)) { 4119 | outline.h += dy; 4120 | outline.diffy += dy; 4121 | } 4122 | } 4123 | outlinedraw(&outline); 4124 | xroot = ev.xmotion.x_root; 4125 | yroot = ev.xmotion.y_root; 4126 | break; 4127 | } 4128 | } 4129 | done: 4130 | clientincrresize(c, octant, outline.diffx, outline.diffy); 4131 | outlinedraw(&(struct Outline){0, 0, 0, 0, 0, 0}); 4132 | clientdecorate(c, 1, 0, FrameNone); /* draw pressed region */ 4133 | XUngrabPointer(dpy, CurrentTime); 4134 | } 4135 | 4136 | /* handle mouse operation, focusing and raising */ 4137 | static void 4138 | xeventbuttonpress(XEvent *e) 4139 | { 4140 | struct Winres res; 4141 | static Time lasttime = 0; 4142 | static struct Client *lastc = NULL; 4143 | XButtonPressedEvent *ev = &e->xbutton; 4144 | struct Monitor *mon; 4145 | struct Client *c; 4146 | struct Tab *t; 4147 | enum Octant octant; 4148 | int region; 4149 | 4150 | res = getwin(ev->window); 4151 | c = res.c; 4152 | t = res.t; 4153 | 4154 | /* if user clicked in no window, focus the monitor below cursor */ 4155 | if (c == NULL) { 4156 | mon = getmon(ev->x_root, ev->y_root); 4157 | if (mon) 4158 | deskchange(mon->seldesk); 4159 | goto done; 4160 | } 4161 | 4162 | region = frameregion(c, ev->window, ev->x, ev->y); 4163 | octant = frameoctant(c, ev->window, ev->x, ev->y); 4164 | 4165 | /* focus client */ 4166 | if (region == FrameTitle && ev->button == Button1) 4167 | tabfocus(t); 4168 | if (c != focused && 4169 | ((ev->button == Button1 && config.focusbuttons & 1 << 0) || 4170 | (ev->button == Button2 && config.focusbuttons & 1 << 1) || 4171 | (ev->button == Button3 && config.focusbuttons & 1 << 2) || 4172 | (ev->button == Button4 && config.focusbuttons & 1 << 3) || 4173 | (ev->button == Button5 && config.focusbuttons & 1 << 4))) 4174 | clientstate(c, FOCUS, ADD); 4175 | 4176 | /* raise client */ 4177 | if (c != raised && 4178 | ((ev->button == Button1 && config.raisebuttons & 1 << 0) || 4179 | (ev->button == Button2 && config.raisebuttons & 1 << 1) || 4180 | (ev->button == Button3 && config.raisebuttons & 1 << 2) || 4181 | (ev->button == Button4 && config.raisebuttons & 1 << 3) || 4182 | (ev->button == Button5 && config.raisebuttons & 1 << 4))) 4183 | clientraise(c); 4184 | 4185 | /* get action performed by mouse */ 4186 | if (ev->state == config.modifier && ev->button == Button1) { 4187 | mousemove(c, NULL, ev->x_root, ev->y_root, 0, FrameNone); 4188 | } else if ((ev->state == config.modifier && ev->button == Button3) || 4189 | (region == FrameBorder && ev->button == Button1)) { 4190 | mouseresize(c, ev->x_root, ev->y_root, octant); 4191 | } else if (region == FrameBorder && ev->button == Button3) { 4192 | mousemove(c, NULL, ev->x_root, ev->y_root, octant, region); 4193 | } else if (ev->button == Button1 && (region == FrameButtonLeft || region == FrameButtonRight)) { 4194 | mousebutton(c, region); 4195 | } else if (region == FrameTitle && ev->button == Button3 && t != NULL && t->c != NULL && t->title == ev->window) { 4196 | mouseretab(t, ev->x_root, ev->y_root, ev->x, ev->y); 4197 | } else if (region == FrameTitle && ev->button == Button1) { 4198 | if (lastc == c && ev->time - lasttime < DOUBLECLICK) { 4199 | clientstate(c, SHADE, TOGGLE); 4200 | lasttime = 0; 4201 | lastc = NULL; 4202 | return; 4203 | } 4204 | lastc = c; 4205 | lasttime = ev->time; 4206 | mousemove(c, t, ev->x_root, ev->y_root, 0, region); 4207 | } 4208 | 4209 | done: 4210 | XAllowEvents(dpy, ReplayPointer, CurrentTime); 4211 | } 4212 | 4213 | /* handle client message event */ 4214 | static void 4215 | xeventclientmessage(XEvent *e) 4216 | { 4217 | struct Desktop *desk; 4218 | XClientMessageEvent *ev = &e->xclient; 4219 | XWindowChanges wc; 4220 | unsigned value_mask = 0; 4221 | struct Winres res; 4222 | struct Client *c, *f; 4223 | int i; 4224 | 4225 | res = getwin(ev->window); 4226 | c = res.c; 4227 | if (ev->message_type == atoms[NetCurrentDesktop]) { 4228 | deskchange(getdesk(ev->data.l[0])); 4229 | } else if (ev->message_type == atoms[NetShowingDesktop]) { 4230 | clientshowdesk(ev->data.l[0]); 4231 | if (showingdesk) { 4232 | clientstate(focused, FOCUS, ADD); 4233 | } 4234 | } else if (ev->message_type == atoms[NetWMState]) { 4235 | if (c == NULL) 4236 | return; 4237 | if (config.ignoreindirect && ev->data.l[3] == INDIRECT_SOURCE) 4238 | return; 4239 | if (((Atom)ev->data.l[1] == atoms[NetWMStateMaximizedVert] || 4240 | (Atom)ev->data.l[1] == atoms[NetWMStateMaximizedHorz]) && 4241 | ((Atom)ev->data.l[2] == atoms[NetWMStateMaximizedVert] || 4242 | (Atom)ev->data.l[2] == atoms[NetWMStateMaximizedHorz])) { 4243 | clientstate(c, MAXIMIZE, ev->data.l[0]); 4244 | } 4245 | for (i = 1; i < 3; i++) { 4246 | if ((Atom)ev->data.l[i] == atoms[NetWMStateFullscreen]) { 4247 | clientstate(c, FULLSCREEN, ev->data.l[0]); 4248 | } else if ((Atom)ev->data.l[i] == atoms[NetWMStateShaded]) { 4249 | clientstate(c, SHADE, ev->data.l[0]); 4250 | } else if ((Atom)ev->data.l[i] == atoms[NetWMStateSticky]) { 4251 | clientstate(c, STICK, ev->data.l[0]); 4252 | } else if ((Atom)ev->data.l[i] == atoms[NetWMStateHidden]) { 4253 | clientstate(c, HIDE, ev->data.l[0]); 4254 | } else if ((Atom)ev->data.l[i] == atoms[NetWMStateAbove]) { 4255 | clientstate(c, ABOVE, ev->data.l[0]); 4256 | } else if ((Atom)ev->data.l[i] == atoms[NetWMStateBelow]) { 4257 | clientstate(c, BELOW, ev->data.l[0]); 4258 | } else if ((Atom)ev->data.l[i] == atoms[NetWMStateDemandsAttention]) { 4259 | tabupdateurgency(res.t, ev->data.l[0] == ADD || (ev->data.l[0] == TOGGLE && !res.t->isurgent)); 4260 | } 4261 | } 4262 | } else if (ev->message_type == atoms[NetActiveWindow]) { 4263 | if (c == NULL) 4264 | return; 4265 | if (config.ignoreindirect && ev->data.l[0] == INDIRECT_SOURCE) 4266 | return; 4267 | if (c->state == Minimized) 4268 | clientminimize(c, 0); 4269 | if (res.t != NULL) 4270 | c->seltab = res.t; 4271 | deskchange(c->desk); 4272 | clientraise(c); 4273 | clientstate(c, FOCUS, ADD); 4274 | } else if (ev->message_type == atoms[NetCloseWindow]) { 4275 | if (c == NULL) 4276 | return; 4277 | if (config.ignoreindirect && ev->data.l[1] == INDIRECT_SOURCE) 4278 | return; 4279 | windowclose(ev->window); 4280 | } else if (ev->message_type == atoms[NetMoveresizeWindow]) { 4281 | if (c == NULL) 4282 | return; 4283 | value_mask = CWX | CWY | CWWidth | CWHeight; 4284 | wc.x = ev->data.l[1]; 4285 | wc.y = ev->data.l[2]; 4286 | wc.width = ev->data.l[3]; 4287 | wc.height = ev->data.l[4]; 4288 | if (res.trans != NULL) { 4289 | transconfigure(res.trans, value_mask, &wc); 4290 | } else { 4291 | clientconfigure(c, value_mask, &wc); 4292 | } 4293 | } else if (ev->message_type == atoms[NetWMDesktop]) { 4294 | if (c == NULL) 4295 | return; 4296 | if (config.ignoreindirect && ev->data.l[1] == INDIRECT_SOURCE) 4297 | return; 4298 | if (ev->data.l[0] == 0xFFFFFFFF) { 4299 | clientstate(c, STICK, ADD); 4300 | } else if (c->state != Sticky && c->state != Minimized) { 4301 | if ((desk = getdesk(ev->data.l[0])) == NULL || desk == c->desk) 4302 | return; 4303 | if (c->state == Tiled) 4304 | clienttile(c, 0); 4305 | f = getnextfocused(c); 4306 | clientsendtodesk(c, getdesk(ev->data.l[0]), 1); 4307 | clientstate(f, FOCUS, ADD); 4308 | } 4309 | } else if (ev->message_type == atoms[NetRequestFrameExtents]) { 4310 | /* 4311 | * A client can request an estimate for the frame size 4312 | * which the window manager will put around it before 4313 | * actually mapping its window. Java does this (as of 4314 | * openjdk-7). 4315 | */ 4316 | if (c == NULL) 4317 | return; 4318 | ewmhsetframeextents(ev->window, c->b, c->t); 4319 | } else if (ev->message_type == atoms[NetWMMoveresize]) { 4320 | /* 4321 | * Client-side decorated Gtk3 windows emit this signal when being 4322 | * dragged by their GtkHeaderBar 4323 | */ 4324 | if (c == NULL) 4325 | return; 4326 | switch (ev->data.l[2]) { 4327 | case MOVERESIZE_CANCEL: 4328 | XUngrabPointer(dpy, CurrentTime); 4329 | break; 4330 | case MOVERESIZE_SIZE_TOPLEFT: 4331 | mouseresize(c, ev->data.l[0], ev->data.l[1], NW); 4332 | break; 4333 | case MOVERESIZE_SIZE_TOP: 4334 | mouseresize(c, ev->data.l[0], ev->data.l[1], N); 4335 | break; 4336 | case MOVERESIZE_SIZE_TOPRIGHT: 4337 | mouseresize(c, ev->data.l[0], ev->data.l[1], NE); 4338 | break; 4339 | case MOVERESIZE_SIZE_RIGHT: 4340 | mouseresize(c, ev->data.l[0], ev->data.l[1], E); 4341 | break; 4342 | case MOVERESIZE_SIZE_BOTTOMRIGHT: 4343 | mouseresize(c, ev->data.l[0], ev->data.l[1], SE); 4344 | break; 4345 | case MOVERESIZE_SIZE_BOTTOM: 4346 | mouseresize(c, ev->data.l[0], ev->data.l[1], S); 4347 | break; 4348 | case MOVERESIZE_SIZE_BOTTOMLEFT: 4349 | mouseresize(c, ev->data.l[0], ev->data.l[1], SW); 4350 | break; 4351 | case MOVERESIZE_SIZE_LEFT: 4352 | mouseresize(c, ev->data.l[0], ev->data.l[1], W); 4353 | break; 4354 | case MOVERESIZE_MOVE: 4355 | mousemove(c, NULL, ev->data.l[0], ev->data.l[1], 0, FrameNone); 4356 | break; 4357 | default: 4358 | return; 4359 | } 4360 | } 4361 | } 4362 | 4363 | /* handle configure notify event */ 4364 | static void 4365 | xeventconfigurenotify(XEvent *e) 4366 | { 4367 | XConfigureEvent *ev = &e->xconfigure; 4368 | 4369 | if (ev->window == root) { 4370 | screenw = ev->width; 4371 | screenh = ev->height; 4372 | monupdate(); 4373 | notifplace(); 4374 | ewmhsetworkarea(screenw, screenh); 4375 | } 4376 | } 4377 | 4378 | /* handle configure request event */ 4379 | static void 4380 | xeventconfigurerequest(XEvent *e) 4381 | { 4382 | XConfigureRequestEvent *ev = &e->xconfigurerequest; 4383 | XWindowChanges wc; 4384 | struct Winres res; 4385 | 4386 | wc.x = ev->x; 4387 | wc.y = ev->y; 4388 | wc.width = ev->width; 4389 | wc.height = ev->height; 4390 | wc.border_width = ev->border_width; 4391 | wc.sibling = ev->above; 4392 | wc.stack_mode = ev->detail; 4393 | res = getwin(ev->window); 4394 | if (res.trans != NULL) { 4395 | transconfigure(res.trans, ev->value_mask, &wc); 4396 | } else if (res.c != NULL) { 4397 | clientconfigure(res.c, ev->value_mask, &wc); 4398 | } else if (res.c == NULL){ 4399 | XConfigureWindow(dpy, ev->window, ev->value_mask, &wc); 4400 | } 4401 | } 4402 | 4403 | /* forget about client */ 4404 | static void 4405 | xeventdestroynotify(XEvent *e) 4406 | { 4407 | XDestroyWindowEvent *ev = &e->xdestroywindow; 4408 | struct Winres res; 4409 | 4410 | res = getwin(ev->window); 4411 | if (res.n && ev->window == res.n->win) { 4412 | notifdel(res.n); 4413 | } else if (res.trans && ev->window == res.trans->win) { 4414 | transdel(res.trans); 4415 | goto update; 4416 | } else if (res.t && ev->window == res.t->win) { 4417 | unmanage(res.t); 4418 | goto update; 4419 | } 4420 | update: 4421 | ewmhsetclients(); 4422 | ewmhsetclientsstacking(); 4423 | } 4424 | 4425 | /* focus window when cursor enter it (only if there is no focus button) */ 4426 | static void 4427 | xevententernotify(XEvent *e) 4428 | { 4429 | struct Winres res; 4430 | 4431 | if (config.focusbuttons) 4432 | return; 4433 | while (XCheckTypedEvent(dpy, EnterNotify, e)) 4434 | ; 4435 | res = getwin(e->xcrossing.window); 4436 | if (res.c != NULL) { 4437 | clientstate(res.c, FOCUS, ADD); 4438 | } 4439 | } 4440 | 4441 | /* redraw window decoration */ 4442 | static void 4443 | xeventexpose(XEvent *e) 4444 | { 4445 | XExposeEvent *ev = &e->xexpose; 4446 | struct Winres res; 4447 | 4448 | if (ev->count == 0) { 4449 | res = getwin(ev->window); 4450 | decorate(&res); 4451 | } 4452 | } 4453 | 4454 | /* handle focusin event */ 4455 | static void 4456 | xeventfocusin(XEvent *e) 4457 | { 4458 | XFocusChangeEvent *ev = &e->xfocus; 4459 | struct Winres res; 4460 | 4461 | res = getwin(ev->window); 4462 | if (focused == NULL || focused != res.c) { 4463 | clientstate(focused, FOCUS, ADD); 4464 | } 4465 | } 4466 | 4467 | /* key press event on focuswin */ 4468 | static void 4469 | xeventkeypress(XEvent *e) 4470 | { 4471 | XKeyPressedEvent *ev = &e->xkey; 4472 | 4473 | if (ev->window == focuswin) { 4474 | XSendEvent(dpy, root, False, KeyPressMask, e); 4475 | } 4476 | } 4477 | 4478 | /* manage window */ 4479 | static void 4480 | xeventmaprequest(XEvent *e) 4481 | { 4482 | XMapRequestEvent *ev = &e->xmaprequest; 4483 | XWindowAttributes wa; 4484 | 4485 | if (!XGetWindowAttributes(dpy, ev->window, &wa)) 4486 | return; 4487 | if (wa.override_redirect) 4488 | return; 4489 | manage(ev->window, &wa, 0); 4490 | } 4491 | 4492 | /* change cursor */ 4493 | static void 4494 | xeventmotionnotify(XEvent *e) 4495 | { 4496 | XMotionEvent *ev = &e->xmotion; 4497 | struct Winres res; 4498 | int region; 4499 | 4500 | res = getwin(ev->window); 4501 | if (res.c == NULL || ev->subwindow != res.c->curswin) 4502 | return; 4503 | region = frameregion(res.c, ev->window, ev->x, ev->y); 4504 | if (region == FrameButtonRight) { 4505 | XDefineCursor(dpy, res.c->curswin, cursor[CURSOR_PIRATE]); 4506 | } else if (region == FrameBorder) { 4507 | switch (frameoctant(res.c, ev->window, ev->x, ev->y)) { 4508 | case NW: 4509 | XDefineCursor(dpy, res.c->curswin, (res.c->isshaded ? cursor[CURSOR_W] : cursor[CURSOR_NW])); 4510 | break; 4511 | case NE: 4512 | XDefineCursor(dpy, res.c->curswin, (res.c->isshaded ? cursor[CURSOR_E] : cursor[CURSOR_NE])); 4513 | break; 4514 | case SW: 4515 | XDefineCursor(dpy, res.c->curswin, (res.c->isshaded ? cursor[CURSOR_W] : cursor[CURSOR_SW])); 4516 | break; 4517 | case SE: 4518 | XDefineCursor(dpy, res.c->curswin, (res.c->isshaded ? cursor[CURSOR_E] : cursor[CURSOR_SE])); 4519 | break; 4520 | case N: 4521 | XDefineCursor(dpy, res.c->curswin, cursor[CURSOR_N]); 4522 | break; 4523 | case S: 4524 | XDefineCursor(dpy, res.c->curswin, cursor[CURSOR_S]); 4525 | break; 4526 | case W: 4527 | XDefineCursor(dpy, res.c->curswin, cursor[CURSOR_W]); 4528 | break; 4529 | case E: 4530 | XDefineCursor(dpy, res.c->curswin, cursor[CURSOR_E]); 4531 | break; 4532 | default: 4533 | XDefineCursor(dpy, res.c->curswin, cursor[CURSOR_NORMAL]); 4534 | break; 4535 | } 4536 | } else { 4537 | XDefineCursor(dpy, res.c->curswin, cursor[CURSOR_NORMAL]); 4538 | } 4539 | } 4540 | 4541 | /* update client properties */ 4542 | static void 4543 | xeventpropertynotify(XEvent *e) 4544 | { 4545 | XPropertyEvent *ev = &e->xproperty; 4546 | struct Winres res; 4547 | 4548 | if (ev->state == PropertyDelete) 4549 | return; 4550 | res = getwin(ev->window); 4551 | if (res.t == NULL || ev->window != res.t->win) 4552 | return; 4553 | if (ev->atom == XA_WM_NAME || ev->atom == atoms[NetWMName]) { 4554 | tabupdatetitle(res.t); 4555 | clientdecorate(res.t->c, 1, 0, FrameNone); 4556 | } else if (ev->atom == XA_WM_CLASS) { 4557 | tabupdateclass(res.t); 4558 | } else if (ev->atom == XA_WM_HINTS) { 4559 | tabupdateurgency(res.t, isurgent(res.t->win)); 4560 | } 4561 | } 4562 | 4563 | /* forget about client */ 4564 | static void 4565 | xeventunmapnotify(XEvent *e) 4566 | { 4567 | XUnmapEvent *ev = &e->xunmap; 4568 | struct Winres res; 4569 | 4570 | res = getwin(ev->window); 4571 | if (res.n && ev->window == res.n->win) { 4572 | notifdel(res.n); 4573 | } else if (res.trans && ev->window == res.trans->win) { 4574 | if (res.trans->ignoreunmap) { 4575 | res.trans->ignoreunmap--; 4576 | return; 4577 | } else { 4578 | transdel(res.trans); 4579 | } 4580 | goto update; 4581 | } else if (res.t && ev->window == res.t->win) { 4582 | if (res.t->ignoreunmap) { 4583 | res.t->ignoreunmap--; 4584 | return; 4585 | } else { 4586 | unmanage(res.t); 4587 | } 4588 | goto update; 4589 | } 4590 | return; 4591 | update: 4592 | ewmhsetclients(); 4593 | ewmhsetclientsstacking(); 4594 | } 4595 | 4596 | /* clean clients and other structures */ 4597 | static void 4598 | cleanclients(void) 4599 | { 4600 | while (clients) { 4601 | if (clients->ishidden) 4602 | clienthide(clients, 0); 4603 | if (clients->isshaded) 4604 | clientshade(clients, 0); 4605 | clientdel(clients); 4606 | } 4607 | while (mons) { 4608 | mondel(mons); 4609 | } 4610 | } 4611 | 4612 | /* destroy dummy windows */ 4613 | static void 4614 | cleandummywindows(void) 4615 | { 4616 | int i; 4617 | 4618 | XDestroyWindow(dpy, wmcheckwin); 4619 | XDestroyWindow(dpy, focuswin); 4620 | for (i = 0; i < LayerLast; i++) { 4621 | XDestroyWindow(dpy, layerwin[i]); 4622 | } 4623 | } 4624 | 4625 | /* free cursors */ 4626 | static void 4627 | cleancursors(void) 4628 | { 4629 | size_t i; 4630 | 4631 | for (i = 0; i < CURSOR_LAST; i++) { 4632 | XFreeCursor(dpy, cursor[i]); 4633 | } 4634 | } 4635 | 4636 | /* free pixmaps */ 4637 | static void 4638 | cleanpixmaps(void) 4639 | { 4640 | int i, j; 4641 | 4642 | for (i = 0; i < STYLE_LAST; i++) { 4643 | for (j = 0; i < DECOR_LAST; i++) { 4644 | XFreePixmap(dpy, decor[i][j].bl); 4645 | XFreePixmap(dpy, decor[i][j].tl); 4646 | XFreePixmap(dpy, decor[i][j].t); 4647 | XFreePixmap(dpy, decor[i][j].tr); 4648 | XFreePixmap(dpy, decor[i][j].br); 4649 | XFreePixmap(dpy, decor[i][j].nw); 4650 | XFreePixmap(dpy, decor[i][j].nf); 4651 | XFreePixmap(dpy, decor[i][j].n); 4652 | XFreePixmap(dpy, decor[i][j].nl); 4653 | XFreePixmap(dpy, decor[i][j].ne); 4654 | XFreePixmap(dpy, decor[i][j].wf); 4655 | XFreePixmap(dpy, decor[i][j].w); 4656 | XFreePixmap(dpy, decor[i][j].wl); 4657 | XFreePixmap(dpy, decor[i][j].ef); 4658 | XFreePixmap(dpy, decor[i][j].e); 4659 | XFreePixmap(dpy, decor[i][j].el); 4660 | XFreePixmap(dpy, decor[i][j].sw); 4661 | XFreePixmap(dpy, decor[i][j].sf); 4662 | XFreePixmap(dpy, decor[i][j].s); 4663 | XFreePixmap(dpy, decor[i][j].sl); 4664 | XFreePixmap(dpy, decor[i][j].se); 4665 | XFreePixmap(dpy, decor[i][j].fg); 4666 | XFreePixmap(dpy, decor[i][j].bg); 4667 | } 4668 | } 4669 | } 4670 | 4671 | /* free fontset */ 4672 | static void 4673 | cleanfontset(void) 4674 | { 4675 | XFreeFontSet(dpy, fontset); 4676 | } 4677 | 4678 | /* shod window manager */ 4679 | int 4680 | main(int argc, char *argv[]) 4681 | { 4682 | XEvent ev; 4683 | void (*xevents[LASTEvent])(XEvent *) = { 4684 | [ButtonPress] = xeventbuttonpress, 4685 | [ClientMessage] = xeventclientmessage, 4686 | [ConfigureNotify] = xeventconfigurenotify, 4687 | [ConfigureRequest] = xeventconfigurerequest, 4688 | [DestroyNotify] = xeventdestroynotify, 4689 | [EnterNotify] = xevententernotify, 4690 | [Expose] = xeventexpose, 4691 | [FocusIn] = xeventfocusin, 4692 | [KeyPress] = xeventkeypress, 4693 | [MapRequest] = xeventmaprequest, 4694 | [MotionNotify] = xeventmotionnotify, 4695 | [PropertyNotify] = xeventpropertynotify, 4696 | [UnmapNotify] = xeventunmapnotify 4697 | }; 4698 | 4699 | /* open connection to server and set X variables */ 4700 | if (!setlocale(LC_ALL, "") || !XSupportsLocale()) 4701 | warnx("warning: no locale support"); 4702 | if ((dpy = XOpenDisplay(NULL)) == NULL) 4703 | errx(1, "could not open display"); 4704 | screen = DefaultScreen(dpy); 4705 | screenw = DisplayWidth(dpy, screen); 4706 | screenh = DisplayHeight(dpy, screen); 4707 | depth = DefaultDepth(dpy, screen); 4708 | root = RootWindow(dpy, screen); 4709 | gc = XCreateGC(dpy, root, 0, NULL); 4710 | xerrorxlib = XSetErrorHandler(xerror); 4711 | 4712 | /* initialize resources database, read options */ 4713 | config.theme_path = NULL; 4714 | XrmInitialize(); 4715 | if ((xrm = XResourceManagerString(dpy)) != NULL && (xdb = XrmGetStringDatabase(xrm)) != NULL) 4716 | getresources(); 4717 | getoptions(argc, argv); 4718 | 4719 | /* initialize */ 4720 | initsignal(); 4721 | initdummywindows(); 4722 | initfontset(); 4723 | initcursors(); 4724 | initatoms(); 4725 | initnotif(); 4726 | initroot(); 4727 | 4728 | /* set up list of monitors */ 4729 | monupdate(); 4730 | selmon = mons; 4731 | 4732 | /* initialize ewmh hints */ 4733 | ewmhinit(); 4734 | ewmhsetcurrentdesktop(0); 4735 | ewmhsetworkarea(screenw, screenh); 4736 | ewmhsetshowingdesktop(0); 4737 | ewmhsetclients(); 4738 | ewmhsetclientsstacking(); 4739 | ewmhsetactivewindow(None); 4740 | 4741 | /* setup theme */ 4742 | settheme(); 4743 | 4744 | /* scan windows */ 4745 | scan(); 4746 | mapfocuswin(); 4747 | 4748 | /* run main event loop */ 4749 | while (running && !XNextEvent(dpy, &ev)) 4750 | if (xevents[ev.type]) 4751 | (*xevents[ev.type])(&ev); 4752 | 4753 | /* clean up */ 4754 | cleandummywindows(); 4755 | cleancursors(); 4756 | cleanclients(); 4757 | cleanpixmaps(); 4758 | cleanfontset(); 4759 | 4760 | /* clear ewmh hints */ 4761 | ewmhsetclients(); 4762 | ewmhsetclientsstacking(); 4763 | ewmhsetactivewindow(None); 4764 | 4765 | /* close connection to server */ 4766 | XUngrabPointer(dpy, CurrentTime); 4767 | XrmDestroyDatabase(xdb); 4768 | XCloseDisplay(dpy); 4769 | 4770 | return 0; 4771 | } 4772 | -------------------------------------------------------------------------------- /shod.h: -------------------------------------------------------------------------------- 1 | #define INDIRECT_SOURCE 1 2 | #define IGNOREUNMAP 6 /* number of unmap notifies to ignore while scanning existing clients */ 3 | #define DIV 15 /* number to divide the screen into grids */ 4 | #define DOUBLECLICK 250 /* time in miliseconds of a double click */ 5 | #define NAMEMAXLEN 1024 /* maximum length of window's name */ 6 | #define RULEMINSIZ 23 /* length of "shod.instance..desktop" + 1 for \0 */ 7 | #define WIDTH(x) ((x)->w + 2 * (x)->b) 8 | #define HEIGHT(x) ((x)->h + 2 * (x)->b + (x)->t) 9 | 10 | /* internal window states */ 11 | enum { 12 | Normal, /* floating non-sticky window */ 13 | Sticky, /* floating sticky window */ 14 | Tiled, /* tiled window */ 15 | Minimized, /* hidden window */ 16 | }; 17 | 18 | /* role prefixes */ 19 | enum { 20 | TITLE = 0, 21 | INSTANCE = 1, 22 | CLASS = 2, 23 | ROLE = 3, 24 | LAST_PREFIX = 4 25 | }; 26 | 27 | /* role sufixes */ 28 | enum { 29 | DESKTOP = 0, 30 | STATE = 1, 31 | AUTOTAB = 2, 32 | POSITION = 3, 33 | LAST_SUFFIX = 4 34 | }; 35 | 36 | /* EWMH window state actions */ 37 | enum { 38 | STICK, 39 | MAXIMIZE, 40 | SHADE, 41 | HIDE, 42 | FULLSCREEN, 43 | ABOVE, 44 | BELOW, 45 | FOCUS 46 | }; 47 | 48 | /* state flag */ 49 | enum { 50 | REMOVE = 0, 51 | ADD = 1, 52 | TOGGLE = 2 53 | }; 54 | 55 | /* motion action */ 56 | enum { 57 | NoAction, 58 | Retabbing, 59 | Button, 60 | Moving, 61 | Resizing 62 | }; 63 | 64 | /* decoration style */ 65 | enum { 66 | FOCUSED, 67 | UNFOCUSED, 68 | URGENT, 69 | STYLE_LAST 70 | }; 71 | 72 | /* decoration state */ 73 | enum { 74 | /* the first decoration state is used both for focused tab and for unpressed borders */ 75 | UNPRESSED = 0, 76 | TAB_FOCUSED = 0, 77 | 78 | PRESSED = 1, 79 | TAB_PRESSED = 1, 80 | 81 | /* the third decoration state is used for unfocused tab, transient borders, and merged borders */ 82 | TAB_UNFOCUSED = 2, 83 | TRANSIENT = 2, 84 | MERGE_BORDERS = 2, 85 | 86 | DECOR_LAST = 3 87 | }; 88 | 89 | /* moveresize action */ 90 | enum { 91 | MOVERESIZE_SIZE_TOPLEFT = 0, 92 | MOVERESIZE_SIZE_TOP = 1, 93 | MOVERESIZE_SIZE_TOPRIGHT = 2, 94 | MOVERESIZE_SIZE_RIGHT = 3, 95 | MOVERESIZE_SIZE_BOTTOMRIGHT = 4, 96 | MOVERESIZE_SIZE_BOTTOM = 5, 97 | MOVERESIZE_SIZE_BOTTOMLEFT = 6, 98 | MOVERESIZE_SIZE_LEFT = 7, 99 | MOVERESIZE_MOVE = 8, /* movement only */ 100 | MOVERESIZE_SIZE_KEYBOARD = 9, /* size via keyboard */ 101 | MOVERESIZE_MOVE_KEYBOARD = 10, /* move via keyboard */ 102 | MOVERESIZE_CANCEL = 11, /* cancel operation */ 103 | }; 104 | 105 | /* atoms */ 106 | enum { 107 | /* utf8 */ 108 | Utf8String, 109 | 110 | /* ICCCM atoms */ 111 | WMDeleteWindow, 112 | WMWindowRole, 113 | WMTakeFocus, 114 | WMProtocols, 115 | WMState, 116 | 117 | /* EWMH atoms */ 118 | NetSupported, 119 | NetClientList, 120 | NetClientListStacking, 121 | NetNumberOfDesktops, 122 | NetCurrentDesktop, 123 | NetActiveWindow, 124 | NetWMDesktop, 125 | NetWorkarea, 126 | NetSupportingWMCheck, 127 | NetShowingDesktop, 128 | NetCloseWindow, 129 | NetMoveresizeWindow, 130 | NetWMMoveresize, 131 | NetRequestFrameExtents, 132 | NetWMName, 133 | NetWMWindowType, 134 | NetWMWindowTypeDesktop, 135 | NetWMWindowTypeMenu, 136 | NetWMWindowTypeToolbar, 137 | NetWMWindowTypeDock, 138 | NetWMWindowTypeDialog, 139 | NetWMWindowTypeUtility, 140 | NetWMWindowTypeSplash, 141 | NetWMWindowTypePrompt, 142 | NetWMWindowTypeNotification, 143 | NetWMState, 144 | NetWMStateSticky, 145 | NetWMStateMaximizedVert, 146 | NetWMStateMaximizedHorz, 147 | NetWMStateShaded, 148 | NetWMStateHidden, 149 | NetWMStateFullscreen, 150 | NetWMStateAbove, 151 | NetWMStateBelow, 152 | NetWMStateFocused, 153 | NetWMStateDemandsAttention, 154 | NetWMAllowedActions, 155 | NetWMActionMove, 156 | NetWMActionResize, 157 | NetWMActionMinimize, 158 | NetWMActionStick, 159 | NetWMActionMaximizeHorz, 160 | NetWMActionMaximizeVert, 161 | NetWMActionFullscreen, 162 | NetWMActionChangeDesktop, 163 | NetWMActionClose, 164 | NetWMActionAbove, 165 | NetWMActionBelow, 166 | NetWMStrut, 167 | NetWMStrutPartial, 168 | NetWMUserTime, 169 | NetWMStateAttention, 170 | NetFrameExtents, 171 | NetDesktopViewport, 172 | 173 | ShodTabGroup, 174 | 175 | AtomLast 176 | }; 177 | 178 | /* window layers */ 179 | enum { 180 | LayerDesktop, 181 | LayerBars, 182 | LayerTiled, 183 | LayerBelow, 184 | LayerTop, 185 | LayerAbove, 186 | LayerFullscreen, 187 | LayerLast 188 | }; 189 | 190 | /* cursor types */ 191 | enum { 192 | CURSOR_NORMAL, 193 | CURSOR_MOVE, 194 | CURSOR_NW, 195 | CURSOR_NE, 196 | CURSOR_SW, 197 | CURSOR_SE, 198 | CURSOR_N, 199 | CURSOR_S, 200 | CURSOR_W, 201 | CURSOR_E, 202 | CURSOR_PIRATE, 203 | CURSOR_LAST 204 | }; 205 | 206 | /* frame region */ 207 | enum { 208 | FrameNone = 0, 209 | FrameButtonLeft = 1, 210 | FrameButtonRight = 2, 211 | FrameTitle = 3, 212 | FrameBorder = 4, 213 | }; 214 | 215 | /* auto-tab behavior */ 216 | enum { 217 | NoAutoTab, 218 | TabFloating, 219 | TabTilingAlways, 220 | TabTilingMulti, 221 | TabAlways, 222 | }; 223 | 224 | /* notification spawning direction */ 225 | enum { 226 | DownWards, 227 | UpWards 228 | }; 229 | 230 | /* window eight sections (aka octants) */ 231 | enum Octant { 232 | C = 0, 233 | N = (1 << 0), 234 | S = (1 << 1), 235 | W = (1 << 2), 236 | E = (1 << 3), 237 | NW = (1 << 0) | (1 << 2), 238 | NE = (1 << 0) | (1 << 3), 239 | SW = (1 << 1) | (1 << 2), 240 | SE = (1 << 1) | (1 << 3), 241 | }; 242 | 243 | /* transient window structure */ 244 | struct Transient { 245 | struct Transient *prev, *next; 246 | struct Tab *t; 247 | Window frame; 248 | Window win; 249 | Pixmap pix; 250 | int x, y, w, h; 251 | int maxw, maxh; 252 | int pw, ph; 253 | int ignoreunmap; 254 | }; 255 | 256 | /* prompt structure, used only when calling promptisvalid() */ 257 | struct Prompt { 258 | Window win, frame; 259 | }; 260 | 261 | /* tab structure */ 262 | struct Tab { 263 | struct Tab *prev, *next; 264 | struct Client *c; 265 | struct Transient *trans; 266 | Window title; 267 | Window frame; 268 | Window win; 269 | Pixmap pix; 270 | char *name; 271 | char *class; 272 | int ignoreunmap; 273 | int isurgent; 274 | int winw, winh; /* window geometry */ 275 | int x, w; /* tab geometry */ 276 | int pw; /* pixmap width */ 277 | }; 278 | 279 | /* client structure */ 280 | struct Client { 281 | struct Client *prev, *next; 282 | struct Client *fprev, *fnext; 283 | struct Client *rprev, *rnext; 284 | struct Monitor *mon; 285 | struct Desktop *desk; 286 | struct Row *row; 287 | struct Tab *tabs; 288 | struct Tab *seltab; 289 | int ntabs; 290 | int ishidden, isuserplaced, isshaded, isfullscreen; 291 | int state; 292 | int saveh; /* original height, used for shading */ 293 | int rh; /* row height */ 294 | int x, y, w, h, b, t; /* current geometry */ 295 | int pw, ph; /* pixmap width and height */ 296 | int fx, fy, fw, fh; /* floating geometry */ 297 | int tx, ty, tw, th; /* tiled geometry */ 298 | int layer; /* stacking order */ 299 | long shflags; 300 | Window curswin; 301 | Window frame; 302 | Pixmap pix; 303 | }; 304 | 305 | /* row in a column of tiled windows */ 306 | struct Row { 307 | struct Row *prev, *next; 308 | struct Column *col; 309 | struct Client *c; 310 | int h; /* row height */ 311 | }; 312 | 313 | /* column of tiled windows */ 314 | struct Column { 315 | struct Column *prev, *next; 316 | struct Desktop *desk; 317 | struct Row *row; 318 | int w; /* column width */ 319 | }; 320 | 321 | /* desktop of a monitor */ 322 | struct Desktop { 323 | struct Monitor *mon; 324 | struct Column *col; 325 | int n; /* desktop number */ 326 | }; 327 | 328 | /* data of a monitor */ 329 | struct Monitor { 330 | struct Monitor *prev, *next; 331 | struct Desktop *desks; 332 | struct Desktop *seldesk; 333 | int mx, my, mw, mh; /* screen size */ 334 | int wx, wy, ww, wh; /* window area */ 335 | int gx, gy, gw, gh; /* window area with gaps */ 336 | int n; /* monitor number */ 337 | }; 338 | 339 | /* configuration, mostly set in config.h */ 340 | struct Config { 341 | const char *theme_path; 342 | const char *font; 343 | const char *notifgravity; 344 | 345 | int edge_width; 346 | int ignoregaps; 347 | int ignoretitle; 348 | int ignoreborders; 349 | int mergeborders; 350 | int hidetitle; 351 | 352 | int ignoreindirect; 353 | 354 | int gapinner; 355 | int gapouter; 356 | 357 | int ndesktops; 358 | 359 | int notifgap; 360 | 361 | unsigned int modifier; 362 | unsigned int focusbuttons; 363 | unsigned int raisebuttons; 364 | 365 | /* those elements are computed from elements above */ 366 | int gravity; 367 | int direction; 368 | }; 369 | 370 | /* decoration sections pixmaps */ 371 | struct Decor { 372 | Pixmap bl; /* button left */ 373 | Pixmap tl; /* title left end */ 374 | Pixmap t; /* title middle */ 375 | Pixmap tr; /* title right end */ 376 | Pixmap br; /* button right */ 377 | Pixmap nw; /* northwest corner */ 378 | Pixmap nf; /* north first edge */ 379 | Pixmap n; /* north border */ 380 | Pixmap nl; /* north last edge */ 381 | Pixmap ne; /* northeast corner */ 382 | Pixmap wf; /* west first edge */ 383 | Pixmap w; /* west border */ 384 | Pixmap wl; /* west last edge */ 385 | Pixmap ef; /* east first edge */ 386 | Pixmap e; /* east border */ 387 | Pixmap el; /* east last edge */ 388 | Pixmap sw; /* southwest corner */ 389 | Pixmap sf; /* south first edge */ 390 | Pixmap s; /* south border */ 391 | Pixmap sl; /* south last edge */ 392 | Pixmap se; /* southeast corner */ 393 | unsigned long fg; 394 | unsigned long bg; 395 | }; 396 | 397 | /* union returned by getclient */ 398 | struct Winres { 399 | struct Notification *n; 400 | struct Client *c; 401 | struct Tab *t; 402 | struct Transient *trans; 403 | }; 404 | 405 | /* rectangle */ 406 | struct Outline { 407 | int x, y, w, h; 408 | int diffx, diffy; 409 | }; 410 | 411 | /* window rules read from X resources */ 412 | struct Rules { 413 | int desk; 414 | int state; 415 | int autotab; 416 | int x, y, w, h; 417 | }; 418 | 419 | /* notification window */ 420 | struct Notification { 421 | struct Notification *prev, *next; 422 | Window frame; 423 | Window win; 424 | Pixmap pix; 425 | int w, h; /* geometry of the entire thing (content + decoration) */ 426 | int pw, ph; /* pixmap width and height */ 427 | }; 428 | -------------------------------------------------------------------------------- /theme.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static char *theme[] = { 3 | /* columns rows colors chars-per-pixel */ 4 | "159 159 10 1 7 17", 5 | " c #2E3436", 6 | ". c #555753", 7 | "X c #A40000", 8 | "o c #CC0000", 9 | "O c #EF2929", 10 | "+ c #204A87", 11 | "@ c #3465A4", 12 | "# c #729FCF", 13 | "$ c #888A85", 14 | "% c white", 15 | /* pixels */ 16 | "+++++++++++++++++++++++++++++++++++++++++++++++++++++#####################################################+++++++++++++++++++++++++++++++++++++++++++++++++++++", 17 | "+######################+####+#######################+#++++++++++++++++++++++#++++#+++++++++++++++++++++++#+###################################################+", 18 | "+#####################++###++######################++#+++++++++++++++++++++##+++##++++++++++++++++++++++##+##################################################++", 19 | "+##@@@@@@@@@@@@@@@@@@@++##@++##@@@@@@@@@@@@@@@@@@@@++#++@@@@@@@@@@@@@@@@@@@##++@##++@@@@@@@@@@@@@@@@@@@@##+##@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@++", 20 | "+##@@@@@@@@@@@@@@@@@@@++##@++##@@@@@@@@@@@@@@@@@@@@++#++@@@@@@@@@@@@@@@@@@@##++@##++@@@@@@@@@@@@@@@@@@@@##+##@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@++", 21 | "+##@@+++++++++++++++++++#++++#++++++++++++++++++#@@++#++@@###################+####+##################+@@##+##@@+++++++++++++++++++++++++++++++++++++++++++#@@++", 22 | "+##@@++++++++++++++++++++++++++++++++++++++++++##@@++#++@@##########################################++@@##+##@@++++++++++++++++++++++++++++++++++++++++++##@@++", 23 | "+##@@++################+####+################++##@@++#++@@##++++++++++++++++#++++#++++++++++++++++##++@@##+##@@++################+####+################++##@@++", 24 | "+##@@++###############++###++###############+++##@@++#++@@##+++++++++++++++##+++##+++++++++++++++###++@@##+##@@++###############++###++###############+++##@@++", 25 | "+##@@++##@@@@@@@@@@@@@++##@++##@@@@@@@@@@@@@+++##@@++#++@@##++@@@@@@@@@@@@@##++@##++@@@@@@@@@@@@@###++@@##+##@@++##@@@@@@@@@@@@@++##@++##@@@@@@@@@@@@@+++##@@++", 26 | "+##@@++##@@@@@@@@@@@@@++##@++##@###@@@@@##+@+++##@@++#++@@##++@@@@@@@@@@@@@##++@##++@+++@@@@@++#@###++@@##+##@@++##@@@@@@@@@@@@@++##@++##@###@@@@@##+@+++##@@++", 27 | "+##@@++##@@@@@@@@@@@@@++##@++##@#@@#@@@#@@+@+++##@@++#++@@##++@@@@@@@@@@@@@##++@##++@+@@+@@@+@@#@###++@@##+##@@++##@@@@@@@@@@@@@++##@++##@#@@#@@@#@@+@+++##@@++", 28 | "+##@@++##@@@@@@@@@@@@@++##@++##@#@@@#@#@@@+@+++##@@++#++@@##++@@@@@@@@@@@@@##++@##++@+@@@+@+@@@#@###++@@##+##@@++##@@@@@@@@@@@@@++##@++##@#@@@#@#@@@+@+++##@@++", 29 | "+##@@++##@@@@@@@@@@@@@++##@++##@@#@@@#@@@+@@+++##@@++#++@@##++@@@@@@@@@@@@@##++@##++@@+@@@+@@@#@@###++@@##+##@@++##@@@@@@@@@@@@@++##@++##@@#@@@#@@@+@@+++##@@++", 30 | "+##@@++##@@########+@@++##@++##@@@#@@@@@+@@@+++##@@++#++@@##++@@++++++++#@@##++@##++@@@+@@@@@#@@@###++@@##+##@@++##@@########+@@++##@++##@@@#@@@@@+@@@+++##@@++", 31 | "+##@@++##@@#@@@@@@@+@@++##@++##@@@@#@@@+@@@@+++##@@++#++@@##++@@+@@@@@@@#@@##++@##++@@@@+@@@#@@@@###++@@##+##@@++##@@#@@@@@@@+@@++##@++##@@@@#@@@+@@@@+++##@@++", 32 | "+##@@++##@@#++++++++@@++##@++##@@@#@@@@@+@@@+++##@@++#++@@##++@@+########@@##++@##++@@@+@@@@@#@@@###++@@##+##@@++##@@#++++++++@@++##@++##@@@#@@@@@+@@@+++##@@++", 33 | "+##@@++##@@@@@@@@@@@@@++##@++##@@#@@@+@@@+@@+++##@@++#++@@##++@@@@@@@@@@@@@##++@##++@@+@@@#@@@#@@###++@@##+##@@++##@@@@@@@@@@@@@++##@++##@@#@@@+@@@+@@+++##@@++", 34 | "+##@@++##@@@@@@@@@@@@@++##@++##@#@@@+@+@@@+@+++##@@++#++@@##++@@@@@@@@@@@@@##++@##++@+@@@#@#@@@#@###++@@##+##@@++##@@@@@@@@@@@@@++##@++##@#@@@+@+@@@+@+++##@@++", 35 | "+##@@++##@@@@@@@@@@@@@++##@++##@#@@+@@@+@@+@+++##@@++#++@@##++@@@@@@@@@@@@@##++@##++@+@@#@@@#@@#@###++@@##+##@@++##@@@@@@@@@@@@@++##@++##@#@@+@@@+@@+@+++##@@++", 36 | "+##@@++##@@@@@@@@@@@@@++##@++##@#++@@@@@+++@+++##@@++#++@@##++@@@@@@@@@@@@@##++@##++@+##@@@@@###@###++@@##+##@@++##@@@@@@@@@@@@@++##@++##@#++@@@@@+++@+++##@@++", 37 | "+##@@++##@@@@@@@@@@@@@++##@++##@@@@@@@@@@@@@+++##@@++#++@@##++@@@@@@@@@@@@@##++@##++@@@@@@@@@@@@@###++@@##+##@@++##@@@@@@@@@@@@@++##@++##@@@@@@@@@@@@@+++##@@++", 38 | "+#+++++#++++++++++++++++#++++#+++++++++++++++++#+++++#+#####+################+####+#################+#####+##@@++#++++++++++++++++#++++#+++++++++++++++++##@@++", 39 | "+++++++++++++++++++++++++++++++++++++++++++++++++++++#####################################################+##@@++++++++++++++++++++++++++++++++++++++++++##@@++", 40 | "+#####+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+#####+#+++++#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#+++++#+##@@++#######################################+##@@++", 41 | "+####++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+####++#++++##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++++##+##@@++#######################################+##@@++", 42 | "+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", 43 | "+#+++++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+#+++++#+#####%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#+#####+##@@++#######################################+##@@++", 44 | "+++++++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+++++++#######%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#######+##@@++#######################################+##@@++", 45 | "+#####+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+#####+#+++++#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#+++++#+##@@++#######################################+##@@++", 46 | "+####++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+####++#++++##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++++##+##@@++#######################################+##@@++", 47 | "+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", 48 | "+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", 49 | "+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", 50 | "+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", 51 | "+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", 52 | "+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", 53 | "+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", 54 | "+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", 55 | "+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", 56 | "+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", 57 | "+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", 58 | "+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", 59 | "+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", 60 | "+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", 61 | "+##@@++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+##@@++#++@@##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#++@@##+##@@++#######################################+##@@++", 62 | "+##@@++++++++++++++++++++++++++++++++++++++++++##@@++#++@@##########################################++@@##+##@@++++++++++++++++++++++++++++++++++++++++++##@@++", 63 | "+##@@+#################+####+####################@@++#++@@#+++++++++++++++++#++++#++++++++++++++++++++@@##+##@@+###########################################@@++", 64 | "+##@@#################++###++####################@@++#++@@+++++++++++++++++##+++##++++++++++++++++++++@@##+##@@############################################@@++", 65 | "+##@@@@@@@@@@@@@@@@@@@++##@++##@@@@@@@@@@@@@@@@@@@@++#++@@@@@@@@@@@@@@@@@@@##++@##++@@@@@@@@@@@@@@@@@@@@##+##@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@++", 66 | "+##@@@@@@@@@@@@@@@@@@@++##@++##@@@@@@@@@@@@@@@@@@@@++#++@@@@@@@@@@@@@@@@@@@##++@##++@@@@@@@@@@@@@@@@@@@@##+##@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@++", 67 | "+#++++++++++++++++++++++#++++#+++++++++++++++++++++++#+######################+####+#######################+#+++++++++++++++++++++++++++++++++++++++++++++++++++", 68 | "+++++++++++++++++++++++++++++++++++++++++++++++++++++#####################################################+++++++++++++++++++++++++++++++++++++++++++++++++++++", 69 | " $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ", 70 | " $$$$$$$$$$$$$$$$$$$$$$ $$$$ $$$$$$$$$$$$$$$$$$$$$$$ $ $ $ $ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ", 71 | " $$$$$$$$$$$$$$$$$$$$$ $$$ $$$$$$$$$$$$$$$$$$$$$$ $ $$ $$ $$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ", 72 | " $$................... $$. $$.................... $ ...................$$ .$$ ....................$$ $$................................................ ", 73 | " $$................... $$. $$.................... $ ...................$$ .$$ ....................$$ $$................................................ ", 74 | " $$.. $ $ $.. $ ..$$$$$$$$$$$$$$$$$$$ $$$$ $$$$$$$$$$$$$$$$$$ ..$$ $$.. $.. ", 75 | " $$.. $$.. $ ..$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ..$$ $$.. $$.. ", 76 | " $$.. $$$$$$$$$$$$$$$$ $$$$ $$$$$$$$$$$$$$$$ $$.. $ ..$$ $ $ $$ ..$$ $$.. $$$$$$$$$$$$$$$$ $$$$ $$$$$$$$$$$$$$$$ $$.. ", 77 | " $$.. $$$$$$$$$$$$$$$ $$$ $$$$$$$$$$$$$$$ $$.. $ ..$$ $$ $$ $$$ ..$$ $$.. $$$$$$$$$$$$$$$ $$$ $$$$$$$$$$$$$$$ $$.. ", 78 | " $$.. $$............. $$. $$............. $$.. $ ..$$ .............$$ .$$ .............$$$ ..$$ $$.. $$............. $$. $$............. $$.. ", 79 | " $$.. $$............. $$. $$.$$$.....$$ . $$.. $ ..$$ .............$$ .$$ . ..... $.$$$ ..$$ $$.. $$............. $$. $$.$$$.....$$ . $$.. ", 80 | " $$.. $$............. $$. $$.$..$...$.. . $$.. $ ..$$ .............$$ .$$ . .. ... ..$.$$$ ..$$ $$.. $$............. $$. $$.$..$...$.. . $$.. ", 81 | " $$.. $$............. $$. $$.$...$.$... . $$.. $ ..$$ .............$$ .$$ . ... . ...$.$$$ ..$$ $$.. $$............. $$. $$.$...$.$... . $$.. ", 82 | " $$.. $$............. $$. $$..$...$... .. $$.. $ ..$$ .............$$ .$$ .. ... ...$..$$$ ..$$ $$.. $$............. $$. $$..$...$... .. $$.. ", 83 | " $$.. $$..$$$$$$$$ .. $$. $$...$..... ... $$.. $ ..$$ .. $..$$ .$$ ... .....$...$$$ ..$$ $$.. $$..$$$$$$$$ .. $$. $$...$..... ... $$.. ", 84 | " $$.. $$..$....... .. $$. $$....$... .... $$.. $ ..$$ .. .......$..$$ .$$ .... ...$....$$$ ..$$ $$.. $$..$....... .. $$. $$....$... .... $$.. ", 85 | " $$.. $$..$ .. $$. $$...$..... ... $$.. $ ..$$ .. $$$$$$$$..$$ .$$ ... .....$...$$$ ..$$ $$.. $$..$ .. $$. $$...$..... ... $$.. ", 86 | " $$.. $$............. $$. $$..$... ... .. $$.. $ ..$$ .............$$ .$$ .. ...$...$..$$$ ..$$ $$.. $$............. $$. $$..$... ... .. $$.. ", 87 | " $$.. $$............. $$. $$.$... . ... . $$.. $ ..$$ .............$$ .$$ . ...$.$...$.$$$ ..$$ $$.. $$............. $$. $$.$... . ... . $$.. ", 88 | " $$.. $$............. $$. $$.$.. ... .. . $$.. $ ..$$ .............$$ .$$ . ..$...$..$.$$$ ..$$ $$.. $$............. $$. $$.$.. ... .. . $$.. ", 89 | " $$.. $$............. $$. $$. ..... . $$.. $ ..$$ .............$$ .$$ . $$.....$$$.$$$ ..$$ $$.. $$............. $$. $$. ..... . $$.. ", 90 | " $$.. $$............. $$. $$............. $$.. $ ..$$ .............$$ .$$ .............$$$ ..$$ $$.. $$............. $$. $$............. $$.. ", 91 | " $ $ $ $ $ $ $$$$$ $$$$$$$$$$$$$$$$ $$$$ $$$$$$$$$$$$$$$$$ $$$$$ $$.. $ $ $ $$.. ", 92 | " $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. $$.. ", 93 | " $$$$$ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$$$$ $ $%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ $ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", 94 | " $$$$ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$$$ $ $$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ $$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", 95 | " $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", 96 | " $ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $ $ $$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ $$$$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", 97 | " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$$$$$$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", 98 | " $$$$$ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$$$$ $ $%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ $ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", 99 | " $$$$ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$$$ $ $$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ $$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", 100 | " $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", 101 | " $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", 102 | " $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", 103 | " $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", 104 | " $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", 105 | " $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", 106 | " $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", 107 | " $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", 108 | " $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", 109 | " $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", 110 | " $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", 111 | " $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", 112 | " $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", 113 | " $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", 114 | " $$.. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% $$.. $ ..$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$.. ", 115 | " $$.. $$.. $ ..$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ..$$ $$.. $$.. ", 116 | " $$.. $$$$$$$$$$$$$$$$$ $$$$ $$$$$$$$$$$$$$$$$$$$.. $ ..$ $ $ ..$$ $$.. $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$.. ", 117 | " $$..$$$$$$$$$$$$$$$$$ $$$ $$$$$$$$$$$$$$$$$$$$.. $ .. $$ $$ ..$$ $$..$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$.. ", 118 | " $$................... $$. $$.................... $ ...................$$ .$$ ....................$$ $$................................................ ", 119 | " $$................... $$. $$.................... $ ...................$$ .$$ ....................$$ $$................................................ ", 120 | " $ $ $ $ $$$$$$$$$$$$$$$$$$$$$$ $$$$ $$$$$$$$$$$$$$$$$$$$$$$ $ ", 121 | " $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ", 122 | "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 123 | "XOOOOOOOOOOOOOOOOOOOOOOXOOOOXOOOOOOOOOOOOOOOOOOOOOOOXOXXXXXXXXXXXXXXXXXXXXXXOXXXXOXXXXXXXXXXXXXXXXXXXXXXXOXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOX", 124 | "XOOOOOOOOOOOOOOOOOOOOOXXOOOXXOOOOOOOOOOOOOOOOOOOOOOXXOXXXXXXXXXXXXXXXXXXXXXOOXXXOOXXXXXXXXXXXXXXXXXXXXXXOOXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXX", 125 | "XOOoooooooooooooooooooXXOOoXXOOooooooooooooooooooooXXOXXoooooooooooooooooooOOXXoOOXXooooooooooooooooooooOOXOOooooooooooooooooooooooooooooooooooooooooooooooooXX", 126 | "XOOoooooooooooooooooooXXOOoXXOOooooooooooooooooooooXXOXXoooooooooooooooooooOOXXoOOXXooooooooooooooooooooOOXOOooooooooooooooooooooooooooooooooooooooooooooooooXX", 127 | "XOOooXXXXXXXXXXXXXXXXXXXOXXXXOXXXXXXXXXXXXXXXXXXOooXXOXXooOOOOOOOOOOOOOOOOOOOXOOOOXOOOOOOOOOOOOOOOOOOXooOOXOOooXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOooXX", 128 | "XOOooXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOOooXXOXXooOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXXooOOXOOooXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOOooXX", 129 | "XOOooXXOOOOOOOOOOOOOOOOXOOOOXOOOOOOOOOOOOOOOOXXOOooXXOXXooOOXXXXXXXXXXXXXXXXOXXXXOXXXXXXXXXXXXXXXXOOXXooOOXOOooXXOOOOOOOOOOOOOOOOXOOOOXOOOOOOOOOOOOOOOOXXOOooXX", 130 | "XOOooXXOOOOOOOOOOOOOOOXXOOOXXOOOOOOOOOOOOOOOXXXOOooXXOXXooOOXXXXXXXXXXXXXXXOOXXXOOXXXXXXXXXXXXXXXOOOXXooOOXOOooXXOOOOOOOOOOOOOOOXXOOOXXOOOOOOOOOOOOOOOXXXOOooXX", 131 | "XOOooXXOOoooooooooooooXXOOoXXOOoooooooooooooXXXOOooXXOXXooOOXXoooooooooooooOOXXoOOXXoooooooooooooOOOXXooOOXOOooXXOOoooooooooooooXXOOoXXOOoooooooooooooXXXOOooXX", 132 | "XOOooXXOOoooooooooooooXXOOoXXOOoOOOoooooOOXoXXXOOooXXOXXooOOXXoooooooooooooOOXXoOOXXoXXXoooooXXOoOOOXXooOOXOOooXXOOoooooooooooooXXOOoXXOOoOOOoooooOOXoXXXOOooXX", 133 | "XOOooXXOOoooooooooooooXXOOoXXOOoOooOoooOooXoXXXOOooXXOXXooOOXXoooooooooooooOOXXoOOXXoXooXoooXooOoOOOXXooOOXOOooXXOOoooooooooooooXXOOoXXOOoOooOoooOooXoXXXOOooXX", 134 | "XOOooXXOOoooooooooooooXXOOoXXOOoOoooOoOoooXoXXXOOooXXOXXooOOXXoooooooooooooOOXXoOOXXoXoooXoXoooOoOOOXXooOOXOOooXXOOoooooooooooooXXOOoXXOOoOoooOoOoooXoXXXOOooXX", 135 | "XOOooXXOOoooooooooooooXXOOoXXOOooOoooOoooXooXXXOOooXXOXXooOOXXoooooooooooooOOXXoOOXXooXoooXoooOooOOOXXooOOXOOooXXOOoooooooooooooXXOOoXXOOooOoooOoooXooXXXOOooXX", 136 | "XOOooXXOOooOOOOOOOOXooXXOOoXXOOoooOoooooXoooXXXOOooXXOXXooOOXXooXXXXXXXXOooOOXXoOOXXoooXoooooOoooOOOXXooOOXOOooXXOOooOOOOOOOOXooXXOOoXXOOoooOoooooXoooXXXOOooXX", 137 | "XOOooXXOOooOoooooooXooXXOOoXXOOooooOoooXooooXXXOOooXXOXXooOOXXooXoooooooOooOOXXoOOXXooooXoooOooooOOOXXooOOXOOooXXOOooOoooooooXooXXOOoXXOOooooOoooXooooXXXOOooXX", 138 | "XOOooXXOOooOXXXXXXXXooXXOOoXXOOoooOoooooXoooXXXOOooXXOXXooOOXXooXOOOOOOOOooOOXXoOOXXoooXoooooOoooOOOXXooOOXOOooXXOOooOXXXXXXXXooXXOOoXXOOoooOoooooXoooXXXOOooXX", 139 | "XOOooXXOOoooooooooooooXXOOoXXOOooOoooXoooXooXXXOOooXXOXXooOOXXoooooooooooooOOXXoOOXXooXoooOoooOooOOOXXooOOXOOooXXOOoooooooooooooXXOOoXXOOooOoooXoooXooXXXOOooXX", 140 | "XOOooXXOOoooooooooooooXXOOoXXOOoOoooXoXoooXoXXXOOooXXOXXooOOXXoooooooooooooOOXXoOOXXoXoooOoOoooOoOOOXXooOOXOOooXXOOoooooooooooooXXOOoXXOOoOoooXoXoooXoXXXOOooXX", 141 | "XOOooXXOOoooooooooooooXXOOoXXOOoOooXoooXooXoXXXOOooXXOXXooOOXXoooooooooooooOOXXoOOXXoXooOoooOooOoOOOXXooOOXOOooXXOOoooooooooooooXXOOoXXOOoOooXoooXooXoXXXOOooXX", 142 | "XOOooXXOOoooooooooooooXXOOoXXOOoXXXoooooXXXoXXXOOooXXOXXooOOXXoooooooooooooOOXXoOOXXoXOOoooooOOOoOOOXXooOOXOOooXXOOoooooooooooooXXOOoXXOOoXXXoooooXXXoXXXOOooXX", 143 | "XOOooXXOOoooooooooooooXXOOoXXOOoooooooooooooXXXOOooXXOXXooOOXXoooooooooooooOOXXoOOXXoooooooooooooOOOXXooOOXOOooXXOOoooooooooooooXXOOoXXOOoooooooooooooXXXOOooXX", 144 | "XOXXXXXOXXXXXXXXXXXXXXXXOXXXXOXXXXXXXXXXXXXXXXXOXXXXXOXOOOOOXOOOOOOOOOOOOOOOOXOOOOXOOOOOOOOOOOOOOOOOXOOOOOXOOooXXOXXXXXXXXXXXXXXXXOXXXXOXXXXXXXXXXXXXXXXXOOooXX", 145 | "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOOooXX", 146 | "XOOOOOX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOOOOXOXXXXXO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXXXXOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", 147 | "XOOOOXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOOOXXOXXXXOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXXXOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", 148 | "XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", 149 | "XOXXXXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOXXXXXOXOOOOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXOOOOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", 150 | "XXXXXXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XXXXXXXOOOOOOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OOOOOOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", 151 | "XOOOOOX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOOOOXOXXXXXO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXXXXOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", 152 | "XOOOOXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOOOXXOXXXXOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXXXOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", 153 | "XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", 154 | "XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", 155 | "XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", 156 | "XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", 157 | "XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", 158 | "XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", 159 | "XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", 160 | "XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", 161 | "XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", 162 | "XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", 163 | "XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", 164 | "XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", 165 | "XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", 166 | "XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", 167 | "XOOooXX%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%XOOooXXOXXooOO%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%OXXooOOXOOooXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXOOooXX", 168 | "XOOooXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOOooXXOXXooOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXXooOOXOOooXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOOooXX", 169 | "XOOooXOOOOOOOOOOOOOOOOOXOOOOXOOOOOOOOOOOOOOOOOOOOooXXOXXooOXXXXXXXXXXXXXXXXXOXXXXOXXXXXXXXXXXXXXXXXXXXooOOXOOooXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOooXX", 170 | "XOOooOOOOOOOOOOOOOOOOOXXOOOXXOOOOOOOOOOOOOOOOOOOOooXXOXXooXXXXXXXXXXXXXXXXXOOXXXOOXXXXXXXXXXXXXXXXXXXXooOOXOOooOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOooXX", 171 | "XOOoooooooooooooooooooXXOOoXXOOooooooooooooooooooooXXOXXoooooooooooooooooooOOXXoOOXXooooooooooooooooooooOOXOOooooooooooooooooooooooooooooooooooooooooooooooooXX", 172 | "XOOoooooooooooooooooooXXOOoXXOOooooooooooooooooooooXXOXXoooooooooooooooooooOOXXoOOXXooooooooooooooooooooOOXOOooooooooooooooooooooooooooooooooooooooooooooooooXX", 173 | "XOXXXXXXXXXXXXXXXXXXXXXXOXXXXOXXXXXXXXXXXXXXXXXXXXXXXOXOOOOOOOOOOOOOOOOOOOOOOXOOOOXOOOOOOOOOOOOOOOOOOOOOOOXOXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 174 | "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" 175 | }; 176 | --------------------------------------------------------------------------------