├── .gitignore ├── ISSUE.md ├── LICENSE ├── Makefile ├── README.md ├── dk.desktop ├── dk.png ├── doc ├── dk.png ├── dkrc ├── scripts │ ├── bar.sh │ ├── dk.conkyrc │ ├── dzenws.sh │ ├── gap_alphaborder.sh │ ├── layouts.sh │ ├── title.sh │ ├── workspaces.sh │ └── wsborders.sh └── sxhkdrc ├── man ├── dk.1 └── dkcmd.1 └── src ├── cmd.c ├── cmd.h ├── config.def.h ├── dk.c ├── dk.h ├── dkcmd.c ├── event.c ├── event.h ├── layout.c ├── layout.h ├── parse.c ├── parse.h ├── status.c ├── status.h ├── strl.c ├── strl.h ├── util.c └── util.h /.gitignore: -------------------------------------------------------------------------------- 1 | dk 2 | dkcmd 3 | config.h 4 | src/config.h 5 | tags 6 | src/tags 7 | .cache 8 | compile_commands.json 9 | .clang-format 10 | *.o 11 | *.bak 12 | -------------------------------------------------------------------------------- /ISSUE.md: -------------------------------------------------------------------------------- 1 | # TODOs and issues for dk 2 | 3 | 1. When entering full screen mode and open other windows, dk does not automatically 4 | exit full screen mode and this causes various glitches. I believe that if the 5 | application is in full screen mode, then when opening windows of any kind (tiled, 6 | floating, pop-up), the application should automatically exit the full screen mode. 7 | 8 | 2. In tile layout we can only change the width of the master window, and the stack 9 | windows can only be changed in height. 10 | 11 | 3. There is a free game war thunder in steam which for some reason does not go into 12 | full screen mode automatically. It's not hard for me to press the keyboard 13 | shortcut to go to full screen, but I've tested it in many other window managers 14 | and it doesn't have this problem. I think that the problem does not apply to a 15 | single game and can be deeper and appear in a different use case. 16 | 17 | 4. In the gparted program, when confirming operations, nothing is visible in the 18 | pop-up window that appears showing the progress of operations; there is also a 19 | problem with the border (I will attach a screenshot). 20 | 21 | 5. Regarding the fact that the dk does not exit full-screen mode when opening other 22 | windows. Strictly speaking, this is not a bug, but the problem is that when a new 23 | window opens, it receives focus but the window itself is not visible, so you have 24 | to return to the previous window and manually exit the fullscreen, or alternatively, 25 | exit the fullscreen in advance before opening another window :) Window managers 26 | such as i3, bspwm, hyprland implement automatic exit from full-screen mode when 27 | opening another window, it seems to me that this is the best solution. 28 | 29 | 6. I will also clarify that it is impossible to resize windows when the stack window 30 | is in focus: I use the tile layout with the following parameters 31 | ```dkcmd set ws=_ apply layout=tile master=1 stack=0 gap=6 msplit=0.55 ssplit=0.50``` 32 | that is, in the stack has only one window vertically, like in vanilla dwm. So I 33 | can only resize when the master window has focus, but when the stack window has 34 | focus, "dkcmd win resize {w=-40,w=+40}" does nothing. 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | © 2019-2023 Nathaniel Maia 4 | © 2020 Dmitry Belitsky 5 | © 2022 Kjetil Molteberg 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a 8 | copy of this software and associated documentation files (the "Software"), 9 | to deal in the Software without restriction, including without limitation 10 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | and/or sell copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 20 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # dk window manager 2 | # see license file for copyright and license details 3 | 4 | # install paths 5 | VPATH = src 6 | PREFIX ?= /usr/local 7 | MAN ?= ${PREFIX}/share/man 8 | DOC ?= ${PREFIX}/share/doc/dk 9 | SES ?= /usr/share/xsessions 10 | 11 | ifeq ($(PREFIX),/usr/local) 12 | VERSION = $(shell git describe --tags | sed 's+-+.r+' | tr - . | cut -c2-) 13 | else 14 | VERSION = 2.2 15 | endif 16 | 17 | # source and object files 18 | SRC = dk.c cmd.c event.c layout.c parse.c status.c strl.c util.c 19 | OBJ = ${SRC:.c=.o} 20 | CSRC = dkcmd.c strl.c util.c 21 | COBJ = ${CSRC:.c=.o} 22 | 23 | # compiler and linker flags 24 | OPTLVL = -O3 25 | 26 | CPPFLAGS += -D_DEFAULT_SOURCE -D_BSD_SOURCE -DVERSION=\"${VERSION}\" 27 | CFLAGS += -flto=auto -std=c17 -pedantic -Wall -Wextra -I/usr/X11R6/include 28 | LDFLAGS += -s -L/usr/X11R6/lib -lxcb -lxcb-keysyms -lxcb-util -lxcb-cursor -lxcb-icccm -lxcb-randr -lxcb-res 29 | 30 | all: dk dkcmd 31 | 32 | debug: CPPFLAGS += -DDEBUG 33 | debug: all 34 | 35 | fdebug: CFLAGS += -finstrument-functions -export-dynamic 36 | fdebug: LDFLAGS += -lmcheck -ldl -Wl,--export-dynamic 37 | fdebug: CPPFLAGS += -DFUNCDEBUG 38 | fdebug: debug 39 | 40 | leak: OPTLVL = -Og 41 | leak: CFLAGS += -ggdb3 42 | leak: all 43 | 44 | .c.o: 45 | ${CC} ${CFLAGS} ${OPTLVL} ${CPPFLAGS} -c $< 46 | 47 | ${OBJ}: config.h 48 | 49 | config.h: 50 | @test -e src/$@ || cp -v src/config.def.h src/$@ 51 | 52 | dk: config.h ${OBJ} 53 | ${CC} ${CFLAGS} ${OPTLVL} ${OBJ} -o $@ ${LDFLAGS} 54 | 55 | dkcmd: ${COBJ} 56 | ${CC} ${CFLAGS} ${OPTLVL} ${COBJ} -o $@ 57 | 58 | clean: 59 | rm -f *.o dk dkcmd 60 | 61 | install: all 62 | mkdir -p ${DESTDIR}${PREFIX}/bin ${DESTDIR}${SES} ${DESTDIR}${MAN}/man1 ${DESTDIR}${DOC} 63 | install -Dm755 dk dkcmd ${DESTDIR}${PREFIX}/bin/ 64 | sed "s/VERSION/${VERSION}/g" man/dk.1 > ${DESTDIR}${MAN}/man1/dk.1 65 | cp -rfp man/dkcmd.1 ${DESTDIR}${MAN}/man1/dkcmd.1 66 | chmod 644 ${DESTDIR}${MAN}/man1/dk.1 ${DESTDIR}${MAN}/man1/dkcmd.1 67 | cp -rf doc/* ${DESTDIR}${DOC} 68 | install -Dm644 dk.desktop ${DESTDIR}${SES}/ 69 | 70 | uninstall: 71 | rm -f ${DESTDIR}${PREFIX}/bin/dk ${DESTDIR}${PREFIX}/bin/dkcmd 72 | rm -f ${DESTDIR}${MAN}/man1/dk.1 ${DESTDIR}${MAN}/man1/dkcmd.1 73 | rm -rf ${DESTDIR}${DOC} 74 | rm -f ${DESTDIR}${SES}/dk.desktop 75 | 76 | .PHONY: all debug fdebug leak clean install uninstall 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](dk.png) 2 | 3 | A list based tiling window manager in the vein of dwm, bspwm, and xmonad. 4 | 5 | Some basics: 6 | 7 | - Heavily scriptable and tinker friendly. 8 | - Dynamic workspaces, any workspace on any monitor. 9 | - More dynamic tile layout with multiple stacks and window resizing. 10 | - Gaps, double borders, additional layouts, padding and more. 11 | - Better support for mouse and floating windows, resize tiles with mouse. 12 | - Startup script for configuration and running programs. 13 | - Status info can be output to a file or piped for use in bars or scripts. 14 | - No built-in extras *(bar, font drawing, or key bindings)*. 15 | - Sane support for 16 | [icccm](https://www.x.org/releases/X11R7.6/doc/xorg-docs/specs/ICCCM/icccm.html#client_to_window_manager_communication), 17 | [ewmh](https://specifications.freedesktop.org/wm-spec/wm-spec-latest.html), and 18 | [motif](http://www.ist.co.uk/motif/books/vol6A/ch-20.fm.html#963509). 19 | 20 | 21 | ### Installation 22 | 23 | You need the xcb headers 24 | 25 | Arch 26 | ``` 27 | xcb-proto xcb-util xcb-util-wm xcb-util-cursor xcb-util-keysyms 28 | ``` 29 | 30 | Void 31 | ``` 32 | libxcb-devel xcb-proto xcb-util-devel xcb-util-wm-devel xcb-util-cursor-devel xcb-util-keysyms-devel 33 | ``` 34 | 35 | Debian/Ubuntu 36 | ``` 37 | libxcb-randr0-dev libxcb-util-dev libxcb-icccm4-dev libxcb-cursor-dev libxcb-keysyms1-dev libxcb-res0-dev 38 | ``` 39 | 40 | Other systems should have packages with similar names. 41 | 42 | 43 | To compile run 44 | ``` bash 45 | make 46 | ``` 47 | 48 | Edit `config.h` if needed, then run *(as root if needed)* 49 | ``` bash 50 | make install 51 | ``` 52 | 53 | If at any time you want to uninstall, run 54 | ``` bash 55 | make uninstall 56 | ``` 57 | 58 | ### Updating 59 | 60 | In order to update dk when built from source you can run 61 | 62 | ``` bash 63 | cd dk 64 | git fetch 65 | git reset --hard HEAD 66 | git merge '@{u}' 67 | make 68 | sudo make install 69 | ``` 70 | 71 | ### Keybinds 72 | 73 | As mentioned above dk has no keybind support so you'll need a program like 74 | `sxhkd`, `xbindkeys`, etc. to launch programs and control the window manager. 75 | We'll install sxhkd because the example config uses it. 76 | 77 | Arch/Debian/Ubuntu/Void/etc. 78 | ``` bash 79 | sxhkd 80 | ``` 81 | 82 | ### Usage 83 | 84 | To start dk you can add the following to your `~/.xinitrc` 85 | ``` bash 86 | exec dk 87 | ``` 88 | 89 | Optionally copy the example dkrc and/or sxhkdrc to `~/.config/dk/` 90 | ``` bash 91 | mkdir -p ~/.config/dk 92 | cp /usr/local/share/doc/dk/dkrc ~/.config/dk/ 93 | cp /usr/local/share/doc/dk/sxhkdrc ~/.config/dk/ 94 | ``` 95 | 96 | ### Configuration 97 | 98 | There are example `dkrc` and `sxhkdrc` files in `doc/` or 99 | `/usr/local/share/doc/dk` after installation. 100 | 101 | dk looks for an rc file in the following order 102 | ``` bash 103 | $DKRC # user specified location 104 | $HOME/.config/dk/dkrc # default location 105 | ``` 106 | 107 | and tries to run it, **it must be executable in order for this to happen**. 108 | 109 | Advanced changes and configuration like new layouts, callbacks, or commands 110 | can be done by copying the default config header `config.def.h` to `config.h`, 111 | editing it and recompiling. This file isn't tracked by git so you can keep your 112 | configuration and avoid conflicts when pulling new updates. 113 | 114 | ### dkcmd 115 | Most of your interaction with the window manager will be using `dkcmd` 116 | which writes one or more commands into the socket where it is then read 117 | and parsed by the window manager *(see Commands section below)*. 118 | 119 | dkcmd accepts one flag with an optional file argument 120 | - `-p` Pretty format JSON input from passed file or STDIN and print on STDOUT. 121 | 122 | 123 | ```bash 124 | dkcmd status type=bar num=1 | dkcmd -p 125 | 126 | # or 127 | dkcmd -p output.json 128 | ``` 129 | 130 | ### Syntax Outline 131 | The commands have a very basic syntax and parsing, the input is broken 132 | down into smaller pieces *(tokens)* which are then passed to the matching 133 | keyword function, otherwise an error is returned. 134 | 135 | Tokens are delimited by one or more: 136 | 137 | - whitespace *(space or tab)* 138 | 139 | - quotation mark *(`'` or `"`)* 140 | 141 | - equal sign *(`=`)* 142 | 143 | This means the following inputs are all equivalent. 144 | ``` 145 | setting=value 146 | setting value 147 | setting="value" 148 | setting = 'value' 149 | setting "value" 150 | setting "value" 151 | ``` 152 | 153 | and result in two tokens: `setting` and `value` 154 | 155 | --- 156 | 157 | Quotation exists as a way to preserve whitespace and avoid interpretation by the shell, 158 | otherwise we have no way of determining whether an argument is a continuation of the 159 | previous or the beginning of the next. Consider the following 160 | ``` bash 161 | title="^open files$" 162 | ``` 163 | 164 | If the value being matched has quotes in it, they can be escaped or strong quoted 165 | ``` bash 166 | title="^\"preserved quotes\"$" 167 | title='^"preserved quotes"$' 168 | ``` 169 | 170 | --- 171 | 172 | For various commands dk will expect a certain data type or format to be given. 173 | 174 | - string: normal plain text, must be less than 256 characters. 175 | 176 | - boolean: `true`, `false`, `1`, or `0`. 177 | 178 | - hex: `(0x/#)XXXXXXXX`, used for window ids 179 | 180 | - integer: `(+/-)1`, if it is preceded by a sign it is considered relative. 181 | 182 | - float: `(+/-)0.1`, same as integer but must contain a decimal value. 183 | 184 | - colour: `(0x/#)[AA]RRGGBB`, hex value, if no alpha channel is given the colour is opaque. 185 | 186 | --- 187 | 188 | ### Commands 189 | ``` bash 190 | dkcmd COMMAND 191 | ``` 192 | #### WM 193 | 194 | - `exit` exit dk. 195 | - `restart` re-execute dk. 196 | 197 | #### Ws and Mon 198 | `mon` and `ws` operate on monitors and workspaces respectively. 199 | 200 | - `CLIENT` (hex/string) The window id in hex or class string, if unspecified the active window is used. 201 | - `TARGET` (integer/string) Name or number of the workspace or monitor to target or strings 202 | - `next` relative forward 203 | - `prev` relative backward 204 | - `last` last viewed 205 | - `nextne` next non-empty 206 | - `prevne` previous non-empty 207 | 208 | ``` bash 209 | ws [SUBCOMMAND] [CLIENT] TARGET 210 | mon [SUBCOMMAND] [CLIENT] TARGET 211 | ``` 212 | 213 | ###### Subcommands 214 | `view` View the TARGET, default if no subcommand is given. 215 | ``` bash 216 | ws view TARGET 217 | ws TARGET 218 | ``` 219 | --- 220 | 221 | `send` Send CLIENT to the TARGET. 222 | ``` bash 223 | mon send [CLIENT] TARGET 224 | ``` 225 | --- 226 | 227 | `follow` Follow CLIENT to the TARGET. 228 | ``` bash 229 | ws follow [CLIENT] TARGET 230 | ``` 231 | 232 | #### Rule 233 | `rule` operates on window rules. 234 | 235 | - `MATCH` one or more regex strings to be used when matching window properties. 236 | - `SETTING` one or more window setting to be applied when a matched window is encountered. 237 | 238 | ``` bash 239 | rule [SUBCOMMAND] MATCH SETTING 240 | ``` 241 | 242 | ###### Subcommands 243 | 244 | `apply` applies RULE to all matching windows, if RULE is `*` apply all rules and MATCH is ignored. 245 | ``` bash 246 | rule apply RULE [MATCH] 247 | ``` 248 | --- 249 | 250 | `remove` removes RULE, if RULE is `*` remove all rules and MATCH is ignored. 251 | 252 | ``` bash 253 | rule remove RULE [MATCH] 254 | ``` 255 | 256 | ###### Settings 257 | 258 | `class` `instance` `title` `type` (string) regex to match the window class, instance, title, and 259 | type respectively *(may be prefixed with match_ for clarity)*. Regex matching is always done **case insensitive** 260 | with extended regex mode enabled. 261 | ``` bash 262 | rule [SUBCOMMAND] class="^firefox$" instance="^navigator$" title="^mozilla firefox$" type=dialog [SETTING] 263 | ``` 264 | 265 | 266 | `type` currently only supports `dialog` and `splash` windows, all others are treated as normal windows. 267 | 268 | --- 269 | 270 | `ws` (integer/string) determine what workspace the window should be on. 271 | ``` bash 272 | rule MATCH ws=1 # using index 273 | rule MATCH ws=term # using name 274 | ``` 275 | --- 276 | 277 | `mon` (integer/string) determine what monitor the window should be on. 278 | ``` bash 279 | rule MATCH mon=1 # using index 280 | rule MATCH mon=HDMI-A-0 # using name 281 | ``` 282 | --- 283 | 284 | `x` `y` `w` `width` `h` `height` `bw` `border_width` (integer/string) determine the window location and size. 285 | 286 | - `x` change the x coordinate, can be an integer or one of the following. 287 | - `center left` and `right` gravitate on the x coordinate. 288 | 289 | - `y` change the y coordinate, can be an integer or one of the following. 290 | - `center top` and `bottom` gravitate on the y coordinate. 291 | 292 | - `w` `width` change the window width. 293 | - `h` `height` change the window height. 294 | - `bw` `border_width` change the window border width. 295 | 296 | ``` bash 297 | rule MATCH x=20 y=100 w=1280 h=720 bw=0 # using absolute values 298 | rule MATCH x=center y=center w=1280 h=720 bw=0 # using gravities 299 | ``` 300 | --- 301 | 302 | `callback` (string) determine a callback function to be invoked on window open and close. 303 | These are defined in the config header and compiled into the source, one example is provided. 304 | ``` bash 305 | rule MATCH callback=albumart 306 | ``` 307 | --- 308 | 309 | `float` `stick` (boolean) determine if the window should be floating or stick respectively. 310 | ``` bash 311 | rule MATCH float=true stick=true 312 | ``` 313 | --- 314 | 315 | `ignore_cfg` (boolean) determine if the window should ignore configure request 316 | events (size or location changes). 317 | ``` bash 318 | rule MATCH ignore_cfg=true 319 | ``` 320 | --- 321 | 322 | `ignore_msg` (boolean) determine if the window should ignore client message 323 | window activation events (grabbing focus). 324 | ``` bash 325 | rule MATCH ignore_msg=true 326 | ``` 327 | --- 328 | 329 | `focus` (boolean) determine if the window should be focused and `view` it's workspace. 330 | If `mon` is also set it will be activated first before viewing the workspace. 331 | ``` bash 332 | rule MATCH focus=true 333 | ``` 334 | --- 335 | 336 | `terminal` (boolean) determine if the window should be considered a terminal for 337 | absorbing other windows and not being absorbed itself. 338 | ``` bash 339 | rule MATCH terminal=true 340 | ``` 341 | --- 342 | 343 | `no_absorb` (boolean) determine if the window should never absorb other windows. 344 | ``` bash 345 | rule MATCH no_absorb=true 346 | ``` 347 | --- 348 | 349 | `scratch` (boolean) determine if the window should be in the scratchpad. 350 | ``` bash 351 | rule MATCH scratch=true 352 | ``` 353 | --- 354 | 355 | #### Set 356 | `set` operates on workspace or global configuration settings. 357 | 358 | - `SETTING` one or more settings to be changed. 359 | - `WS` the workspace which subcommand should apply to, if unspecified the current is used. 360 | `_` is a special workspace used to define default values for new workspaces which 361 | haven't been created yet. 362 | 363 | ``` bash 364 | set [WS] SETTING 365 | set ws=_ [apply] SETTING 366 | ``` 367 | 368 | ###### Settings 369 | `numws` (integer) change the number of workspaces to allocate. 370 | ``` bash 371 | set numws=10 372 | ``` 373 | --- 374 | 375 | `name` (string) change the WS name. 376 | ``` bash 377 | set ws=1 name="term" 378 | ``` 379 | --- 380 | 381 | `static_ws` (boolean) disable dynamic workspaces for multi-head systems. 382 | ``` bash 383 | set static_ws=false 384 | ``` 385 | --- 386 | 387 | `mon` (integer/string) change which monitor WS should be on (requires `static_ws=true`). 388 | ``` bash 389 | set ws=1 mon=HDMI-A-0 390 | set ws=1 mon=1 391 | ``` 392 | --- 393 | 394 | `master` `stack` (integer) change the number of windows to occupy the master area (tile layout). 395 | ``` bash 396 | set [WS] stack 3 # absolute values have no signs 397 | set [WS] master +1 stack -1 # relative change with signed integers (+/-) 398 | ``` 399 | --- 400 | 401 | `msplit` `ssplit` (float) change the workspace master or stack split ratios respectively. 402 | ``` bash 403 | set [WS] msplit +0.1 404 | set [WS] ssplit 0.55 405 | ``` 406 | --- 407 | 408 | `gap` (integer) change the workspace gap width. 409 | ``` bash 410 | set [WS] gap 10 411 | ``` 412 | --- 413 | 414 | `tile_hints` (boolean) obey size hints in tiled layouts. 415 | ``` bash 416 | set tile_hints=true 417 | ``` 418 | --- 419 | 420 | `tile_tohead` (boolean) place new windows at the head of the list in tiled layouts. 421 | ``` bash 422 | set tile_tohead=true 423 | ``` 424 | --- 425 | 426 | `smart_gap` (boolean) whether gaps are disabled on workspaces with only one tiled window. 427 | ``` bash 428 | set smart_gap=true 429 | ``` 430 | --- 431 | 432 | `smart_border` (boolean) whether borders are disabled on workspaces with only one tiled window. 433 | ``` bash 434 | set smart_border=true 435 | ``` 436 | --- 437 | 438 | `focus_urgent` (boolean) focus windows that request it. 439 | ``` bash 440 | set focus_urgent=true 441 | ``` 442 | --- 443 | 444 | `focus_open` (boolean) whether windows are focused when opened. 445 | ``` bash 446 | set focus_open=false 447 | ``` 448 | --- 449 | 450 | `focus_mouse` (boolean) whether window focus follows the mouse. 451 | ``` bash 452 | set focus_mouse=false 453 | ``` 454 | --- 455 | 456 | `obey_motif` (boolean) whether to obey motif hints for borders. 457 | ``` bash 458 | set obey_motif=false 459 | ``` 460 | --- 461 | 462 | `win_minxy` (integer) amount of window (in pixels) to be kept on the screen when moving. 463 | ``` bash 464 | set win_minxy=20 465 | ``` 466 | --- 467 | 468 | `win_minwh` (integer) minimum window size. 469 | ``` bash 470 | set win_minwh=50 471 | ``` 472 | --- 473 | 474 | `apply` when changing the default `_` workspace apply settings to existing real workspaces. 475 | ``` bash 476 | set ws=_ apply SETTING 477 | ``` 478 | --- 479 | 480 | `layout` (string) change the workspace window layout. 481 | 482 | - `tile` default tile layout 483 | - `rtile` tile layout with master area on the right 484 | - `mono` windows arranged maximized and stacked 485 | - `grid` all windows try to occupy equal space 486 | - `spiral` windows shrink by 1/2 towards the center of the screen 487 | - `dwindle` windows shrink by 1/2 towards the bottom right of the screen 488 | - `tstack` windows are grouped into a master area on the bottom and 489 | one horizontal stack area on top. 490 | - `none` floating layout, windows can be freely moved and resized. 491 | - `cycle` switch between available layouts. 492 | 493 | ``` bash 494 | set [WS] layout mono 495 | ``` 496 | --- 497 | 498 | `border` change the window border sizes and colours. 499 | 500 | - `w` `width` (integer) change the overall window border width. 501 | - `ow` `outer` `outer_width` (integer) change the outer border width (greater than 0 makes double borders). 502 | - `colour` `color` (string) change the border (overall and outer) colour for various window states. 503 | - `f` `focus` (colour) the active window border overall colour. 504 | - `r` `urgent` (colour) urgent window border overall colour. 505 | - `u` `unfocus` (colour) normal window border overall colour. 506 | - `of` `outer_focus` (colour) the active window outer border colour. 507 | - `or` `outer_urgent` (colour) urgent window outer border colour. 508 | - `ou` `outer_unfocus` (colour) normal window outer border colour. 509 | 510 | ``` bash 511 | set border w=5 ow=3 colour f='#6699cc' u='#444444' r='#ee5555' of='#222222' ou='#222222' or='#222222' 512 | ``` 513 | --- 514 | 515 | `pad` change the workspace padding. 516 | 517 | - `l` `left` (integer) change the workspace left side padding. 518 | - `r` `right` (integer) change the workspace right side padding. 519 | - `t` `top` (integer) change the workspace top padding. 520 | - `b` `bottom` (integer) change the workspace bottom padding. 521 | 522 | ``` bash 523 | set [WS] pad l=50 r=50 t=50 b=50 524 | ``` 525 | --- 526 | 527 | `mouse` change the mouse binds for move and resize (global, does not take a workspace). 528 | 529 | - `mod` (string) change the modifier used in combination with move resize buttons. 530 | - `alt` `mod1` Alt key (default). 531 | - `super` `mod4` Win key. 532 | - `ctrl` `control` Ctrl key. 533 | 534 | - `move` `resize` (string) change the button used for move and resize respectively. 535 | - `button1` left mouse button. 536 | - `button2` right mouse button. 537 | - `button3` middle mouse button. 538 | 539 | ``` bash 540 | set mouse move=button1 resize=button2 mod=mod1 541 | ``` 542 | --- 543 | 544 | #### Win 545 | `win` operates on windows. 546 | 547 | - `CLIENT` (hex/string) the window id in hex or class string, if unspecified the current window is used. 548 | 549 | ``` bash 550 | win [CLIENT] ACTION 551 | ``` 552 | 553 | ###### Settings 554 | `cycle` cycle windows in place. 555 | ``` bash 556 | win cycle 557 | ``` 558 | --- 559 | 560 | `float` change the window floating state. 561 | ``` bash 562 | win [CLIENT] float 563 | win [CLIENT] float=false 564 | ``` 565 | --- 566 | 567 | `full` change the window fullscreen state. 568 | ``` bash 569 | win [CLIENT] full 570 | ``` 571 | --- 572 | 573 | `fakefull` change the window fake fullscreen state (allow moving, resizing, and tiling when fullscreen). 574 | ``` bash 575 | win [CLIENT] fakefull 576 | ``` 577 | --- 578 | 579 | `stick` change the window sticky state. 580 | ``` bash 581 | win [CLIENT] stick 582 | ``` 583 | --- 584 | 585 | `swap` change the window between it's current location and master. 586 | ``` bash 587 | win [CLIENT] swap 588 | ``` 589 | --- 590 | 591 | `kill` close the window. 592 | ``` bash 593 | win [CLIENT] kill 594 | ``` 595 | --- 596 | 597 | `focus` (integer/string) change the focused window. 598 | 599 | - `next` focus the next window. 600 | - `prev` focus the previous window. 601 | 602 | ``` bash 603 | win CLIENT focus # focus window by id 604 | win focus next # focus the next window 605 | win focus +2 # focus two windows ahead 606 | ``` 607 | --- 608 | 609 | `scratch` view or hide a scratchpad window. 610 | 611 | - `pop` show a window in the scratch. 612 | - `push` hide a window in the scratch. 613 | 614 | With no other arguments 615 | - If there are window(s) in the scratch it will continue to pop them out until empty. 616 | - If there is a window on any workspace (other than the current workspace) 617 | that has been recently popped, it will be brought to the current 618 | workspace. If it's on the current workspace it is instead pushed. 619 | - If there are no window(s) in the scratch and no windows that have 620 | been there previously it will push the active window into the scratch. 621 | 622 | ``` bash 623 | win scratch 624 | win [CLIENT] scratch # same toggle behaviour but on the passed window 625 | win [CLIENT] scratch push # push the given window or the active window. 626 | ``` 627 | 628 | --- 629 | 630 | `mvstack` (integer/string) move a tiled window around the stack. 631 | 632 | - `up` move the tiled window up the stack. 633 | - `down` move the tiled window down the stack. 634 | 635 | ``` bash 636 | win [CLIENT] mvstack up 637 | ``` 638 | --- 639 | 640 | `resize` change the window size, location, and border width. 641 | 642 | - `x` change the x coordinate, can be an integer or one of the following. 643 | - `center left` and `right` gravitate on the x coordinate. 644 | 645 | - `y` change the y coordinate, can be an integer or one of the following. 646 | - `center top` and `bottom` gravitate on the y coordinate. 647 | 648 | - `w` `width` change the window width. 649 | - `h` `height` change the window height. 650 | - `bw` `border_width` change the window border width. 651 | 652 | ``` bash 653 | win [CLIENT] resize x=100 y=100 w=1280 h=720 bw=1 654 | win [CLIENT] resize x=center y=center w=1280 h=720 bw=1 655 | ``` 656 | --- 657 | 658 | #### Status 659 | `status` print status information as JSON to a file or stdout. 660 | 661 | ``` bash 662 | status [TYPE] [FILE] [NUM] 663 | ``` 664 | 665 | ###### Subcommands 666 | `type` (string) the type of status info to output and when to trigger. 667 | 668 | - `ws` output full workspace info - triggers on workspace change. 669 | - `win` output current window title - triggers on window or title change. 670 | - `layout` output current layout name - triggers on layout change. 671 | - `bar` identical output to `ws` except - triggers on all changes. 672 | - `full` output full wm and client state - triggers on all changes. 673 | 674 | ``` bash 675 | status type=ws [FILE] [NUM] 676 | ``` 677 | --- 678 | 679 | `file` (string) the location of the status file. 680 | 681 | ``` bash 682 | status file=/tmp/dk.status [TYPE] [NUM] 683 | ``` 684 | --- 685 | 686 | `num` (integer) the number of times to output, -1 is infinite and default if not specified. 687 | 688 | ``` bash 689 | status [TYPE] [FILE] # output forever 690 | status num=1 [TYPE] [FILE] # output once 691 | ``` 692 | 693 | ### Todo 694 | 695 | - Simplification and code quality improvement. 696 | - See ISSSUE.md for current problems and possible additions. 697 | - `grep -n 'TODO' src/*` will give some code I'm not happy with. 698 | 699 | ### Contributing 700 | 701 | I'm very open to contributions or ideas, please feel free to email me or open 702 | an issue/PR. For myself I have various builds that can help finding issues or 703 | provide some insight. These include: 704 | 705 | 706 | stderr debug output 707 | ``` bash 708 | make debug 709 | ``` 710 | 711 | debug output and function calls 712 | ``` bash 713 | make fdebug 714 | ``` 715 | 716 | don't strip debug symbols *(for gdb, valgrind, etc.)*. 717 | ``` bash 718 | make nostrip 719 | ``` 720 | 721 | ### Credits 722 | 723 | See the LICENSE file for a list of authors/contributors. 724 | 725 | Huge thanks to Kjetil Molteberg *(@badkarma)* for the logo. 726 | 727 | Non contributors that I owe a huge thanks to: 728 | - [dwm](https://dmw.suckless.org) 729 | - [bspwm](https://github.com/baskerville/bspwm) 730 | - [xmonad](https://xmonad.org) 731 | - [evilwm](http://www.6809.org.uk/evilwm/) 732 | - [monsterwm-xcb](https://github.com/Cloudef/monsterwm-xcb) 733 | - [frankenwm](https://github.com/sulami/FrankenWM). 734 | 735 | -------------------------------------------------------------------------------- /dk.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=dk 3 | Comment=dk tiling window manager 4 | Exec=dk 5 | Type=XSession 6 | -------------------------------------------------------------------------------- /dk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natemaia/dk/f59cc3b9bf1d235607fb5007b34efbc0da36e5b8/dk.png -------------------------------------------------------------------------------- /doc/dk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natemaia/dk/f59cc3b9bf1d235607fb5007b34efbc0da36e5b8/doc/dk.png -------------------------------------------------------------------------------- /doc/dkrc: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # example dkrc to provide a close-to-default setup and 4 | # show some basic command usage with error reporting 5 | 6 | # determine where to place the log file 7 | logfile="$HOME/.dkrc.log" 8 | [ -d "$HOME/.local/share/xorg" ] && logfile="$HOME/.local/share/xorg/dkrc.log" 9 | : > "$logfile" 10 | 11 | # load sxhkd for keybinds 12 | pgrep sxhkd || sxhkd -c "$HOME/.config/dk/sxhkdrc" & 13 | 14 | # spawn a scratchpad terminal if not already (see sxhkdrc and rules for binds/setup) 15 | # pgrep -f "st -c scratchpad" || st -c scratchpad & 16 | 17 | # adjust border widths based on the DPI of the monitor 18 | px=$(xrandr | grep ' connected' | tail -n1 | grep -o '[0-9]\+x[0-9]\+' | cut -d'x' -f2) 19 | mm=$(xrandr | grep ' connected' | tail -n1 | grep -o '[0-9]\+mm' | tail -n1 | sed 's/mm//') 20 | dpi=$(( (px / mm) * 25 )) 21 | 22 | if [ $dpi -ge 140 ]; then 23 | border_width=5 24 | border_outer_width=3 25 | elif [ $dpi -ge 120 ]; then 26 | border_width=4 27 | border_outer_width=2 28 | else 29 | border_width=2 30 | border_outer_width=1 31 | fi 32 | 33 | { # compound command to redirect all output 34 | 35 | # workspace settings 36 | # ------------------------ 37 | 38 | # initialize 6 workspaces (1-6) (default: 1/monitor) 39 | dkcmd set numws=6 40 | 41 | # default workspace '_' values used when allocating new workspaces 42 | # can be applied to all existing workspaces when passed 'apply' after ws=_ 43 | dkcmd set ws=_ apply layout=tile master=1 stack=3 gap=0 msplit=0.5 ssplit=0.5 44 | 45 | # use grid layout, padding, and gaps on last workspace 46 | dkcmd set ws=6 layout=grid pad left=200 right=200 top=100 bottom=100 gap=50 47 | 48 | # change workspace names (default: number == name -> 1:1, 2:2, 3:3....) 49 | # dkcmd set \ 50 | # ws=1 name="edit" \ 51 | # ws=2 name="web" \ 52 | # ws=3 name="😀" \ 53 | # ws=4 name="😠" \ 54 | # ws=5 name="5" \ 55 | # ws=6 name="6" \ 56 | 57 | # enable static workspaces assigned to monitors (relevant for multiple monitors) 58 | # aside - 59 | # If you come from this workspace model, try embracing the default behaviour 60 | # and access to all workspaces from any monitor, you might end up liking it :) 61 | # 62 | # this could be done in the names section above if being used 63 | # 64 | # mon1='DisplayPort-0' 65 | # mon2='HDMI-A-0' 66 | # dkcmd set static_ws=true \ 67 | # ws=1 mon=$mon1 \ 68 | # ws=2 mon=$mon1 \ 69 | # ws=3 mon=$mon1 \ 70 | # ws=4 mon=$mon2 \ 71 | # ws=5 mon=$mon2 \ 72 | # ws=6 mon=$mon2 \ 73 | 74 | 75 | # global settings 76 | # --------------------- 77 | 78 | # focus windows when receiving activation and enable focus-follows-mouse 79 | dkcmd set focus_open=true focus_urgent=true focus_mouse=true 80 | 81 | # place clients at the tail and ignore size hints on tiled windows 82 | dkcmd set tile_tohead=0 tile_hints=false 83 | 84 | # minimum width/height for resizing, and minimum allowed on-screen when moving 85 | dkcmd set win_minwh=50 win_minxy=10 86 | 87 | # disable gaps and borders in single window layouts 88 | dkcmd set smart_gap=true smart_border=true 89 | 90 | # define mouse mod and move/resize buttons 91 | dkcmd set mouse mod=alt move=button1 resize=button3 92 | 93 | # obey motif border hints on windows that draw their own (steam, easyeffects, etc.) 94 | dkcmd set obey_motif=true 95 | 96 | 97 | # borders 98 | # --------- 99 | 100 | # traditional 101 | # set border width and colour for each window state 102 | # dkcmd set border width=$border_width colour focus='#6699cc' unfocus='#000000' urgent='#ee5555' 103 | 104 | # alternative 105 | # enable split borders and colours, width is overall width, outer_width consumes some of width. 106 | # outer_width must be less than width, outer_width of 0 will be single borders 107 | dkcmd set border width=$border_width outer_width=$border_outer_width \ 108 | colour \ 109 | focus='#6699cc' \ 110 | unfocus='#444444' \ 111 | urgent='#ee5555' \ 112 | outer_focus='#222222' \ 113 | outer_unfocus='#222222' \ 114 | outer_urgent='#222222' 115 | 116 | 117 | # window rules 118 | # -------------- 119 | 120 | # rule class, instance, and title regex are *always* case INSENSITIVE 121 | 122 | # open window(s) on a specific workspace (assigned monitor) 123 | # dkcmd rule class="^gimp$" ws=2 124 | 125 | # open window(s) on a monitor by number or name (active workspace on monitor) 126 | # dkcmd rule class="^chromium$" mon="HDMI-A-0" 127 | 128 | # open window(s) and use a callback function (user defined in config.h) 129 | # we also ignore_cfg=true to stop the window from being resized on it's own from events 130 | # eg. mpv --x11-name=albumart /path/to/media 131 | # dkcmd rule class="^mpv$" instance="^albumart$" float=true ignore_cfg=true callback=albumart bw=0 132 | 133 | # open window(s) in a floating state 134 | dkcmd rule class="^(pavucontrol|transmission-gtk|steam|lxappearance)$" float=true 135 | 136 | # open window(s) with a specific geometry and coordinates (floating only!) 137 | # dkcmd rule class="^google-chrome$" title="^open files$" float=true w=1280 h=720 138 | 139 | # open window(s) with ignore_msg=true to avoid focus being grabbed and changing workspace 140 | # dkcmd rule class="^TelegramDesktop$" ignore_msg=true 141 | 142 | # define some terminals and allow them to be absorbed by spawned windows 143 | dkcmd rule class="^(st|st-256color|urxvt|kitty|alacritty|xterm|xterm-256color)$" terminal=true 144 | 145 | # set a window to never absorb other windows, like the xev event tester 146 | dkcmd rule title="^Event Tester$" no_absorb=true 147 | 148 | # send a window to the scratchpad 149 | dkcmd rule class="^scratchpad$" scratch=true 150 | 151 | # focus window and workspace on opening 152 | # dkcmd rule class="^firefox$" ws=1 focus=true 153 | 154 | # update or remove an existing rule with the same match patterns 155 | # dkcmd rule class="^firefox$" mon="HDMI-A-0" 156 | # dkcmd rule remove class="^firefox$" 157 | 158 | # apply current rule set to all existing windows (used mainly for WM restart) 159 | dkcmd rule apply '*' 160 | 161 | # delete all rules 162 | # dkcmd rule remove '*' 163 | 164 | } >> "$logfile" 2>&1 # append responses 165 | 166 | # inform of any errors in a notification 167 | if grep -q 'error:' "$logfile"; then 168 | hash notify-send && notify-send -t 0 -u critical "dkrc has errors" \ 169 | "$(awk '/error:/ {sub(/^error: /, ""); gsub(/= 140 )); then 21 | font="-xos4-terminus-medium-r-normal--24-240-72-72-c-120-iso10646-1" 22 | elif (( dpi >= 120 )); then 23 | font="-xos4-terminus-medium-r-normal--18-240-72-72-c-120-iso10646-1" 24 | elif (( dpi >= 100 )); then 25 | font="-xos4-terminus-medium-r-normal--14-240-72-72-c-120-iso10646-1" 26 | fi 27 | 28 | # mimic dwm style layout symbols 29 | typeset -A layouts=( 30 | [tile]="[]=" 31 | [rtile]="=[]" 32 | [mono]="[M]" 33 | [none]="><>" 34 | [grid]="###" 35 | [spiral]="(@)" 36 | [dwindle]="[\\]" 37 | [tstack]="F^F" 38 | ) 39 | 40 | clock() 41 | { 42 | if [[ $1 ]]; then 43 | date +"T%%{A1:$1:} %a %H:%M $sep%%{A}" 44 | else 45 | date +"T %a %H:%M $sep" 46 | fi 47 | 48 | # sync up the clock to the minute mark 49 | min=$(date +"%M") 50 | while [[ $(date +"%M") == "$min" ]]; do 51 | sleep 1 52 | done 53 | 54 | if [[ $1 ]]; then 55 | while :; do 56 | date +"T%%{A1:$1:} %a %H:%M $sep%%{A}" 57 | sleep 60 58 | done 59 | else 60 | while :; do 61 | date +"T %a %H:%M $sep" 62 | sleep 60 63 | done 64 | fi 65 | } 66 | 67 | battery() 68 | { 69 | lvl=$(acpi --battery 2>/dev/null | grep -v 'Unknown\| 0%' | cut -d, -f2 | tr -d '[:space:]') 70 | [[ ! $lvl ]] && return # no battery so we don't need to continue 71 | 72 | if [[ $1 ]]; then 73 | while :; do 74 | bat="$(acpi --battery 2>/dev/null | grep -v 'Unknown\| 0%' | cut -d, -f2 | tr -d '[:space:]')" 75 | if [[ $lastb != "$bat" ]]; then 76 | lastb="$bat" 77 | printf 'B%s\n' "%{A1:$1:} Bat: ${bat} %{A}${sep}" 78 | fi 79 | sleep 120 80 | done 81 | else 82 | while :; do 83 | bat="$(acpi --battery 2>/dev/null | grep -v 'Unknown\| 0%' | cut -d, -f2 | tr -d '[:space:]')" 84 | if [[ $lastb != "$bat" ]]; then 85 | lastb="$bat" 86 | printf 'B%s\n' " Bat: ${bat} ${sep}" 87 | fi 88 | sleep 120 89 | done 90 | fi 91 | } 92 | 93 | volume() 94 | { 95 | if [[ $1 ]]; then 96 | while :; do 97 | vol="$(pamixer --get-volume-human)" 98 | if [[ $lastv != "$vol" ]]; then 99 | lastv=$vol 100 | printf 'V%s\n' "%{A1:$1:} vol: $vol %{A}${sep}" 101 | fi 102 | sleep 1 103 | done 104 | else 105 | while :; do 106 | vol="$(pamixer --get-volume-human)" 107 | if [[ $lastv != "$vol" ]]; then 108 | lastv=${vol} 109 | printf 'V%s\n' " vol: ${vol} ${sep}" 110 | fi 111 | sleep 1 112 | done 113 | fi 114 | } 115 | 116 | network() 117 | { 118 | check() 119 | { 120 | if hash nm-online > /dev/null 2>&1 && [[ $(systemctl is-active NetworkManager.service) == "active" ]]; then 121 | nm-online > /dev/null 2>&1 122 | else 123 | ping -qc1 'archlinux.org' > /dev/null 2>&1 124 | fi 125 | } 126 | 127 | if [[ $1 ]]; then 128 | while :; do 129 | printf 'N%s\n' "%{A1:$1:} disconnected %{A}${sep}" 130 | until check; do 131 | sleep 30 132 | done 133 | printf 'N%s\n' "%{A1:$1:} connected %{A}${sep}" 134 | while :; do 135 | sleep 300 136 | check || break 137 | done 138 | done 139 | else 140 | while :; do 141 | printf 'N%s\n' " disconnected ${sep}" 142 | until check; do 143 | sleep 30 144 | done 145 | printf 'N%s\n' " connected ${sep}" 146 | while :; do 147 | sleep 300 148 | check || break 149 | done 150 | done 151 | fi 152 | } 153 | 154 | workspaces() 155 | { 156 | # these will be filled out once we eval each line 157 | typeset name="" title="" layout="" focused=false active=false 158 | 159 | WS="" 160 | while read -r l; do 161 | eval "$l" 162 | if [[ $l =~ ^title=* ]]; then 163 | # default foreground, background, and underline colours 164 | # changing the foreground and underline based on 165 | # the active and focused state 166 | f="$fg" b="$bg" u="$fg"; 167 | $active && u="$hi" 168 | if $focused; then 169 | # clicking the layout symbol will cycle the layout 170 | lyt="%{A:dkcmd set layout cycle:}${layouts[$layout]}%{A}" 171 | WIN="${title:0:50}" 172 | f="$hi" 173 | fi 174 | # clicking on a workspace name will view it 175 | WS="$WS%{F$f}%{B$b}%{+u}%{U$u}%{A:dkcmd ws $name:} $name %{A}%{-u}%{B-}%{F-}" 176 | fi 177 | # turn the dk JSON output into lines that can be `eval`ed one by one, 178 | # filling out the following fields: name, focused, active, layout, title 179 | done < <(dkcmd -p <<< "$1" | sed '/.*\[\|]/d; /[{}],\?/d; s/^\s*\|,$//g; /"monitor":\|"number":\|"id":/d; s/"\(.*\)": /\1=/; s/\$(/\\\$(/g; s/`/\\`/g') 180 | WS="$WS $sep $lyt" 181 | } 182 | 183 | parsefifo() 184 | { 185 | # globals to simplify the workspaces call 186 | declare -g WS WIN 187 | 188 | local time vol bat net 189 | 190 | while read -r line; do 191 | case $line in 192 | T*) time="${line#?}" ;; 193 | V*) vol="${line#?}" ;; 194 | B*) bat="${line#?}" ;; 195 | N*) net="${line#?}" ;; 196 | '{'*) workspaces "$line" ;; 197 | esac 198 | printf "%s\n" "%{l}$sep ${WS}%{c}${WIN}%{r}${net}${bat}${vol}${time}" 199 | done 200 | } 201 | 202 | # kill the process and cleanup if we exit or get killed 203 | trap "trap - TERM; rm -f '$fifo'; kill 0" INT TERM QUIT EXIT PIPE 204 | 205 | # make the fifo 206 | [ -e "$fifo" ] && rm "$fifo" 207 | mkfifo "$fifo" 208 | 209 | # here we dump info into the FIFO, order does not matter things are parsed 210 | # out using the first character of the line. Click commands for left button 211 | # can be added by passing an argument containing the command (like below) 212 | # 213 | # comment a line to remove the "module" 214 | network '' > "$fifo" & 215 | clock 'gsimplecal' > "$fifo" & 216 | battery '' > "$fifo" & 217 | volume 'pavucontrol' > "$fifo" & 218 | dkcmd status type=bar > "$fifo" & 219 | 220 | # run the pipeline 221 | if [[ $1 == '-b' ]]; then 222 | parsefifo < "$fifo" | lemonbar -b -a 32 -u $ul -B "$bg" -F "$fg" -f "$font" | sh; 223 | else 224 | parsefifo < "$fifo" | lemonbar -a 32 -u $ul -B "$bg" -F "$fg" -f "$font" | sh 225 | fi 226 | 227 | # vim:ft=sh:fdm=marker:fmr={,} 228 | -------------------------------------------------------------------------------- /doc/scripts/dk.conkyrc: -------------------------------------------------------------------------------- 1 | # example for using conky with dk - show the time and rotating arrows 2 | 3 | conky.config = { 4 | own_window = true, 5 | own_window_type = 'desktop', 6 | own_window_transparent = true, 7 | gap_x = 10, 8 | gap_y = 10, 9 | border_width = 0, 10 | minimum_height = 10, 11 | use_xft = true, 12 | font = 'DejaVu Sans:size=20', 13 | double_buffer = true, 14 | update_interval = 1, 15 | }; 16 | 17 | conky.text = [[ 18 | ${time %H:%M }\ 19 | ${if_match ${exec echo $(( $(date +%S | sed 's/^0\([0-9]\)$/\1/') % 4 ))} == 0} ^ ${endif}\ 20 | ${if_match ${exec echo $(( $(date +%S | sed 's/^0\([0-9]\)$/\1/') % 4 ))} == 1} > ${endif}\ 21 | ${if_match ${exec echo $(( $(date +%S | sed 's/^0\([0-9]\)$/\1/') % 4 ))} == 2} v ${endif}\ 22 | ${if_match ${exec echo $(( $(date +%S | sed 's/^0\([0-9]\)$/\1/') % 4 ))} == 3} < ${endif} 23 | ]]; 24 | -------------------------------------------------------------------------------- /doc/scripts/dzenws.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # show current workspace in a dzen2 window 4 | 5 | 6 | width=100 # enough to fit one character plus padding 7 | height=100 # roughly font size * 3, e.g. 36pt * 3 = 108 = 100px 8 | font="Liberation:size=36:style=bold" 9 | 10 | if ! hash dzen2 >/dev/null 2>&1; then 11 | echo "requires dzen2 installed" >&2 12 | exit 1 13 | fi 14 | 15 | stat=$(dkcmd status type=full num=1) 16 | 17 | # get current monitor width and height 18 | typeset -i w=0 h=0 19 | eval "$(awk '{ 20 | if (!s && $1 == "monitors:") { 21 | for (i = 1; i <= NF; i++) { 22 | if ($i ~ "*") { 23 | gsub(/\*|:[0-9]+/, ""); 24 | s = $i; 25 | } 26 | } 27 | } else if (s && $1 == s) { 28 | print "w="$5, "h="$6; 29 | exit; 30 | } 31 | }' <<< "$stat")" 32 | 33 | # determine current workspace number 34 | WS=$(awk '/^workspaces:/ { 35 | for (i = 1; i <= NF; i++) { 36 | if ($i ~ "*") 37 | print i - 1 38 | } 39 | }' <<< "$stat") 40 | 41 | # find center of the screen 42 | x=$(( (w / 2) - (width / 2) )) 43 | y=$(( (h / 2) - (height / 2) )) 44 | 45 | dzen2 -fn "$font" -p 1 -x $x -y $y -tw $width <<< "$WS" 46 | -------------------------------------------------------------------------------- /doc/scripts/gap_alphaborder.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # simple script to toggle dk border opacity based on the gap width 4 | # written by Nathaniel Maia, 2020 5 | 6 | # example usage bind with sxhkd 7 | # alt + {equal,minus,apostrophe} 8 | # /path/to/script {+5,-5} 9 | 10 | # first runs `dkcmd set gap width $1`, then checks to see what the current 11 | # gap setting is if it's >=threshold we add slight border transparency 12 | 13 | # does not have to be transparency were toggling, could be any setting where 14 | # multiple states may be desired depending on the situation, but this is just 15 | # an example 16 | 17 | # alpha value to use for when borders should be semi-transparent, 00 - ff 18 | # this will get changed to 'ff' when gap width is < threshold 19 | alpha='80' 20 | 21 | # gap width threshold for when to turn borders semi-transparent, 1 - N 22 | # 0 would be always setting them semi-transparent, which is wasteful 23 | thresh=10 24 | 25 | # border colours, #000000 - #ffffff 26 | typeset -A col=( 27 | [f]='#6699cc' 28 | [u]='#ee5555' 29 | [uf]='#444444' 30 | [of]='#222222' 31 | [ou]='#222222' 32 | [ouf]='#222222' 33 | ) 34 | 35 | if ! hash jq >/dev/null 2>&1; then 36 | echo "error: $0 requires 'jq' installed" 37 | exit 2 38 | fi 39 | 40 | if (( $# == 0 )); then 41 | echo "usage: $0 " 42 | exit 2 43 | fi 44 | 45 | 46 | currentwsgap() 47 | { 48 | dkcmd status type=full num=1 | jq '.workspaces | .[] | select(.focused==true) | .gap' 49 | } 50 | 51 | # store the gap width before and after changing 52 | old=$(currentwsgap) 53 | dkcmd set gap width "$1" 54 | ret=$? # if we don't cross the threshold this will be our exit code 55 | new=$(currentwsgap) 56 | 57 | # did we cross the threshold, if so we need to update the border colours alpha 58 | if (( (old >= thresh && new < thresh) || (old < thresh && new >= thresh) )); then 59 | (( new < thresh )) && alpha='ff' 60 | for i in "${!col[@]}"; do 61 | c="${col[$i]}" 62 | col[$i]="${c/\#/\#$alpha}" 63 | done 64 | dkcmd set border colour focus="${col[f]}" urgent="${col[u]}" unfocus="${col[uf]}" \ 65 | outer_focus="${col[of]}" outer_urgent="${col[ou]}" outer_unfocus="${col[ouf]}" 66 | else 67 | exit $ret 68 | fi 69 | -------------------------------------------------------------------------------- /doc/scripts/layouts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # print workspace layouts 4 | dkcmd status type=full num=1 | jq -r '.workspaces | .[] | [.layout] | .[]' 5 | -------------------------------------------------------------------------------- /doc/scripts/title.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # print the active window title 4 | dkcmd status type=win num=1 | sed 's/{.*:"\|"}$//g' 5 | -------------------------------------------------------------------------------- /doc/scripts/workspaces.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # print workspace numbers 4 | dkcmd status type=full num=1 | jq -r '.workspaces | .[] | [.number] | .[]' 5 | -------------------------------------------------------------------------------- /doc/scripts/wsborders.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # simple script to change dk border colour based on the workspace 4 | # written by Nathaniel Maia, 2021 5 | 6 | # example usage bind with sxhkd 7 | # alt + {_,shift + ,ctrl + }{1-6} 8 | # /path/to/script ws {view,send,follow} {1-6} 9 | 10 | if (( $# == 0 )); then 11 | echo "usage: $0 [action] " 12 | exit 2 13 | fi 14 | 15 | currentws() 16 | { 17 | dkcmd status type=ws num=1 | jq '.workspaces | .[] | select(.focused==true) | .number' 18 | } 19 | 20 | if [[ $1 =~ (view|send|follow) ]]; then 21 | [[ $1 == send ]] && { dkcmd ws "$1" "$2"; exit; } 22 | action="$1" 23 | shift 24 | else 25 | action="send" 26 | fi 27 | 28 | (( $1 == $(currentws) )) && exit 0 29 | 30 | case "$1" in 31 | [1-3]) 32 | typeset -A col=( 33 | [f]='#6699cc' 34 | [u]='#ee5555' 35 | [uf]='#444444' 36 | [of]='#222222' 37 | [ou]='#222222' 38 | [ouf]='#222222' 39 | ) 40 | ;; 41 | [4-6]) 42 | typeset -A col=( 43 | [f]='#ee5555' 44 | [u]='#6699cc' 45 | [uf]='#444444' 46 | [of]='#222222' 47 | [ou]='#222222' 48 | [ouf]='#222222' 49 | ) 50 | ;; 51 | esac 52 | 53 | dkcmd set border colour focus="${col[f]}" urgent="${col[u]}" unfocus="${col[uf]}" \ 54 | outer_focus="${col[of]}" outer_urgent="${col[ou]}" outer_unfocus="${col[ouf]}" || exit 55 | dkcmd ws "$action" "$1" 56 | -------------------------------------------------------------------------------- /doc/sxhkdrc: -------------------------------------------------------------------------------- 1 | # example sxhkdrc for use with dk 2 | ######################################################### 3 | 4 | # launcher 5 | alt + p 6 | dmenu_run 7 | 8 | # terminal 9 | alt + shift + Return 10 | st 11 | 12 | # screenshot and selection capture 13 | {_,alt + }@Print 14 | scrot {_,-s} 15 | 16 | # dedicated volume keys 17 | {XF86AudioRaiseVolume,XF86AudioLowerVolume} 18 | pamixer {-i,-d} 2 19 | 20 | # dedicated backlight keys 21 | {XF86MonBrightnessUp,XF86MonBrightnessDown} 22 | xbacklight {+10,-10} 23 | 24 | # alt volume keys 25 | alt + {Insert,Delete} 26 | pamixer {-i,-d} 2 27 | 28 | # reload sxhkd 29 | alt + shift + x 30 | pkill -USR1 -x sxhkd 31 | 32 | 33 | 34 | # quit dk 35 | alt + shift + q 36 | dkcmd exit 37 | 38 | # reload dkrc 39 | alt + shift + r 40 | $HOME/.config/dk/dkrc 41 | 42 | # restart dk 43 | alt + ctrl + shift + r 44 | dkcmd restart 45 | 46 | # focus next or previous window 47 | alt + {j,k} 48 | dkcmd win focus {next,prev} 49 | 50 | # close window, swap tiled window in/out of master, cycle tiled windows in place 51 | alt + {q,space,Tab} 52 | dkcmd win {kill,swap,cycle} 53 | 54 | # toggle fullscreen and fake fullscreen (enable manipulating fullscreen window) 55 | {_,alt + }F11 56 | dkcmd win {full,fakefull} 57 | 58 | # toggle floating, sticky, or scratchpad 59 | alt + shift + {space,s,u} 60 | dkcmd win {float,stick,scratch} 61 | # alternatively to scratch a window by class "scratchpad" 62 | #dkcmd win {float,stick,scratchpad scratch} 63 | 64 | # move window, signed (+/-) for relative changes, for tiled windows 65 | # y coord changes will move the window up/down the stack 66 | alt + shift + {h,j,k,l} 67 | dkcmd win resize {x=-20,y=+20,y=-20,x=+20} 68 | 69 | # resize window, signed (+/-) for relative changes 70 | alt + ctrl + {h,j,k,l} 71 | dkcmd win resize {w=-20,h=+20,h=-20,w=+20} 72 | 73 | # view, send, or follow to a workspace (by number) 74 | alt + {_,shift + ,ctrl + }{1-6} 75 | dkcmd ws {view,send,follow} {1-6} 76 | 77 | # view, send, or follow to the next, previous, last active, 78 | # next non-empty, or prev non-empty workspace 79 | alt + {_,shift + ,ctrl + }{bracketleft,bracketright,BackSpace,Left,Right} 80 | dkcmd ws {view,send,follow} {prev,next,last,prevne,nextne} 81 | 82 | # view, send, or follow to the next, previous, or last active monitor 83 | alt + {_,shift + ,ctrl + }{comma,period,backslash} 84 | dkcmd mon {view,send,follow} {prev,next,last} 85 | 86 | # change active workspace layout or cycle between them 87 | alt + {t,r,m,g,s,w,f,u,c} 88 | dkcmd set layout {tile,rtile,mono,grid,spiral,dwindle,none,tstack,cycle} 89 | 90 | # change number of windows in master or first stack 91 | alt + {_,shift + }{i,d} 92 | dkcmd set {master,stack} {+1,-1} 93 | 94 | # change gap width 95 | alt + {equal,minus} 96 | dkcmd set gap {+5,-5} 97 | 98 | # change border widths 99 | alt + ctrl + {_,shift + }{equal,minus} 100 | dkcmd set border {width,outer_width} {+1,-1} 101 | 102 | 103 | # vim:ft=sxhkdrc 104 | -------------------------------------------------------------------------------- /man/dk.1: -------------------------------------------------------------------------------- 1 | .TH DK 1 dk\-VERSION 2 | .SH NAME 3 | dk \- A tiling window manager in the vein of dwm, bspwm, and xmonad. 4 | .SH SYNOPSIS 5 | .B dk 6 | .RB [ \-vh ] 7 | .PP 8 | .B dkcmd 9 | .RB [ \-vh ]\ [ \-p\ [ FILE ] ]\ [ COMMAND ] 10 | .SH DESCRIPTION 11 | .PP 12 | Windows are managed in various layouts, and are grouped by workspaces. 13 | Each window can be assigned to a single workspace and will have a 14 | small border to indicate the focus state. Changes to one workspace will 15 | not impact others. Workspaces can be dynamically or statically assigned 16 | and viewed on any monitor, by default only one workspace per monitor is 17 | created. 18 | .PP 19 | Monitors can be assigned one workspace at a time, all workspaces are available 20 | from all monitors in dynamic mode, floating windows will retain their 21 | relative location within each monitor area. If viewing an already visible 22 | workspace on another monitor, the two workspaces will swap monitors when 23 | dynamic, otherwise the mouse will be warped to the assigned monitor. 24 | .PP 25 | In tiled layout windows are organized in a master and two stack areas, 26 | the number of masters and first stack windows can be adjusted 27 | from 0-n with the second stack containing all remaining windows. 28 | .PP 29 | In monocle layout windows are maximized and stacked on top of each other with 30 | only one visible at a time. 31 | .PP 32 | In grid layout windows attempt to occupy equal space with all being visible. 33 | .PP 34 | In spiral and dwindle layout windows occupy 1/2 the space of the previous, 35 | moving inwards to the center of the screen or outwards to the bottom right 36 | respectively. 37 | .PP 38 | In floating layout windows are not organized or placed in any particular way 39 | and can be moved or resized freely. 40 | .SH OPTIONS 41 | .TP 42 | .B \-h 43 | Prints usage information to stdout and exits. 44 | .TP 45 | .B \-v 46 | Prints version information to stdout and exits. 47 | .TP 48 | .B \-p 49 | Pretty format JSON input from FILE or STDIN and print on STDOUT. 50 | .SH CUSTOMIZATION 51 | For basic changes dk can be customized by running commands through the 52 | .B dkcmd 53 | program. 54 | .PP 55 | On startup dk looks for a dkrc file in: 56 | .IP \[bu] 2 57 | .BR $DKRC 58 | .IP \[bu] 2 59 | .BR $XDG_CONFIG_HOME/dk/dkrc 60 | .IP \[bu] 2 61 | .BR $HOME/.config/dk/dkrc 62 | .PP 63 | and runs the first it can, this file must be executable. 64 | This can be used to initialize settings, customization, and running programs. 65 | .PP 66 | Further configuration such as adding layouts or callbacks can be done by 67 | copying config.def.h to config.h, editing it, and recompiling. 68 | .SH DKCMD 69 | .PP 70 | Most of your interaction with the window manager will be using 71 | \fIdkcmd\fR which writes one or more commands into the socket where 72 | it is then read and parsed by the window manager. 73 | .SH Syntax Outline 74 | The commands have a very basic syntax and parsing, the input is broken 75 | down into smaller pieces (tokens) which are then passed to the matching 76 | keyword function, otherwise an error is returned. 77 | .PP 78 | Tokens are delimited by one or more: 79 | .IP \[bu] 2 80 | whitespace \fI(space or tab)\fR 81 | .IP \[bu] 2 82 | quotation mark \fI(\f[CI]\[aq]\fI or \f[CI]\[dq]\f[I])\f[R] 83 | .IP \[bu] 2 84 | equal sign \f[I](\f[CI]=\f[I])\f[R] 85 | .PP 86 | This means the following inputs are all equivalent. 87 | .IP 88 | .nf 89 | \fI\f[C] 90 | setting=value 91 | setting value 92 | setting=\[dq]value\[dq] 93 | setting = \[aq]value\[aq] 94 | setting \[dq]value\[dq] 95 | setting \[dq]value\[dq] 96 | \f[R] 97 | .fi 98 | .PP 99 | and result in two tokens: \fI\f[C]setting\f[R] and \fI\f[C]value\f[R] 100 | Quotation exists as a way to preserve whitespace and avoid 101 | interpretation by the shell, otherwise we have no way of determining 102 | whether an argument is a continuation of the previous or the beginning 103 | of the next. If the value being matched has quotes in it, they can be 104 | escaped or strong quoted 105 | .IP 106 | .nf 107 | \f[C] 108 | title=\[dq]\[ha]\[rs]\[dq]preserved quotes\[rs]\[dq]$\[dq] 109 | title=\[aq]\[ha]\[dq]preserved quotes\[dq]$\[aq] 110 | \f[R] 111 | .PP 112 | .fi 113 | For various commands dk will expect a certain data type or format to be 114 | given. 115 | .IP \[bu] 2 116 | string: normal plain text, must be less than 256 characters. 117 | .IP \[bu] 2 118 | boolean: \f[C]true\f[R], \f[C]false\f[R], \f[C]1\f[R], or \f[C]0\f[R]. 119 | .IP \[bu] 2 120 | hex: \f[C](0x/#)XXXXXXXX\f[R], used for window ids 121 | .IP \[bu] 2 122 | integer: \f[C](+/-)1\f[R], if it is preceded by a sign it is considered 123 | relative. 124 | .IP \[bu] 2 125 | float: \f[C](+/-)0.1\f[R], same as integer but must contain a decimal 126 | value. 127 | .IP \[bu] 2 128 | colour: \f[C](0x/#)[AA]RRGGBB\f[R], hex value, if no alpha channel is 129 | given the colour is opaque. 130 | .SH COMMANDS 131 | \fI\fC 132 | dkcmd COMMAND 133 | \fR 134 | .PP 135 | \l'60' 136 | .SS WM 137 | .IP \[bu] 2 138 | \fIexit\fR exit dk. 139 | .IP \[bu] 2 140 | \fIrestart\fR re-execute dk. 141 | .SS Ws and Mon 142 | .PP 143 | \fC\fImon\fR and \fC\fIws\fR operate on monitors and workspaces 144 | respectively. 145 | .IP \[bu] 2 146 | \fC\fICLIENT\fR (hex/string) The window id in hex or class string, if 147 | unspecified the active window is used. 148 | .IP \[bu] 2 149 | \fC\fITARGET\fR (integer/string) Name or number of the workspace or 150 | monitor to target or strings 151 | .RS 2 152 | .IP \[bu] 2 153 | \fC\fInext\fR relative forward 154 | .IP \[bu] 2 155 | \fC\fIprev\fR relative backward 156 | .IP \[bu] 2 157 | \fC\fIlast\fR last viewed 158 | .IP \[bu] 2 159 | \fC\fInextne\fR\fR next non-empty 160 | .IP \[bu] 2 161 | \fC\fIprevne\fR\fR previous non-empty 162 | .RE 163 | .IP 164 | .nf 165 | \fI\fC 166 | ws [SUBCOMMAND] [CLIENT] TARGET 167 | mon [SUBCOMMAND] [CLIENT] TARGET 168 | \fR\fR 169 | .fi 170 | .SS Subcommands 171 | .PP 172 | \fC\fIview\fR\fR View the TARGET, default if no subcommand is given. 173 | .IP 174 | .nf 175 | \fI\fC 176 | ws view TARGET 177 | ws TARGET 178 | \fR\fR 179 | .fi 180 | .PP 181 | \fC\fIsend\fR\fR Send CLIENT to the TARGET. 182 | .IP 183 | .nf 184 | \fI\fC 185 | mon send [CLIENT] TARGET 186 | \fR\fR 187 | .fi 188 | .PP 189 | \fC\fIfollow\fR\fR Follow CLIENT to the TARGET. 190 | .IP 191 | .nf 192 | \fI\fC 193 | ws follow [CLIENT] TARGET 194 | \fR\fR 195 | .fi 196 | .PP 197 | \l'60' 198 | .SS Rule 199 | .PP 200 | \fCrule\fR operates on window rules. 201 | .IP \[bu] 2 202 | \fCMATCH\fR one or more regex strings to be used when matching 203 | window properties. 204 | .IP \[bu] 2 205 | \fCSETTING\fR one or more window setting to be applied when a 206 | matched window is encountered. 207 | .IP 208 | .nf 209 | \fC 210 | rule [SUBCOMMAND] MATCH SETTING 211 | \fR 212 | .fi 213 | .SS Subcommands 214 | .PP 215 | \fI\fCapply\fR applies RULE to all matching windows, if RULE is 216 | \fI\fC*\fR apply all rules and MATCH is ignored. 217 | .IP 218 | .nf 219 | \fI\fC 220 | rule apply RULE [MATCH] 221 | \fR 222 | .fi 223 | .PP 224 | \fI\fCremove\fR removes RULE, if RULE is \fI\fC*\fR remove all rules 225 | and MATCH is ignored. 226 | .IP 227 | .nf 228 | \fI\fC 229 | rule remove RULE [MATCH] 230 | \fR 231 | .fi 232 | .SS Settings 233 | .PP 234 | \fI\fCclass instance title type\fR (string) regex to match the window 235 | class, instance, title, and type respectively (may be prefixed with 236 | match_ for clarity). Regex matching is always done \f[B]case insensitive\fR 237 | with extended regex mode enabled. 238 | .IP 239 | .nf 240 | \fI\fC 241 | rule [SUBCOMMAND] class=\[dq]\[ha]firefox$\[dq] instance=\[dq]\[ha]navigator$\[dq] title=\[dq]\[ha]mozilla firefox$\[dq] type=dialog [SETTING] 242 | \fR 243 | .BR 244 | type currently only supports dialog and splash windows, all others are treated as normal windows. 245 | .fi 246 | .PP 247 | \fI\fCws\fR (integer/string) determine what workspace the window should 248 | be on. 249 | .IP 250 | .nf 251 | \fI\fC 252 | rule MATCH ws=1 253 | rule MATCH ws=term 254 | \fR 255 | .fi 256 | .PP 257 | \fI\fCmon\fR (integer/string) determine what monitor the window should 258 | be on. 259 | .IP 260 | .nf 261 | \fI\fC 262 | rule MATCH mon=1 263 | rule MATCH mon=HDMI-A-0 264 | \fR 265 | .fi 266 | .PP 267 | \fI\fCx y w width h height bw border_width\fR (integer/string) 268 | determine the window location and size. 269 | .IP \[bu] 2 270 | \fI\fCx\fR change the x coordinate, can be an integer or one of the following. 271 | .RS 2 272 | .IP \[bu] 2 273 | \fI\fCcenter left\fR and \fI\fCright\fR gravitate on the x coordinate. 274 | .RE 275 | .IP \[bu] 2 276 | \fI\fCy\fR change the y coordinate, can be an integer or one of the following. 277 | .RS 2 278 | .IP \[bu] 2 279 | \fI\fCcenter top\fR and \fI\fCbottom\fR gravitate on the y coordinate. 280 | .RE 281 | .IP \[bu] 2 282 | \fI\fCw width\fR change the window width. 283 | .IP \[bu] 2 284 | \fI\fCh height\fR change the window height. 285 | .IP \[bu] 2 286 | \fI\fCbw border_width\fR change the window border width. 287 | .IP 288 | .nf 289 | \fI\fC 290 | rule MATCH x=20 y=100 w=1280 h=720 bw=0 291 | rule MATCH x=center y=center w=1280 h=720 bw=0 292 | \fR 293 | .fi 294 | .PP 295 | \fI\fCcallback\fR (string) determine a callback function to be invoked 296 | on window open and close. 297 | .in +.9i 298 | .in +.9i 299 | These are defined in the config header and compiled into the source, 300 | .br 301 | one example is provided. 302 | .IP 303 | .nf 304 | \fI\fC 305 | rule MATCH callback=albumart 306 | \fR 307 | .fi 308 | .PP 309 | \fI\fCfloat stick\fR (boolean) determine if the window should 310 | be floating or stick respectively. 311 | .IP 312 | .nf 313 | \fI\fC 314 | rule MATCH float=true stick=true 315 | \fR 316 | .fi 317 | .PP 318 | \fI\fCignore_cfg\fR (boolean) determine if the window should ignore configure 319 | request events (size or location changes). 320 | .IP 321 | .nf 322 | \fI\fC 323 | rule MATCH ignore_cfg=true 324 | \fR 325 | .fi 326 | .PP 327 | \fI\fCignore_msg\fR (boolean) determine if the window should ignore client 328 | message window activation events (grabbing focus). 329 | .IP 330 | .nf 331 | \fI\fC 332 | rule MATCH ignore_msg=true 333 | \fR 334 | .fi 335 | .PP 336 | \fI\fCfocus\fR (boolean) determine if the window should be focused and 337 | view it\[cq]s workspace. 338 | .in +.8i 339 | .in +.8i 340 | If \fI\fCmon\fR is also set it will be activated first before viewing 341 | the workspace. 342 | .IP 343 | .nf 344 | \fI\fC 345 | rule MATCH focus=true 346 | \fR 347 | .fi 348 | .PP 349 | \fI\fCterminal\fR (boolean) determine if the window should be considered 350 | a terminal for absorbing other windows and not being absorbed itself. 351 | .IP 352 | .nf 353 | \fI\fC 354 | rule MATCH terminal=true 355 | \fR 356 | .fi 357 | .PP 358 | \fI\fCno_absorb\fR (boolean) determine if the window should never absorb 359 | other windows. 360 | .IP 361 | .nf 362 | \fI\fC 363 | rule MATCH no_absorb=true 364 | \fR 365 | .fi 366 | .PP 367 | \fI\fCscratch\fR (boolean) determine if the window should be in the scratchpad. 368 | .IP 369 | .nf 370 | \fI\fC 371 | rule MATCH scratch=true 372 | \fR 373 | .fi 374 | .PP 375 | \l'60' 376 | .SS Set 377 | .PP 378 | \fI\fCset\fR operates on workspace or global configuration settings. 379 | .IP \[bu] 2 380 | \fI\fCSETTING\fR one or more settings to be changed. 381 | .IP \[bu] 2 382 | \fI\fCWS\fR the workspace which subcommand should apply to, if 383 | unspecified the current is used. 384 | .in +.3i 385 | \fI_\fR is a special workspace used to define default values for 386 | new workspaces which haven\[cq]t been created yet. 387 | .IP 388 | .nf 389 | \fI\fC 390 | set [WS] SETTING 391 | set ws=_ [apply] SETTING 392 | \fR 393 | .fi 394 | .SS Set Options 395 | .PP 396 | \fI\fCnumws\fR (integer) change the number of workspaces to allocate. 397 | .IP 398 | .nf 399 | \fI\fC 400 | set numws=10 401 | \fR 402 | .fi 403 | .PP 404 | \fI\fCname\fR (string) change the WS name. 405 | .IP 406 | .nf 407 | \fI\fC 408 | set ws=1 name=\[dq]term\[dq] 409 | \fR 410 | .fi 411 | .PP 412 | \fI\fCstatic_ws\fR (boolean) disable dynamic workspaces for multi-head 413 | systems. 414 | .IP 415 | .nf 416 | \fI\fC 417 | set static_ws=false 418 | \fR 419 | .fi 420 | .PP 421 | \fI\fCmon\fR (integer/string) change which monitor WS should be on 422 | (requires \fI\fCstatic_ws=true\fR). 423 | .IP 424 | .nf 425 | \fI\fC 426 | set ws=1 mon=HDMI-A-0 427 | set ws=1 mon=1 428 | \fR 429 | .fi 430 | .PP 431 | \fI\fCmaster stack\fR (integer) change the number of windows 432 | to occupy the master area (tile layout). 433 | .IP 434 | .nf 435 | \fI\fC 436 | set [WS] stack 3 437 | set [WS] master +1 stack -1 438 | \fR 439 | .fi 440 | .PP 441 | \fI\fCmsplit ssplit\fR (float) change the workspace master or 442 | stack split ratios respectively. 443 | .IP 444 | .nf 445 | \fI\fC 446 | set [WS] msplit +0.1 447 | set [WS] ssplit 0.55 448 | \fR 449 | .fi 450 | .PP 451 | \fI\fCgap\fR (integer) change the workspace gap width. 452 | .IP 453 | .nf 454 | \fI\fC 455 | set [WS] gap 10 456 | \fR 457 | .fi 458 | .PP 459 | \fI\fCtile_hints\fR (boolean) whether to respect size hints in tiled layouts. 460 | .IP 461 | .nf 462 | \fI\fC 463 | set tile_hints=true 464 | \fR 465 | .fi 466 | .PP 467 | \fI\fCtile_tohead\fR (boolean) whether to place new windows at the head 468 | or the tail of the list in tiled layouts. 469 | .IP 470 | .nf 471 | \fI\fC 472 | set tile_tohead=true 473 | \fR 474 | .fi 475 | .PP 476 | \fI\fCsmart_gap\fR (boolean) whether gaps are disabled on workspaces 477 | with only one tiled window. 478 | .IP 479 | .nf 480 | \fI\fC 481 | set smart_gap=true 482 | \fR 483 | .fi 484 | .PP 485 | \fI\fCsmart_border\fR (boolean) whether borders are disabled on workspaces 486 | with only one tiled window. 487 | .IP 488 | .nf 489 | \fI\fC 490 | set smart_border=true 491 | \fR 492 | .fi 493 | .PP 494 | \fI\fCfocus_urgent\fR (boolean) whether to focus windows that request it. 495 | .IP 496 | .nf 497 | \fI\fC 498 | set focus_urgent=true 499 | \fR 500 | .fi 501 | .PP 502 | \fI\fCfocus_open\fR (boolean) whether windows are focused when opened. 503 | .IP 504 | .nf 505 | \fI\fC 506 | set focus_open=false 507 | \fR 508 | .fi 509 | .PP 510 | \fI\fCfocus_mouse\fR (boolean) whether window focus follows the mouse. 511 | .IP 512 | .nf 513 | \fI\fC 514 | set focus_mouse=false 515 | \fR 516 | .fi 517 | .PP 518 | \fI\fCobey_motif\fR (boolean) whether to obey motif hints for borders. 519 | .IP 520 | .nf 521 | \fI\fC 522 | set obey_motif=false 523 | \fR 524 | .fi 525 | .PP 526 | \fI\fCwin_minxy\fR (integer) amount of window (in pixels) to be kept on 527 | the screen when moving. 528 | .IP 529 | .nf 530 | \fI\fC 531 | set win_minxy=20 532 | \fR 533 | .fi 534 | .PP 535 | \fI\fCwin_minwh\fR (integer) minimum window size. 536 | .IP 537 | .nf 538 | \fI\fC 539 | set win_minwh=50 540 | \fR 541 | .fi 542 | .PP 543 | \fI\fCapply\fR when changing the default \fI\fC_\fR workspace apply 544 | settings to existing real workspaces. 545 | .IP 546 | .nf 547 | \fI\fC 548 | set ws=_ apply SETTING 549 | \fR 550 | .fi 551 | .PP 552 | \fI\fClayout\fR (string) change the workspace window layout. 553 | .IP \[bu] 2 554 | \fI\fCtile\fR windows are grouped into master and stack areas. 555 | .IP \[bu] 2 556 | \fI\fCrtile\fR tile layout with master area on the right 557 | .IP \[bu] 2 558 | \fI\fCmono\fR windows arranged maximized and stacked on top of one another. 559 | .IP \[bu] 2 560 | \fI\fCgrid\fR all windows try to occupy equal space. 561 | .IP \[bu] 2 562 | \fI\fCspiral\fR windows shrink by 1/2 towards the center of the screen. 563 | .IP \[bu] 2 564 | \fI\fCdwindle\fR windows shrink by 1/2 towards the bottom right of the 565 | screen. 566 | .IP \[bu] 2 567 | \fI\fCtstack\fR windows are grouped into a master area on the bottom and 568 | one horizontal stack area on top. 569 | .IP \[bu] 2 570 | \fI\fCnone\fR floating layout, windows can be freely moved and resized. 571 | .IP \[bu] 2 572 | \fI\fCcycle\fR switch between available layouts. 573 | .IP 574 | .nf 575 | \fI\fC 576 | set [WS] layout mono 577 | \fR 578 | .fi 579 | .PP 580 | \fI\fCborder\fR change the window border sizes and colours. 581 | .IP \[bu] 2 582 | \fI\fCw width\fR (integer) change the overall window border 583 | width. 584 | .IP \[bu] 2 585 | \fI\fCow outer outer_width\fR (integer) change the 586 | outer border width (greater than 0 makes double borders). 587 | .IP \[bu] 2 588 | \fI\fCcolour color\fR (string) change the border (overall and 589 | outer) colour for various window states. 590 | .RS 2 591 | .IP \[bu] 2 592 | \fI\fCf focus\fR (colour) the active window border overall 593 | colour. 594 | .IP \[bu] 2 595 | \fI\fCr urgent\fR (colour) urgent window border overall 596 | colour. 597 | .IP \[bu] 2 598 | \fI\fCu unfocus\fR (colour) normal window border overall 599 | colour. 600 | .IP \[bu] 2 601 | \fI\fCof outer_focus\fR (colour) the active window outer 602 | border colour. 603 | .IP \[bu] 2 604 | \fI\fCor outer_urgent\fR (colour) urgent window outer border 605 | colour. 606 | .IP \[bu] 2 607 | \fI\fCou outer_unfocus\fR (colour) normal window outer border 608 | colour. 609 | .RE 610 | .IP 611 | .nf 612 | \fI\fC 613 | set border w=5 ow=3 colour f=\[aq]#6699cc\[aq] u=\[aq]#444444\[aq] r=\[aq]#ee5555\[aq] of=\[aq]#222222\[aq] ou=\[aq]#222222\[aq] or=\[aq]#222222\[aq] 614 | \fR 615 | .fi 616 | .PP 617 | \fI\fCpad\fR change the workspace padding. 618 | .IP \[bu] 2 619 | \fI\fCl left\fR (integer) change the workspace left side 620 | padding. 621 | .IP \[bu] 2 622 | \fI\fCr right\fR (integer) change the workspace right side 623 | padding. 624 | .IP \[bu] 2 625 | \fI\fCt top\fR (integer) change the workspace top padding. 626 | .IP \[bu] 2 627 | \fI\fCb bottom\fR (integer) change the workspace bottom 628 | padding. 629 | .IP 630 | .nf 631 | \fI\fC 632 | set [WS] pad l=50 r=50 t=50 b=50 633 | \fR 634 | .fi 635 | .PP 636 | \fI\fCmouse\fR change the mouse binds for move and resize (global, does 637 | not take a workspace). 638 | .IP \[bu] 2 639 | \fI\fCmod\fR (string) change the modifier used in combination with move 640 | resize buttons. 641 | .RS 2 642 | .IP \[bu] 2 643 | \fI\fCalt mod1\fR Alt key (default). 644 | .IP \[bu] 2 645 | \fI\fCsuper mod4\fR Win key. 646 | .IP \[bu] 2 647 | \fI\fCctrl control\fR Ctrl key. 648 | .RE 649 | .IP \[bu] 2 650 | \fI\fCmove resize\fR (string) change the button used for move 651 | and resize respectively. 652 | .RS 2 653 | .IP \[bu] 2 654 | \fI\fCbutton1\fR left mouse button. 655 | .IP \[bu] 2 656 | \fI\fCbutton2\fR right mouse button. 657 | .IP \[bu] 2 658 | \fI\fCbutton3\fR middle mouse button. 659 | .RE 660 | .IP 661 | .nf 662 | \fI\fC 663 | set mouse move=button1 resize=button2 mod=mod1 664 | \fR 665 | .fi 666 | .PP 667 | \l'60' 668 | .SS Win 669 | .PP 670 | \fI\fCwin\fR operates on windows. 671 | .IP \[bu] 2 672 | \fI\fCCLIENT\fR (hex/string) the window id in hex or class string, 673 | if unspecified the current window is used. 674 | .IP 675 | .nf 676 | \fI\fC 677 | win [CLIENT] ACTION 678 | \fR 679 | .fi 680 | .SS Actions 681 | .PP 682 | \fI\fCcycle\fR cycle windows in place. 683 | .IP 684 | .nf 685 | \fI\fC 686 | win cycle 687 | \fR 688 | .fi 689 | .PP 690 | \fI\fCfloat\fR change the window floating state. 691 | .IP 692 | .nf 693 | \fI\fC 694 | win [CLIENT] float 695 | win [CLIENT] float=false 696 | \fR 697 | .fi 698 | .PP 699 | \fI\fCfull\fR change the window fullscreen state. 700 | .IP 701 | .nf 702 | \fI\fC 703 | win [CLIENT] full 704 | \fR 705 | .fi 706 | .PP 707 | \fI\fCfakefull\fR change the window fake fullscreen state (allow 708 | moving, resizing, and tiling when fullscreen). 709 | .IP 710 | .nf 711 | \fI\fC 712 | win [CLIENT] fakefull 713 | \fR 714 | .fi 715 | .PP 716 | \fI\fCstick\fR change the window sticky state. 717 | .IP 718 | .nf 719 | \fI\fC 720 | win [CLIENT] stick 721 | \fR 722 | .fi 723 | .PP 724 | \fI\fCswap\fR change the window between it\[cq]s current location and 725 | master. 726 | .IP 727 | .nf 728 | \fI\fC 729 | win [CLIENT] swap 730 | \fR 731 | .fi 732 | .PP 733 | \fI\fCkill\fR close the window. 734 | .IP 735 | .nf 736 | \fI\fC 737 | win [CLIENT] kill 738 | \fR 739 | .fi 740 | .PP 741 | \fI\fCfocus\fR (integer/string) change the focused window. 742 | .IP \[bu] 2 743 | \fI\fCnext\fR focus the next window. 744 | .IP \[bu] 2 745 | \fI\fCprev\fR focus the previous window. 746 | .IP 747 | .nf 748 | \fI\fC 749 | win CLIENT focus 750 | win focus next 751 | win focus +2 752 | \fR 753 | .fi 754 | .PP 755 | \fI\fCscratch\fR (integer/string) show or hide a scratchpad window. 756 | .IP \[bu] 2 757 | \fI\fCpop\fR show a window in the scratch. 758 | .IP \[bu] 2 759 | \fI\fCpush\fR hide a window in the scratch. 760 | .IP 761 | With no arguments \fI\fCscratch\fR will do the following 762 | .IP \[bu] 2 763 | If there are window(s) in the scratch it will continue to pop them 764 | out until empty. 765 | .IP \[bu] 2 766 | If there is a window on any workspace (other than the current workspace) 767 | that has been recently popped, it will be brought to the current 768 | workspace. If it's on the current workspace it is instead pushed. 769 | .IP \[bu] 2 770 | If there are no window(s) in the scratch and no windows that have 771 | been there previously it will push the active window into the scratch. 772 | .IP 773 | .nf 774 | \fI\fC 775 | win scratch 776 | win [CLIENT] scratch # same toggle behaviour but on the passed window 777 | win [CLIENT] scratch push # push the given window or the active window. 778 | \fR 779 | .fi 780 | .PP 781 | \fI\fCmvstack\fR (integer/string) move a tiled window around the stack. 782 | .IP \[bu] 2 783 | \fI\fCup\fR move the tiled window up the stack. 784 | .IP \[bu] 2 785 | \fI\fCdown\fR move the tiled window down the stack. 786 | .IP 787 | .nf 788 | \fI\fC 789 | win CLIENT mvstack up 790 | \fR 791 | .fi 792 | .PP 793 | \fI\fCresize\fR change the window size, location, and border width. 794 | .IP \[bu] 2 795 | \fI\fCx\fR change the x coordinate, can be an integer or one of the following. 796 | .RS 2 797 | .IP \[bu] 2 798 | \fI\fCcenter left\fR and \fI\fCright\fR gravitate on the x coordinate. 799 | .RE 800 | .IP \[bu] 2 801 | \fI\fCy\fR change the y coordinate, can be an integer or one of the following. 802 | .RS 2 803 | .IP \[bu] 2 804 | \fI\fCcenter top\fR and \fI\fCbottom\fR gravitate on the y coordinate. 805 | .RE 806 | .IP \[bu] 2 807 | \fI\fCw width\fR change the window width. 808 | .IP \[bu] 2 809 | \fI\fCh height\fR change the window height. 810 | .IP \[bu] 2 811 | \fI\fCbw border_width\fR change the window border width. 812 | .IP 813 | .nf 814 | \fI\fC 815 | win [CLIENT] resize x=100 y=100 w=1280 h=720 bw=1 816 | win [CLIENT] resize x=center y=center w=1280 h=720 bw=1 817 | \fR 818 | .fi 819 | .PP 820 | \l'60' 821 | .SS Status 822 | .PP 823 | \fI\fCstatus\fR print status information as JSON to a file or stdout. 824 | .IP 825 | .nf 826 | \fI\fC 827 | status [TYPE] [FILE] [NUM] 828 | \fR 829 | .fi 830 | .SS Settings 831 | .PP 832 | \fI\fCtype\fR the type of status to output and when to trigger. 833 | .IP \[bu] 2 834 | \fI\fCws\fR output full workspace info - triggers on workspace change. 835 | .IP \[bu] 2 836 | \fI\fCwin\fR output current window title - triggers on window or title change. 837 | .IP \[bu] 2 838 | \fI\fClayout\fR output current layout name - triggers on layout change. 839 | .IP \[bu] 2 840 | \fI\fCbar\fR identical output to `ws` except - triggers on all changes. 841 | .IP \[bu] 2 842 | \fI\fCfull\fR output full wm and client state - triggers on all changes. 843 | .IP 844 | .nf 845 | \fI\fC 846 | status type=ws [FILE] [NUM] 847 | \fR 848 | .fi 849 | .PP 850 | \fI\fCfile\fR the location of the status file (if not passed stdout is used). 851 | .IP 852 | .nf 853 | \fI\fC 854 | status file=/tmp/dk.status [TYPE] [NUM] 855 | \fR 856 | .fi 857 | .PP 858 | \fI\fCnum\fR the number of times to output, -1 is infinite and default if not passed. 859 | .IP 860 | .nf 861 | \fI\fC 862 | status [TYPE] [FILE] 863 | status num=1 [TYPE] [FILE] 864 | \fR 865 | .fi 866 | .SH BUGS 867 | Please submit a bug report with as much detail as possible to 868 | .B https://bitbucket.org/natemaia/dk/issues/new 869 | .SH AUTHORS/CONTRIBUTORS 870 | Nathaniel Maia <\fInatemaia10@gmail.com\fR>, 871 | Dmitry Belitsky <\fIdmitry.belitsky@gmail.com\fR> 872 | -------------------------------------------------------------------------------- /man/dkcmd.1: -------------------------------------------------------------------------------- 1 | dk.1 -------------------------------------------------------------------------------- /src/cmd.c: -------------------------------------------------------------------------------- 1 | /* dk window manager 2 | * 3 | * see license file for copyright and license details 4 | * vim:ft=c:fdm=syntax:ts=4:sts=4:sw=4 5 | */ 6 | 7 | /* 8 | * various functions to handle command parsing and actions 9 | * 10 | * all cmd* functions should: 11 | * 12 | * return -1 on error 13 | * return number of arguments parsed on success (including 0) 14 | * 15 | * caller is responsible for the argument counter increment 16 | * which should be passed back up the call stack to parsecmd() 17 | */ 18 | 19 | #define _XOPEN_SOURCE 700 20 | 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | #include 35 | #include 36 | 37 | #include "dk.h" 38 | #include "cmd.h" 39 | #include "util.h" 40 | #include "strl.h" 41 | #include "parse.h" 42 | #include "status.h" 43 | #include "event.h" 44 | #include "layout.h" 45 | 46 | int cmdc_passed = 0; 47 | 48 | int adjustisetting(int i, int rel, int *val, int other, int border) 49 | { 50 | int n; 51 | int max = setws->mon->wh - setws->padb - setws->padt; 52 | 53 | if (!rel && !(i -= *val)) { 54 | return -1; 55 | } 56 | n = CLAMP(*val + i, 0, border ? (max / 6) - other : max / globalcfg[GLB_MIN_WH].val); 57 | if (n != *val) { 58 | *val = n; 59 | needsrefresh = 1; 60 | } 61 | return 0; 62 | } 63 | 64 | int adjustwsormon(char **argv) 65 | { 66 | int opt, nparsed = 0, e = 0; 67 | int (*fn)(Workspace *) = cmdview; 68 | Workspace *ws = NULL, *cur = selws; 69 | Monitor *m = NULL, *cm = cur->mon; 70 | 71 | if (*argv) { 72 | /* find which command function we'll be using: view, follow, send */ 73 | for (uint32_t i = 0; wscmds[i].str; i++) { 74 | if (!strcmp(wscmds[i].str, *argv)) { 75 | fn = wscmds[i].func; 76 | argv++, nparsed++; 77 | break; 78 | } 79 | } 80 | 81 | /* when not viewing a workspace we can pass a client as a parameter */ 82 | if (fn != cmdview && (cmdc = parseclient(*argv, &e))) { 83 | cur = cmdc->ws; 84 | cm = cur->mon; 85 | argv++, nparsed++; 86 | } else if (e == -1) { 87 | respond(cmdresp, "!invalid window id: %s\nexpected hex e.g. 0x001fefe7", *argv); 88 | return e; 89 | } else { 90 | /* use the selected window otherwise */ 91 | cmdc = selws->sel; 92 | } 93 | } 94 | 95 | if (!*argv) { 96 | /* we expect a direction or name of the ws/mon */ 97 | respond(cmdresp, "!%s %s", cmdusemon ? "mon" : "ws", enoargs); 98 | return -1; 99 | } 100 | 101 | /* parse directions: next, prev, last, etc. */ 102 | if ((opt = parseopt(*argv, dirs, (int)(sizeof(dirs) / sizeof(*dirs)))) >= 0) { 103 | if (opt == DIR_LAST) { 104 | ws = cmdusemon ? (lastmon && lastmon->connected) ? lastmon->ws : cur : lastws ? lastws : cur; 105 | } else if (opt == DIR_NEXT && cmdusemon) { 106 | if (!(m = nextmon(cm->next))) { 107 | m = nextmon(monitors); 108 | } 109 | ws = m->ws; 110 | } else if (opt == DIR_NEXT) { 111 | ws = cur->next ? cur->next : workspaces; 112 | } else if (cmdusemon && opt == DIR_PREV) { 113 | for (m = nextmon(monitors); m && nextmon(m->next) && nextmon(m->next) != cm; m = nextmon(m->next)) 114 | ; 115 | ws = m ? m->ws : selws; 116 | } else if (opt == DIR_PREV) { 117 | PREV(ws, cur, workspaces); 118 | } else { 119 | int r = 0; 120 | Workspace *save = cur; 121 | while (!ws && r < globalcfg[GLB_NUM_WS].val) { 122 | if (opt == DIR_NEXTNE) { 123 | if (cmdusemon) { 124 | if (!(m = nextmon(cm))) { 125 | m = nextmon(monitors); 126 | } 127 | ws = m->ws; 128 | } else { 129 | ws = cur->next ? cur->next : workspaces; 130 | } 131 | } else if (cmdusemon) { 132 | for (m = nextmon(monitors); m && nextmon(m->next) && nextmon(m->next) != cm; 133 | m = nextmon(m->next)) 134 | ; 135 | ws = m ? m->ws : selws; 136 | } else { 137 | PREV(ws, cur, workspaces); 138 | } 139 | cur = ws; 140 | cm = ws->mon; 141 | if (!ws->clients && ws != save) { 142 | ws = NULL; 143 | } 144 | r++; 145 | } 146 | } 147 | } else { 148 | /* with no direction passed we search for workspace/monitor name or 149 | * index */ 150 | ws = parsewsormon(*argv, cmdusemon); 151 | } 152 | 153 | if (ws) { 154 | DBG("adjustwsormon: using workspace %d : monitor %s", ws->num + 1, ws->mon->name) 155 | nparsed++; 156 | if ((cmdc && ws != cmdc->ws) || ws != selws || selws->mon != ws->mon) { 157 | DBG("adjustwsormon: %s client: 0x%08x %s", 158 | fn == cmdsend ? "sending" 159 | : fn == cmdfollow ? "following" 160 | : "viewing", 161 | cmdc ? cmdc->win : 0, cmdc ? cmdc->title : "none"); 162 | lytchange = fn != cmdsend && ws->layout != selws->layout; 163 | fn(ws); 164 | wschange = 1; 165 | } 166 | } else { 167 | if (cmdusemon) { 168 | respond(cmdresp, "!%s mon: %s\nexpected integer or monitor name e.g. HDMI-A-0", ebadarg, *argv); 169 | } else { 170 | respond(cmdresp, "!%s ws: %s\nexpected integer or workspace name e.g. 2", ebadarg, *argv); 171 | } 172 | return -1; 173 | } 174 | return nparsed; 175 | } 176 | 177 | int cmdborder(char **argv) 178 | { 179 | Client *c; 180 | Workspace *ws; 181 | int i, nparsed = 0, rel, col = 0, first; 182 | int bw = border[BORD_WIDTH], old = border[BORD_WIDTH], ow = border[BORD_O_WIDTH]; 183 | 184 | #define COLOUR(type) \ 185 | if (!(++argv) || parsecolour(*argv, &border[BORD_##type]) < 0) goto badvalue 186 | 187 | while (*argv) { 188 | int outer = 0; 189 | if (!strcmp(*argv, "w") || !strcmp(*argv, "width") || 190 | (outer = !strcmp("ow", *argv) || !strcmp("outer", *argv) || !strcmp("outer_width", *argv))) { 191 | col = 0, nparsed++, argv++; 192 | if (!argv || (i = parseint(*argv, &rel, 1)) == INT_MIN) { 193 | goto badvalue; 194 | } 195 | adjustisetting(i, rel, outer ? &ow : &bw, selws->gappx + (outer ? bw : 0), 1); 196 | } else if (col || (first = !strcmp(*argv, "colour") || !strcmp(*argv, "color"))) { 197 | if (!col) { 198 | col = 1, argv++, nparsed++; 199 | } 200 | if (!strcmp("f", *argv) || !strcmp("focus", *argv)) { 201 | COLOUR(FOCUS); 202 | } else if (!strcmp("u", *argv) || !strcmp("urgent", *argv)) { 203 | COLOUR(URGENT); 204 | } else if (!strcmp("r", *argv) || !strcmp("unfocus", *argv)) { 205 | COLOUR(UNFOCUS); 206 | } else if (!strcmp("of", *argv) || !strcmp("outer_focus", *argv)) { 207 | COLOUR(O_FOCUS); 208 | } else if (!strcmp("ou", *argv) || !strcmp("outer_urgent", *argv)) { 209 | COLOUR(O_URGENT); 210 | } else if (!strcmp("or", *argv) || !strcmp("outer_unfocus", *argv)) { 211 | COLOUR(O_UNFOCUS); 212 | } else if (first) { 213 | goto badvalue; 214 | } else { 215 | col = first = 0; 216 | continue; 217 | } 218 | first = 0, nparsed++; 219 | #undef COLOUR 220 | } else { 221 | break; 222 | badvalue: 223 | respond(cmdresp, "!set border: invalid %s value: %s", *(argv - 1), *argv); 224 | return -1; 225 | } 226 | argv++, nparsed++; 227 | } 228 | if (bw - ow < 1 && (uint32_t)ow != border[BORD_O_WIDTH]) { 229 | respond(cmdresp, "!border outer exceeds limit: %d - maximum: %d", ow, bw - 1); 230 | } else if (bw - ow > 0) { 231 | border[BORD_O_WIDTH] = ow; 232 | } 233 | border[BORD_WIDTH] = bw; 234 | 235 | for (ws = workspaces; ws; ws = ws->next) { 236 | for (c = ws->clients; c; c = c->next) { 237 | if (!STATE(c, NOBORDER) && c->bw == old) { 238 | c->bw = bw; 239 | } 240 | } 241 | } 242 | for (c = scratch.clients; c; c = c->next) { 243 | if (!STATE(c, NOBORDER) && c->bw == old) { 244 | c->bw = bw; 245 | } 246 | } 247 | return nparsed; 248 | } 249 | 250 | int cmdcycle(__attribute__((unused)) char **argv) 251 | { 252 | Client *c = cmdc, *first; 253 | 254 | if (FLOATING(c) || FULLSCREEN(c) || tilecount(c->ws) <= 1) { 255 | respond(cmdresp, "!unable to cycle floating, fullscreen, or single tiled windows"); 256 | return -1; 257 | } 258 | if (c == (first = nexttiled(selws->clients)) && !nexttiled(c->next)) { 259 | return 0; 260 | } 261 | if (!(c = nexttiled(selws->sel->next))) { 262 | c = first; 263 | } 264 | 265 | /* TODO: avoid using focus() twice here, possibly just 266 | * 267 | * cmdc = first; 268 | */ 269 | focus(first); 270 | movestack(-1); 271 | focus(c); 272 | 273 | return 0; 274 | } 275 | 276 | int cmdexit(__attribute__((unused)) char **argv) 277 | { 278 | running = 0; 279 | return 0; 280 | } 281 | 282 | int cmdfakefull(__attribute__((unused)) char **argv) 283 | { 284 | Client *c = cmdc; 285 | 286 | if ((c->state ^= STATE_FAKEFULL) & STATE_FULLSCREEN) { 287 | if (c->w != MON(c)->w || c->h != MON(c)->h) { 288 | c->bw = c->old_bw; 289 | } 290 | if (!STATE(c, FAKEFULL)) { 291 | resize(c, MON(c)->x, MON(c)->y, MON(c)->w, MON(c)->h, 0); 292 | } 293 | needsrefresh = 1; 294 | } 295 | return 0; 296 | } 297 | 298 | int cmdfloat(char **argv) 299 | { 300 | int i, nparsed = 0; 301 | Client *c = cmdc; 302 | 303 | if (!c || !c->ws->layout->func) { 304 | return nparsed; 305 | } 306 | if (argv && *argv && !strcmp(*argv, "all")) { 307 | nparsed++; 308 | for (c = cmdc->ws->clients; c; c = c->next) { 309 | cmdc = c; 310 | if (FLOATING(c) || STATE(c, WASFLOATING)) { 311 | if (FLOATING(c)) { 312 | c->state |= STATE_WASFLOATING; 313 | } else { 314 | c->state &= ~STATE_WASFLOATING; 315 | } 316 | cmdfloat(NULL); 317 | } 318 | } 319 | return nparsed; 320 | } 321 | 322 | if (FULLSCREEN(c) || STATE(c, STICKY) || STATE(c, FIXED)) { 323 | respond(cmdresp, "!unable to change floating state of fullscreen, " 324 | "sticky, or fixed size windows"); 325 | return nparsed; 326 | } 327 | 328 | if (argv && *argv) { 329 | if ((i = parsebool(*argv)) < 0) { 330 | respond(cmdresp, "!invalid argument for win float: %s", *argv); 331 | return nparsed; 332 | } else if (i == 0) { 333 | c->state |= STATE_FLOATING; 334 | } else { 335 | c->state &= ~STATE_FLOATING; 336 | } 337 | } 338 | 339 | if ((c->state ^= STATE_FLOATING) & STATE_FLOATING) { 340 | Monitor *m = MON(c); 341 | if (c->old_x + c->old_y == m->wx + m->wy || c->old_x + c->old_y == m->x + m->y) { 342 | quadrant(c, &c->old_x, &c->old_y, &c->old_w, &c->old_h); 343 | } 344 | if (W(c) >= m->ww && H(c) >= m->wh) { 345 | c->h = m->ww - (m->ww / 8); 346 | c->w = m->wh - (m->wh / 8); 347 | gravitate(c, GRAV_CENTER, GRAV_CENTER, 1); 348 | } 349 | resizehint(c, c->old_x, c->old_y, c->old_w, c->old_h, c->bw, 0, 0); 350 | } else { 351 | c->old_x = c->x, c->old_y = c->y, c->old_w = c->w, c->old_h = c->h; 352 | } 353 | needsrefresh = 1; 354 | return nparsed; 355 | } 356 | 357 | int cmdfocus(char **argv) 358 | { 359 | int i = 0, nparsed = 0, opt; 360 | Client *c = cmdc; 361 | Workspace *ws = c->ws; 362 | 363 | if (FULLSCREEN(c) || !c->ws->clients->next) { 364 | return nparsed; 365 | } 366 | if (cmdc_passed) { 367 | if (ws != selws) { 368 | cmdview(c->ws); 369 | } 370 | focus(c); 371 | if (FLOATING(c)) { 372 | setstackmode(c->win, XCB_STACK_MODE_ABOVE); 373 | } 374 | return nparsed; 375 | } 376 | if ((opt = parseopt(*argv, dirs, LEN(dirs))) < 0 && (i = parseint(*argv, NULL, 0)) == INT_MIN) { 377 | respond(cmdresp, "!%s win focus: %s", ebadarg, *argv); 378 | return -1; 379 | } 380 | 381 | nparsed++; 382 | int direction = opt == -1 ? i : (opt == DIR_NEXT) ? 1 : -1; 383 | while (direction) { 384 | if (direction > 0) { 385 | c = ws->sel->next ? ws->sel->next : ws->clients; 386 | direction--; 387 | } else { 388 | PREV(c, ws->sel, ws->clients); 389 | direction++; 390 | } 391 | if (c) { 392 | focus(c); 393 | } 394 | } 395 | if (c && FLOATING(c)) { 396 | setstackmode(c->win, XCB_STACK_MODE_ABOVE); 397 | } 398 | return nparsed; 399 | } 400 | 401 | int cmdfollow(Workspace *ws) 402 | { 403 | if (ws && cmdc && ws != cmdc->ws) { 404 | DBG("cmdfollow: following client to workspace %d : monitor %s : %s", 405 | ws->num + 1, ws->mon->name, cmdc->title) 406 | cmdsend(ws); 407 | cmdview(ws); 408 | } 409 | return 0; 410 | } 411 | 412 | int cmdfull(__attribute__((unused)) char **argv) 413 | { 414 | setfullscreen(cmdc, !STATE(cmdc, FULLSCREEN)); 415 | return 0; 416 | } 417 | 418 | int cmdgappx(char **argv) 419 | { 420 | int i, rel, nparsed = 0; 421 | 422 | if (!*argv) { 423 | respond(cmdresp, "!gap %s", enoargs); 424 | return -1; 425 | } else if ((i = parseint(*argv, &rel, 1)) == INT_MIN) { 426 | respond(cmdresp, "!invalid value for gap: %s\n\nexpected integer e.g. 10", *argv); 427 | return -1; 428 | } else { 429 | nparsed++; 430 | adjustisetting(i, rel, &setws->gappx, border[BORD_WIDTH], 1); 431 | } 432 | return nparsed; 433 | } 434 | 435 | int cmdkill(__attribute__((unused)) char **argv) 436 | { 437 | if (!sendwmproto(cmdc, wmatom[WM_DELETE])) { 438 | xcb_grab_server(con); 439 | xcb_set_close_down_mode(con, XCB_CLOSE_DOWN_DESTROY_ALL); 440 | xcb_kill_client(con, cmdc->win); 441 | xcb_aux_sync(con); 442 | xcb_ungrab_server(con); 443 | } else { 444 | xcb_aux_sync(con); 445 | } 446 | ignore(XCB_ENTER_NOTIFY); 447 | return 0; 448 | } 449 | 450 | int cmdlayout(char **argv) 451 | { 452 | if (!strcmp("cycle", *argv)) { 453 | for (uint32_t i = 0; layouts[i].name; i++) { 454 | if (&layouts[i] == setws->layout) { 455 | uint32_t n; 456 | for (n = 0; layouts[n].name; n++) 457 | ; 458 | setws->layout = &layouts[(i + 1) % n]; 459 | needsrefresh = lytchange = 1; 460 | break; 461 | } 462 | } 463 | return 1; 464 | } 465 | 466 | /* slayouts and layouts.name are 1-1 mapped so we can do this */ 467 | for (uint32_t i = 0; slayouts[i]; i++) { 468 | if (!strcmp(slayouts[i], *argv)) { 469 | if (&layouts[i] != setws->layout) { 470 | setws->layout = &layouts[i]; 471 | needsrefresh = lytchange = 1; 472 | } 473 | return 1; 474 | } 475 | } 476 | respond(cmdresp, "!invalid layout argument: %s\nexpected string e.g tile", *argv); 477 | return -1; 478 | } 479 | 480 | int cmdmon(char **argv) 481 | { 482 | int nparsed = 0; 483 | if (monitors && nextmon(monitors)) { 484 | cmdusemon = 1; 485 | nparsed = adjustwsormon(argv); 486 | cmdusemon = 0; 487 | } 488 | return nparsed; 489 | } 490 | 491 | int cmdmors(char **argv) 492 | { 493 | int i, rel = 1; 494 | 495 | if ((i = parseint(*argv, &rel, 1)) == INT_MIN || 496 | adjustisetting(i, rel, !strcmp("stack", *(argv - 1)) ? &setws->nstack : &setws->nmaster, 0, 0) == 497 | -1) { 498 | return -1; 499 | } 500 | return 1; 501 | } 502 | 503 | int cmdmouse(char **argv) 504 | { 505 | Client *c; 506 | Workspace *ws; 507 | int arg, nparsed = 0; 508 | xcb_mod_mask_t oldmod = mousemod; 509 | xcb_button_t oldmove = mousemove, oldresize = mouseresize; 510 | 511 | while (*argv) { 512 | if (!strcmp("mod", *argv)) { 513 | argv++, nparsed++; 514 | if (!strcmp("alt", *argv) || !strcmp("mod1", *argv)) { 515 | mousemod = XCB_MOD_MASK_1; 516 | } else if (!strcmp("super", *argv) || !strcmp("mod4", *argv)) { 517 | mousemod = XCB_MOD_MASK_4; 518 | } else if (!strcmp("ctrl", *argv) || !strcmp("control", *argv)) { 519 | mousemod = XCB_MOD_MASK_CONTROL; 520 | } else { 521 | goto badvalue; 522 | } 523 | } else if ((arg = !strcmp("move", *argv)) || !strcmp("resize", *argv)) { 524 | argv++, nparsed++; 525 | xcb_button_t *btn = arg ? &mousemove : &mouseresize; 526 | if (!strcmp("button1", *argv)) { 527 | *btn = XCB_BUTTON_INDEX_1; 528 | } else if (!strcmp("button2", *argv)) { 529 | *btn = XCB_BUTTON_INDEX_2; 530 | } else if (!strcmp("button3", *argv)) { 531 | *btn = XCB_BUTTON_INDEX_3; 532 | } else { 533 | goto badvalue; 534 | } 535 | } else { 536 | break; 537 | badvalue: 538 | respond(cmdresp, "!set mouse: invalid value for %s: %s", *(argv - 1), *argv); 539 | return -1; 540 | } 541 | argv++, nparsed++; 542 | } 543 | if (oldmove != mousemove || oldresize != mouseresize || oldmod != mousemod) { 544 | for (ws = workspaces; ws; ws = ws->next) { 545 | for (c = ws->clients; c; c = c->next) { 546 | grabbuttons(c); 547 | } 548 | } 549 | for (c = scratch.clients; c; c = c->next) { 550 | grabbuttons(c); 551 | } 552 | } 553 | return nparsed; 554 | } 555 | 556 | int cmdmvstack(char **argv) 557 | { 558 | char arg[8]; 559 | char *dir = "y"; 560 | char *newargv[] = {dir, arg, NULL}; 561 | 562 | if (!cmdc || FLOATING(cmdc) || FULLSCREEN(cmdc)) { 563 | respond(cmdresp, "!no window available to move in the stack"); 564 | return -1; 565 | } 566 | if (!strcmp("up", *argv)) { 567 | strlcpy(arg, "-20", sizeof(arg)); 568 | } else if (!strcmp("down", *argv)) { 569 | strlcpy(arg, "+20", sizeof(arg)); 570 | } else if (parseint(*argv, NULL, 0) != INT_MIN) { 571 | strlcpy(arg, *argv, sizeof(arg)); 572 | } else { 573 | respond(cmdresp, "!win mvstack: invalid value: %s", *argv); 574 | return -1; 575 | } 576 | cmdresize(newargv); 577 | return 1; 578 | } 579 | 580 | int cmdpad(char **argv) 581 | { 582 | int i, rel, orig, nparsed = 0; 583 | 584 | #define PAD(v, max) \ 585 | argv++, nparsed++, orig = v; \ 586 | if (!argv || (i = parseintclamp(*argv, &rel, v * -1, max)) == INT_MIN) goto badvalue; \ 587 | v = CLAMP(rel ? v + i : i, 0, max); \ 588 | needsrefresh = needsrefresh || v != orig 589 | 590 | while (*argv) { 591 | if (!strcmp("l", *argv) || !strcmp("left", *argv)) { 592 | PAD(setws->padl, setws->mon->w / 3); 593 | } else if (!strcmp("r", *argv) || !strcmp("right", *argv)) { 594 | PAD(setws->padr, setws->mon->w / 3); 595 | } else if (!strcmp("t", *argv) || !strcmp("top", *argv)) { 596 | PAD(setws->padt, setws->mon->h / 3); 597 | } else if (!strcmp("b", *argv) || !strcmp("bottom", *argv)) { 598 | PAD(setws->padb, setws->mon->h / 3); 599 | } else { 600 | break; 601 | badvalue: 602 | respond(cmdresp, "!set pad: invalid value for %s: %s", *(argv - 1), *argv); 603 | return -1; 604 | } 605 | argv++, nparsed++; 606 | } 607 | return nparsed; 608 | #undef PAD 609 | } 610 | 611 | int cmdresize(char **argv) 612 | { 613 | Client *c = cmdc, *t; 614 | Workspace *ws = c ? c->ws : selws; 615 | float f, *sf; 616 | int i, nparsed = 0; 617 | int xgrav = GRAV_NONE, ygrav = GRAV_NONE; 618 | int x = INT_MIN, y = INT_MIN, w = INT_MIN, h = INT_MIN, bw = INT_MIN; 619 | int ohoff, relx = 0, rely = 0, relw = 0, relh = 0, relbw = 0; 620 | 621 | #define ARG(val, rel, allowzero) \ 622 | argv++, nparsed++; \ 623 | if (!argv || (val = parseint(*argv, rel, allowzero)) == INT_MIN) goto badvalue; 624 | 625 | if (!c) { 626 | respond(cmdresp, "!no window available to resize"); 627 | return -1; 628 | } else if (FULLSCREEN(c)) { 629 | respond(cmdresp, "!unable to resize fullscreen windows"); 630 | return -1; 631 | } 632 | while (*argv) { 633 | if (!strcmp("x", *argv)) { 634 | argv++, nparsed++; 635 | if (!argv || !parsecoord(*argv, 'x', &x, &relx, &xgrav)) { 636 | goto badvalue; 637 | } 638 | } else if (!strcmp("y", *argv)) { 639 | argv++, nparsed++; 640 | if (!argv || !parsecoord(*argv, 'y', &y, &rely, &ygrav)) { 641 | goto badvalue; 642 | } 643 | } else if (!strcmp("w", *argv) || !strcmp("width", *argv)) { 644 | ARG(w, &relw, 0); 645 | } else if (!strcmp("h", *argv) || !strcmp("height", *argv)) { 646 | ARG(h, &relh, 0); 647 | } else if (!strcmp("bw", *argv) || !strcmp("border_width", *argv)) { 648 | ARG(bw, &relbw, 1); 649 | } else { 650 | break; 651 | badvalue: 652 | respond(cmdresp, "!win resize: invalid value for %s: %s", *(argv - 1), *argv); 653 | return -1; 654 | } 655 | argv++, nparsed++; 656 | } 657 | 658 | if (bw != INT_MIN) { 659 | if (c->bw != (bw = CLAMP(relbw ? c->bw + bw : bw, 0, ws->mon->wh / 6))) { 660 | if ((c->bw = bw) == 0) { 661 | c->state |= STATE_NOBORDER; 662 | } 663 | needsrefresh = 1; 664 | } 665 | } 666 | 667 | if (!FLOATING(c) && y != INT_MIN) { 668 | movestack(y > 0 || ygrav == GRAV_BOTTOM ? 1 : -1); 669 | } 670 | 671 | if (!ws->layout->implements_resize) { 672 | if (x != INT_MIN || w != INT_MIN || h != INT_MIN) { 673 | respond(cmdresp, "!unable to resize windows in %s layout", ws->layout->name); 674 | } 675 | goto end; 676 | } 677 | 678 | if (FLOATING(c)) { 679 | x = x == INT_MIN || xgrav != GRAV_NONE ? c->x : (relx ? c->x + x : x); 680 | y = y == INT_MIN || ygrav != GRAV_NONE ? c->y : (rely ? c->y + y : y); 681 | w = w == INT_MIN ? c->w : (relw ? c->w + w : w); 682 | h = h == INT_MIN ? c->h : (relh ? c->h + h : h); 683 | resizehint(c, x, y, w, h, c->bw, 1, 0); 684 | if (xgrav != GRAV_NONE || ygrav != GRAV_NONE) { 685 | gravitate(c, xgrav, ygrav, 1); 686 | } 687 | } else if (ws->layout->tile_resize) { 688 | if (w != INT_MIN) { 689 | if (ws->layout->func == rtile) { 690 | w += w * -2; 691 | } 692 | for (i = 0, t = nexttiled(ws->clients); t && t != c; t = nexttiled(t->next), i++) 693 | ; 694 | sf = (ws->nmaster && i < ws->nmaster + ws->nstack) ? &ws->msplit : &ws->ssplit; 695 | f = relw ? ((ws->mon->ww * *sf) + w) / ws->mon->ww : (float)w / ws->mon->ww; 696 | if (f < 0.05 || f > 0.95) { 697 | respond(cmdresp, "!width exceeded limit: %f", ws->mon->ww * f); 698 | } else { 699 | *sf = f; 700 | if (h == INT_MIN) { 701 | ws->layout->func(ws); 702 | } 703 | } 704 | } 705 | if (h != INT_MIN) { 706 | ohoff = c->hoff; 707 | c->hoff = relh ? c->hoff + h : h; 708 | if (ws->layout->func(ws) < 0) { 709 | respond(cmdresp, "!height offset exceeded limit: %d", c->hoff); 710 | c->hoff = ohoff; 711 | } 712 | } 713 | } else if (w != INT_MIN || (h != INT_MIN && ws->layout->invert_split_direction)) { 714 | if (w != INT_MIN) { 715 | f = relw ? ((ws->mon->ww * ws->msplit) + w) / ws->mon->ww : (float)w / ws->mon->ww; 716 | } else { 717 | f = relh ? ((ws->mon->wh * ws->msplit) + h) / ws->mon->wh : (float)h / ws->mon->wh; 718 | } 719 | if (f < 0.05 || f > 0.95) { 720 | respond(cmdresp, "!%s exceeded limit: %f", w != INT_MIN ? "width" : "height", ws->mon->ww * f); 721 | } else { 722 | ws->msplit = f; 723 | ws->layout->func(ws); 724 | } 725 | } 726 | end: 727 | xcb_aux_sync(con); 728 | ignore(XCB_ENTER_NOTIFY); 729 | return nparsed; 730 | #undef ARG 731 | } 732 | 733 | int cmdrestart(__attribute__((unused)) char **argv) 734 | { 735 | running = 0, restart = 1; 736 | return 0; 737 | } 738 | 739 | int cmdrule(char **argv) 740 | { 741 | Client *c; 742 | Workspace *ws; 743 | Rule *pr, *nr = NULL; 744 | int j, nparsed = 0, match; 745 | uint32_t i, delete = 0, apply = 0; 746 | Rule r = { 747 | .x = -1, 748 | .y = -1, 749 | .w = -1, 750 | .h = -1, 751 | .ws = -1, 752 | .bw = -1, 753 | .focus = 0, 754 | .state = STATE_NONE, 755 | .type = 0, 756 | .xgrav = GRAV_NONE, 757 | .ygrav = GRAV_NONE, 758 | .cb = NULL, 759 | .mon = NULL, 760 | .inst = NULL, 761 | .clss = NULL, 762 | .title = NULL, 763 | }; 764 | 765 | #define ARG(val) \ 766 | argv++, nparsed++; \ 767 | if (!argv || (j = parseint(*argv, NULL, 0)) == INT_MIN) goto badvalue; \ 768 | val = j 769 | #define STR(val) \ 770 | argv++, nparsed++; \ 771 | if (!argv || !*argv) goto badvalue; \ 772 | val = *argv 773 | #define CSTATE(val) \ 774 | argv++, nparsed++; \ 775 | if (!argv || (j = parsebool(*argv)) < 0) goto badvalue; \ 776 | if (j) \ 777 | r.state |= (val); \ 778 | else \ 779 | r.state &= ~(val) 780 | 781 | while (*argv) { 782 | if (!strcmp(*argv, "class") || !strcmp(*argv, "match_class")) { 783 | STR(r.clss); 784 | } else if (!strcmp(*argv, "instance") || !strcmp(*argv, "match_instance")) { 785 | STR(r.inst); 786 | } else if (!strcmp(*argv, "title") || !strcmp(*argv, "match_title")) { 787 | STR(r.title); 788 | } else if (!strcmp(*argv, "type") || !strcmp(*argv, "match_type")) { 789 | argv++, nparsed++; 790 | if (!argv || !*argv) { 791 | goto badvalue; 792 | } 793 | if (!strcmp(*argv, "splash")) { 794 | r.type = netatom[NET_TYPE_SPLASH]; 795 | } else if (!strcmp(*argv, "dialog")) { 796 | r.type = netatom[NET_TYPE_DIALOG]; 797 | } else { 798 | goto badvalue; 799 | } 800 | 801 | } else if (!strcmp(*argv, "mon")) { 802 | STR(r.mon); 803 | } else if (!strcmp(*argv, "ws")) { 804 | argv++, nparsed++; 805 | if (!argv) { 806 | goto badvalue; 807 | } 808 | if ((r.ws = parseintclamp(*argv, NULL, 1, globalcfg[GLB_NUM_WS].val)) == INT_MIN) { 809 | r.ws = -1; 810 | match = 0; 811 | for (ws = workspaces; ws; ws = ws->next) { 812 | if ((match = !strcmp(ws->name, *argv))) { 813 | r.ws = ws->num; 814 | break; 815 | } 816 | } 817 | if (!match) { 818 | goto badvalue; 819 | } 820 | } 821 | } else if (!strcmp(*argv, "callback")) { 822 | argv++, nparsed++; 823 | if (argv) { 824 | for (i = 0; callbacks[i].name; i++) { 825 | if (!strcmp(callbacks[i].name, *argv)) { 826 | r.cb = &callbacks[i]; 827 | break; 828 | } 829 | } 830 | } 831 | if (!r.cb) { 832 | goto badvalue; 833 | } 834 | } else if (!strcmp(*argv, "x")) { 835 | argv++, nparsed++; 836 | if (!argv || !parsecoord(*argv, 'x', &r.x, NULL, &r.xgrav)) { 837 | goto badvalue; 838 | } 839 | } else if (!strcmp(*argv, "y")) { 840 | argv++, nparsed++; 841 | if (!argv || !parsecoord(*argv, 'y', &r.y, NULL, &r.ygrav)) { 842 | goto badvalue; 843 | } 844 | } else if (!strcmp("w", *argv) || !strcmp("width", *argv)) { 845 | ARG(r.w); 846 | } else if (!strcmp("h", *argv) || !strcmp("height", *argv)) { 847 | ARG(r.h); 848 | } else if (!strcmp("bw", *argv) || !strcmp("border_width", *argv)) { 849 | argv++, nparsed++; 850 | if (!argv || (j = parseintclamp(*argv, NULL, 0, primary->h / 6)) == INT_MIN) { 851 | goto badvalue; 852 | } 853 | if ((r.bw = j) == 0 && border[BORD_WIDTH]) { 854 | r.state |= STATE_NOBORDER; 855 | } 856 | if (j) { 857 | r.state &= ~STATE_NOBORDER; 858 | } 859 | } else if (!strcmp(*argv, "float")) { 860 | CSTATE(STATE_FLOATING); 861 | } else if (!strcmp(*argv, "full")) { 862 | CSTATE(STATE_FULLSCREEN); 863 | } else if (!strcmp(*argv, "fakefull")) { 864 | CSTATE(STATE_FAKEFULL); 865 | } else if (!strcmp(*argv, "stick")) { 866 | CSTATE(STATE_STICKY | STATE_FLOATING); 867 | } else if (!strcmp(*argv, "ignore_cfg")) { 868 | CSTATE(STATE_IGNORECFG); 869 | } else if (!strcmp(*argv, "ignore_msg")) { 870 | CSTATE(STATE_IGNOREMSG); 871 | } else if (!strcmp(*argv, "terminal")) { 872 | CSTATE(STATE_TERMINAL); 873 | } else if (!strcmp(*argv, "no_absorb")) { 874 | CSTATE(STATE_NOABSORB); 875 | } else if (!strcmp(*argv, "scratch")) { 876 | CSTATE(STATE_SCRATCH); 877 | } else if (!strcmp(*argv, "focus")) { 878 | argv++, nparsed++; 879 | if (!argv || (j = parsebool(*argv)) < 0) { 880 | goto badvalue; 881 | } 882 | r.focus = j; 883 | } else if (!strcmp("apply", *argv)) { 884 | apply = 1; 885 | if (!strcmp("*", *(argv + 1))) { 886 | nparsed += 2; 887 | goto applyall; 888 | } 889 | } else if (!strcmp("remove", *argv)) { 890 | delete = 1; 891 | if (!strcmp("*", *(argv + 1))) { 892 | nparsed += 2; 893 | while (rules) { 894 | freerule(rules); 895 | } 896 | return nparsed; 897 | } 898 | } else { 899 | break; 900 | badvalue: 901 | respond(cmdresp, "!rule: invalid value for %s: %s", *(argv - 1), *argv); 902 | return -1; 903 | } 904 | argv++, nparsed++; 905 | } 906 | 907 | if ((r.clss || r.inst || r.title || r.type) && 908 | (r.ws != -1 || r.mon || r.focus || r.cb || r.state != STATE_NONE || r.x != -1 || r.y != -1 || 909 | r.w != -1 || r.h != -1 || r.bw != -1 || r.xgrav != GRAV_NONE || r.ygrav != GRAV_NONE)) { 910 | #define M(a, b) (a == NULL || (b && !strcmp(a, b))) 911 | 912 | for (pr = rules; pr; pr = pr->next) { /* free any existing rule that uses the same matches */ 913 | if (M(r.clss, pr->clss) && M(r.inst, pr->inst) && M(r.title, pr->title)) { 914 | freerule(pr); 915 | break; 916 | } 917 | } 918 | 919 | if (!delete) { 920 | if ((nr = initrule(&r)) && apply) { 921 | applyall: 922 | for (ws = workspaces; ws; ws = ws->next) { 923 | for (c = ws->clients; c; c = c->next) { 924 | clientrule(c, nr, 0); 925 | if (c->cb) { 926 | c->cb->func(c, 0); 927 | } 928 | } 929 | } 930 | needsrefresh = 1; 931 | } 932 | } 933 | } 934 | return nparsed; 935 | #undef CSTATE 936 | #undef ARG 937 | #undef STR 938 | #undef M 939 | } 940 | 941 | int cmdscratch(char **argv) 942 | { 943 | int nparsed = 0; 944 | Client *c = cmdc; 945 | 946 | if (argv && *argv) { 947 | DBG("cmdscratch: got arg %s", *argv) 948 | if (!strcmp("pop", *argv)) { 949 | argv++, nparsed++; 950 | if (!scratch.clients) { 951 | respond(cmdresp, "!no scratch clients to pop"); 952 | return nparsed; 953 | } 954 | if (c && !STATE(c, SCRATCH)) { 955 | c = scratch.clients; 956 | } 957 | pop: 958 | c->state &= ~(STATE_SCRATCH | STATE_HIDDEN); 959 | c->old_state = c->state | STATE_SCRATCH; 960 | c->state |= STATE_NEEDSMAP; 961 | setworkspace(c, selws, 0); 962 | winmap(c->win, &c->state); 963 | showhide(selws->stack); 964 | goto end; 965 | } else if (!strcmp("push", *argv)) { 966 | argv++, nparsed++; 967 | if (!c) { 968 | respond(cmdresp, "!no clients to scratch push"); 969 | return nparsed; 970 | } 971 | push: 972 | if (FULLSCREEN(c)) { 973 | respond(cmdresp, "!unable to scratch fullscreen windows"); 974 | return nparsed; 975 | } 976 | if (c == selws->sel) { 977 | unfocus(c, 1); 978 | } 979 | if (!FLOATING(c)) { 980 | Monitor *m = MON(c); 981 | c->state |= STATE_FLOATING; 982 | c->w = m->ww / 3; 983 | c->x = m->wx + c->w; 984 | c->y = m->wy; 985 | c->h = m->wh / 3; 986 | } 987 | c->state |= STATE_SCRATCH | STATE_HIDDEN | STATE_FLOATING; 988 | /* setworkspace() wont work for scratch push so we do our own swap */ 989 | detach(c, 0); 990 | detachstack(c); 991 | c->ws = &scratch; 992 | attach(c, 1); 993 | attachstack(c); 994 | winunmap(c->win); 995 | goto end; 996 | } 997 | respond(cmdresp, "!invalid scratch command: %s\nexpected pop or push", *argv); 998 | return -1; 999 | } else if (cmdc_passed) { 1000 | if (STATE(c, SCRATCH)) { 1001 | goto pop; 1002 | } 1003 | goto push; 1004 | } else if (scratch.clients) { 1005 | c = scratch.clients; 1006 | goto pop; 1007 | } else { 1008 | for (Workspace *ws = workspaces; ws; ws = ws->next) { 1009 | for (c = ws->clients; c; c = c->next) { 1010 | if ((c->old_state & STATE_SCRATCH) && FLOATING(c) && !FULLSCREEN(c)) { 1011 | if (c->ws == selws) { 1012 | goto push; 1013 | } 1014 | c->old_state = c->state | STATE_SCRATCH; 1015 | goto pop; /* on another workspace so bring it to us */ 1016 | } 1017 | } 1018 | } 1019 | /* if all else fails we push the active window */ 1020 | if (selws->sel) { 1021 | c = selws->sel; 1022 | goto push; 1023 | } 1024 | } 1025 | 1026 | end: 1027 | xcb_flush(con); 1028 | needsrefresh = winchange = wschange = 1; 1029 | return nparsed; 1030 | } 1031 | 1032 | int cmdsend(Workspace *ws) 1033 | { 1034 | Client *c = cmdc; 1035 | 1036 | if (ws && c && ws != c->ws) { 1037 | DBG("cmdsend: sending client: 0x%08x %s -- to workspace %d monitor %s", c->win, c->title, ws->num + 1, 1038 | ws->mon->name) 1039 | Monitor *old = MON(c); 1040 | unfocus(c, 1); 1041 | setworkspace(c, ws, c != c->ws->sel); 1042 | if (ws->mon != old && ws->mon->ws == ws) { 1043 | DBG("cmdsend: relocating window: 0x%08x %s -- from %s to %s", c->win, c->title, old->name, 1044 | ws->mon->name) 1045 | relocate(c, ws->mon, old); 1046 | } 1047 | if (FLOATING(c)) { 1048 | DBG("cmdsend: resizing floating window: 0x%08x %s x=%d, y=%d, " 1049 | "w=%d, h=%d", 1050 | c->win, c->title, c->x, c->y, c->w, c->h) 1051 | MOVERESIZE(c->win, c->x, c->y, c->w, c->h, c->bw); 1052 | } 1053 | showhide(ws->stack); 1054 | showhide(selws->stack); 1055 | needsrefresh = 1; 1056 | wschange = c->ws->clients->next ? wschange : 1; 1057 | } 1058 | return 0; 1059 | } 1060 | 1061 | int cmdset(char **argv) 1062 | { 1063 | uint32_t j; 1064 | Workspace *ws = NULL; 1065 | int i = -1, nparsed = 0, names = 0; 1066 | 1067 | if (!argv || !*argv) { 1068 | respond(cmdresp, "!set %s", enoargs); 1069 | return -1; 1070 | } 1071 | 1072 | setws = selws; 1073 | while (*argv) { 1074 | if (!strcmp("ws", *argv)) { 1075 | argv++, nparsed++; 1076 | if (!argv) { 1077 | goto badvalue; 1078 | } 1079 | if (!strcmp("_", *argv)) { 1080 | setws = &wsdef; 1081 | wsdef.mon = selmon; 1082 | if ((i = cmdws_(argv + 1)) == -1) { 1083 | return -1; 1084 | } 1085 | setws = selws; 1086 | argv += i + 1, nparsed += i + 1; 1087 | continue; 1088 | } else if (!(ws = parsewsormon(*argv, 0))) { 1089 | goto badvalue; 1090 | } 1091 | setws = ws; 1092 | } else if (!strcmp("mon", *argv)) { 1093 | argv++, nparsed++; 1094 | if (!globalcfg[GLB_WS_STATIC].val) { 1095 | respond(cmdresp, "!unable to set workspace monitor without static_ws=true"); 1096 | return -1; 1097 | } else if (!argv || !(ws = parsewsormon(*argv, 1))) { 1098 | respond(cmdresp, "!invalid monitor index or name: %s", argv ? *argv : NULL); 1099 | return -1; 1100 | } 1101 | assignws(setws, ws->mon); 1102 | } else if (!strcmp("name", *argv)) { 1103 | argv++, nparsed++; 1104 | if (!argv || !*argv) { 1105 | goto badvalue; 1106 | } 1107 | if (strcmp(setws->name, *argv)) { 1108 | strlcpy(setws->name, *argv, sizeof(setws->name)); 1109 | names = 1; 1110 | wschange = 1; 1111 | } 1112 | } else { 1113 | int match = 0; 1114 | for (j = 0; j < LEN(globalcfg); j++) { 1115 | if ((match = !strcmp(globalcfg[j].str, *argv))) { 1116 | argv++, nparsed++; 1117 | needsrefresh = needsrefresh || globalcfg[j].val != i; 1118 | switch (globalcfg[j].type) { 1119 | case TYPE_BOOL: 1120 | if (!argv || (i = parsebool(*argv)) < 0) { 1121 | goto badvalue; 1122 | } 1123 | globalcfg[j].val = i; 1124 | if (j == GLB_OBEY_MOTIF) { 1125 | clientmotif(); 1126 | } 1127 | break; 1128 | case TYPE_NUMWS: 1129 | if (!argv || (i = parseintclamp(*argv, NULL, 1, 256)) == INT_MIN) { 1130 | goto badvalue; 1131 | } 1132 | if (i > globalcfg[j].val) { 1133 | updworkspaces(i); 1134 | } 1135 | break; 1136 | case TYPE_INT: 1137 | if (!argv || (i = parseintclamp(*argv, NULL, 1, 10000)) == INT_MIN) { 1138 | goto badvalue; 1139 | } 1140 | globalcfg[j].val = i; 1141 | break; 1142 | } 1143 | argv++, nparsed++; 1144 | break; 1145 | } 1146 | } 1147 | if (!match) { 1148 | for (j = 0; setcmds[j].str; j++) { 1149 | if ((match = !strcmp(setcmds[j].str, *argv))) { 1150 | argv++, nparsed++; 1151 | if (!argv || !*argv) { 1152 | respond(cmdresp, "!missing next argument for %s", *(argv - 1)); 1153 | return -1; 1154 | } 1155 | if ((i = setcmds[j].func(argv)) == -1) { 1156 | return -1; 1157 | } 1158 | argv += i, nparsed += i; 1159 | break; 1160 | } 1161 | } 1162 | } 1163 | if (match) { 1164 | continue; 1165 | } 1166 | break; 1167 | badvalue: 1168 | respond(cmdresp, "!set: invalid value for %s: %s", *(argv - 1), *argv); 1169 | return -1; 1170 | } 1171 | argv++, nparsed++; 1172 | } 1173 | if (names) { 1174 | setnetwsnames(); 1175 | } 1176 | return nparsed; 1177 | } 1178 | 1179 | int cmdsplit(char **argv) 1180 | { 1181 | int rel = 0; 1182 | float f = 0.0; 1183 | 1184 | if ((f = parsefloat(*argv, &rel)) && f != NAN) { 1185 | float *ff = !strcmp("msplit", *(argv - 1)) ? &setws->msplit : &setws->ssplit; 1186 | if (setws->layout->func && f != 0.0) { 1187 | float nf = rel ? CLAMP(f + *ff, 0.05, 0.95) : CLAMP(f, 0.05, 0.95); 1188 | if (nf != *ff) { 1189 | *ff = nf, needsrefresh = 1; 1190 | } 1191 | } 1192 | return 1; 1193 | } 1194 | return -1; 1195 | } 1196 | 1197 | int cmdstatus(char **argv) 1198 | { 1199 | int i, nparsed = 0; 1200 | Status s = {.num = -1, .type = STAT_BAR, .file = cmdresp, .path = NULL, .next = NULL}; 1201 | 1202 | while (*argv) { 1203 | if (!strcmp("type", *argv)) { 1204 | argv++, nparsed++; 1205 | if (!*argv) { 1206 | goto badvalue; 1207 | } else if (!strcmp("bar", *argv)) { 1208 | s.type = STAT_BAR; 1209 | } else if (!strcmp("win", *argv)) { 1210 | s.type = STAT_WIN, winchange = 1; 1211 | } else if (!strcmp("ws", *argv)) { 1212 | s.type = STAT_WS, wschange = 1; 1213 | } else if (!strcmp("layout", *argv)) { 1214 | s.type = STAT_LYT, lytchange = 1; 1215 | } else if (!strcmp("full", *argv)) { 1216 | s.type = STAT_FULL; 1217 | } else { 1218 | goto badvalue; 1219 | } 1220 | } else if (!strcmp("num", *argv)) { 1221 | argv++, nparsed++; 1222 | if (!argv || (i = parseintclamp(*argv, NULL, -1, INT_MAX)) == INT_MIN) { 1223 | goto badvalue; 1224 | } 1225 | s.num = i; 1226 | } else if (!strcmp("file", *argv)) { 1227 | argv++, nparsed++; 1228 | if (!*argv) { 1229 | goto badvalue; 1230 | } 1231 | s.path = *argv; 1232 | } else { 1233 | break; 1234 | badvalue: 1235 | respond(cmdresp, "!status: invalid or missing value for %s: %s", *(argv - 1), *argv); 1236 | return -1; 1237 | } 1238 | argv++, nparsed++; 1239 | } 1240 | 1241 | if (s.path && s.path[0] && !(s.file = fopen(s.path, "w"))) { 1242 | respond(cmdresp, "!unable to open file in write mode: %s: %s", s.path, strerror(errno)); 1243 | } 1244 | if (s.file) { 1245 | if (s.num == 1) { 1246 | printstatus(&s, 0); 1247 | } else { 1248 | status_usingcmdresp = s.file == cmdresp; 1249 | printstatus(initstatus(&s), 1); 1250 | } 1251 | } else { 1252 | respond(cmdresp, "!unable to create status: %s", s.path ? s.path : "stdout"); 1253 | } 1254 | return nparsed; 1255 | } 1256 | 1257 | int cmdstick(__attribute__((unused)) char **argv) 1258 | { 1259 | Client *c = cmdc; 1260 | 1261 | if (FULLSCREEN(c)) { 1262 | respond(cmdresp, "!unable to change sticky state of fullscreen windows"); 1263 | return 0; 1264 | } 1265 | if (STATE(c, STICKY)) { 1266 | c->state &= ~STATE_STICKY; 1267 | PROP(REPLACE, c->win, netatom[NET_WM_DESK], XCB_ATOM_CARDINAL, 32, 1, &c->ws->num); 1268 | } else { 1269 | cmdfloat(NULL); 1270 | c->state |= STATE_STICKY | STATE_FLOATING; 1271 | PROP(REPLACE, c->win, netatom[NET_WM_DESK], XCB_ATOM_CARDINAL, 32, 1, &(uint32_t){0xffffffff}); 1272 | } 1273 | return 0; 1274 | } 1275 | 1276 | int cmdswap(__attribute__((unused)) char **argv) 1277 | { 1278 | static Client *last = NULL; 1279 | Client *c = cmdc, *old, *cur = NULL, *prev = NULL; 1280 | 1281 | if (FLOATING(c) || FULLSCREEN(c) || tilecount(c->ws) <= 1) { 1282 | respond(cmdresp, "!unable to swap floating, fullscreen, or single tiled windows"); 1283 | return 0; 1284 | } 1285 | if (c == nexttiled(c->ws->clients)) { 1286 | PREV(cur, last, c->ws->clients); 1287 | if (cur != c->ws->clients) { 1288 | prev = nexttiled(cur->next); 1289 | } 1290 | if (!prev || prev != last) { 1291 | last = NULL; 1292 | if (!(c = nexttiled(c->next))) { 1293 | return 0; 1294 | } 1295 | } else { 1296 | c = prev; 1297 | } 1298 | } 1299 | if (c != (old = nexttiled(c->ws->clients)) && !cur) { 1300 | PREV(cur, c, c->ws->clients); 1301 | } 1302 | detach(c, 1); 1303 | if (c != old && cur && cur != c->ws->clients) { 1304 | last = old; 1305 | if (old && cur != old) { 1306 | detach(old, 0); 1307 | ATTACH(old, cur->next); 1308 | } 1309 | } 1310 | needsrefresh = 1; 1311 | return 0; 1312 | } 1313 | 1314 | int cmdview(Workspace *ws) 1315 | { 1316 | if (ws) { 1317 | DBG("cmdview: viewing workspace %d : monitor %s", ws->num + 1, ws->mon->name) 1318 | changews(ws, globalcfg[GLB_WS_STATIC].val ? 0 : !cmdusemon, 1319 | cmdusemon || (globalcfg[GLB_WS_STATIC].val && selws->mon != ws->mon)); 1320 | } 1321 | return 0; 1322 | } 1323 | 1324 | int cmdwin(char **argv) 1325 | { 1326 | Client *c; 1327 | int e = 0, nparsed = 0; 1328 | cmdc_passed = 0; 1329 | 1330 | while (*argv) { 1331 | if ((c = parseclient(*argv, &e))) { 1332 | cmdc = c; 1333 | cmdc_passed = 1; 1334 | } else if (e == -1) { 1335 | respond(cmdresp, "!invalid window id: %s\nexpected hex e.g. 0x001fefe7", *argv); 1336 | return -1; 1337 | } else { 1338 | int match = 0; 1339 | for (uint32_t i = 0; wincmds[i].str; i++) { 1340 | if ((match = !strcmp(wincmds[i].str, *argv))) { 1341 | if ((wincmds[i].func != cmdscratch && !cmdc) || (e = wincmds[i].func(argv + 1)) == -1) { 1342 | return -1; 1343 | } 1344 | nparsed += e; 1345 | argv += e; 1346 | break; 1347 | } 1348 | } 1349 | if (!match) { 1350 | break; 1351 | } 1352 | } 1353 | argv++, nparsed++; 1354 | } 1355 | return nparsed; 1356 | } 1357 | 1358 | int cmdws(char **argv) 1359 | { 1360 | return (workspaces && workspaces->next) ? adjustwsormon(argv) : 0; 1361 | } 1362 | 1363 | int cmdws_(char **argv) 1364 | { 1365 | float f; 1366 | uint32_t i; 1367 | int j, nparsed = 0, apply = 0; 1368 | 1369 | while (*argv) { 1370 | int *s; 1371 | float *ff; 1372 | if (!strcmp(*argv, "apply")) { 1373 | apply = 1; 1374 | } else if (!strcmp(*argv, "layout")) { 1375 | argv++, nparsed++; 1376 | int match = 0; 1377 | for (i = 0; layouts[i].name; i++) { 1378 | if ((match = !strcmp(layouts[i].name, *argv))) { 1379 | wsdef.layout = &layouts[i]; 1380 | break; 1381 | } 1382 | } 1383 | if (!match) { 1384 | goto badvalue; 1385 | } 1386 | } else if ((s = !strcmp(*argv, "master") ? &wsdef.nmaster 1387 | : !strcmp(*argv, "stack") ? &wsdef.nstack 1388 | : NULL)) { 1389 | argv++, nparsed++; 1390 | if (!argv || (j = parseintclamp(*argv, NULL, 0, INT_MAX - 1)) == INT_MIN) { 1391 | goto badvalue; 1392 | } 1393 | *s = j; 1394 | } else if ((ff = !strcmp(*argv, "msplit") ? &wsdef.msplit 1395 | : !strcmp(*argv, "ssplit") ? &wsdef.ssplit 1396 | : NULL)) { 1397 | argv++, nparsed++; 1398 | if (!argv || (f = parsefloat(*argv, NULL)) == NAN) { 1399 | goto badvalue; 1400 | } 1401 | *ff = CLAMP(f, 0.05, 0.95); 1402 | } else if (!strcmp(*argv, "gap")) { 1403 | argv++, nparsed++; 1404 | if (!argv || (j = parseintclamp(*argv, NULL, 0, primary->h / 6)) == INT_MIN) { 1405 | goto badvalue; 1406 | } 1407 | wsdef.gappx = j; 1408 | } else if (!strcmp(*argv, "pad")) { 1409 | setws = &wsdef; 1410 | j = cmdpad(argv + 1); 1411 | setws = selws; 1412 | if (j == -1) { 1413 | return -1; 1414 | } 1415 | nparsed += j; 1416 | argv += j; 1417 | } else { 1418 | break; 1419 | badvalue: 1420 | respond(cmdresp, "!set ws=_: invalid value for %s: %s", *(argv - 1), *argv); 1421 | return -1; 1422 | } 1423 | argv++, nparsed++; 1424 | } 1425 | 1426 | if (apply) { 1427 | Workspace *ws; 1428 | for (ws = workspaces; ws; ws = ws->next) { 1429 | lytchange = lytchange || ws->layout != wsdef.layout; 1430 | ws->layout = wsdef.layout; 1431 | ws->gappx = wsdef.gappx; 1432 | ws->nmaster = wsdef.nmaster; 1433 | ws->nstack = wsdef.nstack; 1434 | ws->msplit = wsdef.msplit; 1435 | ws->ssplit = wsdef.ssplit; 1436 | ws->padl = wsdef.padl; 1437 | ws->padr = wsdef.padr; 1438 | ws->padt = wsdef.padt; 1439 | ws->padb = wsdef.padb; 1440 | } 1441 | } 1442 | return nparsed; 1443 | } 1444 | -------------------------------------------------------------------------------- /src/cmd.h: -------------------------------------------------------------------------------- 1 | /* dk window manager 2 | * 3 | * see license file for copyright and license details 4 | * vim:ft=c:fdm=syntax:ts=4:sts=4:sw=4 5 | */ 6 | 7 | #pragma once 8 | 9 | int adjustisetting(int i, int rel, int *val, int other, int border); 10 | int adjustwsormon(char **argv); 11 | int cmdborder(char **argv); 12 | int cmdcycle(__attribute__((unused)) char **argv); 13 | int cmdexit(__attribute__((unused)) char **argv); 14 | int cmdfakefull(__attribute__((unused)) char **argv); 15 | int cmdfloat(char **argv); 16 | int cmdfocus(char **argv); 17 | int cmdfollow(Workspace *ws); 18 | int cmdfull(__attribute__((unused)) char **argv); 19 | int cmdgappx(char **argv); 20 | int cmdkill(__attribute__((unused)) char **argv); 21 | int cmdlayout(char **argv); 22 | int cmdmon(char **argv); 23 | int cmdmors(char **argv); 24 | int cmdmouse(char **argv); 25 | int cmdmvstack(char **argv); 26 | int cmdpad(char **argv); 27 | int cmdresize(char **argv); 28 | int cmdrestart(__attribute__((unused)) char **argv); 29 | int cmdrule(char **argv); 30 | int cmdscratch(char **argv); 31 | int cmdsend(Workspace *ws); 32 | int cmdset(char **argv); 33 | int cmdsplit(char **argv); 34 | int cmdstatus(char **argv); 35 | int cmdstick(__attribute__((unused)) char **argv); 36 | int cmdswap(char **argv); 37 | int cmdview(Workspace *ws); 38 | int cmdwin(char **argv); 39 | int cmdws(char **argv); 40 | int cmdws_(char **argv); 41 | -------------------------------------------------------------------------------- /src/config.def.h: -------------------------------------------------------------------------------- 1 | /* In order to customize settings or add new commands 2 | * copy this file to config.h and edit it 3 | * 4 | * see license file for copyright and license details */ 5 | #pragma once 6 | 7 | uint32_t border[BORD_LAST] = { 8 | [BORD_WIDTH] = 1, /* int: total border width in pixels */ 9 | [BORD_FOCUS] = 0xFF6699cc, /* hex: focused window border colour (inner) */ 10 | [BORD_URGENT] = 0xFFee5555, /* hex: urgent window border colour (inner) */ 11 | [BORD_UNFOCUS] = 0xFF444444, /* hex: unfocused window border colour (inner) */ 12 | [BORD_O_WIDTH] = 0, /* int: outer border width in pixels */ 13 | [BORD_O_FOCUS] = 0xFF222222, /* hex: focused window border colour (outer) */ 14 | [BORD_O_URGENT] = 0xFF222222, /* hex: urgent window border colour (outer) */ 15 | [BORD_O_UNFOCUS] = 0xFF222222, /* hex: unfocused window border colour (outer) */ 16 | }; 17 | 18 | GlobalCfg globalcfg[GLB_LAST] = { 19 | /* setting value, type, string */ 20 | [GLB_FOCUS_MOUSE] = {1, TYPE_BOOL, "focus_mouse"}, /* enable focus follows mouse */ 21 | [GLB_FOCUS_OPEN] = {1, TYPE_BOOL, "focus_open"}, /* enable focus on open */ 22 | [GLB_FOCUS_URGENT] = {1, TYPE_BOOL, "focus_urgent"}, /* enable focus urgent windows */ 23 | [GLB_MIN_WH] = {50, TYPE_INT, "win_minwh"}, /* minimum window size allowed when resizing */ 24 | [GLB_MIN_XY] = {10, TYPE_INT, "win_minxy"}, /* minimum window area allowed inside the screen when moving */ 25 | [GLB_NUM_WS] = {0, TYPE_NUMWS, "numws"}, /* number of workspaces currently allocated */ 26 | [GLB_SMART_BORDER] = {1, TYPE_BOOL, "smart_border"}, /* disable borders in layouts with only one visible window */ 27 | [GLB_SMART_GAP] = {1, TYPE_BOOL, "smart_gap"}, /* disable gaps in layouts with only one visible window */ 28 | [GLB_TILE_HINTS] = {0, TYPE_BOOL, "tile_hints"}, /* respect size hints in tiled layouts */ 29 | [GLB_TILE_TOHEAD] = {0, TYPE_BOOL, "tile_tohead"}, /* place new clients at the tail of the stack */ 30 | [GLB_WS_STATIC] = {0, TYPE_BOOL, "static_ws"}, /* use static workspace assignment */ 31 | [GLB_OBEY_MOTIF] = {1, TYPE_BOOL, "obey_motif"}, /* obey motif_wm_hints for border drawing on supported windows */ 32 | }; 33 | 34 | /* default modifier and buttons for mouse move/resize */ 35 | xcb_mod_mask_t mousemod = XCB_MOD_MASK_1; 36 | xcb_button_t mousemove = XCB_BUTTON_INDEX_1; 37 | xcb_button_t mouseresize = XCB_BUTTON_INDEX_3; 38 | 39 | const char *cursors[] = { 40 | /* see: https://tronche.com/gui/x/xlib/appendix/b/ */ 41 | [CURS_MOVE] = "fleur", 42 | [CURS_NORMAL] = "left_ptr", 43 | [CURS_RESIZE] = "sizing", 44 | }; 45 | 46 | void albumart(Client *c, int closed) 47 | { 48 | /* 49 | * basic example of a user-defined callback 50 | * 51 | * on open: apply padding, gravitate the window to the right-center, and 52 | * avoid focus grab. 53 | * on close: remove padding 54 | */ 55 | 56 | if (closed) { 57 | c->ws->padr = 0; 58 | } else { 59 | c->ws->padr = W(c) + (2 * c->ws->gappx); 60 | gravitate(c, GRAV_RIGHT, GRAV_CENTER, 1); 61 | focus(c->snext); 62 | } 63 | } 64 | 65 | void popfull(__attribute__((unused)) Client *c, int closed) 66 | { 67 | /* 68 | * basic example of a user-defined callback 69 | * 70 | * on open: disable active window fullscreen 71 | * on close: nothing 72 | */ 73 | 74 | if (!closed && c->ws->sel && FULLSCREEN(c->ws->sel)) 75 | setfullscreen(c->ws->sel, 0); 76 | } 77 | 78 | int focusmaster(__attribute__((unused)) char **argv) 79 | { 80 | /* 81 | * basic example of a new `win` command 82 | * 83 | * (re)focus the master window on the current workspace 84 | */ 85 | focus(nexttiled(selws->clients)); 86 | return 0; 87 | } 88 | 89 | int tstack(Workspace *ws) 90 | { 91 | /* 92 | * basic example of a new user-defined layout 93 | * 94 | * an inverted version of the bottomstack layout for dwm: 95 | * https://dwm.suckless.org/patches/bottomstack/ 96 | * 97 | * additions to work with dk padding, gaps, and other features. 98 | */ 99 | 100 | Client *c; 101 | int i, n, g, mh, mw, mx, sx, sw = 0; 102 | 103 | if (!(n = tilecount(ws))) 104 | return 1; 105 | 106 | /* apply the workspace padding */ 107 | int wx = ws->mon->wx + ws->padl; 108 | int wy = ws->mon->wy + ws->padt; 109 | int ww = ws->mon->ww - ws->padl - ws->padr; 110 | int wh = ws->mon->wh - ws->padt - ws->padb; 111 | 112 | /* apply smart gap */ 113 | if (globalcfg[GLB_SMART_GAP].val && n == 1) 114 | g = 0, ws->smartgap = 1; 115 | else 116 | g = ws->gappx, ws->smartgap = 0; 117 | mw = (ww - g) / MAX(1, ws->nmaster); 118 | mh = wh - g; 119 | 120 | /* adjust sizes for master instances */ 121 | if (n > ws->nmaster) { 122 | mh = ws->nmaster ? (ws->msplit * wh) - (g / 2) : 0; 123 | sw = (ww - g) / (n - ws->nmaster); 124 | } 125 | 126 | for (i = 0, mx = sx = wx + g, c = nexttiled(ws->clients); c; 127 | c = nexttiled(c->next), i++) { 128 | /* apply smart border */ 129 | int bw = !globalcfg[GLB_SMART_BORDER].val || n > 1 ? c->bw : 0; 130 | if (i < ws->nmaster) { /* master windows */ 131 | resizehint(c, mx, (wy + wh) - mh, mw - g - (2 * bw), 132 | mh - g - (2 * bw), bw, 0, 0); 133 | mx += W(c) + g; 134 | } else { /* stack windows */ 135 | resizehint(c, sx, wy + g, sw - g - (2 * bw), 136 | wh - (mh + (2 * g)) - (2 * bw), bw, 0, 0); 137 | sx += W(c) + g; 138 | } 139 | } 140 | xcb_aux_sync(con); 141 | return 1; 142 | } 143 | 144 | int dyntile(Workspace *ws) 145 | { 146 | /* 147 | * basic example of a new user-defined layout 148 | * 149 | * will switch to grid when the number of tiled clients on a workspace == 4 150 | * 151 | */ 152 | if (tilecount(ws) == 4) { 153 | ws->layout->implements_resize = 0; 154 | ws->layout->tile_resize = 0; 155 | ws->layout->name = "grid"; 156 | return grid(ws); 157 | } 158 | ws->layout->implements_resize = 1; 159 | ws->layout->tile_resize = 1; 160 | ws->layout->name = "tile"; 161 | return ltile(ws); 162 | } 163 | 164 | /* New commands and callbacks must be added to the below arrays in order to 165 | * use them */ 166 | 167 | Callback callbacks[] = { 168 | /* command, function */ 169 | {"albumart", albumart}, 170 | {"popfull", popfull }, 171 | 172 | /* don't add below the terminating null */ 173 | {NULL, NULL } 174 | }; 175 | 176 | Cmd keywords[] = { 177 | /* command, function */ 178 | {"win", cmdwin }, 179 | {"ws", cmdws }, 180 | {"mon", cmdmon }, 181 | {"set", cmdset }, 182 | {"rule", cmdrule }, 183 | {"status", cmdstatus }, 184 | {"exit", cmdexit }, 185 | {"restart", cmdrestart}, 186 | 187 | /* don't add below the terminating null */ 188 | {NULL, NULL } 189 | }; 190 | 191 | Cmd setcmds[] = { 192 | /* command, function */ 193 | {"layout", cmdlayout}, 194 | {"master", cmdmors }, 195 | {"stack", cmdmors }, 196 | {"msplit", cmdsplit }, 197 | {"ssplit", cmdsplit }, 198 | {"border", cmdborder}, 199 | {"gap", cmdgappx }, 200 | {"pad", cmdpad }, 201 | {"mouse", cmdmouse }, 202 | 203 | /* don't add below the terminating null */ 204 | {NULL, NULL } 205 | }; 206 | 207 | Cmd wincmds[] = { 208 | /* command, function */ 209 | {"focus", cmdfocus }, 210 | {"kill", cmdkill }, 211 | {"resize", cmdresize }, 212 | {"mvstack", cmdmvstack }, 213 | {"swap", cmdswap }, 214 | {"float", cmdfloat }, 215 | {"full", cmdfull }, 216 | {"cycle", cmdcycle }, 217 | {"stick", cmdstick }, 218 | {"scratch", cmdscratch }, 219 | {"fakefull", cmdfakefull}, 220 | {"focusm", focusmaster}, 221 | 222 | /* don't add below the terminating null */ 223 | {NULL, NULL } 224 | }; 225 | 226 | Layout layouts[] = { 227 | /* name, function, implements_resize, invert_split_direction, tile_resize */ 228 | {"tile", ltile, 1, 0, 1}, /* first is default */ 229 | {"rtile", rtile, 1, 1, 1}, 230 | {"mono", mono, 0, 0, 0}, 231 | {"grid", grid, 0, 0, 0}, 232 | {"spiral", spiral, 1, 0, 0}, 233 | {"dwindle", dwindle, 1, 0, 0}, 234 | {"none", NULL, 1, 0, 0}, /* NULL layout function is floating */ 235 | {"tstack", tstack, 1, 1, 0}, 236 | {"dyntile", dyntile, 1, 0, 1}, 237 | /* don't add below the terminating null */ 238 | {NULL, NULL, 0, 0, 0} 239 | }; 240 | 241 | WsCmd wscmds[] = { 242 | {"view", cmdview }, 243 | {"follow", cmdfollow}, 244 | {"send", cmdsend }, 245 | 246 | /* don't add below the terminating null */ 247 | {NULL, NULL } 248 | }; 249 | 250 | /* workspaces defaults */ 251 | Workspace wsdef = { 252 | .nmaster = 1, 253 | .nstack = 3, 254 | .gappx = 0, 255 | .smartgap = 1, 256 | .padl = 0, 257 | .padr = 0, 258 | .padt = 0, 259 | .padb = 0, 260 | .msplit = 0.5, 261 | .ssplit = 0.5, 262 | .layout = &layouts[0], 263 | }; 264 | -------------------------------------------------------------------------------- /src/dk.h: -------------------------------------------------------------------------------- 1 | /* dk window manager 2 | * 3 | * see license file for copyright and license details 4 | * vim:ft=c:fdm=syntax:ts=4:sts=4:sw=4 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #ifdef DEBUG 29 | #define DBG(fmt, ...) warnx("%d: " fmt, __LINE__, __VA_ARGS__); 30 | #else 31 | #define DBG(fmt, ...) 32 | #endif 33 | 34 | #ifndef VERSION 35 | #define VERSION "2.2" 36 | #endif 37 | 38 | #define NAN (0.0f / 0.0f) 39 | #define LEN(x) (sizeof(x) / sizeof(*x)) 40 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 41 | #define MAX(a, b) ((a) > (b) ? (a) : (b)) 42 | #define CLAMP(x, min, max) (MIN(MAX((x), (min)), (max))) 43 | #define INRECT(x, y, w, h, rx, ry, rw, rh) (x >= rx && x + w <= rx + rw && y >= ry && y + h <= ry + rh) 44 | #define W(c) (c->w + (2 * c->bw)) 45 | #define H(c) (c->h + (2 * c->bw)) 46 | #define MON(c) c->ws->mon 47 | #define STATE(v, s) ((v)->state & STATE_##s) 48 | #define VISIBLE(c) (c->ws == MON(c)->ws) 49 | #define FLOATING(c) (STATE(c, FLOATING) || !c->ws->layout->func) 50 | #define FULLSCREEN(c) (STATE(c, FULLSCREEN) && !STATE(c, FAKEFULL)) 51 | #define TAIL(v, list) for (v = list; v && v->next; v = v->next) 52 | #define PREV(v, cur, list) for (v = list; v && v->next && v->next != cur; v = v->next) 53 | 54 | #define ATTACH(v, list) \ 55 | do { \ 56 | v->next = list; \ 57 | list = v; \ 58 | } while (0) 59 | 60 | #define DETACH(v, listptr) \ 61 | do { \ 62 | while (*(listptr) && *(listptr) != v) (listptr) = &(*(listptr))->next; \ 63 | *(listptr) = v->next; \ 64 | } while (0) 65 | 66 | #define PROP(mode, win, atom, type, membsize, nmemb, value) \ 67 | xcb_change_property(con, XCB_PROP_MODE_##mode, win, atom, type, (membsize), (nmemb), (const void *)value); \ 68 | xcb_flush(con) 69 | 70 | #define MOVE(win, x, y) \ 71 | xcb_configure_window(con, win, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, (uint32_t[]){(x), (y)}) 72 | 73 | #define MOVERESIZE(win, x, y, w, h, bw) \ 74 | xcb_configure_window(con, win, \ 75 | XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | \ 76 | XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_BORDER_WIDTH, \ 77 | (uint32_t[]){(x), (y), MAX((w), globalcfg[GLB_MIN_WH].val), \ 78 | MAX((h), globalcfg[GLB_MIN_WH].val), (bw)}) 79 | 80 | enum States { 81 | STATE_NONE = 0, 82 | STATE_FAKEFULL = 1 << 0, 83 | STATE_FIXED = 1 << 1, 84 | STATE_FLOATING = 1 << 2, 85 | STATE_FULLSCREEN = 1 << 3, 86 | STATE_NOBORDER = 1 << 4, 87 | STATE_NOINPUT = 1 << 5, 88 | STATE_STICKY = 1 << 6, 89 | STATE_URGENT = 1 << 7, 90 | STATE_NEEDSMAP = 1 << 8, 91 | STATE_WASFLOATING = 1 << 9, 92 | STATE_IGNORECFG = 1 << 10, 93 | STATE_IGNOREMSG = 1 << 11, 94 | STATE_ABOVE = 1 << 12, 95 | STATE_HIDDEN = 1 << 13, 96 | STATE_SCRATCH = 1 << 14, 97 | STATE_TERMINAL = 1 << 15, 98 | STATE_NOABSORB = 1 << 16, 99 | }; 100 | 101 | enum Cursors { 102 | CURS_MOVE = 0, 103 | CURS_NORMAL = 1, 104 | CURS_RESIZE = 2, 105 | CURS_LAST = 3, 106 | }; 107 | 108 | enum Borders { 109 | BORD_WIDTH = 0, 110 | BORD_FOCUS = 1, 111 | BORD_URGENT = 2, 112 | BORD_UNFOCUS = 3, 113 | BORD_O_WIDTH = 4, 114 | BORD_O_FOCUS = 5, 115 | BORD_O_URGENT = 6, 116 | BORD_O_UNFOCUS = 7, 117 | BORD_LAST = 8, 118 | }; 119 | 120 | enum DirOpts { DIR_NEXT, DIR_PREV, DIR_LAST, DIR_NEXTNE, DIR_PREVNE, DIR_END }; 121 | 122 | enum WMAtoms { 123 | WM_DELETE = 0, 124 | WM_FOCUS = 1, 125 | WM_MOTIF = 2, 126 | WM_PROTO = 3, 127 | WM_STATE = 4, 128 | WM_UTF8STR = 5, 129 | WM_LAST = 6, 130 | }; 131 | 132 | enum NetAtoms { 133 | NET_ACTIVE = 0, 134 | NET_CLIENTS = 1, 135 | NET_CLOSE = 2, 136 | NET_DESK_CUR = 3, 137 | NET_DESK_GEOM = 4, 138 | NET_DESK_NAMES = 5, 139 | NET_DESK_NUM = 6, 140 | NET_DESK_VP = 7, 141 | NET_DESK_WA = 8, 142 | NET_STATE_ABOVE = 9, 143 | NET_STATE_DEMANDATT = 10, 144 | NET_STATE_FULL = 11, 145 | NET_SUPPORTED = 12, 146 | NET_TYPE_DESK = 13, 147 | NET_TYPE_DIALOG = 14, 148 | NET_TYPE_DOCK = 15, 149 | NET_TYPE_SPLASH = 16, 150 | NET_WM_CHECK = 17, 151 | NET_WM_DESK = 18, 152 | NET_WM_NAME = 19, 153 | NET_WM_STATE = 20, 154 | NET_WM_STRUT = 21, 155 | NET_WM_STRUTP = 22, 156 | NET_WM_TYPE = 23, 157 | NET_LAST = 24, 158 | }; 159 | 160 | enum Gravities { 161 | GRAV_NONE = 0, 162 | GRAV_LEFT = 1, 163 | GRAV_RIGHT = 2, 164 | GRAV_CENTER = 3, 165 | GRAV_TOP = 4, 166 | GRAV_BOTTOM = 5, 167 | GRAV_LAST = 6, 168 | }; 169 | 170 | enum StatusType { 171 | STAT_WS = 0, 172 | STAT_LYT = 1, 173 | STAT_WIN = 2, 174 | STAT_BAR = 3, 175 | STAT_FULL = 4, 176 | }; 177 | 178 | enum CfgType { 179 | TYPE_BOOL = 0, 180 | TYPE_NUMWS = 1, 181 | TYPE_INT = 2, 182 | }; 183 | 184 | enum GlobalSettings { 185 | GLB_NUM_WS = 0, 186 | GLB_WS_STATIC = 1, 187 | GLB_FOCUS_MOUSE = 2, 188 | GLB_FOCUS_OPEN = 3, 189 | GLB_FOCUS_URGENT = 4, 190 | GLB_MIN_WH = 5, 191 | GLB_MIN_XY = 6, 192 | GLB_SMART_BORDER = 7, 193 | GLB_SMART_GAP = 8, 194 | GLB_TILE_HINTS = 9, 195 | GLB_TILE_TOHEAD = 10, 196 | GLB_OBEY_MOTIF = 11, 197 | GLB_LAST = 12, 198 | }; 199 | 200 | typedef struct Callback Callback; 201 | typedef struct Workspace Workspace; 202 | 203 | typedef struct Monitor { 204 | char name[64]; 205 | int num, connected; 206 | int x, y, w, h; 207 | int wx, wy, ww, wh; 208 | xcb_randr_output_t id; 209 | struct Monitor *next; 210 | Workspace *ws; 211 | } Monitor; 212 | 213 | typedef struct Desk { 214 | uint32_t state; 215 | xcb_window_t win; 216 | char clss[64], inst[64]; 217 | struct Desk *next; 218 | Monitor *mon; 219 | } Desk; 220 | 221 | typedef struct Rule { 222 | int x, y, w, h, bw; 223 | int xgrav, ygrav; 224 | int ws, focus; 225 | uint32_t state; 226 | xcb_atom_t type; 227 | char *title, *clss, *inst, *mon; 228 | const Callback *cb; 229 | regex_t titlereg, clssreg, instreg; 230 | struct Rule *next; 231 | } Rule; 232 | 233 | typedef struct Panel { 234 | int x, y, w, h; 235 | int l, r, t, b; /* struts */ 236 | uint32_t state; 237 | char clss[64], inst[64]; 238 | xcb_window_t win; 239 | struct Panel *next; 240 | Monitor *mon; 241 | } Panel; 242 | 243 | typedef struct Status { 244 | int num; 245 | uint32_t type; 246 | FILE *file; 247 | char *path; 248 | struct Status *next; 249 | } Status; 250 | 251 | typedef struct Client { 252 | char title[256], clss[64], inst[64]; 253 | int32_t x, y, w, h, bw, hoff, depth, old_x, old_y, old_w, old_h, old_bw; 254 | int32_t max_w, max_h, min_w, min_h, base_w, base_h, inc_w, inc_h, hints; 255 | int32_t has_motif; 256 | pid_t pid; 257 | float min_aspect, max_aspect; 258 | uint32_t state, old_state; 259 | xcb_window_t win; 260 | Workspace *ws; 261 | const Callback *cb; 262 | struct Client *trans, *next, *snext, *absorbed; 263 | } Client; 264 | 265 | typedef struct Cmd { 266 | const char *str; 267 | int (*func)(char **); 268 | } Cmd; 269 | 270 | typedef struct WsCmd { 271 | const char *str; 272 | int (*func)(Workspace *); 273 | } WsCmd; 274 | 275 | typedef struct Layout { 276 | const char *name; 277 | int (*func)(Workspace *); 278 | int implements_resize; 279 | int invert_split_direction; 280 | int tile_resize; 281 | } Layout; 282 | 283 | typedef struct GlobalCfg { 284 | int val; 285 | enum CfgType type; 286 | char *str; 287 | } GlobalCfg; 288 | 289 | struct Callback { 290 | const char *name; 291 | void (*func)(Client *c, int closed); 292 | }; 293 | 294 | struct Workspace { 295 | int nmaster, nstack, gappx, smartgap; 296 | int padr, padl, padt, padb; 297 | float msplit, ssplit; 298 | Layout *layout; 299 | int num; 300 | char name[64]; 301 | Monitor *mon; 302 | Workspace *next; 303 | Client *sel, *stack, *clients; 304 | }; 305 | 306 | /* dk.c values */ 307 | extern FILE *cmdresp; 308 | extern uint32_t lockmask; 309 | extern char *argv0, **environ; 310 | extern int running, restart, needsrefresh, status_usingcmdresp, depth; 311 | extern int scr_h, scr_w, randrbase, cmdusemon, winchange, wschange, lytchange; 312 | 313 | extern Desk *desks; 314 | extern Rule *rules; 315 | extern Panel *panels; 316 | extern Status *stats; 317 | extern Client *cmdc; 318 | extern Monitor *monitors, *primary, *selmon, *lastmon; 319 | extern Workspace *workspaces, *setws, *selws, *lastws, scratch; 320 | 321 | extern xcb_window_t root; 322 | extern xcb_connection_t *con; 323 | extern xcb_key_symbols_t *keysyms; 324 | extern xcb_cursor_t cursor[CURS_LAST]; 325 | extern xcb_atom_t wmatom[WM_LAST], netatom[NET_LAST]; 326 | 327 | extern const char *ebadarg; 328 | extern const char *enoargs; 329 | extern const char *wmatoms[WM_LAST]; 330 | extern const char *netatoms[NET_LAST]; 331 | extern const char *cursors[CURS_LAST]; 332 | extern const char *dirs[DIR_END]; 333 | extern const char *gravs[GRAV_LAST]; 334 | extern const char *slayouts[]; 335 | 336 | /* config.h values */ 337 | extern uint32_t border[BORD_LAST]; 338 | extern GlobalCfg globalcfg[GLB_LAST]; 339 | extern xcb_mod_mask_t mousemod; 340 | extern xcb_button_t mousemove, mouseresize; 341 | extern Callback callbacks[]; 342 | extern Cmd keywords[]; 343 | extern Cmd setcmds[]; 344 | extern Cmd wincmds[]; 345 | extern Layout layouts[]; 346 | extern WsCmd wscmds[]; 347 | extern Workspace wsdef; 348 | 349 | int applysizehints(Client *c, int *x, int *y, int *w, int *h, int bw, int usermotion, int mouse); 350 | int assignws(Workspace *ws, Monitor *mon); 351 | void attach(Client *c, int tohead); 352 | void attachstack(Client *c); 353 | void changews(Workspace *ws, int swap, int warp); 354 | void clientborder(Client *c, int focused); 355 | void clienthints(Client *c); 356 | void clientmotif(void); 357 | int clientname(Client *c); 358 | void clientrule(Client *c, Rule *wr, int nofocus); 359 | void clienttype(Client *c); 360 | Monitor *coordtomon(int x, int y); 361 | void detach(Client *c, int reattach); 362 | void detachstack(Client *c); 363 | void execcfg(void); 364 | void fillstruts(Panel *p); 365 | void focus(Client *c); 366 | void freerule(Rule *r); 367 | void freestatus(Status *s); 368 | void freewm(void); 369 | void grabbuttons(Client *c); 370 | void gravitate(Client *c, int horz, int vert, int matchgap); 371 | int iferr(int lvl, char *msg, xcb_generic_error_t *e); 372 | Rule *initrule(Rule *wr); 373 | Status *initstatus(Status *tmp); 374 | Monitor *itomon(int num); 375 | Workspace *itows(int num); 376 | void manage(xcb_window_t win, int scan); 377 | void movestack(int direction); 378 | Monitor *nextmon(Monitor *m); 379 | Client *nexttiled(Client *c); 380 | void numlockmask(void); 381 | void popfloat(Client *c); 382 | void quadrant(Client *c, int *x, int *y, const int *w, const int *h); 383 | void refresh(void); 384 | void relocate(Client *c, Monitor *mon, Monitor *old); 385 | void resize(Client *c, int x, int y, int w, int h, int bw); 386 | void resizehint(Client *c, int x, int y, int w, int h, int bw, int usermotion, int mouse); 387 | void sendconfigure(Client *c); 388 | int sendwmproto(Client *c, xcb_atom_t proto); 389 | void setfullscreen(Client *c, int fullscreen); 390 | void setinputfocus(Client *c); 391 | void setnetstate(xcb_window_t win, uint32_t state); 392 | void setnetwsnames(void); 393 | void setstackmode(xcb_window_t win, uint32_t mode); 394 | void seturgent(Client *c, int urg); 395 | void setwinstate(xcb_window_t win, uint32_t state); 396 | void setworkspace(Client *c, Workspace *ws, int stacktail); 397 | void showhide(Client *c); 398 | void sizehints(Client *c, int uss); 399 | int tilecount(Workspace *ws); 400 | void unfocus(Client *c, int focusroot); 401 | void unmanage(xcb_window_t win, int destroyed); 402 | int updrandr(int init); 403 | void updstruts(void); 404 | void updworkspaces(int needed); 405 | void winmap(xcb_window_t win, uint32_t *state); 406 | Client *wintoclient(xcb_window_t win); 407 | Desk *wintodesk(xcb_window_t win); 408 | Panel *wintopanel(xcb_window_t win); 409 | xcb_window_t wintrans(xcb_window_t win); 410 | void winunmap(xcb_window_t win); 411 | 412 | #ifdef FUNCDEBUG 413 | void __cyg_profile_func_enter(void *fn, void *caller) __attribute__((no_instrument_function)); 414 | void __cyg_profile_func_exit(void *fn, void *caller) __attribute__((no_instrument_function)); 415 | #endif 416 | -------------------------------------------------------------------------------- /src/dkcmd.c: -------------------------------------------------------------------------------- 1 | /* dk window manager 2 | * 3 | * see license file for copyright and license details 4 | * vim:ft=c:fdm=syntax:ts=4:sts=4:sw=4 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "strl.h" 18 | #include "util.h" 19 | 20 | #ifndef VERSION 21 | #define VERSION "2.2" 22 | #endif 23 | 24 | #ifndef INDENT 25 | #define INDENT 2 26 | #endif 27 | 28 | static int json_pretty(int argc, char *argv[]) 29 | { 30 | size_t len = 0; 31 | FILE *f = stdin; 32 | char p, n, *c, *line = NULL; 33 | int lvl = 1, inkey = 0, instr = 0, inarr = 0, first = 1; 34 | 35 | if (argc && *argv && !(f = fopen(*argv, "r"))) { 36 | perror("open"); 37 | return 1; 38 | } 39 | 40 | while (getline(&line, &len, f) != -1 && (c = line)) { 41 | if (first) { 42 | first = 0; 43 | printf("%c\n", *c); 44 | p = *c++; 45 | } 46 | while (*c) { 47 | if (instr && (*c != '"' || *(c - 1) == '\\')) { 48 | printf("%c", *c++); 49 | continue; 50 | } 51 | n = *(c + 1); 52 | switch (*c) { 53 | case ',': 54 | printf("%c\n", *c); 55 | p = *c; 56 | break; 57 | case ':': 58 | printf("%c ", *c); 59 | inkey = 0; 60 | p = *c; 61 | break; 62 | case '"': 63 | if (instr) { 64 | instr = 0; 65 | printf("%c%s", *c, (n == '}' || n == ']') ? "\n" : ""); 66 | } else if (!instr && !inkey && (p == ',' || p == '{' || p == '[')) { 67 | if (inarr) { 68 | instr = 1; 69 | } else { 70 | inkey = 1; 71 | } 72 | printf("%*s%c", lvl * INDENT, lvl ? " " : "", *c); 73 | } else if (!instr && !inkey && p == ':') { 74 | instr = 1; 75 | printf("%c", *c); 76 | } else { 77 | printf("%c", *c); 78 | } 79 | p = *c; 80 | break; 81 | case '{': 82 | case '[': 83 | if (*c != '{') { 84 | inarr = 1; 85 | } 86 | if (n == (*c) + 2) { 87 | char nn = *(c + 2); 88 | printf("%c%c%s", *c, (*c) + 2, nn == ',' || (nn != ']' && nn != '}') ? "" : "\n"); 89 | c++; 90 | p = *c; 91 | } else if (p == ':') { 92 | printf("%c\n", *c); 93 | lvl++; 94 | p = *c; 95 | } else { 96 | printf("%*s%c%s", lvl * INDENT, lvl ? " " : "", *c, n != (*c) + 2 ? "\n" : ""); 97 | lvl++; 98 | p = *c; 99 | } 100 | break; 101 | case '}': 102 | case ']': 103 | if (*c != '}') { 104 | inarr = 0; 105 | } 106 | lvl--; 107 | printf("%*s%c%s", lvl * INDENT, lvl ? " " : "", *c, n != ',' && (n == '}' || n == ']') ? "\n" : ""); 108 | p = *c; 109 | break; 110 | default: 111 | /* handle array items that aren't strings or objects (booleans and numbers) */ 112 | if (inarr && (p == ',' || p == '[')) { 113 | printf("%*s%c%s", lvl * INDENT, lvl ? " " : "", *c, n == ']' ? "\n" : ""); 114 | } else if (n == '}' || (inarr && n == ']')) { 115 | printf("%c\n", *c); 116 | } else { 117 | printf("%c", *c); 118 | } 119 | break; 120 | } 121 | c++; 122 | } 123 | fflush(stdout); 124 | } 125 | free(line); 126 | fclose(f); 127 | return 0; 128 | } 129 | 130 | int main(int argc, char *argv[]) 131 | { 132 | if (argc == 1) { 133 | return usage(argv[0], VERSION, 1, 'h', "[-hv] "); 134 | } else if (!strcmp(argv[1], "-v") || !strcmp(argv[1], "-h")) { 135 | return usage(argv[0], VERSION, 0, argv[1][1], "[-hv] "); 136 | } else if (!strcmp(argv[1], "-p")) { 137 | return json_pretty(argc - 2, argv + 2); 138 | } 139 | 140 | size_t j = 0, n = 0; 141 | int i, fd, ret = 0, offs = 1; 142 | char *sock, *equal = NULL, *space = NULL, buf[BUFSIZ], resp[BUFSIZ]; 143 | struct sockaddr_un addr; 144 | struct pollfd fds[] = { 145 | {-1, POLLIN, 0}, 146 | {STDOUT_FILENO, POLLHUP, 0}, 147 | }; 148 | 149 | if (!(sock = getenv("DKSOCK"))) { 150 | err(1, "unable to get socket path from environment"); 151 | } 152 | addr.sun_family = AF_UNIX; 153 | check((fd = socket(AF_UNIX, SOCK_STREAM, 0)), "unable to create socket"); 154 | fds[0].fd = fd; 155 | strlcpy(addr.sun_path, sock, sizeof(addr.sun_path)); 156 | if (addr.sun_path[0] == '\0') { 157 | err(1, "unable to write socket path: %s", sock); 158 | } 159 | check(connect(fd, (struct sockaddr *)&addr, sizeof(addr)), "unable to connect socket"); 160 | 161 | for (i = 1, j = 0, offs = 1; n + 1 < sizeof(buf) && i < argc; i++, j = 0, offs = 1) { 162 | if ((space = strchr(argv[i], ' ')) || (space = strchr(argv[i], '\t'))) { 163 | if (!(equal = strchr(argv[i], '=')) || space < equal) { 164 | buf[n++] = '"'; 165 | } 166 | offs++; 167 | } 168 | while (n + offs + 1 < sizeof(buf) && argv[i][j]) { 169 | buf[n++] = argv[i][j++]; 170 | if (equal && space > equal && buf[n - 1] == '=') { 171 | buf[n++] = '"'; 172 | equal = NULL; 173 | } 174 | } 175 | if (offs > 1) { 176 | buf[n++] = '"'; 177 | } 178 | buf[n++] = ' '; 179 | } 180 | buf[n - 1] = '\0'; 181 | 182 | check(send(fd, buf, n, 0), "unable to send command"); 183 | 184 | ssize_t s; 185 | while (poll(fds, 2, -1) > 0) { 186 | if (fds[0].revents & POLLIN) { 187 | if ((s = recv(fd, resp, sizeof(resp) - 1, 0)) > 0) { 188 | resp[s] = '\0'; 189 | if ((ret = *resp == '!')) { 190 | fprintf(stderr, "%s: error: %s\n", argv[0], resp + 1); 191 | fflush(stderr); 192 | } else { 193 | fprintf(stdout, "%s\n", resp); 194 | fflush(stdout); 195 | } 196 | } else { 197 | break; 198 | } 199 | } 200 | if (fds[1].revents & (POLLERR | POLLHUP)) { 201 | break; 202 | } 203 | } 204 | close(fd); 205 | return ret; 206 | } 207 | -------------------------------------------------------------------------------- /src/event.c: -------------------------------------------------------------------------------- 1 | /* dk window manager 2 | * 3 | * see license file for copyright and license details 4 | * vim:ft=c:fdm=syntax:ts=4:sts=4:sw=4 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "dk.h" 19 | #include "cmd.h" 20 | #include "event.h" 21 | 22 | int released = 1, grabbing = 0; 23 | 24 | static void (*handlers[XCB_NO_OPERATION + 1])(xcb_generic_event_t *) = { 25 | [XCB_BUTTON_PRESS] = &buttonpress, 26 | [XCB_CLIENT_MESSAGE] = &clientmessage, 27 | [XCB_CONFIGURE_NOTIFY] = &confignotify, 28 | [XCB_CONFIGURE_REQUEST] = &configrequest, 29 | [XCB_DESTROY_NOTIFY] = &destroynotify, 30 | [XCB_ENTER_NOTIFY] = &enternotify, 31 | [XCB_FOCUS_IN] = &focusin, 32 | [XCB_MAPPING_NOTIFY] = &mappingnotify, 33 | [XCB_MAP_REQUEST] = &maprequest, 34 | [XCB_MOTION_NOTIFY] = &motionnotify, 35 | [XCB_PROPERTY_NOTIFY] = &propertynotify, 36 | [XCB_UNMAP_NOTIFY] = &unmapnotify, 37 | [XCB_NO_OPERATION] = NULL}; 38 | 39 | void buttonpress(xcb_generic_event_t *ev) 40 | { 41 | Client *c; 42 | xcb_generic_error_t *er; 43 | xcb_grab_pointer_cookie_t pc; 44 | xcb_button_press_event_t *e = (xcb_button_press_event_t *)ev; 45 | 46 | if (!(c = wintoclient(e->event))) { 47 | return; 48 | } 49 | if (c != selws->sel) { 50 | focus(c); 51 | } 52 | if (FLOATING(c) && (e->detail == mousemove || e->detail == mouseresize)) { 53 | setstackmode(c->win, XCB_STACK_MODE_ABOVE); 54 | } 55 | xcb_allow_events(con, XCB_ALLOW_REPLAY_POINTER, e->time); 56 | if ((e->state & ~(lockmask | XCB_MOD_MASK_LOCK)) == (mousemod & ~(lockmask | XCB_MOD_MASK_LOCK)) && 57 | (e->detail == mousemove || e->detail == mouseresize)) { 58 | if (FULLSCREEN(c) || (STATE(c, FIXED) && e->detail != mousemove)) { 59 | return; 60 | } 61 | DBG("buttonpress: %s - 0x%08x", e->detail == mousemove ? "move" : "resize", e->event) 62 | xcb_grab_pointer_reply_t *p; 63 | pc = xcb_grab_pointer(con, 0, root, 64 | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_MOTION | 65 | XCB_EVENT_MASK_POINTER_MOTION, 66 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, root, 67 | cursor[e->detail == mousemove ? CURS_MOVE : CURS_RESIZE], XCB_CURRENT_TIME); 68 | if ((p = xcb_grab_pointer_reply(con, pc, &er)) && p->status == XCB_GRAB_STATUS_SUCCESS) { 69 | mousemotion(c, e->detail, e->root_x, e->root_y); 70 | } else { 71 | iferr(0, "unable to grab pointer", er); 72 | } 73 | free(p); 74 | } 75 | } 76 | 77 | void buttonrelease(int move) 78 | { 79 | released = 1, grabbing = 0; 80 | DBG("buttonrelease: ungrabbing pointer - 0x%08x", selws->sel->win) 81 | iferr(1, "failed to ungrab pointer", 82 | xcb_request_check(con, xcb_ungrab_pointer_checked(con, XCB_CURRENT_TIME))); 83 | if (!move) { 84 | ignore(XCB_ENTER_NOTIFY); 85 | } 86 | } 87 | 88 | void clientmessage(xcb_generic_event_t *ev) 89 | { 90 | Client *c; 91 | Workspace *ws; 92 | xcb_client_message_event_t *e = (xcb_client_message_event_t *)ev; 93 | uint32_t *d = e->data.data32; 94 | 95 | if (e->window == root && e->type == netatom[NET_DESK_CUR]) { 96 | DBG("clientmessage: 0x%08x -- e->type = %d (_NET_CURRENT_DESKTOP)", e->window, e->type) 97 | unfocus(selws->sel, 1); 98 | cmdview(itows(d[0])); 99 | } else if (e->type == netatom[NET_CLOSE]) { 100 | DBG("clientmessage: 0x%08x -- e->type = %d (_NET_CLOSE_WINDOW)", e->window, e->type) 101 | unmanage(e->window, 1); 102 | } else if ((c = wintoclient(e->window))) { 103 | if (e->type == netatom[NET_WM_DESK]) { 104 | if (!(ws = itows(d[0]))) { 105 | warnx("invalid workspace index: %d", d[0]); 106 | return; 107 | } 108 | setworkspace(c, ws, c != c->ws->sel); 109 | wschange = winchange = needsrefresh = 1; 110 | } else if (e->type == netatom[NET_WM_STATE]) { 111 | if (d[1] == netatom[NET_STATE_FULL] || d[2] == netatom[NET_STATE_FULL]) { 112 | int full = (d[0] == 1 || (d[0] == 2 && !STATE(c, FULLSCREEN))); 113 | if (VISIBLE(c)) { 114 | setfullscreen(c, full); 115 | ignore(XCB_ENTER_NOTIFY); 116 | xcb_aux_sync(con); 117 | } 118 | } else if (d[1] == netatom[NET_STATE_ABOVE] || d[2] == netatom[NET_STATE_ABOVE]) { 119 | int above = d[0] == 1 || (d[0] == 2 && !STATE(c, ABOVE)); 120 | if (above && !STATE(c, ABOVE)) { 121 | c->state |= STATE_ABOVE | STATE_FLOATING; 122 | needsrefresh = 1; 123 | } else if (!above && STATE(c, ABOVE)) { 124 | c->state &= ~STATE_ABOVE; 125 | needsrefresh = 1; 126 | } 127 | } else if (d[1] == netatom[NET_STATE_DEMANDATT] || d[2] == netatom[NET_STATE_DEMANDATT]) { 128 | setnetstate(c->win, c->state); 129 | goto activate; 130 | } 131 | } else if (e->type == netatom[NET_ACTIVE] && c != selws->sel) { 132 | activate: 133 | if (globalcfg[GLB_FOCUS_URGENT].val && !STATE(c, IGNOREMSG) && !STATE(c, SCRATCH)) { 134 | DBG("clientmessage: focusing activated window: 0x%08x %s", c->win, c->title) 135 | if (grabbing && !released) { 136 | buttonrelease(0); 137 | } 138 | if (c->ws != selws) { 139 | cmdview(c->ws); 140 | } 141 | focus(c); 142 | if (FLOATING(c)) { 143 | setstackmode(c->win, XCB_STACK_MODE_ABOVE); 144 | } 145 | DBG("clientmessage: focusing activated window: 0x%08x %s", c->win, c->title) 146 | } else { 147 | seturgent(c, 1); 148 | clientborder(c, 0); 149 | } 150 | } 151 | } 152 | } 153 | 154 | void confignotify(xcb_generic_event_t *ev) 155 | { 156 | xcb_configure_notify_event_t *e = (xcb_configure_notify_event_t *)ev; 157 | 158 | if (e->window == root) { 159 | scr_w = e->width; 160 | scr_h = e->height; 161 | } 162 | } 163 | 164 | void configrequest(xcb_generic_event_t *ev) 165 | { 166 | Client *c; 167 | Monitor *m; 168 | xcb_configure_request_event_t *e = (xcb_configure_request_event_t *)ev; 169 | 170 | if ((c = wintoclient(e->window))) { 171 | if (e->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) { 172 | c->bw = e->border_width; 173 | } else if (FLOATING(c)) { 174 | if (!VISIBLE(c) || STATE(c, IGNORECFG) || 175 | ((e->value_mask & XCB_CONFIG_WINDOW_X) && 176 | (e->x == W(c) * -2 || c->x + 1 <= e->x || c->x + 1 >= e->x))) { 177 | DBG("configrequest: 0x%08x: floating - ignoring hidden window or small shift", c->win) 178 | return; 179 | } 180 | m = MON(c); 181 | if (e->value_mask & XCB_CONFIG_WINDOW_X) { 182 | c->old_x = c->x; 183 | c->x = m->x + e->x; 184 | } 185 | if (e->value_mask & XCB_CONFIG_WINDOW_Y) { 186 | c->old_y = c->y; 187 | c->y = m->y + e->y; 188 | } 189 | if (e->value_mask & XCB_CONFIG_WINDOW_WIDTH) { 190 | c->old_w = c->w; 191 | c->w = CLAMP(e->width, globalcfg[GLB_MIN_WH].val, m->w); 192 | } 193 | if (e->value_mask & XCB_CONFIG_WINDOW_HEIGHT) { 194 | c->old_h = c->h; 195 | c->h = CLAMP(e->height, globalcfg[GLB_MIN_WH].val, m->h); 196 | } 197 | if (c->x + c->w < m->x + globalcfg[GLB_MIN_XY].val || 198 | c->x > m->x + m->w - globalcfg[GLB_MIN_XY].val) { 199 | c->x = m->x + (m->w / 2 - W(c) / 2); 200 | } 201 | if (c->y + c->h < m->y + globalcfg[GLB_MIN_XY].val || 202 | c->y > m->y + m->h - globalcfg[GLB_MIN_XY].val) { 203 | c->y = m->y + (m->h / 2 - H(c) / 2); 204 | } 205 | applysizehints(c, &c->x, &c->y, &c->w, &c->h, c->bw, 0, 0); 206 | resize(c, c->x, c->y, c->w, c->h, c->bw); 207 | } else { 208 | sendconfigure(c); 209 | } 210 | } else { 211 | xcb_params_configure_window_t wc = {.x = e->x, 212 | .y = e->y, 213 | .width = e->width, 214 | .height = e->height, 215 | .sibling = e->sibling, 216 | .stack_mode = e->stack_mode, 217 | .border_width = e->border_width}; 218 | xcb_aux_configure_window(con, e->window, e->value_mask, &wc); 219 | } 220 | xcb_flush(con); 221 | } 222 | 223 | void destroynotify(xcb_generic_event_t *ev) 224 | { 225 | DBG("destroynotify: 0x%08x", ((xcb_destroy_notify_event_t *)ev)->window) 226 | unmanage(((xcb_destroy_notify_event_t *)ev)->window, 1); 227 | } 228 | 229 | void dispatch(xcb_generic_event_t *ev) 230 | { 231 | short type; 232 | 233 | if ((type = XCB_EVENT_RESPONSE_TYPE(ev))) { 234 | if (handlers[type]) { 235 | handlers[type](ev); 236 | } else if (ev->response_type == randrbase + XCB_RANDR_SCREEN_CHANGE_NOTIFY && 237 | ((xcb_randr_screen_change_notify_event_t *)ev)->root == root) { 238 | if (updrandr(0)) { 239 | updworkspaces(globalcfg[GLB_NUM_WS].val); 240 | } 241 | updstruts(); 242 | } 243 | } else { 244 | xcb_generic_error_t *e = (xcb_generic_error_t *)ev; 245 | 246 | /* ignore some specific error types */ 247 | if (e->error_code == XCB_WINDOW || 248 | (e->error_code == XCB_MATCH && 249 | (e->major_code == XCB_SET_INPUT_FOCUS || e->major_code == XCB_CONFIGURE_WINDOW)) || 250 | (e->error_code == XCB_ACCESS && 251 | (e->major_code == XCB_GRAB_BUTTON || e->major_code == XCB_GRAB_KEY)) || 252 | (e->error_code == XCB_DRAWABLE && 253 | (e->major_code == XCB_CREATE_PIXMAP || e->major_code == XCB_CREATE_GC || 254 | e->major_code == XCB_POLY_FILL_RECTANGLE)) || 255 | (e->error_code == XCB_G_CONTEXT && 256 | (e->major_code == XCB_CHANGE_GC || e->major_code == XCB_FREE_GC)) || 257 | (e->error_code == XCB_PIXMAP && e->major_code == XCB_FREE_PIXMAP)) { 258 | return; 259 | } 260 | fprintf(stderr, 261 | "dk: previous request returned error %i, \"%s\"" 262 | " major code %u, minor code %u resource id %u sequence %u\n", 263 | e->error_code, xcb_event_get_error_label(e->error_code), (uint32_t)e->major_code, 264 | (uint32_t)e->minor_code, (uint32_t)e->resource_id, (uint32_t)e->sequence); 265 | } 266 | } 267 | 268 | void enternotify(xcb_generic_event_t *ev) 269 | { 270 | Client *c; 271 | Monitor *m; 272 | Workspace *ws; 273 | xcb_enter_notify_event_t *e = (xcb_enter_notify_event_t *)ev; 274 | 275 | if (e->event != root && 276 | (e->mode != XCB_NOTIFY_MODE_NORMAL || e->detail == XCB_NOTIFY_DETAIL_INFERIOR)) { 277 | return; 278 | } 279 | DBG("enternotify: 0x%08x", e->event) 280 | ws = selws; 281 | if ((c = wintoclient(e->event))) { 282 | ws = c->ws; 283 | } else if ((m = coordtomon(e->root_x, e->root_y))) { 284 | ws = m->ws; 285 | } 286 | if (ws && ws != selws) { 287 | changews(ws, 0, 0); 288 | } 289 | if (c && c != ws->sel && globalcfg[GLB_FOCUS_MOUSE].val) { 290 | focus(c); 291 | } 292 | } 293 | 294 | void focusin(xcb_generic_event_t *ev) 295 | { 296 | xcb_focus_in_event_t *e = (xcb_focus_in_event_t *)ev; 297 | 298 | if (e->mode == XCB_NOTIFY_MODE_GRAB || e->mode == XCB_NOTIFY_MODE_UNGRAB || 299 | e->detail == XCB_NOTIFY_DETAIL_POINTER || e->detail == XCB_NOTIFY_DETAIL_POINTER_ROOT || 300 | e->detail == XCB_NOTIFY_DETAIL_NONE) { 301 | return; 302 | } 303 | if (selws->sel && e->event != selws->sel->win) { 304 | DBG("focusin: 0x%08x", e->event) 305 | setinputfocus(selws->sel); 306 | } 307 | } 308 | 309 | void ignore(uint8_t type) 310 | { 311 | xcb_generic_event_t *ev = NULL; 312 | 313 | xcb_flush(con); 314 | while (running && (ev = xcb_poll_for_event(con))) { 315 | if (XCB_EVENT_RESPONSE_TYPE(ev) != type) { 316 | dispatch(ev); 317 | } 318 | free(ev); 319 | } 320 | } 321 | 322 | void mappingnotify(xcb_generic_event_t *ev) 323 | { 324 | Client *c; 325 | Workspace *ws; 326 | xcb_mapping_notify_event_t *e = (xcb_mapping_notify_event_t *)ev; 327 | 328 | if (e->request == XCB_MAPPING_KEYBOARD || e->request == XCB_MAPPING_MODIFIER) { 329 | xcb_refresh_keyboard_mapping(keysyms, e); 330 | numlockmask(); 331 | for (ws = workspaces; ws; ws = ws->next) { 332 | for (c = ws->clients; c; c = c->next) { 333 | grabbuttons(c); 334 | } 335 | } 336 | for (c = scratch.clients; c; c = c->next) { 337 | grabbuttons(c); 338 | } 339 | } 340 | } 341 | 342 | void maprequest(xcb_generic_event_t *ev) 343 | { 344 | manage(((xcb_map_request_event_t *)ev)->window, 0); 345 | } 346 | 347 | void motionnotify(xcb_generic_event_t *ev) 348 | { 349 | Monitor *m; 350 | xcb_motion_notify_event_t *e = (xcb_motion_notify_event_t *)ev; 351 | 352 | if (e->event == root && (m = coordtomon(e->root_x, e->root_y)) && m->ws != selws) { 353 | DBG("motionnotify: updating active monitor - 0x%08x", e->event) 354 | changews(m->ws, 0, 0); 355 | focus(NULL); 356 | } 357 | } 358 | 359 | static void mousemotion_move(Client *c, int mx, int my) 360 | { 361 | Monitor *m = selws->mon; 362 | xcb_timestamp_t last = 0; 363 | xcb_motion_notify_event_t *e; 364 | xcb_generic_event_t *ev = NULL; 365 | int ox = c->x, oy = c->y, nx, ny, w, h; 366 | 367 | /* single pass to ensure the border is drawn and the client is floating */ 368 | if (!FLOATING(c) || (STATE(c, FULLSCREEN) && STATE(c, FAKEFULL))) { 369 | while (running && !released && (ev = xcb_wait_for_event(con))) { 370 | switch (XCB_EVENT_RESPONSE_TYPE(ev)) { 371 | case XCB_MOTION_NOTIFY: 372 | e = (xcb_motion_notify_event_t *)ev; 373 | if (e->time - last < 1000 / 60) { 374 | break; 375 | } 376 | last = e->time; 377 | nx = ox + (e->root_x - mx); 378 | ny = oy + (e->root_y - my); 379 | if (nx == c->x && ny == c->y) { 380 | break; 381 | } 382 | c->state |= STATE_FLOATING; 383 | c->old_state |= STATE_FLOATING; 384 | if (selws->layout->func) { 385 | selws->layout->func(selws); 386 | } 387 | setstackmode(c->win, XCB_STACK_MODE_ABOVE); 388 | w = c->w, h = c->h; 389 | resizehint(c, nx, ny, w, h, c->bw, 1, 1); 390 | free(ev); 391 | goto primary_loop; 392 | break; 393 | case XCB_BUTTON_RELEASE: 394 | grabbing = 0, released = 1; 395 | buttonrelease(1); 396 | break; 397 | default: dispatch(ev); 398 | } 399 | free(ev); 400 | } 401 | } 402 | primary_loop: 403 | while (running && !released && (ev = xcb_wait_for_event(con))) { 404 | switch (XCB_EVENT_RESPONSE_TYPE(ev)) { 405 | case XCB_MOTION_NOTIFY: 406 | e = (xcb_motion_notify_event_t *)ev; 407 | if (e->time - last < 1000 / 60) { 408 | break; 409 | } 410 | last = e->time; 411 | nx = ox + (e->root_x - mx); 412 | ny = oy + (e->root_y - my); 413 | if (nx == c->x && ny == c->y) { 414 | break; 415 | } 416 | if ((m = coordtomon(e->root_x, e->root_y)) && m->ws != c->ws) { 417 | setworkspace(c, m->ws, 0); 418 | changews(m->ws, 0, 0); 419 | focus(c); 420 | } 421 | w = c->w, h = c->h; 422 | if (applysizehints(c, &nx, &ny, &w, &h, c->bw, 1, 1)) { 423 | c->x = nx, c->y = ny, c->w = w, c->h = h; 424 | MOVERESIZE(c->win, c->x, c->y, c->w, c->h, c->bw); 425 | sendconfigure(c); 426 | xcb_flush(con); 427 | } 428 | break; 429 | case XCB_BUTTON_RELEASE: 430 | grabbing = 0, released = 1; 431 | buttonrelease(1); 432 | break; 433 | default: dispatch(ev); 434 | } 435 | free(ev); 436 | } 437 | } 438 | 439 | /* already floating windows and tiled layouts that don't support resize */ 440 | static void mousemotion_resize(Client *c, int mx, int my) 441 | { 442 | xcb_timestamp_t last = 0; 443 | xcb_motion_notify_event_t *e; 444 | xcb_generic_event_t *ev = NULL; 445 | int x, y, nw, nh, ow = c->w, oh = c->h; 446 | ; 447 | 448 | while (running && !released && (ev = xcb_wait_for_event(con))) { 449 | switch (XCB_EVENT_RESPONSE_TYPE(ev)) { 450 | case XCB_MOTION_NOTIFY: 451 | e = (xcb_motion_notify_event_t *)ev; 452 | if (e->time - last < 1000 / 60) { 453 | break; 454 | } 455 | last = e->time; 456 | nw = ow + (e->root_x - mx); 457 | nh = oh + (e->root_y - my); 458 | if (nw == c->w && nh == c->h) { 459 | break; 460 | } 461 | if (!FLOATING(c) || (STATE(c, FULLSCREEN) && STATE(c, FAKEFULL))) { 462 | c->state |= STATE_FLOATING; 463 | c->old_state |= STATE_FLOATING; 464 | if (selws->layout->func) { 465 | selws->layout->func(selws); 466 | } 467 | setstackmode(c->win, XCB_STACK_MODE_ABOVE); 468 | } 469 | resizehint(c, (x = c->x), (y = c->y), nw, nh, c->bw, 1, 1); 470 | break; 471 | case XCB_BUTTON_RELEASE: 472 | grabbing = 0, released = 1; 473 | buttonrelease(0); 474 | break; 475 | default: dispatch(ev); 476 | } 477 | free(ev); 478 | } 479 | } 480 | 481 | /* duplicate code in both _resizet and _resizetinv */ 482 | #define HEIGHT_OFFSET \ 483 | if (prev || nearend) { \ 484 | int ohoff = c->hoff; \ 485 | if (first) { \ 486 | first = 0; \ 487 | if (isend) { \ 488 | c->hoff = ((e->root_y - my) * -1) + ohoff; \ 489 | my += ohoff; \ 490 | } else { \ 491 | c->hoff = (e->root_y - my) + ohoff; \ 492 | my -= ohoff; \ 493 | } \ 494 | } else { \ 495 | c->hoff = isend ? (e->root_y - my) * -1 : e->root_y - my; \ 496 | } \ 497 | if (selws->layout->func(selws) < 0) { \ 498 | c->hoff = ohoff; \ 499 | selws->layout->func(selws); \ 500 | } \ 501 | } else if (selws->layout->func(selws) < 0) { \ 502 | selws->layout->func(selws); \ 503 | } \ 504 | xcb_flush(con) 505 | 506 | /* layouts that support resize with standard tiling direction */ 507 | static void mousemotion_resizet(Client *c, Client *prev, int idx, int mx, int my, int isend, int nearend) 508 | { 509 | Monitor *m = selws->mon; 510 | xcb_timestamp_t last = 0; 511 | xcb_motion_notify_event_t *e; 512 | xcb_generic_event_t *ev = NULL; 513 | int first = 1, ow = c->w, ox = c->x; 514 | 515 | while (running && !released && (ev = xcb_wait_for_event(con))) { 516 | switch (XCB_EVENT_RESPONSE_TYPE(ev)) { 517 | case XCB_MOTION_NOTIFY: 518 | e = (xcb_motion_notify_event_t *)ev; 519 | if (e->time - last < 1000 / 60) { 520 | break; 521 | } 522 | last = e->time; 523 | if (selws->nstack && idx >= selws->nstack + selws->nmaster) { 524 | selws->ssplit = CLAMP((ox - m->x + (e->root_x - mx) - (m->w * selws->msplit)) / 525 | (m->w - (m->w * selws->msplit)), 526 | 0.05, 0.95); 527 | } else if (selws->nmaster && idx >= selws->nmaster) { 528 | selws->msplit = CLAMP((float)(ox - m->x + (e->root_x - mx)) / m->w, 0.05, 0.95); 529 | } else { 530 | selws->msplit = CLAMP((float)(ox - m->x + ow + (e->root_x - mx)) / m->w, 0.05, 0.95); 531 | } 532 | HEIGHT_OFFSET; 533 | break; 534 | case XCB_BUTTON_RELEASE: 535 | buttonrelease(0); 536 | break; 537 | default: dispatch(ev); 538 | } 539 | free(ev); 540 | } 541 | } 542 | 543 | /* layouts that support resize with inverted tiling direction */ 544 | static void mousemotion_resizetinv(Client *c, Client *prev, int idx, int mx, int my, int isend, int nearend) 545 | { 546 | Monitor *m = selws->mon; 547 | xcb_timestamp_t last = 0; 548 | xcb_motion_notify_event_t *e; 549 | xcb_generic_event_t *ev = NULL; 550 | int first = 1, ow = c->w, ox = c->x; 551 | 552 | while (running && !released && (ev = xcb_wait_for_event(con))) { 553 | switch (XCB_EVENT_RESPONSE_TYPE(ev)) { 554 | case XCB_MOTION_NOTIFY: 555 | e = (xcb_motion_notify_event_t *)ev; 556 | if (e->time - last < 1000 / 60) { 557 | break; 558 | } 559 | last = e->time; 560 | if (selws->nstack && idx >= selws->nstack + selws->nmaster) { 561 | selws->ssplit = CLAMP( 562 | (ox - m->x + ow - (e->root_x - mx)) / (m->w - (m->w * selws->msplit)), 0.05, 0.95); 563 | } else if (selws->nmaster && idx >= selws->nmaster) { 564 | selws->msplit = CLAMP((float)(ox - m->x + ow - (e->root_x - mx)) / m->w, 0.05, 0.95); 565 | } else { 566 | selws->msplit = CLAMP((float)(ox - m->x - (e->root_x - mx)) / m->w, 0.05, 0.95); 567 | } 568 | HEIGHT_OFFSET; 569 | break; 570 | case XCB_BUTTON_RELEASE: 571 | buttonrelease(0); 572 | break; 573 | default: dispatch(ev); 574 | } 575 | free(ev); 576 | } 577 | } 578 | #undef HEIGHT_OFFSET 579 | 580 | void mousemotion(Client *c, xcb_button_t button, int mx, int my) 581 | { 582 | grabbing = 1, released = 0; 583 | 584 | if (button == mousemove) { 585 | mousemotion_move(c, mx, my); 586 | } else { 587 | if (!FLOATING(c) && c->ws->layout->implements_resize) { 588 | int i, isend, nearend; 589 | Client *p, *prev = NULL; 590 | for (i = 0, p = nexttiled(selws->clients); p && p != c; p = nexttiled(p->next), i++) { 591 | if (nexttiled(p->next) == c) { 592 | prev = (i + 1 == selws->nmaster || i + 1 == selws->nstack + selws->nmaster) ? NULL : p; 593 | } 594 | } 595 | /* calculate these here to save the resize loops extra unnecessary work */ 596 | isend = i + 1 == selws->nmaster || i + 1 == selws->nmaster + selws->nstack || !nexttiled(c->next); 597 | nearend = (i == selws->nmaster || i == selws->nmaster + selws->nstack) && nexttiled(c->next); 598 | 599 | if (!selws->layout->invert_split_direction) { 600 | mousemotion_resizet(c, prev, i, mx, my, isend, nearend); 601 | } else { 602 | mousemotion_resizetinv(c, prev, i, mx, my, isend, nearend); 603 | } 604 | } else { 605 | mousemotion_resize(c, mx, my); 606 | } 607 | } 608 | } 609 | 610 | void propertynotify(xcb_generic_event_t *ev) 611 | { 612 | Panel *p; 613 | Client *c; 614 | xcb_property_notify_event_t *e = (xcb_property_notify_event_t *)ev; 615 | 616 | if (e->state == XCB_PROPERTY_DELETE || e->window == root) { 617 | return; 618 | } 619 | if ((c = wintoclient(e->window))) { 620 | switch (e->atom) { 621 | case XCB_ATOM_WM_HINTS: clienthints(c); break; 622 | case XCB_ATOM_WM_NORMAL_HINTS: c->hints = 0; break; 623 | case XCB_ATOM_WM_TRANSIENT_FOR: 624 | if ((c->trans = wintoclient(wintrans(c->win))) && !FLOATING(c)) { 625 | c->state |= STATE_FLOATING; 626 | needsrefresh = 1; 627 | } 628 | break; 629 | default: 630 | if (e->atom == XCB_ATOM_WM_NAME || e->atom == netatom[NET_WM_NAME]) { 631 | if (clientname(c)) { 632 | winchange = 1; 633 | } 634 | } else if (e->atom == netatom[NET_WM_TYPE]) { 635 | clienttype(c); 636 | } 637 | } 638 | } else if ((e->atom == netatom[NET_WM_STRUTP] || e->atom == netatom[NET_WM_STRUT]) && 639 | (p = wintopanel(e->window))) { 640 | fillstruts(p); 641 | updstruts(); 642 | needsrefresh = 1; 643 | } 644 | } 645 | 646 | void unmapnotify(xcb_generic_event_t *ev) 647 | { 648 | xcb_generic_error_t *er; 649 | xcb_unmap_notify_event_t *e = (xcb_unmap_notify_event_t *)ev; 650 | 651 | if (e->event != root) { 652 | free(xcb_query_tree_reply(con, xcb_query_tree(con, e->window), &er)); 653 | if (er) { 654 | free(er); 655 | return; 656 | } 657 | DBG("unmapnotify: un-managing window: 0x%08x", e->window) 658 | unmanage(e->window, 0); 659 | } 660 | } 661 | -------------------------------------------------------------------------------- /src/event.h: -------------------------------------------------------------------------------- 1 | /* dk window manager 2 | * 3 | * see license file for copyright and license details 4 | * vim:ft=c:fdm=syntax:ts=4:sts=4:sw=4 5 | */ 6 | 7 | #pragma once 8 | 9 | void buttonpress(xcb_generic_event_t *ev); 10 | void buttonrelease(int move); 11 | void clientmessage(xcb_generic_event_t *ev); 12 | void confignotify(xcb_generic_event_t *ev); 13 | void configrequest(xcb_generic_event_t *ev); 14 | void destroynotify(xcb_generic_event_t *ev); 15 | void dispatch(xcb_generic_event_t *ev); 16 | void enternotify(xcb_generic_event_t *ev); 17 | void focusin(xcb_generic_event_t *ev); 18 | void ignore(uint8_t type); 19 | void mappingnotify(xcb_generic_event_t *ev); 20 | void maprequest(xcb_generic_event_t *ev); 21 | void motionnotify(xcb_generic_event_t *ev); 22 | void mousemotion(Client *c, xcb_button_t button, int mx, int my); 23 | void propertynotify(xcb_generic_event_t *ev); 24 | void unmapnotify(xcb_generic_event_t *ev); 25 | -------------------------------------------------------------------------------- /src/layout.c: -------------------------------------------------------------------------------- 1 | /* dk window manager 2 | * 3 | * see license file for copyright and license details 4 | * vim:ft=c:fdm=syntax:ts=4:sts=4:sw=4 5 | */ 6 | 7 | #ifdef DEBUG 8 | #include 9 | #endif 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "dk.h" 16 | #include "layout.h" 17 | 18 | int dwindle(Workspace *ws) 19 | { 20 | Client *c; 21 | Monitor *m = ws->mon; 22 | uint32_t i, n; 23 | int x, y, w, h, ww, g, f = 0, ret = 1; 24 | 25 | if (!(n = tilecount(ws))) { 26 | return 1; 27 | } 28 | 29 | if (globalcfg[GLB_SMART_GAP].val && n == 1) { 30 | g = 0, ws->smartgap = 1; 31 | } else { 32 | g = ws->gappx, ws->smartgap = 0; 33 | } 34 | 35 | x = m->wx + ws->padl; 36 | y = m->wy + ws->padt; 37 | w = m->ww - ws->padl - ws->padr; 38 | h = m->wh - ws->padt - ws->padb; 39 | ww = w; 40 | 41 | for (i = 0, c = nexttiled(ws->clients); c; c = nexttiled(c->next), i++) { 42 | uint32_t ox = x, oy = y; 43 | int *p = (i % 2) ? &h : &w; 44 | int b = globalcfg[GLB_SMART_BORDER].val && n == 1 ? 0 : c->bw; 45 | if (i < n - 1) { 46 | *p /= 2; 47 | } 48 | switch (i % 4) { 49 | case 0: y += h; break; 50 | case 1: x += w; break; 51 | case 2: y += h; break; 52 | case 3: x += w; break; 53 | } 54 | if (!i) { 55 | w = n > 1 ? (ww * ws->msplit) - (float)g / 2 : ww - g; 56 | h -= g; 57 | y = m->wy + ws->padt; 58 | } else if (i == 1) { 59 | w = ww - ((ww * ws->msplit) + (float)g / 2); 60 | } 61 | if (f || *p - g - (2 * b) < globalcfg[GLB_MIN_WH].val) { 62 | if (f) { 63 | popfloat(c); 64 | ret = -1; 65 | continue; 66 | } 67 | f = 1; 68 | *p *= 2; 69 | ret = -1; 70 | if (i % 2) { 71 | y = oy; 72 | } else { 73 | x = ox; 74 | } 75 | } 76 | resizehint(c, x + g, y + g, w - g - (2 * b), h - g - (2 * b), b, 0, 0); 77 | } 78 | return ret; 79 | } 80 | 81 | int grid(Workspace *ws) 82 | { 83 | Client *c; 84 | Monitor *m = ws->mon; 85 | int wx, wy, ww, wh; 86 | int i, n, g, cols, rows, col, row; 87 | 88 | if (!(n = tilecount(ws))) { 89 | return 1; 90 | } 91 | for (cols = 0; cols <= n / 2; cols++) { 92 | if (cols * cols >= n) { 93 | break; 94 | } 95 | } 96 | if (n == 5) { 97 | cols = 2; 98 | } 99 | cols = MAX(1, cols); 100 | rows = n / MAX(1, cols); 101 | wx = m->wx + ws->padl; 102 | wy = m->wy + ws->padt; 103 | ww = m->ww - ws->padl - ws->padr; 104 | wh = m->wh - ws->padt - ws->padb; 105 | 106 | if (globalcfg[GLB_SMART_GAP].val && n == 1) { 107 | g = 0, ws->smartgap = 1; 108 | } else { 109 | g = ws->gappx, ws->smartgap = 0; 110 | } 111 | 112 | for (i = col = row = 0, c = nexttiled(ws->clients); c; i++, c = nexttiled(c->next)) { 113 | if (i / MAX(1, rows) + 1 > cols - n % cols) { 114 | rows = n / cols + 1; 115 | } 116 | int b = globalcfg[GLB_SMART_BORDER].val && n == 1 ? 0 : c->bw; 117 | int ch = rows ? (wh - g) / rows : wh; 118 | int cw = cols ? (ww - g) / cols : ww; 119 | int cx = (wx + g) + col * cw; 120 | int cy = (wy + g) + row * ch; 121 | resizehint(c, cx, cy, cw - (2 * b) - g, ch - (2 * b) - g, b, 0, 0); 122 | if (++row >= rows) { 123 | row = 0; 124 | col++; 125 | } 126 | } 127 | return 1; 128 | } 129 | 130 | int ltile(Workspace *ws) 131 | { 132 | Client *c; 133 | Monitor *m = ws->mon; 134 | int i, g, n, x, *y, remain, ret = 0, p = -1, pbw = 0; 135 | int mw, my, sw, sy, ss, ssw, ssy, ns = 1; 136 | int minh = globalcfg[GLB_MIN_WH].val; 137 | 138 | if (!(n = tilecount(ws))) { 139 | return 1; 140 | } 141 | mw = ss = sw = ssw = 0; 142 | int geo[n + 1][4]; 143 | int wx = m->wx + ws->padl; 144 | int wy = m->wy + ws->padt; 145 | int ww = m->ww - ws->padl - ws->padr; 146 | int wh = m->wh - ws->padt - ws->padb; 147 | 148 | if (globalcfg[GLB_SMART_GAP].val && n == 1) { 149 | g = 0, ws->smartgap = 1; 150 | } else { 151 | g = ws->gappx, ws->smartgap = 0; 152 | } 153 | 154 | if (n <= ws->nmaster) { 155 | mw = ww, ss = 1; 156 | } else if (ws->nmaster) { 157 | ns = 2, mw = ww * ws->msplit; 158 | } 159 | if (n - ws->nmaster <= ws->nstack) { 160 | sw = ww - mw; 161 | } else if (ws->nstack) { 162 | sw = (ww - mw) * ws->ssplit; 163 | } 164 | if (n - ws->nmaster > ws->nstack) { 165 | ss = 1, ssw = ww - mw - sw; 166 | } 167 | if (!ws->nmaster) { 168 | ss = 0; 169 | } 170 | 171 | /* We use an array to store client geometries so we can change the size of 172 | * the previous client if needed. There's a lot of messy maths to adjust 173 | * the size of each window using it's height offset and stack split ratio. 174 | */ 175 | for (i = 0, my = sy = ssy = g, c = nexttiled(ws->clients); c; c = nexttiled(c->next), ++i) { 176 | if (i < ws->nmaster) { 177 | remain = MIN(n, ws->nmaster) - i; 178 | x = g; 179 | y = &my; 180 | geo[i][2] = mw - g * (5 - ns) / 2; 181 | } else if (i - ws->nmaster < ws->nstack) { 182 | remain = MIN(n - ws->nmaster, ws->nstack) - (i - ws->nmaster); 183 | x = mw + (g / ns); 184 | y = &sy; 185 | geo[i][2] = 186 | (sw - g * (5 - ns - ss) / 2) + (!ws->nmaster && n > ws->nmaster + ws->nstack ? g / 2 : 0); 187 | } else { 188 | remain = n - i; 189 | x = mw + sw + (g / ns) - (!ws->nmaster ? g / 2 : 0); 190 | y = &ssy; 191 | geo[i][2] = (ssw - g * (5 - ns) / 2) + (!ws->nmaster ? g / 2 : 0); 192 | } 193 | geo[i][0] = wx + x; 194 | geo[i][1] = wy + *y; 195 | int bw = !globalcfg[GLB_SMART_BORDER].val || n > 1 ? c->bw : 0; 196 | if (p == -1 && remain == 1) { 197 | geo[i][3] = wh - *y - g; 198 | goto update; 199 | } else { 200 | geo[i][3] = ((wh - *y) / MAX(1, remain)) - g + c->hoff; 201 | } 202 | int available = wh - (*y + geo[i][3] + g); 203 | if (!c->hoff && geo[i][3] - (2 * bw) < minh) { 204 | popfloat(c); 205 | continue; 206 | } else if (remain > 1 && (remain - 1) * (minh + g + (2 * bw)) > available) { 207 | geo[i][3] += available - ((remain - 1) * (minh + g + (2 * bw))); 208 | ret = -1; 209 | } else if (remain == 1 && *y + geo[i][3] != wh - g) { 210 | if (p != -1) { 211 | if (geo[p][3] + available < minh + (2 * bw)) { 212 | geo[p][3] = minh + (2 * pbw); 213 | geo[i][1] = geo[p][1] + geo[p][3] + g + (2 * pbw); 214 | geo[i][3] = (wh - (2 * g)) - (geo[p][1] + geo[p][3]) - (2 * pbw); 215 | ret = -1; 216 | } else if (geo[i][3] <= minh) { 217 | geo[p][3] -= minh - geo[i][3] + (2 * bw); 218 | geo[i][1] = geo[p][1] + geo[p][3] + g; 219 | geo[i][3] = minh + (2 * bw); 220 | ret = -1; 221 | } else { 222 | geo[p][3] += available; 223 | geo[i][1] += available; 224 | } 225 | } else { 226 | geo[i][3] = available; 227 | } 228 | } else if (geo[i][3] - (2 * bw) < minh) { 229 | geo[i][3] = remain == 1 ? wh - (2 * g) : minh + (2 * bw); 230 | ret = -1; 231 | } 232 | update: 233 | *y += geo[i][3] + g; 234 | geo[i][2] -= (2 * bw); 235 | geo[i][3] -= (2 * bw); 236 | p = (remain == 1 && n - i != 0) ? -1 : i; 237 | pbw = bw; 238 | } 239 | 240 | /* Do the actual resizing, if a client goes below the minimum allowed size 241 | * we return -1 to signify the layout exceeded it. 242 | */ 243 | for (i = 0, c = nexttiled(ws->clients); c; c = nexttiled(c->next), i++) { 244 | if (geo[i][3] <= globalcfg[GLB_MIN_WH].val) { 245 | ret = -1; 246 | } 247 | resizehint(c, geo[i][0], geo[i][1], geo[i][2], geo[i][3], 248 | !globalcfg[GLB_SMART_BORDER].val || n > 1 ? c->bw : 0, 0, 0); 249 | } 250 | return ret; 251 | } 252 | 253 | int mono(Workspace *ws) 254 | { 255 | int g; 256 | Client *c; 257 | 258 | if (!ws->sel) { 259 | return 1; 260 | } 261 | if (globalcfg[GLB_SMART_GAP].val) { 262 | g = 0, ws->smartgap = 1; 263 | } else { 264 | g = ws->gappx, ws->smartgap = 0; 265 | } 266 | 267 | int b = globalcfg[GLB_SMART_BORDER].val ? 0 : ws->sel->bw; 268 | 269 | for (c = nexttiled(ws->clients); c; c = nexttiled(c->next)) { 270 | resizehint(c, ws->mon->wx + ws->padl + g, ws->mon->wy + ws->padt + g, 271 | ws->mon->ww - ws->padl - ws->padr - (2 * g) - (2 * b), 272 | ws->mon->wh - ws->padt - ws->padb - (2 * g) - (2 * b), b, 0, 0); 273 | if (c != c->ws->sel) { /* hide inactive windows */ 274 | MOVE(c->win, W(c) * -2, c->y); 275 | } 276 | } 277 | return 1; 278 | } 279 | 280 | int rtile(Workspace *ws) 281 | { 282 | Client *c; 283 | Monitor *m = ws->mon; 284 | int i, g, n, x, *y, remain, ret = 0, p = -1, pbw = 0; 285 | int mw, my, sw, sy, ss, ssw, ssy, ns = 1; 286 | int minh = globalcfg[GLB_MIN_WH].val; 287 | 288 | if (!(n = tilecount(ws))) { 289 | return 1; 290 | } 291 | mw = ss = sw = ssw = 0; 292 | int geo[n + 1][4]; 293 | int wx = m->wx + ws->padl; 294 | int wy = m->wy + ws->padt; 295 | int ww = m->ww - ws->padl - ws->padr; 296 | int wh = m->wh - ws->padt - ws->padb; 297 | 298 | if (globalcfg[GLB_SMART_GAP].val && n == 1) { 299 | g = 0, ws->smartgap = 1; 300 | } else { 301 | g = ws->gappx, ws->smartgap = 0; 302 | } 303 | 304 | if (n <= ws->nmaster) { 305 | mw = ww, ss = 1; 306 | } else if (ws->nmaster) { 307 | ns = 2, mw = ww * ws->msplit; 308 | } 309 | if (n - ws->nmaster <= ws->nstack) { 310 | sw = ww - mw; 311 | } else if (ws->nstack) { 312 | sw = (ww - mw) * ws->ssplit; 313 | } 314 | if (n - ws->nmaster > ws->nstack) { 315 | ss = 1, ssw = ww - mw - sw; 316 | } 317 | if (!ws->nmaster) { 318 | ss = 0; 319 | } 320 | 321 | for (i = 0, my = sy = ssy = g, c = nexttiled(ws->clients); c; c = nexttiled(c->next), ++i) { 322 | if (i < ws->nmaster) { 323 | remain = MIN(n, ws->nmaster) - i; 324 | x = sw + ssw + (g / ns); 325 | y = &my; 326 | geo[i][2] = mw - g * (5 - ns) / 2; 327 | } else if (i - ws->nmaster < ws->nstack) { 328 | remain = MIN(n - ws->nmaster, ws->nstack) - (i - ws->nmaster); 329 | x = n <= ws->nmaster + ws->nstack 330 | ? g 331 | : (ssw + g / ns) - (!ws->nmaster && n > ws->nmaster + ws->nstack ? g / 2 : 0); 332 | y = &sy; 333 | geo[i][2] = 334 | (sw - g * (5 - ns - ss) / 2) + (!ws->nmaster && n > ws->nmaster + ws->nstack ? g / 2 : 0); 335 | } else { 336 | remain = n - i; 337 | x = g; 338 | y = &ssy; 339 | geo[i][2] = ssw - g * (5 - ns) / 2; 340 | if (!ws->nmaster) { 341 | geo[i][2] += g / 2; 342 | } 343 | } 344 | geo[i][0] = wx + x; 345 | geo[i][1] = wy + *y; 346 | int bw = !globalcfg[GLB_SMART_BORDER].val || n > 1 ? c->bw : 0; 347 | if (p == -1 && remain == 1) { 348 | geo[i][3] = wh - *y - g; 349 | goto update; 350 | } else { 351 | geo[i][3] = ((wh - *y) / MAX(1, remain)) - g + c->hoff; 352 | } 353 | int available = wh - (*y + geo[i][3] + g); 354 | if (!c->hoff && geo[i][3] - (2 * bw) < minh) { 355 | popfloat(c); 356 | ret = -1; 357 | continue; 358 | } else if (remain > 1 && (remain - 1) * (minh + g + (2 * bw)) > available) { 359 | geo[i][3] += available - ((remain - 1) * (minh + g + (2 * bw))); 360 | ret = -1; 361 | } else if (remain == 1 && *y + geo[i][3] != wh - g) { 362 | if (p != -1) { 363 | if (geo[p][3] + available < minh + (2 * bw)) { 364 | geo[p][3] = minh + (2 * pbw); 365 | geo[i][1] = geo[p][1] + geo[p][3] + g + (2 * pbw); 366 | geo[i][3] = (wh - (2 * g)) - (geo[p][1] + geo[p][3]) - (2 * pbw); 367 | ret = -1; 368 | } else if (geo[i][3] <= minh) { 369 | geo[p][3] -= minh - geo[i][3] + (2 * bw); 370 | geo[i][1] = geo[p][1] + geo[p][3] + g; 371 | geo[i][3] = minh + (2 * bw); 372 | ret = -1; 373 | } else { 374 | geo[p][3] += available; 375 | geo[i][1] += available; 376 | } 377 | } else { 378 | geo[i][3] = available; 379 | } 380 | } else if (geo[i][3] - (2 * bw) < minh) { 381 | geo[i][3] = remain == 1 ? wh - (2 * g) : minh + (2 * bw); 382 | ret = -1; 383 | } 384 | update: 385 | *y += geo[i][3] + g; 386 | geo[i][2] -= (2 * bw); 387 | geo[i][3] -= (2 * bw); 388 | p = (remain == 1 && n - i != 0) ? -1 : i; 389 | pbw = bw; 390 | } 391 | 392 | for (i = 0, c = nexttiled(ws->clients); c; c = nexttiled(c->next), i++) { 393 | if (geo[i][3] <= globalcfg[GLB_MIN_WH].val) { 394 | ret = -1; 395 | } 396 | resizehint(c, geo[i][0], geo[i][1], geo[i][2], geo[i][3], 397 | !globalcfg[GLB_SMART_BORDER].val || n > 1 ? c->bw : 0, 0, 0); 398 | } 399 | return ret; 400 | } 401 | 402 | int spiral(Workspace *ws) 403 | { 404 | Client *c; 405 | Monitor *m = ws->mon; 406 | uint32_t i, n; 407 | int x, y, w, h, ww, g, f = 0, ret = 1; 408 | 409 | if (!(n = tilecount(ws))) { 410 | return 1; 411 | } 412 | 413 | if (globalcfg[GLB_SMART_GAP].val && n == 1) { 414 | g = 0, ws->smartgap = 1; 415 | } else { 416 | g = ws->gappx, ws->smartgap = 0; 417 | } 418 | 419 | x = m->wx + ws->padl; 420 | y = m->wy + ws->padt; 421 | w = m->ww - ws->padl - ws->padr; 422 | h = m->wh - ws->padt - ws->padb; 423 | ww = w; 424 | 425 | for (i = 0, c = nexttiled(ws->clients); c; c = nexttiled(c->next), i++) { 426 | uint32_t ox = x, oy = y; 427 | int *p = (i % 2) ? &h : &w; 428 | int b = globalcfg[GLB_SMART_BORDER].val && n == 1 ? 0 : c->bw; 429 | if (i < n - 1) { 430 | *p /= 2; 431 | if (i % 4 == 2) { 432 | x += w; 433 | } else if (i % 4 == 3) { 434 | y += h; 435 | } 436 | } 437 | switch (i % 4) { 438 | case 0: y -= h; break; 439 | case 1: x += w; break; 440 | case 2: y += h; break; 441 | case 3: x -= w; break; 442 | } 443 | if (!i) { 444 | w = n > 1 ? (ww * ws->msplit) - (float)g / 2 : ww - g; 445 | h -= g; 446 | y = m->wy + ws->padt; 447 | } else if (i == 1) { 448 | w = ww - ((ww * ws->msplit) + (float)g / 2); 449 | } 450 | 451 | if (f || *p - g - (2 * b) < globalcfg[GLB_MIN_WH].val) { 452 | if (f) { 453 | popfloat(c); 454 | ret = -1; 455 | continue; 456 | } 457 | f = 1; 458 | *p *= 2; 459 | ret = -1; 460 | if (i % 2) { 461 | y = oy; 462 | } else { 463 | x = ox; 464 | } 465 | } 466 | resizehint(c, x + g, y + g, w - (2 * b) - g, h - (2 * b) - g, b, 0, 0); 467 | } 468 | return ret; 469 | } 470 | -------------------------------------------------------------------------------- /src/layout.h: -------------------------------------------------------------------------------- 1 | /* dk window manager 2 | * 3 | * see license file for copyright and license details 4 | * vim:ft=c:fdm=syntax:ts=4:sts=4:sw=4 5 | */ 6 | 7 | #pragma once 8 | 9 | int dwindle(Workspace *ws); 10 | int grid(Workspace *ws); 11 | int ltile(Workspace *ws); 12 | int mono(Workspace *ws); 13 | int rtile(Workspace *ws); 14 | int spiral(Workspace *ws); 15 | -------------------------------------------------------------------------------- /src/parse.c: -------------------------------------------------------------------------------- 1 | /* dk window manager 2 | * 3 | * see license file for copyright and license details 4 | * vim:ft=c:fdm=syntax:ts=4:sts=4:sw=4 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | #include "dk.h" 19 | #include "parse.h" 20 | #include "util.h" 21 | 22 | int parsebool(char *arg) 23 | { 24 | int i; 25 | char *end; 26 | 27 | if (arg && (((i = !strcmp("true", arg)) || !strcmp("false", arg)) || 28 | (((i = strtoul(arg, &end, 0)) > 0 || !strcmp("0", arg)) && *end == '\0'))) { 29 | return (i ? 1 : 0); 30 | } 31 | return -1; 32 | } 33 | 34 | Client *parseclient(char *arg, int *ebadwin) 35 | { 36 | char *end; 37 | uint32_t i; 38 | Client *c = NULL, *t; 39 | 40 | if (!arg || !arg[0]) { 41 | return c; 42 | } 43 | if ((arg[0] == '#' || (arg[0] == '0' && arg[1] == 'x')) && 44 | (i = strtoul(*arg == '#' ? arg + 1 : arg, &end, 16)) > 0 && *end == '\0') { 45 | *ebadwin = (c = wintoclient(i)) ? 0 : -1; 46 | } else { 47 | Workspace *ws; 48 | for (ws = workspaces; ws; ws = ws->next) { 49 | for (t = ws->clients; t; t = t->next) { 50 | if (!strcmp(arg, t->clss)) { 51 | return t; 52 | } 53 | } 54 | } 55 | for (t = scratch.clients; t; t = t->next) { 56 | if (!strcmp(arg, t->clss)) { 57 | return t; 58 | } 59 | } 60 | } 61 | return c; 62 | } 63 | 64 | static char *parsetoken(char **src) 65 | { 66 | /* returns a pointer to a null terminated char * inside src 67 | * *unsafe* modifies src replace separator with null terminator 68 | * handles both strong and weak quoted strings 69 | */ 70 | int qoute, strongquote = 0; 71 | char *s, *t, *head, *tail; 72 | 73 | if (!(*src) || !(**src)) { 74 | return NULL; 75 | } 76 | while (**src && (**src == ' ' || **src == '\t' || **src == '=')) { 77 | (*src)++; 78 | } 79 | 80 | if ((qoute = **src == '"' || (strongquote = **src == '\''))) { 81 | head = *src + 1; 82 | if (!(tail = strchr(head, strongquote ? '\'' : '"'))) { 83 | return 0; 84 | } 85 | if (!strongquote) { 86 | while (*(tail - 1) == '\\') { 87 | tail = strchr(tail + 1, '"'); 88 | } 89 | } 90 | } else { 91 | head = *src; 92 | tail = strpbrk(*src, " =\n\t"); 93 | } 94 | 95 | s = t = head; 96 | while (tail ? s < tail : *s) { 97 | if (qoute && !strongquote && *s == '\\' && *(s + 1) == '"') { 98 | s++; 99 | } else { 100 | *t++ = *s++; 101 | } 102 | } 103 | *t = '\0'; 104 | *src = tail ? ++tail : '\0'; 105 | return head; 106 | } 107 | 108 | void parsecmd(char *buf) 109 | { 110 | char **argv, **save, *tok; 111 | int n = 0, match = 0, max = 32; 112 | status_usingcmdresp = 0; 113 | 114 | save = argv = ecalloc(max, sizeof(char *)); 115 | while ((tok = parsetoken(&buf))) { 116 | if (n + 1 >= max) { 117 | save = argv = erealloc(argv, (max *= 2) * sizeof(char *)); 118 | } 119 | argv[n++] = tok; 120 | } 121 | argv[n] = NULL; 122 | 123 | if (n) { 124 | int j = n; 125 | uint32_t i; 126 | while (j > 0 && *argv) { 127 | for (i = 0, match = 0; keywords[i].str; i++) { 128 | if ((match = !strcmp(keywords[i].str, *argv))) { 129 | cmdc = selws->sel; 130 | if ((n = keywords[i].func(argv + 1)) == -1) { 131 | goto end; 132 | } 133 | argv += ++n, j -= n; 134 | break; 135 | } 136 | } 137 | if (!match && j-- <= 0) { 138 | break; 139 | } 140 | } 141 | } 142 | if (!match && *argv) { 143 | respond(cmdresp, "!invalid or unknown command: %s", *argv); 144 | } 145 | end: 146 | if (cmdresp && !status_usingcmdresp) { 147 | fflush(cmdresp); 148 | fclose(cmdresp); 149 | } 150 | free(save); 151 | } 152 | 153 | int parsecolour(char *arg, uint32_t *result) 154 | { 155 | char *end; 156 | uint32_t argb, len, orig = *result; 157 | 158 | if ((len = strlen(arg)) < 6 || len > 10) { 159 | return -1; 160 | } 161 | len -= arg[0] == '#' ? 1 : (arg[0] == '0' && arg[1] == 'x') ? 2 : 0; 162 | if ((argb = strtoul(arg[0] == '#' ? arg + 1 : arg, &end, 16)) <= 0xffffffff && *end == '\0') { 163 | uint8_t a, r, g, b; 164 | if (len == 6) { 165 | *result = (argb | 0xff000000); 166 | } else if ((a = ((argb & 0xff000000) >> 24)) && a != 0xff) { 167 | r = ((argb & 0x00ff0000 >> 16) * a) / 255; 168 | g = ((argb & 0x0000ff00 >> 8) * a) / 255; 169 | b = ((argb & 0x000000ff) * a) / 255; 170 | *result = (a << 24 | r << 16 | g << 8 | b); 171 | } else { 172 | *result = argb; 173 | } 174 | needsrefresh = needsrefresh || orig != *result; 175 | return 1; 176 | } 177 | return -1; 178 | } 179 | 180 | int parsecoord(char *arg, char type, int *i, int *rel, int *grav) 181 | { 182 | int j; 183 | 184 | if (!arg) { 185 | return 0; 186 | } 187 | if ((j = parseint(arg, rel, type == 'x' || type == 'y' ? 1 : 0)) != INT_MIN) { 188 | *i = j; 189 | } else if (grav) { 190 | if (!strcmp("center", arg)) { 191 | *grav = GRAV_CENTER; 192 | } else { 193 | switch (type) { 194 | case 'x': 195 | if (!strcmp("left", arg)) { 196 | *grav = GRAV_LEFT; 197 | } else if (!strcmp("right", arg)) { 198 | *grav = GRAV_RIGHT; 199 | } else { 200 | return 0; 201 | } 202 | break; 203 | case 'y': 204 | if (!strcmp("top", arg)) { 205 | *grav = GRAV_TOP; 206 | } else if (!strcmp("bottom", arg)) { 207 | *grav = GRAV_BOTTOM; 208 | } else { 209 | return 0; 210 | } 211 | break; 212 | case 'w': /* FALLTHROUGH */ 213 | case 'h': return 0; 214 | } 215 | } 216 | } 217 | return 1; 218 | } 219 | 220 | float parsefloat(char *arg, int *rel) 221 | { 222 | float f; 223 | char *end; 224 | 225 | if (arg && (f = strtof(arg, &end)) && *end == '\0' && f != NAN) { 226 | if (rel) { 227 | *rel = arg[0] == '-' || arg[0] == '+'; 228 | } 229 | return f; 230 | } 231 | return NAN; 232 | } 233 | 234 | int parseint(char *arg, int *rel, int allowzero) 235 | { 236 | int i; 237 | char *end; 238 | 239 | if (arg && ((i = strtol(arg, &end, 0)) || (allowzero && !strcmp("0", arg))) && *end == '\0') { 240 | if (rel) { 241 | *rel = arg[0] == '-' || arg[0] == '+'; 242 | } 243 | return i; 244 | } 245 | return INT_MIN; 246 | } 247 | 248 | int parseintclamp(char *arg, int *rel, int min, int max) 249 | { 250 | int i; 251 | 252 | if ((i = parseint(arg, rel, min <= 0 && max >= 0)) != INT_MIN && i >= min && i <= max) { 253 | return i; 254 | } 255 | return INT_MIN; 256 | } 257 | 258 | int parseopt(char *arg, const char **optarr, int len_optarr) 259 | { 260 | if (arg) { 261 | for (int i = 0; i < len_optarr; optarr++, i++) { 262 | if (!strcmp(*optarr, arg)) { 263 | return i; 264 | } 265 | } 266 | } 267 | return -1; 268 | } 269 | 270 | Workspace *parsewsormon(char *arg, int mon) 271 | { 272 | int i, n = 0; 273 | Monitor *m; 274 | Workspace *cws = selws, *ws; 275 | 276 | if (!arg) { 277 | return NULL; 278 | } 279 | if (mon) { 280 | for (m = nextmon(monitors); m; m = nextmon(m->next)) { 281 | if (!strcmp(m->name, arg)) { 282 | return m->ws; 283 | } 284 | } 285 | } else { 286 | for (ws = workspaces; ws; ws = ws->next) { 287 | if (!strcmp(ws->name, arg)) { 288 | return ws; 289 | } 290 | } 291 | } 292 | if (mon) { 293 | for (m = nextmon(monitors); m; m = nextmon(m->next), n++) 294 | ; 295 | } 296 | if ((i = parseintclamp(arg, NULL, 1, mon ? n : globalcfg[GLB_NUM_WS].val)) == INT_MIN || i <= 0) { 297 | return NULL; 298 | } 299 | return mon ? ((m = nextmon(itomon(i - 1))) ? m->ws : cws) : itows(i - 1); 300 | } 301 | -------------------------------------------------------------------------------- /src/parse.h: -------------------------------------------------------------------------------- 1 | /* dk window manager 2 | * 3 | * see license file for copyright and license details 4 | * vim:ft=c:fdm=syntax:ts=4:sts=4:sw=4 5 | */ 6 | 7 | #pragma once 8 | 9 | int parsebool(char *arg); 10 | Client *parseclient(char *arg, int *ebadwin); 11 | void parsecmd(char *buf); 12 | int parsecolour(char *arg, uint32_t *result); 13 | int parsecoord(char *arg, char type, int *i, int *rel, int *grav); 14 | float parsefloat(char *arg, int *rel); 15 | int parseint(char *arg, int *rel, int allowzero); 16 | int parseintclamp(char *arg, int *rel, int min, int max); 17 | int parseopt(char *arg, const char **optarr, int len_optarr); 18 | Workspace *parsewsormon(char *arg, int mon); 19 | -------------------------------------------------------------------------------- /src/status.c: -------------------------------------------------------------------------------- 1 | /* dk window manager 2 | * 3 | * see license file for copyright and license details 4 | * vim:ft=c:fdm=syntax:ts=4:sts=4:sw=4 5 | */ 6 | 7 | #include 8 | 9 | #include "dk.h" 10 | #include "status.h" 11 | 12 | #define STOB(v, s) STATE(v, s) ? "true" : "false" 13 | 14 | static void _client(Client *c, FILE *f); 15 | static void _clients(FILE *f); 16 | static void _desks(FILE *f); 17 | static void _global(FILE *f); 18 | static void _monitor(Monitor *m, FILE *f); 19 | static void _monitors(FILE *f); 20 | static void _panels(FILE *f); 21 | static void _rules(FILE *f); 22 | static char *_title(Client *c); 23 | static void _workspaces(FILE *f); 24 | static void _workspace(Workspace *ws, FILE *f); 25 | 26 | static void _client(Client *c, FILE *f) 27 | { 28 | fprintf(f, "\"id\":\"0x%08x\",", c->win); 29 | fprintf(f, "\"pid\":%d,", c->pid); 30 | fprintf(f, "\"title\":\"%s\",", _title(c)); 31 | fprintf(f, "\"class\":\"%s\",", c->clss); 32 | fprintf(f, "\"instance\":\"%s\",", c->inst); 33 | fprintf(f, "\"workspace\":%d,", c->ws->num + !STATE(c, SCRATCH)); 34 | fprintf(f, "\"focused\":%s,", c == selws->sel ? "true" : "false"); 35 | fprintf(f, "\"x\":%d,", c->x); 36 | fprintf(f, "\"y\":%d,", c->y); 37 | fprintf(f, "\"w\":%d,", c->w); 38 | fprintf(f, "\"h\":%d,", c->h); 39 | fprintf(f, "\"bw\":%d,", c->bw); 40 | fprintf(f, "\"hoff\":%d,", c->hoff); 41 | fprintf(f, "\"float\":%s,", STOB(c, FLOATING)); 42 | fprintf(f, "\"full\":%s,", STOB(c, FULLSCREEN)); 43 | fprintf(f, "\"fakefull\":%s,", STOB(c, FAKEFULL)); 44 | fprintf(f, "\"fixed\":%s,", STOB(c, FIXED)); 45 | fprintf(f, "\"sticky\":%s,", STOB(c, STICKY)); 46 | fprintf(f, "\"urgent\":%s,", STOB(c, URGENT)); 47 | fprintf(f, "\"above\":%s,", STOB(c, ABOVE)); 48 | fprintf(f, "\"hidden\":%s,", STOB(c, HIDDEN)); 49 | fprintf(f, "\"scratch\":%s,", STOB(c, SCRATCH)); 50 | fprintf(f, "\"no_absorb\":%s,", STOB(c, NOABSORB)); 51 | fprintf(f, "\"callback\":\"%s\",", c->cb ? c->cb->name : ""); 52 | fprintf(f, "\"transient\":{"); 53 | if (c->trans) { 54 | _client(c->trans, f); 55 | } 56 | fprintf(f, "},"); 57 | fprintf(f, "\"absorbed\":{"); 58 | if (c->absorbed) { 59 | _client(c->absorbed, f); 60 | } 61 | fprintf(f, "}"); 62 | fflush(f); 63 | } 64 | 65 | static void _clients(FILE *f) 66 | { 67 | Client *c; 68 | Workspace *ws; 69 | int hasprev = 0; 70 | 71 | fprintf(f, "\"clients\":["); 72 | for (ws = workspaces; ws; ws = ws->next) { 73 | for (c = ws->clients; c; c = c->next) { 74 | fprintf(f, "{"); 75 | _client(c, f); 76 | fprintf(f, "}%s", c->next ? "," : ""); 77 | hasprev = 1; 78 | } 79 | if (hasprev && ws->next && ws->next->clients) { 80 | fprintf(f, ","); 81 | } 82 | } 83 | if (scratch.clients) { 84 | if (hasprev) { 85 | fprintf(f, ","); 86 | } 87 | for (c = scratch.clients; c; c = c->next) { 88 | fprintf(f, "{"); 89 | _client(c, f); 90 | fprintf(f, "}%s", c->next ? "," : ""); 91 | } 92 | } 93 | fprintf(f, "]"); 94 | } 95 | 96 | static void _desks(FILE *f) 97 | { 98 | Desk *d; 99 | 100 | fprintf(f, "\"desks\":["); 101 | for (d = desks; d; d = d->next) { 102 | fprintf(f, "{"); 103 | fprintf(f, "\"id\":\"0x%08x\",", d->win); 104 | fprintf(f, "\"class\":\"%s\",", d->clss); 105 | fprintf(f, "\"instance\":\"%s\",", d->inst); 106 | fprintf(f, "\"monitor\":\"%s\"", d->mon->name); 107 | fprintf(f, "}%s", d->next ? "," : ""); 108 | } 109 | fprintf(f, "]"); 110 | } 111 | 112 | static void _global(FILE *f) 113 | { 114 | fprintf(f, "\"global\":{"); 115 | for (uint32_t i = 0; i < LEN(globalcfg); i++) { 116 | if (globalcfg[i].type == TYPE_BOOL) { 117 | fprintf(f, "\"%s\":%s,", globalcfg[i].str, globalcfg[i].val ? "true" : "false"); 118 | } else { 119 | fprintf(f, "\"%s\":%d,", globalcfg[i].str, globalcfg[i].val); 120 | } 121 | } 122 | fprintf(f, "\"layouts\":["); 123 | for (Layout *l = layouts; l && l->name; ) { 124 | fprintf(f, "\"%s\"", l->name); 125 | l++; 126 | if (l && l->name) { 127 | fprintf(f, ","); 128 | } 129 | } 130 | fprintf(f, "],"); 131 | fprintf(f, "\"callbacks\":["); 132 | for (Callback *cb = callbacks; cb && cb->name; ) { 133 | fprintf(f, "\"%s\"", cb->name); 134 | cb++; 135 | if (cb && cb->name) { 136 | fprintf(f, ","); 137 | } 138 | } 139 | fprintf(f, "],"); 140 | fprintf(f, "\"border\":{"); 141 | fprintf(f, "\"width\":%u,", border[BORD_WIDTH]); 142 | fprintf(f, "\"outer_width\":%u,", border[BORD_O_WIDTH]); 143 | fprintf(f, "\"focus\":\"0x%08x\",", border[BORD_FOCUS]); 144 | fprintf(f, "\"urgent\":\"0x%08x\",", border[BORD_URGENT]); 145 | fprintf(f, "\"unfocus\":\"0x%08x\",", border[BORD_UNFOCUS]); 146 | fprintf(f, "\"outer_focus\":\"0x%08x\",", border[BORD_O_FOCUS]); 147 | fprintf(f, "\"outer_urgent\":\"0x%08x\",", border[BORD_O_URGENT]); 148 | fprintf(f, "\"outer_unfocus\":\"0x%08x\"", border[BORD_O_UNFOCUS]); 149 | fprintf(f, "},"); 150 | fprintf(f, "\"focused\":{"); 151 | _monitor(selmon, f); 152 | fprintf(f, "}}"); 153 | } 154 | 155 | static void _monitor(Monitor *m, FILE *f) 156 | { 157 | fprintf(f, "\"name\":\"%s\",", m->name); 158 | fprintf(f, "\"number\":%d,", m->num + 1); 159 | fprintf(f, "\"focused\":%s,", m->ws == selws ? "true" : "false"); 160 | fprintf(f, "\"x\":%d,", m->x); 161 | fprintf(f, "\"y\":%d,", m->y); 162 | fprintf(f, "\"w\":%d,", m->w); 163 | fprintf(f, "\"h\":%d,", m->h); 164 | fprintf(f, "\"wx\":%d,", m->wx); 165 | fprintf(f, "\"wy\":%d,", m->wy); 166 | fprintf(f, "\"ww\":%d,", m->ww); 167 | fprintf(f, "\"wh\":%d,", m->wh); 168 | fprintf(f, "\"workspace\":{"); 169 | _workspace(m->ws, f); 170 | fprintf(f, "}"); 171 | } 172 | 173 | static void _monitors(FILE *f) 174 | { 175 | Monitor *m; 176 | 177 | fprintf(f, "\"monitors\":["); 178 | for (m = monitors; m; m = m->next) { 179 | if (m->connected) { 180 | fprintf(f, "{"); 181 | _monitor(m, f); 182 | fprintf(f, "}%s", m->next ? "," : ""); 183 | } 184 | } 185 | fprintf(f, "]"); 186 | } 187 | 188 | static void _panels(FILE *f) 189 | { 190 | Panel *p; 191 | 192 | fprintf(f, "\"panels\":["); 193 | for (p = panels; p; p = p->next) { 194 | fprintf(f, "{"); 195 | fprintf(f, "\"id\":\"0x%08x\",", p->win); 196 | fprintf(f, "\"class\":\"%s\",", p->clss); 197 | fprintf(f, "\"instance\":\"%s\",", p->inst); 198 | fprintf(f, "\"x\":%d,", p->x); 199 | fprintf(f, "\"y\":%d,", p->y); 200 | fprintf(f, "\"w\":%d,", p->w); 201 | fprintf(f, "\"h\":%d,", p->h); 202 | fprintf(f, "\"l\":%d,", p->l); 203 | fprintf(f, "\"r\":%d,", p->r); 204 | fprintf(f, "\"t\":%d,", p->t); 205 | fprintf(f, "\"b\":%d,", p->b); 206 | fprintf(f, "\"monitor\":{"); 207 | _monitor(p->mon, f); 208 | fprintf(f, "}}%s", p->next ? "," : ""); 209 | } 210 | fprintf(f, "]"); 211 | } 212 | 213 | static void _rules(FILE *f) 214 | { 215 | Rule *r; 216 | 217 | fprintf(f, "\"rules\":["); 218 | for (r = rules; r; r = r->next) { 219 | fprintf(f, "{"); 220 | fprintf(f, "\"title\":\"%s\",", r->title ? r->title : ""); 221 | fprintf(f, "\"class\":\"%s\",", r->clss ? r->clss : ""); 222 | fprintf(f, "\"instance\":\"%s\",", r->inst ? r->inst : ""); 223 | fprintf(f, "\"workspace\":%d,", r->ws + !STATE(r, SCRATCH)); 224 | fprintf(f, "\"monitor\":\"%s\",", r->mon ? r->mon : ""); 225 | fprintf(f, "\"x\":%d,", r->x); 226 | fprintf(f, "\"y\":%d,", r->y); 227 | fprintf(f, "\"w\":%d,", r->w); 228 | fprintf(f, "\"h\":%d,", r->h); 229 | fprintf(f, "\"float\":%s,", STOB(r, FLOATING)); 230 | fprintf(f, "\"full\":%s,", STOB(r, FULLSCREEN)); 231 | fprintf(f, "\"fakefull\":%s,", STOB(r, FAKEFULL)); 232 | fprintf(f, "\"sticky\":%s,", STOB(r, STICKY)); 233 | fprintf(f, "\"scratch\":%s,", STOB(r, SCRATCH)); 234 | fprintf(f, "\"focus\":%s,", r->focus ? "true" : "false"); 235 | fprintf(f, "\"ignore_cfg\":%s,", STOB(r, IGNORECFG)); 236 | fprintf(f, "\"ignore_msg\":%s,", STOB(r, IGNOREMSG)); 237 | fprintf(f, "\"no_absorb\":%s,", STOB(r, NOABSORB)); 238 | fprintf(f, "\"callback\":\"%s\",", r->cb ? r->cb->name : ""); 239 | fprintf(f, "\"xgrav\":\"%s\",", r->xgrav != GRAV_NONE ? gravs[r->xgrav] : ""); 240 | fprintf(f, "\"ygrav\":\"%s\"", r->ygrav != GRAV_NONE ? gravs[r->ygrav] : ""); 241 | fprintf(f, "}%s", r->next ? "," : ""); 242 | } 243 | fprintf(f, "]"); 244 | } 245 | 246 | static char *_title(Client *c) 247 | { 248 | int idx = 0; 249 | static char title[512]; 250 | 251 | for (uint32_t i = 0; i < sizeof(title) && c->title[i]; i++) { 252 | if (c->title[i] >= ' ') { 253 | if (c->title[i] == '"' || c->title[i] == '\\') { 254 | title[idx++] = '\\'; 255 | } 256 | title[idx++] = c->title[i]; 257 | } 258 | } 259 | title[idx] = '\0'; 260 | 261 | return title; 262 | } 263 | 264 | static void _workspace(Workspace *ws, FILE *f) 265 | { 266 | Client *c; 267 | 268 | fprintf(f, "\"name\":\"%s\",", ws->name); 269 | fprintf(f, "\"number\":%d,", ws->num + 1); 270 | fprintf(f, "\"focused\":%s,", ws == selws ? "true" : "false"); 271 | fprintf(f, "\"monitor\":\"%s\",", ws->mon->name); 272 | fprintf(f, "\"layout\":\"%s\",", ws->layout->name); 273 | fprintf(f, "\"master\":%d,", ws->nmaster); 274 | fprintf(f, "\"stack\":%d,", ws->nstack); 275 | fprintf(f, "\"msplit\":%0.2f,", ws->msplit); 276 | fprintf(f, "\"ssplit\":%0.2f,", ws->ssplit); 277 | fprintf(f, "\"gap\":%d,", ws->gappx); 278 | fprintf(f, "\"smart_gap\":%s,", (ws->smartgap && tilecount(ws) == 1) ? "true" : "false"); 279 | fprintf(f, "\"pad_l\":%d,", ws->padl); 280 | fprintf(f, "\"pad_r\":%d,", ws->padr); 281 | fprintf(f, "\"pad_t\":%d,", ws->padt); 282 | fprintf(f, "\"pad_b\":%d,", ws->padb); 283 | fprintf(f, "\"clients\":["); 284 | for (c = ws->clients; c; c = c->next) { 285 | fprintf(f, "{"); 286 | _client(c, f); 287 | fprintf(f, "}%s", c->next ? "," : ""); 288 | } 289 | fprintf(f, "],"); 290 | fprintf(f, "\"focus_stack\":["); 291 | for (c = ws->stack; c; c = c->snext) { 292 | fprintf(f, "{"); 293 | _client(c, f); 294 | fprintf(f, "}%s", c->snext ? "," : ""); 295 | } 296 | fprintf(f, "]"); 297 | } 298 | 299 | static void _workspaces(FILE *f) 300 | { 301 | Workspace *ws; 302 | 303 | fprintf(f, "\"workspaces\":["); 304 | for (ws = workspaces; ws; ws = ws->next) { 305 | fprintf(f, "{"); 306 | _workspace(ws, f); 307 | fprintf(f, "}%s", ws->next ? "," : ""); 308 | } 309 | fprintf(f, "]"); 310 | } 311 | 312 | void printstatus(Status *s, int freeable) 313 | { 314 | Status *next; 315 | Workspace *ws; 316 | int single = 1; 317 | 318 | if (!s) { 319 | s = stats; 320 | single = 0; 321 | } 322 | while (s) { 323 | next = s->next; 324 | switch (s->type) { 325 | case STAT_WIN: 326 | if (winchange) { 327 | winchange = 0; 328 | fprintf(s->file, "{\"focused\":\"%s\"}", selws->sel ? _title(selws->sel) : ""); 329 | } 330 | break; 331 | case STAT_LYT: 332 | if (lytchange) { 333 | lytchange = 0; 334 | fprintf(s->file, "{\"layout\":\"%s\"}", selws->layout->name); 335 | } 336 | break; 337 | case STAT_WS: 338 | if (!wschange) { 339 | break; 340 | } 341 | wschange = 0; 342 | /* FALL THROUGH */ 343 | case STAT_BAR: 344 | if (s->type == STAT_BAR) { 345 | winchange = lytchange = wschange = 0; 346 | } 347 | fprintf(s->file, "{\"workspaces\":["); 348 | for (ws = workspaces; ws; ws = ws->next) { 349 | fprintf(s->file, "{"); 350 | fprintf(s->file, "\"name\":\"%s\",", ws->name); 351 | fprintf(s->file, "\"number\":%d,", ws->num + 1); 352 | fprintf(s->file, "\"focused\":%s,", ws == selws ? "true" : "false"); 353 | fprintf(s->file, "\"active\":%s,", ws->clients ? "true" : "false"); 354 | fprintf(s->file, "\"monitor\":\"%s\",", ws->mon->name); 355 | fprintf(s->file, "\"layout\":\"%s\",", ws->layout->name); 356 | if (ws->sel && !STATE(ws->sel, HIDDEN)) { 357 | fprintf(s->file, "\"title\":\"%s\",", _title(ws->sel)); 358 | fprintf(s->file, "\"id\":\"0x%08x\"", ws->sel->win); 359 | } else { 360 | fprintf(s->file, "\"title\":\"\","); 361 | fprintf(s->file, "\"id\":\"\""); 362 | } 363 | fprintf(s->file, "}%s", ws->next ? "," : ""); 364 | } 365 | fprintf(s->file, "]}"); 366 | break; 367 | case STAT_FULL: 368 | fprintf(s->file, "{"); 369 | _global(s->file); 370 | fprintf(s->file, ","); 371 | fflush(s->file); 372 | _workspaces(s->file); 373 | fprintf(s->file, ","); 374 | fflush(s->file); 375 | _monitors(s->file); 376 | fprintf(s->file, ","); 377 | fflush(s->file); 378 | _clients(s->file); 379 | fprintf(s->file, ","); 380 | fflush(s->file); 381 | _rules(s->file); 382 | fprintf(s->file, ","); 383 | fflush(s->file); 384 | _panels(s->file); 385 | fprintf(s->file, ","); 386 | fflush(s->file); 387 | _desks(s->file); 388 | fprintf(s->file, "}"); 389 | winchange = lytchange = wschange = 0; 390 | break; 391 | } 392 | fflush(s->file); 393 | /* one-shot status prints have no allocations so aren't free-able */ 394 | if (freeable && !(s->num -= s->num > 0 ? 1 : 0)) { 395 | freestatus(s); 396 | } 397 | if (single) { 398 | break; 399 | } 400 | s = next; 401 | } 402 | } 403 | -------------------------------------------------------------------------------- /src/status.h: -------------------------------------------------------------------------------- 1 | /* dk window manager 2 | * 3 | * see license file for copyright and license details 4 | * vim:ft=c:fdm=syntax:ts=4:sts=4:sw=4 5 | */ 6 | 7 | #pragma once 8 | 9 | void printstatus(Status *s, int freeable); 10 | 11 | -------------------------------------------------------------------------------- /src/strl.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: strlcpy.c,v/strlcat.c,v 1.16 2019/01/25 00:19:25 millert Exp $ 2 | */ 3 | 4 | /* 5 | * Copyright (c) 1998, 2015 Todd C. Miller 6 | * 7 | * Permission to use, copy, modify, and distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | */ 19 | 20 | #include 21 | 22 | #include 23 | 24 | size_t strlcat(char *dst, const char *src, size_t dsize) 25 | { 26 | char *d = dst; 27 | const char *s = src; 28 | size_t n = dsize; 29 | size_t dlen; 30 | 31 | while (n-- != 0 && *d != '\0') { 32 | d++; 33 | } 34 | dlen = d - dst; 35 | n = dsize - dlen; 36 | if (n == 0) { 37 | return dlen + strlen(s); 38 | } 39 | while (*s != '\0') { 40 | if (n != 1) { 41 | *d++ = *s; 42 | n--; 43 | } 44 | s++; 45 | } 46 | *d = '\0'; 47 | return dlen + (s - src); 48 | } 49 | 50 | size_t strlcpy(char *dst, const char *src, size_t dsize) 51 | { 52 | const char *osrc = src; 53 | size_t nleft = dsize; 54 | 55 | if (nleft != 0) { 56 | while (--nleft != 0) { 57 | if ((*dst++ = *src++) == '\0') { 58 | break; 59 | } 60 | } 61 | } 62 | if (nleft == 0) { 63 | if (dsize != 0) { 64 | *dst = '\0'; 65 | } 66 | while (*src++) 67 | ; 68 | } 69 | return (src - osrc - 1); 70 | } 71 | -------------------------------------------------------------------------------- /src/strl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | size_t strlcat(char *dst, const char *src, size_t siz); 4 | size_t strlcpy(char *dst, const char *src, size_t dsize); 5 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | /* dk window manager 2 | * 3 | * see license file for copyright and license details 4 | * vim:ft=c:fdm=syntax:ts=4:sts=4:sw=4 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "util.h" 16 | 17 | void check(int i, char *msg) 18 | { 19 | if (i < 0) { 20 | err(1, "%s", msg); 21 | } 22 | } 23 | 24 | void *ecalloc(size_t elems, size_t elemsize) 25 | { 26 | void *p; 27 | 28 | if (UNLIKELY((p = calloc(elems, elemsize)) == NULL)) { 29 | err(1, "unable to allocate space"); 30 | } 31 | return p; 32 | } 33 | 34 | void *erealloc(void *p, size_t size) 35 | { 36 | void *np; 37 | 38 | if (UNLIKELY((np = realloc(p, size)) == NULL)) { 39 | err(1, "unable to reallocate space"); 40 | } 41 | return np; 42 | } 43 | 44 | char *itoa(int n, char *s) 45 | { 46 | int j, i = 0, sign = n; 47 | 48 | if (sign < 0) { 49 | n = -n; 50 | } 51 | do { 52 | s[i++] = n % 10 + '0'; 53 | } while ((n /= 10) > 0); 54 | if (sign < 0) { 55 | s[i++] = '-'; 56 | } 57 | s[i] = '\0'; 58 | for (j = i - 1, i = 0; i < j; i++, j--) { 59 | char c = s[i]; 60 | s[i] = s[j]; 61 | s[j] = c; 62 | } 63 | return s; 64 | } 65 | 66 | void respond(FILE *f, const char *fmt, ...) 67 | { 68 | va_list ap; 69 | 70 | if (!f) { 71 | return; 72 | } 73 | va_start(ap, fmt); 74 | vfprintf(f, fmt, ap); 75 | va_end(ap); 76 | } 77 | 78 | int usage(char *prog, char *ver, int e, char flag, char *flagstr) 79 | { 80 | switch (flag) { 81 | case 'h': fprintf(stderr, "usage: %s %s\n", prog, flagstr); break; 82 | case 'v': fprintf(stderr, "%s %s\n", prog, ver); break; 83 | } 84 | return e; 85 | } 86 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | /* dk window manager 2 | * 3 | * see license file for copyright and license details 4 | * vim:ft=c:fdm=syntax:ts=4:sts=4:sw=4 5 | */ 6 | 7 | #pragma once 8 | 9 | #define UNLIKELY(x) __builtin_expect(!!(x), 0) 10 | 11 | void check(int i, char *msg); 12 | void *ecalloc(size_t elems, size_t elemsize); 13 | void *erealloc(void *p, size_t size); 14 | char *itoa(int n, char *s); 15 | void respond(FILE *f, const char *fmt, ...); 16 | int usage(char *prog, char *ver, int e, char flag, char *flagstr); 17 | --------------------------------------------------------------------------------