├── .github ├── dependabot.yml └── workflows │ ├── rust.yml │ └── stale.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── backends ├── mod.rs ├── sway.rs ├── wlroots.rs └── xorg.rs └── main.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "monthly" 12 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | check: 14 | name: Check 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: actions-rs/toolchain@v1 19 | with: 20 | profile: minimal 21 | toolchain: stable 22 | override: true 23 | - uses: actions-rs/cargo@v1 24 | with: 25 | command: check 26 | 27 | test: 28 | name: Test Suite 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v2 32 | - uses: actions-rs/toolchain@v1 33 | with: 34 | profile: minimal 35 | toolchain: stable 36 | override: true 37 | - uses: actions-rs/cargo@v1 38 | with: 39 | command: test 40 | 41 | fmt: 42 | name: Rustfmt 43 | runs-on: ubuntu-latest 44 | steps: 45 | - uses: actions/checkout@v2 46 | - uses: actions-rs/toolchain@v1 47 | with: 48 | profile: minimal 49 | toolchain: stable 50 | override: true 51 | - run: rustup component add rustfmt 52 | - uses: actions-rs/cargo@v1 53 | with: 54 | command: fmt 55 | args: --all -- --check 56 | 57 | clippy: 58 | name: Clippy 59 | runs-on: ubuntu-latest 60 | steps: 61 | - uses: actions/checkout@v2 62 | - uses: actions-rs/toolchain@v1 63 | with: 64 | profile: minimal 65 | toolchain: stable 66 | override: true 67 | - run: rustup component add clippy 68 | - uses: actions-rs/cargo@v1 69 | with: 70 | command: clippy 71 | args: -- -D warnings -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. 2 | # 3 | # You can adjust the behavior by modifying this file. 4 | # For more information, see: 5 | # https://github.com/actions/stale 6 | name: Close inactive issues 7 | 8 | on: 9 | schedule: 10 | - cron: '31 3 * * *' 11 | 12 | jobs: 13 | stale: 14 | 15 | runs-on: ubuntu-latest 16 | permissions: 17 | issues: write 18 | pull-requests: write 19 | 20 | steps: 21 | - uses: actions/stale@v5 22 | with: 23 | repo-token: ${{ secrets.GITHUB_TOKEN }} 24 | days-before-issue-stale: 56 25 | days-before-issue-close: 14 26 | close-issue-message: 'closed for inactivity' 27 | stale-issue-message: 'This issue is scheduled to close soon. This issue is stale because it has been open for 56 days with no activity.' 28 | stale-issue-label: 'closing soon' 29 | operations-per-run: 1000 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "atty" 16 | version = "0.2.14" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 19 | dependencies = [ 20 | "hermit-abi", 21 | "libc", 22 | "winapi", 23 | ] 24 | 25 | [[package]] 26 | name = "autocfg" 27 | version = "1.1.0" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 30 | 31 | [[package]] 32 | name = "bitflags" 33 | version = "1.3.2" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 36 | 37 | [[package]] 38 | name = "bitflags" 39 | version = "2.4.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" 42 | 43 | [[package]] 44 | name = "cc" 45 | version = "1.0.83" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 48 | dependencies = [ 49 | "libc", 50 | ] 51 | 52 | [[package]] 53 | name = "cfg-if" 54 | version = "1.0.0" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 57 | 58 | [[package]] 59 | name = "clap" 60 | version = "3.2.25" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" 63 | dependencies = [ 64 | "atty", 65 | "bitflags 1.3.2", 66 | "clap_lex", 67 | "indexmap", 68 | "strsim", 69 | "termcolor", 70 | "textwrap", 71 | ] 72 | 73 | [[package]] 74 | name = "clap_lex" 75 | version = "0.2.4" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 78 | dependencies = [ 79 | "os_str_bytes", 80 | ] 81 | 82 | [[package]] 83 | name = "dlib" 84 | version = "0.5.2" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" 87 | dependencies = [ 88 | "libloading", 89 | ] 90 | 91 | [[package]] 92 | name = "downcast-rs" 93 | version = "1.2.0" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" 96 | 97 | [[package]] 98 | name = "glob" 99 | version = "0.3.1" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 102 | 103 | [[package]] 104 | name = "hashbrown" 105 | version = "0.12.3" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 108 | 109 | [[package]] 110 | name = "hermit-abi" 111 | version = "0.1.19" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 114 | dependencies = [ 115 | "libc", 116 | ] 117 | 118 | [[package]] 119 | name = "indexmap" 120 | version = "1.9.3" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 123 | dependencies = [ 124 | "autocfg", 125 | "hashbrown", 126 | ] 127 | 128 | [[package]] 129 | name = "itoa" 130 | version = "1.0.9" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 133 | 134 | [[package]] 135 | name = "libc" 136 | version = "0.2.148" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" 139 | 140 | [[package]] 141 | name = "libloading" 142 | version = "0.8.1" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" 145 | dependencies = [ 146 | "cfg-if", 147 | "windows-sys", 148 | ] 149 | 150 | [[package]] 151 | name = "log" 152 | version = "0.4.20" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 155 | 156 | [[package]] 157 | name = "memchr" 158 | version = "2.6.4" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 161 | 162 | [[package]] 163 | name = "memoffset" 164 | version = "0.7.1" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" 167 | dependencies = [ 168 | "autocfg", 169 | ] 170 | 171 | [[package]] 172 | name = "nix" 173 | version = "0.26.4" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" 176 | dependencies = [ 177 | "bitflags 1.3.2", 178 | "cfg-if", 179 | "libc", 180 | "memoffset", 181 | ] 182 | 183 | [[package]] 184 | name = "os_str_bytes" 185 | version = "6.5.1" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" 188 | 189 | [[package]] 190 | name = "pkg-config" 191 | version = "0.3.27" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" 194 | 195 | [[package]] 196 | name = "proc-macro2" 197 | version = "1.0.67" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" 200 | dependencies = [ 201 | "unicode-ident", 202 | ] 203 | 204 | [[package]] 205 | name = "quick-xml" 206 | version = "0.30.0" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" 209 | dependencies = [ 210 | "memchr", 211 | ] 212 | 213 | [[package]] 214 | name = "quote" 215 | version = "1.0.33" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 218 | dependencies = [ 219 | "proc-macro2", 220 | ] 221 | 222 | [[package]] 223 | name = "regex" 224 | version = "1.9.6" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" 227 | dependencies = [ 228 | "aho-corasick", 229 | "memchr", 230 | "regex-automata", 231 | "regex-syntax", 232 | ] 233 | 234 | [[package]] 235 | name = "regex-automata" 236 | version = "0.3.9" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" 239 | dependencies = [ 240 | "aho-corasick", 241 | "memchr", 242 | "regex-syntax", 243 | ] 244 | 245 | [[package]] 246 | name = "regex-syntax" 247 | version = "0.7.5" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" 250 | 251 | [[package]] 252 | name = "rot8" 253 | version = "1.0.0" 254 | dependencies = [ 255 | "clap", 256 | "glob", 257 | "regex", 258 | "serde", 259 | "serde_json", 260 | "wayland-client", 261 | "wayland-protocols-wlr", 262 | ] 263 | 264 | [[package]] 265 | name = "ryu" 266 | version = "1.0.15" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 269 | 270 | [[package]] 271 | name = "scoped-tls" 272 | version = "1.0.1" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 275 | 276 | [[package]] 277 | name = "serde" 278 | version = "1.0.188" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" 281 | dependencies = [ 282 | "serde_derive", 283 | ] 284 | 285 | [[package]] 286 | name = "serde_derive" 287 | version = "1.0.188" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" 290 | dependencies = [ 291 | "proc-macro2", 292 | "quote", 293 | "syn", 294 | ] 295 | 296 | [[package]] 297 | name = "serde_json" 298 | version = "1.0.107" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" 301 | dependencies = [ 302 | "itoa", 303 | "ryu", 304 | "serde", 305 | ] 306 | 307 | [[package]] 308 | name = "smallvec" 309 | version = "1.11.1" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" 312 | 313 | [[package]] 314 | name = "strsim" 315 | version = "0.10.0" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 318 | 319 | [[package]] 320 | name = "syn" 321 | version = "2.0.37" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" 324 | dependencies = [ 325 | "proc-macro2", 326 | "quote", 327 | "unicode-ident", 328 | ] 329 | 330 | [[package]] 331 | name = "termcolor" 332 | version = "1.3.0" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" 335 | dependencies = [ 336 | "winapi-util", 337 | ] 338 | 339 | [[package]] 340 | name = "textwrap" 341 | version = "0.16.0" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" 344 | 345 | [[package]] 346 | name = "unicode-ident" 347 | version = "1.0.12" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 350 | 351 | [[package]] 352 | name = "wayland-backend" 353 | version = "0.3.2" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "19152ddd73f45f024ed4534d9ca2594e0ef252c1847695255dae47f34df9fbe4" 356 | dependencies = [ 357 | "cc", 358 | "downcast-rs", 359 | "nix", 360 | "scoped-tls", 361 | "smallvec", 362 | "wayland-sys", 363 | ] 364 | 365 | [[package]] 366 | name = "wayland-client" 367 | version = "0.31.1" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "1ca7d52347346f5473bf2f56705f360e8440873052e575e55890c4fa57843ed3" 370 | dependencies = [ 371 | "bitflags 2.4.0", 372 | "nix", 373 | "wayland-backend", 374 | "wayland-scanner", 375 | ] 376 | 377 | [[package]] 378 | name = "wayland-protocols" 379 | version = "0.31.0" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "e253d7107ba913923dc253967f35e8561a3c65f914543e46843c88ddd729e21c" 382 | dependencies = [ 383 | "bitflags 2.4.0", 384 | "wayland-backend", 385 | "wayland-client", 386 | "wayland-scanner", 387 | ] 388 | 389 | [[package]] 390 | name = "wayland-protocols-wlr" 391 | version = "0.2.0" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" 394 | dependencies = [ 395 | "bitflags 2.4.0", 396 | "wayland-backend", 397 | "wayland-client", 398 | "wayland-protocols", 399 | "wayland-scanner", 400 | ] 401 | 402 | [[package]] 403 | name = "wayland-scanner" 404 | version = "0.31.0" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "fb8e28403665c9f9513202b7e1ed71ec56fde5c107816843fb14057910b2c09c" 407 | dependencies = [ 408 | "proc-macro2", 409 | "quick-xml", 410 | "quote", 411 | ] 412 | 413 | [[package]] 414 | name = "wayland-sys" 415 | version = "0.31.1" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af" 418 | dependencies = [ 419 | "dlib", 420 | "log", 421 | "pkg-config", 422 | ] 423 | 424 | [[package]] 425 | name = "winapi" 426 | version = "0.3.9" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 429 | dependencies = [ 430 | "winapi-i686-pc-windows-gnu", 431 | "winapi-x86_64-pc-windows-gnu", 432 | ] 433 | 434 | [[package]] 435 | name = "winapi-i686-pc-windows-gnu" 436 | version = "0.4.0" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 439 | 440 | [[package]] 441 | name = "winapi-util" 442 | version = "0.1.6" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" 445 | dependencies = [ 446 | "winapi", 447 | ] 448 | 449 | [[package]] 450 | name = "winapi-x86_64-pc-windows-gnu" 451 | version = "0.4.0" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 454 | 455 | [[package]] 456 | name = "windows-sys" 457 | version = "0.48.0" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 460 | dependencies = [ 461 | "windows-targets", 462 | ] 463 | 464 | [[package]] 465 | name = "windows-targets" 466 | version = "0.48.5" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 469 | dependencies = [ 470 | "windows_aarch64_gnullvm", 471 | "windows_aarch64_msvc", 472 | "windows_i686_gnu", 473 | "windows_i686_msvc", 474 | "windows_x86_64_gnu", 475 | "windows_x86_64_gnullvm", 476 | "windows_x86_64_msvc", 477 | ] 478 | 479 | [[package]] 480 | name = "windows_aarch64_gnullvm" 481 | version = "0.48.5" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 484 | 485 | [[package]] 486 | name = "windows_aarch64_msvc" 487 | version = "0.48.5" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 490 | 491 | [[package]] 492 | name = "windows_i686_gnu" 493 | version = "0.48.5" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 496 | 497 | [[package]] 498 | name = "windows_i686_msvc" 499 | version = "0.48.5" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 502 | 503 | [[package]] 504 | name = "windows_x86_64_gnu" 505 | version = "0.48.5" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 508 | 509 | [[package]] 510 | name = "windows_x86_64_gnullvm" 511 | version = "0.48.5" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 514 | 515 | [[package]] 516 | name = "windows_x86_64_msvc" 517 | version = "0.48.5" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 520 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rot8" 3 | version = "1.0.0" 4 | authors = ["efernau ", "deadly_platypus ", "nicola-sorace"] 5 | license = "MIT" 6 | description = "automatic display rotation using built-in accelerometer" 7 | homepage = "https://github.com/efernau/rot8" 8 | documentation = "https://github.com/efernau/rot8" 9 | repository = "https://github.com/efernau/rot8" 10 | keywords = ["sway", "x11", "display", "rotation"] 11 | edition = "2018" 12 | 13 | [dependencies] 14 | clap = "3.2" 15 | glob = "0.3" 16 | regex = "1" 17 | serde = { version = "1.0", features = ["derive"] } 18 | serde_json = "1.0" 19 | wayland-client = "0.31.0" 20 | wayland-protocols-wlr = { version = "0.2.0", features = ["client"] } 21 | 22 | [profile.release] 23 | strip = true 24 | lto = true 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Esra Fernau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rot8 2 | 3 | [![Rust](https://github.com/efernau/rot8/actions/workflows/rust.yml/badge.svg?branch=master)](https://github.com/efernau/rot8/actions/workflows/rust.yml) 4 | 5 | ## automatic display rotation using built-in accelerometer 6 | 7 | Automatically rotate modern Linux desktop screen and input devices. Handy for 8 | convertible touchscreen notebooks like HP Spectre x360, Lenovo IdeaPad Flex 9 | or Linux phone like Pinephone. 10 | 11 | Compatible with [X11](https://www.x.org/wiki/Releases/7.7/) and Wayland 12 | compositors which support the `wlr_output_management_v1` protocol (Like 13 | [sway](http://swaywm.org/) and [hyprland](https://hyprland.org/)). 14 | 15 | ### installation 16 | 17 | #### packages 18 | 19 | Arch User Repository: [rot8-git](https://aur.archlinux.org/packages/rot8-git/) 20 | 21 | GNU Guix Package: [rot8](https://packages.guix.gnu.org/packages/rot8/) 22 | 23 | Nixpkgs: [rot8](https://search.nixos.org/packages?show=rot8&type=packages&query=rot8) 24 | 25 | Void Package: [rot8](https://github.com/void-linux/void-packages/tree/master/srcpkgs/rot8) 26 | 27 | 28 | 29 | #### manually build from source 30 | 31 | Rust language and the cargo package manager are required to build the binary. 32 | 33 | ``` 34 | $ git clone https://github.com/efernau/rot8 35 | $ cd rot8 && cargo build --release 36 | $ cp target/release/rot8 /usr/bin/rot8 37 | ``` 38 | 39 | or 40 | 41 | ``` 42 | $ cargo install rot8 43 | 44 | ``` 45 | 46 | ### usage 47 | 48 | Map your inputs to the output device as necessary. e.g. for sway: 49 | 50 | ``` 51 | 52 | $ swaymsg input map_to_output 53 | 54 | ``` 55 | 56 | Call rot8 from your compositor configuration. e.g. for sway: 57 | 58 | ``` 59 | 60 | exec rot8 61 | 62 | ``` 63 | 64 | For X11 set Touchscreen Device 65 | 66 | ``` 67 | 68 | rot8 --touchscreen 69 | 70 | ``` 71 | 72 | This will start the daemon running, continuously checking for rotations. 73 | 74 | There are the following args (defaults): 75 | 76 | ``` 77 | 78 | --sleep // Set millis to sleep between rotation checks (500) 79 | --display // Set Display Device (eDP-1) 80 | --touchscreen // Set Touchscreen Device X11, allows multiple devices (ELAN0732:00 04F3:22E1) 81 | --keyboard // Set keyboard to deactivate upon rotation, for Sway only 82 | --threshold // Set a rotation threshold between 0 and 1, higher is more sensitive (0.5) 83 | --normalization-factor // Set factor for sensor value normalization (1e6) 84 | --invert-x // Invert readings from the HW x axis 85 | --invert-y // Invert readings from the HW y axis 86 | --invert-z // Invert readings from the HW z axis 87 | --invert-xy // Map HW axes to internal x and y respectively (xy, yx, zy, yz, xz, zx) 88 | --oneshot // Updates the screen rotation just once instead of continuously 89 | --beforehooks // Execute a custom script before rotation 90 | --hooks // Execute a custom script after the rotation has finished 91 | --version // Returns the rot8 version 92 | 93 | ``` 94 | 95 | You may need to play with the normalization factor (try multiples of 10) and the axis inversions to get the accelerometer readings to calculate right. 96 | -------------------------------------------------------------------------------- /src/backends/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::Orientation; 2 | use wayland_client::protocol::wl_output::Transform; 3 | 4 | pub trait DisplayManager { 5 | /// Change the orientation of the target display. 6 | fn change_rotation_state(&mut self, new_state: &Orientation); 7 | 8 | /// Get the current transformation of the target display. 9 | fn get_rotation_state(&mut self) -> Result; 10 | } 11 | 12 | pub mod sway; 13 | pub mod wlroots; 14 | pub mod xorg; 15 | -------------------------------------------------------------------------------- /src/backends/sway.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | 3 | use serde_json::Value; 4 | use wayland_client::protocol::wl_output::Transform; 5 | 6 | use crate::Orientation; 7 | 8 | use super::{wlroots::WaylandBackend, DisplayManager}; 9 | 10 | pub struct SwayBackend { 11 | wayland_backend: WaylandBackend, 12 | manage_keyboard: bool, 13 | } 14 | 15 | impl SwayBackend { 16 | pub fn new(wayland_backend: WaylandBackend, manage_keyboard: bool) -> Self { 17 | SwayBackend { 18 | wayland_backend, 19 | manage_keyboard, 20 | } 21 | } 22 | 23 | fn get_keyboards() -> Vec { 24 | let raw_inputs = String::from_utf8( 25 | Command::new("swaymsg") 26 | .arg("-t") 27 | .arg("get_inputs") 28 | .arg("--raw") 29 | .output() 30 | .expect("Swaymsg get inputs command failed") 31 | .stdout, 32 | ) 33 | .unwrap(); 34 | 35 | let mut keyboards = vec![]; 36 | let deserialized: Vec = 37 | serde_json::from_str(&raw_inputs).expect("Unable to deserialize swaymsg JSON output"); 38 | for output in deserialized { 39 | let input_type = output["type"].as_str().unwrap(); 40 | if input_type == "keyboard" { 41 | keyboards.push(output["identifier"].to_string()); 42 | } 43 | } 44 | 45 | keyboards 46 | } 47 | } 48 | 49 | impl DisplayManager for SwayBackend { 50 | fn change_rotation_state(&mut self, new_state: &Orientation) { 51 | self.wayland_backend.change_rotation_state(new_state); 52 | 53 | if !self.manage_keyboard { 54 | return; 55 | } 56 | 57 | let keyboard_state = if new_state.wayland_state == Transform::Normal { 58 | "enabled" 59 | } else { 60 | "disabled" 61 | }; 62 | for keyboard in &SwayBackend::get_keyboards() { 63 | Command::new("swaymsg") 64 | .arg("input") 65 | .arg(keyboard) 66 | .arg("events") 67 | .arg(keyboard_state) 68 | .spawn() 69 | .expect("Swaymsg keyboard command failed to start") 70 | .wait() 71 | .expect("Swaymsg keyboard command wait failed"); 72 | } 73 | } 74 | 75 | fn get_rotation_state(&mut self) -> Result { 76 | self.wayland_backend.get_rotation_state() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/backends/wlroots.rs: -------------------------------------------------------------------------------- 1 | use wayland_client::{ 2 | event_created_child, 3 | protocol::{wl_output::Transform, wl_registry}, 4 | Connection, Dispatch, EventQueue, QueueHandle, 5 | }; 6 | use wayland_protocols_wlr::output_management::v1::client::{ 7 | zwlr_output_configuration_head_v1::{self, ZwlrOutputConfigurationHeadV1}, 8 | zwlr_output_configuration_v1::{self, ZwlrOutputConfigurationV1}, 9 | zwlr_output_head_v1::{self, ZwlrOutputHeadV1}, 10 | zwlr_output_manager_v1::{self, ZwlrOutputManagerV1}, 11 | zwlr_output_mode_v1::{self, ZwlrOutputModeV1}, 12 | }; 13 | 14 | use crate::Orientation; 15 | 16 | use super::DisplayManager; 17 | 18 | pub struct WaylandBackend { 19 | state: AppData, 20 | event_queue: EventQueue, 21 | } 22 | 23 | impl WaylandBackend { 24 | pub fn new(target_display: &str) -> Result { 25 | let conn = wayland_client::Connection::connect_to_env() 26 | .map_err(|_| "Could not connect to wayland socket.")?; 27 | let wl_display = conn.display(); 28 | let mut event_queue = conn.new_event_queue(); 29 | let _registry = wl_display.get_registry(&event_queue.handle(), ()); 30 | let mut state = AppData::new(&mut event_queue, target_display.to_string()); 31 | // Roundtrip twice to sync the outputs 32 | for _ in 0..2 { 33 | event_queue 34 | .roundtrip(&mut state) 35 | .map_err(|e| format!("Failed to communicate with the wayland socket: {}", e))?; 36 | } 37 | 38 | state 39 | .output_manager 40 | .as_ref() 41 | .ok_or("Compositor does not support wlr_output_management_v1.")?; 42 | 43 | Ok(WaylandBackend { state, event_queue }) 44 | } 45 | 46 | /// Receive (and send) all buffered messages across the wayland socket. 47 | fn read_socket(&mut self) { 48 | self.event_queue 49 | .roundtrip(&mut self.state) 50 | .expect("Failed to read display changes."); 51 | } 52 | 53 | /// Send all buffered messages across the wayland socket. 54 | /// Slightly cheaper than `read_socket`. 55 | fn write_socket(&self) { 56 | self.event_queue 57 | .flush() 58 | .expect("Failed to apply display changes."); 59 | } 60 | } 61 | 62 | impl DisplayManager for WaylandBackend { 63 | fn change_rotation_state(&mut self, new_state: &Orientation) { 64 | self.read_socket(); 65 | self.state.update_configuration(new_state.wayland_state); 66 | self.write_socket(); 67 | } 68 | 69 | fn get_rotation_state(&mut self) -> Result { 70 | self.read_socket(); 71 | self.state 72 | .current_transform 73 | .ok_or("Failed to get current display rotation".into()) 74 | } 75 | } 76 | 77 | struct AppData { 78 | target_display_name: String, 79 | target_head: Option, 80 | output_manager: Option, 81 | current_config_serial: Option, 82 | current_transform: Option, 83 | queue_handle: QueueHandle, 84 | } 85 | 86 | /// Public interface 87 | 88 | impl AppData { 89 | pub fn new(event_queue: &mut EventQueue, target_display_name: String) -> Self { 90 | AppData { 91 | target_display_name, 92 | queue_handle: event_queue.handle(), 93 | target_head: None, 94 | output_manager: None, 95 | current_config_serial: None, 96 | current_transform: None, 97 | } 98 | } 99 | 100 | pub fn update_configuration(&mut self, new_transform: Transform) { 101 | if let (Some(output_manager), Some(serial), Some(head)) = ( 102 | &self.output_manager, 103 | self.current_config_serial, 104 | &self.target_head, 105 | ) { 106 | let configuration = output_manager.create_configuration(serial, &self.queue_handle, ()); 107 | let head_config = configuration.enable_head(head, &self.queue_handle, ()); 108 | head_config.set_transform(new_transform); 109 | configuration.apply(); 110 | } 111 | } 112 | } 113 | 114 | /// Event handlers 115 | 116 | impl Dispatch for AppData { 117 | fn event( 118 | state: &mut Self, 119 | registry: &wl_registry::WlRegistry, 120 | event: wl_registry::Event, 121 | _: &(), 122 | _: &Connection, 123 | qh: &QueueHandle, 124 | ) { 125 | if let wl_registry::Event::Global { 126 | name, 127 | interface, 128 | version, 129 | } = event 130 | { 131 | // println!("[{}] {} v{}", name, interface, version); 132 | if interface == "zwlr_output_manager_v1" { 133 | state.output_manager = 134 | Some(registry.bind::(name, version, qh, ())); 135 | } 136 | } 137 | } 138 | } 139 | 140 | impl Dispatch for AppData { 141 | fn event( 142 | state: &mut Self, 143 | _: &ZwlrOutputManagerV1, 144 | event: zwlr_output_manager_v1::Event, 145 | _: &(), 146 | _: &Connection, 147 | _: &QueueHandle, 148 | ) { 149 | if let zwlr_output_manager_v1::Event::Done { serial } = event { 150 | // println!("Current config: {}", serial); 151 | state.current_config_serial = Some(serial); 152 | } 153 | } 154 | 155 | event_created_child!(AppData, ZwlrOutputHeadV1, [ 156 | zwlr_output_manager_v1::EVT_HEAD_OPCODE => (ZwlrOutputHeadV1, ()), 157 | ]); 158 | } 159 | 160 | impl Dispatch for AppData { 161 | fn event( 162 | state: &mut Self, 163 | head: &ZwlrOutputHeadV1, 164 | event: zwlr_output_head_v1::Event, 165 | _: &(), 166 | _: &Connection, 167 | _: &QueueHandle, 168 | ) { 169 | match event { 170 | zwlr_output_head_v1::Event::Name { name } => { 171 | if name == state.target_display_name { 172 | // println!("Found target display: {}", name); 173 | state.target_head = Some(head.clone()); 174 | } 175 | } 176 | zwlr_output_head_v1::Event::Transform { transform } => { 177 | state.current_transform = Some(transform.into_result().unwrap()) 178 | } 179 | _ => {} 180 | } 181 | } 182 | 183 | event_created_child!(AppData, ZwlrOutputModeV1, [ 184 | zwlr_output_head_v1::EVT_CURRENT_MODE_OPCODE => (ZwlrOutputModeV1, ()), 185 | zwlr_output_head_v1::EVT_MODE_OPCODE => (ZwlrOutputModeV1, ()), 186 | ]); 187 | } 188 | 189 | impl Dispatch for AppData { 190 | fn event( 191 | _state: &mut Self, 192 | _: &ZwlrOutputModeV1, 193 | _: zwlr_output_mode_v1::Event, 194 | _: &(), 195 | _: &Connection, 196 | _: &QueueHandle, 197 | ) { 198 | } 199 | } 200 | 201 | impl Dispatch for AppData { 202 | fn event( 203 | _state: &mut Self, 204 | config: &ZwlrOutputConfigurationV1, 205 | event: zwlr_output_configuration_v1::Event, 206 | _: &(), 207 | _: &Connection, 208 | _: &QueueHandle, 209 | ) { 210 | match event { 211 | zwlr_output_configuration_v1::Event::Succeeded => { 212 | // println!("Config applied successfully."); 213 | config.destroy(); 214 | } 215 | zwlr_output_configuration_v1::Event::Failed => { 216 | // println!("Failed to apply new config."); 217 | config.destroy(); 218 | } 219 | zwlr_output_configuration_v1::Event::Cancelled => { 220 | // println!("Config application cancelled."); 221 | config.destroy(); 222 | } 223 | _ => {} 224 | } 225 | } 226 | } 227 | 228 | impl Dispatch for AppData { 229 | fn event( 230 | _state: &mut Self, 231 | _: &ZwlrOutputConfigurationHeadV1, 232 | _: zwlr_output_configuration_head_v1::Event, 233 | _: &(), 234 | _: &Connection, 235 | _: &QueueHandle, 236 | ) { 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/backends/xorg.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | 3 | use wayland_client::protocol::wl_output::Transform; 4 | 5 | use super::DisplayManager; 6 | use crate::Orientation; 7 | 8 | pub struct XorgBackend { 9 | touchscreens: Vec, 10 | target_display: String, 11 | } 12 | 13 | impl XorgBackend { 14 | pub fn new(display: &str, touchscreens: Vec) -> Self { 15 | XorgBackend { 16 | target_display: display.into(), 17 | touchscreens, 18 | } 19 | } 20 | } 21 | 22 | impl DisplayManager for XorgBackend { 23 | fn change_rotation_state(&mut self, new_state: &Orientation) { 24 | Command::new("xrandr") 25 | .arg("-o") 26 | .arg(new_state.x_state) 27 | .spawn() 28 | .expect("Xrandr rotate command failed to start") 29 | .wait() 30 | .expect("Xrandr rotate command wait failed"); 31 | 32 | // Support Touchscreen and Styli on some 2-in-1 devices 33 | for touchscreen in &self.touchscreens { 34 | Command::new("xinput") 35 | .arg("set-prop") 36 | .arg(touchscreen) 37 | .arg("Coordinate Transformation Matrix") 38 | .args(new_state.matrix) 39 | .spawn() 40 | .expect("Xinput rotate command failed to start") 41 | .wait() 42 | .expect("Xinput rotate command wait failed"); 43 | } 44 | } 45 | 46 | fn get_rotation_state(&mut self) -> Result { 47 | let raw_rotation_state = String::from_utf8( 48 | Command::new("xrandr") 49 | .output() 50 | .expect("Xrandr get outputs command failed to start") 51 | .stdout, 52 | ) 53 | .unwrap(); 54 | let xrandr_output_pattern = regex::Regex::new(format!( 55 | r"^{} connected .+? .+? (normal |inverted |left |right )?\(normal left inverted right x axis y axis\) .+$", 56 | regex::escape(&self.target_display), 57 | ).as_str()).unwrap(); 58 | for xrandr_output_line in raw_rotation_state.split('\n') { 59 | if !xrandr_output_pattern.is_match(xrandr_output_line) { 60 | continue; 61 | } 62 | 63 | let xrandr_output_captures = 64 | xrandr_output_pattern.captures(xrandr_output_line).unwrap(); 65 | if let Some(transform) = xrandr_output_captures.get(1) { 66 | return Ok(match transform.as_str() { 67 | "inverted" => Transform::_180, 68 | "right" => Transform::_270, 69 | "left" => Transform::_90, 70 | _ => Transform::Normal, 71 | }); 72 | } else { 73 | return Ok(Transform::Normal); 74 | } 75 | } 76 | 77 | Err(format!( 78 | "Unable to determine rotation state: display {} not found in xrandr output", 79 | self.target_display 80 | )) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate clap; 2 | extern crate glob; 3 | extern crate regex; 4 | 5 | use std::fs; 6 | use std::path::Path; 7 | use std::process::Command; 8 | use std::thread; 9 | use std::time::Duration; 10 | 11 | use clap::{App, Arg}; 12 | use glob::glob; 13 | use wayland_client::protocol::wl_output::Transform; 14 | 15 | mod backends; 16 | use backends::{sway::SwayBackend, wlroots::WaylandBackend, xorg::XorgBackend, DisplayManager}; 17 | 18 | const ROT8_VERSION: &str = env!("CARGO_PKG_VERSION"); 19 | 20 | pub struct Orientation { 21 | vector: (f32, f32), 22 | wayland_state: Transform, 23 | x_state: &'static str, 24 | matrix: [&'static str; 9], 25 | } 26 | 27 | fn main() -> Result<(), String> { 28 | let mut path_x: String = "".to_string(); 29 | let mut path_y: String = "".to_string(); 30 | let mut path_z: String = "".to_string(); 31 | 32 | let args = vec![ 33 | Arg::with_name("oneshot") 34 | .long("oneshot") 35 | .short('O') 36 | .help("Instead of running continuously, just check the accelerometer and perform screen rotation if necessary once") 37 | .takes_value(false), 38 | Arg::with_name("sleep") 39 | .default_value("500") 40 | .long("sleep") 41 | .short('s') 42 | .value_name("SLEEP") 43 | .help("Set sleep millis") 44 | .takes_value(true), 45 | Arg::with_name("display") 46 | .default_value("eDP-1") 47 | .long("display") 48 | .short('d') 49 | .value_name("DISPLAY") 50 | .help("Set Display Device") 51 | .takes_value(true), 52 | Arg::with_name("touchscreen") 53 | .default_value("ELAN0732:00 04F3:22E1") 54 | .long("touchscreen") 55 | .short('i') 56 | .value_name("TOUCHSCREEN") 57 | .help("Set Touchscreen input Device (X11 only)") 58 | .min_values(1) 59 | .takes_value(true), 60 | Arg::with_name("threshold") 61 | .default_value("0.5") 62 | .long("threshold") 63 | .short('t') 64 | .value_name("THRESHOLD") 65 | .help("Set a rotation threshold between 0 and 1") 66 | .takes_value(true), 67 | Arg::with_name("invert-x") 68 | .long("invert-x") 69 | .short('X') 70 | .help("Invert readings from the HW x axis") 71 | .takes_value(false), 72 | Arg::with_name("invert-y") 73 | .long("invert-y") 74 | .short('Y') 75 | .help("Invert readings from the HW y axis") 76 | .takes_value(false), 77 | Arg::with_name("invert-z") 78 | .long("invert-z") 79 | .short('Z') 80 | .help("Invert readings from the HW z axis") 81 | .takes_value(false), 82 | Arg::with_name("invert-xy") 83 | .default_value("xy") 84 | .long("invert-xy") 85 | .value_name("XY") 86 | .help("Map hardware accelerometer axes to internal x and y respectively") 87 | .possible_values(["xy", "yx", "zy", "yz", "xz", "zx"]) 88 | .takes_value(true), 89 | Arg::with_name("normalization-factor") 90 | .long("normalization-factor") 91 | .short('n') 92 | .value_name("NORMALIZATION_FACTOR") 93 | .help("Set factor for sensor value normalization manually. By default this factor is calculated dynamically using the sensor's data.") 94 | .takes_value(true), 95 | Arg::with_name("keyboard") 96 | .long("disable-keyboard") 97 | .short('k') 98 | .help("Disable keyboard for tablet modes (Sway only)") 99 | .takes_value(false), 100 | Arg::with_name("version") 101 | .long("version") 102 | .short('V') 103 | .value_name("VERSION") 104 | .help("Displays rot8 version") 105 | .takes_value(false), 106 | Arg::with_name("beforehooks") 107 | .long("beforehooks") 108 | .short('b') 109 | .value_name("BEFOREHOOKS") 110 | .help("Run hook(s) before screen rotation. Passes $ORIENTATION and $PREV_ORIENTATION to hooks. Comma-seperated.") 111 | .takes_value(true) 112 | .use_value_delimiter(true) 113 | .require_value_delimiter(true), 114 | Arg::with_name("hooks") 115 | .long("hooks") 116 | .short('h') 117 | .value_name("HOOKS") 118 | .help("Run hook(s) after screen rotation. Passes $ORIENTATION and $PREV_ORIENTATION to hooks. Comma-seperated.") 119 | .takes_value(true) 120 | .use_value_delimiter(true) 121 | .require_value_delimiter(true) 122 | ]; 123 | 124 | let cmd_lines = App::new("rot8").version(ROT8_VERSION).args(&args); 125 | 126 | let matches = cmd_lines.get_matches(); 127 | 128 | if matches.is_present("version") { 129 | println!("{}", ROT8_VERSION); 130 | return Ok(()); 131 | } 132 | 133 | let oneshot = matches.is_present("oneshot"); 134 | let sleep = matches.value_of("sleep").unwrap_or("default.conf"); 135 | let display = matches.value_of("display").unwrap_or("default.conf"); 136 | let touchscreens: Vec = matches.get_many("touchscreen").unwrap().cloned().collect(); 137 | let hooks: Vec<&str> = matches.values_of("hooks").unwrap_or_default().collect(); 138 | let beforehooks: Vec<&str> = matches 139 | .values_of("beforehooks") 140 | .unwrap_or_default() 141 | .collect(); 142 | let disable_keyboard = matches.is_present("keyboard"); 143 | let threshold = matches.value_of("threshold").unwrap_or("default.conf"); 144 | 145 | let flip_x = matches.is_present("invert-x"); 146 | let flip_y = matches.is_present("invert-y"); 147 | let flip_z = matches.is_present("invert-z"); 148 | let mut xy = matches.value_of("invert-xy").unwrap_or("xy").chars(); 149 | let x_source = xy.next().unwrap(); 150 | let y_source = xy.next().unwrap(); 151 | 152 | let mut normalization_factor: Option = None; 153 | if let Some(v) = matches.value_of("normalization-factor") { 154 | match v.parse::() { 155 | Ok(p) => normalization_factor = Some(p), 156 | Err(_) => { 157 | return Err( 158 | "The argument 'normalization-factor' is no valid float literal".to_string(), 159 | ); 160 | } 161 | } 162 | } 163 | 164 | let mut backend: Box = match WaylandBackend::new(display) { 165 | Ok(wayland_backend) => { 166 | if process_exists("sway") { 167 | Box::new(SwayBackend::new(wayland_backend, disable_keyboard)) 168 | } else { 169 | Box::new(wayland_backend) 170 | } 171 | } 172 | Err(e) => { 173 | if process_exists("Xorg") || process_exists("X") { 174 | Box::new(XorgBackend::new(display, touchscreens)) 175 | } else { 176 | return Err(format!( 177 | "Unable to find supported Xorg process or wayland compositor: {}.", 178 | e 179 | )); 180 | } 181 | } 182 | }; 183 | 184 | for entry in glob("/sys/bus/iio/devices/iio:device*/in_accel_*_raw").unwrap() { 185 | match entry { 186 | Ok(path) => { 187 | if path.to_str().unwrap().contains("x_raw") { 188 | path_x = path.to_str().unwrap().to_owned(); 189 | } else if path.to_str().unwrap().contains("y_raw") { 190 | path_y = path.to_str().unwrap().to_owned(); 191 | } else if path.to_str().unwrap().contains("z_raw") { 192 | path_z = path.to_str().unwrap().to_owned(); 193 | } 194 | } 195 | Err(e) => println!("{:?}", e), 196 | } 197 | } 198 | 199 | if !Path::new(&path_x).exists() && !Path::new(&path_y).exists() && !Path::new(&path_z).exists() 200 | { 201 | Err("Unknown Accelerometer Device".to_string()) 202 | } else { 203 | let orientations = [ 204 | Orientation { 205 | vector: (0.0, -1.0), 206 | wayland_state: Transform::Normal, 207 | x_state: "normal", 208 | matrix: ["1", "0", "0", "0", "1", "0", "0", "0", "1"], 209 | }, 210 | Orientation { 211 | vector: (0.0, 1.0), 212 | wayland_state: Transform::_180, 213 | x_state: "inverted", 214 | matrix: ["-1", "0", "1", "0", "-1", "1", "0", "0", "1"], 215 | }, 216 | Orientation { 217 | vector: (-1.0, 0.0), 218 | wayland_state: Transform::_270, 219 | x_state: "right", 220 | matrix: ["0", "1", "0", "-1", "0", "1", "0", "0", "1"], 221 | }, 222 | Orientation { 223 | vector: (1.0, 0.0), 224 | wayland_state: Transform::_90, 225 | x_state: "left", 226 | matrix: ["0", "-1", "1", "1", "0", "0", "0", "0", "1"], 227 | }, 228 | ]; 229 | 230 | let mut old_state = backend.get_rotation_state()?; 231 | let mut current_orient: &Orientation = &orientations[0]; 232 | 233 | loop { 234 | let x_raw = fs::read_to_string(path_x.as_str()).unwrap(); 235 | let y_raw = fs::read_to_string(path_y.as_str()).unwrap(); 236 | let z_raw = fs::read_to_string(path_z.as_str()).unwrap(); 237 | let x_clean = x_raw.trim_end_matches('\n').parse::().unwrap_or(0.); 238 | let y_clean = y_raw.trim_end_matches('\n').parse::().unwrap_or(0.); 239 | let z_clean = z_raw.trim_end_matches('\n').parse::().unwrap_or(0.); 240 | 241 | // Normalize vectors 242 | let norm_factor = normalization_factor.unwrap_or_else(|| { 243 | f32::sqrt(x_clean * x_clean + y_clean * y_clean + z_clean * z_clean) 244 | }); 245 | 246 | let mut mut_x: f32 = x_clean / norm_factor; 247 | let mut mut_y: f32 = y_clean / norm_factor; 248 | let mut mut_z: f32 = z_clean / norm_factor; 249 | 250 | // Apply inversions 251 | if flip_x { 252 | mut_x = -mut_x; 253 | } 254 | if flip_y { 255 | mut_y = -mut_y; 256 | } 257 | if flip_z { 258 | mut_z = -mut_z; 259 | } 260 | // Switch axes as requested 261 | let x = match x_source { 262 | 'y' => mut_y, 263 | 'z' => mut_z, 264 | _ => mut_x, 265 | }; 266 | let y = match y_source { 267 | 'x' => mut_x, 268 | 'z' => mut_z, 269 | _ => mut_y, 270 | }; 271 | 272 | for orient in orientations.iter() { 273 | let d = (x - orient.vector.0).powf(2.0) + (y - orient.vector.1).powf(2.0); 274 | if d < threshold.parse::().unwrap_or(0.5) { 275 | current_orient = orient; 276 | break; 277 | } 278 | } 279 | 280 | if current_orient.wayland_state != old_state { 281 | let old_env = transform_to_env(&old_state); 282 | let new_env = transform_to_env(¤t_orient.wayland_state); 283 | for bhook in beforehooks.iter() { 284 | Command::new("bash") 285 | .arg("-c") 286 | .arg(bhook) 287 | .env("ORIENTATION", new_env) 288 | .env("PREV_ORIENTATION", old_env) 289 | .spawn() 290 | .expect("A hook failed to start.") 291 | .wait() 292 | .expect("Waiting for a hook failed."); 293 | } 294 | 295 | backend.change_rotation_state(current_orient); 296 | 297 | for hook in hooks.iter() { 298 | Command::new("bash") 299 | .arg("-c") 300 | .arg(hook) 301 | .env("ORIENTATION", new_env) 302 | .env("PREV_ORIENTATION", old_env) 303 | .spawn() 304 | .expect("A hook failed to start.") 305 | .wait() 306 | .expect("Waiting for a hook failed."); 307 | } 308 | 309 | old_state = current_orient.wayland_state; 310 | } 311 | 312 | if oneshot { 313 | return Ok(()); 314 | } 315 | 316 | thread::sleep(Duration::from_millis(sleep.parse::().unwrap_or(0))); 317 | } 318 | } 319 | } 320 | 321 | fn process_exists(proc_name: &str) -> bool { 322 | !String::from_utf8( 323 | Command::new("pidof") 324 | .arg(proc_name) 325 | .output() 326 | .unwrap() 327 | .stdout, 328 | ) 329 | .unwrap() 330 | .is_empty() 331 | } 332 | 333 | fn transform_to_env(transform: &Transform) -> &str { 334 | match transform { 335 | Transform::Normal => "normal", 336 | Transform::_90 => "270", 337 | Transform::_180 => "inverted", 338 | Transform::_270 => "90", 339 | _ => "normal", 340 | } 341 | } 342 | --------------------------------------------------------------------------------