├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── config └── config.rs ├── core ├── Cargo.lock ├── Cargo.toml ├── src │ ├── config.rs │ ├── core │ │ ├── mod.rs │ │ ├── rational_rect.rs │ │ ├── screen.rs │ │ ├── stack.rs │ │ ├── workspace.rs │ │ └── workspaces.rs │ ├── handlers.rs │ ├── keycodes.rs │ ├── layout.rs │ ├── lib.rs │ ├── util.rs │ ├── window_manager.rs │ └── window_system.rs └── tests │ ├── core │ ├── mod.rs │ ├── stack.rs │ └── workspace.rs │ └── mod.rs ├── default.nix ├── src └── wtftw.rs └── xlib ├── Cargo.lock ├── Cargo.toml └── src └── xlib_window_system.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | 7 | # Executables 8 | *.exe 9 | 10 | # Generated by Cargo 11 | /target/ 12 | /.project 13 | 14 | *.racertmp 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: nightly 3 | 4 | before_install: 5 | - sudo apt-get -qq update 6 | - sudo apt-get install -y libxinerama1 libxinerama-dev 7 | 8 | script: make test 9 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "anyhow" 5 | version = "1.0.34" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7" 8 | 9 | [[package]] 10 | name = "arrayref" 11 | version = "0.3.6" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" 14 | 15 | [[package]] 16 | name = "arrayvec" 17 | version = "0.5.2" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 20 | 21 | [[package]] 22 | name = "autocfg" 23 | version = "1.0.1" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 26 | 27 | [[package]] 28 | name = "base64" 29 | version = "0.12.3" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" 32 | 33 | [[package]] 34 | name = "bitflags" 35 | version = "1.2.1" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 38 | 39 | [[package]] 40 | name = "blake2b_simd" 41 | version = "0.5.11" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" 44 | dependencies = [ 45 | "arrayref", 46 | "arrayvec", 47 | "constant_time_eq", 48 | ] 49 | 50 | [[package]] 51 | name = "cfg-if" 52 | version = "0.1.10" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 55 | 56 | [[package]] 57 | name = "chrono" 58 | version = "0.4.19" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 61 | dependencies = [ 62 | "libc", 63 | "num-integer", 64 | "num-traits", 65 | "time", 66 | "winapi", 67 | ] 68 | 69 | [[package]] 70 | name = "constant_time_eq" 71 | version = "0.1.5" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 74 | 75 | [[package]] 76 | name = "crossbeam-utils" 77 | version = "0.7.2" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 80 | dependencies = [ 81 | "autocfg", 82 | "cfg-if", 83 | "lazy_static", 84 | ] 85 | 86 | [[package]] 87 | name = "dirs" 88 | version = "3.0.1" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff" 91 | dependencies = [ 92 | "dirs-sys", 93 | ] 94 | 95 | [[package]] 96 | name = "dirs-sys" 97 | version = "0.3.5" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" 100 | dependencies = [ 101 | "libc", 102 | "redox_users", 103 | "winapi", 104 | ] 105 | 106 | [[package]] 107 | name = "dylib" 108 | version = "0.0.3" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "f06c13073013a912b363eee1433572499a2028a6b05432dad09383124d64731e" 111 | dependencies = [ 112 | "libc", 113 | ] 114 | 115 | [[package]] 116 | name = "getopts" 117 | version = "0.2.21" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 120 | dependencies = [ 121 | "unicode-width", 122 | ] 123 | 124 | [[package]] 125 | name = "getrandom" 126 | version = "0.1.15" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" 129 | dependencies = [ 130 | "cfg-if", 131 | "libc", 132 | "wasi 0.9.0+wasi-snapshot-preview1", 133 | ] 134 | 135 | [[package]] 136 | name = "itoa" 137 | version = "0.4.6" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" 140 | 141 | [[package]] 142 | name = "lazy_static" 143 | version = "1.4.0" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 146 | 147 | [[package]] 148 | name = "libc" 149 | version = "0.2.80" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" 152 | 153 | [[package]] 154 | name = "log" 155 | version = "0.4.11" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" 158 | dependencies = [ 159 | "cfg-if", 160 | ] 161 | 162 | [[package]] 163 | name = "num-integer" 164 | version = "0.1.44" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 167 | dependencies = [ 168 | "autocfg", 169 | "num-traits", 170 | ] 171 | 172 | [[package]] 173 | name = "num-traits" 174 | version = "0.2.14" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 177 | dependencies = [ 178 | "autocfg", 179 | ] 180 | 181 | [[package]] 182 | name = "pkg-config" 183 | version = "0.3.19" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" 186 | 187 | [[package]] 188 | name = "redox_syscall" 189 | version = "0.1.57" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 192 | 193 | [[package]] 194 | name = "redox_users" 195 | version = "0.3.5" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" 198 | dependencies = [ 199 | "getrandom", 200 | "redox_syscall", 201 | "rust-argon2", 202 | ] 203 | 204 | [[package]] 205 | name = "rust-argon2" 206 | version = "0.8.2" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19" 209 | dependencies = [ 210 | "base64", 211 | "blake2b_simd", 212 | "constant_time_eq", 213 | "crossbeam-utils", 214 | ] 215 | 216 | [[package]] 217 | name = "ryu" 218 | version = "1.0.5" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 221 | 222 | [[package]] 223 | name = "serde" 224 | version = "1.0.117" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" 227 | 228 | [[package]] 229 | name = "serde_json" 230 | version = "1.0.59" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" 233 | dependencies = [ 234 | "itoa", 235 | "ryu", 236 | "serde", 237 | ] 238 | 239 | [[package]] 240 | name = "simplelog" 241 | version = "0.8.0" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "2b2736f58087298a448859961d3f4a0850b832e72619d75adc69da7993c2cd3c" 244 | dependencies = [ 245 | "chrono", 246 | "log", 247 | "termcolor", 248 | ] 249 | 250 | [[package]] 251 | name = "termcolor" 252 | version = "1.1.0" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" 255 | dependencies = [ 256 | "winapi-util", 257 | ] 258 | 259 | [[package]] 260 | name = "time" 261 | version = "0.1.44" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 264 | dependencies = [ 265 | "libc", 266 | "wasi 0.10.0+wasi-snapshot-preview1", 267 | "winapi", 268 | ] 269 | 270 | [[package]] 271 | name = "unicode-width" 272 | version = "0.1.8" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 275 | 276 | [[package]] 277 | name = "wasi" 278 | version = "0.9.0+wasi-snapshot-preview1" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 281 | 282 | [[package]] 283 | name = "wasi" 284 | version = "0.10.0+wasi-snapshot-preview1" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 287 | 288 | [[package]] 289 | name = "winapi" 290 | version = "0.3.9" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 293 | dependencies = [ 294 | "winapi-i686-pc-windows-gnu", 295 | "winapi-x86_64-pc-windows-gnu", 296 | ] 297 | 298 | [[package]] 299 | name = "winapi-i686-pc-windows-gnu" 300 | version = "0.4.0" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 303 | 304 | [[package]] 305 | name = "winapi-util" 306 | version = "0.1.5" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 309 | dependencies = [ 310 | "winapi", 311 | ] 312 | 313 | [[package]] 314 | name = "winapi-x86_64-pc-windows-gnu" 315 | version = "0.4.0" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 318 | 319 | [[package]] 320 | name = "wtftw" 321 | version = "0.4.4" 322 | dependencies = [ 323 | "anyhow", 324 | "bitflags", 325 | "dirs", 326 | "dylib", 327 | "getopts", 328 | "libc", 329 | "log", 330 | "serde_json", 331 | "simplelog", 332 | "wtftw_core", 333 | "wtftw_xlib", 334 | "zombie", 335 | ] 336 | 337 | [[package]] 338 | name = "wtftw_core" 339 | version = "0.3.2" 340 | dependencies = [ 341 | "anyhow", 342 | "bitflags", 343 | "dirs", 344 | "dylib", 345 | "libc", 346 | "log", 347 | "serde_json", 348 | ] 349 | 350 | [[package]] 351 | name = "wtftw_xlib" 352 | version = "0.4.0" 353 | dependencies = [ 354 | "libc", 355 | "log", 356 | "wtftw_core", 357 | "x11", 358 | ] 359 | 360 | [[package]] 361 | name = "x11" 362 | version = "2.18.2" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "77ecd092546cb16f25783a5451538e73afc8d32e242648d54f4ae5459ba1e773" 365 | dependencies = [ 366 | "libc", 367 | "pkg-config", 368 | ] 369 | 370 | [[package]] 371 | name = "zombie" 372 | version = "0.0.4" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "a2a923d29f39b60d437dc36e2f33e88493dd4386a397820105ac17100859c6e6" 375 | dependencies = [ 376 | "libc", 377 | ] 378 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | 3 | name = "wtftw" 4 | version = "0.4.4" 5 | authors = ["Simon Wollwage"] 6 | edition = "2018" 7 | 8 | [dependencies] 9 | anyhow = "1.0.34" 10 | bitflags = "1.2.1" 11 | serde_json = "1.0.59" 12 | getopts = "0.2.21" 13 | log = "0.4.11" 14 | libc = "0.2.80" 15 | dylib = "0.0.3" 16 | simplelog = "0.8.0" 17 | zombie = "0.0.4" 18 | wtftw_core = { path = "core" } 19 | dirs = "3.0.1" 20 | 21 | [dependencies.wtftw_xlib] 22 | path = "xlib" 23 | 24 | [lib] 25 | name = "wtftw" 26 | path = "core/src/lib.rs" 27 | 28 | [[bin]] 29 | name = "wtftw" 30 | path = "src/wtftw.rs" 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Simon Wollwage 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of wtftw nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: test_core test_xlib build_all 2 | 3 | test_core: 4 | cd core && cargo test 5 | 6 | test_xlib: 7 | cd xlib && cargo test 8 | 9 | build_all: 10 | cargo build 11 | 12 | .PHONY: test_core test_xlib build_all 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | wtftw 2 | ===== 3 | 4 | 5 | Window Tiling For The Win. A tiling window manager written in Rust 6 | 7 | ![Screenshot](https://i.imgur.com/8KzbKB9.jpg) 8 | 9 | ## Status 10 | [![Build Status](https://travis-ci.org/Kintaro/wtftw.svg?branch=master)](https://travis-ci.org/Kintaro/wtftw) 11 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Kintaro/wtftw?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 12 | [![Issue Stats](http://www.issuestats.com/github/Kintaro/wtftw/badge/pr?style=flat)](http://www.issuestats.com/github/Kintaro/wtftw) 13 | [![Issue Stats](http://www.issuestats.com/github/Kintaro/wtftw/badge/issue?style=flat)](http://www.issuestats.com/github/Kintaro/wtftw) 14 | 15 | ## Build 16 | 17 | **Notice:** Wtftw is compiled against Rust1.2.0+, so make sure to have a recent enough *rustc*. 18 | 19 | To build it, just run 20 | 21 | ``` 22 | cargo build 23 | ``` 24 | 25 | ### Common build issues 26 | On OSX, you might get a linker error when linking against X11 or Xinerama. For example: 27 | 28 | ``` 29 | ld: library not found for -lXinerama 30 | ``` 31 | 32 | You need to install XQuartz to get the X11 libraries, and make sure that they are found. Then just run 33 | 34 | ``` 35 | LIBRARY_PATH=/opt/X11/lib cargo build 36 | ``` 37 | 38 | On Ubuntu if you have this issue, simply install libxinerama-dev and restart cargo build 39 | 40 | ``` 41 | sudo apt-get install libxinerama-dev 42 | ``` 43 | 44 | ## Testing 45 | 46 | If you want to have your own custom config, create one in *~/.wtftw/src/config.rs*. 47 | You can find an example config in *config/config.rs* in this repository. 48 | 49 | For testing, install either **Xnest** or **Xephyr** on your system and run 50 | 51 | ``` 52 | Xephyr -screen 800x600 :1 & 53 | DISPLAY=:1 ./target/release/wtftw & 54 | DISPLAY=:1 thunar & (or whatever application you want to run) 55 | ``` 56 | 57 | or respectively 58 | 59 | ``` 60 | Xnest -geometry 800x600+0+0 :1 & 61 | DISPLAY=:1 ./target/release/wtftw & 62 | DISPLAY=:1 thunar & 63 | ``` 64 | 65 | ## Installation 66 | 67 | Compile it normally with **cargo build --release**, and then either use it with your .xinitrc 68 | or your favorite display manager. If you want to configure it, take a look at the example config in 69 | *config/*. 70 | 71 | After the first start, the config needs to be placed in *~/.wtftw/src/config.rs*. Voila. 72 | 73 | ## Commands 74 | 75 | In a default setting, the commands are hardcoded, but can be changed in your own config. 76 | 77 | ### Switch workspace 78 | ``` 79 | ALT+num 80 | ``` 81 | 82 | ### Open terminal 83 | ``` 84 | ALT+SHIFT+Enter 85 | ``` 86 | 87 | ## Additional layouts and other extensions 88 | 89 | To keep wtftw as small and lean as possible, all layouts (except the standard one) have been moved to 90 | [wtftw-contrib](https://github.com/Kintaro/wtftw-contrib) 91 | 92 | 93 | ## FAQ 94 | 95 | #### Does it work with dual monitors? 96 | 97 | Yes, yes it does. Just use xrandr and you're set. Wtftw will automatically detect the changed setup. It works with as many monitors as your xrandr and xinerama can handle. 98 | 99 | #### What are the alternatives to xmobar? 100 | 101 | Dzen 102 | 103 | #### What font and programs are you using on the screenshot? 104 | 105 | The font is Envy Code R, and the programs are xmobar, vim, htop, screenfetch, weechat and ncmpcpp. 106 | 107 | #### Why should I use wtftw than dwm or even awesome? 108 | 109 | That is more of a personal choice. Wtftw is akin to xmonad. You can do almost anything you want with the config file. Extend it, change it at runtime, your only boundary is the rust language itself. Plus, using it would help a Rust project to detect bugs and improve it. 110 | 111 | ## Tutorial 112 | 113 | I will be making a tutorial series on how to write a window manager. A bit busy with my thesis 114 | at the moment, but the first part is [here](https://kintaro.github.io/rust/window-manager-in-rust-01/) 115 | -------------------------------------------------------------------------------- /config/config.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate wtftw; 3 | extern crate wtftw_contrib; 4 | 5 | use std::ops::Deref; 6 | //use std::ffi::AsOsStr; 7 | use wtftw::window_system::*; 8 | use wtftw::window_manager::*; 9 | use wtftw::handlers::default::*; 10 | use wtftw::config::*; 11 | use wtftw::util::*; 12 | use wtftw::layout::Direction; 13 | use wtftw::layout::LayoutMessage; 14 | use wtftw_contrib::layout::{ AvoidStrutsLayout, LayoutCollection, BinarySpacePartition, GapLayout, MirrorLayout, NoBordersLayout, FullLayout }; 15 | 16 | 17 | #[no_mangle] 18 | pub extern fn configure(_: &mut WindowManager, w: &dyn WindowSystem, config: &mut Config) { 19 | let modm = KeyModifiers::MOD1MASK; 20 | 21 | config.general.mod_mask = modm; 22 | config.general.border_color = 0x404040; 23 | config.general.focus_border_color = 0xebebeb; 24 | config.general.border_width = 2; 25 | config.general.terminal = (String::from("urxvt"), String::from("")); 26 | config.general.layout = LayoutCollection::boxed_new(vec!( 27 | GapLayout::boxed_new(8, AvoidStrutsLayout::boxed_new(vec!(Direction::Up, Direction::Down), BinarySpacePartition::boxed_new())), 28 | GapLayout::boxed_new(8, AvoidStrutsLayout::boxed_new(vec!(Direction::Up, Direction::Down), MirrorLayout::boxed_new(BinarySpacePartition::boxed_new()))), 29 | NoBordersLayout::boxed_new(Box::new(FullLayout)))); 30 | 31 | config.general.tags = (vec!("一: ターミナル", "二: ウェブ", "三: コード", 32 | "四: メディア", "五: スチーム", "六: ラテック", 33 | "七: 音楽", "八: im", "九: 残り")) 34 | .into_iter().map(String::from).collect(); 35 | 36 | // Register key handlers 37 | 38 | // Some standard key handlers for starting, restarting, etc. 39 | add_key_handler_str!(config, w, "q", modm | KeyModifiers::SHIFTMASK, exit); 40 | add_key_handler_str!(config, w, "q", modm, restart); 41 | add_key_handler_str!(config, w, "Return", modm | KeyModifiers::SHIFTMASK, start_terminal); 42 | add_key_handler_str!(config, w, "p", modm, start_launcher); 43 | 44 | // Focus and window movement 45 | add_key_handler_str!(config, w, "j", modm, |m, w, c| m.windows(w.deref(), c, &|x| x.focus_down())); 46 | add_key_handler_str!(config, w, "k", modm, |m, w, c| m.windows(w.deref(), c, &|x| x.focus_up())); 47 | add_key_handler_str!(config, w, "j", modm | KeyModifiers::SHIFTMASK, |m, w, c| m.windows(w.deref(), c, &|x| x.swap_down())); 48 | add_key_handler_str!(config, w, "k", modm | KeyModifiers::SHIFTMASK, |m, w, c| m.windows(w.deref(), c, &|x| x.swap_up())); 49 | add_key_handler_str!(config, w, "Return", modm, |m, w, c| m.windows(w.deref(), c, &|x| x.swap_master())); 50 | add_key_handler_str!(config, w, "c", modm, |m, w, c| m.kill_window(w.deref()).windows(w.deref(), c, &|x| x.clone())); 51 | 52 | add_key_handler_str!(config, w, "t", modm, |m, w, c| { 53 | match m.workspaces.peek() { 54 | Some(window) => m.windows(w.deref(), c, &|x| x.sink(window)), 55 | None => m 56 | } 57 | }); 58 | 59 | // Layout messages 60 | add_key_handler_str!(config, w, "h", modm, send_layout_message!(LayoutMessage::Decrease)); 61 | add_key_handler_str!(config, w, "l", modm, send_layout_message!(LayoutMessage::Increase)); 62 | add_key_handler_str!(config, w, "z", modm, send_layout_message!(LayoutMessage::DecreaseSlave)); 63 | add_key_handler_str!(config, w, "a", modm, send_layout_message!(LayoutMessage::IncreaseSlave)); 64 | add_key_handler_str!(config, w, "x", modm | KeyModifiers::SHIFTMASK, send_layout_message!(LayoutMessage::IncreaseGap)); 65 | add_key_handler_str!(config, w, "s", modm | KeyModifiers::SHIFTMASK, send_layout_message!(LayoutMessage::DecreaseGap)); 66 | add_key_handler_str!(config, w, "comma", modm, send_layout_message!(LayoutMessage::IncreaseMaster)); 67 | add_key_handler_str!(config, w, "period", modm, send_layout_message!(LayoutMessage::DecreaseMaster)); 68 | add_key_handler_str!(config, w, "space", modm, send_layout_message!(LayoutMessage::Next)); 69 | add_key_handler_str!(config, w, "space", modm | KeyModifiers::SHIFTMASK, send_layout_message!(LayoutMessage::Prev)); 70 | add_key_handler_str!(config, w, "r", modm, send_layout_message!(LayoutMessage::TreeRotate)); 71 | add_key_handler_str!(config, w, "s", modm, send_layout_message!(LayoutMessage::TreeSwap)); 72 | add_key_handler_str!(config, w, "u", modm | KeyModifiers::SHIFTMASK, send_layout_message!(LayoutMessage::TreeExpandTowards(Direction::Left))); 73 | add_key_handler_str!(config, w, "p", modm | KeyModifiers::SHIFTMASK, send_layout_message!(LayoutMessage::TreeExpandTowards(Direction::Right))); 74 | add_key_handler_str!(config, w, "i", modm | KeyModifiers::SHIFTMASK, send_layout_message!(LayoutMessage::TreeExpandTowards(Direction::Down))); 75 | add_key_handler_str!(config, w, "o", modm | KeyModifiers::SHIFTMASK, send_layout_message!(LayoutMessage::TreeExpandTowards(Direction::Up))); 76 | add_key_handler_str!(config, w, "u", modm | KeyModifiers::CONTROLMASK, send_layout_message!(LayoutMessage::TreeShrinkFrom(Direction::Left))); 77 | add_key_handler_str!(config, w, "p", modm | KeyModifiers::CONTROLMASK, send_layout_message!(LayoutMessage::TreeShrinkFrom(Direction::Right))); 78 | add_key_handler_str!(config, w, "i", modm | KeyModifiers::CONTROLMASK, send_layout_message!(LayoutMessage::TreeShrinkFrom(Direction::Down))); 79 | add_key_handler_str!(config, w, "o", modm | KeyModifiers::CONTROLMASK, send_layout_message!(LayoutMessage::TreeShrinkFrom(Direction::Up))); 80 | 81 | 82 | // Workspace switching and moving 83 | for i in 1usize..10 { 84 | add_key_handler_str!(config, w, &i.to_string(), modm, 85 | move |m, w, c| switch_to_workspace(m, w, c, i - 1)); 86 | 87 | add_key_handler_str!(config, w, &i.to_string(), modm | KeyModifiers::SHIFTMASK, 88 | move |m, w, c| move_window_to_workspace(m, w, c, i - 1)); 89 | } 90 | 91 | // Media keys 92 | add_key_handler_str!(config, w, "j", modm | KeyModifiers::CONTROLMASK, run!("amixer", "-q set Master 5%-")); 93 | add_key_handler_str!(config, w, "k", modm | KeyModifiers::CONTROLMASK, run!("amixer", "-q set Master 5%+")); 94 | 95 | add_key_handler_code!(config, 0x1008ff11, KeyModifiers::NONEMASK, run!("amixer", "-q set Master 5%-")); 96 | add_key_handler_code!(config, 0x1008ff13, KeyModifiers::NONEMASK, run!("amixer", "-q set Master 5%+")); 97 | 98 | add_key_handler_code!(config, 0x1008ff02, KeyModifiers::NONEMASK, run!("xbacklight", "+10")); 99 | add_key_handler_code!(config, 0x1008ff03, KeyModifiers::NONEMASK, run!("xbacklight", "-10")); 100 | 101 | add_mouse_handler!(config, BUTTON1, modm, 102 | |m, w, c, s| { 103 | m.focus(s, w.deref(), c).mouse_move_window(w.deref(), c, s).windows(w.deref(), c, &|x| x.shift_master()) 104 | }); 105 | add_mouse_handler!(config, BUTTON3, modm, 106 | |m, w, c, s| { 107 | m.focus(s, w.deref(), c).mouse_resize_window(w.deref(), c, s).windows(w.deref(), c, &|x| x.shift_master()) 108 | }); 109 | } 110 | -------------------------------------------------------------------------------- /core/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "anyhow" 5 | version = "1.0.31" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" 8 | 9 | [[package]] 10 | name = "arrayref" 11 | version = "0.3.6" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" 14 | 15 | [[package]] 16 | name = "arrayvec" 17 | version = "0.5.1" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" 20 | 21 | [[package]] 22 | name = "autocfg" 23 | version = "1.0.0" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 26 | 27 | [[package]] 28 | name = "base64" 29 | version = "0.11.0" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" 32 | 33 | [[package]] 34 | name = "bitflags" 35 | version = "1.2.1" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 38 | 39 | [[package]] 40 | name = "blake2b_simd" 41 | version = "0.5.10" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" 44 | dependencies = [ 45 | "arrayref", 46 | "arrayvec", 47 | "constant_time_eq", 48 | ] 49 | 50 | [[package]] 51 | name = "cfg-if" 52 | version = "0.1.10" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 55 | 56 | [[package]] 57 | name = "constant_time_eq" 58 | version = "0.1.5" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 61 | 62 | [[package]] 63 | name = "crossbeam-utils" 64 | version = "0.7.2" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 67 | dependencies = [ 68 | "autocfg", 69 | "cfg-if", 70 | "lazy_static", 71 | ] 72 | 73 | [[package]] 74 | name = "dirs" 75 | version = "3.0.1" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff" 78 | dependencies = [ 79 | "dirs-sys", 80 | ] 81 | 82 | [[package]] 83 | name = "dirs-sys" 84 | version = "0.3.5" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" 87 | dependencies = [ 88 | "libc", 89 | "redox_users", 90 | "winapi", 91 | ] 92 | 93 | [[package]] 94 | name = "dylib" 95 | version = "0.0.3" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "f06c13073013a912b363eee1433572499a2028a6b05432dad09383124d64731e" 98 | dependencies = [ 99 | "libc", 100 | ] 101 | 102 | [[package]] 103 | name = "getrandom" 104 | version = "0.1.14" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 107 | dependencies = [ 108 | "cfg-if", 109 | "libc", 110 | "wasi", 111 | ] 112 | 113 | [[package]] 114 | name = "itoa" 115 | version = "0.4.6" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" 118 | 119 | [[package]] 120 | name = "lazy_static" 121 | version = "1.4.0" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 124 | 125 | [[package]] 126 | name = "libc" 127 | version = "0.2.71" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" 130 | 131 | [[package]] 132 | name = "log" 133 | version = "0.4.8" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 136 | dependencies = [ 137 | "cfg-if", 138 | ] 139 | 140 | [[package]] 141 | name = "redox_syscall" 142 | version = "0.1.56" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 145 | 146 | [[package]] 147 | name = "redox_users" 148 | version = "0.3.4" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" 151 | dependencies = [ 152 | "getrandom", 153 | "redox_syscall", 154 | "rust-argon2", 155 | ] 156 | 157 | [[package]] 158 | name = "rust-argon2" 159 | version = "0.7.0" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" 162 | dependencies = [ 163 | "base64", 164 | "blake2b_simd", 165 | "constant_time_eq", 166 | "crossbeam-utils", 167 | ] 168 | 169 | [[package]] 170 | name = "ryu" 171 | version = "1.0.5" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 174 | 175 | [[package]] 176 | name = "serde" 177 | version = "1.0.114" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" 180 | 181 | [[package]] 182 | name = "serde_json" 183 | version = "1.0.56" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "3433e879a558dde8b5e8feb2a04899cf34fdde1fafb894687e52105fc1162ac3" 186 | dependencies = [ 187 | "itoa", 188 | "ryu", 189 | "serde", 190 | ] 191 | 192 | [[package]] 193 | name = "wasi" 194 | version = "0.9.0+wasi-snapshot-preview1" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 197 | 198 | [[package]] 199 | name = "winapi" 200 | version = "0.3.9" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 203 | dependencies = [ 204 | "winapi-i686-pc-windows-gnu", 205 | "winapi-x86_64-pc-windows-gnu", 206 | ] 207 | 208 | [[package]] 209 | name = "winapi-i686-pc-windows-gnu" 210 | version = "0.4.0" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 213 | 214 | [[package]] 215 | name = "winapi-x86_64-pc-windows-gnu" 216 | version = "0.4.0" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 219 | 220 | [[package]] 221 | name = "wtftw_core" 222 | version = "0.3.2" 223 | dependencies = [ 224 | "anyhow", 225 | "bitflags", 226 | "dirs", 227 | "dylib", 228 | "libc", 229 | "log", 230 | "serde_json", 231 | ] 232 | -------------------------------------------------------------------------------- /core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "wtftw_core" 3 | version = "0.3.2" 4 | authors = ["Simon Wollwage"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | anyhow = "1.0.31" 9 | bitflags = "1.2.1" 10 | serde_json = "1.0.56" 11 | log = "0.4.8" 12 | libc = "0.2.71" 13 | dylib = "0.0.3" 14 | dirs = "3.0.1" 15 | 16 | -------------------------------------------------------------------------------- /core/src/config.rs: -------------------------------------------------------------------------------- 1 | use crate::core::workspaces::Workspaces; 2 | use crate::handlers::default::{exit, restart, start_terminal}; 3 | use crate::handlers::{KeyHandler, LogHook, ManageHook, MouseHandler, StartupHook}; 4 | use crate::layout::{Layout, TallLayout}; 5 | use crate::window_manager::WindowManager; 6 | use crate::window_system::{ 7 | KeyCommand, KeyModifiers, MouseButton, MouseCommand, Window, WindowSystem, 8 | }; 9 | use std::borrow::ToOwned; 10 | use std::collections::BTreeMap; 11 | 12 | use anyhow::Result; 13 | use dylib::DynamicLibrary; 14 | use std::fs::metadata; 15 | use std::fs::File; 16 | use std::fs::{create_dir_all, read_dir}; 17 | use std::io::Write; 18 | use std::mem; 19 | use std::path::Path; 20 | use std::path::PathBuf; 21 | use std::process::Child; 22 | use std::process::Command; 23 | use std::rc::Rc; 24 | use std::sync::RwLock; 25 | use std::thread::spawn; 26 | 27 | pub struct GeneralConfig { 28 | /// Whether focus follows mouse movements or 29 | /// only click events and keyboard movements. 30 | pub focus_follows_mouse: bool, 31 | /// Border color for focused windows. 32 | pub focus_border_color: u32, 33 | /// Border color for unfocused windows. 34 | pub border_color: u32, 35 | /// Border width. This is the same for both, focused and unfocused. 36 | pub border_width: u32, 37 | /// Default terminal to start 38 | pub terminal: (String, String), 39 | /// Keybind for the terminal 40 | /// Path to the logfile 41 | pub logfile: String, 42 | /// Default tags for workspaces 43 | pub tags: Vec, 44 | /// Default launcher application 45 | pub launcher: String, 46 | pub mod_mask: KeyModifiers, 47 | pub pipes: Vec>>, 48 | pub layout: Box, 49 | } 50 | 51 | impl Clone for GeneralConfig { 52 | fn clone(&self) -> GeneralConfig { 53 | GeneralConfig { 54 | focus_follows_mouse: self.focus_follows_mouse, 55 | focus_border_color: self.focus_border_color, 56 | border_color: self.border_color, 57 | border_width: self.border_width, 58 | terminal: self.terminal.clone(), 59 | logfile: self.logfile.clone(), 60 | tags: self.tags.clone(), 61 | launcher: self.launcher.clone(), 62 | mod_mask: self.mod_mask, 63 | pipes: self.pipes.clone(), 64 | layout: self.layout.copy(), 65 | } 66 | } 67 | } 68 | 69 | pub struct InternalConfig { 70 | pub library: Option, 71 | pub key_handlers: BTreeMap, 72 | pub mouse_handlers: BTreeMap, 73 | pub manage_hook: ManageHook, 74 | pub startup_hook: StartupHook, 75 | pub loghook: Option, 76 | pub wtftw_dir: String, 77 | } 78 | 79 | impl InternalConfig { 80 | pub fn new(manage_hook: ManageHook, startup_hook: StartupHook, home: String) -> InternalConfig { 81 | InternalConfig { 82 | library: None, 83 | key_handlers: BTreeMap::new(), 84 | mouse_handlers: BTreeMap::new(), 85 | manage_hook, 86 | startup_hook, 87 | loghook: None, 88 | wtftw_dir: format!("{}/.wtftw", home), 89 | } 90 | } 91 | } 92 | 93 | /// Common configuration options for the window manager. 94 | pub struct Config { 95 | pub general: GeneralConfig, 96 | pub internal: InternalConfig, 97 | } 98 | 99 | impl Config { 100 | /// Create the Config from a json file 101 | pub fn initialize() -> Result { 102 | let home = dirs::home_dir() 103 | .unwrap_or_else(|| PathBuf::from("./")) 104 | .into_os_string() 105 | .into_string() 106 | .expect("unable to find config directory"); 107 | // Default version of the config, for fallback 108 | let general_config = GeneralConfig { 109 | focus_follows_mouse: true, 110 | focus_border_color: 0x00B6FFB0, 111 | border_color: 0x00444444, 112 | border_width: 2, 113 | mod_mask: KeyModifiers::MOD1MASK, 114 | terminal: ("xterm".to_owned(), "".to_owned()), 115 | logfile: format!("{}/.wtftw.log", home), 116 | tags: vec![ 117 | "1: term".to_owned(), 118 | "2: web".to_owned(), 119 | "3: code".to_owned(), 120 | "4: media".to_owned(), 121 | ], 122 | launcher: "dmenu_run".to_owned(), 123 | pipes: Vec::new(), 124 | layout: Box::new(TallLayout { 125 | num_master: 1, 126 | increment_ratio: 0.3 / 100.0, 127 | ratio: 0.5, 128 | }), 129 | }; 130 | 131 | let internal_config = InternalConfig::new( 132 | Box::new(move |a, _, _| a), 133 | Box::new(move |a, _, _| a), 134 | //Box::new(Config::default_manage_hook), 135 | //Box::new(Config::default_startup_hook), 136 | home, 137 | ); 138 | 139 | Ok(Config { 140 | general: general_config, 141 | internal: internal_config, 142 | }) 143 | } 144 | 145 | pub fn default_manage_hook(m: Workspaces, _: Rc, _: Window) -> Workspaces { 146 | m 147 | } 148 | 149 | pub fn default_startup_hook( 150 | m: WindowManager, 151 | _: Rc, 152 | _: &Config, 153 | ) -> WindowManager { 154 | m 155 | } 156 | 157 | pub fn default_configuration(&mut self, w: &dyn WindowSystem) { 158 | let mod_mask = self.general.mod_mask; 159 | self.add_key_handler( 160 | w.get_keycode_from_string("Return"), 161 | mod_mask | KeyModifiers::SHIFTMASK, 162 | Box::new(|m, ws, c| start_terminal(m, ws, c)), 163 | ); 164 | self.add_key_handler( 165 | w.get_keycode_from_string("q"), 166 | mod_mask, 167 | Box::new(|m, ws, c| restart(m, ws, c).expect("error while restarting wtftw")), 168 | ); 169 | self.add_key_handler( 170 | w.get_keycode_from_string("q"), 171 | mod_mask | KeyModifiers::SHIFTMASK, 172 | Box::new(|m, ws, c| exit(m, ws, c)), 173 | ); 174 | } 175 | 176 | pub fn get_mod_mask(&self) -> KeyModifiers { 177 | self.general.mod_mask 178 | } 179 | 180 | pub fn add_key_handler(&mut self, key: u64, mask: KeyModifiers, keyhandler: KeyHandler) { 181 | self.internal 182 | .key_handlers 183 | .insert(KeyCommand::new(key, mask), keyhandler); 184 | } 185 | 186 | pub fn add_mouse_handler( 187 | &mut self, 188 | button: MouseButton, 189 | mask: KeyModifiers, 190 | mousehandler: MouseHandler, 191 | ) { 192 | self.internal 193 | .mouse_handlers 194 | .insert(MouseCommand::new(button, mask), mousehandler); 195 | } 196 | 197 | pub fn set_manage_hook(&mut self, hook: ManageHook) { 198 | self.internal.manage_hook = hook; 199 | } 200 | 201 | pub fn set_log_hook(&mut self, hook: LogHook) { 202 | self.internal.loghook = Some(hook); 203 | } 204 | 205 | pub fn compile_and_call(&mut self, m: &mut WindowManager, w: &dyn WindowSystem) -> Result<()> { 206 | let toml = format!("{}/Cargo.toml", self.internal.wtftw_dir.clone()); 207 | 208 | if !path_exists(&self.internal.wtftw_dir.clone()) { 209 | match create_dir_all(Path::new(&self.internal.wtftw_dir.clone())) { 210 | Ok(()) => (), 211 | Err(e) => panic!(format!( 212 | "mkdir: {} failed with error {}", 213 | self.internal.wtftw_dir.clone(), 214 | e 215 | )), 216 | } 217 | } 218 | 219 | if !path_exists(&toml) { 220 | let file = File::create(Path::new(&toml).as_os_str()); 221 | file?.write_all( 222 | "[project]\n\ 223 | name = \"config\"\n\ 224 | version = \"0.0.0\"\n\ 225 | authors = [\"wtftw\"]\n\n\ 226 | [dependencies.wtftw_contrib] 227 | git = \"https://github.com/Kintaro/wtftw-contrib.git\"\n 228 | [dependencies.wtftw]\n\ 229 | git = \"https://github.com/Kintaro/wtftw.git\"\n\n\ 230 | [lib]\n\ 231 | name = \"config\"\n\ 232 | crate-type = [\"dylib\"]" 233 | .as_bytes(), 234 | )?; 235 | } 236 | 237 | let config_source = format!("{}/src/lib.rs", self.internal.wtftw_dir.clone()); 238 | if path_exists(&config_source) && self.compile()? { 239 | self.call(m, w)?; 240 | } else { 241 | self.default_configuration(w); 242 | } 243 | Ok(()) 244 | } 245 | 246 | pub fn compile(&self) -> Result { 247 | info!("updating dependencies"); 248 | Command::new("cargo") 249 | .current_dir(&Path::new(&self.internal.wtftw_dir.clone())) 250 | .arg("update") 251 | .env("RUST_LOG", "none") 252 | .output()?; 253 | info!("compiling config module"); 254 | let output = Command::new("cargo") 255 | .current_dir(&Path::new(&self.internal.wtftw_dir.clone())) 256 | .arg("build") //.arg("--release") 257 | .env("RUST_LOG", "none") 258 | .output(); 259 | 260 | Ok(match output { 261 | Ok(o) => { 262 | if o.status.success() { 263 | info!("config module compiled"); 264 | true 265 | } else { 266 | error!("error compiling config module"); 267 | 268 | spawn(move || { 269 | Command::new("xmessage").arg("\"error compiling config module. run 'cargo build' in ~/.wtftw to get more info.\"").spawn().unwrap(); 270 | }); 271 | false 272 | } 273 | } 274 | Err(err) => { 275 | error!("error compiling config module"); 276 | spawn(move || { 277 | Command::new("xmessage") 278 | .arg(format!("{}", err)) 279 | .spawn() 280 | .unwrap(); 281 | }); 282 | false 283 | } 284 | }) 285 | } 286 | 287 | pub fn call(&mut self, m: &mut WindowManager, w: &dyn WindowSystem) -> Result<()> { 288 | debug!("looking for config module"); 289 | let mut contents = read_dir(&Path::new(&format!( 290 | "{}/target/debug", 291 | self.internal.wtftw_dir.clone() 292 | )))?; 293 | let libname = contents.find(|x| match *x { 294 | Ok(ref y) => y 295 | .path() 296 | .into_os_string() 297 | .as_os_str() 298 | .to_str() 299 | .map(|x| x.contains("libconfig.so")) 300 | .unwrap_or(false), 301 | Err(_) => false, 302 | }); 303 | 304 | if let Ok(lib) = DynamicLibrary::open(Some(&Path::new( 305 | &libname 306 | .unwrap() 307 | .unwrap() 308 | .path() 309 | .as_os_str() 310 | .to_str() 311 | .unwrap(), 312 | ))) { 313 | unsafe { 314 | if let Ok(symbol) = lib.symbol("configure") { 315 | let result = mem::transmute::< 316 | *mut u8, 317 | extern "C" fn(&mut WindowManager, &dyn WindowSystem, &mut Config), 318 | >(symbol); 319 | 320 | self.internal.library = Some(lib); 321 | result(m, w, self); 322 | } else { 323 | error!("Error loading config module") 324 | } 325 | } 326 | } 327 | 328 | Ok(()) 329 | } 330 | } 331 | 332 | fn path_exists(path: &str) -> bool { 333 | metadata(path).is_ok() 334 | } 335 | -------------------------------------------------------------------------------- /core/src/core/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod screen; 2 | pub mod stack; 3 | pub mod workspace; 4 | pub mod workspaces; 5 | pub mod rational_rect; 6 | -------------------------------------------------------------------------------- /core/src/core/rational_rect.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | #[derive(Clone, Copy, Debug)] 4 | pub struct RationalRect(pub f32, pub f32, pub f32, pub f32); 5 | -------------------------------------------------------------------------------- /core/src/core/screen.rs: -------------------------------------------------------------------------------- 1 | use crate::config::GeneralConfig; 2 | use crate::core::stack::Stack; 3 | use crate::core::workspace::Workspace; 4 | use crate::layout::LayoutMessage; 5 | use crate::window_manager::ScreenDetail; 6 | use crate::window_system::{Window, WindowSystem}; 7 | 8 | pub struct Screen { 9 | pub workspace: Workspace, 10 | pub screen_id: u32, 11 | pub screen_detail: ScreenDetail, 12 | } 13 | 14 | impl Clone for Screen { 15 | fn clone(&self) -> Screen { 16 | Screen { 17 | workspace: self.workspace.clone(), 18 | screen_id: self.screen_id, 19 | screen_detail: self.screen_detail, 20 | } 21 | } 22 | } 23 | 24 | impl Screen { 25 | /// Create a new screen for the given workspace 26 | /// and the given dimensions 27 | pub fn new(workspace: Workspace, screen_id: u32, screen_detail: ScreenDetail) -> Screen { 28 | Screen { 29 | workspace, 30 | screen_id, 31 | screen_detail, 32 | } 33 | } 34 | 35 | /// Checks if the screen's workspace contains 36 | /// the given window 37 | pub fn contains(&self, window: Window) -> bool { 38 | self.workspace.contains(window) 39 | } 40 | 41 | /// Returns the number of windows in the 42 | /// screen's workspace 43 | pub fn len(&self) -> usize { 44 | self.workspace.len() 45 | } 46 | 47 | pub fn is_empty(&self) -> bool { 48 | self.workspace.is_empty() 49 | } 50 | 51 | pub fn windows(&self) -> Vec { 52 | self.workspace.windows() 53 | } 54 | 55 | pub fn map_workspace(&self, f: F) -> Screen 56 | where 57 | F: Fn(Workspace) -> Workspace, 58 | { 59 | Screen::new( 60 | f(self.workspace.clone()), 61 | self.screen_id, 62 | self.screen_detail, 63 | ) 64 | } 65 | 66 | pub fn map(&self, f: F) -> Screen 67 | where 68 | F: Fn(Stack) -> Stack, 69 | { 70 | Screen::new(self.workspace.map(f), self.screen_id, self.screen_detail) 71 | } 72 | 73 | pub fn map_option(&self, f: F) -> Screen 74 | where 75 | F: Fn(Stack) -> Option>, 76 | { 77 | Screen::new( 78 | self.workspace.map_option(f), 79 | self.screen_id, 80 | self.screen_detail, 81 | ) 82 | } 83 | 84 | pub fn map_or(&self, default: Stack, f: F) -> Screen 85 | where 86 | F: Fn(Stack) -> Stack, 87 | { 88 | Screen::new( 89 | self.workspace.map_or(default, f), 90 | self.screen_id, 91 | self.screen_detail, 92 | ) 93 | } 94 | 95 | pub fn send_layout_message( 96 | &self, 97 | message: LayoutMessage, 98 | window_system: &dyn WindowSystem, 99 | config: &GeneralConfig, 100 | ) -> Screen { 101 | Screen::new( 102 | self.workspace 103 | .send_layout_message(message, window_system, config), 104 | self.screen_id, 105 | self.screen_detail, 106 | ) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /core/src/core/stack.rs: -------------------------------------------------------------------------------- 1 | /// Handles focus tracking on a workspace. 2 | /// `focus` keeps track of the focused window's id 3 | /// and `up` and `down` are the windows above or 4 | /// below the focus stack respectively. 5 | #[derive(Clone, PartialEq, Eq)] 6 | pub struct Stack { 7 | pub focus: T, 8 | pub up: Vec, 9 | pub down: Vec, 10 | } 11 | 12 | impl Stack { 13 | /// Create a new stack with the given values 14 | pub fn new(f: T, up: Vec, down: Vec) -> Stack { 15 | Stack { focus: f, up, down } 16 | } 17 | 18 | /// Create a new stack with only the given element 19 | /// as the focused one and initialize the rest to empty. 20 | pub fn from_element(t: T) -> Stack { 21 | Stack { 22 | focus: t, 23 | up: Vec::new(), 24 | down: Vec::new(), 25 | } 26 | } 27 | 28 | /// Add a new element to the stack 29 | /// and automatically focus it. 30 | pub fn add(&self, t: T) -> Stack { 31 | Stack { 32 | focus: t, 33 | up: self.up.clone(), 34 | down: self 35 | .down 36 | .clone() 37 | .into_iter() 38 | .chain((vec![self.focus.clone()]).into_iter()) 39 | .collect(), 40 | } 41 | } 42 | 43 | /// Flatten the stack into a list 44 | pub fn integrate(&self) -> Vec { 45 | self.up 46 | .iter() 47 | .rev() 48 | .chain((vec![self.focus.clone()]).iter()) 49 | .chain(self.down.iter()) 50 | .cloned() 51 | .collect() 52 | } 53 | 54 | /// Filter the stack to retain only windows 55 | /// that yield true in the given filter function 56 | pub fn filter(&self, f: F) -> Option> 57 | where 58 | F: Fn(&T) -> bool, 59 | { 60 | let lrs: Vec = (vec![self.focus.clone()]) 61 | .iter() 62 | .chain(self.down.iter()) 63 | .filter(|&x| f(x)).cloned() 64 | .collect(); 65 | 66 | if !lrs.is_empty() { 67 | let first: T = lrs[0].clone(); 68 | let rest: Vec = lrs.iter().skip(1).cloned().collect(); 69 | let filtered: Vec = self.up.iter().filter(|&x| f(x)).cloned().collect(); 70 | let stack: Stack = Stack::::new(first, filtered, rest); 71 | 72 | Some(stack) 73 | } else { 74 | let filtered: Vec = self.up.clone().into_iter().filter(|x| f(x)).collect(); 75 | if !filtered.is_empty() { 76 | let first: T = filtered[0].clone(); 77 | let rest: Vec = filtered.iter().skip(1).cloned().collect(); 78 | Some(Stack::::new(first, rest, Vec::new())) 79 | } else { 80 | None 81 | } 82 | } 83 | } 84 | 85 | /// Move the focus to the next element in the `up` list 86 | pub fn focus_up(&self) -> Stack { 87 | if self.up.is_empty() { 88 | let tmp: Vec = (vec![self.focus.clone()]) 89 | .into_iter() 90 | .chain(self.down.clone().into_iter()) 91 | .rev() 92 | .collect(); 93 | let xs: Vec = tmp.iter().skip(1).cloned().collect(); 94 | 95 | Stack::::new(tmp[0].clone(), xs, Vec::new()) 96 | } else { 97 | let down = (vec![self.focus.clone()]) 98 | .into_iter() 99 | .chain(self.down.clone().into_iter()) 100 | .collect(); 101 | let up = self.up.iter().skip(1).cloned().collect(); 102 | Stack::::new(self.up[0].clone(), up, down) 103 | } 104 | } 105 | 106 | /// Move the focus down 107 | pub fn focus_down(&self) -> Stack { 108 | self.reverse().focus_up().reverse() 109 | } 110 | 111 | pub fn swap_up(&self) -> Stack { 112 | if self.up.is_empty() { 113 | Stack::::new( 114 | self.focus.clone(), 115 | self.down.iter().rev().cloned().collect(), 116 | Vec::new(), 117 | ) 118 | } else { 119 | let x = self.up[0].clone(); 120 | let xs = self.up.iter().skip(1).cloned().collect(); 121 | let rs = (vec![x]) 122 | .into_iter() 123 | .chain(self.down.clone().into_iter()) 124 | .collect(); 125 | Stack::::new(self.focus.clone(), xs, rs) 126 | } 127 | } 128 | 129 | pub fn swap_down(&self) -> Stack { 130 | self.reverse().swap_up().reverse() 131 | } 132 | 133 | pub fn swap_master(&self) -> Stack { 134 | if self.up.is_empty() { 135 | return self.clone(); 136 | } 137 | 138 | let r: Vec = self.up.iter().rev().cloned().collect(); 139 | let x: T = r[0].clone(); 140 | let xs: Vec = r.iter().skip(1).cloned().collect(); 141 | let rs: Vec = xs 142 | .into_iter() 143 | .chain((vec![x]).into_iter()) 144 | .chain(self.down.clone().into_iter()) 145 | .collect(); 146 | 147 | Stack::::new(self.focus.clone(), Vec::new(), rs) 148 | } 149 | 150 | /// Reverse the stack by exchanging 151 | /// the `up` and `down` lists 152 | pub fn reverse(&self) -> Stack { 153 | Stack::::new(self.focus.clone(), self.down.clone(), self.up.clone()) 154 | } 155 | 156 | /// Return the number of elements tracked by the stack 157 | pub fn len(&self) -> usize { 158 | 1 + self.up.len() + self.down.len() 159 | } 160 | 161 | pub fn is_empty(&self) -> bool { 162 | self.len() == 0 163 | } 164 | 165 | /// Checks if the given window is tracked by the stack 166 | pub fn contains(&self, window: T) -> bool { 167 | self.focus == window || self.up.contains(&window) || self.down.contains(&window) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /core/src/core/workspace.rs: -------------------------------------------------------------------------------- 1 | use crate::config::GeneralConfig; 2 | use crate::core::stack::Stack; 3 | use crate::layout::{Layout, LayoutMessage}; 4 | use crate::window_system::{Window, WindowSystem}; 5 | 6 | /// Represents a single workspace with a `tag` (name), 7 | /// `id`, a `layout` and a `stack` for all windows 8 | pub struct Workspace { 9 | pub id: u32, 10 | pub tag: String, 11 | pub layout: Box, 12 | pub stack: Option>, 13 | } 14 | 15 | impl Clone for Workspace { 16 | fn clone(&self) -> Workspace { 17 | Workspace { 18 | id: self.id, 19 | tag: self.tag.clone(), 20 | layout: self.layout.copy(), 21 | stack: self.stack.clone(), 22 | } 23 | } 24 | } 25 | 26 | impl Workspace { 27 | /// Create a new workspace 28 | pub fn new( 29 | id: u32, 30 | tag: String, 31 | layout: Box, 32 | stack: Option>, 33 | ) -> Workspace { 34 | Workspace { 35 | id, 36 | tag, 37 | layout, 38 | stack, 39 | } 40 | } 41 | 42 | /// Add a new window to the workspace by adding it to the stack. 43 | /// If the stack doesn't exist yet, create one. 44 | pub fn add(&self, window: Window) -> Workspace { 45 | Workspace::new( 46 | self.id, 47 | self.tag.clone(), 48 | self.layout.copy(), 49 | Some( 50 | self.stack 51 | .clone() 52 | .map_or(Stack::from_element(window), |s| s.add(window)), 53 | ), 54 | ) 55 | } 56 | 57 | /// Returns the number of windows contained in this workspace 58 | pub fn len(&self) -> usize { 59 | self.stack.clone().map_or(0usize, |x| x.len()) 60 | } 61 | 62 | pub fn is_empty(&self) -> bool { 63 | self.len() == 0 64 | } 65 | 66 | /// Checks if the workspace contains the given window 67 | pub fn contains(&self, window: Window) -> bool { 68 | self.stack.clone().map_or(false, |x| x.contains(window)) 69 | } 70 | 71 | pub fn windows(&self) -> Vec { 72 | self.stack.clone().map_or(Vec::new(), |s| s.integrate()) 73 | } 74 | 75 | pub fn peek(&self) -> Option { 76 | self.stack.clone().map(|s| s.focus) 77 | } 78 | 79 | pub fn map(&self, f: F) -> Workspace 80 | where 81 | F: Fn(Stack) -> Stack, 82 | { 83 | Workspace::new( 84 | self.id, 85 | self.tag.clone(), 86 | self.layout.copy(), 87 | self.stack.clone().map(|x| f(x)), 88 | ) 89 | } 90 | 91 | pub fn map_option(&self, f: F) -> Workspace 92 | where 93 | F: Fn(Stack) -> Option>, 94 | { 95 | Workspace::new( 96 | self.id, 97 | self.tag.clone(), 98 | self.layout.copy(), 99 | self.stack.clone().and_then(|x| f(x)), 100 | ) 101 | } 102 | 103 | pub fn map_or(&self, default: Stack, f: F) -> Workspace 104 | where 105 | F: Fn(Stack) -> Stack, 106 | { 107 | Workspace::new( 108 | self.id, 109 | self.tag.clone(), 110 | self.layout.copy(), 111 | Some(self.stack.clone().map_or(default, |x| f(x))), 112 | ) 113 | } 114 | 115 | pub fn send_layout_message( 116 | &self, 117 | message: LayoutMessage, 118 | window_system: &dyn WindowSystem, 119 | config: &GeneralConfig, 120 | ) -> Workspace { 121 | let mut layout = self.layout.copy(); 122 | layout.apply_message(message, window_system, &self.stack, config); 123 | Workspace::new(self.id, self.tag.clone(), layout, self.stack.clone()) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /core/src/core/workspaces.rs: -------------------------------------------------------------------------------- 1 | use crate::config::GeneralConfig; 2 | use crate::core::rational_rect::RationalRect; 3 | use crate::core::screen::Screen; 4 | use crate::core::stack::Stack; 5 | use crate::core::workspace::Workspace; 6 | use crate::layout::{Layout, LayoutMessage}; 7 | use std::collections::BTreeMap; 8 | use std::iter::repeat; 9 | use crate::window_manager::ScreenDetail; 10 | use crate::window_system::{Window, WindowSystem}; 11 | 12 | pub struct Workspaces { 13 | /// The currently focused and visible screen 14 | pub current: Screen, 15 | /// The other visible, but non-focused screens 16 | pub visible: Vec, 17 | /// All remaining workspaces that are currently hidden 18 | pub hidden: Vec, 19 | /// A list of all floating windows 20 | pub floating: BTreeMap, 21 | } 22 | 23 | impl Clone for Workspaces { 24 | fn clone(&self) -> Workspaces { 25 | Workspaces { 26 | current: self.current.clone(), 27 | visible: self.visible.clone(), 28 | hidden: self.hidden.clone(), 29 | floating: self.floating.clone(), 30 | } 31 | } 32 | } 33 | 34 | impl Workspaces { 35 | /// Create a new stackset, of empty stacks, with given tags, 36 | /// with physical screens whose descriptions are given by 'm'. The 37 | /// number of physical screens (@length 'm'@) should be less than or 38 | /// equal to the number of workspace tags. The first workspace in the 39 | /// list will be current. 40 | /// 41 | /// Xinerama: Virtual workspaces are assigned to physical screens, starting at 0. 42 | pub fn new( 43 | layout: Box, 44 | tags: Vec, 45 | screens: Vec, 46 | ) -> Workspaces { 47 | debug!("creating new workspaces with {} screen(s)", screens.len()); 48 | let workspaces: Vec = tags 49 | .iter() 50 | .enumerate() 51 | .map(|(id, tag)| Workspace::new(id as u32, tag.clone(), layout.copy(), None)) 52 | .collect(); 53 | let seen: Vec = workspaces 54 | .iter() 55 | .take(screens.len()) 56 | .cloned() 57 | .collect(); 58 | let unseen: Vec = workspaces 59 | .iter() 60 | .skip(screens.len()) 61 | .cloned() 62 | .collect(); 63 | let current: Vec = seen 64 | .iter() 65 | .enumerate() 66 | .zip(screens.iter()) 67 | .map(|((a, b), c)| Screen::new(b.clone(), a as u32, *c)) 68 | .collect(); 69 | 70 | Workspaces { 71 | current: current[0].clone(), 72 | visible: current.iter().skip(1).cloned().collect(), 73 | hidden: unseen, 74 | floating: BTreeMap::new(), 75 | } 76 | } 77 | 78 | pub fn from_current(&self, current: Screen) -> Workspaces { 79 | Workspaces { 80 | current, 81 | visible: self.visible.clone(), 82 | hidden: self.hidden.clone(), 83 | floating: self.floating.clone(), 84 | } 85 | } 86 | 87 | pub fn from_visible(&self, visible: Vec) -> Workspaces { 88 | Workspaces { 89 | current: self.current.clone(), 90 | visible, 91 | hidden: self.hidden.clone(), 92 | floating: self.floating.clone(), 93 | } 94 | } 95 | 96 | pub fn from_hidden(&self, hidden: Vec) -> Workspaces { 97 | Workspaces { 98 | current: self.current.clone(), 99 | visible: self.visible.clone(), 100 | hidden, 101 | floating: self.floating.clone(), 102 | } 103 | } 104 | 105 | /// Set focus to the workspace with index \'i\'. 106 | /// If the index is out of range, return the original 'StackSet'. 107 | /// 108 | /// Xinerama: If the workspace is not visible on any Xinerama screen, it 109 | /// becomes the current screen. If it is in the visible list, it becomes 110 | /// current. 111 | pub fn view(&self, index: u32) -> Workspaces { 112 | debug!("setting focus to {}", index); 113 | 114 | // We are already on the desired workspace. Do nothing 115 | if self.current.workspace.id == index { 116 | return self.clone(); 117 | } 118 | 119 | // Desired workspace is visible, switch to it by raising 120 | // it to current and pushing the current one to visible 121 | if let Some(screen_pos) = self.visible.iter().position(|s| s.workspace.id == index) { 122 | let screen = self.visible[screen_pos].clone(); 123 | let visible: Vec = self 124 | .visible 125 | .iter() 126 | .enumerate() 127 | .filter(|&(x, _)| x != screen_pos) 128 | .map(|(_, y)| y.clone()) 129 | .collect(); 130 | 131 | self.from_visible( 132 | visible 133 | .into_iter() 134 | .chain((vec![self.current.clone()]).into_iter()) 135 | .collect(), 136 | ) 137 | .from_current(screen) 138 | // Desired workspace is hidden. Switch it with the current workspace 139 | } else if let Some(workspace_pos) = self.hidden.iter().position(|w| w.id == index) { 140 | let hidden: Vec = self 141 | .hidden 142 | .iter() 143 | .enumerate() 144 | .filter(|&(x, _)| x != workspace_pos) 145 | .map(|(_, y)| y.clone()) 146 | .collect(); 147 | 148 | self.from_hidden( 149 | hidden 150 | .into_iter() 151 | .chain((vec![self.current.workspace.clone()]).into_iter()) 152 | .collect(), 153 | ) 154 | .from_current( 155 | self.current 156 | .map_workspace(|_| self.hidden[workspace_pos].clone()), 157 | ) 158 | } else { 159 | self.clone() 160 | } 161 | } 162 | 163 | /// Set focus to the given workspace. If that workspace does not exist 164 | /// in the stackset, the original workspace is returned. If that workspace is 165 | /// 'hidden', then display that workspace on the current screen, and move the 166 | /// current workspace to 'hidden'. If that workspace is 'visible' on another 167 | /// screen, the workspaces of the current screen and the other screen are 168 | /// swapped. 169 | pub fn greedy_view(&self, index: u32) -> Workspaces { 170 | if self.hidden.iter().any(|x| x.id == index) { 171 | self.view(index) 172 | } else if let Some(s) = self.visible.iter().find(|x| x.workspace.id == index) { 173 | let mut current_screen = self.current.clone(); 174 | let old_workspace = current_screen.workspace.clone(); 175 | let mut screen_with_requested_workspace = s.clone(); 176 | let desired_workspace = screen_with_requested_workspace.workspace.clone(); 177 | 178 | current_screen.workspace = desired_workspace; 179 | screen_with_requested_workspace.workspace = old_workspace; 180 | 181 | self.from_current(current_screen).from_visible( 182 | self.visible 183 | .iter() 184 | .filter(|x| x.screen_id != screen_with_requested_workspace.screen_id) 185 | .cloned() 186 | .chain((vec![screen_with_requested_workspace.clone()]).into_iter()) 187 | .collect(), 188 | ) 189 | } else { 190 | self.clone() 191 | } 192 | } 193 | 194 | pub fn float(&self, window: Window, rect: RationalRect) -> Workspaces { 195 | let mut w = self.clone(); 196 | w.floating.insert(window, rect); 197 | w 198 | } 199 | 200 | pub fn sink(&self, window: Window) -> Workspaces { 201 | let mut w = self.clone(); 202 | w.floating.remove(&window); 203 | w 204 | } 205 | 206 | pub fn delete(&self, window: Window) -> Workspaces { 207 | self.delete_p(window).sink(window) 208 | } 209 | 210 | pub fn delete_p(&self, window: Window) -> Workspaces { 211 | fn remove_from_workspace(stack: Stack, window: Window) -> Option> { 212 | stack.filter(|&x| x != window) 213 | } 214 | 215 | self.modify_stack_option(|x| remove_from_workspace(x, window)) 216 | .modify_hidden_option(|x| remove_from_workspace(x, window)) 217 | .modify_visible_option(|x| remove_from_workspace(x, window)) 218 | } 219 | 220 | pub fn focus_window(&self, window: Window) -> Workspaces { 221 | if self.peek() == Some(window) { 222 | return self.clone(); 223 | } 224 | 225 | match self.find_tag(window) { 226 | Some(tag) => { 227 | let mut s = self.view(tag); 228 | while s.peek() != Some(window) { 229 | s = s.focus_up(); 230 | } 231 | s 232 | } 233 | _ => self.clone(), 234 | } 235 | } 236 | 237 | /// Move the focus of the currently focused workspace down 238 | pub fn focus_down(&self) -> Workspaces { 239 | self.modify_stack(|x| x.focus_down()) 240 | } 241 | 242 | pub fn focus_up(&self) -> Workspaces { 243 | self.modify_stack(|x| x.focus_up()) 244 | } 245 | 246 | pub fn swap_down(&self) -> Workspaces { 247 | self.modify_stack(|x| x.swap_down()) 248 | } 249 | 250 | pub fn swap_up(&self) -> Workspaces { 251 | self.modify_stack(|x| x.swap_up()) 252 | } 253 | 254 | pub fn swap_master(&self) -> Workspaces { 255 | self.modify_stack(|x| x.swap_master()) 256 | } 257 | 258 | pub fn modify_stack(&self, f: F) -> Workspaces 259 | where 260 | F: Fn(Stack) -> Stack, 261 | { 262 | self.from_current(self.current.map(|s| f(s))) 263 | } 264 | 265 | pub fn modify_stack_option(&self, f: F) -> Workspaces 266 | where 267 | F: Fn(Stack) -> Option>, 268 | { 269 | self.from_current(self.current.map_option(|s| f(s))) 270 | } 271 | 272 | pub fn modify_hidden(&self, f: F) -> Workspaces 273 | where 274 | F: Fn(Stack) -> Stack, 275 | { 276 | self.from_hidden(self.hidden.iter().map(|x| x.map(|s| f(s))).collect()) 277 | } 278 | 279 | pub fn modify_hidden_option(&self, f: F) -> Workspaces 280 | where 281 | F: Fn(Stack) -> Option>, 282 | { 283 | self.from_hidden(self.hidden.iter().map(|x| x.map_option(|s| f(s))).collect()) 284 | } 285 | 286 | pub fn modify_visible(&self, f: F) -> Workspaces 287 | where 288 | F: Fn(Stack) -> Stack, 289 | { 290 | self.from_visible(self.visible.iter().map(|x| x.map(|s| f(s))).collect()) 291 | } 292 | 293 | pub fn modify_visible_option(&self, f: F) -> Workspaces 294 | where 295 | F: Fn(Stack) -> Option>, 296 | { 297 | self.from_visible( 298 | self.visible 299 | .iter() 300 | .map(|x| x.map_option(|s| f(s))) 301 | .collect(), 302 | ) 303 | } 304 | 305 | pub fn get_focus_window(&self) -> Option { 306 | self.current.workspace.stack.clone().map(|s| s.focus) 307 | } 308 | 309 | /// Retrieve the currently focused workspace's 310 | /// focus element. If there is none, return None. 311 | pub fn peek(&self) -> Option { 312 | self.with(None, |s| Some(s.focus)) 313 | } 314 | 315 | /// Apply the given function to the currently focused stack 316 | /// or return a default if the stack is empty 317 | pub fn with(&self, default: T, f: F) -> T 318 | where 319 | F: Fn(&Stack) -> T, 320 | { 321 | self.clone() 322 | .current 323 | .workspace 324 | .stack 325 | .map_or(default, |x| f(&x)) 326 | } 327 | 328 | /// Return the number of windows 329 | /// contained in all workspaces, including floating windows 330 | pub fn len(&self) -> usize { 331 | self.current.len() 332 | + self.visible.iter().map(|x| x.len()).sum::() 333 | + self.hidden.iter().map(|x| x.len()).sum::() 334 | + self.floating.len() 335 | } 336 | 337 | pub fn is_empty(&self) -> bool { 338 | self.current.len() == 0 339 | } 340 | 341 | /// Checks if any of the workspaces contains the 342 | /// given window 343 | pub fn contains(&self, window: Window) -> bool { 344 | self.current.contains(window) 345 | || self.visible.iter().any(|x| x.contains(window)) 346 | || self.hidden.iter().any(|x| x.contains(window)) 347 | || self.floating.contains_key(&window) 348 | } 349 | 350 | /// Get the number of managed workspaces. 351 | /// This is mostly used for out-of-bounds checking. 352 | pub fn number_workspaces(&self) -> u32 { 353 | (1 + self.visible.len() + self.hidden.len()) as u32 354 | } 355 | 356 | /// Shift the currently focused window to the given workspace 357 | pub fn shift(&self, index: u32) -> Workspaces { 358 | // Get current window 359 | self.peek() 360 | // and move it 361 | .map_or(self.clone(), |w| self.shift_window(index, w)) 362 | } 363 | 364 | pub fn shift_master(&self) -> Workspaces { 365 | self.modify_stack(|s| { 366 | if s.up.is_empty() { 367 | s 368 | } else { 369 | let rev: Vec = 370 | s.up.iter() 371 | .rev() 372 | .chain(s.down.iter()) 373 | .copied() 374 | .collect(); 375 | Stack::::new(s.focus, Vec::::new(), rev) 376 | } 377 | }) 378 | } 379 | 380 | pub fn insert_up(&self, window: Window) -> Workspaces { 381 | if self.contains(window) { 382 | return self.clone(); 383 | } 384 | 385 | self.from_current(self.current.map_or(Stack::from_element(window), |s| { 386 | Stack::::new( 387 | window, 388 | s.up, 389 | (vec![s.focus]) 390 | .into_iter() 391 | .chain(s.down.into_iter()) 392 | .collect(), 393 | ) 394 | })) 395 | } 396 | 397 | /// Retrieve the currently focused workspace's id 398 | pub fn current_tag(&self) -> u32 { 399 | self.current.workspace.id 400 | } 401 | 402 | /// Retrieve the tag of the workspace the given window 403 | /// is contained in. If it is not contained anywhere, 404 | /// return None. 405 | pub fn find_tag(&self, window: Window) -> Option { 406 | debug!("trying to find tag of workspace with window {}", window); 407 | self.workspaces() 408 | .iter() 409 | .filter(|x| x.contains(window)) 410 | .map(|x| x.id) 411 | .next() 412 | } 413 | 414 | pub fn find_screen(&self, window: Window) -> Option { 415 | if self.current.contains(window) { 416 | Some(self.current.clone()) 417 | } else { 418 | self.visible 419 | .iter() 420 | .filter(|x| x.contains(window)) 421 | .cloned().next() 422 | } 423 | } 424 | 425 | /// Flatten all workspaces into a list 426 | pub fn workspaces(&self) -> Vec { 427 | let v: Vec = self.visible.iter().map(|x| x.workspace.clone()).collect(); 428 | (vec![self.current.workspace.clone()]) 429 | .into_iter() 430 | .chain(v.into_iter()) 431 | .chain(self.hidden.clone().into_iter()) 432 | .collect() 433 | } 434 | 435 | /// Shift the given window to the given workspace 436 | pub fn shift_window(&self, index: u32, window: Window) -> Workspaces { 437 | let first_closure = (Box::new(move |w: Workspaces| w.delete(window))) 438 | as Box Workspaces + 'static>; 439 | 440 | let second_closure = (Box::new(move |w: Workspaces| w.insert_up(window))) 441 | as Box Workspaces + 'static>; 442 | 443 | match self.find_tag(window) { 444 | Some(from) => { 445 | let a = self.on_workspace(from, first_closure); 446 | let b = self.on_workspace(index, second_closure); 447 | 448 | debug!("shifting window from {} to {}", from, index); 449 | b(a(self.clone())) 450 | } 451 | None => self.clone(), 452 | } 453 | } 454 | 455 | /// Apply the given function to the given workspace 456 | pub fn on_workspace( 457 | &self, 458 | index: u32, 459 | f: Box Workspaces + 'static>, 460 | ) -> Box Workspaces + 'static> { 461 | (Box::new(move |x: Workspaces| { 462 | let current_tag = x.current_tag(); 463 | (*f)(x.view(index)).view(current_tag) 464 | })) as Box Workspaces + 'static> 465 | } 466 | 467 | /// Return a list of all visible windows. 468 | /// This is just a convenience function. 469 | pub fn visible_windows(&self) -> Vec { 470 | self.current 471 | .windows() 472 | .into_iter() 473 | .chain(self.visible.iter().flat_map(|x| x.windows().into_iter())) 474 | .collect() 475 | } 476 | 477 | /// Return a list of all windows, hidden, visible and floating. 478 | pub fn all_windows(&self) -> Vec { 479 | self.visible_windows() 480 | .into_iter() 481 | .chain(self.hidden.iter().flat_map(|x| x.windows().into_iter())) 482 | .collect() 483 | } 484 | 485 | /// Returns a list of all windows as tuples together with their 486 | /// respective workspace IDs 487 | pub fn all_windows_with_workspaces(&self) -> Vec<(Window, u32)> { 488 | self.current 489 | .windows() 490 | .into_iter() 491 | .rev() 492 | .zip(repeat(self.current.workspace.id)) 493 | .chain( 494 | self.visible 495 | .clone() 496 | .into_iter() 497 | .flat_map(|x| x.windows().into_iter().rev().zip(repeat(x.workspace.id))), 498 | ) 499 | .chain( 500 | self.hidden 501 | .iter() 502 | .flat_map(|x| x.windows().into_iter().rev().zip(repeat(x.id))), 503 | ) 504 | .collect() 505 | } 506 | 507 | /// Return a list of all screens and their workspaces. 508 | /// Mostly used by layout. 509 | pub fn screens(&self) -> Vec { 510 | (vec![self.current.clone()]) 511 | .into_iter() 512 | .chain(self.visible.clone().into_iter()) 513 | .collect() 514 | } 515 | 516 | pub fn send_layout_message( 517 | &self, 518 | message: LayoutMessage, 519 | window_system: &dyn WindowSystem, 520 | config: &GeneralConfig, 521 | ) -> Workspaces { 522 | self.from_current( 523 | self.current 524 | .send_layout_message(message, window_system, config), 525 | ) 526 | } 527 | 528 | pub fn with_focused(&self, f: F) -> Workspaces 529 | where 530 | F: Fn(Window), 531 | { 532 | if let Some(window) = self.peek() { 533 | f(window); 534 | } 535 | 536 | self.clone() 537 | } 538 | 539 | pub fn update_floating_rect(&self, window: Window, rect: RationalRect) -> Workspaces { 540 | let mut map = self.floating.clone(); 541 | map.insert(window, rect); 542 | Workspaces { 543 | current: self.current.clone(), 544 | visible: self.visible.clone(), 545 | hidden: self.hidden.clone(), 546 | floating: map, 547 | } 548 | } 549 | } 550 | -------------------------------------------------------------------------------- /core/src/handlers.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | extern crate serde_json; 3 | 4 | use crate::config::{Config, GeneralConfig}; 5 | use crate::core::workspaces::Workspaces; 6 | use crate::window_manager::WindowManager; 7 | use crate::window_system::Window; 8 | use crate::window_system::WindowSystem; 9 | use std::rc::Rc; 10 | 11 | pub type KeyHandler = 12 | Box, &GeneralConfig) -> WindowManager>; 13 | pub type MouseHandler = 14 | Box, &GeneralConfig, Window) -> WindowManager>; 15 | pub type ManageHook = Box, Window) -> Workspaces>; 16 | pub type StartupHook = Box, &Config) -> WindowManager>; 17 | pub type LogHook = Box)>; 18 | 19 | extern "C" { 20 | pub fn waitpid(fd: libc::pid_t, status: *mut libc::c_int, options: libc::c_int) -> libc::pid_t; 21 | } 22 | 23 | /// Some default handlers for easier config scripts 24 | pub mod default { 25 | use crate::config::GeneralConfig; 26 | use crate::core::workspaces::Workspaces; 27 | use crate::handlers::libc::execvp; 28 | use crate::window_manager::WindowManager; 29 | use crate::window_system::Window; 30 | use crate::window_system::WindowSystem; 31 | use anyhow::Result; 32 | use std::borrow::ToOwned; 33 | use std::env; 34 | use std::ffi::CString; 35 | use std::ops::Deref; 36 | use std::process::Command; 37 | use std::ptr::null; 38 | use std::rc::Rc; 39 | use std::thread::spawn; 40 | 41 | pub fn start_terminal( 42 | window_manager: WindowManager, 43 | _: Rc, 44 | config: &GeneralConfig, 45 | ) -> WindowManager { 46 | let (terminal, args) = config.terminal.clone(); 47 | let arguments: Vec = if args.is_empty() { 48 | Vec::new() 49 | } else { 50 | args.split(' ').map(|x| x.to_owned()).collect() 51 | }; 52 | 53 | spawn(move || { 54 | debug!("spawning terminal"); 55 | let command = if arguments.is_empty() { 56 | Command::new(&terminal).spawn() 57 | } else { 58 | Command::new(&terminal).args(&arguments[..]).spawn() 59 | }; 60 | 61 | if command.is_err() { 62 | panic!("unable to start terminal") 63 | } 64 | }); 65 | 66 | window_manager 67 | } 68 | 69 | pub fn start_launcher( 70 | window_manager: WindowManager, 71 | _: Rc, 72 | config: &GeneralConfig, 73 | ) -> WindowManager { 74 | let launcher = config.launcher.clone(); 75 | spawn(move || { 76 | debug!("spawning launcher"); 77 | match Command::new(&launcher).spawn() { 78 | Ok(_) => (), 79 | _ => panic!("unable to start launcher"), 80 | } 81 | }); 82 | 83 | window_manager 84 | } 85 | 86 | pub fn switch_to_workspace( 87 | window_manager: WindowManager, 88 | window_system: Rc, 89 | config: &GeneralConfig, 90 | index: usize, 91 | ) -> WindowManager { 92 | window_manager.view(window_system.deref(), index as u32, config) 93 | } 94 | 95 | pub fn move_window_to_workspace( 96 | window_manager: WindowManager, 97 | window_system: Rc, 98 | config: &GeneralConfig, 99 | index: usize, 100 | ) -> WindowManager { 101 | window_manager.move_window_to_workspace(window_system.deref(), config, index as u32) 102 | } 103 | 104 | /// Restart the window manager by calling execvp and replacing the current binary 105 | /// with the new one in memory. 106 | /// Pass a list of all windows to it via command line arguments 107 | /// so it may resume work as usual. 108 | pub fn restart( 109 | window_manager: WindowManager, 110 | _: Rc, 111 | c: &GeneralConfig, 112 | ) -> Result { 113 | // Get absolute path to binary 114 | let filename = env::current_dir()?.join(&env::current_exe()?); 115 | // Collect all managed windows 116 | let window_ids: String = 117 | json!(&window_manager.workspaces.all_windows_with_workspaces()).to_string(); 118 | 119 | // Create arguments 120 | let resume = &"--resume"; 121 | let windows = window_ids; 122 | let filename_c = CString::new(filename.into_os_string().into_string().unwrap().as_bytes())?; 123 | 124 | for p in c.pipes.iter() { 125 | p.write().unwrap().wait()?; 126 | } 127 | 128 | let resume_str = CString::new(resume.as_bytes())?; 129 | let windows_str = CString::new(windows.as_bytes())?; 130 | 131 | unsafe { 132 | let slice: &mut [*const i8; 4] = &mut [ 133 | filename_c.as_ptr(), 134 | resume_str.as_ptr(), 135 | windows_str.as_ptr(), 136 | null(), 137 | ]; 138 | execvp(filename_c.as_ptr(), slice.as_mut_ptr()); 139 | } 140 | 141 | Ok(window_manager) 142 | } 143 | 144 | /// Stop the window manager 145 | pub fn exit(w: WindowManager, _: Rc, _: &GeneralConfig) -> WindowManager { 146 | WindowManager { 147 | running: false, 148 | dragging: None, 149 | workspaces: w.workspaces, 150 | waiting_unmap: w.waiting_unmap, 151 | } 152 | } 153 | 154 | pub fn shift(index: u32, workspace: Workspaces, window: Window) -> Workspaces { 155 | workspace.shift_window(index, window) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /core/src/keycodes.rs: -------------------------------------------------------------------------------- 1 | enum KeyCode { 2 | KeyCode_BackSpace, 3 | KeyCode_Tab, 4 | KeyCode_Linefeed, 5 | KeyCode_Clear, 6 | KeyCode_Return, 7 | KeyCode_Pause, 8 | KeyCode_Scroll_Lock, 9 | KeyCode_Sys_Req, 10 | KeyCode_Escape, 11 | KeyCode_Delete, 12 | KeyCode_Multi_key, 13 | KeyCode_Codeinput, 14 | KeyCode_SingleCandidate, 15 | KeyCode_MultipleCandidate, 16 | KeyCode_PreviousCandidate, 17 | KeyCode_Home, 18 | KeyCode_Left, 19 | KeyCode_Up, 20 | KeyCode_Right, 21 | KeyCode_Down, 22 | KeyCode_Prior, 23 | KeyCode_Page_Up, 24 | KeyCode_Next, 25 | KeyCode_Page_Down, 26 | KeyCode_End, 27 | KeyCode_Begin, 28 | KeyCode_Select, 29 | KeyCode_Print, 30 | KeyCode_Execute, 31 | KeyCode_Insert, 32 | KeyCode_Undo, 33 | KeyCode_Redo, 34 | KeyCode_Menu, 35 | KeyCode_Find, 36 | KeyCode_Cancel, 37 | KeyCode_Help, 38 | KeyCode_Break, 39 | KeyCode_Mode_switch, 40 | KeyCode_script_switch, 41 | KeyCode_Num_Lock, 42 | KeyCode_KP_Space, 43 | KeyCode_KP_Tab, 44 | KeyCode_KP_Enter, 45 | KeyCode_KP_F1, 46 | KeyCode_KP_F2, 47 | KeyCode_KP_F3, 48 | KeyCode_KP_F4, 49 | KeyCode_KP_Home, 50 | KeyCode_KP_Left, 51 | KeyCode_KP_Up, 52 | KeyCode_KP_Right, 53 | KeyCode_KP_Down, 54 | KeyCode_KP_Prior, 55 | KeyCode_KP_Page_Up, 56 | KeyCode_KP_Next, 57 | KeyCode_KP_Page_Down, 58 | KeyCode_KP_End, 59 | KeyCode_KP_Begin, 60 | KeyCode_KP_Insert, 61 | KeyCode_KP_Delete, 62 | KeyCode_KP_Equal, 63 | KeyCode_KP_Multiply, 64 | KeyCode_KP_Add, 65 | KeyCode_KP_Separator, 66 | KeyCode_KP_Subtract, 67 | KeyCode_KP_Decimal, 68 | KeyCode_KP_Divide, 69 | KeyCode_KP_0, 70 | KeyCode_KP_1, 71 | KeyCode_KP_2, 72 | KeyCode_KP_3, 73 | KeyCode_KP_4, 74 | KeyCode_KP_5, 75 | KeyCode_KP_6, 76 | KeyCode_KP_7, 77 | KeyCode_KP_8, 78 | KeyCode_KP_9, 79 | KeyCode_F1, 80 | KeyCode_F2, 81 | KeyCode_F3, 82 | KeyCode_F4, 83 | KeyCode_F5, 84 | KeyCode_F6, 85 | KeyCode_F7, 86 | KeyCode_F8, 87 | KeyCode_F9, 88 | KeyCode_F10, 89 | KeyCode_F11, 90 | KeyCode_L1, 91 | KeyCode_F12, 92 | KeyCode_L2, 93 | KeyCode_F13, 94 | KeyCode_L3, 95 | KeyCode_F14, 96 | KeyCode_L4, 97 | KeyCode_F15, 98 | KeyCode_L5, 99 | KeyCode_F16, 100 | KeyCode_L6, 101 | KeyCode_F17, 102 | KeyCode_L7, 103 | KeyCode_F18, 104 | KeyCode_L8, 105 | KeyCode_F19, 106 | KeyCode_L9, 107 | KeyCode_F20, 108 | KeyCode_L10, 109 | KeyCode_F21, 110 | KeyCode_R1, 111 | KeyCode_F22, 112 | KeyCode_R2, 113 | KeyCode_F23, 114 | KeyCode_R3, 115 | KeyCode_F24, 116 | KeyCode_R4, 117 | KeyCode_F25, 118 | KeyCode_R5, 119 | KeyCode_F26, 120 | KeyCode_R6, 121 | KeyCode_F27, 122 | KeyCode_R7, 123 | KeyCode_F28, 124 | KeyCode_R8, 125 | KeyCode_F29, 126 | KeyCode_R9, 127 | KeyCode_F30, 128 | KeyCode_R10, 129 | KeyCode_F31, 130 | KeyCode_R11, 131 | KeyCode_F32, 132 | KeyCode_R12, 133 | KeyCode_F33, 134 | KeyCode_R13, 135 | KeyCode_F34, 136 | KeyCode_R14, 137 | KeyCode_F35, 138 | KeyCode_R15, 139 | KeyCode_Shift_L, 140 | KeyCode_Shift_R, 141 | KeyCode_Control_L, 142 | KeyCode_Control_R, 143 | KeyCode_Caps_Lock, 144 | KeyCode_Shift_Lock, 145 | KeyCode_Meta_L, 146 | KeyCode_Meta_R, 147 | KeyCode_Alt_L, 148 | KeyCode_Alt_R, 149 | KeyCode_Super_L, 150 | KeyCode_Super_R, 151 | KeyCode_Hyper_L, 152 | KeyCode_Hyper_R, 153 | KeyCode_space, 154 | KeyCode_exclam, 155 | KeyCode_quotedbl, 156 | KeyCode_numbersign, 157 | KeyCode_dollar, 158 | KeyCode_percent, 159 | KeyCode_ampersand, 160 | KeyCode_apostrophe, 161 | KeyCode_quoteright, 162 | KeyCode_parenleft, 163 | KeyCode_parenright, 164 | KeyCode_asterisk, 165 | KeyCode_plus, 166 | KeyCode_comma, 167 | KeyCode_minus, 168 | KeyCode_period, 169 | KeyCode_slash, 170 | KeyCode_0, 171 | KeyCode_1, 172 | KeyCode_2, 173 | KeyCode_3, 174 | KeyCode_4, 175 | KeyCode_5, 176 | KeyCode_6, 177 | KeyCode_7, 178 | KeyCode_8, 179 | KeyCode_9, 180 | KeyCode_colon, 181 | KeyCode_semicolon, 182 | KeyCode_less, 183 | KeyCode_equal, 184 | KeyCode_greater, 185 | KeyCode_question, 186 | KeyCode_at, 187 | KeyCode_A, 188 | KeyCode_B, 189 | KeyCode_C, 190 | KeyCode_D, 191 | KeyCode_E, 192 | KeyCode_F, 193 | KeyCode_G, 194 | KeyCode_H, 195 | KeyCode_I, 196 | KeyCode_J, 197 | KeyCode_K, 198 | KeyCode_L, 199 | KeyCode_M, 200 | KeyCode_N, 201 | KeyCode_O, 202 | KeyCode_P, 203 | KeyCode_Q, 204 | KeyCode_R, 205 | KeyCode_S, 206 | KeyCode_T, 207 | KeyCode_U, 208 | KeyCode_V, 209 | KeyCode_W, 210 | KeyCode_X, 211 | KeyCode_Y, 212 | KeyCode_Z, 213 | KeyCode_bracketleft, 214 | KeyCode_backslash, 215 | KeyCode_bracketright, 216 | KeyCode_asciicircum, 217 | KeyCode_underscore, 218 | KeyCode_grave, 219 | KeyCode_quoteleft, 220 | KeyCode_a, 221 | KeyCode_b, 222 | KeyCode_c, 223 | KeyCode_d, 224 | KeyCode_e, 225 | KeyCode_f, 226 | KeyCode_g, 227 | KeyCode_h, 228 | KeyCode_i, 229 | KeyCode_j, 230 | KeyCode_k, 231 | KeyCode_l, 232 | KeyCode_m, 233 | KeyCode_n, 234 | KeyCode_o, 235 | KeyCode_p, 236 | KeyCode_q, 237 | KeyCode_r, 238 | KeyCode_s, 239 | KeyCode_t, 240 | KeyCode_u, 241 | KeyCode_v, 242 | KeyCode_w, 243 | KeyCode_x, 244 | KeyCode_y, 245 | KeyCode_z, 246 | KeyCode_braceleft, 247 | KeyCode_bar, 248 | KeyCode_braceright, 249 | KeyCode_asciitilde, 250 | KeyCode_nobreakspace, 251 | KeyCode_exclamdown, 252 | KeyCode_cent, 253 | KeyCode_sterling, 254 | KeyCode_currency, 255 | KeyCode_yen, 256 | KeyCode_brokenbar, 257 | KeyCode_section, 258 | KeyCode_diaeresis, 259 | KeyCode_copyright, 260 | KeyCode_ordfeminine, 261 | KeyCode_guillemotleft, 262 | KeyCode_notsign, 263 | KeyCode_hyphen, 264 | KeyCode_registered, 265 | KeyCode_macron, 266 | KeyCode_degree, 267 | KeyCode_plusminus, 268 | KeyCode_twosuperior, 269 | KeyCode_threesuperior, 270 | KeyCode_acute, 271 | KeyCode_mu, 272 | KeyCode_paragraph, 273 | KeyCode_periodcentered, 274 | KeyCode_cedilla, 275 | KeyCode_onesuperior, 276 | KeyCode_masculine, 277 | KeyCode_guillemotright, 278 | KeyCode_onequarter, 279 | KeyCode_onehalf, 280 | KeyCode_threequarters, 281 | KeyCode_questiondown, 282 | KeyCode_Agrave, 283 | KeyCode_Aacute, 284 | KeyCode_Acircumflex, 285 | KeyCode_Atilde, 286 | KeyCode_Adiaeresis, 287 | KeyCode_Aring, 288 | KeyCode_AE, 289 | KeyCode_Ccedilla, 290 | KeyCode_Egrave, 291 | KeyCode_Eacute, 292 | KeyCode_Ecircumflex, 293 | KeyCode_Ediaeresis, 294 | KeyCode_Igrave, 295 | KeyCode_Iacute, 296 | KeyCode_Icircumflex, 297 | KeyCode_Idiaeresis, 298 | KeyCode_ETH, 299 | KeyCode_Eth, 300 | KeyCode_Ntilde, 301 | KeyCode_Ograve, 302 | KeyCode_Oacute, 303 | KeyCode_Ocircumflex, 304 | KeyCode_Otilde, 305 | KeyCode_Odiaeresis, 306 | KeyCode_multiply, 307 | KeyCode_Ooblique, 308 | KeyCode_Ugrave, 309 | KeyCode_Uacute, 310 | KeyCode_Ucircumflex, 311 | KeyCode_Udiaeresis, 312 | KeyCode_Yacute, 313 | KeyCode_THORN, 314 | KeyCode_Thorn, 315 | KeyCode_ssharp, 316 | KeyCode_agrave, 317 | KeyCode_aacute, 318 | KeyCode_acircumflex, 319 | KeyCode_atilde, 320 | KeyCode_adiaeresis, 321 | KeyCode_aring, 322 | KeyCode_ae, 323 | KeyCode_ccedilla, 324 | KeyCode_egrave, 325 | KeyCode_eacute, 326 | KeyCode_ecircumflex, 327 | KeyCode_ediaeresis, 328 | KeyCode_igrave, 329 | KeyCode_iacute, 330 | KeyCode_icircumflex, 331 | KeyCode_idiaeresis, 332 | KeyCode_eth, 333 | KeyCode_ntilde, 334 | KeyCode_ograve, 335 | KeyCode_oacute, 336 | KeyCode_ocircumflex, 337 | KeyCode_otilde, 338 | KeyCode_odiaeresis, 339 | KeyCode_division, 340 | KeyCode_oslash, 341 | KeyCode_ugrave, 342 | KeyCode_uacute, 343 | KeyCode_ucircumflex, 344 | KeyCode_udiaeresis, 345 | KeyCode_yacute, 346 | KeyCode_thorn, 347 | KeyCode_ydiaeresis 348 | } 349 | -------------------------------------------------------------------------------- /core/src/layout.rs: -------------------------------------------------------------------------------- 1 | use crate::config::GeneralConfig; 2 | use crate::core::stack::Stack; 3 | use std::borrow::ToOwned; 4 | use crate::window_manager::ScreenDetail; 5 | use crate::window_system::Rectangle; 6 | use crate::window_system::Window; 7 | use crate::window_system::WindowSystem; 8 | 9 | #[derive(Clone, Copy)] 10 | pub enum LayoutMessage { 11 | Increase, 12 | Decrease, 13 | IncreaseMaster, 14 | DecreaseMaster, 15 | IncreaseSlave, 16 | DecreaseSlave, 17 | IncreaseGap, 18 | DecreaseGap, 19 | Next, 20 | Prev, 21 | HorizontalSplit, 22 | VerticalSplit, 23 | Hide, 24 | TreeRotate, 25 | TreeSwap, 26 | TreeExpandTowards(Direction), 27 | TreeShrinkFrom(Direction), 28 | } 29 | 30 | pub fn mirror_rect(&Rectangle(x, y, w, h): &Rectangle) -> Rectangle { 31 | Rectangle(y, x, h, w) 32 | } 33 | 34 | pub fn tile(ratio: f32, screen: ScreenDetail, num_master: u32, num_windows: u32) -> Vec { 35 | if num_windows <= num_master || num_master == 0 { 36 | split_vertically(num_windows, screen) 37 | } else { 38 | let (r1, r2) = split_horizontally_by(ratio, screen); 39 | let v1 = split_vertically(num_master, r1); 40 | let v2 = split_vertically(num_windows - num_master, r2); 41 | v1.iter().chain(v2.iter()).copied().collect() 42 | } 43 | } 44 | 45 | pub fn split_vertically(num: u32, screen: ScreenDetail) -> Vec { 46 | if num < 2 { 47 | return vec![screen]; 48 | } 49 | 50 | let Rectangle(sx, sy, sw, sh) = screen; 51 | let smallh = sh / num; 52 | (vec![Rectangle(sx, sy, sw, smallh)]) 53 | .iter() 54 | .chain(split_vertically(num - 1, Rectangle(sx, sy + smallh as i32, sw, sh - smallh)).iter()) 55 | .copied() 56 | .collect() 57 | } 58 | 59 | pub fn split_horizontally_by(ratio: f32, screen: ScreenDetail) -> (Rectangle, Rectangle) { 60 | let Rectangle(sx, sy, sw, sh) = screen; 61 | let leftw = (sw as f32 * ratio).floor() as u32; 62 | 63 | ( 64 | Rectangle(sx, sy, leftw, sh), 65 | Rectangle(sx + leftw as i32, sy, sw - leftw, sh), 66 | ) 67 | } 68 | 69 | pub trait Layout { 70 | fn apply_layout( 71 | &mut self, 72 | window_system: &dyn WindowSystem, 73 | screen: Rectangle, 74 | config: &GeneralConfig, 75 | stack: &Option>, 76 | ) -> Vec<(Window, Rectangle)>; 77 | fn apply_message( 78 | &mut self, 79 | _: LayoutMessage, 80 | _: &dyn WindowSystem, 81 | _: &Option>, 82 | _: &GeneralConfig, 83 | ) -> bool { 84 | true 85 | } 86 | fn description(&self) -> String; 87 | fn copy(&self) -> Box { 88 | panic!("") 89 | } 90 | fn unhook(&self, _: &dyn WindowSystem, _: &Option>, _: &GeneralConfig) {} 91 | } 92 | 93 | #[derive(Clone, Copy)] 94 | pub struct TallLayout { 95 | pub num_master: u32, 96 | pub increment_ratio: f32, 97 | pub ratio: f32, 98 | } 99 | 100 | impl TallLayout { 101 | pub fn boxed_new() -> Box { 102 | Box::new(TallLayout { 103 | num_master: 1, 104 | increment_ratio: 0.03, 105 | ratio: 0.5, 106 | }) 107 | } 108 | } 109 | 110 | impl Layout for TallLayout { 111 | fn apply_layout( 112 | &mut self, 113 | _: &dyn WindowSystem, 114 | screen: Rectangle, 115 | _: &GeneralConfig, 116 | stack: &Option>, 117 | ) -> Vec<(Window, Rectangle)> { 118 | match *stack { 119 | Some(ref s) => { 120 | let ws = s.integrate(); 121 | s.integrate() 122 | .iter() 123 | .zip(tile(self.ratio, screen, self.num_master, ws.len() as u32).iter()) 124 | .map(|(&x, &y)| (x, y)) 125 | .collect() 126 | } 127 | _ => Vec::new(), 128 | } 129 | } 130 | 131 | fn apply_message( 132 | &mut self, 133 | message: LayoutMessage, 134 | _: &dyn WindowSystem, 135 | _: &Option>, 136 | _: &GeneralConfig, 137 | ) -> bool { 138 | match message { 139 | LayoutMessage::Increase => { 140 | self.ratio += 0.05; 141 | true 142 | } 143 | LayoutMessage::Decrease => { 144 | self.ratio -= 0.05; 145 | true 146 | } 147 | LayoutMessage::IncreaseMaster => { 148 | self.num_master += 1; 149 | true 150 | } 151 | LayoutMessage::DecreaseMaster => { 152 | if self.num_master > 1 { 153 | self.num_master -= 1 154 | } 155 | true 156 | } 157 | _ => false, 158 | } 159 | } 160 | 161 | fn description(&self) -> String { 162 | "Tall".to_owned() 163 | } 164 | 165 | fn copy(&self) -> Box { 166 | Box::new(*self) 167 | } 168 | } 169 | 170 | #[repr(usize)] 171 | #[derive(Clone, Copy, Ord, Eq, PartialOrd, PartialEq)] 172 | pub enum Direction { 173 | Up, 174 | Down, 175 | Left, 176 | Right, 177 | } 178 | 179 | impl Direction { 180 | pub fn opposite(&self) -> Direction { 181 | match *self { 182 | Direction::Up => Direction::Down, 183 | Direction::Down => Direction::Up, 184 | Direction::Left => Direction::Right, 185 | Direction::Right => Direction::Left, 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /core/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | #[macro_use] 4 | extern crate bitflags; 5 | #[macro_use] 6 | extern crate serde_json; 7 | 8 | pub mod config; 9 | pub mod core; 10 | pub mod handlers; 11 | pub mod layout; 12 | pub mod util; 13 | pub mod window_manager; 14 | pub mod window_system; 15 | -------------------------------------------------------------------------------- /core/src/util.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Config; 2 | use crate::core::workspaces::Workspaces; 3 | use std::convert::AsRef; 4 | use std::ffi::OsStr; 5 | use std::process::Child; 6 | use std::process::Command; 7 | use std::process::Stdio; 8 | use std::rc::Rc; 9 | use std::sync::RwLock; 10 | use crate::window_system::*; 11 | 12 | #[macro_export] 13 | macro_rules! add_key_handler_str( 14 | ($config: expr, $w:expr, $key:expr, $modkey:expr, $inp:expr) => ( 15 | $config.add_key_handler($w.get_keycode_from_string($key), $modkey, Box::new($inp)); 16 | ) 17 | ); 18 | 19 | #[macro_export] 20 | macro_rules! add_key_handler_code( 21 | ($config: expr, $key:expr, $modkey:expr, $inp:expr) => ( 22 | $config.add_key_handler($key, $modkey, Box::new($inp)); 23 | ) 24 | ); 25 | 26 | #[macro_export] 27 | macro_rules! add_mouse_handler( 28 | ($config: expr, $button:expr, $modkey:expr, $inp:expr) => ( 29 | $config.add_mouse_handler($button, $modkey, Box::new($inp)); 30 | ) 31 | ); 32 | 33 | #[macro_export] 34 | macro_rules! send_layout_message( 35 | ($message: expr) => ( 36 | |m, w, c| m.send_layout_message($message, w.deref(), c).windows(w.deref(), c, &|x| x.clone()) 37 | ) 38 | ); 39 | 40 | #[macro_export] 41 | macro_rules! run( 42 | ($command: expr, $options: expr) => ( 43 | |w, _, _| { run($command, String::from($options).split(' ').map(String::from).collect()); w } 44 | ) 45 | ); 46 | 47 | pub fn run>(program: S, args: Vec) { 48 | Command::new(program).args(&args).spawn().unwrap(); 49 | } 50 | 51 | pub fn spawn_pipe>( 52 | config: &mut Config, 53 | program: S, 54 | args: Vec, 55 | ) -> Rc> { 56 | let result = Command::new(program) 57 | .args(&args) 58 | .stdin(Stdio::piped()) 59 | .spawn() 60 | .unwrap(); 61 | let rc = Rc::new(RwLock::new(result)); 62 | config.general.pipes.push(rc.clone()); 63 | rc 64 | } 65 | 66 | pub fn spawn_on( 67 | workspaces: Workspaces, 68 | _: &dyn WindowSystem, 69 | window: Window, 70 | workspace_id: u32, 71 | ) -> Workspaces { 72 | workspaces.focus_window(window).shift(workspace_id) 73 | } 74 | -------------------------------------------------------------------------------- /core/src/window_manager.rs: -------------------------------------------------------------------------------- 1 | //extern crate collections; 2 | 3 | use crate::config::GeneralConfig; 4 | use crate::core::rational_rect::RationalRect; 5 | use crate::core::screen::Screen; 6 | use crate::core::workspace::Workspace; 7 | use crate::core::workspaces::Workspaces; 8 | use crate::layout::LayoutMessage; 9 | use crate::window_system::Rectangle; 10 | use crate::window_system::Window; 11 | use crate::window_system::WindowSystem; 12 | 13 | use std::cmp; 14 | use std::collections::BTreeMap; 15 | use std::collections::BTreeSet; 16 | use std::rc::Rc; 17 | 18 | pub type ScreenDetail = Rectangle; 19 | pub type MouseDrag = Box WindowManager>; 20 | 21 | #[derive(Clone)] 22 | pub struct WindowManager { 23 | pub running: bool, 24 | pub dragging: Option>, 25 | pub workspaces: Workspaces, 26 | pub waiting_unmap: BTreeMap, 27 | } 28 | 29 | impl WindowManager { 30 | /// Create a new window manager for the given window system and configuration 31 | pub fn new(window_system: &dyn WindowSystem, config: &GeneralConfig) -> WindowManager { 32 | WindowManager { 33 | running: true, 34 | dragging: None, 35 | workspaces: Workspaces::new( 36 | config.layout.copy(), 37 | config.tags.clone(), 38 | window_system.get_screen_infos(), 39 | ), 40 | waiting_unmap: BTreeMap::new(), 41 | } 42 | } 43 | 44 | /// Checks if the given window is already managed by the WindowManager 45 | pub fn is_window_managed(&self, window: Window) -> bool { 46 | self.workspaces.contains(window) 47 | } 48 | 49 | /// Switch to the workspace given by index. If index is out of bounds, 50 | /// just do nothing and return. 51 | /// Then, reapply the layout to show the changes. 52 | pub fn view( 53 | &self, 54 | window_system: &dyn WindowSystem, 55 | index: u32, 56 | config: &GeneralConfig, 57 | ) -> WindowManager { 58 | if index < self.workspaces.number_workspaces() { 59 | debug!( 60 | "switching to workspace {}", 61 | config.tags[index as usize].clone() 62 | ); 63 | self.windows(window_system, config, &|w: &Workspaces| w.view(index)) 64 | } else { 65 | self.clone() 66 | } 67 | } 68 | 69 | pub fn move_window_to_workspace( 70 | &self, 71 | window_system: &dyn WindowSystem, 72 | config: &GeneralConfig, 73 | index: u32, 74 | ) -> WindowManager { 75 | self.windows(window_system, config, &|w| w.shift(index)) 76 | } 77 | 78 | /// Rearrange the workspaces across the given screens. 79 | /// Needs to be called when the screen arrangement changes. 80 | pub fn rescreen(&self, window_system: &dyn WindowSystem) -> WindowManager { 81 | let screens = window_system.get_screen_infos(); 82 | let visible: Vec = (vec![self.workspaces.current.clone()]) 83 | .iter() 84 | .chain(self.workspaces.visible.iter()) 85 | .map(|x| x.workspace.clone()) 86 | .collect(); 87 | let hidden: Vec = visible 88 | .iter() 89 | .chain(self.workspaces.hidden.iter()) 90 | .skip(screens.len()) 91 | .cloned() 92 | .collect(); 93 | let sc: Vec = visible 94 | .iter() 95 | .chain(self.workspaces.hidden.iter()) 96 | .take(screens.len()) 97 | .cloned() 98 | .enumerate() 99 | .zip(screens.iter()) 100 | .map(|((a, b), &c)| Screen::new(b, a as u32, c)) 101 | .collect(); 102 | 103 | self.modify_workspaces(|w: &Workspaces| { 104 | let mut r = w.clone(); 105 | r.current = sc.first().unwrap().clone(); 106 | r.visible = sc.iter().skip(1).cloned().collect(); 107 | r.hidden = hidden.clone(); 108 | r 109 | }) 110 | } 111 | 112 | pub fn update_layouts( 113 | &self, 114 | window_system: &dyn WindowSystem, 115 | config: &GeneralConfig, 116 | ) -> WindowManager { 117 | let screens: Vec = self 118 | .workspaces 119 | .screens() 120 | .into_iter() 121 | .map(|mut s| { 122 | s.workspace.layout.apply_layout( 123 | window_system, 124 | s.screen_detail, 125 | config, 126 | &self 127 | .workspaces 128 | .view(s.workspace.id) 129 | .current 130 | .workspace 131 | .stack 132 | .and_then(|x| x.filter(|w| !self.workspaces.floating.contains_key(w))), 133 | ); 134 | s 135 | }) 136 | .collect(); 137 | 138 | WindowManager { 139 | running: self.running, 140 | dragging: self.dragging.clone(), 141 | workspaces: self 142 | .workspaces 143 | .from_current(screens[0].clone()) 144 | .from_visible(screens.into_iter().skip(1).collect()), 145 | waiting_unmap: self.waiting_unmap.clone(), 146 | } 147 | } 148 | 149 | pub fn unfocus_windows(&self, window_system: &dyn WindowSystem, config: &GeneralConfig) { 150 | for &win in self.workspaces.visible_windows().iter() { 151 | window_system.set_window_border_color(win, config.border_color); 152 | } 153 | } 154 | 155 | /// Manage a new window that was either created just now or already present 156 | /// when the WM started. 157 | pub fn manage( 158 | &self, 159 | window_system: &dyn WindowSystem, 160 | window: Window, 161 | config: &GeneralConfig, 162 | ) -> WindowManager { 163 | fn adjust(RationalRect(x, y, w, h): RationalRect) -> RationalRect { 164 | if x + w > 1.0 || y + h > 1.0 || x < 0.0 || y < 0.0 { 165 | RationalRect(0.5 - w / 2.0, 0.5 - h / 2.0, w, h) 166 | } else { 167 | RationalRect(x, y, w, h) 168 | } 169 | } 170 | 171 | let size_hints = window_system.get_size_hints(window); 172 | 173 | let is_transient = false; 174 | let is_fixed_size = 175 | size_hints.min_size.is_some() && size_hints.min_size == size_hints.max_size; 176 | 177 | debug!("setting focus to newly managed window {}", window); 178 | 179 | let result = if is_transient || is_fixed_size { 180 | let r = adjust(self.float_location(window_system, window)); 181 | self.windows(window_system, config, &|x| { 182 | x.insert_up(window).float(window, r) 183 | }) 184 | .focus(window, window_system, config) 185 | } else { 186 | self.windows(window_system, config, &|x| x.insert_up(window)) 187 | .focus(window, window_system, config) 188 | }; 189 | 190 | debug!("focus is set to {}", window); 191 | 192 | result 193 | } 194 | 195 | /// Unmanage a window. This happens when a window is closed. 196 | pub fn unmanage( 197 | &self, 198 | window_system: &dyn WindowSystem, 199 | window: Window, 200 | config: &GeneralConfig, 201 | ) -> WindowManager { 202 | if self.workspaces.contains(window) { 203 | debug!("unmanaging window {}", window); 204 | self.windows(window_system, config, &|x| x.delete(window)) 205 | } else { 206 | self.clone() 207 | } 208 | } 209 | 210 | pub fn focus( 211 | &self, 212 | window: Window, 213 | window_system: &dyn WindowSystem, 214 | config: &GeneralConfig, 215 | ) -> WindowManager { 216 | if let Some(screen) = self.workspaces.find_screen(window) { 217 | if screen.screen_id == self.workspaces.current.screen_id 218 | && screen.workspace.peek() != Some(window) 219 | { 220 | return self.windows(window_system, config, &|w| w.focus_window(window)); 221 | } else if window == window_system.get_root() { 222 | return self.windows(window_system, config, &|w| w.view(screen.workspace.id)); 223 | } 224 | } 225 | self.clone() 226 | } 227 | 228 | pub fn focus_down(&self) -> WindowManager { 229 | self.modify_workspaces(|x| x.focus_down()) 230 | } 231 | 232 | pub fn focus_up(&self) -> WindowManager { 233 | self.modify_workspaces(|x| x.focus_up()) 234 | } 235 | 236 | pub fn modify_workspaces(&self, f: F) -> WindowManager 237 | where 238 | F: Fn(&Workspaces) -> Workspaces, 239 | { 240 | WindowManager { 241 | running: self.running, 242 | dragging: self.dragging.clone(), 243 | workspaces: f(&self.workspaces), 244 | waiting_unmap: self.waiting_unmap.clone(), 245 | } 246 | } 247 | 248 | pub fn reveal(&self, window_system: &dyn WindowSystem, window: Window) -> WindowManager { 249 | window_system.show_window(window); 250 | self.clone() 251 | } 252 | 253 | pub fn windows( 254 | &self, 255 | window_system: &dyn WindowSystem, 256 | config: &GeneralConfig, 257 | f: &F, 258 | ) -> WindowManager 259 | where 260 | F: Fn(&Workspaces) -> Workspaces, 261 | { 262 | let ws = f(&self.workspaces); 263 | let old_visible = self 264 | .workspaces 265 | .visible_windows() 266 | .into_iter() 267 | .collect::>(); 268 | let new_windows = ws 269 | .visible_windows() 270 | .into_iter() 271 | .collect::>() 272 | .difference(&old_visible) 273 | .copied() 274 | .collect::>(); 275 | 276 | // Initialize all new windows 277 | for &window in new_windows.iter() { 278 | window_system.set_initial_properties(window); 279 | window_system.set_window_border_width(window, config.border_width); 280 | } 281 | 282 | let all_screens = ws.screens(); 283 | let summed_visible = (vec![BTreeSet::new()]) 284 | .into_iter() 285 | .chain( 286 | all_screens 287 | .iter() 288 | .scan(Vec::new(), |acc, x| { 289 | acc.extend(x.workspace.windows().into_iter()); 290 | Some(acc.clone()) 291 | }) 292 | .map(|x| x.into_iter().collect::>()), 293 | ) 294 | .collect::>(); 295 | 296 | let rects = all_screens 297 | .iter() 298 | .zip(summed_visible.iter()) 299 | .flat_map(|(w, vis)| { 300 | let mut wsp = w.workspace.clone(); 301 | let this = ws.view(wsp.id); 302 | let tiled = this 303 | .clone() 304 | .current 305 | .workspace 306 | .stack 307 | .and_then(|x| x.filter(|win| !ws.floating.contains_key(win))) 308 | .and_then(|x| x.filter(|win| !vis.contains(win))); 309 | let view_rect = w.screen_detail; 310 | 311 | let rs = wsp 312 | .layout 313 | .apply_layout(window_system, view_rect, config, &tiled); 314 | 315 | let flt = this 316 | .with(Vec::new(), |x| x.integrate()) 317 | .into_iter() 318 | .filter(|x| self.workspaces.floating.contains_key(x)) 319 | .map(|x| { 320 | ( 321 | x, 322 | WindowManager::scale_rational_rect( 323 | view_rect, 324 | self.workspaces.floating[&x], 325 | ), 326 | ) 327 | }) 328 | .collect::>(); 329 | 330 | let vs: Vec<(Window, Rectangle)> = flt.into_iter().chain(rs.into_iter()).collect(); 331 | window_system.restack_windows(vs.iter().map(|x| x.0).collect()); 332 | 333 | vs.into_iter() 334 | }) 335 | .collect::>(); 336 | 337 | let visible = rects.iter().map(|x| x.0).collect::>(); 338 | 339 | for &(window, rect) in rects.iter() { 340 | WindowManager::tile_window(window_system, config, window, rect); 341 | } 342 | 343 | visible.iter().fold((), |_, &x| { 344 | window_system.set_window_border_color(x, config.border_color) 345 | }); 346 | visible.iter().fold((), |_, &x| { 347 | window_system.set_window_border_width(x, config.border_width) 348 | }); 349 | 350 | for &win in visible.iter() { 351 | window_system.show_window(win); 352 | } 353 | 354 | match ws.peek() { 355 | Some(focused_window) => { 356 | window_system.set_window_border_color(focused_window, config.focus_border_color); 357 | window_system.focus_window(focused_window, self); 358 | } 359 | None => window_system.focus_window(window_system.get_root(), self), 360 | } 361 | 362 | let to_hide = old_visible 363 | .union(&new_windows.into_iter().collect()) 364 | .copied() 365 | .collect::>() 366 | .difference(&visible.into_iter().collect()) 367 | .copied() 368 | .collect::>(); 369 | 370 | for &win in to_hide.iter() { 371 | window_system.hide_window(win); 372 | } 373 | 374 | if config.focus_follows_mouse { 375 | window_system.remove_enter_events(); 376 | } 377 | 378 | let modified = self 379 | .modify_workspaces(|_| ws.clone()) 380 | .update_layouts(window_system, config); 381 | 382 | to_hide 383 | .into_iter() 384 | .fold(modified, |a, x| a.insert_or_update_unmap(x)) 385 | } 386 | 387 | /// Send the given message to the current layout 388 | pub fn send_layout_message( 389 | &self, 390 | message: LayoutMessage, 391 | window_system: &dyn WindowSystem, 392 | config: &GeneralConfig, 393 | ) -> WindowManager { 394 | self.modify_workspaces(|w| w.send_layout_message(message, window_system, config)) 395 | } 396 | 397 | /// Close the currently focused window 398 | pub fn close_window(&self, window_system: &dyn WindowSystem) -> WindowManager { 399 | self.workspaces 400 | .with_focused(|w| window_system.close_client(w)); 401 | self.clone() 402 | } 403 | 404 | /// Kill the currently focused window 405 | pub fn kill_window(&self, window_system: &dyn WindowSystem) -> WindowManager { 406 | self.workspaces 407 | .with_focused(|w| window_system.kill_client(w)); 408 | self.clone() 409 | } 410 | 411 | fn scale_rational_rect( 412 | Rectangle(sx, sy, sw, sh): Rectangle, 413 | RationalRect(rx, ry, rw, rh): RationalRect, 414 | ) -> Rectangle { 415 | fn scale(s: u32, r: f32) -> u32 { 416 | ((s as f32) * r) as u32 417 | } 418 | Rectangle( 419 | sx + scale(sw, rx) as i32, 420 | sy + scale(sh, ry) as i32, 421 | scale(sw, rw), 422 | scale(sh, rh), 423 | ) 424 | } 425 | 426 | fn tile_window( 427 | window_system: &dyn WindowSystem, 428 | config: &GeneralConfig, 429 | window: Window, 430 | Rectangle(x, y, w, h): Rectangle, 431 | ) { 432 | window_system.resize_window( 433 | window, 434 | w - 2 * config.border_width, 435 | h - 2 * config.border_width, 436 | ); 437 | window_system.move_window(window, x, y); 438 | window_system.show_window(window); 439 | } 440 | 441 | pub fn float_location(&self, window_system: &dyn WindowSystem, window: Window) -> RationalRect { 442 | let Rectangle(sx, sy, sw, sh) = self.workspaces.current.screen_detail; 443 | let Rectangle(rx, ry, rw, rh) = window_system.get_geometry(window); 444 | 445 | RationalRect( 446 | (rx as f32 - sx as f32) / sw as f32, 447 | (ry as f32 - sy as f32) / sh as f32, 448 | rw as f32 / sw as f32, 449 | rh as f32 / sh as f32, 450 | ) 451 | } 452 | 453 | pub fn float( 454 | &self, 455 | window_system: &dyn WindowSystem, 456 | config: &GeneralConfig, 457 | window: Window, 458 | ) -> WindowManager { 459 | let rect = self.float_location(window_system, window); 460 | self.windows(window_system, config, &|w| w.float(window, rect)) 461 | } 462 | 463 | pub fn mouse_drag( 464 | &self, 465 | window_system: &dyn WindowSystem, 466 | f: Box WindowManager>, 467 | ) -> WindowManager { 468 | window_system.grab_pointer(); 469 | 470 | let motion = Rc::new(Box::new(move |x, y, window_manager, w: &dyn WindowSystem| { 471 | let z = f(x, y, window_manager, w); 472 | w.remove_motion_events(); 473 | z 474 | }) as MouseDrag); 475 | 476 | WindowManager { 477 | running: self.running, 478 | dragging: Some(motion), 479 | workspaces: self.workspaces.clone(), 480 | waiting_unmap: self.waiting_unmap.clone(), 481 | } 482 | } 483 | 484 | pub fn mouse_move_window( 485 | &self, 486 | window_system: &dyn WindowSystem, 487 | config: &GeneralConfig, 488 | window: Window, 489 | ) -> WindowManager { 490 | let (ox, oy) = window_system.get_pointer(window); 491 | let Rectangle(x, y, _, _) = window_system.get_geometry(window); 492 | 493 | self.mouse_drag( 494 | window_system, 495 | Box::new( 496 | move |ex: u32, ey: u32, m: WindowManager, w: &dyn WindowSystem| { 497 | w.move_window( 498 | window, 499 | x + (ex as i32 - ox as i32), 500 | y + (ey as i32 - oy as i32), 501 | ); 502 | m.modify_workspaces(|wsp| { 503 | wsp.update_floating_rect(window, m.float_location(w, window)) 504 | }) 505 | }, 506 | ), 507 | ) 508 | .float(window_system, config, window) 509 | } 510 | 511 | pub fn mouse_resize_window( 512 | &self, 513 | window_system: &dyn WindowSystem, 514 | config: &GeneralConfig, 515 | window: Window, 516 | ) -> WindowManager { 517 | let Rectangle(x, y, w, h) = window_system.get_geometry(window); 518 | 519 | window_system.warp_pointer(window, w, h); 520 | self.mouse_drag( 521 | window_system, 522 | Box::new( 523 | move |ex: u32, ey: u32, m: WindowManager, w: &dyn WindowSystem| { 524 | let nx = cmp::max(0, ex as i32 - x) as u32; 525 | let ny = cmp::max(0, ey as i32 - y) as u32; 526 | w.resize_window(window, nx, ny); 527 | m.modify_workspaces(|wsp| { 528 | wsp.update_floating_rect(window, m.float_location(w, window)) 529 | }) 530 | }, 531 | ), 532 | ) 533 | .float(window_system, config, window) 534 | } 535 | 536 | // Checks if the window is awaiting an unmap operation 537 | pub fn is_waiting_unmap(&self, window: Window) -> bool { 538 | self.waiting_unmap.contains_key(&window) 539 | } 540 | 541 | // Add a window to the unmap queue 542 | pub fn update_unmap(&self, window: Window) -> WindowManager { 543 | if !self.waiting_unmap.contains_key(&window) { 544 | return self.clone(); 545 | } 546 | 547 | let val = self.waiting_unmap[&window]; 548 | let mut new_map = self.waiting_unmap.clone(); 549 | 550 | if val == 1 { 551 | new_map.remove(&window); 552 | } else { 553 | new_map.insert(window, val - 1); 554 | } 555 | 556 | WindowManager { 557 | running: self.running, 558 | dragging: self.dragging.clone(), 559 | workspaces: self.workspaces.clone(), 560 | waiting_unmap: new_map, 561 | } 562 | } 563 | 564 | pub fn insert_or_update_unmap(&self, window: Window) -> WindowManager { 565 | let mut new_map = self.waiting_unmap.clone(); 566 | 567 | *new_map.entry(window).or_insert(0) += 1; 568 | 569 | WindowManager { 570 | running: self.running, 571 | dragging: self.dragging.clone(), 572 | workspaces: self.workspaces.clone(), 573 | waiting_unmap: new_map, 574 | } 575 | } 576 | 577 | pub fn remove_from_unmap(&self, window: Window) -> WindowManager { 578 | let mut new_map = self.waiting_unmap.clone(); 579 | if new_map.contains_key(&window) { 580 | new_map.remove(&window); 581 | } 582 | WindowManager { 583 | running: self.running, 584 | dragging: self.dragging.clone(), 585 | workspaces: self.workspaces.clone(), 586 | waiting_unmap: new_map, 587 | } 588 | } 589 | } 590 | -------------------------------------------------------------------------------- /core/src/window_system.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | 3 | use self::libc::{c_int, c_ulong}; 4 | use crate::config::GeneralConfig; 5 | use crate::window_manager::WindowManager; 6 | use std::fmt::{Debug, Error, Formatter}; 7 | 8 | pub type Window = u64; 9 | 10 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 11 | pub struct Rectangle(pub i32, pub i32, pub u32, pub u32); 12 | 13 | impl Rectangle { 14 | pub fn is_inside(&self, x: i32, y: i32) -> bool { 15 | let &Rectangle(rx, ry, rw, rh) = self; 16 | 17 | x >= rx && x <= rx + rw as i32 && y >= ry && y <= ry + rh as i32 18 | } 19 | 20 | pub fn overlaps(&self, &Rectangle(bx, by, bw, bh): &Rectangle) -> bool { 21 | let &Rectangle(ax, ay, aw, ah) = self; 22 | !(bx >= ax + aw as i32 23 | || bx + bw as i32 <= ax 24 | || by >= ay + ah as i32 25 | || by + bh as i32 <= ay) 26 | } 27 | } 28 | 29 | #[derive(Clone, Copy, Debug)] 30 | pub struct WindowChanges { 31 | pub x: u32, 32 | pub y: u32, 33 | pub width: u32, 34 | pub height: u32, 35 | pub border_width: u32, 36 | pub sibling: Window, 37 | pub stack_mode: u32, 38 | } 39 | 40 | /// Represents a keyboard input 41 | /// with an abstracted modifier mask 42 | /// and the key represented as a string 43 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] 44 | pub struct KeyCommand { 45 | pub mask: KeyModifiers, 46 | pub key: u64, 47 | } 48 | 49 | impl KeyCommand { 50 | pub fn new(key: u64, mask: KeyModifiers) -> KeyCommand { 51 | KeyCommand { key, mask } 52 | } 53 | } 54 | 55 | impl Debug for KeyCommand { 56 | fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { 57 | f.write_str(&format!("{:}", self.key))?; 58 | Ok(()) 59 | } 60 | } 61 | 62 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] 63 | pub struct MouseCommand { 64 | pub mask: KeyModifiers, 65 | pub button: MouseButton, 66 | } 67 | 68 | impl Debug for MouseCommand { 69 | fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { 70 | f.write_str(&format!("{:}", self.button)).unwrap(); 71 | Ok(()) 72 | } 73 | } 74 | 75 | impl MouseCommand { 76 | pub fn new(button: MouseButton, mask: KeyModifiers) -> MouseCommand { 77 | MouseCommand { button, mask } 78 | } 79 | } 80 | 81 | bitflags! { 82 | pub struct KeyModifiers : u32 { 83 | const NONEMASK = 0; 84 | const SHIFTMASK = 1; 85 | const LOCKMASK = 2; 86 | const CONTROLMASK = 4; 87 | const MOD1MASK = 8; 88 | const MOD2MASK = 16; 89 | const MOD3MASK = 32; 90 | const MOD4MASK = 64; 91 | const MOD5MASK = 128; 92 | } 93 | } 94 | 95 | pub type MouseButton = u32; 96 | pub const BUTTON1: MouseButton = 1; 97 | pub const BUTTON2: MouseButton = 2; 98 | pub const BUTTON3: MouseButton = 3; 99 | pub const BUTTON4: MouseButton = 4; 100 | pub const BUTTON5: MouseButton = 5; 101 | 102 | impl KeyModifiers { 103 | pub fn get_mask(&self) -> u32 { 104 | self.bits() 105 | } 106 | } 107 | 108 | #[derive(Clone, Copy, PartialEq, Eq)] 109 | pub struct SizeHint { 110 | pub min_size: Option<(u32, u32)>, 111 | pub max_size: Option<(u32, u32)>, 112 | } 113 | 114 | #[derive(Clone, Copy, Debug)] 115 | pub enum WindowSystemEvent { 116 | ConfigurationNotification(Window), 117 | ConfigurationRequest(Window, WindowChanges, u64), 118 | /// A window has been created and needs to be managed. 119 | WindowCreated(Window), 120 | /// A window has been destroyed and needs to be unmanaged. 121 | WindowDestroyed(Window), 122 | WindowUnmapped(Window, bool), 123 | /// The pointer has entered a window's area. Mostly used 124 | /// for mousefollow focus. 125 | Enter(Window), 126 | /// The pointer has left a window's area. Mostly used 127 | /// for mousefollow focus. 128 | Leave(Window), 129 | ButtonPressed(Window, Window, MouseCommand, u32, u32), 130 | ButtonReleased, 131 | MouseMotion(u32, u32), 132 | KeyPressed(Window, KeyCommand), 133 | ClientMessageEvent(Window, c_ulong, c_int, [i32; 5]), 134 | PropertyMessageEvent(bool, Window, c_ulong), 135 | /// The underlying event by xlib or wayland is unknown 136 | /// and can be ignored. 137 | UnknownEvent, 138 | } 139 | 140 | pub trait WindowSystem { 141 | fn get_string_from_keycode(&self, key: u32) -> String; 142 | fn get_keycode_from_string(&self, key: &str) -> u64; 143 | fn get_root(&self) -> Window; 144 | /// Retrieve geometry infos over all screens 145 | fn get_screen_infos(&self) -> Vec; 146 | /// Get the number of physical displays 147 | fn get_number_of_screens(&self) -> usize; 148 | /// Get the width of the given physical screen 149 | fn get_display_width(&self, screen: usize) -> u32; 150 | /// Get the height of the given physical screen 151 | fn get_display_height(&self, screen: usize) -> u32; 152 | /// Get the given window's name 153 | fn get_window_name(&self, window: Window) -> String; 154 | fn get_class_name(&self, window: Window) -> String; 155 | // Get 'role' name of window 156 | fn get_role_name(&self, window: Window) -> String; 157 | /// Get a list of all windows 158 | fn get_windows(&self) -> Vec; 159 | /// Set the given window's border width 160 | fn set_window_border_width(&self, window: Window, border_width: u32); 161 | fn get_window_border_width(&self, window: Window) -> u32; 162 | /// Set the given window's border color 163 | fn set_window_border_color(&self, window: Window, border_color: u32); 164 | /// Resize the window to the given dimensions 165 | fn resize_window(&self, window: Window, width: u32, height: u32); 166 | /// Move the window's top left corner to the given coordinates 167 | fn move_window(&self, window: Window, x: i32, y: i32); 168 | /// Map the window to the screen and show it 169 | fn show_window(&self, window: Window); 170 | fn hide_window(&self, window: Window); 171 | fn focus_window(&self, window: Window, window_manager: &WindowManager); 172 | fn get_focused_window(&self) -> Window; 173 | fn configure_window( 174 | &self, 175 | window: Window, 176 | window_changes: WindowChanges, 177 | mask: u64, 178 | is_floating: bool, 179 | ); 180 | /// Check if there are events pending 181 | fn event_pending(&self) -> bool; 182 | /// Get the next event from the queue 183 | fn get_event(&self) -> WindowSystemEvent; 184 | fn flush(&self); 185 | fn grab_keys(&self, keys: Vec); 186 | fn grab_button(&self, button: MouseCommand); 187 | fn remove_enter_events(&self); 188 | fn remove_motion_events(&self); 189 | fn get_partial_strut(&self, window: Window) -> Option>; 190 | fn get_strut(&self, window: Window) -> Option>; 191 | fn set_initial_properties(&self, window: Window); 192 | fn is_dock(&self, window: Window) -> bool; 193 | fn get_geometry(&self, window: Window) -> Rectangle; 194 | fn get_size_hints(&self, window: Window) -> SizeHint; 195 | fn restack_windows(&self, windows: Vec); 196 | fn close_client(&self, window: Window); 197 | fn kill_client(&self, window: Window); 198 | fn grab_pointer(&self); 199 | fn ungrab_pointer(&self); 200 | fn get_pointer(&self, window: Window) -> (u32, u32); 201 | fn warp_pointer(&self, window: Window, x: u32, y: u32); 202 | fn overrides_redirect(&self, window: Window) -> bool; 203 | fn update_server_state(&self, manager: &WindowManager); 204 | fn process_message( 205 | &self, 206 | window_manager: &WindowManager, 207 | config: &GeneralConfig, 208 | window: Window, 209 | atom: c_ulong, 210 | ) -> WindowManager; 211 | } 212 | -------------------------------------------------------------------------------- /core/tests/core/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod stack; 2 | -------------------------------------------------------------------------------- /core/tests/core/stack.rs: -------------------------------------------------------------------------------- 1 | extern crate wtftw_core; 2 | 3 | use self::wtftw_core::core::stack::Stack; 4 | 5 | #[test] 6 | fn stack_add() { 7 | let s1 = Stack::from_element(42); 8 | let s2 = s1.add(23); 9 | 10 | assert!(s2.focus == 23); 11 | assert!(s1.up == s2.up); 12 | assert!(s1.focus == s2.down[0]); 13 | } 14 | 15 | #[test] 16 | fn stack_integrate() { 17 | let s1 = Stack::new(1, vec!(2, 3), vec!(4, 5, 6)); 18 | assert!(s1.integrate() == vec!(3, 2, 1, 4, 5, 6)); 19 | } 20 | 21 | #[test] 22 | fn stack_filter() { 23 | let s1 = Stack::new(1, vec!(2, 3), vec!(4, 5, 6)); 24 | let s2 = s1.filter(|&x| x != 3); 25 | 26 | assert!(s2.is_some()); 27 | assert!(s2.unwrap() == Stack::new(1, vec!(2), vec!(4, 5, 6))); 28 | } 29 | 30 | #[test] 31 | fn stack_focus_up() { 32 | let s1 = Stack::new(1, vec!(2, 3), vec!(4, 5, 6)); 33 | let s2 = s1.focus_up(); 34 | 35 | assert!(s2 == Stack::new(2, vec!(3), vec!(1, 4, 5, 6))); 36 | } 37 | 38 | #[test] 39 | fn stack_focus_down() { 40 | let s1 = Stack::new(1, vec!(2, 3), vec!(4, 5, 6)); 41 | let s2 = s1.focus_down(); 42 | 43 | assert!(s2 == Stack::new(4, vec!(1, 2, 3), vec!(5, 6))); 44 | } 45 | 46 | #[test] 47 | fn stack_reverse() { 48 | let v1 = vec!(2, 3); 49 | let v2 = vec!(4, 5, 6); 50 | 51 | let s1 = Stack::new(1, v1.clone(), v2.clone()); 52 | let s2 = Stack::new(1, v2, v1); 53 | 54 | assert!(s1.reverse() == s2); 55 | assert!(s2.reverse() == s1); 56 | assert!(s1.reverse().reverse() == s1); 57 | } 58 | 59 | #[test] 60 | fn stack_len() { 61 | let s1 = Stack::new(1, vec!(2, 3), vec!(4, 5, 6)); 62 | 63 | assert!(s1.len() == 6); 64 | } 65 | 66 | #[test] 67 | fn stack_contains() { 68 | let s1 = Stack::new(42, vec!(2, 3), vec!(4, 5, 6)); 69 | let s2 = Stack::new(23, Vec::new(), Vec::new()); 70 | 71 | assert!(s1.contains(42)); 72 | assert!(!s1.contains(23)); 73 | assert!(!s2.contains(42)); 74 | assert!(s2.contains(23)); 75 | } 76 | -------------------------------------------------------------------------------- /core/tests/core/workspace.rs: -------------------------------------------------------------------------------- 1 | #![feature(collections)] 2 | 3 | extern crate wtftw_core; 4 | 5 | use wtftw_core::layout::FullLayout; 6 | use wtftw_core::core::stack::Stack; 7 | use wtftw_core::core::workspace::Workspace; 8 | 9 | #[test] 10 | fn workspace_contains() { 11 | let s1 = Stack::new(42, vec!(2, 3), vec!(4, 5, 6)); 12 | let w1 = Workspace::new(1, String::from_str("Foo"), Box::new(FullLayout), Some(s1)); 13 | let w2 = Workspace::new(1, String::from_str("Foo"), Box::new(FullLayout), None); 14 | 15 | assert!(w1.contains(42)); 16 | assert!(!w1.contains(23)); 17 | assert!(!w2.contains(2)); 18 | } 19 | -------------------------------------------------------------------------------- /core/tests/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod core; 2 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | with import {}; with xlibs; 2 | 3 | stdenv.mkDerivation rec { 4 | name = "wtftw"; 5 | buildInputs = [ makeWrapper cargo rustcMaster libXinerama libX11 ]; 6 | buildPhase = "cargo build"; 7 | libPath = lib.makeLibraryPath [ libXinerama libX11 ]; 8 | unpackPhase = "true"; 9 | } 10 | -------------------------------------------------------------------------------- /src/wtftw.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | extern crate getopts; 4 | extern crate serde_json; 5 | extern crate wtftw_core; 6 | extern crate wtftw_xlib; 7 | extern crate zombie; 8 | extern crate simplelog; 9 | 10 | use anyhow::Result; 11 | use std::env; 12 | use std::rc::Rc; 13 | use std::ops::Deref; 14 | use getopts::Options; 15 | use wtftw_core::config::Config; 16 | use wtftw_core::window_manager::WindowManager; 17 | use wtftw_core::window_system::*; 18 | use wtftw_xlib::XlibWindowSystem; 19 | 20 | pub fn parse_window_ids(ids: &str) -> Vec<(Window, u32)> { 21 | match serde_json::from_str(ids) { 22 | Ok(x) => x, 23 | _ => Vec::new() 24 | } 25 | } 26 | 27 | fn init_terminal_logger(verbose_mode_enabled: bool) { 28 | let level = if verbose_mode_enabled { simplelog::LevelFilter::Debug } else { simplelog::LevelFilter::Warn }; 29 | simplelog::CombinedLogger::init(vec![ 30 | simplelog::TermLogger::new(level, simplelog::Config::default(), simplelog::TerminalMode::Mixed), 31 | ]).unwrap(); 32 | } 33 | 34 | fn main() -> Result<()> { 35 | // Parse command line arguments 36 | let args : Vec = env::args().collect(); 37 | 38 | let mut options = Options::new(); 39 | options.optopt("r", "resume", "list of window IDs to capture in resume", "WINDOW"); 40 | options.optflag("v", "verbose", "be verbose"); 41 | 42 | let matches = match options.parse(args.into_iter().skip(1).collect::>()) { 43 | Ok(m) => m, 44 | Err(f) => panic!(f.to_string()) 45 | }; 46 | 47 | init_terminal_logger(matches.opt_present("v")); 48 | 49 | // Create a default config.generaluration 50 | let mut config = Config::initialize()?; 51 | // Initialize window system. Use xlib here for now 52 | debug!("initialize window system"); 53 | let xlib = XlibWindowSystem::new(); 54 | let window_system : Rc = Rc::new(xlib); 55 | // Create the actual window manager 56 | debug!("create window manager"); 57 | let mut window_manager = WindowManager::new(window_system.deref(), &config.general); 58 | 59 | // If available, compile the config.general file at ~/.wtftw/config.general.rs 60 | // and call the config.generalure method 61 | config.compile_and_call(&mut window_manager, window_system.deref()); 62 | window_manager = WindowManager::new(window_system.deref(), &config.general); 63 | 64 | // Output some initial information 65 | info!("WTFTW - Window Tiling For The Win"); 66 | info!("Starting wtftw on {} screen(s)", window_system.get_screen_infos().len()); 67 | 68 | // Output information about displays 69 | for (i, &Rectangle(x, y, w, h)) in window_system.get_screen_infos().iter().enumerate() { 70 | debug!("Display {}: {}x{} ({}, {})", i, w, h, x, y); 71 | } 72 | 73 | debug!("Size of keyhandlers after config.generaluration: {}", config.internal.key_handlers.len()); 74 | 75 | for (command, _) in config.internal.key_handlers.iter() { 76 | window_system.grab_keys(vec!(*command)); 77 | } 78 | 79 | for (&command, _) in config.internal.mouse_handlers.iter() { 80 | window_system.grab_button(command); 81 | } 82 | 83 | let window_ids = if matches.opt_present("r") { 84 | debug!("trying to manage pre-existing windows"); 85 | debug!("found {}", matches.opt_str("r").unwrap()); 86 | parse_window_ids(&matches.opt_str("r").unwrap()) 87 | } else { 88 | Vec::new() 89 | }; 90 | 91 | for (window, workspace) in window_ids { 92 | debug!("re-inserting window {}", window); 93 | window_manager = window_manager.view(window_system.deref(), workspace, &config.general) 94 | .manage(window_system.deref(), window, &config.general).windows(window_system.deref(), &config.general, 95 | &|x| (config.internal.manage_hook)(x.clone(), 96 | window_system.clone(), window)); 97 | } 98 | 99 | window_manager = (*config.internal.startup_hook)(window_manager, window_system.clone(), &config); 100 | 101 | // Enter the event loop and just listen for events 102 | while window_manager.running { 103 | let event = window_system.clone().get_event(); 104 | match event { 105 | WindowSystemEvent::ClientMessageEvent(_, _, _, _) => { 106 | }, 107 | WindowSystemEvent::PropertyMessageEvent(process, window, atom) => { 108 | if process { 109 | window_manager = window_system.process_message(&window_manager, &config.general, window, atom); 110 | } 111 | }, 112 | // The X11/Wayland configuration changed, so we need to readjust the 113 | // screen configurations. 114 | WindowSystemEvent::ConfigurationNotification(window) => { 115 | if window_system.get_root() == window { 116 | debug!("screen configuration changed. rescreen"); 117 | window_manager = window_manager.rescreen(window_system.deref()); 118 | } 119 | }, 120 | // A window asked to be reconfigured (i.e. resized, border change, etc.) 121 | WindowSystemEvent::ConfigurationRequest(window, window_changes, mask) => { 122 | let floating = window_manager.workspaces.floating.iter().any(|(&x, _)| x == window) || 123 | !window_manager.workspaces.contains(window); 124 | window_system.configure_window(window, window_changes, mask, floating); 125 | window_manager = window_manager.windows(window_system.deref(), &config.general, &|x| x.clone()); 126 | }, 127 | // A new window was created, so we need to manage 128 | // it unless it is already managed by us. 129 | WindowSystemEvent::WindowCreated(window) => { 130 | if window_manager.is_window_managed(window) || window_system.overrides_redirect(window) { 131 | continue; 132 | } 133 | 134 | window_manager = window_manager.manage(window_system.deref(), window, &config.general) 135 | .windows(window_system.deref(), &config.general, 136 | &|x| (config.internal.manage_hook)(x.clone(), 137 | window_system.clone(), window)); 138 | }, 139 | WindowSystemEvent::WindowUnmapped(window, synthetic) => { 140 | if synthetic && window_manager.is_window_managed(window) { 141 | window_manager = if synthetic || !window_manager.is_waiting_unmap(window) { 142 | window_manager.unmanage(window_system.deref(), window, &config.general) 143 | } else { 144 | window_manager.update_unmap(window) 145 | }; 146 | } 147 | zombie::collect_zombies(); 148 | }, 149 | WindowSystemEvent::WindowDestroyed(window) => { 150 | if window_manager.is_window_managed(window) { 151 | window_manager = window_manager.unmanage(window_system.deref(), window, &config.general) 152 | .remove_from_unmap(window); 153 | } 154 | }, 155 | // The mouse pointer entered a window's region. If focus following 156 | // is enabled, we need to set focus to it. 157 | WindowSystemEvent::Enter(window) => { 158 | if config.general.focus_follows_mouse && window_manager.is_window_managed(window) { 159 | window_manager = window_manager.focus(window, window_system.deref(), &config.general); 160 | } 161 | }, 162 | // Mouse button has been pressed. We need to check if there is a mouse handler 163 | // associated and if necessary, call it. Otherwise it results in a focus action. 164 | WindowSystemEvent::ButtonPressed(window, subwindow, button, _, _) => { 165 | let is_root = window_system.get_root() == window; 166 | let is_sub_root = window_system.get_root() == subwindow || subwindow == 0; 167 | let act = config.internal.mouse_handlers.get(&button); 168 | 169 | match act { 170 | Some(ref action) => { 171 | // If it's a root window, then it's an event we grabbed 172 | if is_root && !is_sub_root { 173 | let local_window_manager = window_manager.clone(); 174 | window_manager = action(local_window_manager, window_system.clone(), 175 | &config.general, subwindow); 176 | } 177 | } 178 | None => { 179 | // Otherwise just clock to focus 180 | if !is_root { 181 | window_manager = window_manager.focus(window, window_system.deref(), &config.general); 182 | } 183 | } 184 | } 185 | }, 186 | WindowSystemEvent::ButtonReleased => { 187 | // If we were dragging, release the pointer and 188 | // reset the dragging closure 189 | if window_manager.dragging.is_some() { 190 | window_system.ungrab_pointer(); 191 | window_manager.dragging = None; 192 | } 193 | } 194 | WindowSystemEvent::KeyPressed(_, key) => { 195 | if config.internal.key_handlers.contains_key(&key) { 196 | let local_window_manager = window_manager.clone(); 197 | window_manager = config.internal.key_handlers[&key](local_window_manager, 198 | window_system.clone(), &config.general); 199 | } 200 | }, 201 | WindowSystemEvent::MouseMotion(x, y) => { 202 | let local_window_manager = window_manager.clone(); 203 | if let Some(drag) = window_manager.dragging { 204 | window_manager = drag(x, y, local_window_manager, window_system.deref()); 205 | window_system.remove_motion_events(); 206 | } 207 | }, 208 | _ => () 209 | }; 210 | 211 | if let Some(ref mut loghook) = config.internal.loghook { 212 | loghook(window_manager.clone(), window_system.clone()); 213 | } 214 | 215 | window_system.update_server_state(&window_manager); 216 | } 217 | 218 | Ok(()) 219 | } 220 | -------------------------------------------------------------------------------- /xlib/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "arrayref" 5 | version = "0.3.6" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" 8 | 9 | [[package]] 10 | name = "arrayvec" 11 | version = "0.5.1" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" 14 | 15 | [[package]] 16 | name = "autocfg" 17 | version = "1.0.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 20 | 21 | [[package]] 22 | name = "base64" 23 | version = "0.11.0" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" 26 | 27 | [[package]] 28 | name = "bitflags" 29 | version = "1.2.1" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 32 | 33 | [[package]] 34 | name = "blake2b_simd" 35 | version = "0.5.10" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" 38 | dependencies = [ 39 | "arrayref", 40 | "arrayvec", 41 | "constant_time_eq", 42 | ] 43 | 44 | [[package]] 45 | name = "cfg-if" 46 | version = "0.1.10" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 49 | 50 | [[package]] 51 | name = "constant_time_eq" 52 | version = "0.1.5" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 55 | 56 | [[package]] 57 | name = "crossbeam-utils" 58 | version = "0.7.2" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 61 | dependencies = [ 62 | "autocfg", 63 | "cfg-if", 64 | "lazy_static", 65 | ] 66 | 67 | [[package]] 68 | name = "dirs" 69 | version = "3.0.1" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff" 72 | dependencies = [ 73 | "dirs-sys", 74 | ] 75 | 76 | [[package]] 77 | name = "dirs-sys" 78 | version = "0.3.5" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" 81 | dependencies = [ 82 | "libc", 83 | "redox_users", 84 | "winapi", 85 | ] 86 | 87 | [[package]] 88 | name = "dylib" 89 | version = "0.0.3" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "f06c13073013a912b363eee1433572499a2028a6b05432dad09383124d64731e" 92 | dependencies = [ 93 | "libc", 94 | ] 95 | 96 | [[package]] 97 | name = "getrandom" 98 | version = "0.1.14" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 101 | dependencies = [ 102 | "cfg-if", 103 | "libc", 104 | "wasi", 105 | ] 106 | 107 | [[package]] 108 | name = "itoa" 109 | version = "0.4.6" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" 112 | 113 | [[package]] 114 | name = "lazy_static" 115 | version = "1.4.0" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 118 | 119 | [[package]] 120 | name = "libc" 121 | version = "0.2.71" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" 124 | 125 | [[package]] 126 | name = "log" 127 | version = "0.4.8" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 130 | dependencies = [ 131 | "cfg-if", 132 | ] 133 | 134 | [[package]] 135 | name = "pkg-config" 136 | version = "0.3.17" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" 139 | 140 | [[package]] 141 | name = "redox_syscall" 142 | version = "0.1.56" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 145 | 146 | [[package]] 147 | name = "redox_users" 148 | version = "0.3.4" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" 151 | dependencies = [ 152 | "getrandom", 153 | "redox_syscall", 154 | "rust-argon2", 155 | ] 156 | 157 | [[package]] 158 | name = "rust-argon2" 159 | version = "0.7.0" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" 162 | dependencies = [ 163 | "base64", 164 | "blake2b_simd", 165 | "constant_time_eq", 166 | "crossbeam-utils", 167 | ] 168 | 169 | [[package]] 170 | name = "ryu" 171 | version = "1.0.5" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 174 | 175 | [[package]] 176 | name = "serde" 177 | version = "1.0.114" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" 180 | 181 | [[package]] 182 | name = "serde_json" 183 | version = "1.0.56" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "3433e879a558dde8b5e8feb2a04899cf34fdde1fafb894687e52105fc1162ac3" 186 | dependencies = [ 187 | "itoa", 188 | "ryu", 189 | "serde", 190 | ] 191 | 192 | [[package]] 193 | name = "wasi" 194 | version = "0.9.0+wasi-snapshot-preview1" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 197 | 198 | [[package]] 199 | name = "winapi" 200 | version = "0.3.9" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 203 | dependencies = [ 204 | "winapi-i686-pc-windows-gnu", 205 | "winapi-x86_64-pc-windows-gnu", 206 | ] 207 | 208 | [[package]] 209 | name = "winapi-i686-pc-windows-gnu" 210 | version = "0.4.0" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 213 | 214 | [[package]] 215 | name = "winapi-x86_64-pc-windows-gnu" 216 | version = "0.4.0" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 219 | 220 | [[package]] 221 | name = "wtftw_core" 222 | version = "0.3.2" 223 | dependencies = [ 224 | "bitflags", 225 | "dirs", 226 | "dylib", 227 | "libc", 228 | "log", 229 | "serde_json", 230 | ] 231 | 232 | [[package]] 233 | name = "wtftw_xlib" 234 | version = "0.4.0" 235 | dependencies = [ 236 | "libc", 237 | "log", 238 | "wtftw_core", 239 | "x11", 240 | ] 241 | 242 | [[package]] 243 | name = "x11" 244 | version = "2.18.2" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "77ecd092546cb16f25783a5451538e73afc8d32e242648d54f4ae5459ba1e773" 247 | dependencies = [ 248 | "libc", 249 | "pkg-config", 250 | ] 251 | -------------------------------------------------------------------------------- /xlib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | 3 | name = "wtftw_xlib" 4 | version = "0.4.0" 5 | authors = ["Simon Wollwage"] 6 | 7 | [dependencies] 8 | log = "0.4.8" 9 | libc = "0.2.71" 10 | x11 = { version = "2.18.2", features = ["xlib", "xinerama"] } 11 | 12 | [dependencies.wtftw_core] 13 | path = "../core" 14 | 15 | [lib] 16 | name = "wtftw_xlib" 17 | path = "src/xlib_window_system.rs" 18 | crate-type = ["rlib"] 19 | -------------------------------------------------------------------------------- /xlib/src/xlib_window_system.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | extern crate libc; 4 | extern crate wtftw_core; 5 | extern crate x11; 6 | 7 | use std::borrow::ToOwned; 8 | use wtftw_core::config::GeneralConfig; 9 | use x11::xinerama; 10 | use x11::xlib; 11 | 12 | use std::env::vars; 13 | use std::ffi::CStr; 14 | use std::ffi::CString; 15 | use std::io::Write; 16 | use std::mem; 17 | use std::ptr::null; 18 | use std::ptr::null_mut; 19 | use std::slice::from_raw_parts; 20 | use std::str; 21 | use std::str::from_utf8; 22 | 23 | use wtftw_core::window_manager::*; 24 | use wtftw_core::window_system::*; 25 | 26 | const KEYPRESS: usize = 2; 27 | const BUTTONPRESS: usize = 4; 28 | const BUTTONRELEASE: usize = 5; 29 | const MOTIONOTIFY: usize = 6; 30 | const ENTERNOTIFY: usize = 7; 31 | const LEAVENOTIFY: usize = 8; 32 | const DESTROYNOTIFY: usize = 17; 33 | const UNMAPNOTIFY: usize = 18; 34 | const MAPREQUEST: usize = 20; 35 | const CONFIGURENOTIFY: usize = 22; 36 | const CONFIGUREREQUEST: usize = 23; 37 | const PROPERTYNOTIFY: usize = 28; 38 | const CLIENTMESSAGE: usize = 33; 39 | 40 | /// A custom error handler to prevent xlib from crashing the whole WM. 41 | /// Necessary because a few events may call the error routine. 42 | unsafe extern "C" fn error_handler(_: *mut xlib::Display, _: *mut xlib::XErrorEvent) -> i32 { 43 | 0 44 | } 45 | 46 | /// The xlib interface. Holds a pointer to the display, 47 | /// the root window's id and a generic vent so 48 | /// we don't have to allocate it every time. 49 | //#[derive(Clone, Copy)] 50 | pub struct XlibWindowSystem { 51 | display: *mut xlib::Display, 52 | root: Window, 53 | ewmh_child: Window, 54 | } 55 | 56 | impl Default for XlibWindowSystem { 57 | fn default() -> Self { 58 | Self::new() 59 | } 60 | } 61 | 62 | impl XlibWindowSystem { 63 | /// Creates a new xlib interface on the default display (i.e. ${DISPLAY}) 64 | /// and creates a root window spanning all screens (including xlib::Xinerama). 65 | pub fn new() -> XlibWindowSystem { 66 | unsafe { 67 | let display = xlib::XOpenDisplay(null()); 68 | 69 | if display.is_null() { 70 | error!( 71 | "No display found at {}", 72 | vars() 73 | .find(|&(ref d, _)| *d == "DISPLAY") 74 | .map(|(_, v)| v) 75 | .unwrap() 76 | ); 77 | panic!("Exiting"); 78 | } 79 | 80 | let screen = xlib::XDefaultScreenOfDisplay(display); 81 | let root = xlib::XRootWindowOfScreen(screen); 82 | 83 | xlib::XSetErrorHandler(Some(error_handler)); 84 | 85 | xlib::XSelectInput(display, root, 0x5A0034); 86 | xlib::XSync(display, 0); 87 | 88 | xlib::XUngrabButton(display, 0, 0x8000, root); 89 | 90 | let mut res = XlibWindowSystem { 91 | display, 92 | root: root as u64, 93 | ewmh_child: 0, 94 | }; 95 | 96 | // let name = (*CString::new(&b"wtftw"[..]).unwrap()).as_ptr(); 97 | let name = "wtftw"; 98 | let name_ptr = name.as_ptr(); 99 | 100 | let wmcheck = res.get_atom("_NET_SUPPORTING_WM_CHECK"); 101 | let wmname = res.get_atom("_NET_WM_NAME"); 102 | let utf8 = res.get_atom("UTF8_STRING"); 103 | let atom_type = res.get_atom("ATOM"); 104 | let window = res.get_atom("WINDOW"); 105 | let net_supported = res.get_atom("_NET_SUPPORTED"); 106 | 107 | let mut attributes = 1u32; 108 | let attrib_ptr: *mut u32 = &mut attributes; 109 | 110 | res.ewmh_child = xlib::XCreateWindow( 111 | display, 112 | root, 113 | -1, 114 | -1, 115 | 1, 116 | 1, 117 | 0, // border 118 | 0, // depth (CopyFromParent) 119 | 2, // InputOnly 120 | ::std::ptr::null_mut(), 121 | 1 << 9, 122 | mem::transmute(attrib_ptr) 123 | //as *mut xlib::XSetWindowAttributes, 124 | ) as u64; 125 | 126 | let mut f = ::std::fs::File::create("/tmp/window_id.txt").unwrap(); 127 | f.write_all(format!("WindowID: {}", res.ewmh_child).as_bytes()) 128 | .unwrap(); 129 | 130 | let mut child: u32 = res.ewmh_child as u32; 131 | let child_ptr: *mut u32 = &mut child; 132 | xlib::XChangeProperty( 133 | display, 134 | res.ewmh_child as u64, 135 | wmcheck as u64, 136 | window as u64, 137 | 32, 138 | 0, 139 | child_ptr as *mut u8, 140 | 1, 141 | ); 142 | xlib::XChangeProperty( 143 | display, 144 | res.ewmh_child as u64, 145 | wmname as u64, 146 | utf8 as u64, 147 | 8, 148 | 0, 149 | name_ptr as *mut u8, 150 | 5, 151 | ); 152 | xlib::XChangeProperty( 153 | display, 154 | root, 155 | wmcheck as u64, 156 | window as u64, 157 | 32, 158 | 0, 159 | child_ptr as *mut u8, 160 | 1, 161 | ); 162 | xlib::XChangeProperty( 163 | display, 164 | root, 165 | wmname as u64, 166 | utf8 as u64, 167 | 8, 168 | 0, 169 | name_ptr as *mut u8, 170 | 5, 171 | ); 172 | 173 | let supported = [ 174 | res.get_atom("_NET_ACTIVE_WINDOW"), 175 | res.get_atom("_NET_NUMBER_OF_DESKTOPS"), 176 | res.get_atom("_NET_CURRENT_DESKTOP"), 177 | ]; 178 | xlib::XChangeProperty( 179 | res.display, 180 | res.root as u64, 181 | net_supported as u64, 182 | atom_type as u64, 183 | 32, 184 | 0, 185 | supported.as_ptr() as *mut u8, 186 | supported.len() as i32, 187 | ); 188 | 189 | //xlib::XMapWindow(res.display, res.ewmh_child); 190 | //let mut changes = xlib::XWindowChanges { 191 | // x: -1, 192 | // y: -1, 193 | // width: -1, 194 | // height: -1, 195 | // border_width: 0, 196 | // sibling: 0, 197 | // stack_mode: 1, 198 | //}; 199 | //let changes_ptr: &mut xlib::XWindowChanges = &mut changes; 200 | 201 | //xlib::XConfigureWindow(res.display, res.ewmh_child, 64, changes_ptr); 202 | 203 | res 204 | } 205 | } 206 | 207 | fn get_property(&self, atom: Window, window: Window) -> Option> { 208 | unsafe { 209 | let mut actual_type_return: u64 = 0; 210 | let mut actual_format_return: i32 = 0; 211 | let mut nitems_return: u64 = 0; 212 | let mut bytes_after_return: u64 = 0; 213 | let mut prop_return: *mut u8 = mem::MaybeUninit::uninit().assume_init(); 214 | 215 | let r = xlib::XGetWindowProperty( 216 | self.display, 217 | window as u64, 218 | atom as u64, 219 | 0, 220 | i64::MAX, 221 | 0, 222 | 0, 223 | &mut actual_type_return, 224 | &mut actual_format_return, 225 | &mut nitems_return, 226 | &mut bytes_after_return, 227 | &mut prop_return, 228 | ); 229 | 230 | if r != 0 || actual_format_return == 0 { 231 | None 232 | } else { 233 | Some( 234 | from_raw_parts(mem::transmute::<*mut u8, *const u64>(prop_return), nitems_return as usize) 235 | .iter() 236 | .map(|&c| c as u64) 237 | .collect(), 238 | ) 239 | } 240 | } 241 | } 242 | 243 | fn get_property_from_string(&self, s: &str, window: Window) -> Option> { 244 | unsafe { 245 | match CString::new(s.as_bytes()) { 246 | Ok(b) => { 247 | let atom = xlib::XInternAtom(self.display, b.as_ptr(), 0); 248 | self.get_property(atom as u64, window) 249 | } 250 | _ => None, 251 | } 252 | } 253 | } 254 | 255 | fn get_atom(&self, s: &str) -> u64 { 256 | unsafe { 257 | match CString::new(s) { 258 | Ok(b) => xlib::XInternAtom(self.display, b.as_ptr() as *const i8, 0) as u64, 259 | _ => panic!("Invalid atom! {}", s), 260 | } 261 | } 262 | } 263 | 264 | fn get_protocols(&self, window: Window) -> Vec { 265 | unsafe { 266 | let mut protocols: *mut u64 = mem::MaybeUninit::uninit().assume_init(); 267 | let mut num = 0; 268 | xlib::XGetWMProtocols(self.display, window as u64, &mut protocols, &mut num); 269 | let slice = from_raw_parts(protocols, num as usize); 270 | slice.iter().map(|&c| c as u64).collect::>() 271 | } 272 | } 273 | 274 | fn change_property( 275 | &self, 276 | window: Window, 277 | property: u64, 278 | typ: u64, 279 | mode: i32, 280 | dat: &mut [u64], 281 | ) { 282 | unsafe { 283 | let ptr = dat.as_mut_ptr() as *mut u8; 284 | xlib::XChangeProperty( 285 | self.display, 286 | window as u64, 287 | property as u64, 288 | typ as u64, 289 | 32, 290 | mode, 291 | ptr, 292 | 2, 293 | ); 294 | } 295 | } 296 | 297 | fn set_button_grab(&self, grab: bool, window: Window) { 298 | if grab { 299 | debug!("grabbing mouse buttons for {}", window); 300 | for &button in (vec![1, 2, 3]).iter() { 301 | unsafe { 302 | xlib::XGrabButton( 303 | self.display, 304 | button, 305 | 0x8000, 306 | window as u64, 307 | 0, 308 | 4, 309 | 1, 310 | 0, 311 | 0, 312 | 0, 313 | ); 314 | } 315 | } 316 | } else { 317 | debug!("ungrabbing mouse buttons for {}", window); 318 | unsafe { 319 | xlib::XUngrabButton(self.display, 0, 0x8000, window as u64); 320 | } 321 | } 322 | } 323 | 324 | fn set_focus(&self, window: Window, window_manager: &WindowManager) { 325 | debug!("setting focus to {}", window); 326 | for &other_window in window_manager.workspaces.visible_windows().iter() { 327 | self.set_button_grab(true, other_window); 328 | } 329 | if window != self.root { 330 | self.set_button_grab(false, window); 331 | } 332 | } 333 | } 334 | 335 | impl WindowSystem for XlibWindowSystem { 336 | fn get_partial_strut(&self, window: Window) -> Option> { 337 | self.get_property_from_string("_NET_WM_STRUT_PARTIAL", window) 338 | } 339 | 340 | fn get_strut(&self, window: Window) -> Option> { 341 | self.get_property_from_string("_NET_WM_STRUT", window) 342 | } 343 | 344 | fn is_dock(&self, window: Window) -> bool { 345 | let dock = self.get_atom("_NET_WM_WINDOW_TYPE_DOCK"); 346 | let desk = self.get_atom("_NET_WM_WINDOW_TYPE_DESKTOP"); 347 | 348 | if let Some(rs) = self.get_property_from_string("_NET_WM_WINDOW_TYPE", window) { 349 | rs.iter().any(|&x| x == dock || x == desk) 350 | } else { 351 | false 352 | } 353 | } 354 | 355 | fn get_string_from_keycode(&self, key: u32) -> String { 356 | unsafe { 357 | let keysym = xlib::XKeycodeToKeysym(self.display, key as u8, 0); 358 | let keyname: *mut i8 = xlib::XKeysymToString(keysym); 359 | 360 | match from_utf8(CStr::from_ptr(keyname as *const i8).to_bytes()) { 361 | Ok(x) => x.to_owned(), 362 | _ => panic!("Invalid keycode!"), 363 | } 364 | } 365 | } 366 | 367 | fn get_keycode_from_string(&self, key: &str) -> u64 { 368 | unsafe { 369 | match CString::new(key.as_bytes()) { 370 | Ok(b) => xlib::XStringToKeysym(b.as_ptr()) as u64, 371 | _ => panic!("Invalid key string!"), 372 | } 373 | } 374 | } 375 | 376 | fn get_root(&self) -> Window { 377 | self.root 378 | } 379 | 380 | fn get_screen_infos(&self) -> Vec { 381 | unsafe { 382 | let mut num: i32 = 0; 383 | let screen_ptr: *const xinerama::XineramaScreenInfo = 384 | xinerama::XineramaQueryScreens(self.display, &mut num); 385 | 386 | // If xinerama is not active, just return the default display 387 | // dimensions and "emulate" xinerama. 388 | if num == 0 { 389 | return vec![Rectangle( 390 | 0, 391 | 0, 392 | self.get_display_width(0), 393 | self.get_display_height(0), 394 | )]; 395 | } 396 | 397 | let screens = from_raw_parts(screen_ptr, num as usize).to_vec(); 398 | screens 399 | .into_iter() 400 | .map(|s| { 401 | Rectangle( 402 | s.x_org as i32, 403 | s.y_org as i32, 404 | s.width as u32, 405 | s.height as u32, 406 | ) 407 | }) 408 | .collect() 409 | } 410 | } 411 | 412 | fn get_number_of_screens(&self) -> usize { 413 | unsafe { xlib::XScreenCount(self.display) as usize } 414 | } 415 | 416 | fn get_display_width(&self, screen: usize) -> u32 { 417 | unsafe { xlib::XDisplayWidth(self.display, screen as i32) as u32 } 418 | } 419 | 420 | fn get_display_height(&self, screen: usize) -> u32 { 421 | unsafe { xlib::XDisplayHeight(self.display, screen as i32) as u32 } 422 | } 423 | 424 | fn get_window_name(&self, window: Window) -> String { 425 | if window == self.root { 426 | return "root".to_owned(); 427 | } 428 | unsafe { 429 | let mut name: *mut i8 = mem::MaybeUninit::uninit().assume_init(); 430 | let result = xlib::XFetchName(self.display, window as u64, &mut name); 431 | if 0 == result || name.is_null() { 432 | let mut actual_type_return: u64 = 0; 433 | let mut actual_format_return: i32 = 0; 434 | let mut nitems_return: u64 = 0; 435 | let mut bytes_after_return: u64 = 0; 436 | let mut prop_return: *mut u8 = mem::MaybeUninit::uninit().assume_init(); 437 | 438 | let wmname = CString::new("WM_NAME").unwrap(); 439 | let atom = xlib::XInternAtom(self.display, wmname.as_ptr(), 0); 440 | let r = xlib::XGetWindowProperty( 441 | self.display, 442 | window as u64, 443 | atom as u64, 444 | 0, 445 | i64::MAX, 446 | 0, 447 | 0, 448 | &mut actual_type_return, 449 | &mut actual_format_return, 450 | &mut nitems_return, 451 | &mut bytes_after_return, 452 | &mut prop_return, 453 | ); 454 | if r != 0 { 455 | "Unknown".to_owned() 456 | } else { 457 | CStr::from_ptr(prop_return as *const i8) 458 | .to_string_lossy() 459 | .into_owned() 460 | } 461 | } else { 462 | str::from_utf8_unchecked(CStr::from_ptr(name as *const i8).to_bytes()) 463 | .to_owned() 464 | } 465 | } 466 | } 467 | 468 | fn get_class_name(&self, window: Window) -> String { 469 | unsafe { 470 | let mut class_hint: xlib::XClassHint = mem::MaybeUninit::uninit().assume_init(); 471 | let rs = xlib::XGetClassHint(self.display, window as u64, &mut class_hint); 472 | 473 | let result = if rs == 0 || class_hint.res_class.is_null() { 474 | "unknown".to_owned() 475 | } else { 476 | debug!("getting class name"); 477 | CStr::from_ptr(class_hint.res_class) 478 | .to_string_lossy() 479 | .into_owned() 480 | }; 481 | 482 | debug!("class name is {}", result); 483 | 484 | result 485 | } 486 | } 487 | 488 | fn get_role_name(&self, window: Window) -> String { 489 | unsafe { 490 | let mut class_hint: xlib::XClassHint = mem::MaybeUninit::uninit().assume_init(); 491 | let rs = xlib::XGetClassHint(self.display, window as u64, &mut class_hint); 492 | 493 | let result = if rs == 0 || class_hint.res_name.is_null() { 494 | "unknown".to_owned() 495 | } else { 496 | debug!("getting role name"); 497 | CStr::from_ptr(class_hint.res_name) 498 | .to_string_lossy() 499 | .into_owned() 500 | }; 501 | 502 | debug!("role name is {}", result); 503 | 504 | result 505 | } 506 | } 507 | 508 | fn get_windows(&self) -> Vec { 509 | unsafe { 510 | let mut unused: u64 = 0; 511 | let mut children: *mut u64 = mem::MaybeUninit::uninit().assume_init(); 512 | let children_ptr: *mut *mut u64 = &mut children; 513 | let mut num_children: u32 = 0; 514 | xlib::XQueryTree( 515 | self.display, 516 | self.root as u64, 517 | &mut unused, 518 | &mut unused, 519 | children_ptr, 520 | &mut num_children, 521 | ); 522 | let const_children: *const u64 = children as *const u64; 523 | debug!("Found {} windows", num_children); 524 | from_raw_parts(const_children, num_children as usize) 525 | .iter() 526 | .filter(|&&c| c != self.root) 527 | .copied() 528 | .collect() 529 | } 530 | } 531 | 532 | fn set_window_border_width(&self, window: Window, border_width: u32) { 533 | if window == self.root { 534 | return; 535 | } 536 | unsafe { 537 | xlib::XSetWindowBorderWidth(self.display, window as u64, border_width); 538 | } 539 | } 540 | 541 | fn get_window_border_width(&self, window: Window) -> u32 { 542 | unsafe { 543 | let mut attributes: xlib::XWindowAttributes = mem::MaybeUninit::uninit().assume_init(); 544 | xlib::XGetWindowAttributes(self.display, window as u64, &mut attributes); 545 | attributes.border_width as u32 546 | } 547 | } 548 | 549 | fn set_window_border_color(&self, window: Window, border_color: u32) { 550 | if window == self.root { 551 | return; 552 | } 553 | unsafe { 554 | xlib::XSetWindowBorder(self.display, window as u64, border_color as u64); 555 | } 556 | } 557 | 558 | fn resize_window(&self, window: Window, width: u32, height: u32) { 559 | unsafe { 560 | xlib::XResizeWindow(self.display, window as u64, width, height); 561 | } 562 | } 563 | 564 | fn move_window(&self, window: Window, x: i32, y: i32) { 565 | unsafe { 566 | xlib::XMoveWindow(self.display, window as u64, x, y); 567 | } 568 | } 569 | 570 | fn set_initial_properties(&self, window: Window) { 571 | unsafe { 572 | let atom = self.get_atom("WM_STATE"); 573 | self.change_property(window as u64, atom, atom, 0, &mut [3, 0]); 574 | xlib::XSelectInput(self.display, window as u64, 0x420010); 575 | } 576 | } 577 | 578 | fn show_window(&self, window: Window) { 579 | unsafe { 580 | let atom = self.get_atom("WM_STATE"); 581 | self.change_property(window, atom, atom, 0, &mut [1, 0]); 582 | xlib::XMapWindow(self.display, window as u64); 583 | } 584 | } 585 | 586 | fn hide_window(&self, window: Window) { 587 | unsafe { 588 | xlib::XSelectInput(self.display, window as u64, 0x400010); 589 | xlib::XUnmapWindow(self.display, window as u64); 590 | xlib::XSelectInput(self.display, window as u64, 0x420010); 591 | let atom = self.get_atom("WM_STATE"); 592 | self.change_property(window as u64, atom, atom, 0, &mut [3, 0]); 593 | } 594 | } 595 | 596 | fn focus_window(&self, window: Window, window_manager: &WindowManager) { 597 | unsafe { 598 | self.set_focus(window, window_manager); 599 | xlib::XSetInputFocus(self.display, window as u64, 1, 0); 600 | } 601 | } 602 | 603 | fn get_focused_window(&self) -> Window { 604 | unsafe { 605 | let mut window = 0; 606 | let mut tmp = 0; 607 | 608 | xlib::XGetInputFocus(self.display, &mut window, &mut tmp) as Window 609 | } 610 | } 611 | 612 | fn configure_window( 613 | &self, 614 | window: Window, 615 | window_changes: WindowChanges, 616 | mask: u64, 617 | is_floating: bool, 618 | ) { 619 | unsafe { 620 | let result = if is_floating { 621 | let mut xlib_window_changes = xlib::XWindowChanges { 622 | x: window_changes.x as i32, 623 | y: window_changes.y as i32, 624 | width: window_changes.width as i32, 625 | height: window_changes.height as i32, 626 | border_width: window_changes.border_width as i32, 627 | sibling: window_changes.sibling as u64, 628 | stack_mode: window_changes.stack_mode as i32, 629 | }; 630 | xlib::XConfigureWindow( 631 | self.display, 632 | window as u64, 633 | mask as u32, 634 | &mut xlib_window_changes, 635 | ); 636 | } else { 637 | let Rectangle(x, y, w, h) = self.get_geometry(window); 638 | 639 | let mut attributes: xlib::XWindowAttributes = 640 | mem::MaybeUninit::uninit().assume_init(); 641 | xlib::XGetWindowAttributes(self.display, window as u64, &mut attributes); 642 | 643 | let mut configure_event: xlib::XConfigureEvent = 644 | mem::MaybeUninit::uninit().assume_init(); 645 | 646 | configure_event.type_ = CONFIGURENOTIFY as i32; 647 | configure_event.x = attributes.x; 648 | configure_event.y = attributes.y; 649 | configure_event.width = attributes.width; 650 | configure_event.height = attributes.height; 651 | configure_event.border_width = attributes.border_width; 652 | configure_event.above = 0; 653 | configure_event.override_redirect = attributes.override_redirect; 654 | 655 | let mut event = xlib::XEvent::from(configure_event); 656 | 657 | debug!( 658 | "sending configure notification for window {}: ({}, {}) {}x{} redirect: {}", 659 | window, x, y, w, h, attributes.override_redirect 660 | ); 661 | xlib::XSendEvent(self.display, window as u64, 0, 0, &mut event); 662 | }; 663 | 664 | xlib::XSync(self.display, 0); 665 | result 666 | } 667 | } 668 | 669 | fn flush(&self) { 670 | unsafe { 671 | xlib::XFlush(self.display); 672 | } 673 | } 674 | 675 | fn event_pending(&self) -> bool { 676 | unsafe { xlib::XPending(self.display) != 0 } 677 | } 678 | 679 | fn get_event(&self) -> WindowSystemEvent { 680 | let mut event = xlib::XEvent { pad: [0; 24] }; 681 | unsafe { 682 | xlib::XNextEvent(self.display, &mut event); 683 | } 684 | 685 | let event_type = event.get_type(); 686 | 687 | match event_type as usize { 688 | CLIENTMESSAGE => { 689 | let event = xlib::XClientMessageEvent::from(event); 690 | let data: [i32; 5] = [ 691 | event.data.get_long(0) as i32, 692 | event.data.get_long(1) as i32, 693 | event.data.get_long(2) as i32, 694 | event.data.get_long(3) as i32, 695 | event.data.get_long(4) as i32, 696 | ]; 697 | WindowSystemEvent::ClientMessageEvent( 698 | event.window as u64, 699 | event.message_type, 700 | event.format, 701 | data, 702 | ) 703 | } 704 | PROPERTYNOTIFY => { 705 | let event = xlib::XPropertyEvent::from(event); 706 | WindowSystemEvent::PropertyMessageEvent( 707 | event.window as u64 == self.root, 708 | event.window as u64, 709 | event.atom, 710 | ) 711 | } 712 | CONFIGUREREQUEST => { 713 | let event = xlib::XConfigureRequestEvent::from(event); 714 | let window_changes = WindowChanges { 715 | x: event.x as u32, 716 | y: event.y as u32, 717 | width: event.width as u32, 718 | height: event.height as u32, 719 | border_width: event.border_width as u32, 720 | sibling: event.above as Window, 721 | stack_mode: event.detail as u32, 722 | }; 723 | 724 | WindowSystemEvent::ConfigurationRequest( 725 | event.window as u64, 726 | window_changes, 727 | event.value_mask as u64, 728 | ) 729 | } 730 | CONFIGURENOTIFY => { 731 | let event = xlib::XConfigureEvent::from(event); 732 | WindowSystemEvent::ConfigurationNotification(event.window as u64) 733 | } 734 | MAPREQUEST => { 735 | let event = xlib::XMapRequestEvent::from(event); 736 | WindowSystemEvent::WindowCreated(event.window as u64) 737 | } 738 | UNMAPNOTIFY => { 739 | let event = xlib::XUnmapEvent::from(event); 740 | WindowSystemEvent::WindowUnmapped(event.window as u64, event.send_event > 0) 741 | } 742 | DESTROYNOTIFY => { 743 | let event = xlib::XDestroyWindowEvent::from(event); 744 | WindowSystemEvent::WindowDestroyed(event.window as u64) 745 | } 746 | ENTERNOTIFY => { 747 | let event = xlib::XEnterWindowEvent::from(event); 748 | if event.detail != 2 { 749 | WindowSystemEvent::Enter(event.window as u64) 750 | } else { 751 | WindowSystemEvent::UnknownEvent 752 | } 753 | } 754 | LEAVENOTIFY => { 755 | let event = xlib::XLeaveWindowEvent::from(event); 756 | if event.detail != 2 { 757 | WindowSystemEvent::Leave(event.window as u64) 758 | } else { 759 | WindowSystemEvent::UnknownEvent 760 | } 761 | } 762 | BUTTONPRESS => { 763 | let event = xlib::XButtonEvent::from(event); 764 | let button = MouseCommand { 765 | button: event.button, 766 | mask: KeyModifiers::from_bits(0xEF & event.state as u32).unwrap(), 767 | }; 768 | WindowSystemEvent::ButtonPressed( 769 | event.window as u64, 770 | event.subwindow as u64, 771 | button, 772 | event.x_root as u32, 773 | event.y_root as u32, 774 | ) 775 | } 776 | BUTTONRELEASE => WindowSystemEvent::ButtonReleased, 777 | KEYPRESS => unsafe { 778 | let event = xlib::XKeyEvent::from(event); 779 | let key = KeyCommand { 780 | key: xlib::XKeycodeToKeysym(self.display, event.keycode as u8, 0) as u64, 781 | mask: KeyModifiers::from_bits(0xEF & event.state as u32).unwrap(), 782 | }; 783 | WindowSystemEvent::KeyPressed(event.window as u64, key) 784 | }, 785 | MOTIONOTIFY => { 786 | let event = xlib::XMotionEvent::from(event); 787 | WindowSystemEvent::MouseMotion(event.x_root as u32, event.y_root as u32) 788 | } 789 | _ => { 790 | debug!("unknown event is {}", event_type); 791 | WindowSystemEvent::UnknownEvent 792 | } 793 | } 794 | } 795 | 796 | fn grab_keys(&self, keys: Vec) { 797 | for &key in keys.iter() { 798 | unsafe { 799 | xlib::XGrabKey( 800 | self.display, 801 | xlib::XKeysymToKeycode(self.display, key.key as u64) as i32, 802 | key.mask.get_mask(), 803 | self.root as u64, 804 | 1, 805 | 1, 806 | 1, 807 | ); 808 | xlib::XGrabKey( 809 | self.display, 810 | xlib::XKeysymToKeycode(self.display, key.key as u64) as i32, 811 | key.mask.get_mask() | 0x10, 812 | self.root as u64, 813 | 1, 814 | 1, 815 | 1, 816 | ); 817 | } 818 | } 819 | } 820 | 821 | fn grab_button(&self, button: MouseCommand) { 822 | unsafe { 823 | xlib::XGrabButton( 824 | self.display, 825 | button.button, 826 | button.mask.get_mask(), 827 | self.root as u64, 828 | 0, 829 | 4, 830 | 1, 831 | 0, 832 | 0, 833 | 0, 834 | ); 835 | } 836 | } 837 | 838 | fn grab_pointer(&self) { 839 | unsafe { 840 | xlib::XGrabPointer(self.display, self.root as u64, 0, 0x48, 1, 1, 0, 0, 0); 841 | } 842 | } 843 | 844 | fn ungrab_pointer(&self) { 845 | unsafe { 846 | xlib::XUngrabPointer(self.display, 0); 847 | } 848 | } 849 | 850 | fn remove_enter_events(&self) { 851 | unsafe { 852 | let mut event = xlib::XEvent { pad: [0; 24] }; 853 | xlib::XSync(self.display, 0); 854 | while xlib::XCheckMaskEvent(self.display, 16, &mut event) != 0 {} 855 | } 856 | } 857 | 858 | fn remove_motion_events(&self) { 859 | unsafe { 860 | let mut event = xlib::XEvent { pad: [0; 24] }; 861 | xlib::XSync(self.display, 0); 862 | while xlib::XCheckMaskEvent(self.display, 0x40, &mut event) != 0 {} 863 | } 864 | } 865 | 866 | fn get_geometry(&self, window: Window) -> Rectangle { 867 | unsafe { 868 | let mut attributes: xlib::XWindowAttributes = mem::MaybeUninit::uninit().assume_init(); 869 | xlib::XGetWindowAttributes(self.display, window as u64, &mut attributes); 870 | 871 | Rectangle( 872 | attributes.x as i32, 873 | attributes.y as i32, 874 | attributes.width as u32, 875 | attributes.height as u32, 876 | ) 877 | } 878 | } 879 | 880 | fn get_size_hints(&self, window: Window) -> SizeHint { 881 | unsafe { 882 | let mut size_hint: xlib::XSizeHints = mem::MaybeUninit::uninit().assume_init(); 883 | let mut tmp: i64 = 0; 884 | xlib::XGetWMNormalHints(self.display, window as u64, &mut size_hint, &mut tmp); 885 | 886 | let min_size = if size_hint.flags & xlib::PMinSize == xlib::PMinSize { 887 | Some((size_hint.min_width as u32, size_hint.min_height as u32)) 888 | } else { 889 | None 890 | }; 891 | 892 | let max_size = if size_hint.flags & xlib::PMaxSize == xlib::PMaxSize { 893 | Some((size_hint.max_width as u32, size_hint.max_height as u32)) 894 | } else { 895 | None 896 | }; 897 | 898 | SizeHint { min_size, max_size } 899 | } 900 | } 901 | 902 | fn restack_windows(&self, w: Vec) { 903 | unsafe { 904 | let mut windows = w.iter().map(|&x| x as u64).collect::>(); 905 | xlib::XRestackWindows( 906 | self.display, 907 | (&mut windows[..]).as_mut_ptr(), 908 | windows.len() as i32, 909 | ); 910 | } 911 | } 912 | 913 | fn close_client(&self, window: Window) { 914 | unsafe { 915 | xlib::XKillClient(self.display, window as u64); 916 | } 917 | } 918 | 919 | fn kill_client(&self, window: Window) { 920 | unsafe { 921 | let wmdelete = self.get_atom("WM_DELETE_WINDOW"); 922 | let wmprotocols = self.get_atom("WM_PROTOCOLS"); 923 | let protocols = self.get_protocols(window); 924 | 925 | debug!( 926 | "supported protocols: {:?} (wmdelete = {:?})", 927 | protocols, wmdelete 928 | ); 929 | 930 | if protocols.iter().any(|x| *x == wmdelete) { 931 | let mut data: xlib::ClientMessageData = mem::MaybeUninit::uninit().assume_init(); 932 | data.set_long(0, (wmdelete >> 32) as i64); 933 | data.set_long(0, (wmdelete & 0xFFFFFFFF) as i64); 934 | let mut event = xlib::XEvent::from(xlib::XClientMessageEvent { 935 | type_: 33, 936 | serial: 0, 937 | send_event: 0, 938 | display: null_mut(), 939 | window: window as u64, 940 | message_type: wmprotocols as u64, 941 | format: 32, 942 | data, 943 | }); 944 | xlib::XSendEvent(self.display, window as u64, 0, 0, &mut event); 945 | } else { 946 | xlib::XKillClient(self.display, window as u64); 947 | } 948 | } 949 | } 950 | 951 | fn update_server_state(&self, manager: &WindowManager) { 952 | let i32_type = self.get_atom("CARDINAL"); 953 | let current_desktop: i32 = manager.workspaces.current.workspace.id as i32; 954 | let number_desktops: i32 = manager.workspaces.workspaces().len() as i32; 955 | let window = manager.workspaces.peek(); 956 | let current_desktop_ptr: *const i32 = ¤t_desktop; 957 | let number_desktops_ptr: *const i32 = &number_desktops; 958 | 959 | let net_current_desktop = self.get_atom("_NET_CURRENT_DESKTOP"); 960 | let net_number_desktops = self.get_atom("_NET_NUMBER_OF_DESKTOPS"); 961 | let net_active_window = self.get_atom("_NET_ACTIVE_WINDOW"); 962 | let window_atom = self.get_atom("WINDOW"); 963 | 964 | unsafe { 965 | xlib::XSelectInput(self.display, self.root as u64, 0x1A0034); 966 | xlib::XChangeProperty( 967 | self.display, 968 | self.root as u64, 969 | net_current_desktop as u64, 970 | i32_type as u64, 971 | 32, 972 | 0, 973 | current_desktop_ptr as *mut u8, 974 | 1, 975 | ); 976 | xlib::XChangeProperty( 977 | self.display, 978 | self.root as u64, 979 | net_number_desktops as u64, 980 | i32_type as u64, 981 | 32, 982 | 0, 983 | number_desktops_ptr as *mut u8, 984 | 1, 985 | ); 986 | 987 | if let Some(win) = window { 988 | let w: u32 = win as u32; 989 | let win_ptr: *const u32 = &w; 990 | xlib::XChangeProperty( 991 | self.display, 992 | self.root as u64, 993 | net_active_window as u64, 994 | window_atom as u64, 995 | 32, 996 | 0, 997 | win_ptr as *mut u8, 998 | 1, 999 | ); 1000 | } 1001 | xlib::XSync(self.display, 0); 1002 | xlib::XSelectInput(self.display, self.root as u64, 0x5A0034); 1003 | } 1004 | } 1005 | 1006 | fn get_pointer(&self, window: Window) -> (u32, u32) { 1007 | let mut tmp_win: u64 = 0; 1008 | let mut x: i32 = 0; 1009 | let mut y: i32 = 0; 1010 | let mut tmp: i32 = 0; 1011 | let mut tmp2: u32 = 0; 1012 | unsafe { 1013 | xlib::XQueryPointer( 1014 | self.display, 1015 | window as u64, 1016 | &mut tmp_win, 1017 | &mut tmp_win, 1018 | &mut x, 1019 | &mut y, 1020 | &mut tmp, 1021 | &mut tmp, 1022 | &mut tmp2, 1023 | ); 1024 | } 1025 | 1026 | (x as u32, y as u32) 1027 | } 1028 | 1029 | fn warp_pointer(&self, window: Window, x: u32, y: u32) { 1030 | unsafe { 1031 | xlib::XWarpPointer( 1032 | self.display, 1033 | 0, 1034 | window as u64, 1035 | 0, 1036 | 0, 1037 | 0, 1038 | 0, 1039 | x as i32, 1040 | y as i32, 1041 | ); 1042 | } 1043 | } 1044 | 1045 | fn overrides_redirect(&self, window: Window) -> bool { 1046 | unsafe { 1047 | let mut attributes: xlib::XWindowAttributes = mem::MaybeUninit::uninit().assume_init(); 1048 | xlib::XGetWindowAttributes(self.display, window as u64, &mut attributes); 1049 | attributes.override_redirect != 0 1050 | } 1051 | } 1052 | 1053 | fn process_message( 1054 | &self, 1055 | window_manager: &WindowManager, 1056 | config: &GeneralConfig, 1057 | window: Window, 1058 | atom: u64, 1059 | ) -> WindowManager { 1060 | if atom as u64 == self.get_atom("_NET_CURRENT_DESKTOP") { 1061 | let prop = self.get_property(atom as u64, window).unwrap(); 1062 | window_manager.view(self, prop[0] as u32, config) 1063 | } else { 1064 | window_manager.clone() 1065 | } 1066 | } 1067 | } 1068 | --------------------------------------------------------------------------------