From 8e80c0a69cd847822ccbaa69d0b01eb11ffab0b8 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Wed, 11 Jan 2023 00:28:54 +0100 Subject: [PATCH 01/57] Initial commit --- .gitignore | 1 + Cargo.lock | 1161 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 32 + src/buffer.rs | 321 ++++++++++ src/color.rs | 71 +++ src/draw.rs | 339 +++++++++++ src/fonts.rs | 128 ++++ src/keyboard/keysyms.rs | 19 + src/keyboard/mod.rs | 136 +++++ src/keyboard/state.rs | 158 +++++ src/main.rs | 605 +++++++++++++++++++ src/utils/desktop.rs | 112 ++++ src/utils/mod.rs | 1 + src/widgets/bar_widget.rs | 98 ++++ src/widgets/battery.rs | 144 +++++ src/widgets/clock.rs | 76 +++ src/widgets/date.rs | 72 +++ src/widgets/launcher.rs | 566 ++++++++++++++++++ src/widgets/line.rs | 39 ++ src/widgets/mod.rs | 17 + src/widgets/widget.rs | 166 ++++++ 21 files changed, 4262 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/buffer.rs create mode 100644 src/color.rs create mode 100644 src/draw.rs create mode 100644 src/fonts.rs create mode 100644 src/keyboard/keysyms.rs create mode 100644 src/keyboard/mod.rs create mode 100644 src/keyboard/state.rs create mode 100644 src/main.rs create mode 100644 src/utils/desktop.rs create mode 100644 src/utils/mod.rs create mode 100644 src/widgets/bar_widget.rs create mode 100644 src/widgets/battery.rs create mode 100644 src/widgets/clock.rs create mode 100644 src/widgets/date.rs create mode 100644 src/widgets/launcher.rs create mode 100644 src/widgets/line.rs create mode 100644 src/widgets/mod.rs create mode 100644 src/widgets/widget.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..19c8739 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1161 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330223a1aecc308757b9926e9391c9b47f8ef2dbd8aea9df88312aea18c5e8d6" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + +[[package]] +name = "calloop" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19457a0da465234abd76134a5c2a910c14bd3c5558463e4396ab9a37a328e465" +dependencies = [ + "log", + "nix 0.25.1", + "slotmap", + "thiserror", + "vec_map", +] + +[[package]] +name = "cc" +version = "1.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "time", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "const-cstr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3d0b5ff30645a68f35ece8cea4556ca14ef8a1651455f789a099a0513532a6" + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cxx" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dbus" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8bcdd56d2e5c4ed26a529c5a9029f5db8290d433497506f958eae3be148eb6" +dependencies = [ + "libc", + "libdbus-sys", + "winapi", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading", +] + +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "fontconfig" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b0653582d9c82645f95bacd0dc87b49a3126f7a9633be8cafa9c6d17d525847" +dependencies = [ + "yeslogic-fontconfig-sys", +] + +[[package]] +name = "futures-channel" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" + +[[package]] +name = "libdbus-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c185b5b7ad900923ef3a8ff594083d4d9b5aea80bb4f32b8342363138c0d456b" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memmap" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "memmap2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nix" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[package]] +name = "nix" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "memoffset 0.7.1", + "pin-utils", + "static_assertions", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" + +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e6affeb1632d6ff6a23d2cd40ffed138e82f1532571a26f527c8a284bb2fbb" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "pest" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f6e86fb9e7026527a0d46bc308b841d73170ef8f443e1807f6ef88526a816d4" +dependencies = [ + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96504449aa860c8dcde14f9fba5c58dc6658688ca1fe363589d6327b8662c603" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "798e0220d1111ae63d66cb66a5dcb3fc2d986d520b98e49e1852bfdb11d7c5e7" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "984298b75898e30a843e278a9f2452c31e349a073a0ce6fd950a12a74464e065" +dependencies = [ + "once_cell", + "pest", + "sha1", +] + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11bafc859c6815fbaffbbbf4229ecb767ac913fecb27f9ad4343662e9ef099ea" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rcalc_lib" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b83922b4c02be427b60b94ca2b4118a6a4fb2b9df4ad88c50c56d9ebd93455c9" +dependencies = [ + "dtoa", + "lazy_static", + "num-bigint", + "num-complex", + "num-rational", + "num-traits", + "pest", + "pest_derive", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rusttype" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff8374aa04134254b7995b63ad3dc41c7f7236f69528b28553da7d72efaa967" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + +[[package]] +name = "slotmap" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "ttf-parser" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "ucd-trie" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-segmentation" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "wayland-backend" +version = "0.1.0-beta.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50598bd298e1e01de02ad3cdcfcfcdb961189e40fea41d9325bc07ba81f8740d" +dependencies = [ + "cc", + "downcast-rs", + "io-lifetimes", + "nix 0.25.1", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.30.0-beta.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae06ffbf163db5cc6b40f9aa0ec66e7e1c22b979a327daf209814277f18834e1" +dependencies = [ + "bitflags", + "calloop", + "futures-channel", + "futures-core", + "log", + "nix 0.25.1", + "thiserror", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.30.0-beta.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8491945e2705791d58695ae166bb05bfb1f01f2b7b848faa73966e3462269a2f" +dependencies = [ + "bitflags", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.30.0-beta.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28407261ad2bab693652acd84e8abcf1d2bef7270b5cc840ec8c6bed7c715838" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", + "syn", +] + +[[package]] +name = "wayland-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b2a02ac608e07132978689a6f9bf4214949c85998c247abadd4f4129b1aa06" +dependencies = [ + "dlib", + "log", + "pkg-config", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + +[[package]] +name = "wldash2" +version = "0.1.0" +dependencies = [ + "bitflags", + "calloop", + "chrono", + "dbus", + "fontconfig", + "fuzzy-matcher", + "itertools", + "memmap", + "nix 0.26.1", + "rcalc_lib", + "rust-ini", + "rusttype", + "shlex", + "tempfile", + "unicode-segmentation", + "walkdir", + "wayland-client", + "wayland-protocols", + "xkbcommon", +] + +[[package]] +name = "xkbcommon" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbee136714379ab22da0280207fdb7f47e0bb940adea97731b65598b8c7a92e" +dependencies = [ + "libc", + "memmap2", +] + +[[package]] +name = "yeslogic-fontconfig-sys" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec657fd32bbcbeaef5c7bc8e10b3db95b143fab8db0a50079773dbf936fd4f73" +dependencies = [ + "const-cstr", + "dlib", + "once_cell", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..686767f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "wldash2" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +wayland-client = { version = "0.30.0-beta.13", features = ["calloop", "log"] } +wayland-protocols = { version = "0.30.0-beta.13", features = ["client", "unstable", "staging"] } +tempfile = "3.3" +nix = "0.26" +memmap = "0.7" +fontconfig = "0.6.0" +rusttype = "^0.9" +calloop = { version = "0.10" } +chrono = "0.4" + +walkdir = "2.3" +itertools = "0.10" +rust-ini = "0.18" + +bitflags = "1.3" + +unicode-segmentation = "1.10" +fuzzy-matcher = "0.3" +shlex = "1.1" + +xkbcommon = { version = "0.5", features = ["wayland"] } +dbus = "0.9" + +rcalc_lib = "1.0" diff --git a/src/buffer.rs b/src/buffer.rs new file mode 100644 index 0000000..8a2dc0f --- /dev/null +++ b/src/buffer.rs @@ -0,0 +1,321 @@ +use std::{ + ffi::CStr, + fs::File, + io, + os::unix::io::{FromRawFd, RawFd}, + time::SystemTime, + time::UNIX_EPOCH, +}; + +#[cfg(target_os = "linux")] +use nix::sys::memfd; +use nix::{ + errno::Errno, + fcntl, + sys::{mman, stat}, + unistd, +}; + +use memmap::MmapMut; + +use wayland_client::{ + protocol::{wl_buffer, wl_shm, wl_shm_pool}, + Dispatch, QueueHandle, +}; + +use crate::color::Color; +use crate::widgets::Geometry; + +fn create_shm_fd() -> io::Result { + // Only try memfd on linux + #[cfg(target_os = "linux")] + loop { + match memfd::memfd_create( + CStr::from_bytes_with_nul(b"wldash\0").unwrap(), + memfd::MemFdCreateFlag::MFD_CLOEXEC, + ) { + Ok(fd) => return Ok(fd), + Err(Errno::EINTR) => continue, + Err(Errno::ENOSYS) => break, + Err(errno) => return Err(io::Error::from(errno)), + } + } + + // Fallback to using shm_open + let sys_time = SystemTime::now(); + let mut mem_file_handle = format!( + "/wldash-{}", + sys_time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos() + ); + loop { + match mman::shm_open( + mem_file_handle.as_str(), + fcntl::OFlag::O_CREAT + | fcntl::OFlag::O_EXCL + | fcntl::OFlag::O_RDWR + | fcntl::OFlag::O_CLOEXEC, + stat::Mode::S_IRUSR | stat::Mode::S_IWUSR, + ) { + Ok(fd) => match mman::shm_unlink(mem_file_handle.as_str()) { + Ok(_) => return Ok(fd), + Err(errno) => match unistd::close(fd) { + Ok(_) => return Err(io::Error::from(errno)), + Err(errno) => return Err(io::Error::from(errno)), + }, + }, + Err(Errno::EEXIST) => { + // If a file with that handle exists then change the handle + mem_file_handle = format!( + "/wldash-{}", + sys_time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos() + ); + continue; + } + Err(Errno::EINTR) => continue, + Err(errno) => return Err(io::Error::from(errno)), + } + } +} + +pub struct ShmBuffer { + pub file: File, + pub mmap: MmapMut, + pub buffer: wl_buffer::WlBuffer, + pub refcnt: u32, +} + +impl ShmBuffer { + pub fn new( + shm: &wl_shm::WlShm, + qh: &QueueHandle, + width: i32, + height: i32, + stride: i32, + format: wl_shm::Format, + ) -> io::Result + where + D: Dispatch + Dispatch + 'static, + { + let mem_fd = create_shm_fd()?; + let file = unsafe { File::from_raw_fd(mem_fd) }; + file.set_len((height * stride) as u64)?; + let pool = shm.create_pool(mem_fd, height * stride, &qh, ()); + let mmap = unsafe { MmapMut::map_mut(&file).unwrap() }; + let buffer = pool.create_buffer(0, width, height, stride, format, &qh, ()); + + Ok(ShmBuffer { + refcnt: 0, + file, + mmap, + buffer, + }) + } + + pub fn acquire(&mut self) { + self.refcnt += 1; + } + + pub fn release(&mut self) { + self.refcnt -= 1; + } +} + +pub struct BufferManager { + pub buffers: Vec, +} + +impl BufferManager { + pub fn clear_buffers(&mut self) { + for buf in self.buffers.iter_mut() { + buf.buffer.destroy() + } + self.buffers.clear() + } + + pub fn next_buffer(&mut self) -> Option<&mut ShmBuffer> { + for buf in self.buffers.iter_mut() { + if buf.refcnt == 0 { + return Some(buf); + } + } + None + } + + pub fn add_buffer( + &mut self, + wl_shm: &wl_shm::WlShm, + dimensions: (i32, i32), + qh: &QueueHandle, + ) where + D: Dispatch + Dispatch + 'static, + { + let mut buf = ShmBuffer::new( + wl_shm, + qh, + dimensions.0, + dimensions.1, + dimensions.0 * 4, + wl_shm::Format::Argb8888, + ) + .expect("unable to add buffer"); + BufferView::new(&mut buf.mmap, (dimensions.0 as u32, dimensions.1 as u32)); + self.buffers.push(buf); + } +} + +pub struct BufferView<'a> { + buf: &'a mut MmapMut, + dimensions: (u32, u32), + subdimensions: Option<(u32, u32, u32, u32)>, +} + +impl<'a> BufferView<'a> { + pub fn new(buf: &'a mut MmapMut, dimensions: (u32, u32)) -> BufferView { + BufferView { + buf, + dimensions, + subdimensions: None, + } + } + + pub fn get_bounds(&self) -> (u32, u32, u32, u32) { + if let Some(subdim) = self.subdimensions { + subdim + } else { + (0, 0, self.dimensions.0, self.dimensions.1) + } + } + + pub fn subdimensions( + &mut self, + subdimensions: (u32, u32, u32, u32), + ) -> Result { + let bounds = self.get_bounds(); + if subdimensions.0 + subdimensions.2 > bounds.2 + || subdimensions.1 + subdimensions.3 > bounds.3 + { + return Err(::std::io::Error::new( + ::std::io::ErrorKind::Other, + format!( + "cannot create subdimensions larger than buffer: {:?} > {:?}", + subdimensions, bounds + ), + )); + } + + Ok(BufferView { + buf: self.buf, + dimensions: self.dimensions, + subdimensions: Some(( + subdimensions.0 + bounds.0, + subdimensions.1 + bounds.1, + subdimensions.2, + subdimensions.3, + )), + }) + } + + pub fn subgeometry(&mut self, geo: Geometry) -> Result { + self.subdimensions((geo.x, geo.y, geo.width, geo.height)) + } + + pub fn offset(&mut self, offset: (u32, u32)) -> Result { + let bounds = self.get_bounds(); + if offset.0 > bounds.2 || offset.1 > bounds.3 { + return Err(::std::io::Error::new( + ::std::io::ErrorKind::Other, + format!( + "cannot create offset outside buffer: {:?} > {:?}", + offset, bounds + ), + )); + } + + Ok(BufferView { + buf: self.buf, + dimensions: self.dimensions, + subdimensions: Some(( + offset.0 + bounds.0, + offset.1 + bounds.1, + bounds.2 - offset.0, + bounds.3 - offset.1, + )), + }) + } + + pub fn limit(&mut self, dimensions: (u32, u32)) -> Result { + let bounds = self.get_bounds(); + if dimensions.0 > bounds.2 || dimensions.1 > bounds.3 { + return Err(::std::io::Error::new( + ::std::io::ErrorKind::Other, + format!( + "cannot create offset outside buffer: {:?} > {:?}", + dimensions, bounds + ), + )); + } + + Ok(BufferView { + buf: self.buf, + dimensions: self.dimensions, + subdimensions: Some((bounds.0, bounds.1, dimensions.0, dimensions.1)), + }) + } + + pub fn memset(&mut self, c: &Color) { + let v = c.as_argb8888(); + if let Some(subdim) = self.subdimensions { + unsafe { + let ptr = self.buf.as_mut_ptr(); + for y in subdim.1..(subdim.1 + subdim.3) { + for x in subdim.0..(subdim.0 + subdim.2) { + *((ptr as *mut u32).offset((x + y * self.dimensions.0) as isize)) = v; + } + } + } + } else { + unsafe { + let ptr = self.buf.as_mut_ptr(); + for p in 0..(self.dimensions.0 * self.dimensions.1) { + *((ptr as *mut u32).offset(p as isize)) = v; + } + } + } + } + + pub fn put(&mut self, pos: (u32, u32), c: &Color) -> Result<(), ::std::io::Error> { + let true_pos = if let Some(subdim) = self.subdimensions { + if pos.0 >= subdim.2 || pos.1 >= subdim.3 { + return Err(::std::io::Error::new( + ::std::io::ErrorKind::Other, + format!( + "put({:?}) is not within subdimensions of buffer ({:?})", + pos, subdim + ), + )); + } + (pos.0 + subdim.0, pos.1 + subdim.1) + } else { + if pos.0 >= self.dimensions.0 || pos.1 >= self.dimensions.1 { + return Err(::std::io::Error::new( + ::std::io::ErrorKind::Other, + format!( + "put({:?}) is not within dimensions of buffer ({:?})", + pos, self.dimensions + ), + )); + } + pos + }; + + unsafe { + let ptr = self + .buf + .as_mut_ptr() + .offset(4 * (true_pos.0 + (true_pos.1 * self.dimensions.0)) as isize); + *(ptr as *mut u32) = c.as_argb8888(); + }; + + Ok(()) + } +} diff --git a/src/color.rs b/src/color.rs new file mode 100644 index 0000000..67675b2 --- /dev/null +++ b/src/color.rs @@ -0,0 +1,71 @@ +//use serde::{Deserialize, Serialize}; + +//#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)] +pub struct Color { + red: f32, + green: f32, + blue: f32, + opacity: f32, +} + +impl Color { + pub fn new(red: f32, green: f32, blue: f32, opacity: f32) -> Color { + let (red, green, blue, opacity) = clamp(red, green, blue, opacity); + Color { + red, + green, + blue, + opacity, + } + } + + pub fn blend(&self, other: &Color, ratio: f32) -> Color { + let ratio = clamp_f32(ratio, 0.0, 1.0); + + Color { + red: blend_f32(self.red, other.red, ratio), + green: blend_f32(self.green, other.green, ratio), + blue: blend_f32(self.blue, other.blue, ratio), + opacity: blend_f32(self.opacity, other.opacity, ratio), + } + } + + #[inline] + pub fn as_argb8888(&self) -> u32 { + ((255.0 * self.opacity) as u32 & 0xFF) << 24 + | ((255.0 * self.red) as u32 & 0xFF) << 16 + | ((255.0 * self.green) as u32 & 0xFF) << 8 + | ((255.0 * self.blue) as u32 & 0xFF) + } +} + +#[inline] +fn clamp(r: f32, g: f32, b: f32, o: f32) -> (f32, f32, f32, f32) { + clamp_naive(r, g, b, o) +} + +#[inline] +fn clamp_naive(r: f32, g: f32, b: f32, o: f32) -> (f32, f32, f32, f32) { + ( + clamp_f32(r, 0.0, 1.0), + clamp_f32(g, 0.0, 1.0), + clamp_f32(b, 0.0, 1.0), + clamp_f32(o, 0.0, 1.0), + ) +} + +#[inline] +fn clamp_f32(v: f32, a: f32, b: f32) -> f32 { + if v > b { + b + } else if v < a { + a + } else { + v + } +} + +#[inline] +fn blend_f32(a: f32, b: f32, r: f32) -> f32 { + a + ((b - a) * r) +} diff --git a/src/draw.rs b/src/draw.rs new file mode 100644 index 0000000..747dc6c --- /dev/null +++ b/src/draw.rs @@ -0,0 +1,339 @@ +use crate::buffer::BufferView; +use crate::{color::Color, fonts::FontRef}; + +use rusttype::{point, Scale}; +use std::collections::HashMap; + +struct CachedGlyph { + dimensions: (u32, u32), + origin: (i32, i32), + render: Vec, + advance: i32, +} + +impl CachedGlyph { + fn new(font: FontRef, size: f32, ch: char) -> CachedGlyph { + let scale = Scale::uniform(size); + let v_metrics = font.v_metrics(scale); + let glyph = font.glyph(ch).scaled(scale); + let advance = glyph.h_metrics().advance_width as i32; + let glyph = glyph.positioned(point(0.0, v_metrics.ascent)); + + if let Some(bounding_box) = glyph.pixel_bounding_box() { + let origin = (bounding_box.min.x, bounding_box.min.y); + + let dimensions = ( + (bounding_box.max.x - bounding_box.min.x) as u32, + (bounding_box.max.y - bounding_box.min.y) as u32, + ); + let mut render = vec![0.0; (dimensions.0 * dimensions.1) as usize]; + glyph.draw(|x, y, o| { + let pos = x + (y * dimensions.0); + render[pos as usize] = o; + }); + CachedGlyph { + origin, + dimensions, + render, + advance, + } + } else { + CachedGlyph { + origin: (0, 0), + dimensions: (0, 0), + render: Vec::new(), + advance: (size / 4.0) as i32, + } + } + } + + fn draw(&self, buf: &mut BufferView, pos: (i32, i32), bg: &Color, c: &Color) { + let mut x = 0; + let mut y = 0; + for v in &self.render { + let _ = buf.put( + ( + (x + pos.0 + self.origin.0) as u32, + (y + pos.1 + self.origin.1) as u32, + ), + &bg.blend(&c, *v), + ); + + if x == self.dimensions.0 as i32 - 1 { + y += 1; + x = 0; + } else { + x += 1; + } + } + } +} + +pub struct Font<'a> { + glyphs: HashMap, + font: FontRef<'a>, + size: f32, +} + +impl<'a> Font<'a> { + pub fn new(font: FontRef<'a>, size: f32) -> Font { + Font { + glyphs: HashMap::new(), + font, + size, + } + } + + pub fn add_str_to_cache(&mut self, s: &str) { + for ch in s.chars() { + if self.glyphs.get(&ch).is_none() { + let glyph = CachedGlyph::new(self.font, self.size, ch); + self.glyphs.insert(ch, glyph); + } + } + } + + pub fn draw_text( + &self, + buf: &mut BufferView, + bg: &Color, + c: &Color, + s: &str, + ) -> Result<(u32, u32), ::std::io::Error> { + let mut x_off = 0; + let mut off = 0; + let mut glyphs = Vec::with_capacity(s.len()); + for ch in s.chars() { + let glyph = match self.glyphs.get(&ch) { + Some(glyph) => glyph, + None => { + return Err(::std::io::Error::new( + ::std::io::ErrorKind::Other, + format!("glyph for {:} not in cache", ch), + )) + } + }; + glyphs.push(glyph); + if glyph.origin.1 < off { + off = glyph.origin.1 + } + } + for glyph in glyphs { + glyph.draw(buf, (x_off, -off), bg, c); + x_off += glyph.advance; + } + + Ok((x_off as u32, self.size as u32)) + } + + pub fn draw_text_with_cursor( + &self, + buf: &mut BufferView, + bg: &Color, + c: &Color, + s: &str, + cursor: usize, + ) -> Result<(u32, u32), ::std::io::Error> { + let mut x_off: i32 = 0; + let mut off: i32 = 0; + let mut glyphs = Vec::with_capacity(s.len()); + for ch in s.chars() { + let glyph = match self.glyphs.get(&ch) { + Some(glyph) => glyph, + None => { + return Err(::std::io::Error::new( + ::std::io::ErrorKind::Other, + format!("glyph for {:} not in cache", ch), + )) + } + }; + glyphs.push(glyph); + if glyph.origin.1 < off { + off = glyph.origin.1 + } + } + + let height = buf.get_bounds().3; + for (i, glyph) in glyphs.iter().enumerate() { + if i == cursor { + self.draw_cursor(buf, c, x_off as u32, height)?; + } + glyph.draw(buf, (x_off, -off), bg, c); + x_off += glyph.advance; + } + if cursor == glyphs.len() { + self.draw_cursor(buf, c, x_off as u32, height)?; + } + + Ok((x_off as u32, self.size as u32)) + } + + pub fn draw_cursor( + &self, + buf: &mut BufferView, + c: &Color, + offset: u32, + height: u32, + ) -> Result<(), ::std::io::Error> { + // draw cursor + for i in 1..height { + buf.put((offset, i), c)? + } + Ok(()) + } + + pub fn auto_widest(&mut self, s: &str) -> Result { + self.add_str_to_cache(s); + let mut max = 0; + for ch in s.chars() { + let glyph = match self.glyphs.get(&ch) { + Some(glyph) => glyph, + None => { + return Err(::std::io::Error::new( + ::std::io::ErrorKind::Other, + format!("glyph for {:} not in cache", ch), + )) + } + }; + let width = glyph.advance; + if width > max { + max = width + } + } + Ok(max as u32) + } + + pub fn auto_draw_text( + &mut self, + buf: &mut BufferView, + bg: &Color, + c: &Color, + s: &str, + ) -> Result<(u32, u32), ::std::io::Error> { + self.add_str_to_cache(s); + self.draw_text(buf, bg, c, s) + } + + pub fn auto_draw_text_with_cursor( + &mut self, + buf: &mut BufferView, + bg: &Color, + c: &Color, + s: &str, + cursor: usize, + ) -> Result<(u32, u32), ::std::io::Error> { + self.add_str_to_cache(s); + self.draw_text_with_cursor(buf, bg, c, s, cursor) + } + + pub fn draw_text_fixed_width( + &self, + buf: &mut BufferView, + bg: &Color, + c: &Color, + distances: &[u32], + s: &str, + ) -> Result<(u32, u32), ::std::io::Error> { + let mut x_off = 0; + let mut off = 0; + let mut glyphs = Vec::with_capacity(s.len()); + for ch in s.chars() { + let glyph = match self.glyphs.get(&ch) { + Some(glyph) => glyph, + None => { + return Err(::std::io::Error::new( + ::std::io::ErrorKind::Other, + format!("glyph for {:} not in cache", ch), + )) + } + }; + glyphs.push(glyph); + if glyph.origin.1 < off { + off = glyph.origin.1 + } + } + for (idx, glyph) in glyphs.into_iter().enumerate() { + glyph.draw(buf, (x_off, -off), bg, c); + x_off += distances[idx] as i32; + } + + Ok((x_off as u32, self.size as u32)) + } + + pub fn draw_text_individual_colors( + &self, + buf: &mut BufferView, + bg: &Color, + color: &[Color], + s: &str, + ) -> Result<(u32, u32), ::std::io::Error> { + let mut x_off = 0; + let mut off = 0; + let mut glyphs = Vec::with_capacity(s.len()); + for ch in s.chars() { + let glyph = match self.glyphs.get(&ch) { + Some(glyph) => glyph, + None => { + return Err(::std::io::Error::new( + ::std::io::ErrorKind::Other, + format!("glyph for {:} not in cache", ch), + )) + } + }; + glyphs.push(glyph); + if glyph.origin.1 < off { + off = glyph.origin.1 + } + } + for (idx, glyph) in glyphs.into_iter().enumerate() { + glyph.draw(buf, (x_off, -off), bg, &color[idx]); + x_off += glyph.advance; + } + + Ok((x_off as u32, self.size as u32)) + } + + pub fn auto_draw_text_individual_colors( + &mut self, + buf: &mut BufferView, + bg: &Color, + color: &[Color], + s: &str, + ) -> Result<(u32, u32), ::std::io::Error> { + self.add_str_to_cache(s); + self.draw_text_individual_colors(buf, bg, color, s) + } +} + +pub fn draw_box(buf: &mut BufferView, c: &Color, dim: (u32, u32)) -> Result<(), ::std::io::Error> { + for x in 0..dim.0 { + let _ = buf.put((x, 0), c); + let _ = buf.put((x, dim.1 - 1), c); + } + for y in 0..dim.1 { + buf.put((0, y), c)?; + buf.put((dim.0 - 1, y), c)?; + } + + Ok(()) +} + +pub fn draw_bar( + buf: &mut BufferView, + color: &Color, + length: u32, + height: u32, + fill: f32, +) -> Result<(), ::std::io::Error> { + let mut fill_pos = ((length as f32) * fill) as u32; + if fill_pos > length { + fill_pos = length; + } + for y in 0..height { + for x in 0..fill_pos { + let _ = buf.put((x, y), color); + } + } + + Ok(()) +} diff --git a/src/fonts.rs b/src/fonts.rs new file mode 100644 index 0000000..7171a16 --- /dev/null +++ b/src/fonts.rs @@ -0,0 +1,128 @@ +//! Utility module for fonts + +use crate::draw; +use fontconfig::Fontconfig as FontConfig; +use rusttype::Font; +use std::{ + collections::HashMap, + fs::File, + hash, + io::Read, + path::{Path, PathBuf}, +}; + +/// FontRef is used to store Fonts on widgets. +pub type FontRef<'a> = &'a rusttype::Font<'a>; + +/// FontSeeker is a marker struct that is used to look up fonts +pub(crate) struct FontSeeker; + +impl FontSeeker { + /// Acts like fc-match. + /// Given a string, it matches it to a font file and returns its path. + pub(crate) fn from_string(name: &str) -> PathBuf { + let fc = FontConfig::new().unwrap(); + fc.find(name, None).unwrap().path + } +} + +/// FontLoader is a marker struct that is used to load files +pub(crate) struct FontLoader; + +impl FontLoader { + /// Given a path, loads it as a Font, which can be rendered to the screen. + pub(crate) fn from_path<'a, P>(path: P) -> Option> + where + P: AsRef, + { + let mut file = File::open(path).expect("Font file not found"); + let mut data = match file.metadata() { + Ok(metadata) => Vec::with_capacity(metadata.len() as usize), + Err(_) => vec![], + }; + file.read_to_end(&mut data).unwrap(); + Font::try_from_vec(data) + } +} + +#[derive(Debug, Copy, Clone)] +struct ComparableF32(f32); +impl ComparableF32 { + fn key(&self) -> u64 { + self.0.to_bits() as u64 + } +} + +impl hash::Hash for ComparableF32 { + fn hash(&self, state: &mut H) + where + H: hash::Hasher, + { + self.key().hash(state) + } +} + +impl PartialEq for ComparableF32 { + fn eq(&self, other: &ComparableF32) -> bool { + self.key() == other.key() + } +} + +impl Eq for ComparableF32 {} + +pub struct FontMap { + fonts: HashMap<(&'static str, ComparableF32), draw::Font<'static>>, + required_fonts: HashMap<&'static str, (&'static str, Vec)>, +} + +impl FontMap { + pub fn new() -> FontMap { + FontMap { + fonts: HashMap::new(), + required_fonts: HashMap::new(), + } + } + + pub fn queue_font_name(&mut self, font_name: &'static str, size: f32) { + match self.required_fonts.get_mut(font_name) { + Some(v) => v.1.push(size), + None => { + self.required_fonts + .insert(font_name, (font_name, vec![size])); + } + } + } + + pub fn queue_font_path(&mut self, font_name: &'static str, font_path: &'static str, size: f32) { + match self.required_fonts.get_mut(font_name) { + Some(v) => v.1.push(size), + None => { + self.required_fonts + .insert(font_name, (font_path, vec![size])); + } + } + } + + pub fn load_fonts(&mut self) { + for (font_name, v) in self.required_fonts.iter() { + let path = if v.0.starts_with("/") { + std::path::PathBuf::from(v.0) + } else { + FontSeeker::from_string(v.0) + }; + let fontref = Box::leak(Box::new( + FontLoader::from_path(path).expect("unable to load font"), + )); + for size in &v.1 { + let font = draw::Font::new(fontref, *size); + self.fonts.insert((font_name, ComparableF32(*size)), font); + } + } + } + + pub fn get_font(&mut self, font_name: &'static str, size: f32) -> &mut draw::Font<'static> { + self.fonts + .get_mut(&(font_name, ComparableF32(size))) + .expect("no font at specified size") + } +} diff --git a/src/keyboard/keysyms.rs b/src/keyboard/keysyms.rs new file mode 100644 index 0000000..276584a --- /dev/null +++ b/src/keyboard/keysyms.rs @@ -0,0 +1,19 @@ +#![allow(missing_docs, non_upper_case_globals, unused_parens, clippy::all)] + +pub const XKB_KEY_BackSpace: u32 = 0xff08; /* Back space, back char */ +//pub const XKB_KEY_Tab: u32 = 0xff09; +pub const XKB_KEY_Return: u32 = 0xff0d; /* Return, enter */ +//pub const XKB_KEY_Escape: u32 = 0xff1b; +pub const XKB_KEY_Delete: u32 = 0xffff; /* Delete, rubout */ +pub const XKB_KEY_Home: u32 = 0xff50; +pub const XKB_KEY_Left: u32 = 0xff51; /* Move left, left arrow */ +pub const XKB_KEY_Up: u32 = 0xff52; /* Move up, up arrow */ +pub const XKB_KEY_Right: u32 = 0xff53; /* Move right, right arrow */ +pub const XKB_KEY_Down: u32 = 0xff54; /* Move down, down arrow */ +pub const XKB_KEY_End: u32 = 0xff57; /* EOL */ + +//pub const XKB_KEY_ISO_Left_Tab: u32 = 0xfe20; +pub const XKB_KEY_e: u32 = 0x0065; /* U+0065 LATIN SMALL LETTER E */ +pub const XKB_KEY_a: u32 = 0x0061; /* U+0061 LATIN SMALL LETTER A */ +//pub const XKB_KEY_c: u32 = 0x0063; /* U+0063 LATIN SMALL LETTER C */ +//pub const XKB_KEY_u: u32 = 0x0075; /* U+0075 LATIN SMALL LETTER U */ diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs new file mode 100644 index 0000000..6b40547 --- /dev/null +++ b/src/keyboard/mod.rs @@ -0,0 +1,136 @@ +//! Utilities for keymap interpretation of keyboard input +//! +//! This module provides an implementation for `wl_keyboard` +//! objects using `libxkbcommon` to interpret the keyboard input +//! given the user keymap. +//! +//! The entry point of this module is the [`map_keyboard`](fn.map_keyboard.html) +//! function which, given a `wl_seat` and a callback, setup keymap interpretation +//! and key repetition for the `wl_keyboard` of this seat. +//! +//! Key repetition relies on an event source, that needs to be inserted in your +//! calloop event loop. Not doing so will prevent key repetition to work +//! (but the rest of the functionnality will not be affected). + +use std::default::Default; +use std::os::unix::io::{AsRawFd, OwnedFd}; + +use wayland_client::{protocol::wl_keyboard, WEnum}; +use xkbcommon::xkb; + +pub mod keysyms; +mod state; + +use self::state::KbState; +pub use self::state::ModifiersState; + +pub struct KeyEvent { + pub serial: u32, + pub time: u32, + pub rawkey: u32, + pub keysym: u32, + pub state: WEnum, + pub modifiers: ModifiersState, + pub utf8: Option, + pub repeats: bool, +} + +pub struct Keyboard { + state: Option, +} + +impl Keyboard { + pub fn new() -> Keyboard { + Keyboard { state: None } + } + + pub fn keymap(&mut self, format: WEnum, fd: OwnedFd, size: u32) { + match format { + WEnum::Value(wl_keyboard::KeymapFormat::XkbV1) => { + self.state = Some( + KbState::new_from_fd(fd.as_raw_fd(), size as usize) + .expect("unable to load keymap"), + ); + } + WEnum::Value(wl_keyboard::KeymapFormat::NoKeymap) => { + // TODO: how to handle this (hopefully never occuring) case? + } + _ => unreachable!(), + } + } + + pub fn key( + &mut self, + serial: u32, + time: u32, + key: u32, + key_state: WEnum, + ) -> KeyEvent { + if let Some(state) = &mut self.state { + let (sym, utf8, repeats) = { + // Get the values to generate a key event + let sym = state.get_one_sym_raw(key); + let utf8 = if key_state == WEnum::Value(wl_keyboard::KeyState::Pressed) { + match state.compose_feed(sym) { + Some(xkb::compose::FeedResult::Accepted) => { + if let Some(status) = state.compose_status() { + match status { + xkb::compose::Status::Composed => state.compose_get_utf8(), + xkb::compose::Status::Nothing => state.get_utf8_raw(key), + _ => None, + } + } else { + state.get_utf8_raw(key) + } + } + Some(_) => { + // XKB_COMPOSE_FEED_IGNORED + None + } + None => { + // XKB COMPOSE is not initialized + state.get_utf8_raw(key) + } + } + } else { + None + }; + let repeats = state.key_repeats(key + 8); + (sym, utf8, repeats) + }; + KeyEvent { + rawkey: key, + keysym: sym, + state: key_state, + modifiers: state.mods_state(), + serial, + time, + utf8, + repeats, + } + } else { + KeyEvent { + rawkey: key, + keysym: 0, + state: key_state, + modifiers: Default::default(), + utf8: None, + repeats: false, + serial, + time, + } + } + } + + pub fn modifiers( + &mut self, + mods_depressed: u32, + mods_latched: u32, + mods_locked: u32, + group: u32, + ) { + if let Some(state) = &mut self.state { + state.update_modifiers(mods_depressed, mods_latched, mods_locked, group); + } + } +} diff --git a/src/keyboard/state.rs b/src/keyboard/state.rs new file mode 100644 index 0000000..ba5ed64 --- /dev/null +++ b/src/keyboard/state.rs @@ -0,0 +1,158 @@ +use std::{env, os::unix::io::RawFd}; + +use xkbcommon::xkb; + +pub struct KbState { + xkb_context: xkb::Context, + xkb_keymap: xkb::Keymap, + xkb_state: xkb::State, + xkb_compose_table: Option, + xkb_compose_state: Option, + mods_state: ModifiersState, +} + +/// Represents the current state of the keyboard modifiers +/// +/// Each field of this struct represents a modifier and is `true` if this modifier is active. +/// +/// For some modifiers, this means that the key is currently pressed, others are toggled +/// (like caps lock). +#[derive(Copy, Clone, Debug, Default)] +pub struct ModifiersState { + /// The "control" key + pub ctrl: bool, + /// The "alt" key + pub alt: bool, + /// The "shift" key + pub shift: bool, + /// The "Caps lock" key + pub caps_lock: bool, + /// The "logo" key + /// + /// Also known as the "windows" key on most keyboards + pub logo: bool, + /// The "Num lock" key + pub num_lock: bool, +} + +impl ModifiersState { + fn new() -> ModifiersState { + ModifiersState::default() + } + + fn update_with(&mut self, state: &xkb::State) { + self.ctrl = state.mod_name_is_active("Control", xkb::STATE_MODS_EFFECTIVE); + self.alt = state.mod_name_is_active("Mod1", xkb::STATE_MODS_EFFECTIVE); + self.shift = state.mod_name_is_active("Shift", xkb::STATE_MODS_EFFECTIVE); + self.caps_lock = state.mod_name_is_active("Lock", xkb::STATE_MODS_EFFECTIVE); + self.num_lock = state.mod_name_is_active("Mod2", xkb::STATE_MODS_EFFECTIVE); + self.logo = state.mod_name_is_active("Mod4", xkb::STATE_MODS_EFFECTIVE); + } +} + +impl KbState { + pub(crate) fn update_modifiers( + &mut self, + mods_depressed: u32, + mods_latched: u32, + mods_locked: u32, + group: u32, + ) { + let mask = + self.xkb_state + .update_mask(mods_depressed, mods_latched, mods_locked, 0, 0, group); + if (mask & xkb::STATE_MODS_EFFECTIVE) > 0 { + // effective value of mods have changed, we need to update our state + self.mods_state.update_with(&self.xkb_state); + } + } + + pub(crate) fn get_one_sym_raw(&mut self, keycode: u32) -> u32 { + self.xkb_state.key_get_one_sym(keycode + 8) + } + + pub(crate) fn get_utf8_raw(&mut self, keycode: u32) -> Option { + Some(self.xkb_state.key_get_utf8(keycode + 8)) + } + + pub(crate) fn compose_feed(&mut self, keysym: u32) -> Option { + match &mut self.xkb_compose_state { + Some(compose_state) => Some(compose_state.feed(keysym)), + None => None, + } + } + + pub(crate) fn compose_status(&mut self) -> Option { + match &mut self.xkb_compose_state { + Some(compose_state) => Some(compose_state.status()), + None => None, + } + } + + pub(crate) fn compose_get_utf8(&mut self) -> Option { + match &mut self.xkb_compose_state { + Some(compose_state) => compose_state.utf8(), + None => None, + } + } + + pub(crate) fn new_from_fd(fd: RawFd, size: usize) -> Result { + let xkb_context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS); + let xkb_keymap = unsafe { + xkb::Keymap::new_from_fd( + &xkb_context, + fd, + size, + xkb::KEYMAP_FORMAT_TEXT_V1, + xkb::KEYMAP_COMPILE_NO_FLAGS, + )? + .unwrap() + }; + let xkb_state = xkb::State::new(&xkb_keymap); + let mut me = KbState { + xkb_context, + xkb_keymap, + xkb_state, + xkb_compose_table: None, + xkb_compose_state: None, + mods_state: ModifiersState::new(), + }; + + me.init_compose(); + + Ok(me) + } + + pub(crate) fn init_compose(&mut self) { + let locale = env::var_os("LC_ALL") + .and_then(|v| if v.is_empty() { None } else { Some(v) }) + .or_else(|| env::var_os("LC_CTYPE")) + .and_then(|v| if v.is_empty() { None } else { Some(v) }) + .or_else(|| env::var_os("LANG")) + .and_then(|v| if v.is_empty() { None } else { Some(v) }) + .unwrap_or_else(|| "C".into()); + + let compose_table = match xkb::compose::Table::new_from_locale( + &self.xkb_context, + &locale, + xkb::compose::COMPILE_NO_FLAGS, + ) { + Ok(t) => t, + Err(_) => return, + }; + + let compose_state = xkb::compose::State::new(&compose_table, xkb::compose::STATE_NO_FLAGS); + + self.xkb_compose_table = Some(compose_table); + self.xkb_compose_state = Some(compose_state); + } + + pub(crate) fn key_repeats(&mut self, keycode: xkb::Keycode) -> bool { + self.xkb_keymap.key_repeats(keycode) + } + + #[inline] + pub(crate) fn mods_state(&self) -> ModifiersState { + self.mods_state + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..1841b40 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,605 @@ +mod buffer; +mod color; +mod draw; +mod fonts; +mod keyboard; +mod utils; +mod widgets; + +use wayland_client::{ + protocol::{ + wl_buffer, wl_compositor, wl_keyboard, wl_registry, wl_seat, wl_shm, wl_shm_pool, + wl_surface, + }, + Connection, Dispatch, QueueHandle, WEnum, WaylandSource, +}; + +use wayland_protocols::xdg::{ + activation::v1::client::{xdg_activation_token_v1, xdg_activation_v1}, + shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base}, +}; + +use calloop::{ + timer::{TimeoutAction, Timer}, + EventLoop, +}; +use chrono::{Duration, Local, Timelike}; + +use buffer::{BufferManager, BufferView}; +use color::Color; +use fonts::FontMap; +use keyboard::Keyboard; +use widgets::{ + Battery, Clock, Date, Geometry, HorizontalLayout, IndexedLayout, Interface, Layout, Line, + Margin, VerticalLayout, Widget, WidgetUpdater, +}; + +use std::{cell::RefCell, env, mem, rc::Rc, thread}; + +enum MaybeFontMap { + Waiting(thread::JoinHandle), + Ready(Rc>), + Invalid, +} + +impl MaybeFontMap { + fn unwrap(&self) -> Rc> { + match self { + MaybeFontMap::Ready(f) => f.clone(), + _ => panic!("fontmap not yet ready"), + } + } + + fn resolve(&mut self) { + if matches!(self, MaybeFontMap::Waiting(_)) { + let s = mem::replace(self, MaybeFontMap::Invalid); + match s { + MaybeFontMap::Waiting(handle) => { + *self = MaybeFontMap::Ready(Rc::new(RefCell::new(handle.join().unwrap()))); + } + _ => unreachable!(), + } + } + } +} + +struct State { + running: bool, + dirty: bool, + base_surface: Option, + wm_base: Option, + xdg_surface: Option<(xdg_surface::XdgSurface, xdg_toplevel::XdgToplevel)>, + wl_shm: Option, + xdg_activation: Option, + configured: bool, + dimensions: (i32, i32), + bufmgr: BufferManager, + widgets: Vec>, + keyboard: Keyboard, + fonts: MaybeFontMap, +} + +impl State { + fn add_buffer(&mut self, qh: &QueueHandle) { + self.bufmgr.add_buffer( + self.wl_shm.as_ref().expect("missing wl_shm"), + self.dimensions, + qh, + ); + } + + fn activate(&mut self, qh: &QueueHandle) { + let key = match env::var("XDG_ACTIVATION_TOKEN") { + Ok(token) => token, + Err(_) => return, + }; + match (&self.base_surface, &self.xdg_activation) { + (Some(surface), Some(activation)) => { + activation.activate(key, surface); + let activation_token = activation.get_activation_token(qh, ()); + activation_token.commit(); + env::remove_var("XDG_ACTIVATION_TOKEN"); + } + _ => (), + } + } +} + +impl WidgetUpdater for State { + fn geometry_update( + &mut self, + idx: usize, + fonts: &mut FontMap, + geometry: &Geometry, + ) -> Geometry { + self.widgets[idx].geometry_update(fonts, geometry) + } +} + +fn main() { + let font_thread = thread::spawn(move || { + let mut fm = FontMap::new(); + // Hard-coding font paths make things a lot faster + fm.queue_font_path("sans", "/usr/share/fonts/noto/NotoSans-Regular.ttf", 96.); + fm.queue_font_path("sans", "/usr/share/fonts/noto/NotoSans-Regular.ttf", 40.); + fm.queue_font_path("sans", "/usr/share/fonts/noto/NotoSans-Regular.ttf", 24.); + fm.queue_font_path( + "monospace", + "/usr/share/fonts/noto/NotoSansMono-Regular.ttf", + 32., + ); + fm.load_fonts(); + fm + }); + let conn = Connection::connect_to_env().unwrap(); + + let event_queue = conn.new_event_queue(); + let qhandle: QueueHandle = event_queue.handle(); + + let display = conn.display(); + display.get_registry(&qhandle, ()); + + let bufmgr = BufferManager { + buffers: Vec::new(), + }; + + let (ping_sender, ping_source) = calloop::ping::make_ping().unwrap(); + + let clock = Box::new(Clock::new()); + let date = Box::new(Date::new()); + let line = Box::new(Line::new()); + let launcher = Box::new(Interface::new()); + let battery = Box::new(Battery::new(ping_sender)); + + let mut event_loop: EventLoop = + EventLoop::try_new().expect("Failed to initialize the event loop!"); + + let handle = event_loop.handle(); + handle + .insert_source( + WaylandSource::new(event_queue).expect("Could not create WaylandSource!"), + |_event, queue, mut state| queue.dispatch_pending(&mut state), + ) + .expect("Failed to insert event source!"); + + handle + .insert_source(ping_source, |(), &mut (), state| { + state.widgets[2].set_dirty(true); + state.dirty = true; + }) + .expect("Failed to insert ping source!"); + + let clock_source = Timer::from_duration(std::time::Duration::from_secs(1)); + handle + .insert_source(clock_source, |_event, _metadata, state| { + let now = Local::now().naive_local(); + let target = (now + Duration::seconds(60)) + .with_second(0) + .unwrap() + .with_nanosecond(0) + .unwrap(); + let d = target - now; + state.widgets[0].set_dirty(true); + state.widgets[1].set_dirty(true); + state.dirty = true; + + // The timer event source requires us to return a TimeoutAction to + // specify if the timer should be rescheduled. In our case we just drop it. + TimeoutAction::ToDuration(d.to_std().unwrap()) + }) + .expect("Failed to insert event source!"); + + let mut state = State { + running: true, + dirty: true, + base_surface: None, + wm_base: None, + xdg_surface: None, + wl_shm: None, + xdg_activation: None, + configured: false, + dimensions: (320, 240), + bufmgr: bufmgr, + widgets: vec![clock, date, battery, line, launcher], + keyboard: Keyboard::new(), + fonts: MaybeFontMap::Waiting(font_thread), + }; + + let mut damage = Vec::new(); + for _ in 0..state.widgets.len() { + damage.push(Geometry::new()); + } + + while state.running { + event_loop + .dispatch(None, &mut state) + .expect("Could not dispatch event loop"); + + if !state.configured || !state.dirty { + continue; + } + + let mut drew = false; + let mut force = false; + if state.bufmgr.buffers.len() == 0 { + state.add_buffer(&qhandle); + force = true; + } + + // We expect to see the usual shm optimization + let buf = match state.bufmgr.next_buffer() { + Some(b) => b, + None => { + // We are still waiting for our buffer + continue; + } + }; + + state.dirty = false; + buf.acquire(); + let mut bufview = BufferView::new( + &mut buf.mmap, + (state.dimensions.0 as u32, state.dimensions.1 as u32), + ); + + let bg = Color::new(0., 0., 0., 1.); + + let surface = state.base_surface.as_ref().unwrap().clone(); + for (idx, widget) in state.widgets.iter_mut().enumerate() { + if force || widget.get_dirty() { + widget.set_dirty(false); + drew = true; + + let geo = widget.geometry(); + let old_damage = damage[idx]; + + if !force { + bufview.subgeometry(old_damage).unwrap().memset(&bg); + } else { + bufview.subgeometry(geo).unwrap().memset(&bg); + } + + let mut subview = bufview.subgeometry(geo).unwrap(); + let new_damage = widget.draw(&mut state.fonts.unwrap().borrow_mut(), &mut subview); + let combined_damage = new_damage.expand(old_damage); + damage[idx] = new_damage; + surface.damage_buffer( + combined_damage.x as i32, + combined_damage.y as i32, + combined_damage.width as i32, + combined_damage.height as i32, + ); + //draw_box(&mut subview, &Color::new(1.0, 0.5, 0.0, 1.0), (geo.width, geo.height)); + } + } + if !drew { + buf.release(); + continue; + } + + if force { + surface.damage_buffer(0, 0, 0x7FFFFFFF, 0x7FFFFFFF) + } + surface.attach(Some(&buf.buffer), 0, 0); + surface.commit(); + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + registry: &wl_registry::WlRegistry, + event: wl_registry::Event, + _: &(), + _: &Connection, + qh: &QueueHandle, + ) { + if let wl_registry::Event::Global { + name, interface, .. + } = event + { + match &interface[..] { + "wl_compositor" => { + let compositor = + registry.bind::(name, 4, qh, ()); + let surface = compositor.create_surface(qh, ()); + state.base_surface = Some(surface); + state.activate(qh); + + if state.wm_base.is_some() && state.xdg_surface.is_none() { + state.init_xdg_surface(qh); + } + } + "wl_shm" => { + state.wl_shm = Some(registry.bind::(name, 1, qh, ())); + } + "wl_seat" => { + registry.bind::(name, 1, qh, ()); + } + "xdg_wm_base" => { + let wm_base = registry.bind::(name, 1, qh, ()); + state.wm_base = Some(wm_base); + + if state.base_surface.is_some() && state.xdg_surface.is_none() { + state.init_xdg_surface(qh); + } + } + "xdg_activation_v1" => { + state.xdg_activation = Some( + registry.bind::(name, 1, qh, ()), + ); + state.activate(qh); + } + _ => {} + } + } + } +} + +impl Dispatch for State { + fn event( + _: &mut Self, + _: &wl_compositor::WlCompositor, + _: wl_compositor::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for State { + fn event( + _: &mut Self, + _: &wl_surface::WlSurface, + _: wl_surface::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for State { + fn event( + _: &mut Self, + _: &wl_shm::WlShm, + _: wl_shm::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for State { + fn event( + _: &mut Self, + _: &wl_shm_pool::WlShmPool, + _: wl_shm_pool::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + buffer: &wl_buffer::WlBuffer, + _: wl_buffer::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + for buf in state.bufmgr.buffers.iter_mut() { + if &buf.buffer == buffer { + buf.release(); + } + } + } +} + +impl State { + fn init_xdg_surface(&mut self, qh: &QueueHandle) { + let wm_base = self.wm_base.as_ref().unwrap(); + let base_surface = self.base_surface.as_ref().unwrap(); + + let xdg_surface = wm_base.get_xdg_surface(base_surface, qh, ()); + let toplevel = xdg_surface.get_toplevel(qh, ()); + toplevel.set_title("wldash".into()); + + base_surface.commit(); + + self.xdg_surface = Some((xdg_surface, toplevel)); + } +} + +impl Dispatch for State { + fn event( + _: &mut Self, + wm_base: &xdg_wm_base::XdgWmBase, + event: xdg_wm_base::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + if let xdg_wm_base::Event::Ping { serial } = event { + wm_base.pong(serial); + } + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + xdg_surface: &xdg_surface::XdgSurface, + event: xdg_surface::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + if let xdg_surface::Event::Configure { serial, .. } = event { + xdg_surface.ack_configure(serial); + state.configured = true; + state.dirty = true; + } + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + _: &xdg_toplevel::XdgToplevel, + event: xdg_toplevel::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + match event { + xdg_toplevel::Event::Configure { width, height, .. } => { + if (width, height) == (0, 0) { + return; + } + if state.dimensions != (width, height) { + state.bufmgr.clear_buffers(); + state.dimensions = (width, height); + let mut layout = VerticalLayout { + widgets: vec![ + Box::new(HorizontalLayout { + widgets: vec![ + Box::new(IndexedLayout { widget_idx: 0 }), + Box::new(Margin { + widget: Box::new(IndexedLayout { widget_idx: 1 }), + margin: (16, 8, 0, 0), + }), + Box::new(Margin { + widget: Box::new(IndexedLayout { widget_idx: 2 }), + margin: (16, 8, 8, 0), + }), + ], + }), + Box::new(IndexedLayout { widget_idx: 3 }), + Box::new(IndexedLayout { widget_idx: 4 }), + ], + }; + + state.fonts.resolve(); + let fonts = state.fonts.unwrap(); + layout.geometry_update( + &mut fonts.borrow_mut(), + &Geometry { + x: 0, + y: 0, + width: width as u32, + height: height as u32, + }, + state, + ); + } + } + xdg_toplevel::Event::Close => { + state.running = false; + } + _ => (), + } + } +} + +impl Dispatch for State { + fn event( + _: &mut Self, + seat: &wl_seat::WlSeat, + event: wl_seat::Event, + _: &(), + _: &Connection, + qh: &QueueHandle, + ) { + if let wl_seat::Event::Capabilities { + capabilities: WEnum::Value(capabilities), + } = event + { + if capabilities.contains(wl_seat::Capability::Keyboard) { + seat.get_keyboard(qh, ()); + } + } + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + _: &wl_keyboard::WlKeyboard, + event: wl_keyboard::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + match event { + wl_keyboard::Event::Key { + serial, + time, + key, + state: kbstate, + } => { + if key == 1 { + state.running = false; + return; + } + let k = state.keyboard.key(serial, time, key, kbstate); + for widget in state.widgets.iter_mut() { + widget.keyboard_input(&k); + } + state.dirty = true; + } + wl_keyboard::Event::Modifiers { + mods_depressed, + mods_latched, + mods_locked, + group, + .. + } => { + state + .keyboard + .modifiers(mods_depressed, mods_latched, mods_locked, group); + } + wl_keyboard::Event::Keymap { format, fd, size } => { + state.keyboard.keymap(format, fd, size); + } + _ => (), + } + } +} + +impl Dispatch for State { + fn event( + _: &mut Self, + _: &xdg_activation_v1::XdgActivationV1, + _: xdg_activation_v1::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + _: &xdg_activation_token_v1::XdgActivationTokenV1, + event: xdg_activation_token_v1::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + match event { + xdg_activation_token_v1::Event::Done { token } => { + for widget in state.widgets.iter_mut() { + widget.token_update(&token); + } + } + _ => (), + } + } +} diff --git a/src/utils/desktop.rs b/src/utils/desktop.rs new file mode 100644 index 0000000..8457685 --- /dev/null +++ b/src/utils/desktop.rs @@ -0,0 +1,112 @@ +use ini::{Ini, ParseOption}; +use itertools::Itertools; +use std::cmp::Ordering; +use std::env; +use std::error::Error; +use std::io::Error as io_error; +use std::io::ErrorKind; +use walkdir::WalkDir; + +#[derive(Clone, Debug, Eq, Hash)] +pub struct Desktop { + pub entry_type: String, + pub name: String, + pub no_display: bool, + pub hidden: bool, + pub exec: Option, + pub url: Option, + pub term: bool, + pub keywords: Vec, +} + +impl Desktop { + fn parse(f: &str) -> Result> { + let file = Ini::load_from_file_opt( + f, + ParseOption { + enabled_quote: false, + enabled_escape: false, + }, + )?; + match file.section(Some("Desktop Entry")) { + Some(desktop) => Ok(Desktop { + entry_type: desktop.get("Type").unwrap_or(&"".to_string()).to_string(), + name: desktop.get("Name").unwrap_or(&"".to_string()).to_string(), + term: desktop.get("Terminal").unwrap_or(&"".to_string()) == "true", + no_display: desktop.get("NoDisplay").unwrap_or(&"".to_string()) == "true", + hidden: desktop.get("Hidden").unwrap_or(&"".to_string()) == "true", + exec: desktop.get("Exec").map(|x| x.to_string()), + url: desktop.get("URL").map(|x| x.to_string()), + keywords: desktop + .get("Keywords") + .map(|x| { + x.split(';') + .map(|y| y.trim().to_string()) + .filter(|z| z != "") + .collect() + }) + .unwrap_or_else(|| vec![]), + }), + None => Err(Box::new(io_error::new( + ErrorKind::NotFound, + "no desktop entry in file", + ))), + } + } + + fn parse_dir(d: &str) -> Result, Box> { + let mut files: Vec = Vec::with_capacity(16); + for entry in WalkDir::new(d) { + let entry = entry?; + let path = entry.path(); + + if let Ok(d) = Desktop::parse(path.to_str().unwrap()) { + files.push(d) + } + } + + Ok(files) + } +} + +impl Ord for Desktop { + fn cmp(&self, other: &Self) -> Ordering { + self.name.cmp(&other.name) + } +} + +impl PartialOrd for Desktop { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialEq for Desktop { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} + +pub fn load_desktop_files() -> Vec { + let home = env::var_os("HOME").unwrap().into_string().unwrap(); + + let xdg_data_home = match env::var_os("XDG_DATA_HOME") { + Some(s) => s.into_string().unwrap(), + None => format!("{}/.local/share", home), + }; + let xdg_data_dirs = match env::var_os("XDG_DATA_DIRS") { + Some(s) => s.into_string().unwrap(), + None => "/usr/local/share:/usr/share".to_string(), + }; + + std::iter::once(xdg_data_home.as_str()) + .chain(xdg_data_dirs.split(':')) + .map(|p| Desktop::parse_dir(&format!("{}/applications", p))) + .filter_map(Result::ok) + .flatten() + .filter(|d| { + !d.hidden && !d.no_display && (d.entry_type == "Application" || d.entry_type == "Link") + }) + .unique_by(|x| x.name.clone()) + .collect() +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..6cf24c7 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod desktop; diff --git a/src/widgets/bar_widget.rs b/src/widgets/bar_widget.rs new file mode 100644 index 0000000..67164ba --- /dev/null +++ b/src/widgets/bar_widget.rs @@ -0,0 +1,98 @@ +use std::{ + cmp::min, + default::Default, +}; + +use crate::{ + buffer::BufferView, + color::Color, + draw::{draw_bar, draw_box}, + fonts::FontMap, + widgets::{Geometry, Widget}, +}; + +const BAR_FONT: &str = "sans"; +const BAR_SIZE: f32 = 24.; + +pub trait BarWidgetImpl { + fn get_dirty(&self) -> bool; + fn set_dirty(&mut self, dirty: bool); + fn update(&mut self); + fn name(&self) -> &'static str; + fn value(&self) -> f32; + fn color(&self) -> Color; +} + +pub struct BarWidget { + geometry: Geometry, + inner_widget: Box, +} + +impl BarWidget { + pub fn new(inner_widget: Box) -> BarWidget { + BarWidget { + geometry: Default::default(), + inner_widget, + } + } +} + +impl Widget for BarWidget { + fn geometry(&self) -> Geometry { + self.geometry + } + + fn get_dirty(&self) -> bool { + self.inner_widget.get_dirty() + } + + fn set_dirty(&mut self, dirty: bool) { + self.inner_widget.set_dirty(dirty) + } + + fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { + let font = fonts.get_font(BAR_FONT, BAR_SIZE); + let fg = Color::new(1., 1., 1., 1.); + let bg = Color::new(0., 0., 0., 1.); + + font.auto_draw_text(view, &bg, &fg, self.inner_widget.name()) + .unwrap(); + let size = BAR_SIZE.ceil() as u32; + let bar_offset = 4 * size; + let val = self.inner_widget.value(); + + let c = self.inner_widget.color(); + draw_bar( + &mut view.offset((bar_offset, 0)).unwrap(), + &c, + self.geometry.width - bar_offset, + size, + val, + ) + .unwrap(); + + draw_box( + &mut view.offset((bar_offset, 0)).unwrap(), + &c, + (self.geometry.width - bar_offset, size), + ) + .unwrap(); + self.geometry + } + + fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry { + let w = min(geometry.width, 768); + let mut x = geometry.x; + if geometry.width > w { + x += geometry.width - w; + } + self.geometry = Geometry { + x: x, + y: geometry.y, + width: w, + height: BAR_SIZE.ceil() as u32, + }; + self.geometry + } +} + diff --git a/src/widgets/battery.rs b/src/widgets/battery.rs new file mode 100644 index 0000000..57fb3fd --- /dev/null +++ b/src/widgets/battery.rs @@ -0,0 +1,144 @@ +use std::{ + thread, + time::Duration, + sync::{Arc, Mutex}, +}; + +use calloop::ping::Ping; + +use crate::{ + color::Color, + widgets::bar_widget::{ + BarWidget, + BarWidgetImpl, + }, +}; + +enum UpowerBatteryState { + Charging, + Discharging, + Empty, + Full, + NotCharging, + Unknown, +} + +struct InnerBattery { + value: f32, + state: UpowerBatteryState, +} + +fn start_monitor(inner: Arc>, ping: Ping) { + thread::spawn(move || { + use dbus::blocking::LocalConnection; + use dbus::blocking::stdintf::org_freedesktop_dbus::Properties; + use dbus::blocking::stdintf::org_freedesktop_dbus::PropertiesPropertiesChanged; + let conn = LocalConnection::new_system().unwrap(); + let device_path = "/org/freedesktop/UPower/devices/DisplayDevice"; + let proxy = conn.with_proxy("org.freedesktop.UPower", device_path, Duration::from_millis(500)); + + let capacity: f64 = proxy.get("org.freedesktop.UPower.Device", "Percentage").expect("unable to get property"); + let state: u32 = proxy.get("org.freedesktop.UPower.Device", "State").expect("unable to get state"); + + { + let mut inner = inner.lock().unwrap(); + inner.value = capacity as f32 / 100.; + inner.state = match state { + 1 => UpowerBatteryState::Charging, + 2 => UpowerBatteryState::Discharging, + 3 => UpowerBatteryState::Empty, + 4 => UpowerBatteryState::Full, + 5 => UpowerBatteryState::NotCharging, + 6 => UpowerBatteryState::Discharging, + _ => UpowerBatteryState::Unknown, + } + } + + + proxy.match_signal(move |c: PropertiesPropertiesChanged, _: &LocalConnection, _: &dbus::Message| { + if c.interface_name != "org.freedesktop.UPower.Device" { + return true; + } + let mut inner = inner.lock().unwrap(); + for (key, value) in c.changed_properties { + match key.as_str() { + "State" => { + inner.state = match value.0.as_u64().unwrap() { + 1 => UpowerBatteryState::Charging, + 2 => UpowerBatteryState::Discharging, + 3 => UpowerBatteryState::Empty, + 4 => UpowerBatteryState::Full, + 5 => UpowerBatteryState::NotCharging, + 6 => UpowerBatteryState::Discharging, + _ => UpowerBatteryState::Unknown, + }; + ping.ping(); + }, + "Percentage" => { + inner.value = value.0.as_f64().unwrap() as f32 / 100.; + ping.ping(); + }, + _ => (), + } + } + true + }).unwrap(); + + loop { conn.process(Duration::from_millis(60000)).unwrap(); } + }); +} + +pub struct Battery { + inner: Arc>, + dirty: bool, +} + +impl Battery { + pub fn new(ping: Ping) -> BarWidget { + let battery = Battery{ + inner: Arc::new(Mutex::new(InnerBattery{ + value: 0., + state: UpowerBatteryState::Unknown, + })), + dirty: false, + }; + start_monitor(battery.inner.clone(), ping); + BarWidget::new(Box::new(battery)) + } +} + + +impl BarWidgetImpl for Battery { + fn update(&mut self) {} + fn get_dirty(&self) -> bool { + self.dirty + } + fn set_dirty(&mut self, dirty: bool) { + self.dirty = dirty + } + fn name(&self) -> &'static str { + "battery" + } + fn value(&self) -> f32 { + let inner = self.inner.lock().unwrap(); + inner.value + } + fn color(&self) -> Color { + let inner = self.inner.lock().unwrap(); + match inner.state { + UpowerBatteryState::Discharging | UpowerBatteryState::Unknown => { + if inner.value > 0.1 { + Color::new(1.0, 1.0, 1.0, 1.0) + } else { + Color::new(1.0, 0.5, 0.0, 1.0) + } + } + UpowerBatteryState::Charging | UpowerBatteryState::Full => { + Color::new(0.5, 1.0, 0.5, 1.0) + } + UpowerBatteryState::NotCharging | UpowerBatteryState::Empty => { + Color::new(1.0, 0.5, 0.5, 1.0) + } + } + } +} diff --git a/src/widgets/clock.rs b/src/widgets/clock.rs new file mode 100644 index 0000000..91b4fce --- /dev/null +++ b/src/widgets/clock.rs @@ -0,0 +1,76 @@ +use std::default::Default; + +use crate::{ + buffer::BufferView, + color::Color, + fonts::FontMap, + widgets::{Geometry, Widget}, +}; + +use chrono::{Local, Timelike}; + +const CLOCK_FONT: &str = "sans"; +const CLOCK_SIZE: f32 = 96.; + +pub struct Clock { + dirty: bool, + geometry: Geometry, + digit_width: u32, + colon_width: u32, +} + +impl<'a> Clock { + pub fn new() -> Clock { + Clock { + dirty: true, + geometry: Default::default(), + digit_width: 0, + colon_width: 0, + } + } +} + +impl<'a> Widget for Clock { + fn set_dirty(&mut self, dirty: bool) { + self.dirty = dirty; + } + fn get_dirty(&self) -> bool { + self.dirty + } + + fn geometry(&self) -> Geometry { + self.geometry + } + + fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { + let time = Local::now().naive_local(); + let fg = Color::new(1., 1., 1., 1.); + let bg = Color::new(0., 0., 0., 1.); + + let digit = self.digit_width; + let colon = self.colon_width; + let font = fonts.get_font(CLOCK_FONT, CLOCK_SIZE); + font.draw_text_fixed_width( + view, + &bg, + &fg, + &[digit, digit, colon, digit, digit], + &format!("{:02}:{:02}", time.hour(), time.minute()), + ) + .unwrap(); + self.geometry + } + + fn geometry_update(&mut self, fonts: &mut FontMap, geometry: &Geometry) -> Geometry { + let font = fonts.get_font(CLOCK_FONT, CLOCK_SIZE); + self.digit_width = font.auto_widest("0123456789").unwrap(); + self.colon_width = font.auto_widest(":").unwrap(); + self.geometry = Geometry { + x: geometry.x, + y: geometry.y, + width: self.digit_width * 4 + self.colon_width, + height: CLOCK_SIZE.ceil() as u32, + }; + self.geometry + } +} diff --git a/src/widgets/date.rs b/src/widgets/date.rs new file mode 100644 index 0000000..7544308 --- /dev/null +++ b/src/widgets/date.rs @@ -0,0 +1,72 @@ +use std::default::Default; + +use crate::{ + buffer::BufferView, + color::Color, + fonts::FontMap, + widgets::{Geometry, Widget}, +}; + +use chrono::{Datelike, Local}; + +const DATE_FONT: &str = "sans"; +const DATE_SIZE: f32 = 40.; + +pub struct Date { + dirty: bool, + geometry: Geometry, +} + +impl Date { + pub fn new() -> Date { + Date { + dirty: true, + geometry: Default::default(), + } + } +} + +impl<'a> Widget for Date { + fn set_dirty(&mut self, dirty: bool) { + self.dirty = dirty; + } + fn get_dirty(&self) -> bool { + self.dirty + } + + fn geometry(&self) -> Geometry { + self.geometry + } + + fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { + let time = Local::now().naive_local(); + let fg = Color::new(1., 1., 1., 1.); + let bg = Color::new(0., 0., 0., 1.); + let mut date_line = view.offset((0, 8)).unwrap(); + let font = fonts.get_font(DATE_FONT, DATE_SIZE); + font.auto_draw_text( + &mut date_line, + &bg, + &fg, + &format!( + "{}, {:02}/{:02}/{:4}", + time.weekday(), + time.day(), + time.month(), + time.year() + ), + ) + .unwrap(); + self.geometry + } + + fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry { + self.geometry = Geometry { + x: geometry.x, + y: geometry.y, + width: 256, + height: DATE_SIZE.ceil() as u32, + }; + self.geometry + } +} diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs new file mode 100644 index 0000000..b4c0a8f --- /dev/null +++ b/src/widgets/launcher.rs @@ -0,0 +1,566 @@ +use std::cmp::{min, Ordering}; +use std::collections::HashMap; +use std::default::Default; +use std::process::{exit, Command}; + +use crate::{ + buffer::BufferView, + color::Color, + fonts::FontMap, + keyboard::{keysyms, KeyEvent}, + utils::desktop::{load_desktop_files, Desktop}, + widgets::{Geometry, Widget}, +}; + +use fuzzy_matcher::skim::SkimMatcherV2; +use fuzzy_matcher::FuzzyMatcher; +use unicode_segmentation::UnicodeSegmentation; +use wayland_client::{protocol::wl_keyboard, WEnum}; + +use std::sync::{Arc, Mutex}; +use std::thread; + +const LAUNCHER_FONT: &str = "monospace"; +const LAUNCHER_SIZE: f32 = 32.; + +struct Matcher { + matches: HashMap, +} + +impl Matcher { + fn new() -> Self { + Self { + matches: HashMap::new(), + } + } + + fn try_match(&mut self, dtop: Desktop, val: &str, input: &str, prio: f32) { + let fuzzy_matcher = SkimMatcherV2::default(); + if let Some(ma) = fuzzy_matcher.fuzzy_match(val, input) { + let ma = ((ma as f32) * prio) as i64; + if let Some(ma_old) = self.matches.get(&dtop) { + // Skip over new matches for the same program that are worse + // than the one we already have. + if ma_old > &ma { + return; + } + } + + self.matches.insert(dtop, ma); + } + } + + fn matches(&self) -> Vec { + let mut m = self + .matches + .iter() + .map(|(key, ma)| (*ma, key.clone())) + .collect::>(); + + m.sort_by(|(ma1, d1), (ma2, d2)| { + if ma1 > ma2 { + Ordering::Less + } else if ma1 < ma2 { + Ordering::Greater + } else { + d1.cmp(d2) + } + }); + + m.into_iter().map(|(_, x)| x).collect() + } +} + +enum PromptMode { + Normal, + Shell, + Calc, +} + +struct Prompt { + input: String, + mode: PromptMode, + cursor: usize, +} + +impl Prompt { + fn new() -> Prompt { + Prompt { + input: String::new(), + mode: PromptMode::Normal, + cursor: 0, + } + } + + fn move_cursor(&mut self, distance: isize) { + let new_cursor = self.cursor as isize + distance; + if new_cursor < 0 { + self.cursor = 0; + return; + } else if new_cursor > self.input.len() as isize { + self.cursor = self.input.len() + } else { + self.cursor = new_cursor as usize; + } + } + + fn append(&mut self, v: &str) { + if self.input.len() == 0 { + match v { + "!" => { + self.mode = PromptMode::Shell; + return; + }, + "=" => { + self.mode = PromptMode::Calc; + return; + }, + _ => (), + } + } + let indices: Vec<(usize, &str)> = self.input.grapheme_indices(true).collect(); + if self.cursor == indices.len() { + self.input += &v; + } else { + let index_at = indices[self.cursor].0; + self.input.insert_str(index_at, &v); + } + self.cursor += 1; + } + + fn backspace(&mut self) { + let mut indices: Vec<(usize, &str)> = self.input.grapheme_indices(true).collect(); + if !indices.is_empty() && self.cursor > 0 { + self.cursor -= 1; + indices.remove(self.cursor); + self.input = indices.iter().fold("".into(), |acc, el| acc + el.1); + } else { + self.mode = PromptMode::Normal; + } + } + + fn delete(&mut self) { + let mut indices: Vec<(usize, &str)> = self.input.grapheme_indices(true).collect(); + if !indices.is_empty() && self.cursor < indices.len() { + indices.remove(self.cursor); + self.input = indices.iter().fold("".into(), |acc, el| acc + el.1); + } else { + self.mode = PromptMode::Normal; + } + } + + fn home(&mut self) { + self.cursor = 0; + } + + fn end(&mut self) { + self.cursor = self.input.len(); + } +} + +trait InterfaceWidget { + fn trigger(&mut self, intf: &InnerInterface); + fn draw(&mut self, intf: &InnerInterface, fonts: &mut FontMap, view: &mut BufferView) -> Geometry; + fn update(&mut self, intf: &InnerInterface); +} + +struct Launcher { + next_token: Option, + options: Arc>>, + matches: Vec, +} + +impl Launcher { + fn new() -> Launcher { + let options = Arc::new(Mutex::new(Vec::new())); + { + let options = Arc::clone(&options); + thread::spawn(move || { + let mut options = options.lock().unwrap(); + *options = load_desktop_files(); + }); + } + + Launcher { + next_token: None, + options: options, + matches: Vec::new(), + } + } + + fn exec(&self, args: Vec) { + let mut cmd = Command::new(args[0].clone()); + if let Some(token) = &self.next_token { + cmd.env("XDG_ACTIVATION_TOKEN", token); + } + cmd.args(&args[1..]).spawn().unwrap(); + exit(0); + } +} + +impl InterfaceWidget for Launcher { + fn trigger(&mut self, intf: &InnerInterface) { + if self.matches.len() > intf.selection { + let d = &self.matches[intf.selection]; + if let Some(exec) = &d.exec { + let exec = exec + .replace("%f", "") + .replace("%F", "") + .replace("%u", "") + .replace("%U", ""); + + let lexed = shlex::split(&exec).unwrap(); + if !lexed.is_empty() { + self.exec(lexed); + } + } + } + } + + fn update(&mut self, intf: &InnerInterface) { + let mut matcher = Matcher::new(); + let options = self.options.lock().unwrap(); + for desktop in options.iter() { + matcher.try_match( + desktop.clone(), + &desktop.name.to_lowercase(), + &intf.prompt.input.to_lowercase(), + 1.0, + ); + for keyword in desktop.keywords.iter() { + matcher.try_match( + desktop.clone(), + &keyword.to_lowercase(), + &intf.prompt.input.to_lowercase(), + 0.5, + ); + } + } + + self.matches = matcher.matches(); + } + + fn draw(&mut self, intf: &InnerInterface, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { + let fg = Color::new(1., 1., 1., 1.); + let bg = Color::new(0., 0., 0., 1.); + + let line_height = LAUNCHER_SIZE.ceil() as u32; + + // Draw line + let mut prompt_offset = intf.geometry.height - line_height; + let mut prompt_line = view.offset((0, prompt_offset)).unwrap(); + let mut prompt_border = prompt_line.limit((intf.geometry.width, 1)).unwrap(); + prompt_border.memset(&fg); + + // Draw prompt + let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE); + font.auto_draw_text_with_cursor( + &mut prompt_line, + &bg, + &fg, + &format!(" {}", &intf.prompt.input), + intf.prompt.cursor + 3, + ) + .unwrap(); + + let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE); + font.auto_draw_text( + &mut prompt_line, + &bg, + &fg, + ">", + ) + .unwrap(); + + // Draw entries + let dimfg = Color::new(0.5, 0.5, 0.5, 1.0); + prompt_offset -= 16; + + for (idx, m) in self.matches.iter().enumerate() { + if prompt_offset < line_height { + break; + } + prompt_offset -= line_height; + let mut line = view.offset((0, prompt_offset)).unwrap(); + + if idx == intf.selection { + let fuzzy_matcher = SkimMatcherV2::default(); + let (_, indices) = fuzzy_matcher + .fuzzy_indices(&m.name.to_lowercase(), &intf.prompt.input.to_lowercase()) + .unwrap_or((0, vec![])); + + let mut colors = Vec::with_capacity(m.name.len()); + for pos in 0..m.name.len() { + if indices.contains(&pos) { + colors.push(Color::new(1.0, 0.65, 0., 1.0)); + } else { + colors.push(Color::new(0.75, 0.75, 0.75, 1.0)); + } + } + font.auto_draw_text_individual_colors(&mut line, &bg, &colors, &m.name) + .unwrap(); + } else { + font.auto_draw_text(&mut line, &bg, &dimfg, &m.name) + .unwrap(); + } + } + + let content_height = min(intf.geometry.height, (self.matches.len() + 1) as u32 * line_height + 16); + Geometry{ + x: intf.geometry.x, + y: intf.geometry.y + intf.geometry.height - content_height, + width: intf.geometry.width, + height: content_height, + } + } +} + +struct Shell (); + +impl InterfaceWidget for Shell { + fn trigger(&mut self, intf: &InnerInterface) { + let args = vec![ + "/bin/sh".to_string(), + "-c".to_string(), + intf.prompt.input.clone(), + ]; + Command::new(args[0].clone()) + .args(&args[1..]) + .spawn() + .unwrap(); + exit(0); + } + + fn update(&mut self, _intf: &InnerInterface) {} + + fn draw(&mut self, intf: &InnerInterface, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { + let fg = Color::new(1., 1., 1., 1.); + let bg = Color::new(0., 0., 0., 1.); + + let line_height = LAUNCHER_SIZE.ceil() as u32; + + // Draw line + let prompt_offset = intf.geometry.height - line_height; + let mut prompt_line = view.offset((0, prompt_offset)).unwrap(); + let mut prompt_border = prompt_line.limit((intf.geometry.width, 1)).unwrap(); + prompt_border.memset(&fg); + + // Draw prompt + let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE); + font.auto_draw_text_with_cursor( + &mut prompt_line, + &bg, + &fg, + &format!(" {}", &intf.prompt.input), + intf.prompt.cursor + 3, + ) + .unwrap(); + let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE); + font.auto_draw_text( + &mut prompt_line, + &bg, + &Color::new(1., 0.75, 0.5, 1.), + "!", + ) + .unwrap(); + + Geometry{ + x: intf.geometry.x, + y: intf.geometry.y + intf.geometry.height - line_height, + width: intf.geometry.width, + height: line_height, + } + } +} + +struct Calc { + old: Vec, + result: Option, +} + +impl InterfaceWidget for Calc { + fn trigger(&mut self, intf: &InnerInterface) { + if let Some(res) = &self.result { + self.old.push(format!("{} = {}", &intf.prompt.input, res)); + } + } + + fn update(&mut self, intf: &InnerInterface) { + let res = rcalc_lib::parse::eval(&intf.prompt.input, &mut rcalc_lib::parse::CalcState::new()) + .map(|x| format!("{}", x)) + .map_err(|x| format!("{}", x)); + self.result = match res { + Ok(v) => Some(v), + Err(_) => None, + }; + } + + fn draw(&mut self, intf: &InnerInterface, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { + let fg = Color::new(1., 1., 1., 1.); + let bg = Color::new(0., 0., 0., 1.); + + let line_height = LAUNCHER_SIZE.ceil() as u32; + + // Draw line + let mut prompt_offset = intf.geometry.height - (LAUNCHER_SIZE.ceil() as u32); + let mut prompt_line = view.offset((0, prompt_offset)).unwrap(); + let mut prompt_border = prompt_line.limit((intf.geometry.width, 1)).unwrap(); + prompt_border.memset(&fg); + + // Draw prompt + let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE); + let res_off = font.auto_draw_text_with_cursor( + &mut prompt_line, + &bg, + &fg, + &format!(" {} ", &intf.prompt.input), + intf.prompt.cursor + 3, + ) + .unwrap(); + + let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE); + font.auto_draw_text( + &mut prompt_line, + &bg, + &Color::new(1., 0.75, 0.5, 1.), + "=", + ) + .unwrap(); + + if let Some(res) = &self.result { + let resfg = Color::new(1.0, 0.65, 0., 1.0); + let mut result_line = view.offset((res_off.0, prompt_offset)).unwrap(); + font.auto_draw_text(&mut result_line, &bg, &resfg, &format!(" = {}", &res)).unwrap(); + } + + for m in self.old.iter().rev() { + let dimfg = Color::new(0.5, 0.5, 0.5, 1.0); + if prompt_offset < LAUNCHER_SIZE.ceil() as u32 { + break; + } + prompt_offset -= LAUNCHER_SIZE.ceil() as u32; + let mut result_line = view.offset((0, prompt_offset)).unwrap(); + font.auto_draw_text(&mut result_line, &bg, &dimfg, &m).unwrap(); + } + + let content_height = min(intf.geometry.height, (self.old.len() + 1) as u32 * line_height + 16); + Geometry{ + x: intf.geometry.x, + y: intf.geometry.y + intf.geometry.height - content_height, + width: intf.geometry.width, + height: content_height, + } + } +} + +struct InnerInterface { + dirty: bool, + geometry: Geometry, + selection: usize, + prompt: Prompt, +} + +pub struct Interface { + launcher: Launcher, + shell: Shell, + calc: Calc, + inner: InnerInterface, +} + +impl Interface { + pub fn new() -> Interface { + Interface{ + launcher: Launcher::new(), + shell: Shell{}, + calc: Calc{ + old: Vec::new(), + result: None, + }, + inner: InnerInterface{ + dirty: false, + geometry: Default::default(), + selection: 0, + prompt: Prompt::new(), + }, + } + } +} + +impl Widget for Interface { + fn set_dirty(&mut self, dirty: bool) { + self.inner.dirty = dirty; + } + fn get_dirty(&self) -> bool { + self.inner.dirty + } + + fn geometry(&self) -> Geometry { + self.inner.geometry + } + + fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { + match self.inner.prompt.mode { + PromptMode::Shell => self.shell.draw(&self.inner, fonts, view), + PromptMode::Normal => self.launcher.draw(&self.inner, fonts, view), + PromptMode::Calc => self.calc.draw(&self.inner, fonts, view), + } + } + + fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry { + self.inner.geometry = geometry.clone(); + self.inner.geometry + } + + fn keyboard_input(&mut self, event: &KeyEvent) { + if event.state != WEnum::Value(wl_keyboard::KeyState::Pressed) { + return; + } + self.inner.dirty = true; + let widget = match self.inner.prompt.mode { + PromptMode::Shell => &mut self.shell as &mut dyn InterfaceWidget, + PromptMode::Normal => &mut self.launcher as &mut dyn InterfaceWidget, + PromptMode::Calc => &mut self.calc as &mut dyn InterfaceWidget, + }; + match event.keysym { + keysyms::XKB_KEY_a if event.modifiers.ctrl => self.inner.prompt.home(), + keysyms::XKB_KEY_e if event.modifiers.ctrl => self.inner.prompt.end(), + keysyms::XKB_KEY_Home => self.inner.prompt.home(), + keysyms::XKB_KEY_End => self.inner.prompt.end(), + keysyms::XKB_KEY_BackSpace => { + self.inner.prompt.backspace(); + self.inner.selection = 0; + widget.update(&self.inner); + } + keysyms::XKB_KEY_Delete => { + self.inner.prompt.delete(); + self.inner.selection = 0; + widget.update(&self.inner); + } + keysyms::XKB_KEY_Return => widget.trigger(&self.inner), + keysyms::XKB_KEY_Left => self.inner.prompt.move_cursor(-1), + keysyms::XKB_KEY_Right => self.inner.prompt.move_cursor(1), + keysyms::XKB_KEY_Up => { + self.inner.selection += 1; + } + keysyms::XKB_KEY_Down => { + if self.inner.selection > 0 { + self.inner.selection -= 1; + } + } + _ => { + if let Some(utf8) = &event.utf8 { + self.inner.prompt.append(&utf8); + self.inner.selection = 0; + widget.update(&self.inner); + } else { + self.inner.dirty = false; + } + } + } + } + + fn token_update(&mut self, token: &str) { + self.launcher.next_token = Some(token.to_string()); + } +} + diff --git a/src/widgets/line.rs b/src/widgets/line.rs new file mode 100644 index 0000000..705097b --- /dev/null +++ b/src/widgets/line.rs @@ -0,0 +1,39 @@ +use std::default::Default; + +use crate::{ + buffer::BufferView, + color::Color, + fonts::FontMap, + widgets::{Geometry, Widget}, +}; + +#[derive(Default)] +pub struct Line { + geometry: Geometry, +} + +impl Line { + pub fn new() -> Line { + Default::default() + } +} + +impl Widget for Line { + fn geometry(&self) -> Geometry { + self.geometry + } + fn draw(&mut self, _fonts: &mut FontMap, view: &mut BufferView) -> Geometry { + view.memset(&Color::new(1., 1., 1., 1.)); + self.geometry + } + + fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry { + self.geometry = Geometry { + x: geometry.x, + y: geometry.y, + width: geometry.width, + height: 1, + }; + self.geometry + } +} diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs new file mode 100644 index 0000000..a95977a --- /dev/null +++ b/src/widgets/mod.rs @@ -0,0 +1,17 @@ +mod bar_widget; +mod battery; +mod clock; +mod date; +mod launcher; +mod line; +mod widget; + +pub use battery::Battery; +pub use clock::Clock; +pub use date::Date; +pub use launcher::Interface; +pub use line::Line; +pub use widget::{ + Geometry, HorizontalLayout, IndexedLayout, Layout, Margin, VerticalLayout, Widget, + WidgetUpdater, +}; diff --git a/src/widgets/widget.rs b/src/widgets/widget.rs new file mode 100644 index 0000000..d1c40b3 --- /dev/null +++ b/src/widgets/widget.rs @@ -0,0 +1,166 @@ +use std::cmp::{min, max}; +use std::fmt; + +use crate::{buffer::BufferView, fonts::FontMap, keyboard::KeyEvent}; + +#[derive(Debug, Default, Clone, Copy)] +pub struct Geometry { + pub x: u32, + pub y: u32, + pub width: u32, + pub height: u32, +} + +impl fmt::Display for Geometry { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "(x: {}, y: {}, width: {}, height: {})", + self.x, self.y, self.width, self.height + ) + } +} + +impl Geometry { + pub fn new() -> Geometry { + Geometry{ + x: 0, + y: 0, + width: 0, + height: 0, + } + } + + pub fn expand(&self, other: Geometry) -> Geometry { + let x = min(self.x, other.x); + let y = min(self.y, other.y); + Geometry { + x, y, + width: max(self.width + self.x, other.width + other.x) - x, + height: max(self.height + self.y, other.height + other.height) - y, + } + } +} + +pub trait Widget { + fn get_dirty(&self) -> bool { + false + } + fn set_dirty(&mut self, _dirty: bool) {} + fn geometry(&self) -> Geometry; + fn geometry_update(&mut self, fonts: &mut FontMap, geometry: &Geometry) -> Geometry; + fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry; + fn keyboard_input(&mut self, _event: &KeyEvent) {} + fn token_update(&mut self, _token: &str) {} +} + +pub trait Layout { + fn geometry_update( + &mut self, + fonts: &mut FontMap, + geometry: &Geometry, + user_data: &mut U, + ) -> Geometry; +} +pub struct HorizontalLayout { + pub widgets: Vec>>, +} + +impl Layout for HorizontalLayout { + fn geometry_update( + &mut self, + fonts: &mut FontMap, + geometry: &Geometry, + user_data: &mut U, + ) -> Geometry { + let mut geo = geometry.clone(); + let mut max_width = 0; + let mut max_height = 0; + for w in self.widgets.iter_mut() { + let result = w.geometry_update(fonts, &geo, user_data); + geo.x = result.x + result.width; + geo.width -= result.width; + max_width += result.width; + max_height = max(result.height, max_height); + } + geo.width = max_width; + geo.height = max_height; + geo + } +} + +pub struct VerticalLayout { + pub widgets: Vec>>, +} + +impl Layout for VerticalLayout { + fn geometry_update( + &mut self, + fonts: &mut FontMap, + geometry: &Geometry, + user_data: &mut U, + ) -> Geometry { + let mut geo = geometry.clone(); + let mut max_width = 0; + let mut max_height = 0; + for w in self.widgets.iter_mut() { + let result = w.geometry_update(fonts, &geo, user_data); + geo.y = result.y + result.height; + geo.height -= result.height; + max_width = max(result.width, max_width); + max_height += result.height; + } + geo.width = max_width; + geo.height = max_height; + geo + } +} + +pub trait WidgetUpdater { + fn geometry_update(&mut self, idx: usize, fonts: &mut FontMap, geometry: &Geometry) + -> Geometry; +} + +pub struct IndexedLayout { + pub widget_idx: usize, +} + +impl Layout for IndexedLayout { + fn geometry_update( + &mut self, + fonts: &mut FontMap, + geometry: &Geometry, + user_data: &mut U, + ) -> Geometry { + user_data.geometry_update(self.widget_idx, fonts, geometry) + } +} + +pub struct Margin { + pub widget: Box>, + pub margin: (u32, u32, u32, u32), +} + +impl Layout for Margin { + fn geometry_update( + &mut self, + fonts: &mut FontMap, + geometry: &Geometry, + user_data: &mut U, + ) -> Geometry { + let geo = Geometry{ + x: geometry.x + self.margin.0, + y: geometry.y + self.margin.1, + width: geometry.width - self.margin.0 - self.margin.2, + height: geometry.height - self.margin.1 - self.margin.3, + }; + + let out = self.widget.geometry_update(fonts, &geo, user_data); + Geometry { + x: out.x - self.margin.0, + y: out.y - self.margin.1, + width: out.width + self.margin.0 + self.margin.2, + height: out.height + self.margin.1 + self.margin.3, + } + } +} From 0a4544a608d17b6293132bca8c21ddf84ff8d50b Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Wed, 11 Jan 2023 23:54:14 +0100 Subject: [PATCH 02/57] launcher: Add ctrl k/u handling --- src/keyboard/keysyms.rs | 5 ++-- src/widgets/launcher.rs | 54 ++++++++++++++++++++++++++++++++++------- 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/src/keyboard/keysyms.rs b/src/keyboard/keysyms.rs index 276584a..bbde9a2 100644 --- a/src/keyboard/keysyms.rs +++ b/src/keyboard/keysyms.rs @@ -13,7 +13,8 @@ pub const XKB_KEY_Down: u32 = 0xff54; /* Move down, down arrow */ pub const XKB_KEY_End: u32 = 0xff57; /* EOL */ //pub const XKB_KEY_ISO_Left_Tab: u32 = 0xfe20; -pub const XKB_KEY_e: u32 = 0x0065; /* U+0065 LATIN SMALL LETTER E */ pub const XKB_KEY_a: u32 = 0x0061; /* U+0061 LATIN SMALL LETTER A */ +pub const XKB_KEY_e: u32 = 0x0065; /* U+0065 LATIN SMALL LETTER E */ //pub const XKB_KEY_c: u32 = 0x0063; /* U+0063 LATIN SMALL LETTER C */ -//pub const XKB_KEY_u: u32 = 0x0075; /* U+0075 LATIN SMALL LETTER U */ +pub const XKB_KEY_k: u32 = 0x006b; /* U+006B LATIN SMALL LETTER K */ +pub const XKB_KEY_u: u32 = 0x0075; /* U+0075 LATIN SMALL LETTER U */ diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs index b4c0a8f..0da7e35 100644 --- a/src/widgets/launcher.rs +++ b/src/widgets/launcher.rs @@ -130,20 +130,46 @@ impl Prompt { fn backspace(&mut self) { let mut indices: Vec<(usize, &str)> = self.input.grapheme_indices(true).collect(); - if !indices.is_empty() && self.cursor > 0 { - self.cursor -= 1; - indices.remove(self.cursor); - self.input = indices.iter().fold("".into(), |acc, el| acc + el.1); + if indices.is_empty() { + self.mode = PromptMode::Normal; + return; + } + if self.cursor == 0 { + return; + } + self.cursor -= 1; + indices.remove(self.cursor); + self.input = indices.iter().fold("".into(), |acc, el| acc + el.1); + } + + fn delete(&mut self) { + let mut indices: Vec<(usize, &str)> = self.input.grapheme_indices(true).collect(); + if indices.is_empty() { + self.mode = PromptMode::Normal; + return; + } + if self.cursor >= indices.len() { + return; + } + + indices.remove(self.cursor); + self.input = indices.iter().fold("".into(), |acc, el| acc + el.1); + } + + fn clear_right(&mut self) { + let indices: Vec<(usize, &str)> = self.input.grapheme_indices(true).collect(); + if !indices.is_empty() { + self.input = indices[0..self.cursor].iter().fold("".into(), |acc, el| acc + el.1); } else { self.mode = PromptMode::Normal; } } - fn delete(&mut self) { - let mut indices: Vec<(usize, &str)> = self.input.grapheme_indices(true).collect(); - if !indices.is_empty() && self.cursor < indices.len() { - indices.remove(self.cursor); - self.input = indices.iter().fold("".into(), |acc, el| acc + el.1); + fn clear_left(&mut self) { + let indices: Vec<(usize, &str)> = self.input.grapheme_indices(true).collect(); + if !indices.is_empty() { + self.input = indices[self.cursor..indices.len()].iter().fold("".into(), |acc, el| acc + el.1); + self.cursor = 0; } else { self.mode = PromptMode::Normal; } @@ -524,6 +550,16 @@ impl Widget for Interface { match event.keysym { keysyms::XKB_KEY_a if event.modifiers.ctrl => self.inner.prompt.home(), keysyms::XKB_KEY_e if event.modifiers.ctrl => self.inner.prompt.end(), + keysyms::XKB_KEY_u if event.modifiers.ctrl => { + self.inner.prompt.clear_left(); + self.inner.selection = 0; + widget.update(&self.inner); + }, + keysyms::XKB_KEY_k if event.modifiers.ctrl => { + self.inner.prompt.clear_right(); + self.inner.selection = 0; + widget.update(&self.inner); + }, keysyms::XKB_KEY_Home => self.inner.prompt.home(), keysyms::XKB_KEY_End => self.inner.prompt.end(), keysyms::XKB_KEY_BackSpace => { From dd790468ad6bb88c88ecf257f78ebfb6ee1ca018 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Wed, 11 Jan 2023 23:54:39 +0100 Subject: [PATCH 03/57] battery: Minor cleanup --- src/widgets/bar_widget.rs | 1 - src/widgets/battery.rs | 24 ++++++++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/widgets/bar_widget.rs b/src/widgets/bar_widget.rs index 67164ba..f281d9c 100644 --- a/src/widgets/bar_widget.rs +++ b/src/widgets/bar_widget.rs @@ -17,7 +17,6 @@ const BAR_SIZE: f32 = 24.; pub trait BarWidgetImpl { fn get_dirty(&self) -> bool; fn set_dirty(&mut self, dirty: bool); - fn update(&mut self); fn name(&self) -> &'static str; fn value(&self) -> f32; fn color(&self) -> Color; diff --git a/src/widgets/battery.rs b/src/widgets/battery.rs index 57fb3fd..862df51 100644 --- a/src/widgets/battery.rs +++ b/src/widgets/battery.rs @@ -6,6 +6,14 @@ use std::{ use calloop::ping::Ping; +use dbus::blocking::{ + LocalConnection, + stdintf::org_freedesktop_dbus::{ + Properties, + PropertiesPropertiesChanged, + }, +}; + use crate::{ color::Color, widgets::bar_widget::{ @@ -29,10 +37,9 @@ struct InnerBattery { } fn start_monitor(inner: Arc>, ping: Ping) { - thread::spawn(move || { - use dbus::blocking::LocalConnection; - use dbus::blocking::stdintf::org_freedesktop_dbus::Properties; - use dbus::blocking::stdintf::org_freedesktop_dbus::PropertiesPropertiesChanged; + thread::Builder::new() + .name("battmon".to_string()) + .spawn(move || { let conn = LocalConnection::new_system().unwrap(); let device_path = "/org/freedesktop/UPower/devices/DisplayDevice"; let proxy = conn.with_proxy("org.freedesktop.UPower", device_path, Duration::from_millis(500)); @@ -85,7 +92,7 @@ fn start_monitor(inner: Arc>, ping: Ping) { }).unwrap(); loop { conn.process(Duration::from_millis(60000)).unwrap(); } - }); + }).unwrap(); } pub struct Battery { @@ -109,7 +116,6 @@ impl Battery { impl BarWidgetImpl for Battery { - fn update(&mut self) {} fn get_dirty(&self) -> bool { self.dirty } @@ -127,10 +133,12 @@ impl BarWidgetImpl for Battery { let inner = self.inner.lock().unwrap(); match inner.state { UpowerBatteryState::Discharging | UpowerBatteryState::Unknown => { - if inner.value > 0.1 { + if inner.value > 0.25 { Color::new(1.0, 1.0, 1.0, 1.0) - } else { + } else if inner.value > 0.1 { Color::new(1.0, 0.5, 0.0, 1.0) + } else { + Color::new(1.0, 0.0, 0.0, 1.0) } } UpowerBatteryState::Charging | UpowerBatteryState::Full => { From 25644c2b15af31b9065c48a63b903b1b51d1a1f8 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Wed, 11 Jan 2023 23:54:52 +0100 Subject: [PATCH 04/57] backlight: Add --- src/main.rs | 21 ++++++++---- src/widgets/backlight.rs | 69 ++++++++++++++++++++++++++++++++++++++++ src/widgets/mod.rs | 2 ++ 3 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 src/widgets/backlight.rs diff --git a/src/main.rs b/src/main.rs index 1841b40..506ee86 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,7 +30,7 @@ use color::Color; use fonts::FontMap; use keyboard::Keyboard; use widgets::{ - Battery, Clock, Date, Geometry, HorizontalLayout, IndexedLayout, Interface, Layout, Line, + Battery, Backlight, Clock, Date, Geometry, HorizontalLayout, IndexedLayout, Interface, Layout, Line, Margin, VerticalLayout, Widget, WidgetUpdater, }; @@ -150,6 +150,7 @@ fn main() { let line = Box::new(Line::new()); let launcher = Box::new(Interface::new()); let battery = Box::new(Battery::new(ping_sender)); + let backlight = Box::new(Backlight::new("intel_backlight")); let mut event_loop: EventLoop = EventLoop::try_new().expect("Failed to initialize the event loop!"); @@ -200,7 +201,7 @@ fn main() { configured: false, dimensions: (320, 240), bufmgr: bufmgr, - widgets: vec![clock, date, battery, line, launcher], + widgets: vec![clock, date, battery, backlight, line, launcher], keyboard: Keyboard::new(), fonts: MaybeFontMap::Waiting(font_thread), }; @@ -474,14 +475,22 @@ impl Dispatch for State { widget: Box::new(IndexedLayout { widget_idx: 1 }), margin: (16, 8, 0, 0), }), - Box::new(Margin { - widget: Box::new(IndexedLayout { widget_idx: 2 }), - margin: (16, 8, 8, 0), + Box::new(VerticalLayout{ + widgets: vec![ + Box::new(Margin { + widget: Box::new(IndexedLayout { widget_idx: 2 }), + margin: (16, 8, 8, 0), + }), + Box::new(Margin { + widget: Box::new(IndexedLayout { widget_idx: 3 }), + margin: (16, 8, 8, 0), + }), + ], }), ], }), - Box::new(IndexedLayout { widget_idx: 3 }), Box::new(IndexedLayout { widget_idx: 4 }), + Box::new(IndexedLayout { widget_idx: 5 }), ], }; diff --git a/src/widgets/backlight.rs b/src/widgets/backlight.rs new file mode 100644 index 0000000..b972ee4 --- /dev/null +++ b/src/widgets/backlight.rs @@ -0,0 +1,69 @@ +use std::{ + fs, + io::{ErrorKind, Error}, + path::{Path, PathBuf}, +}; + +use crate::{ + color::Color, + widgets::bar_widget::{ + BarWidget, + BarWidgetImpl, + }, +}; + +fn read_file_as_u64(path: &Path) -> Result { + let mut s = fs::read_to_string(path)?; + s.pop(); + s.parse::() + .map_err(|_e| Error::new(ErrorKind::Other, "unable to parse value")) +} + +pub struct Backlight { + device_path: PathBuf, + cur: u64, + max: u64, + dirty: bool, +} + +impl Backlight { + fn update(&mut self) { + self.cur = read_file_as_u64(self.device_path.join("brightness").as_path()).unwrap(); + self.max = read_file_as_u64(self.device_path.join("max_brightness").as_path()).unwrap(); + } + + pub fn new(path: &str) -> BarWidget { + let mut dev = Backlight{ + device_path: Path::new("/sys/class/backlight").to_path_buf().join(path), + cur: 0, + max: 0, + dirty: false, + }; + + dev.update(); + BarWidget::new(Box::new(dev)) + } +} + +impl BarWidgetImpl for Backlight { + fn get_dirty(&self) -> bool { + self.dirty + } + fn set_dirty(&mut self, dirty: bool) { + self.dirty = dirty + } + fn name(&self) -> &'static str { + "backlight" + } + fn value(&self) -> f32 { + if self.cur > self.max { + // what. + return 1.0; + } + + self.cur as f32 / self.max as f32 + } + fn color(&self) -> Color { + Color::new(1.0, 1.0, 1.0, 1.0) + } +} diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index a95977a..679eb67 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,5 +1,6 @@ mod bar_widget; mod battery; +mod backlight; mod clock; mod date; mod launcher; @@ -7,6 +8,7 @@ mod line; mod widget; pub use battery::Battery; +pub use backlight::Backlight; pub use clock::Clock; pub use date::Date; pub use launcher::Interface; From d3002d21808c2b1559657caeadb827c83dd9ce8d Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Thu, 12 Jan 2023 00:13:08 +0100 Subject: [PATCH 05/57] Exit when no longer activated --- src/main.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 506ee86..ed77192 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,7 +34,7 @@ use widgets::{ Margin, VerticalLayout, Widget, WidgetUpdater, }; -use std::{cell::RefCell, env, mem, rc::Rc, thread}; +use std::{cell::RefCell, env, mem, rc::Rc, thread, process::exit}; enum MaybeFontMap { Waiting(thread::JoinHandle), @@ -66,6 +66,7 @@ impl MaybeFontMap { struct State { running: bool, dirty: bool, + activated: bool, base_surface: Option, wm_base: Option, xdg_surface: Option<(xdg_surface::XdgSurface, xdg_toplevel::XdgToplevel)>, @@ -193,6 +194,7 @@ fn main() { let mut state = State { running: true, dirty: true, + activated: false, base_surface: None, wm_base: None, xdg_surface: None, @@ -459,10 +461,19 @@ impl Dispatch for State { _: &QueueHandle, ) { match event { - xdg_toplevel::Event::Configure { width, height, .. } => { + xdg_toplevel::Event::Configure { width, height, states, .. } => { if (width, height) == (0, 0) { return; } + let activated = states.iter().find(|&st| *st as u32 == xdg_toplevel::State::Activated as u32).is_some(); + + if activated { + state.activated = true; + } else if state.activated && !activated { + exit(0); + } + + if state.dimensions != (width, height) { state.bufmgr.clear_buffers(); state.dimensions = (width, height); From be6ee9899c22e34608607fbecd51171b007407c4 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Thu, 12 Jan 2023 00:13:15 +0100 Subject: [PATCH 06/57] cargo update --- Cargo.lock | 43 ++++++++++++------------------------------- Cargo.toml | 4 ++-- 2 files changed, 14 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 19c8739..de1ca1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,9 +57,9 @@ checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "calloop" -version = "0.10.4" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19457a0da465234abd76134a5c2a910c14bd3c5558463e4396ab9a37a328e465" +checksum = "1a59225be45a478d772ce015d9743e49e92798ece9e34eda9a6aa2a6a7f40192" dependencies = [ "log", "nix 0.25.1", @@ -252,21 +252,6 @@ dependencies = [ "yeslogic-fontconfig-sys", ] -[[package]] -name = "futures-channel" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" - [[package]] name = "fuzzy-matcher" version = "0.3.7" @@ -960,14 +945,14 @@ checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "wayland-backend" -version = "0.1.0-beta.14" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50598bd298e1e01de02ad3cdcfcfcdb961189e40fea41d9325bc07ba81f8740d" +checksum = "fb23bfea266c92bb051ea36cce0eb1a52b743dc1c5f168021947eda79764656d" dependencies = [ "cc", "downcast-rs", "io-lifetimes", - "nix 0.25.1", + "nix 0.26.1", "scoped-tls", "smallvec", "wayland-sys", @@ -975,26 +960,23 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.30.0-beta.14" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae06ffbf163db5cc6b40f9aa0ec66e7e1c22b979a327daf209814277f18834e1" +checksum = "1a925bd68c8b652af4e6f11a32410bd31bf84061c5ef279ed081520c60f203b4" dependencies = [ "bitflags", "calloop", - "futures-channel", - "futures-core", "log", - "nix 0.25.1", - "thiserror", + "nix 0.26.1", "wayland-backend", "wayland-scanner", ] [[package]] name = "wayland-protocols" -version = "0.30.0-beta.14" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8491945e2705791d58695ae166bb05bfb1f01f2b7b848faa73966e3462269a2f" +checksum = "7fefbeb8a360abe67ab7c2efe1d297a1a50ee011f5460791bc18870c26bb84e2" dependencies = [ "bitflags", "wayland-backend", @@ -1004,14 +986,13 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.30.0-beta.14" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28407261ad2bab693652acd84e8abcf1d2bef7270b5cc840ec8c6bed7c715838" +checksum = "4834c14b3edf1d9986c83ca79b1e7e3afbe9874c7c144702f6467063259ce45d" dependencies = [ "proc-macro2", "quick-xml", "quote", - "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 686767f..411d477 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -wayland-client = { version = "0.30.0-beta.13", features = ["calloop", "log"] } -wayland-protocols = { version = "0.30.0-beta.13", features = ["client", "unstable", "staging"] } +wayland-client = { version = "0.30", features = ["calloop", "log"] } +wayland-protocols = { version = "0.30", features = ["client", "unstable", "staging"] } tempfile = "3.3" nix = "0.26" memmap = "0.7" From b2a9475e4c77a9b2028220c2dec2cae2b2285f08 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Thu, 12 Jan 2023 00:13:27 +0100 Subject: [PATCH 07/57] cargo fmt --- src/keyboard/keysyms.rs | 2 +- src/main.rs | 25 +++++--- src/widgets/backlight.rs | 9 +-- src/widgets/bar_widget.rs | 6 +- src/widgets/battery.rs | 129 ++++++++++++++++++++------------------ src/widgets/launcher.rs | 128 +++++++++++++++++++++---------------- src/widgets/mod.rs | 4 +- src/widgets/widget.rs | 9 +-- 8 files changed, 170 insertions(+), 142 deletions(-) diff --git a/src/keyboard/keysyms.rs b/src/keyboard/keysyms.rs index bbde9a2..898303f 100644 --- a/src/keyboard/keysyms.rs +++ b/src/keyboard/keysyms.rs @@ -16,5 +16,5 @@ pub const XKB_KEY_End: u32 = 0xff57; /* EOL */ pub const XKB_KEY_a: u32 = 0x0061; /* U+0061 LATIN SMALL LETTER A */ pub const XKB_KEY_e: u32 = 0x0065; /* U+0065 LATIN SMALL LETTER E */ //pub const XKB_KEY_c: u32 = 0x0063; /* U+0063 LATIN SMALL LETTER C */ -pub const XKB_KEY_k: u32 = 0x006b; /* U+006B LATIN SMALL LETTER K */ +pub const XKB_KEY_k: u32 = 0x006b; /* U+006B LATIN SMALL LETTER K */ pub const XKB_KEY_u: u32 = 0x0075; /* U+0075 LATIN SMALL LETTER U */ diff --git a/src/main.rs b/src/main.rs index ed77192..b33d0a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,11 +30,11 @@ use color::Color; use fonts::FontMap; use keyboard::Keyboard; use widgets::{ - Battery, Backlight, Clock, Date, Geometry, HorizontalLayout, IndexedLayout, Interface, Layout, Line, - Margin, VerticalLayout, Widget, WidgetUpdater, + Backlight, Battery, Clock, Date, Geometry, HorizontalLayout, IndexedLayout, Interface, Layout, + Line, Margin, VerticalLayout, Widget, WidgetUpdater, }; -use std::{cell::RefCell, env, mem, rc::Rc, thread, process::exit}; +use std::{cell::RefCell, env, mem, process::exit, rc::Rc, thread}; enum MaybeFontMap { Waiting(thread::JoinHandle), @@ -143,7 +143,7 @@ fn main() { let bufmgr = BufferManager { buffers: Vec::new(), }; - + let (ping_sender, ping_source) = calloop::ping::make_ping().unwrap(); let clock = Box::new(Clock::new()); @@ -212,7 +212,7 @@ fn main() { for _ in 0..state.widgets.len() { damage.push(Geometry::new()); } - + while state.running { event_loop .dispatch(None, &mut state) @@ -461,11 +461,19 @@ impl Dispatch for State { _: &QueueHandle, ) { match event { - xdg_toplevel::Event::Configure { width, height, states, .. } => { + xdg_toplevel::Event::Configure { + width, + height, + states, + .. + } => { if (width, height) == (0, 0) { return; } - let activated = states.iter().find(|&st| *st as u32 == xdg_toplevel::State::Activated as u32).is_some(); + let activated = states + .iter() + .find(|&st| *st as u32 == xdg_toplevel::State::Activated as u32) + .is_some(); if activated { state.activated = true; @@ -473,7 +481,6 @@ impl Dispatch for State { exit(0); } - if state.dimensions != (width, height) { state.bufmgr.clear_buffers(); state.dimensions = (width, height); @@ -486,7 +493,7 @@ impl Dispatch for State { widget: Box::new(IndexedLayout { widget_idx: 1 }), margin: (16, 8, 0, 0), }), - Box::new(VerticalLayout{ + Box::new(VerticalLayout { widgets: vec![ Box::new(Margin { widget: Box::new(IndexedLayout { widget_idx: 2 }), diff --git a/src/widgets/backlight.rs b/src/widgets/backlight.rs index b972ee4..b046d6b 100644 --- a/src/widgets/backlight.rs +++ b/src/widgets/backlight.rs @@ -1,15 +1,12 @@ use std::{ fs, - io::{ErrorKind, Error}, + io::{Error, ErrorKind}, path::{Path, PathBuf}, }; use crate::{ color::Color, - widgets::bar_widget::{ - BarWidget, - BarWidgetImpl, - }, + widgets::bar_widget::{BarWidget, BarWidgetImpl}, }; fn read_file_as_u64(path: &Path) -> Result { @@ -33,7 +30,7 @@ impl Backlight { } pub fn new(path: &str) -> BarWidget { - let mut dev = Backlight{ + let mut dev = Backlight { device_path: Path::new("/sys/class/backlight").to_path_buf().join(path), cur: 0, max: 0, diff --git a/src/widgets/bar_widget.rs b/src/widgets/bar_widget.rs index f281d9c..77e73b7 100644 --- a/src/widgets/bar_widget.rs +++ b/src/widgets/bar_widget.rs @@ -1,7 +1,4 @@ -use std::{ - cmp::min, - default::Default, -}; +use std::{cmp::min, default::Default}; use crate::{ buffer::BufferView, @@ -94,4 +91,3 @@ impl Widget for BarWidget { self.geometry } } - diff --git a/src/widgets/battery.rs b/src/widgets/battery.rs index 862df51..776a9ad 100644 --- a/src/widgets/battery.rs +++ b/src/widgets/battery.rs @@ -1,25 +1,19 @@ use std::{ + sync::{Arc, Mutex}, thread, time::Duration, - sync::{Arc, Mutex}, }; use calloop::ping::Ping; use dbus::blocking::{ + stdintf::org_freedesktop_dbus::{Properties, PropertiesPropertiesChanged}, LocalConnection, - stdintf::org_freedesktop_dbus::{ - Properties, - PropertiesPropertiesChanged, - }, }; use crate::{ color::Color, - widgets::bar_widget::{ - BarWidget, - BarWidgetImpl, - }, + widgets::bar_widget::{BarWidget, BarWidgetImpl}, }; enum UpowerBatteryState { @@ -40,59 +34,75 @@ fn start_monitor(inner: Arc>, ping: Ping) { thread::Builder::new() .name("battmon".to_string()) .spawn(move || { - let conn = LocalConnection::new_system().unwrap(); - let device_path = "/org/freedesktop/UPower/devices/DisplayDevice"; - let proxy = conn.with_proxy("org.freedesktop.UPower", device_path, Duration::from_millis(500)); + let conn = LocalConnection::new_system().unwrap(); + let device_path = "/org/freedesktop/UPower/devices/DisplayDevice"; + let proxy = conn.with_proxy( + "org.freedesktop.UPower", + device_path, + Duration::from_millis(500), + ); - let capacity: f64 = proxy.get("org.freedesktop.UPower.Device", "Percentage").expect("unable to get property"); - let state: u32 = proxy.get("org.freedesktop.UPower.Device", "State").expect("unable to get state"); + let capacity: f64 = proxy + .get("org.freedesktop.UPower.Device", "Percentage") + .expect("unable to get property"); + let state: u32 = proxy + .get("org.freedesktop.UPower.Device", "State") + .expect("unable to get state"); - { - let mut inner = inner.lock().unwrap(); - inner.value = capacity as f32 / 100.; - inner.state = match state { - 1 => UpowerBatteryState::Charging, - 2 => UpowerBatteryState::Discharging, - 3 => UpowerBatteryState::Empty, - 4 => UpowerBatteryState::Full, - 5 => UpowerBatteryState::NotCharging, - 6 => UpowerBatteryState::Discharging, - _ => UpowerBatteryState::Unknown, - } - } - - - proxy.match_signal(move |c: PropertiesPropertiesChanged, _: &LocalConnection, _: &dbus::Message| { - if c.interface_name != "org.freedesktop.UPower.Device" { - return true; - } - let mut inner = inner.lock().unwrap(); - for (key, value) in c.changed_properties { - match key.as_str() { - "State" => { - inner.state = match value.0.as_u64().unwrap() { - 1 => UpowerBatteryState::Charging, - 2 => UpowerBatteryState::Discharging, - 3 => UpowerBatteryState::Empty, - 4 => UpowerBatteryState::Full, - 5 => UpowerBatteryState::NotCharging, - 6 => UpowerBatteryState::Discharging, - _ => UpowerBatteryState::Unknown, - }; - ping.ping(); - }, - "Percentage" => { - inner.value = value.0.as_f64().unwrap() as f32 / 100.; - ping.ping(); - }, - _ => (), + { + let mut inner = inner.lock().unwrap(); + inner.value = capacity as f32 / 100.; + inner.state = match state { + 1 => UpowerBatteryState::Charging, + 2 => UpowerBatteryState::Discharging, + 3 => UpowerBatteryState::Empty, + 4 => UpowerBatteryState::Full, + 5 => UpowerBatteryState::NotCharging, + 6 => UpowerBatteryState::Discharging, + _ => UpowerBatteryState::Unknown, } } - true - }).unwrap(); - loop { conn.process(Duration::from_millis(60000)).unwrap(); } - }).unwrap(); + proxy + .match_signal( + move |c: PropertiesPropertiesChanged, + _: &LocalConnection, + _: &dbus::Message| { + if c.interface_name != "org.freedesktop.UPower.Device" { + return true; + } + let mut inner = inner.lock().unwrap(); + for (key, value) in c.changed_properties { + match key.as_str() { + "State" => { + inner.state = match value.0.as_u64().unwrap() { + 1 => UpowerBatteryState::Charging, + 2 => UpowerBatteryState::Discharging, + 3 => UpowerBatteryState::Empty, + 4 => UpowerBatteryState::Full, + 5 => UpowerBatteryState::NotCharging, + 6 => UpowerBatteryState::Discharging, + _ => UpowerBatteryState::Unknown, + }; + ping.ping(); + } + "Percentage" => { + inner.value = value.0.as_f64().unwrap() as f32 / 100.; + ping.ping(); + } + _ => (), + } + } + true + }, + ) + .unwrap(); + + loop { + conn.process(Duration::from_millis(60000)).unwrap(); + } + }) + .unwrap(); } pub struct Battery { @@ -102,8 +112,8 @@ pub struct Battery { impl Battery { pub fn new(ping: Ping) -> BarWidget { - let battery = Battery{ - inner: Arc::new(Mutex::new(InnerBattery{ + let battery = Battery { + inner: Arc::new(Mutex::new(InnerBattery { value: 0., state: UpowerBatteryState::Unknown, })), @@ -114,7 +124,6 @@ impl Battery { } } - impl BarWidgetImpl for Battery { fn get_dirty(&self) -> bool { self.dirty diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs index 0da7e35..ca39206 100644 --- a/src/widgets/launcher.rs +++ b/src/widgets/launcher.rs @@ -110,11 +110,11 @@ impl Prompt { "!" => { self.mode = PromptMode::Shell; return; - }, + } "=" => { self.mode = PromptMode::Calc; return; - }, + } _ => (), } } @@ -159,7 +159,9 @@ impl Prompt { fn clear_right(&mut self) { let indices: Vec<(usize, &str)> = self.input.grapheme_indices(true).collect(); if !indices.is_empty() { - self.input = indices[0..self.cursor].iter().fold("".into(), |acc, el| acc + el.1); + self.input = indices[0..self.cursor] + .iter() + .fold("".into(), |acc, el| acc + el.1); } else { self.mode = PromptMode::Normal; } @@ -168,7 +170,9 @@ impl Prompt { fn clear_left(&mut self) { let indices: Vec<(usize, &str)> = self.input.grapheme_indices(true).collect(); if !indices.is_empty() { - self.input = indices[self.cursor..indices.len()].iter().fold("".into(), |acc, el| acc + el.1); + self.input = indices[self.cursor..indices.len()] + .iter() + .fold("".into(), |acc, el| acc + el.1); self.cursor = 0; } else { self.mode = PromptMode::Normal; @@ -186,7 +190,12 @@ impl Prompt { trait InterfaceWidget { fn trigger(&mut self, intf: &InnerInterface); - fn draw(&mut self, intf: &InnerInterface, fonts: &mut FontMap, view: &mut BufferView) -> Geometry; + fn draw( + &mut self, + intf: &InnerInterface, + fonts: &mut FontMap, + view: &mut BufferView, + ) -> Geometry; fn update(&mut self, intf: &InnerInterface); } @@ -266,7 +275,12 @@ impl InterfaceWidget for Launcher { self.matches = matcher.matches(); } - fn draw(&mut self, intf: &InnerInterface, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { + fn draw( + &mut self, + intf: &InnerInterface, + fonts: &mut FontMap, + view: &mut BufferView, + ) -> Geometry { let fg = Color::new(1., 1., 1., 1.); let bg = Color::new(0., 0., 0., 1.); @@ -290,13 +304,8 @@ impl InterfaceWidget for Launcher { .unwrap(); let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE); - font.auto_draw_text( - &mut prompt_line, - &bg, - &fg, - ">", - ) - .unwrap(); + font.auto_draw_text(&mut prompt_line, &bg, &fg, ">") + .unwrap(); // Draw entries let dimfg = Color::new(0.5, 0.5, 0.5, 1.0); @@ -331,8 +340,11 @@ impl InterfaceWidget for Launcher { } } - let content_height = min(intf.geometry.height, (self.matches.len() + 1) as u32 * line_height + 16); - Geometry{ + let content_height = min( + intf.geometry.height, + (self.matches.len() + 1) as u32 * line_height + 16, + ); + Geometry { x: intf.geometry.x, y: intf.geometry.y + intf.geometry.height - content_height, width: intf.geometry.width, @@ -341,7 +353,7 @@ impl InterfaceWidget for Launcher { } } -struct Shell (); +struct Shell(); impl InterfaceWidget for Shell { fn trigger(&mut self, intf: &InnerInterface) { @@ -359,7 +371,12 @@ impl InterfaceWidget for Shell { fn update(&mut self, _intf: &InnerInterface) {} - fn draw(&mut self, intf: &InnerInterface, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { + fn draw( + &mut self, + intf: &InnerInterface, + fonts: &mut FontMap, + view: &mut BufferView, + ) -> Geometry { let fg = Color::new(1., 1., 1., 1.); let bg = Color::new(0., 0., 0., 1.); @@ -382,15 +399,10 @@ impl InterfaceWidget for Shell { ) .unwrap(); let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE); - font.auto_draw_text( - &mut prompt_line, - &bg, - &Color::new(1., 0.75, 0.5, 1.), - "!", - ) - .unwrap(); + font.auto_draw_text(&mut prompt_line, &bg, &Color::new(1., 0.75, 0.5, 1.), "!") + .unwrap(); - Geometry{ + Geometry { x: intf.geometry.x, y: intf.geometry.y + intf.geometry.height - line_height, width: intf.geometry.width, @@ -412,16 +424,22 @@ impl InterfaceWidget for Calc { } fn update(&mut self, intf: &InnerInterface) { - let res = rcalc_lib::parse::eval(&intf.prompt.input, &mut rcalc_lib::parse::CalcState::new()) - .map(|x| format!("{}", x)) - .map_err(|x| format!("{}", x)); + let res = + rcalc_lib::parse::eval(&intf.prompt.input, &mut rcalc_lib::parse::CalcState::new()) + .map(|x| format!("{}", x)) + .map_err(|x| format!("{}", x)); self.result = match res { Ok(v) => Some(v), Err(_) => None, }; } - fn draw(&mut self, intf: &InnerInterface, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { + fn draw( + &mut self, + intf: &InnerInterface, + fonts: &mut FontMap, + view: &mut BufferView, + ) -> Geometry { let fg = Color::new(1., 1., 1., 1.); let bg = Color::new(0., 0., 0., 1.); @@ -435,28 +453,25 @@ impl InterfaceWidget for Calc { // Draw prompt let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE); - let res_off = font.auto_draw_text_with_cursor( - &mut prompt_line, - &bg, - &fg, - &format!(" {} ", &intf.prompt.input), - intf.prompt.cursor + 3, - ) - .unwrap(); + let res_off = font + .auto_draw_text_with_cursor( + &mut prompt_line, + &bg, + &fg, + &format!(" {} ", &intf.prompt.input), + intf.prompt.cursor + 3, + ) + .unwrap(); let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE); - font.auto_draw_text( - &mut prompt_line, - &bg, - &Color::new(1., 0.75, 0.5, 1.), - "=", - ) - .unwrap(); + font.auto_draw_text(&mut prompt_line, &bg, &Color::new(1., 0.75, 0.5, 1.), "=") + .unwrap(); if let Some(res) = &self.result { let resfg = Color::new(1.0, 0.65, 0., 1.0); let mut result_line = view.offset((res_off.0, prompt_offset)).unwrap(); - font.auto_draw_text(&mut result_line, &bg, &resfg, &format!(" = {}", &res)).unwrap(); + font.auto_draw_text(&mut result_line, &bg, &resfg, &format!(" = {}", &res)) + .unwrap(); } for m in self.old.iter().rev() { @@ -466,11 +481,15 @@ impl InterfaceWidget for Calc { } prompt_offset -= LAUNCHER_SIZE.ceil() as u32; let mut result_line = view.offset((0, prompt_offset)).unwrap(); - font.auto_draw_text(&mut result_line, &bg, &dimfg, &m).unwrap(); + font.auto_draw_text(&mut result_line, &bg, &dimfg, &m) + .unwrap(); } - let content_height = min(intf.geometry.height, (self.old.len() + 1) as u32 * line_height + 16); - Geometry{ + let content_height = min( + intf.geometry.height, + (self.old.len() + 1) as u32 * line_height + 16, + ); + Geometry { x: intf.geometry.x, y: intf.geometry.y + intf.geometry.height - content_height, width: intf.geometry.width, @@ -495,14 +514,14 @@ pub struct Interface { impl Interface { pub fn new() -> Interface { - Interface{ + Interface { launcher: Launcher::new(), - shell: Shell{}, - calc: Calc{ + shell: Shell {}, + calc: Calc { old: Vec::new(), result: None, }, - inner: InnerInterface{ + inner: InnerInterface { dirty: false, geometry: Default::default(), selection: 0, @@ -554,12 +573,12 @@ impl Widget for Interface { self.inner.prompt.clear_left(); self.inner.selection = 0; widget.update(&self.inner); - }, + } keysyms::XKB_KEY_k if event.modifiers.ctrl => { self.inner.prompt.clear_right(); self.inner.selection = 0; widget.update(&self.inner); - }, + } keysyms::XKB_KEY_Home => self.inner.prompt.home(), keysyms::XKB_KEY_End => self.inner.prompt.end(), keysyms::XKB_KEY_BackSpace => { @@ -599,4 +618,3 @@ impl Widget for Interface { self.launcher.next_token = Some(token.to_string()); } } - diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 679eb67..46f7cb4 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,14 +1,14 @@ +mod backlight; mod bar_widget; mod battery; -mod backlight; mod clock; mod date; mod launcher; mod line; mod widget; -pub use battery::Battery; pub use backlight::Backlight; +pub use battery::Battery; pub use clock::Clock; pub use date::Date; pub use launcher::Interface; diff --git a/src/widgets/widget.rs b/src/widgets/widget.rs index d1c40b3..a5fef27 100644 --- a/src/widgets/widget.rs +++ b/src/widgets/widget.rs @@ -1,4 +1,4 @@ -use std::cmp::{min, max}; +use std::cmp::{max, min}; use std::fmt; use crate::{buffer::BufferView, fonts::FontMap, keyboard::KeyEvent}; @@ -23,7 +23,7 @@ impl fmt::Display for Geometry { impl Geometry { pub fn new() -> Geometry { - Geometry{ + Geometry { x: 0, y: 0, width: 0, @@ -35,7 +35,8 @@ impl Geometry { let x = min(self.x, other.x); let y = min(self.y, other.y); Geometry { - x, y, + x, + y, width: max(self.width + self.x, other.width + other.x) - x, height: max(self.height + self.y, other.height + other.height) - y, } @@ -148,7 +149,7 @@ impl Layout for Margin { geometry: &Geometry, user_data: &mut U, ) -> Geometry { - let geo = Geometry{ + let geo = Geometry { x: geometry.x + self.margin.0, y: geometry.y + self.margin.1, width: geometry.width - self.margin.0 - self.margin.2, From 3a40bb18c529abd231cacdfd6a81351b20bf5817 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Fri, 13 Jan 2023 21:53:07 +0100 Subject: [PATCH 08/57] Refactor slightly --- src/fonts.rs | 33 +++ src/main.rs | 448 ++------------------------------------ src/state.rs | 412 +++++++++++++++++++++++++++++++++++ src/widgets/backlight.rs | 4 +- src/widgets/bar_widget.rs | 15 +- src/widgets/battery.rs | 4 +- src/widgets/clock.rs | 14 +- src/widgets/date.rs | 15 +- src/widgets/launcher.rs | 61 +++--- 9 files changed, 513 insertions(+), 493 deletions(-) create mode 100644 src/state.rs diff --git a/src/fonts.rs b/src/fonts.rs index 7171a16..49705d3 100644 --- a/src/fonts.rs +++ b/src/fonts.rs @@ -4,6 +4,10 @@ use crate::draw; use fontconfig::Fontconfig as FontConfig; use rusttype::Font; use std::{ + cell::RefCell, + mem, + rc::Rc, + thread, collections::HashMap, fs::File, hash, @@ -11,6 +15,7 @@ use std::{ path::{Path, PathBuf}, }; + /// FontRef is used to store Fonts on widgets. pub type FontRef<'a> = &'a rusttype::Font<'a>; @@ -126,3 +131,31 @@ impl FontMap { .expect("no font at specified size") } } + +pub enum MaybeFontMap { + Waiting(thread::JoinHandle), + Ready(Rc>), + Invalid, +} + +impl MaybeFontMap { + pub fn unwrap(&self) -> Rc> { + match self { + MaybeFontMap::Ready(f) => f.clone(), + _ => panic!("fontmap not yet ready"), + } + } + + pub fn resolve(&mut self) { + if matches!(self, MaybeFontMap::Waiting(_)) { + let s = mem::replace(self, MaybeFontMap::Invalid); + match s { + MaybeFontMap::Waiting(handle) => { + *self = MaybeFontMap::Ready(Rc::new(RefCell::new(handle.join().unwrap()))); + } + _ => unreachable!(), + } + } + } +} + diff --git a/src/main.rs b/src/main.rs index b33d0a5..a99243e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,19 +5,9 @@ mod fonts; mod keyboard; mod utils; mod widgets; +mod state; -use wayland_client::{ - protocol::{ - wl_buffer, wl_compositor, wl_keyboard, wl_registry, wl_seat, wl_shm, wl_shm_pool, - wl_surface, - }, - Connection, Dispatch, QueueHandle, WEnum, WaylandSource, -}; - -use wayland_protocols::xdg::{ - activation::v1::client::{xdg_activation_token_v1, xdg_activation_v1}, - shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base}, -}; +use wayland_client::{Connection, QueueHandle, WaylandSource,}; use calloop::{ timer::{TimeoutAction, Timer}, @@ -27,84 +17,14 @@ use chrono::{Duration, Local, Timelike}; use buffer::{BufferManager, BufferView}; use color::Color; -use fonts::FontMap; +use fonts::{MaybeFontMap, FontMap}; use keyboard::Keyboard; use widgets::{ - Backlight, Battery, Clock, Date, Geometry, HorizontalLayout, IndexedLayout, Interface, Layout, - Line, Margin, VerticalLayout, Widget, WidgetUpdater, + Backlight, Battery, Clock, Date, Geometry, Interface, Line, WidgetUpdater, }; +use state::State; -use std::{cell::RefCell, env, mem, process::exit, rc::Rc, thread}; - -enum MaybeFontMap { - Waiting(thread::JoinHandle), - Ready(Rc>), - Invalid, -} - -impl MaybeFontMap { - fn unwrap(&self) -> Rc> { - match self { - MaybeFontMap::Ready(f) => f.clone(), - _ => panic!("fontmap not yet ready"), - } - } - - fn resolve(&mut self) { - if matches!(self, MaybeFontMap::Waiting(_)) { - let s = mem::replace(self, MaybeFontMap::Invalid); - match s { - MaybeFontMap::Waiting(handle) => { - *self = MaybeFontMap::Ready(Rc::new(RefCell::new(handle.join().unwrap()))); - } - _ => unreachable!(), - } - } - } -} - -struct State { - running: bool, - dirty: bool, - activated: bool, - base_surface: Option, - wm_base: Option, - xdg_surface: Option<(xdg_surface::XdgSurface, xdg_toplevel::XdgToplevel)>, - wl_shm: Option, - xdg_activation: Option, - configured: bool, - dimensions: (i32, i32), - bufmgr: BufferManager, - widgets: Vec>, - keyboard: Keyboard, - fonts: MaybeFontMap, -} - -impl State { - fn add_buffer(&mut self, qh: &QueueHandle) { - self.bufmgr.add_buffer( - self.wl_shm.as_ref().expect("missing wl_shm"), - self.dimensions, - qh, - ); - } - - fn activate(&mut self, qh: &QueueHandle) { - let key = match env::var("XDG_ACTIVATION_TOKEN") { - Ok(token) => token, - Err(_) => return, - }; - match (&self.base_surface, &self.xdg_activation) { - (Some(surface), Some(activation)) => { - activation.activate(key, surface); - let activation_token = activation.get_activation_token(qh, ()); - activation_token.commit(); - env::remove_var("XDG_ACTIVATION_TOKEN"); - } - _ => (), - } - } -} +use std::thread; impl WidgetUpdater for State { fn geometry_update( @@ -121,8 +41,8 @@ fn main() { let font_thread = thread::spawn(move || { let mut fm = FontMap::new(); // Hard-coding font paths make things a lot faster - fm.queue_font_path("sans", "/usr/share/fonts/noto/NotoSans-Regular.ttf", 96.); - fm.queue_font_path("sans", "/usr/share/fonts/noto/NotoSans-Regular.ttf", 40.); + fm.queue_font_path("sans", "/usr/share/fonts/noto/NotoSans-Regular.ttf", 128.); + fm.queue_font_path("sans", "/usr/share/fonts/noto/NotoSans-Regular.ttf", 48.); fm.queue_font_path("sans", "/usr/share/fonts/noto/NotoSans-Regular.ttf", 24.); fm.queue_font_path( "monospace", @@ -146,12 +66,12 @@ fn main() { let (ping_sender, ping_source) = calloop::ping::make_ping().unwrap(); - let clock = Box::new(Clock::new()); - let date = Box::new(Date::new()); + let clock = Box::new(Clock::new("sans", 128.)); + let date = Box::new(Date::new("sans", 48.)); let line = Box::new(Line::new()); - let launcher = Box::new(Interface::new()); - let battery = Box::new(Battery::new(ping_sender)); - let backlight = Box::new(Backlight::new("intel_backlight")); + let launcher = Box::new(Interface::new("monospace", 32.)); + let battery = Box::new(Battery::new(ping_sender, "sans", 24.)); + let backlight = Box::new(Backlight::new("intel_backlight", "sans", 24.)); let mut event_loop: EventLoop = EventLoop::try_new().expect("Failed to initialize the event loop!"); @@ -288,345 +208,3 @@ fn main() { } } -impl Dispatch for State { - fn event( - state: &mut Self, - registry: &wl_registry::WlRegistry, - event: wl_registry::Event, - _: &(), - _: &Connection, - qh: &QueueHandle, - ) { - if let wl_registry::Event::Global { - name, interface, .. - } = event - { - match &interface[..] { - "wl_compositor" => { - let compositor = - registry.bind::(name, 4, qh, ()); - let surface = compositor.create_surface(qh, ()); - state.base_surface = Some(surface); - state.activate(qh); - - if state.wm_base.is_some() && state.xdg_surface.is_none() { - state.init_xdg_surface(qh); - } - } - "wl_shm" => { - state.wl_shm = Some(registry.bind::(name, 1, qh, ())); - } - "wl_seat" => { - registry.bind::(name, 1, qh, ()); - } - "xdg_wm_base" => { - let wm_base = registry.bind::(name, 1, qh, ()); - state.wm_base = Some(wm_base); - - if state.base_surface.is_some() && state.xdg_surface.is_none() { - state.init_xdg_surface(qh); - } - } - "xdg_activation_v1" => { - state.xdg_activation = Some( - registry.bind::(name, 1, qh, ()), - ); - state.activate(qh); - } - _ => {} - } - } - } -} - -impl Dispatch for State { - fn event( - _: &mut Self, - _: &wl_compositor::WlCompositor, - _: wl_compositor::Event, - _: &(), - _: &Connection, - _: &QueueHandle, - ) { - } -} - -impl Dispatch for State { - fn event( - _: &mut Self, - _: &wl_surface::WlSurface, - _: wl_surface::Event, - _: &(), - _: &Connection, - _: &QueueHandle, - ) { - } -} - -impl Dispatch for State { - fn event( - _: &mut Self, - _: &wl_shm::WlShm, - _: wl_shm::Event, - _: &(), - _: &Connection, - _: &QueueHandle, - ) { - } -} - -impl Dispatch for State { - fn event( - _: &mut Self, - _: &wl_shm_pool::WlShmPool, - _: wl_shm_pool::Event, - _: &(), - _: &Connection, - _: &QueueHandle, - ) { - } -} - -impl Dispatch for State { - fn event( - state: &mut Self, - buffer: &wl_buffer::WlBuffer, - _: wl_buffer::Event, - _: &(), - _: &Connection, - _: &QueueHandle, - ) { - for buf in state.bufmgr.buffers.iter_mut() { - if &buf.buffer == buffer { - buf.release(); - } - } - } -} - -impl State { - fn init_xdg_surface(&mut self, qh: &QueueHandle) { - let wm_base = self.wm_base.as_ref().unwrap(); - let base_surface = self.base_surface.as_ref().unwrap(); - - let xdg_surface = wm_base.get_xdg_surface(base_surface, qh, ()); - let toplevel = xdg_surface.get_toplevel(qh, ()); - toplevel.set_title("wldash".into()); - - base_surface.commit(); - - self.xdg_surface = Some((xdg_surface, toplevel)); - } -} - -impl Dispatch for State { - fn event( - _: &mut Self, - wm_base: &xdg_wm_base::XdgWmBase, - event: xdg_wm_base::Event, - _: &(), - _: &Connection, - _: &QueueHandle, - ) { - if let xdg_wm_base::Event::Ping { serial } = event { - wm_base.pong(serial); - } - } -} - -impl Dispatch for State { - fn event( - state: &mut Self, - xdg_surface: &xdg_surface::XdgSurface, - event: xdg_surface::Event, - _: &(), - _: &Connection, - _: &QueueHandle, - ) { - if let xdg_surface::Event::Configure { serial, .. } = event { - xdg_surface.ack_configure(serial); - state.configured = true; - state.dirty = true; - } - } -} - -impl Dispatch for State { - fn event( - state: &mut Self, - _: &xdg_toplevel::XdgToplevel, - event: xdg_toplevel::Event, - _: &(), - _: &Connection, - _: &QueueHandle, - ) { - match event { - xdg_toplevel::Event::Configure { - width, - height, - states, - .. - } => { - if (width, height) == (0, 0) { - return; - } - let activated = states - .iter() - .find(|&st| *st as u32 == xdg_toplevel::State::Activated as u32) - .is_some(); - - if activated { - state.activated = true; - } else if state.activated && !activated { - exit(0); - } - - if state.dimensions != (width, height) { - state.bufmgr.clear_buffers(); - state.dimensions = (width, height); - let mut layout = VerticalLayout { - widgets: vec![ - Box::new(HorizontalLayout { - widgets: vec![ - Box::new(IndexedLayout { widget_idx: 0 }), - Box::new(Margin { - widget: Box::new(IndexedLayout { widget_idx: 1 }), - margin: (16, 8, 0, 0), - }), - Box::new(VerticalLayout { - widgets: vec![ - Box::new(Margin { - widget: Box::new(IndexedLayout { widget_idx: 2 }), - margin: (16, 8, 8, 0), - }), - Box::new(Margin { - widget: Box::new(IndexedLayout { widget_idx: 3 }), - margin: (16, 8, 8, 0), - }), - ], - }), - ], - }), - Box::new(IndexedLayout { widget_idx: 4 }), - Box::new(IndexedLayout { widget_idx: 5 }), - ], - }; - - state.fonts.resolve(); - let fonts = state.fonts.unwrap(); - layout.geometry_update( - &mut fonts.borrow_mut(), - &Geometry { - x: 0, - y: 0, - width: width as u32, - height: height as u32, - }, - state, - ); - } - } - xdg_toplevel::Event::Close => { - state.running = false; - } - _ => (), - } - } -} - -impl Dispatch for State { - fn event( - _: &mut Self, - seat: &wl_seat::WlSeat, - event: wl_seat::Event, - _: &(), - _: &Connection, - qh: &QueueHandle, - ) { - if let wl_seat::Event::Capabilities { - capabilities: WEnum::Value(capabilities), - } = event - { - if capabilities.contains(wl_seat::Capability::Keyboard) { - seat.get_keyboard(qh, ()); - } - } - } -} - -impl Dispatch for State { - fn event( - state: &mut Self, - _: &wl_keyboard::WlKeyboard, - event: wl_keyboard::Event, - _: &(), - _: &Connection, - _: &QueueHandle, - ) { - match event { - wl_keyboard::Event::Key { - serial, - time, - key, - state: kbstate, - } => { - if key == 1 { - state.running = false; - return; - } - let k = state.keyboard.key(serial, time, key, kbstate); - for widget in state.widgets.iter_mut() { - widget.keyboard_input(&k); - } - state.dirty = true; - } - wl_keyboard::Event::Modifiers { - mods_depressed, - mods_latched, - mods_locked, - group, - .. - } => { - state - .keyboard - .modifiers(mods_depressed, mods_latched, mods_locked, group); - } - wl_keyboard::Event::Keymap { format, fd, size } => { - state.keyboard.keymap(format, fd, size); - } - _ => (), - } - } -} - -impl Dispatch for State { - fn event( - _: &mut Self, - _: &xdg_activation_v1::XdgActivationV1, - _: xdg_activation_v1::Event, - _: &(), - _: &Connection, - _: &QueueHandle, - ) { - } -} - -impl Dispatch for State { - fn event( - state: &mut Self, - _: &xdg_activation_token_v1::XdgActivationTokenV1, - event: xdg_activation_token_v1::Event, - _: &(), - _: &Connection, - _: &QueueHandle, - ) { - match event { - xdg_activation_token_v1::Event::Done { token } => { - for widget in state.widgets.iter_mut() { - widget.token_update(&token); - } - } - _ => (), - } - } -} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..d22d1e9 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,412 @@ +use std::{ + env, + process::exit, +}; + +use wayland_client::{ + protocol::{ + wl_buffer, wl_compositor, wl_keyboard, wl_registry, wl_seat, wl_shm, wl_shm_pool, + wl_surface, + }, + Connection, Dispatch, QueueHandle, WEnum, +}; + +use wayland_protocols::xdg::{ + activation::v1::client::{xdg_activation_token_v1, xdg_activation_v1}, + shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base}, +}; + +use crate::{ + buffer::BufferManager, + fonts::MaybeFontMap, + keyboard::Keyboard, + widgets::{ + Geometry, HorizontalLayout, IndexedLayout, Layout, Margin, VerticalLayout, Widget, + }, +}; + +pub struct State { + pub running: bool, + pub dirty: bool, + pub activated: bool, + pub base_surface: Option, + pub wm_base: Option, + pub xdg_surface: Option<(xdg_surface::XdgSurface, xdg_toplevel::XdgToplevel)>, + pub wl_shm: Option, + pub xdg_activation: Option, + pub configured: bool, + pub dimensions: (i32, i32), + pub bufmgr: BufferManager, + pub widgets: Vec>, + pub keyboard: Keyboard, + pub fonts: MaybeFontMap, +} + +impl State { + pub fn add_buffer(&mut self, qh: &QueueHandle) { + self.bufmgr.add_buffer( + self.wl_shm.as_ref().expect("missing wl_shm"), + self.dimensions, + qh, + ); + } + + pub fn activate(&mut self, qh: &QueueHandle) { + let key = match env::var("XDG_ACTIVATION_TOKEN") { + Ok(token) => token, + Err(_) => return, + }; + match (&self.base_surface, &self.xdg_activation) { + (Some(surface), Some(activation)) => { + activation.activate(key, surface); + let activation_token = activation.get_activation_token(qh, ()); + activation_token.commit(); + env::remove_var("XDG_ACTIVATION_TOKEN"); + } + _ => (), + } + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + registry: &wl_registry::WlRegistry, + event: wl_registry::Event, + _: &(), + _: &Connection, + qh: &QueueHandle, + ) { + if let wl_registry::Event::Global { + name, interface, .. + } = event + { + match &interface[..] { + "wl_compositor" => { + let compositor = + registry.bind::(name, 4, qh, ()); + let surface = compositor.create_surface(qh, ()); + state.base_surface = Some(surface); + state.activate(qh); + + if state.wm_base.is_some() && state.xdg_surface.is_none() { + state.init_xdg_surface(qh); + } + } + "wl_shm" => { + state.wl_shm = Some(registry.bind::(name, 1, qh, ())); + } + "wl_seat" => { + registry.bind::(name, 1, qh, ()); + } + "xdg_wm_base" => { + let wm_base = registry.bind::(name, 1, qh, ()); + state.wm_base = Some(wm_base); + + if state.base_surface.is_some() && state.xdg_surface.is_none() { + state.init_xdg_surface(qh); + } + } + "xdg_activation_v1" => { + state.xdg_activation = Some( + registry.bind::(name, 1, qh, ()), + ); + state.activate(qh); + } + _ => {} + } + } + } +} + +impl Dispatch for State { + fn event( + _: &mut Self, + _: &wl_compositor::WlCompositor, + _: wl_compositor::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for State { + fn event( + _: &mut Self, + _: &wl_surface::WlSurface, + _: wl_surface::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for State { + fn event( + _: &mut Self, + _: &wl_shm::WlShm, + _: wl_shm::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for State { + fn event( + _: &mut Self, + _: &wl_shm_pool::WlShmPool, + _: wl_shm_pool::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + buffer: &wl_buffer::WlBuffer, + _: wl_buffer::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + for buf in state.bufmgr.buffers.iter_mut() { + if &buf.buffer == buffer { + buf.release(); + } + } + } +} + +impl State { + fn init_xdg_surface(&mut self, qh: &QueueHandle) { + let wm_base = self.wm_base.as_ref().unwrap(); + let base_surface = self.base_surface.as_ref().unwrap(); + + let xdg_surface = wm_base.get_xdg_surface(base_surface, qh, ()); + let toplevel = xdg_surface.get_toplevel(qh, ()); + toplevel.set_title("wldash".into()); + + base_surface.commit(); + + self.xdg_surface = Some((xdg_surface, toplevel)); + } +} + +impl Dispatch for State { + fn event( + _: &mut Self, + wm_base: &xdg_wm_base::XdgWmBase, + event: xdg_wm_base::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + if let xdg_wm_base::Event::Ping { serial } = event { + wm_base.pong(serial); + } + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + xdg_surface: &xdg_surface::XdgSurface, + event: xdg_surface::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + if let xdg_surface::Event::Configure { serial, .. } = event { + xdg_surface.ack_configure(serial); + state.configured = true; + state.dirty = true; + } + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + _: &xdg_toplevel::XdgToplevel, + event: xdg_toplevel::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + match event { + xdg_toplevel::Event::Configure { + width, + height, + states, + .. + } => { + if (width, height) == (0, 0) { + return; + } + let activated = states + .iter() + .find(|&st| *st as u32 == xdg_toplevel::State::Activated as u32) + .is_some(); + + if activated { + state.activated = true; + } else if state.activated && !activated { + exit(0); + } + + if state.dimensions != (width, height) { + state.bufmgr.clear_buffers(); + state.dimensions = (width, height); + let mut layout = VerticalLayout { + widgets: vec![ + Box::new(HorizontalLayout { + widgets: vec![ + Box::new(IndexedLayout { widget_idx: 0 }), + Box::new(Margin { + widget: Box::new(IndexedLayout { widget_idx: 1 }), + margin: (16, 8, 0, 0), + }), + Box::new(VerticalLayout { + widgets: vec![ + Box::new(Margin { + widget: Box::new(IndexedLayout { widget_idx: 2 }), + margin: (16, 8, 8, 0), + }), + Box::new(Margin { + widget: Box::new(IndexedLayout { widget_idx: 3 }), + margin: (16, 8, 8, 0), + }), + ], + }), + ], + }), + Box::new(IndexedLayout { widget_idx: 4 }), + Box::new(IndexedLayout { widget_idx: 5 }), + ], + }; + + state.fonts.resolve(); + let fonts = state.fonts.unwrap(); + layout.geometry_update( + &mut fonts.borrow_mut(), + &Geometry { + x: 0, + y: 0, + width: width as u32, + height: height as u32, + }, + state, + ); + } + } + xdg_toplevel::Event::Close => { + state.running = false; + } + _ => (), + } + } +} + +impl Dispatch for State { + fn event( + _: &mut Self, + seat: &wl_seat::WlSeat, + event: wl_seat::Event, + _: &(), + _: &Connection, + qh: &QueueHandle, + ) { + if let wl_seat::Event::Capabilities { + capabilities: WEnum::Value(capabilities), + } = event + { + if capabilities.contains(wl_seat::Capability::Keyboard) { + seat.get_keyboard(qh, ()); + } + } + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + _: &wl_keyboard::WlKeyboard, + event: wl_keyboard::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + match event { + wl_keyboard::Event::Key { + serial, + time, + key, + state: kbstate, + } => { + if key == 1 { + state.running = false; + return; + } + let k = state.keyboard.key(serial, time, key, kbstate); + for widget in state.widgets.iter_mut() { + widget.keyboard_input(&k); + } + state.dirty = true; + } + wl_keyboard::Event::Modifiers { + mods_depressed, + mods_latched, + mods_locked, + group, + .. + } => { + state + .keyboard + .modifiers(mods_depressed, mods_latched, mods_locked, group); + } + wl_keyboard::Event::Keymap { format, fd, size } => { + state.keyboard.keymap(format, fd, size); + } + _ => (), + } + } +} + +impl Dispatch for State { + fn event( + _: &mut Self, + _: &xdg_activation_v1::XdgActivationV1, + _: xdg_activation_v1::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + _: &xdg_activation_token_v1::XdgActivationTokenV1, + event: xdg_activation_token_v1::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + match event { + xdg_activation_token_v1::Event::Done { token } => { + for widget in state.widgets.iter_mut() { + widget.token_update(&token); + } + } + _ => (), + } + } +} diff --git a/src/widgets/backlight.rs b/src/widgets/backlight.rs index b046d6b..02064a9 100644 --- a/src/widgets/backlight.rs +++ b/src/widgets/backlight.rs @@ -29,7 +29,7 @@ impl Backlight { self.max = read_file_as_u64(self.device_path.join("max_brightness").as_path()).unwrap(); } - pub fn new(path: &str) -> BarWidget { + pub fn new(path: &str, font: &'static str, size: f32) -> BarWidget { let mut dev = Backlight { device_path: Path::new("/sys/class/backlight").to_path_buf().join(path), cur: 0, @@ -38,7 +38,7 @@ impl Backlight { }; dev.update(); - BarWidget::new(Box::new(dev)) + BarWidget::new(Box::new(dev), font, size) } } diff --git a/src/widgets/bar_widget.rs b/src/widgets/bar_widget.rs index 77e73b7..f57febb 100644 --- a/src/widgets/bar_widget.rs +++ b/src/widgets/bar_widget.rs @@ -8,9 +8,6 @@ use crate::{ widgets::{Geometry, Widget}, }; -const BAR_FONT: &str = "sans"; -const BAR_SIZE: f32 = 24.; - pub trait BarWidgetImpl { fn get_dirty(&self) -> bool; fn set_dirty(&mut self, dirty: bool); @@ -20,15 +17,17 @@ pub trait BarWidgetImpl { } pub struct BarWidget { + font: &'static str, + size: f32, geometry: Geometry, inner_widget: Box, } impl BarWidget { - pub fn new(inner_widget: Box) -> BarWidget { + pub fn new(inner_widget: Box, font: &'static str, size: f32) -> BarWidget { BarWidget { geometry: Default::default(), - inner_widget, + inner_widget, font, size, } } } @@ -47,13 +46,13 @@ impl Widget for BarWidget { } fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { - let font = fonts.get_font(BAR_FONT, BAR_SIZE); + let font = fonts.get_font(self.font, self.size); let fg = Color::new(1., 1., 1., 1.); let bg = Color::new(0., 0., 0., 1.); font.auto_draw_text(view, &bg, &fg, self.inner_widget.name()) .unwrap(); - let size = BAR_SIZE.ceil() as u32; + let size = self.size.ceil() as u32; let bar_offset = 4 * size; let val = self.inner_widget.value(); @@ -86,7 +85,7 @@ impl Widget for BarWidget { x: x, y: geometry.y, width: w, - height: BAR_SIZE.ceil() as u32, + height: self.size.ceil() as u32, }; self.geometry } diff --git a/src/widgets/battery.rs b/src/widgets/battery.rs index 776a9ad..e448871 100644 --- a/src/widgets/battery.rs +++ b/src/widgets/battery.rs @@ -111,7 +111,7 @@ pub struct Battery { } impl Battery { - pub fn new(ping: Ping) -> BarWidget { + pub fn new(ping: Ping, font: &'static str, size: f32) -> BarWidget { let battery = Battery { inner: Arc::new(Mutex::new(InnerBattery { value: 0., @@ -120,7 +120,7 @@ impl Battery { dirty: false, }; start_monitor(battery.inner.clone(), ping); - BarWidget::new(Box::new(battery)) + BarWidget::new(Box::new(battery), font, size) } } diff --git a/src/widgets/clock.rs b/src/widgets/clock.rs index 91b4fce..63ee961 100644 --- a/src/widgets/clock.rs +++ b/src/widgets/clock.rs @@ -9,10 +9,9 @@ use crate::{ use chrono::{Local, Timelike}; -const CLOCK_FONT: &str = "sans"; -const CLOCK_SIZE: f32 = 96.; - pub struct Clock { + font: &'static str, + size: f32, dirty: bool, geometry: Geometry, digit_width: u32, @@ -20,8 +19,9 @@ pub struct Clock { } impl<'a> Clock { - pub fn new() -> Clock { + pub fn new(font: &'static str, size: f32) -> Clock { Clock { + font, size, dirty: true, geometry: Default::default(), digit_width: 0, @@ -49,7 +49,7 @@ impl<'a> Widget for Clock { let digit = self.digit_width; let colon = self.colon_width; - let font = fonts.get_font(CLOCK_FONT, CLOCK_SIZE); + let font = fonts.get_font(self.font, self.size); font.draw_text_fixed_width( view, &bg, @@ -62,14 +62,14 @@ impl<'a> Widget for Clock { } fn geometry_update(&mut self, fonts: &mut FontMap, geometry: &Geometry) -> Geometry { - let font = fonts.get_font(CLOCK_FONT, CLOCK_SIZE); + let font = fonts.get_font(self.font, self.size); self.digit_width = font.auto_widest("0123456789").unwrap(); self.colon_width = font.auto_widest(":").unwrap(); self.geometry = Geometry { x: geometry.x, y: geometry.y, width: self.digit_width * 4 + self.colon_width, - height: CLOCK_SIZE.ceil() as u32, + height: self.size.ceil() as u32, }; self.geometry } diff --git a/src/widgets/date.rs b/src/widgets/date.rs index 7544308..b56c5f0 100644 --- a/src/widgets/date.rs +++ b/src/widgets/date.rs @@ -9,17 +9,17 @@ use crate::{ use chrono::{Datelike, Local}; -const DATE_FONT: &str = "sans"; -const DATE_SIZE: f32 = 40.; - pub struct Date { + font: &'static str, + size: f32, dirty: bool, geometry: Geometry, } impl Date { - pub fn new() -> Date { + pub fn new(font: &'static str, size: f32) -> Date { Date { + font, size, dirty: true, geometry: Default::default(), } @@ -43,7 +43,7 @@ impl<'a> Widget for Date { let fg = Color::new(1., 1., 1., 1.); let bg = Color::new(0., 0., 0., 1.); let mut date_line = view.offset((0, 8)).unwrap(); - let font = fonts.get_font(DATE_FONT, DATE_SIZE); + let font = fonts.get_font(self.font, self.size); font.auto_draw_text( &mut date_line, &bg, @@ -61,11 +61,12 @@ impl<'a> Widget for Date { } fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry { + let width = (3. + 2. + 2. + 2. + 4.) * self.size / 2.; self.geometry = Geometry { x: geometry.x, y: geometry.y, - width: 256, - height: DATE_SIZE.ceil() as u32, + width: width.ceil() as u32, + height: self.size.ceil() as u32, }; self.geometry } diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs index ca39206..7afa221 100644 --- a/src/widgets/launcher.rs +++ b/src/widgets/launcher.rs @@ -1,7 +1,11 @@ -use std::cmp::{min, Ordering}; -use std::collections::HashMap; -use std::default::Default; -use std::process::{exit, Command}; +use std::{ + cmp::{min, max, Ordering}, + collections::HashMap, + default::Default, + process::{exit, Command}, + sync::{Arc, Mutex}, + thread, +}; use crate::{ buffer::BufferView, @@ -17,12 +21,6 @@ use fuzzy_matcher::FuzzyMatcher; use unicode_segmentation::UnicodeSegmentation; use wayland_client::{protocol::wl_keyboard, WEnum}; -use std::sync::{Arc, Mutex}; -use std::thread; - -const LAUNCHER_FONT: &str = "monospace"; -const LAUNCHER_SIZE: f32 = 32.; - struct Matcher { matches: HashMap, } @@ -284,7 +282,7 @@ impl InterfaceWidget for Launcher { let fg = Color::new(1., 1., 1., 1.); let bg = Color::new(0., 0., 0., 1.); - let line_height = LAUNCHER_SIZE.ceil() as u32; + let line_height = intf.size.ceil() as u32; // Draw line let mut prompt_offset = intf.geometry.height - line_height; @@ -292,9 +290,8 @@ impl InterfaceWidget for Launcher { let mut prompt_border = prompt_line.limit((intf.geometry.width, 1)).unwrap(); prompt_border.memset(&fg); - // Draw prompt - let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE); - font.auto_draw_text_with_cursor( + let font = fonts.get_font(intf.font, intf.size); + let mut x_max = font.auto_draw_text_with_cursor( &mut prompt_line, &bg, &fg, @@ -303,7 +300,6 @@ impl InterfaceWidget for Launcher { ) .unwrap(); - let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE); font.auto_draw_text(&mut prompt_line, &bg, &fg, ">") .unwrap(); @@ -332,11 +328,11 @@ impl InterfaceWidget for Launcher { colors.push(Color::new(0.75, 0.75, 0.75, 1.0)); } } - font.auto_draw_text_individual_colors(&mut line, &bg, &colors, &m.name) - .unwrap(); + x_max = max(x_max, font.auto_draw_text_individual_colors(&mut line, &bg, &colors, &m.name) + .unwrap()); } else { - font.auto_draw_text(&mut line, &bg, &dimfg, &m.name) - .unwrap(); + x_max = max(x_max, font.auto_draw_text(&mut line, &bg, &dimfg, &m.name) + .unwrap()); } } @@ -347,7 +343,7 @@ impl InterfaceWidget for Launcher { Geometry { x: intf.geometry.x, y: intf.geometry.y + intf.geometry.height - content_height, - width: intf.geometry.width, + width: x_max.0+1, height: content_height, } } @@ -380,7 +376,7 @@ impl InterfaceWidget for Shell { let fg = Color::new(1., 1., 1., 1.); let bg = Color::new(0., 0., 0., 1.); - let line_height = LAUNCHER_SIZE.ceil() as u32; + let line_height = intf.size.ceil() as u32; // Draw line let prompt_offset = intf.geometry.height - line_height; @@ -389,8 +385,8 @@ impl InterfaceWidget for Shell { prompt_border.memset(&fg); // Draw prompt - let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE); - font.auto_draw_text_with_cursor( + let font = fonts.get_font(intf.font, intf.size); + let x_max = font.auto_draw_text_with_cursor( &mut prompt_line, &bg, &fg, @@ -398,14 +394,13 @@ impl InterfaceWidget for Shell { intf.prompt.cursor + 3, ) .unwrap(); - let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE); font.auto_draw_text(&mut prompt_line, &bg, &Color::new(1., 0.75, 0.5, 1.), "!") .unwrap(); Geometry { x: intf.geometry.x, y: intf.geometry.y + intf.geometry.height - line_height, - width: intf.geometry.width, + width: x_max.0 + 1, height: line_height, } } @@ -443,16 +438,16 @@ impl InterfaceWidget for Calc { let fg = Color::new(1., 1., 1., 1.); let bg = Color::new(0., 0., 0., 1.); - let line_height = LAUNCHER_SIZE.ceil() as u32; + let line_height = intf.size.ceil() as u32; // Draw line - let mut prompt_offset = intf.geometry.height - (LAUNCHER_SIZE.ceil() as u32); + let mut prompt_offset = intf.geometry.height - (line_height); let mut prompt_line = view.offset((0, prompt_offset)).unwrap(); let mut prompt_border = prompt_line.limit((intf.geometry.width, 1)).unwrap(); prompt_border.memset(&fg); // Draw prompt - let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE); + let font = fonts.get_font(intf.font, intf.size); let res_off = font .auto_draw_text_with_cursor( &mut prompt_line, @@ -463,7 +458,6 @@ impl InterfaceWidget for Calc { ) .unwrap(); - let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE); font.auto_draw_text(&mut prompt_line, &bg, &Color::new(1., 0.75, 0.5, 1.), "=") .unwrap(); @@ -476,10 +470,10 @@ impl InterfaceWidget for Calc { for m in self.old.iter().rev() { let dimfg = Color::new(0.5, 0.5, 0.5, 1.0); - if prompt_offset < LAUNCHER_SIZE.ceil() as u32 { + if prompt_offset < line_height { break; } - prompt_offset -= LAUNCHER_SIZE.ceil() as u32; + prompt_offset -= line_height; let mut result_line = view.offset((0, prompt_offset)).unwrap(); font.auto_draw_text(&mut result_line, &bg, &dimfg, &m) .unwrap(); @@ -499,6 +493,8 @@ impl InterfaceWidget for Calc { } struct InnerInterface { + font: &'static str, + size: f32, dirty: bool, geometry: Geometry, selection: usize, @@ -513,7 +509,7 @@ pub struct Interface { } impl Interface { - pub fn new() -> Interface { + pub fn new(font: &'static str, size: f32) -> Interface { Interface { launcher: Launcher::new(), shell: Shell {}, @@ -522,6 +518,7 @@ impl Interface { result: None, }, inner: InnerInterface { + font, size, dirty: false, geometry: Default::default(), selection: 0, From 7593cd48a41e42f4679c1e1f67061adf9738969b Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Mon, 16 Jan 2023 00:25:34 +0100 Subject: [PATCH 09/57] Add calendar, refactor more --- Cargo.lock | 28 ---- Cargo.toml | 6 +- src/buffer.rs | 6 + src/fonts.rs | 16 +-- src/main.rs | 167 ++++++++++++----------- src/state.rs | 135 +++++++++++++------ src/widgets/backlight.rs | 8 -- src/widgets/bar_widget.rs | 13 +- src/widgets/battery.rs | 29 ++-- src/widgets/calendar.rs | 273 ++++++++++++++++++++++++++++++++++++++ src/widgets/clock.rs | 17 ++- src/widgets/date.rs | 17 ++- src/widgets/launcher.rs | 120 +++++++++-------- src/widgets/mod.rs | 6 +- src/widgets/widget.rs | 134 +++++++++++++------ 15 files changed, 689 insertions(+), 286 deletions(-) create mode 100644 src/widgets/calendar.rs diff --git a/Cargo.lock b/Cargo.lock index de1ca1d..6b33d5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,12 +105,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "const-cstr" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3d0b5ff30645a68f35ece8cea4556ca14ef8a1651455f789a099a0513532a6" - [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -243,15 +237,6 @@ dependencies = [ "instant", ] -[[package]] -name = "fontconfig" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b0653582d9c82645f95bacd0dc87b49a3126f7a9633be8cafa9c6d17d525847" -dependencies = [ - "yeslogic-fontconfig-sys", -] - [[package]] name = "fuzzy-matcher" version = "0.3.7" @@ -1102,7 +1087,6 @@ dependencies = [ "calloop", "chrono", "dbus", - "fontconfig", "fuzzy-matcher", "itertools", "memmap", @@ -1128,15 +1112,3 @@ dependencies = [ "libc", "memmap2", ] - -[[package]] -name = "yeslogic-fontconfig-sys" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec657fd32bbcbeaef5c7bc8e10b3db95b143fab8db0a50079773dbf936fd4f73" -dependencies = [ - "const-cstr", - "dlib", - "once_cell", - "pkg-config", -] diff --git a/Cargo.toml b/Cargo.toml index 411d477..372eb25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,10 @@ name = "wldash2" version = "0.1.0" edition = "2021" +[profile.release] +lto = "fat" +panic = "abort" + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] @@ -11,7 +15,7 @@ wayland-protocols = { version = "0.30", features = ["client", "unstable", "stagi tempfile = "3.3" nix = "0.26" memmap = "0.7" -fontconfig = "0.6.0" +#fontconfig = "0.6.0" rusttype = "^0.9" calloop = { version = "0.10" } chrono = "0.4" diff --git a/src/buffer.rs b/src/buffer.rs index 8a2dc0f..685583b 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -125,6 +125,12 @@ pub struct BufferManager { } impl BufferManager { + pub fn new() -> BufferManager { + BufferManager { + buffers: Vec::new(), + } + } + pub fn clear_buffers(&mut self) { for buf in self.buffers.iter_mut() { buf.buffer.destroy() diff --git a/src/fonts.rs b/src/fonts.rs index 49705d3..da6c431 100644 --- a/src/fonts.rs +++ b/src/fonts.rs @@ -1,24 +1,24 @@ //! Utility module for fonts use crate::draw; -use fontconfig::Fontconfig as FontConfig; +//use fontconfig::Fontconfig as FontConfig; use rusttype::Font; use std::{ cell::RefCell, - mem, - rc::Rc, - thread, collections::HashMap, fs::File, hash, io::Read, + mem, path::{Path, PathBuf}, + rc::Rc, + thread, }; - /// FontRef is used to store Fonts on widgets. pub type FontRef<'a> = &'a rusttype::Font<'a>; +/* /// FontSeeker is a marker struct that is used to look up fonts pub(crate) struct FontSeeker; @@ -30,7 +30,7 @@ impl FontSeeker { fc.find(name, None).unwrap().path } } - +*/ /// FontLoader is a marker struct that is used to load files pub(crate) struct FontLoader; @@ -113,7 +113,8 @@ impl FontMap { let path = if v.0.starts_with("/") { std::path::PathBuf::from(v.0) } else { - FontSeeker::from_string(v.0) + //FontSeeker::from_string(v.0) + panic!("required FontSeeker"); }; let fontref = Box::leak(Box::new( FontLoader::from_path(path).expect("unable to load font"), @@ -158,4 +159,3 @@ impl MaybeFontMap { } } } - diff --git a/src/main.rs b/src/main.rs index a99243e..a101746 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,11 +3,11 @@ mod color; mod draw; mod fonts; mod keyboard; +mod state; mod utils; mod widgets; -mod state; -use wayland_client::{Connection, QueueHandle, WaylandSource,}; +use wayland_client::{Connection, QueueHandle, WaylandSource}; use calloop::{ timer::{TimeoutAction, Timer}, @@ -15,27 +15,16 @@ use calloop::{ }; use chrono::{Duration, Local, Timelike}; -use buffer::{BufferManager, BufferView}; +use buffer::BufferView; use color::Color; -use fonts::{MaybeFontMap, FontMap}; -use keyboard::Keyboard; +use fonts::{FontMap, MaybeFontMap}; +use state::{Event, Events, State}; use widgets::{ - Backlight, Battery, Clock, Date, Geometry, Interface, Line, WidgetUpdater, + Backlight, Battery, Calendar, Clock, Date, Geometry, HorizontalLayout, IndexedLayout, + Interface, InvertedHorizontalLayout, Line, Margin, VerticalLayout, Widget, }; -use state::State; -use std::thread; - -impl WidgetUpdater for State { - fn geometry_update( - &mut self, - idx: usize, - fonts: &mut FontMap, - geometry: &Geometry, - ) -> Geometry { - self.widgets[idx].geometry_update(fonts, geometry) - } -} +use std::{rc::Rc, thread}; fn main() { let font_thread = thread::spawn(move || { @@ -49,6 +38,21 @@ fn main() { "/usr/share/fonts/noto/NotoSansMono-Regular.ttf", 32., ); + fm.queue_font_path( + "monospace", + "/usr/share/fonts/noto/NotoSansMono-Regular.ttf", + 12., + ); + fm.queue_font_path( + "monospace", + "/usr/share/fonts/noto/NotoSansMono-Regular.ttf", + 12. * 1.5, + ); + fm.queue_font_path( + "monospace", + "/usr/share/fonts/noto/NotoSansMono-Regular.ttf", + 12. * 2.0, + ); fm.load_fonts(); fm }); @@ -60,19 +64,34 @@ fn main() { let display = conn.display(); display.get_registry(&qhandle, ()); - let bufmgr = BufferManager { - buffers: Vec::new(), - }; - let (ping_sender, ping_source) = calloop::ping::make_ping().unwrap(); + let events = Events::new(ping_sender); + let clock = Box::new(Clock::new("sans", 128.)); +// let calendar = Box::new(Calendar::new("monospace", 12.0, 1, -1)); let date = Box::new(Date::new("sans", 48.)); let line = Box::new(Line::new()); let launcher = Box::new(Interface::new("monospace", 32.)); - let battery = Box::new(Battery::new(ping_sender, "sans", 24.)); + let battery = Box::new(Battery::new(events.clone(), "sans", 24.)); let backlight = Box::new(Backlight::new("intel_backlight", "sans", 24.)); + let widgets: Vec> = + vec![clock, date, battery, backlight, line, launcher]; + let layout = Rc::new(VerticalLayout::new(vec![ + HorizontalLayout::new(vec![ + IndexedLayout::new(0), + Margin::new(IndexedLayout::new(1), (16, 8, 0, 0)), + VerticalLayout::new(vec![ + Margin::new(IndexedLayout::new(2), (16, 8, 8, 0)), + Margin::new(IndexedLayout::new(3), (16, 8, 8, 0)), + ]), + ]), + IndexedLayout::new(4), + IndexedLayout::new(5), +// InvertedHorizontalLayout::new(vec![IndexedLayout::new(5), IndexedLayout::new(6)]), + ])); + let mut event_loop: EventLoop = EventLoop::try_new().expect("Failed to initialize the event loop!"); @@ -86,7 +105,12 @@ fn main() { handle .insert_source(ping_source, |(), &mut (), state| { - state.widgets[2].set_dirty(true); + let events = state.events.lock().unwrap().flush(); + for widget in state.widgets.iter_mut() { + for event in events.iter() { + widget.event(event); + } + } state.dirty = true; }) .expect("Failed to insert ping source!"); @@ -101,8 +125,9 @@ fn main() { .with_nanosecond(0) .unwrap(); let d = target - now; - state.widgets[0].set_dirty(true); - state.widgets[1].set_dirty(true); + for widget in state.widgets.iter_mut() { + widget.event(&Event::NewMinute); + } state.dirty = true; // The timer event source requires us to return a TimeoutAction to @@ -111,22 +136,7 @@ fn main() { }) .expect("Failed to insert event source!"); - let mut state = State { - running: true, - dirty: true, - activated: false, - base_surface: None, - wm_base: None, - xdg_surface: None, - wl_shm: None, - xdg_activation: None, - configured: false, - dimensions: (320, 240), - bufmgr: bufmgr, - widgets: vec![clock, date, battery, backlight, line, launcher], - keyboard: Keyboard::new(), - fonts: MaybeFontMap::Waiting(font_thread), - }; + let mut state = State::new(widgets, layout, MaybeFontMap::Waiting(font_thread), events); let mut damage = Vec::new(); for _ in 0..state.widgets.len() { @@ -142,7 +152,6 @@ fn main() { continue; } - let mut drew = false; let mut force = false; if state.bufmgr.buffers.len() == 0 { state.add_buffer(&qhandle); @@ -168,43 +177,51 @@ fn main() { let bg = Color::new(0., 0., 0., 1.); let surface = state.base_surface.as_ref().unwrap().clone(); - for (idx, widget) in state.widgets.iter_mut().enumerate() { - if force || widget.get_dirty() { - widget.set_dirty(false); - drew = true; - - let geo = widget.geometry(); - let old_damage = damage[idx]; - - if !force { - bufview.subgeometry(old_damage).unwrap().memset(&bg); - } else { + if force { + for (idx, widget) in state.widgets.iter_mut().enumerate() { + if force || widget.get_dirty() { + let geo = widget.geometry(); bufview.subgeometry(geo).unwrap().memset(&bg); - } - let mut subview = bufview.subgeometry(geo).unwrap(); - let new_damage = widget.draw(&mut state.fonts.unwrap().borrow_mut(), &mut subview); - let combined_damage = new_damage.expand(old_damage); - damage[idx] = new_damage; - surface.damage_buffer( - combined_damage.x as i32, - combined_damage.y as i32, - combined_damage.width as i32, - combined_damage.height as i32, - ); - //draw_box(&mut subview, &Color::new(1.0, 0.5, 0.0, 1.0), (geo.width, geo.height)); + let mut subview = bufview.subgeometry(geo).unwrap(); + damage[idx] = widget.draw(&mut state.fonts.unwrap().borrow_mut(), &mut subview); + //draw_box(&mut subview, &Color::new(1.0, 0.5, 0.0, 1.0), (geo.width, geo.height)); + } + } + surface.damage_buffer(0, 0, 0x7FFFFFFF, 0x7FFFFFFF) + } else { + let mut drew = false; + for (idx, widget) in state.widgets.iter_mut().enumerate() { + if force || widget.get_dirty() { + drew = true; + + let old_damage = damage[idx]; + bufview.subgeometry(old_damage).unwrap().memset(&bg); + + let geo = widget.geometry(); + let mut subview = bufview.subgeometry(geo).unwrap(); + + let new_damage = + widget.draw(&mut state.fonts.unwrap().borrow_mut(), &mut subview); + let combined_damage = new_damage.expand(old_damage); + damage[idx] = new_damage; + + surface.damage_buffer( + combined_damage.x as i32, + combined_damage.y as i32, + combined_damage.width as i32, + combined_damage.height as i32, + ); + //draw_box(&mut subview, &Color::new(1.0, 0.5, 0.0, 1.0), (geo.width, geo.height)); + } + } + if !drew { + buf.release(); + continue; } } - if !drew { - buf.release(); - continue; - } - if force { - surface.damage_buffer(0, 0, 0x7FFFFFFF, 0x7FFFFFFF) - } surface.attach(Some(&buf.buffer), 0, 0); surface.commit(); } } - diff --git a/src/state.rs b/src/state.rs index d22d1e9..506e29f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,8 +1,12 @@ use std::{ env, process::exit, + rc::Rc, + sync::{Arc, Mutex}, }; +use calloop::ping::Ping; + use wayland_client::{ protocol::{ wl_buffer, wl_compositor, wl_keyboard, wl_registry, wl_seat, wl_shm, wl_shm_pool, @@ -18,31 +22,93 @@ use wayland_protocols::xdg::{ use crate::{ buffer::BufferManager, - fonts::MaybeFontMap, - keyboard::Keyboard, - widgets::{ - Geometry, HorizontalLayout, IndexedLayout, Layout, Margin, VerticalLayout, Widget, - }, + fonts::{FontMap, MaybeFontMap}, + keyboard::{KeyEvent, Keyboard}, + widgets::{Geometry, Layout, Widget, WidgetUpdater}, }; +pub enum Event { + NewMinute, + PowerUpdate, + KeyEvent(KeyEvent), + TokenUpdate(String), +} + +pub struct Events { + dirty: bool, + events: Vec, + ping: Ping, +} + +impl Events { + pub fn new(ping: Ping) -> Arc> { + Arc::new(Mutex::new(Events { + dirty: false, + events: Vec::new(), + ping, + })) + } + + pub fn add_event(&mut self, ev: Event) { + self.events.push(ev); + if !self.dirty { + self.dirty = true; + self.ping.ping(); + } + } + + pub fn flush(&mut self) -> Vec { + self.dirty = false; + self.events.drain(..).collect() + } +} + pub struct State { pub running: bool, pub dirty: bool, - pub activated: bool, + activated: bool, pub base_surface: Option, - pub wm_base: Option, - pub xdg_surface: Option<(xdg_surface::XdgSurface, xdg_toplevel::XdgToplevel)>, - pub wl_shm: Option, - pub xdg_activation: Option, + wm_base: Option, + xdg_surface: Option<(xdg_surface::XdgSurface, xdg_toplevel::XdgToplevel)>, + wl_shm: Option, + xdg_activation: Option, pub configured: bool, pub dimensions: (i32, i32), pub bufmgr: BufferManager, pub widgets: Vec>, - pub keyboard: Keyboard, + keyboard: Keyboard, pub fonts: MaybeFontMap, + pub events: Arc>, + layout: Rc>, } impl State { + pub fn new( + widgets: Vec>, + layout: Rc>, + fonts: MaybeFontMap, + events: Arc>, + ) -> State { + State { + running: true, + dirty: true, + activated: false, + base_surface: None, + wm_base: None, + xdg_surface: None, + wl_shm: None, + xdg_activation: None, + configured: false, + dimensions: (320, 240), + bufmgr: BufferManager::new(), + keyboard: Keyboard::new(), + widgets, + fonts, + events, + layout, + } + } + pub fn add_buffer(&mut self, qh: &QueueHandle) { self.bufmgr.add_buffer( self.wl_shm.as_ref().expect("missing wl_shm"), @@ -68,6 +134,17 @@ impl State { } } +impl WidgetUpdater for State { + fn geometry_update( + &mut self, + idx: usize, + fonts: &mut FontMap, + geometry: &Geometry, + ) -> Geometry { + self.widgets[idx].geometry_update(fonts, geometry) + } +} + impl Dispatch for State { fn event( state: &mut Self, @@ -264,36 +341,9 @@ impl Dispatch for State { if state.dimensions != (width, height) { state.bufmgr.clear_buffers(); state.dimensions = (width, height); - let mut layout = VerticalLayout { - widgets: vec![ - Box::new(HorizontalLayout { - widgets: vec![ - Box::new(IndexedLayout { widget_idx: 0 }), - Box::new(Margin { - widget: Box::new(IndexedLayout { widget_idx: 1 }), - margin: (16, 8, 0, 0), - }), - Box::new(VerticalLayout { - widgets: vec![ - Box::new(Margin { - widget: Box::new(IndexedLayout { widget_idx: 2 }), - margin: (16, 8, 8, 0), - }), - Box::new(Margin { - widget: Box::new(IndexedLayout { widget_idx: 3 }), - margin: (16, 8, 8, 0), - }), - ], - }), - ], - }), - Box::new(IndexedLayout { widget_idx: 4 }), - Box::new(IndexedLayout { widget_idx: 5 }), - ], - }; - state.fonts.resolve(); let fonts = state.fonts.unwrap(); + let layout = state.layout.clone(); layout.geometry_update( &mut fonts.borrow_mut(), &Geometry { @@ -355,8 +405,9 @@ impl Dispatch for State { return; } let k = state.keyboard.key(serial, time, key, kbstate); + let ev = Event::KeyEvent(k); for widget in state.widgets.iter_mut() { - widget.keyboard_input(&k); + widget.event(&ev); } state.dirty = true; } @@ -372,6 +423,7 @@ impl Dispatch for State { .modifiers(mods_depressed, mods_latched, mods_locked, group); } wl_keyboard::Event::Keymap { format, fd, size } => { + // TODO: Keymap loading blocks xdg configure, consider delaying state.keyboard.keymap(format, fd, size); } _ => (), @@ -402,8 +454,9 @@ impl Dispatch for State { ) { match event { xdg_activation_token_v1::Event::Done { token } => { + let ev = Event::TokenUpdate(token); for widget in state.widgets.iter_mut() { - widget.token_update(&token); + widget.event(&ev); } } _ => (), diff --git a/src/widgets/backlight.rs b/src/widgets/backlight.rs index 02064a9..1226ce8 100644 --- a/src/widgets/backlight.rs +++ b/src/widgets/backlight.rs @@ -20,7 +20,6 @@ pub struct Backlight { device_path: PathBuf, cur: u64, max: u64, - dirty: bool, } impl Backlight { @@ -34,7 +33,6 @@ impl Backlight { device_path: Path::new("/sys/class/backlight").to_path_buf().join(path), cur: 0, max: 0, - dirty: false, }; dev.update(); @@ -43,12 +41,6 @@ impl Backlight { } impl BarWidgetImpl for Backlight { - fn get_dirty(&self) -> bool { - self.dirty - } - fn set_dirty(&mut self, dirty: bool) { - self.dirty = dirty - } fn name(&self) -> &'static str { "backlight" } diff --git a/src/widgets/bar_widget.rs b/src/widgets/bar_widget.rs index f57febb..e10e23f 100644 --- a/src/widgets/bar_widget.rs +++ b/src/widgets/bar_widget.rs @@ -9,8 +9,9 @@ use crate::{ }; pub trait BarWidgetImpl { - fn get_dirty(&self) -> bool; - fn set_dirty(&mut self, dirty: bool); + fn get_dirty(&self) -> bool { + false + } fn name(&self) -> &'static str; fn value(&self) -> f32; fn color(&self) -> Color; @@ -27,7 +28,9 @@ impl BarWidget { pub fn new(inner_widget: Box, font: &'static str, size: f32) -> BarWidget { BarWidget { geometry: Default::default(), - inner_widget, font, size, + inner_widget, + font, + size, } } } @@ -41,10 +44,6 @@ impl Widget for BarWidget { self.inner_widget.get_dirty() } - fn set_dirty(&mut self, dirty: bool) { - self.inner_widget.set_dirty(dirty) - } - fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { let font = fonts.get_font(self.font, self.size); let fg = Color::new(1., 1., 1., 1.); diff --git a/src/widgets/battery.rs b/src/widgets/battery.rs index e448871..4eeaea2 100644 --- a/src/widgets/battery.rs +++ b/src/widgets/battery.rs @@ -4,8 +4,6 @@ use std::{ time::Duration, }; -use calloop::ping::Ping; - use dbus::blocking::{ stdintf::org_freedesktop_dbus::{Properties, PropertiesPropertiesChanged}, LocalConnection, @@ -13,6 +11,7 @@ use dbus::blocking::{ use crate::{ color::Color, + state::{Event, Events}, widgets::bar_widget::{BarWidget, BarWidgetImpl}, }; @@ -28,9 +27,10 @@ enum UpowerBatteryState { struct InnerBattery { value: f32, state: UpowerBatteryState, + dirty: bool, } -fn start_monitor(inner: Arc>, ping: Ping) { +fn start_monitor(inner: Arc>, events: Arc>) { thread::Builder::new() .name("battmon".to_string()) .spawn(move || { @@ -84,11 +84,15 @@ fn start_monitor(inner: Arc>, ping: Ping) { 6 => UpowerBatteryState::Discharging, _ => UpowerBatteryState::Unknown, }; - ping.ping(); + inner.dirty = true; + let mut events = events.lock().unwrap(); + events.add_event(Event::PowerUpdate); } "Percentage" => { inner.value = value.0.as_f64().unwrap() as f32 / 100.; - ping.ping(); + inner.dirty = true; + let mut events = events.lock().unwrap(); + events.add_event(Event::PowerUpdate); } _ => (), } @@ -107,35 +111,32 @@ fn start_monitor(inner: Arc>, ping: Ping) { pub struct Battery { inner: Arc>, - dirty: bool, } impl Battery { - pub fn new(ping: Ping, font: &'static str, size: f32) -> BarWidget { + pub fn new(events: Arc>, font: &'static str, size: f32) -> BarWidget { let battery = Battery { inner: Arc::new(Mutex::new(InnerBattery { value: 0., state: UpowerBatteryState::Unknown, + dirty: false, })), - dirty: false, }; - start_monitor(battery.inner.clone(), ping); + start_monitor(battery.inner.clone(), events); BarWidget::new(Box::new(battery), font, size) } } impl BarWidgetImpl for Battery { fn get_dirty(&self) -> bool { - self.dirty - } - fn set_dirty(&mut self, dirty: bool) { - self.dirty = dirty + self.inner.lock().unwrap().dirty } fn name(&self) -> &'static str { "battery" } fn value(&self) -> f32 { - let inner = self.inner.lock().unwrap(); + let mut inner = self.inner.lock().unwrap(); + inner.dirty = false; inner.value } fn color(&self) -> Color { diff --git a/src/widgets/calendar.rs b/src/widgets/calendar.rs new file mode 100644 index 0000000..f16f96f --- /dev/null +++ b/src/widgets/calendar.rs @@ -0,0 +1,273 @@ +use std::default::Default; + +use crate::{ + buffer::BufferView, + color::Color, + fonts::FontMap, + state::Event, + widgets::{Geometry, Widget}, +}; + +use chrono::{Datelike, Local, NaiveDate}; + +pub struct Calendar { + font: &'static str, + sections_x: i32, + sections_y: i32, + size: f32, + dirty: bool, + geometry: Geometry, +} + +impl Calendar { + pub fn new(font: &'static str, size: f32, sections_x: i32, sections_y: i32) -> Calendar { + Calendar { + font, + size, + sections_x, + sections_y, + dirty: true, + geometry: Default::default(), + } + } + + fn draw_month( + &mut self, + fonts: &mut FontMap, + mut view: &mut BufferView, + orig: NaiveDate, + mut time: NaiveDate, + ) { + let black = Color::new(0., 0., 0., 1.); + let white = Color::new(1., 1., 1., 1.); + let dim = Color::new(0.8, 0.8, 0.8, 1.); + let dimmer = Color::new(0.75, 0.75, 0.75, 1.); + let month_str = match time.month() { + 1 => "January", + 2 => "February", + 3 => "March", + 4 => "April", + 5 => "May", + 6 => "June", + 7 => "July", + 8 => "August", + 9 => "September", + 10 => "October", + 11 => "November", + 12 => "December", + _ => panic!("impossible value"), + }; + + + let mut y_off = 1; + // TODO: Fix offsets: + // - Headline font is not correctly accounted for + + // + // Draw the week day + // + for idx in 1..8 { + let day_font = fonts.get_font(self.font, self.size); + let wk_chr = match idx { + 1 => "MON", + 2 => "TUE", + 3 => "WED", + 4 => "THU", + 5 => "FRI", + 6 => "SAT", + 7 => "SUN", + _ => panic!("impossible value"), + }; + + day_font + .auto_draw_text( + &mut view + .offset(( + idx * (self.size * 2.5).ceil() as u32 + (self.size / 5.).ceil() as u32, + y_off * (self.size * 1.8).ceil() as u32 + + (self.size * 1.).ceil() as u32, + )) + .unwrap(), + &black, + &white, + &wk_chr, + ) + .unwrap(); + } + + // + // Draw the month + // + if time.year() != orig.year() { + let year_font = fonts.get_font(self.font, self.size * 1.5); + year_font + .auto_draw_text( + &mut view.offset(((self.size * 17.).ceil() as u32, 0)).unwrap(), + &black, + &dim, + &format!("{:}", time.year()), + ) + .unwrap(); + } + + let cal_font = fonts.get_font(self.font, self.size * 2.); + cal_font + .auto_draw_text(&mut view, &black, &white, month_str) + .unwrap(); + + let mut done = false; + while !done { + let mut x_pos = 0; + + // + // Find the start of this week + // + let mut wkday = time.weekday(); + while wkday != chrono::Weekday::Mon { + x_pos += 1; + wkday = wkday.pred(); + } + + // + // Draw the week number + // + let wk = time.iso_week(); + cal_font + .auto_draw_text( + &mut view + .offset(( + 0, + y_off * (self.size * 1.8).ceil() as u32 + + (self.size * 2.).ceil() as u32, + )) + .unwrap(), + &black, + &dimmer, + &format!("{:02}", wk.week()), + ) + .unwrap(); + x_pos += 1; + + // + // Draw the dates + // + while x_pos < 8 { + let c = if time.day() == orig.day() && time.month() == orig.month() { + Color::new(1.0, 1.0, 1.0, 1.0) + } else { + Color::new(0.5, 0.5, 0.5, 1.0) + }; + + cal_font + .auto_draw_text( + &mut view + .offset(( + x_pos * (self.size * 2.5).ceil() as u32, + y_off * (self.size * 1.8).ceil() as u32 + (self.size * 2.) as u32, + )) + .unwrap(), + &black, + &c, + &format!("{:02}", time.day()), + ) + .unwrap(); + + let t = time.with_day(time.day() + 1); + if t.is_none() { + done = true; + break; + } + time = t.unwrap(); + x_pos += 1; + } + + y_off += 1; + } + } +} + +impl<'a> Widget for Calendar { + fn get_dirty(&self) -> bool { + self.dirty + } + + fn geometry(&self) -> Geometry { + self.geometry + } + + fn event(&mut self, event: &Event) { + match event { + Event::NewMinute => { + self.dirty = true; + } + _ => (), + } + } + + fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { + let time = Local::now().naive_local().date(); + + let cal_height = (self.size * 16.25).ceil() as u32; + let cal_width = (7. * self.size * 3. + self.size * 2.).ceil() as u32; + let cal_pad = (3. * self.size).ceil() as u32; + let sections_y = if self.sections_y > 0 { + self.sections_y as u32 + } else { + self.geometry.height / cal_height + }; + let sections_x = if self.sections_x > 0 { + self.sections_x as u32 + } else { + self.geometry.width / (cal_height + cal_pad) + }; + + let mut t = time.with_day(1).unwrap(); + let cals = sections_y * sections_x - 1; + if cals >=3 { + t = t.pred_opt().unwrap().with_day(1).unwrap(); + } + + for ydx in 0..sections_y { + for idx in 0..sections_x { + self.draw_month( + fonts, + &mut view + .offset(((cal_width + cal_pad) * idx, (cal_height) * ydx)) + .unwrap(), + time, + t, + ); + t = if t.month() == 12 { + t.with_year(t.year() + 1).unwrap().with_month(1).unwrap() + } else { + t.with_month(t.month() + 1).unwrap() + }; + } + } + self.dirty = false; + self.geometry + } + + fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry { + let cal_width = (7. * self.size * 3. + self.size * 2.).ceil() as u32; + let cal_height = (self.size * 16.25).ceil() as u32; // TODO: Calculate from font sizes + let cal_pad = (self.size * 3.).ceil() as u32; + let width = if self.sections_x > 0 { + cal_width * self.sections_x as u32 + cal_pad * (self.sections_x as u32 - 1) + } else { + (geometry.width / (cal_width + cal_pad)) * (cal_width + cal_pad) + }; + let height = if self.sections_y > 0 { + cal_height * self.sections_y as u32 + } else { + (geometry.height / cal_height) * cal_height + }; + self.geometry = Geometry { + x: geometry.x, + y: geometry.y, + width: width, + height: height, + }; + self.geometry + } +} diff --git a/src/widgets/clock.rs b/src/widgets/clock.rs index 63ee961..0fb807c 100644 --- a/src/widgets/clock.rs +++ b/src/widgets/clock.rs @@ -4,6 +4,7 @@ use crate::{ buffer::BufferView, color::Color, fonts::FontMap, + state::Event, widgets::{Geometry, Widget}, }; @@ -21,7 +22,8 @@ pub struct Clock { impl<'a> Clock { pub fn new(font: &'static str, size: f32) -> Clock { Clock { - font, size, + font, + size, dirty: true, geometry: Default::default(), digit_width: 0, @@ -31,9 +33,6 @@ impl<'a> Clock { } impl<'a> Widget for Clock { - fn set_dirty(&mut self, dirty: bool) { - self.dirty = dirty; - } fn get_dirty(&self) -> bool { self.dirty } @@ -42,6 +41,15 @@ impl<'a> Widget for Clock { self.geometry } + fn event(&mut self, event: &Event) { + match event { + Event::NewMinute => { + self.dirty = true; + } + _ => (), + } + } + fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { let time = Local::now().naive_local(); let fg = Color::new(1., 1., 1., 1.); @@ -58,6 +66,7 @@ impl<'a> Widget for Clock { &format!("{:02}:{:02}", time.hour(), time.minute()), ) .unwrap(); + self.dirty = false; self.geometry } diff --git a/src/widgets/date.rs b/src/widgets/date.rs index b56c5f0..1c9d806 100644 --- a/src/widgets/date.rs +++ b/src/widgets/date.rs @@ -4,6 +4,7 @@ use crate::{ buffer::BufferView, color::Color, fonts::FontMap, + state::Event, widgets::{Geometry, Widget}, }; @@ -19,7 +20,8 @@ pub struct Date { impl Date { pub fn new(font: &'static str, size: f32) -> Date { Date { - font, size, + font, + size, dirty: true, geometry: Default::default(), } @@ -27,9 +29,6 @@ impl Date { } impl<'a> Widget for Date { - fn set_dirty(&mut self, dirty: bool) { - self.dirty = dirty; - } fn get_dirty(&self) -> bool { self.dirty } @@ -38,6 +37,15 @@ impl<'a> Widget for Date { self.geometry } + fn event(&mut self, event: &Event) { + match event { + Event::NewMinute => { + self.dirty = true; + } + _ => (), + } + } + fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { let time = Local::now().naive_local(); let fg = Color::new(1., 1., 1., 1.); @@ -57,6 +65,7 @@ impl<'a> Widget for Date { ), ) .unwrap(); + self.dirty = false; self.geometry } diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs index 7afa221..80c9e82 100644 --- a/src/widgets/launcher.rs +++ b/src/widgets/launcher.rs @@ -1,5 +1,5 @@ use std::{ - cmp::{min, max, Ordering}, + cmp::{max, min, Ordering}, collections::HashMap, default::Default, process::{exit, Command}, @@ -12,6 +12,7 @@ use crate::{ color::Color, fonts::FontMap, keyboard::{keysyms, KeyEvent}, + state::Event, utils::desktop::{load_desktop_files, Desktop}, widgets::{Geometry, Widget}, }; @@ -287,18 +288,17 @@ impl InterfaceWidget for Launcher { // Draw line let mut prompt_offset = intf.geometry.height - line_height; let mut prompt_line = view.offset((0, prompt_offset)).unwrap(); - let mut prompt_border = prompt_line.limit((intf.geometry.width, 1)).unwrap(); - prompt_border.memset(&fg); let font = fonts.get_font(intf.font, intf.size); - let mut x_max = font.auto_draw_text_with_cursor( - &mut prompt_line, - &bg, - &fg, - &format!(" {}", &intf.prompt.input), - intf.prompt.cursor + 3, - ) - .unwrap(); + let mut x_max = font + .auto_draw_text_with_cursor( + &mut prompt_line, + &bg, + &fg, + &format!(" {}", &intf.prompt.input), + intf.prompt.cursor + 3, + ) + .unwrap(); font.auto_draw_text(&mut prompt_line, &bg, &fg, ">") .unwrap(); @@ -328,11 +328,17 @@ impl InterfaceWidget for Launcher { colors.push(Color::new(0.75, 0.75, 0.75, 1.0)); } } - x_max = max(x_max, font.auto_draw_text_individual_colors(&mut line, &bg, &colors, &m.name) - .unwrap()); + x_max = max( + x_max, + font.auto_draw_text_individual_colors(&mut line, &bg, &colors, &m.name) + .unwrap(), + ); } else { - x_max = max(x_max, font.auto_draw_text(&mut line, &bg, &dimfg, &m.name) - .unwrap()); + x_max = max( + x_max, + font.auto_draw_text(&mut line, &bg, &dimfg, &m.name) + .unwrap(), + ); } } @@ -343,7 +349,7 @@ impl InterfaceWidget for Launcher { Geometry { x: intf.geometry.x, y: intf.geometry.y + intf.geometry.height - content_height, - width: x_max.0+1, + width: x_max.0 + 1, height: content_height, } } @@ -381,19 +387,18 @@ impl InterfaceWidget for Shell { // Draw line let prompt_offset = intf.geometry.height - line_height; let mut prompt_line = view.offset((0, prompt_offset)).unwrap(); - let mut prompt_border = prompt_line.limit((intf.geometry.width, 1)).unwrap(); - prompt_border.memset(&fg); // Draw prompt let font = fonts.get_font(intf.font, intf.size); - let x_max = font.auto_draw_text_with_cursor( - &mut prompt_line, - &bg, - &fg, - &format!(" {}", &intf.prompt.input), - intf.prompt.cursor + 3, - ) - .unwrap(); + let x_max = font + .auto_draw_text_with_cursor( + &mut prompt_line, + &bg, + &fg, + &format!(" {}", &intf.prompt.input), + intf.prompt.cursor + 3, + ) + .unwrap(); font.auto_draw_text(&mut prompt_line, &bg, &Color::new(1., 0.75, 0.5, 1.), "!") .unwrap(); @@ -443,8 +448,6 @@ impl InterfaceWidget for Calc { // Draw line let mut prompt_offset = intf.geometry.height - (line_height); let mut prompt_line = view.offset((0, prompt_offset)).unwrap(); - let mut prompt_border = prompt_line.limit((intf.geometry.width, 1)).unwrap(); - prompt_border.memset(&fg); // Draw prompt let font = fonts.get_font(intf.font, intf.size); @@ -518,7 +521,8 @@ impl Interface { result: None, }, inner: InnerInterface { - font, size, + font, + size, dirty: false, geometry: Default::default(), selection: 0, @@ -526,32 +530,6 @@ impl Interface { }, } } -} - -impl Widget for Interface { - fn set_dirty(&mut self, dirty: bool) { - self.inner.dirty = dirty; - } - fn get_dirty(&self) -> bool { - self.inner.dirty - } - - fn geometry(&self) -> Geometry { - self.inner.geometry - } - - fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { - match self.inner.prompt.mode { - PromptMode::Shell => self.shell.draw(&self.inner, fonts, view), - PromptMode::Normal => self.launcher.draw(&self.inner, fonts, view), - PromptMode::Calc => self.calc.draw(&self.inner, fonts, view), - } - } - - fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry { - self.inner.geometry = geometry.clone(); - self.inner.geometry - } fn keyboard_input(&mut self, event: &KeyEvent) { if event.state != WEnum::Value(wl_keyboard::KeyState::Pressed) { @@ -610,8 +588,36 @@ impl Widget for Interface { } } } +} - fn token_update(&mut self, token: &str) { - self.launcher.next_token = Some(token.to_string()); +impl Widget for Interface { + fn get_dirty(&self) -> bool { + self.inner.dirty + } + + fn geometry(&self) -> Geometry { + self.inner.geometry + } + + fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { + self.inner.dirty = false; + match self.inner.prompt.mode { + PromptMode::Shell => self.shell.draw(&self.inner, fonts, view), + PromptMode::Normal => self.launcher.draw(&self.inner, fonts, view), + PromptMode::Calc => self.calc.draw(&self.inner, fonts, view), + } + } + + fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry { + self.inner.geometry = geometry.clone(); + self.inner.geometry + } + + fn event(&mut self, event: &Event) { + match event { + Event::KeyEvent(e) => self.keyboard_input(e), + Event::TokenUpdate(t) => self.launcher.next_token = Some(t.to_string()), + _ => (), + } } } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 46f7cb4..ac08074 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,6 +1,7 @@ mod backlight; mod bar_widget; mod battery; +mod calendar; mod clock; mod date; mod launcher; @@ -9,11 +10,12 @@ mod widget; pub use backlight::Backlight; pub use battery::Battery; +pub use calendar::Calendar; pub use clock::Clock; pub use date::Date; pub use launcher::Interface; pub use line::Line; pub use widget::{ - Geometry, HorizontalLayout, IndexedLayout, Layout, Margin, VerticalLayout, Widget, - WidgetUpdater, + Geometry, HorizontalLayout, IndexedLayout, InvertedHorizontalLayout, Layout, Margin, + VerticalLayout, Widget, WidgetUpdater, }; diff --git a/src/widgets/widget.rs b/src/widgets/widget.rs index a5fef27..c63f8e6 100644 --- a/src/widgets/widget.rs +++ b/src/widgets/widget.rs @@ -1,7 +1,13 @@ -use std::cmp::{max, min}; -use std::fmt; +use std::{ + cmp::{max, min}, + fmt, +}; -use crate::{buffer::BufferView, fonts::FontMap, keyboard::KeyEvent}; +use crate::{ + buffer::BufferView, + fonts::FontMap, + state::{Event, State}, +}; #[derive(Debug, Default, Clone, Copy)] pub struct Geometry { @@ -47,38 +53,42 @@ pub trait Widget { fn get_dirty(&self) -> bool { false } - fn set_dirty(&mut self, _dirty: bool) {} fn geometry(&self) -> Geometry; fn geometry_update(&mut self, fonts: &mut FontMap, geometry: &Geometry) -> Geometry; fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry; - fn keyboard_input(&mut self, _event: &KeyEvent) {} - fn token_update(&mut self, _token: &str) {} + fn event(&mut self, _event: &Event) {} } -pub trait Layout { +pub trait Layout { fn geometry_update( - &mut self, + &self, fonts: &mut FontMap, geometry: &Geometry, - user_data: &mut U, + state: &mut State, ) -> Geometry; } -pub struct HorizontalLayout { - pub widgets: Vec>>, +pub struct HorizontalLayout { + pub widgets: Vec>, } -impl Layout for HorizontalLayout { +impl HorizontalLayout { + pub fn new(widgets: Vec>) -> Box { + Box::new(HorizontalLayout { widgets }) + } +} + +impl Layout for HorizontalLayout { fn geometry_update( - &mut self, + &self, fonts: &mut FontMap, geometry: &Geometry, - user_data: &mut U, + state: &mut State, ) -> Geometry { let mut geo = geometry.clone(); let mut max_width = 0; let mut max_height = 0; - for w in self.widgets.iter_mut() { - let result = w.geometry_update(fonts, &geo, user_data); + for w in self.widgets.iter() { + let result = w.geometry_update(fonts, &geo, state); geo.x = result.x + result.width; geo.width -= result.width; max_width += result.width; @@ -90,22 +100,28 @@ impl Layout for HorizontalLayout { } } -pub struct VerticalLayout { - pub widgets: Vec>>, +pub struct VerticalLayout { + pub widgets: Vec>, } -impl Layout for VerticalLayout { +impl VerticalLayout { + pub fn new(widgets: Vec>) -> Box { + Box::new(VerticalLayout { widgets }) + } +} + +impl Layout for VerticalLayout { fn geometry_update( - &mut self, + &self, fonts: &mut FontMap, geometry: &Geometry, - user_data: &mut U, + state: &mut State, ) -> Geometry { let mut geo = geometry.clone(); let mut max_width = 0; let mut max_height = 0; - for w in self.widgets.iter_mut() { - let result = w.geometry_update(fonts, &geo, user_data); + for w in self.widgets.iter() { + let result = w.geometry_update(fonts, &geo, state); geo.y = result.y + result.height; geo.height -= result.height; max_width = max(result.width, max_width); @@ -126,28 +142,40 @@ pub struct IndexedLayout { pub widget_idx: usize, } -impl Layout for IndexedLayout { - fn geometry_update( - &mut self, - fonts: &mut FontMap, - geometry: &Geometry, - user_data: &mut U, - ) -> Geometry { - user_data.geometry_update(self.widget_idx, fonts, geometry) +impl IndexedLayout { + pub fn new(widget_idx: usize) -> Box { + Box::new(IndexedLayout { widget_idx }) } } -pub struct Margin { - pub widget: Box>, +impl Layout for IndexedLayout { + fn geometry_update( + &self, + fonts: &mut FontMap, + geometry: &Geometry, + state: &mut State, + ) -> Geometry { + state.geometry_update(self.widget_idx, fonts, geometry) + } +} + +pub struct Margin { + pub widget: Box, pub margin: (u32, u32, u32, u32), } -impl Layout for Margin { +impl Margin { + pub fn new(widget: Box, margin: (u32, u32, u32, u32)) -> Box { + Box::new(Margin { widget, margin }) + } +} + +impl Layout for Margin { fn geometry_update( - &mut self, + &self, fonts: &mut FontMap, geometry: &Geometry, - user_data: &mut U, + state: &mut State, ) -> Geometry { let geo = Geometry { x: geometry.x + self.margin.0, @@ -156,7 +184,7 @@ impl Layout for Margin { height: geometry.height - self.margin.1 - self.margin.3, }; - let out = self.widget.geometry_update(fonts, &geo, user_data); + let out = self.widget.geometry_update(fonts, &geo, state); Geometry { x: out.x - self.margin.0, y: out.y - self.margin.1, @@ -165,3 +193,35 @@ impl Layout for Margin { } } } + +pub struct InvertedHorizontalLayout { + pub widgets: Vec>, +} + +impl InvertedHorizontalLayout { + pub fn new(widgets: Vec>) -> Box { + Box::new(InvertedHorizontalLayout { widgets }) + } +} + +impl Layout for InvertedHorizontalLayout { + fn geometry_update( + &self, + fonts: &mut FontMap, + geometry: &Geometry, + state: &mut State, + ) -> Geometry { + let mut geo = geometry.clone(); + let mut max_height = 0; + for w in self.widgets.iter() { + let mut temp_geo = geo.clone(); + let temp_result = w.geometry_update(fonts, &temp_geo, state); + temp_geo.x = geo.x + (geo.width - temp_result.width); + let result = w.geometry_update(fonts, &temp_geo, state); + geo.width -= result.width; + max_height = max(result.height, max_height); + } + geo.height = max_height; + geo + } +} From b49b9e97640e8ac9abc08db94a248eb5be46bff6 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Thu, 2 Feb 2023 23:52:59 +0100 Subject: [PATCH 10/57] More stuff --- Cargo.lock | 761 ++++++++++++++++++++++++++++++++++---- Cargo.toml | 18 +- src/buffer.rs | 120 ++---- src/color.rs | 51 ++- src/draw.rs | 52 +-- src/event.rs | 61 +++ src/fonts.rs | 68 ++-- src/keyboard/mod.rs | 7 +- src/keyboard/state.rs | 3 + src/main.rs | 149 ++++---- src/state.rs | 126 +++++-- src/utils/desktop.rs | 96 ++--- src/utils/inish.rs | 134 +++++++ src/utils/mod.rs | 1 + src/widgets/audio.rs | 238 ++++++++++++ src/widgets/backlight.rs | 7 +- src/widgets/bar_widget.rs | 33 +- src/widgets/battery.rs | 171 ++++----- src/widgets/calendar.rs | 214 ++++++----- src/widgets/clock.rs | 13 +- src/widgets/date.rs | 23 +- src/widgets/launcher.rs | 109 ++++-- src/widgets/layout.rs | 174 +++++++++ src/widgets/line.rs | 14 +- src/widgets/mod.rs | 9 +- src/widgets/widget.rs | 173 +-------- 26 files changed, 2022 insertions(+), 803 deletions(-) create mode 100644 src/event.rs create mode 100644 src/utils/inish.rs create mode 100644 src/widgets/audio.rs create mode 100644 src/widgets/layout.rs diff --git a/Cargo.lock b/Cargo.lock index 6b33d5b..95766ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,14 +9,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330223a1aecc308757b9926e9391c9b47f8ef2dbd8aea9df88312aea18c5e8d6" [[package]] -name = "ahash" -version = "0.7.6" +name = "aho-corasick" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ - "getrandom", - "once_cell", - "version_check", + "memchr", ] [[package]] @@ -28,6 +26,89 @@ dependencies = [ "libc", ] +[[package]] +name = "async-broadcast" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b19760fa2b7301cf235360ffd6d3558b1ed4249edd16d6cca8d690cee265b95" +dependencies = [ + "event-listener", + "futures-core", + "parking_lot", +] + +[[package]] +name = "async-executor" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" +dependencies = [ + "async-lock", + "autocfg", + "concurrent-queue", + "futures-lite", + "libc", + "log", + "parking", + "polling", + "slab", + "socket2", + "waker-fn", + "windows-sys", +] + +[[package]] +name = "async-lock" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" +dependencies = [ + "event-listener", + "futures-lite", +] + +[[package]] +name = "async-recursion" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b015a331cc64ebd1774ba119538573603427eaace0a1950c423ab971f903796" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-task" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" + +[[package]] +name = "async-trait" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff18d764974428cf3a9328e23fc5c986f5fbed46e6cd4cdf42544df5d297ec1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -55,6 +136,18 @@ version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" + [[package]] name = "calloop" version = "0.10.5" @@ -105,6 +198,21 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "concurrent-queue" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-cstr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3d0b5ff30645a68f35ece8cea4556ca14ef8a1651455f789a099a0513532a6" + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -120,6 +228,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -175,14 +292,14 @@ dependencies = [ ] [[package]] -name = "dbus" -version = "0.9.6" +name = "derivative" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8bcdd56d2e5c4ed26a529c5a9029f5db8290d433497506f958eae3be148eb6" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "libc", - "libdbus-sys", - "winapi", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -195,6 +312,26 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dlib" version = "0.5.0" @@ -204,12 +341,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "dlv-list" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" - [[package]] name = "downcast-rs" version = "1.2.0" @@ -223,10 +354,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" [[package]] -name = "either" -version = "1.8.0" +name = "enumflags2" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58dc3c5e468259f19f2d46304a6b28f1c3d034442e14b322d2b850e36f6d5ae" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "fastrand" @@ -237,6 +389,68 @@ dependencies = [ "instant", ] +[[package]] +name = "fontconfig" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b0653582d9c82645f95bacd0dc87b49a3126f7a9633be8cafa9c6d17d525847" +dependencies = [ + "yeslogic-fontconfig-sys", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-io" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-sink" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" + +[[package]] +name = "futures-task" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" + +[[package]] +name = "futures-util" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "fuzzy-matcher" version = "0.3.7" @@ -268,13 +482,10 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.12.3" +name = "hex" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash", -] +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "iana-time-zone" @@ -319,15 +530,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "js-sys" version = "0.3.60" @@ -349,15 +551,6 @@ version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" -[[package]] -name = "libdbus-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c185b5b7ad900923ef3a8ff594083d4d9b5aea80bb4f32b8342363138c0d456b" -dependencies = [ - "pkg-config", -] - [[package]] name = "libloading" version = "0.7.4" @@ -368,6 +561,33 @@ dependencies = [ "winapi", ] +[[package]] +name = "libpulse-binding" +version = "2.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1745b20bfc194ac12ef828f144f0ec2d4a7fe993281fa3567a0bd4969aee6890" +dependencies = [ + "bitflags", + "libc", + "libpulse-sys", + "num-derive", + "num-traits", + "winapi", +] + +[[package]] +name = "libpulse-sys" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2191e6880818d1df4cf72eac8e91dce7a5a52ba0da4b2a5cdafabc22b937eadb" +dependencies = [ + "libc", + "num-derive", + "num-traits", + "pkg-config", + "winapi", +] + [[package]] name = "link-cplusplus" version = "1.0.7" @@ -377,6 +597,16 @@ dependencies = [ "cc", ] +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.17" @@ -429,6 +659,18 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + [[package]] name = "nix" version = "0.25.1" @@ -440,6 +682,7 @@ dependencies = [ "cfg-if", "libc", "memoffset 0.6.5", + "pin-utils", ] [[package]] @@ -477,6 +720,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -515,13 +769,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] -name = "ordered-multimap" -version = "0.4.3" +name = "ordered-stream" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +checksum = "d4eb9ba3f3e42dbdd3b7b122de5ca169c81e93d561eb900da3a8c99bcfcf381a" dependencies = [ - "dlv-list", - "hashbrown", + "futures-core", + "pin-project-lite", ] [[package]] @@ -533,6 +787,35 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + [[package]] name = "pest" version = "2.5.2" @@ -577,6 +860,12 @@ dependencies = [ "sha1", ] +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + [[package]] name = "pin-utils" version = "0.1.0" @@ -589,6 +878,37 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +[[package]] +name = "polling" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6" +dependencies = [ + "autocfg", + "cfg-if", + "libc", + "log", + "wepoll-ffi", + "windows-sys", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-crate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + [[package]] name = "proc-macro2" version = "1.0.47" @@ -616,6 +936,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rcalc_lib" version = "1.0.0" @@ -641,6 +991,34 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + [[package]] name = "remove_dir_all" version = "0.5.3" @@ -650,16 +1028,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "rust-ini" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" -dependencies = [ - "cfg-if", - "ordered-multimap", -] - [[package]] name = "rusttype" version = "0.9.3" @@ -685,12 +1053,49 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "scratch" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_repr" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha1" version = "0.10.5" @@ -708,6 +1113,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + [[package]] name = "slotmap" version = "1.0.6" @@ -723,6 +1137,16 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -803,6 +1227,75 @@ dependencies = [ "winapi", ] +[[package]] +name = "tokio" +version = "1.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "pin-project-lite", + "socket2", + "tracing", + "windows-sys", +] + +[[package]] +name = "tokio-stream" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + [[package]] name = "ttf-parser" version = "0.15.2" @@ -821,6 +1314,16 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +[[package]] +name = "uds_windows" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d" +dependencies = [ + "tempfile", + "winapi", +] + [[package]] name = "unicode-ident" version = "1.0.5" @@ -839,6 +1342,17 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "upower_dbus" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0b1d77de98ab2e5187f5fa2b045b8c4eecfc49e5ccd07f16f4c89f008116f8c" +dependencies = [ + "serde", + "serde_repr", + "zbus", +] + [[package]] name = "vec_map" version = "0.8.2" @@ -851,6 +1365,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "walkdir" version = "2.3.2" @@ -991,6 +1511,15 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1086,21 +1615,23 @@ dependencies = [ "bitflags", "calloop", "chrono", - "dbus", + "fontconfig", "fuzzy-matcher", - "itertools", + "libpulse-binding", "memmap", "nix 0.26.1", "rcalc_lib", - "rust-ini", "rusttype", "shlex", - "tempfile", + "tokio", + "tokio-stream", "unicode-segmentation", + "upower_dbus", "walkdir", "wayland-client", "wayland-protocols", "xkbcommon", + "zbus", ] [[package]] @@ -1112,3 +1643,105 @@ dependencies = [ "libc", "memmap2", ] + +[[package]] +name = "yeslogic-fontconfig-sys" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec657fd32bbcbeaef5c7bc8e10b3db95b143fab8db0a50079773dbf936fd4f73" +dependencies = [ + "const-cstr", + "dlib", + "once_cell", + "pkg-config", +] + +[[package]] +name = "zbus" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "379d587c0ccb632d1179cf44082653f682842f0535f0fdfaefffc34849cc855e" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-recursion", + "async-task", + "async-trait", + "byteorder", + "derivative", + "dirs", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "lazy_static", + "nix 0.25.1", + "once_cell", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tokio", + "tracing", + "uds_windows", + "winapi", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66492a2e90c0df7190583eccb8424aa12eb7ff06edea415a4fff6688fae18cf8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "zbus_names" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f34f314916bd89bdb9934154627fab152f4f28acdda03e7c4c68181b214fe7e3" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zvariant" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "576cc41e65c7f283e5460f5818073e68fb1f1631502b969ef228c2e03c862efb" +dependencies = [ + "byteorder", + "enumflags2", + "libc", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fd4aafc0dee96ae7242a24249ce9babf21e1562822f03df650d4e68c20e41ed" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 372eb25..8bf8b4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,26 +4,25 @@ version = "0.1.0" edition = "2021" [profile.release] +#debug = true lto = "fat" panic = "abort" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +fontloading = ["fontconfig"] [dependencies] wayland-client = { version = "0.30", features = ["calloop", "log"] } wayland-protocols = { version = "0.30", features = ["client", "unstable", "staging"] } -tempfile = "3.3" nix = "0.26" memmap = "0.7" -#fontconfig = "0.6.0" -rusttype = "^0.9" +fontconfig = { version = "0.6.0", optional = true } +rusttype = "0.9" calloop = { version = "0.10" } chrono = "0.4" walkdir = "2.3" -itertools = "0.10" -rust-ini = "0.18" - bitflags = "1.3" unicode-segmentation = "1.10" @@ -31,6 +30,11 @@ fuzzy-matcher = "0.3" shlex = "1.1" xkbcommon = { version = "0.5", features = ["wayland"] } -dbus = "0.9" +upower_dbus = "0.3" +zbus = { version = "3.7", default-features = false, features = ["tokio"] } +tokio = { version = "1.24", default-features = false, features = ["rt"] } +tokio-stream = "0.1" rcalc_lib = "1.0" + +libpulse-binding = "2.26" diff --git a/src/buffer.rs b/src/buffer.rs index 685583b..4be85fa 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -170,20 +170,27 @@ impl BufferManager { } pub struct BufferView<'a> { - buf: &'a mut MmapMut, + view: &'a mut [u32], dimensions: (u32, u32), subdimensions: Option<(u32, u32, u32, u32)>, } impl<'a> BufferView<'a> { pub fn new(buf: &'a mut MmapMut, dimensions: (u32, u32)) -> BufferView { + let view = unsafe { + std::slice::from_raw_parts_mut( + buf.as_mut_ptr() as *mut u32, + (dimensions.0 * dimensions.1) as usize, + ) + }; BufferView { - buf, + view, dimensions, subdimensions: None, } } + #[inline] pub fn get_bounds(&self) -> (u32, u32, u32, u32) { if let Some(subdim) = self.subdimensions { subdim @@ -195,22 +202,16 @@ impl<'a> BufferView<'a> { pub fn subdimensions( &mut self, subdimensions: (u32, u32, u32, u32), - ) -> Result { + ) -> BufferView { let bounds = self.get_bounds(); - if subdimensions.0 + subdimensions.2 > bounds.2 - || subdimensions.1 + subdimensions.3 > bounds.3 + if cfg!(debug_assertions) && (subdimensions.0 + subdimensions.2 > bounds.2 + || subdimensions.1 + subdimensions.3 > bounds.3) { - return Err(::std::io::Error::new( - ::std::io::ErrorKind::Other, - format!( - "cannot create subdimensions larger than buffer: {:?} > {:?}", - subdimensions, bounds - ), - )); + panic!("cannot create subdimensions larger than buffer: {:?} > {:?}", subdimensions, bounds); } - Ok(BufferView { - buf: self.buf, + BufferView { + view: self.view, dimensions: self.dimensions, subdimensions: Some(( subdimensions.0 + bounds.0, @@ -218,27 +219,21 @@ impl<'a> BufferView<'a> { subdimensions.2, subdimensions.3, )), - }) + } } - pub fn subgeometry(&mut self, geo: Geometry) -> Result { + pub fn subgeometry(&mut self, geo: Geometry) -> BufferView { self.subdimensions((geo.x, geo.y, geo.width, geo.height)) } - pub fn offset(&mut self, offset: (u32, u32)) -> Result { + pub fn offset(&mut self, offset: (u32, u32)) -> BufferView { let bounds = self.get_bounds(); - if offset.0 > bounds.2 || offset.1 > bounds.3 { - return Err(::std::io::Error::new( - ::std::io::ErrorKind::Other, - format!( - "cannot create offset outside buffer: {:?} > {:?}", - offset, bounds - ), - )); + if cfg!(debug_assertions) && (offset.0 > bounds.2 || offset.1 > bounds.3) { + panic!("cannot create offset outside buffer: {:?} > {:?}", offset, bounds); } - Ok(BufferView { - buf: self.buf, + BufferView { + view: self.view, dimensions: self.dimensions, subdimensions: Some(( offset.0 + bounds.0, @@ -246,82 +241,47 @@ impl<'a> BufferView<'a> { bounds.2 - offset.0, bounds.3 - offset.1, )), - }) + } } - pub fn limit(&mut self, dimensions: (u32, u32)) -> Result { + pub fn limit(&mut self, dimensions: (u32, u32)) -> BufferView { let bounds = self.get_bounds(); - if dimensions.0 > bounds.2 || dimensions.1 > bounds.3 { - return Err(::std::io::Error::new( - ::std::io::ErrorKind::Other, - format!( - "cannot create offset outside buffer: {:?} > {:?}", - dimensions, bounds - ), - )); + if cfg!(debug_assertions) && (dimensions.0 > bounds.2 || dimensions.1 > bounds.3) { + panic!("cannot create offset outside buffer: {:?} > {:?}", dimensions, bounds); } - Ok(BufferView { - buf: self.buf, + BufferView { + view: self.view, dimensions: self.dimensions, subdimensions: Some((bounds.0, bounds.1, dimensions.0, dimensions.1)), - }) + } } - pub fn memset(&mut self, c: &Color) { - let v = c.as_argb8888(); + pub fn memset(&mut self, c: Color) { if let Some(subdim) = self.subdimensions { - unsafe { - let ptr = self.buf.as_mut_ptr(); - for y in subdim.1..(subdim.1 + subdim.3) { - for x in subdim.0..(subdim.0 + subdim.2) { - *((ptr as *mut u32).offset((x + y * self.dimensions.0) as isize)) = v; - } + for y in subdim.1..(subdim.1 + subdim.3) { + for x in subdim.0..(subdim.0 + subdim.2) { + self.view[(x + y * self.dimensions.0) as usize] = c.0; } } } else { - unsafe { - let ptr = self.buf.as_mut_ptr(); - for p in 0..(self.dimensions.0 * self.dimensions.1) { - *((ptr as *mut u32).offset(p as isize)) = v; - } - } + self.view.fill(c.0); } } - pub fn put(&mut self, pos: (u32, u32), c: &Color) -> Result<(), ::std::io::Error> { + pub fn put(&mut self, pos: (u32, u32), c: Color) { let true_pos = if let Some(subdim) = self.subdimensions { - if pos.0 >= subdim.2 || pos.1 >= subdim.3 { - return Err(::std::io::Error::new( - ::std::io::ErrorKind::Other, - format!( - "put({:?}) is not within subdimensions of buffer ({:?})", - pos, subdim - ), - )); + if cfg!(debug_assertions) && (pos.0 >= subdim.2 || pos.1 >= subdim.3) { + panic!("put({:?}) is not within subdimensions of buffer ({:?})", pos, subdim); } (pos.0 + subdim.0, pos.1 + subdim.1) } else { - if pos.0 >= self.dimensions.0 || pos.1 >= self.dimensions.1 { - return Err(::std::io::Error::new( - ::std::io::ErrorKind::Other, - format!( - "put({:?}) is not within dimensions of buffer ({:?})", - pos, self.dimensions - ), - )); + if cfg!(debug_assertions) && (pos.0 >= self.dimensions.0 || pos.1 >= self.dimensions.1) { + panic!("put({:?}) is not within dimensions of buffer ({:?})", pos, self.dimensions); } pos }; - unsafe { - let ptr = self - .buf - .as_mut_ptr() - .offset(4 * (true_pos.0 + (true_pos.1 * self.dimensions.0)) as isize); - *(ptr as *mut u32) = c.as_argb8888(); - }; - - Ok(()) + self.view[(true_pos.0 + (true_pos.1 * self.dimensions.0)) as usize] = c.0; } } diff --git a/src/color.rs b/src/color.rs index 67675b2..f911120 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,5 +1,5 @@ //use serde::{Deserialize, Serialize}; - +/* //#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)] pub struct Color { red: f32, @@ -37,6 +37,11 @@ impl Color { | ((255.0 * self.green) as u32 & 0xFF) << 8 | ((255.0 * self.blue) as u32 & 0xFF) } + + #[inline] + pub fn as_integer(&self) -> IntegerColor { + IntegerColor(self.as_argb8888()) + } } #[inline] @@ -69,3 +74,47 @@ fn clamp_f32(v: f32, a: f32, b: f32) -> f32 { fn blend_f32(a: f32, b: f32, r: f32) -> f32 { a + ((b - a) * r) } +*/ +#[inline] +fn blend_u8(a: u8, b: u8, r: f32) -> u32 { + let af = a as f32; + let bf = b as f32; + (af + ((bf - af) * r)) as u32 +} + +#[derive(Copy, Clone)] +pub struct Color(pub u32); + +impl Color { + pub const BLACK: Color = Color(0xFF000000); + pub const WHITE: Color = Color(0xFFFFFFFF); + pub const GREY50: Color = Color(0xFF7F7F7F); + pub const GREY75: Color = Color(0xFFBFBFBF); + pub const GREY80: Color = Color(0xFFCCCCCC); + pub const RED: Color = Color(0xFFFF0000); + pub const YELLOW: Color = Color(0xFFFFFF00); + pub const LIGHTGREEN: Color = Color(0xFF7FFF7F); + pub const LIGHTRED: Color = Color(0xFFFF7F7F); + pub const LIGHTORANGE: Color = Color(0xFFFFBF00); + pub const DARKORANGE: Color = Color(0xFFFF7F00); + pub const BUFF: Color = Color(0xFFFFBF7F); + + #[allow(unused)] + pub fn new(red: u8, green: u8, blue: u8, opacity: u8) -> Color { + Color((opacity as u32) << 24 | (red as u32) << 16 | (green as u32) << 8 | (blue as u32)) + } + + fn opacity(self) -> u8 {((self.0 >> 24) & 0xFF) as u8 } + fn red(self) -> u8 {((self.0 >> 16) & 0xFF) as u8 } + fn green(self) -> u8 {((self.0 >> 8) & 0xFF) as u8 } + fn blue(self) -> u8 {(self.0 & 0xFF) as u8 } + + pub fn blend(self, other: Color, ratio: f32) -> Color { + Color( + blend_u8(self.opacity(), other.opacity(), ratio) << 24 | + blend_u8(self.red(), other.red(), ratio) << 16 | + blend_u8(self.green(), other.green(), ratio) << 8 | + blend_u8(self.blue(), other.blue(), ratio) + ) + } +} diff --git a/src/draw.rs b/src/draw.rs index 747dc6c..3bafd4a 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -47,7 +47,7 @@ impl CachedGlyph { } } - fn draw(&self, buf: &mut BufferView, pos: (i32, i32), bg: &Color, c: &Color) { + fn draw(&self, buf: &mut BufferView, pos: (i32, i32), bg: Color, c: Color) { let mut x = 0; let mut y = 0; for v in &self.render { @@ -56,7 +56,7 @@ impl CachedGlyph { (x + pos.0 + self.origin.0) as u32, (y + pos.1 + self.origin.1) as u32, ), - &bg.blend(&c, *v), + bg.blend(c, *v), ); if x == self.dimensions.0 as i32 - 1 { @@ -84,6 +84,12 @@ impl<'a> Font<'a> { } } + pub fn height(&self) -> f32 { + let scale = Scale::uniform(self.size); + let m = self.font.v_metrics(scale); + m.ascent - m.descent + } + pub fn add_str_to_cache(&mut self, s: &str) { for ch in s.chars() { if self.glyphs.get(&ch).is_none() { @@ -96,8 +102,8 @@ impl<'a> Font<'a> { pub fn draw_text( &self, buf: &mut BufferView, - bg: &Color, - c: &Color, + bg: Color, + c: Color, s: &str, ) -> Result<(u32, u32), ::std::io::Error> { let mut x_off = 0; @@ -129,8 +135,8 @@ impl<'a> Font<'a> { pub fn draw_text_with_cursor( &self, buf: &mut BufferView, - bg: &Color, - c: &Color, + bg: Color, + c: Color, s: &str, cursor: usize, ) -> Result<(u32, u32), ::std::io::Error> { @@ -171,13 +177,13 @@ impl<'a> Font<'a> { pub fn draw_cursor( &self, buf: &mut BufferView, - c: &Color, + c: Color, offset: u32, height: u32, ) -> Result<(), ::std::io::Error> { // draw cursor for i in 1..height { - buf.put((offset, i), c)? + buf.put((offset, i), c); } Ok(()) } @@ -206,8 +212,8 @@ impl<'a> Font<'a> { pub fn auto_draw_text( &mut self, buf: &mut BufferView, - bg: &Color, - c: &Color, + bg: Color, + c: Color, s: &str, ) -> Result<(u32, u32), ::std::io::Error> { self.add_str_to_cache(s); @@ -217,8 +223,8 @@ impl<'a> Font<'a> { pub fn auto_draw_text_with_cursor( &mut self, buf: &mut BufferView, - bg: &Color, - c: &Color, + bg: Color, + c: Color, s: &str, cursor: usize, ) -> Result<(u32, u32), ::std::io::Error> { @@ -229,8 +235,8 @@ impl<'a> Font<'a> { pub fn draw_text_fixed_width( &self, buf: &mut BufferView, - bg: &Color, - c: &Color, + bg: Color, + c: Color, distances: &[u32], s: &str, ) -> Result<(u32, u32), ::std::io::Error> { @@ -263,7 +269,7 @@ impl<'a> Font<'a> { pub fn draw_text_individual_colors( &self, buf: &mut BufferView, - bg: &Color, + bg: Color, color: &[Color], s: &str, ) -> Result<(u32, u32), ::std::io::Error> { @@ -286,7 +292,7 @@ impl<'a> Font<'a> { } } for (idx, glyph) in glyphs.into_iter().enumerate() { - glyph.draw(buf, (x_off, -off), bg, &color[idx]); + glyph.draw(buf, (x_off, -off), bg, color[idx]); x_off += glyph.advance; } @@ -296,7 +302,7 @@ impl<'a> Font<'a> { pub fn auto_draw_text_individual_colors( &mut self, buf: &mut BufferView, - bg: &Color, + bg: Color, color: &[Color], s: &str, ) -> Result<(u32, u32), ::std::io::Error> { @@ -305,14 +311,14 @@ impl<'a> Font<'a> { } } -pub fn draw_box(buf: &mut BufferView, c: &Color, dim: (u32, u32)) -> Result<(), ::std::io::Error> { +pub fn draw_box(buf: &mut BufferView, c: Color, dim: (u32, u32)) -> Result<(), ::std::io::Error> { for x in 0..dim.0 { - let _ = buf.put((x, 0), c); - let _ = buf.put((x, dim.1 - 1), c); + buf.put((x, 0), c); + buf.put((x, dim.1 - 1), c); } for y in 0..dim.1 { - buf.put((0, y), c)?; - buf.put((dim.0 - 1, y), c)?; + buf.put((0, y), c); + buf.put((dim.0 - 1, y), c); } Ok(()) @@ -320,7 +326,7 @@ pub fn draw_box(buf: &mut BufferView, c: &Color, dim: (u32, u32)) -> Result<(), pub fn draw_bar( buf: &mut BufferView, - color: &Color, + color: Color, length: u32, height: u32, fill: f32, diff --git a/src/event.rs b/src/event.rs new file mode 100644 index 0000000..eb2bab0 --- /dev/null +++ b/src/event.rs @@ -0,0 +1,61 @@ +use std::sync::{Arc, Mutex}; + +use calloop::ping::Ping; + +use crate::keyboard::KeyEvent; + +#[derive(Copy, Clone, Debug)] +pub enum PointerButton { + Left, + Right, + Middle, + ScrollVertical(f64), + ScrollHorizontal(f64), +} + +#[derive(Debug)] +pub struct PointerEvent { + pub button: PointerButton, + pub pos: (u32, u32), +} + +#[derive(Debug)] +pub enum Event { + NewMinute, + PowerUpdate, + LauncherUpdate, + AudioUpdate, + KeyEvent(KeyEvent), + PointerEvent(PointerEvent), + TokenUpdate(String), +} + +pub struct Events { + dirty: bool, + events: Vec, + ping: Ping, +} + +impl Events { + pub fn new(ping: Ping) -> Arc> { + Arc::new(Mutex::new(Events { + dirty: false, + events: Vec::new(), + ping, + })) + } + + pub fn add_event(&mut self, ev: Event) { + self.events.push(ev); + if !self.dirty { + self.dirty = true; + self.ping.ping(); + } + } + + pub fn flush(&mut self) -> Vec { + self.dirty = false; + self.events.drain(..).collect() + } +} + diff --git a/src/fonts.rs b/src/fonts.rs index da6c431..29c13a2 100644 --- a/src/fonts.rs +++ b/src/fonts.rs @@ -1,7 +1,6 @@ //! Utility module for fonts use crate::draw; -//use fontconfig::Fontconfig as FontConfig; use rusttype::Font; use std::{ cell::RefCell, @@ -10,7 +9,7 @@ use std::{ hash, io::Read, mem, - path::{Path, PathBuf}, + path::Path, rc::Rc, thread, }; @@ -18,19 +17,24 @@ use std::{ /// FontRef is used to store Fonts on widgets. pub type FontRef<'a> = &'a rusttype::Font<'a>; -/* -/// FontSeeker is a marker struct that is used to look up fonts -pub(crate) struct FontSeeker; - -impl FontSeeker { +#[cfg(feature = "fontloading")] +mod fc { + use fontconfig::Fontconfig as FontConfig; /// Acts like fc-match. /// Given a string, it matches it to a font file and returns its path. - pub(crate) fn from_string(name: &str) -> PathBuf { + pub(crate) fn from_string(name: &str) -> String { let fc = FontConfig::new().unwrap(); - fc.find(name, None).unwrap().path + fc.find(name, None).unwrap().path.to_str().unwrap().to_string() } } -*/ + +#[cfg(not(feature = "fontloading"))] +mod fc { + pub(crate) fn from_string(_name: &str) -> String { + panic!("fontloading feature not enabled"); + } +} + /// FontLoader is a marker struct that is used to load files pub(crate) struct FontLoader; @@ -77,51 +81,53 @@ impl Eq for ComparableF32 {} pub struct FontMap { fonts: HashMap<(&'static str, ComparableF32), draw::Font<'static>>, - required_fonts: HashMap<&'static str, (&'static str, Vec)>, + font_paths: HashMap<&'static str, String>, + required_fonts: HashMap<&'static str, (&'static str, Vec<(f32, &'static str)>)>, } impl FontMap { pub fn new() -> FontMap { FontMap { fonts: HashMap::new(), + font_paths: HashMap::new(), required_fonts: HashMap::new(), } } - pub fn queue_font_name(&mut self, font_name: &'static str, size: f32) { + pub fn queue_font(&mut self, font_name: &'static str, size: f32, preload: &'static str) { match self.required_fonts.get_mut(font_name) { - Some(v) => v.1.push(size), + Some(v) => v.1.push((size, preload)), None => { - self.required_fonts - .insert(font_name, (font_name, vec![size])); + self.required_fonts.insert(font_name, (font_name, vec![(size, preload)])); } } } - pub fn queue_font_path(&mut self, font_name: &'static str, font_path: &'static str, size: f32) { - match self.required_fonts.get_mut(font_name) { - Some(v) => v.1.push(size), - None => { - self.required_fonts - .insert(font_name, (font_path, vec![size])); - } - } + pub fn add_font_path(&mut self, font_name: &'static str, font_path: String) { + self.font_paths.insert(font_name, font_path); } pub fn load_fonts(&mut self) { for (font_name, v) in self.required_fonts.iter() { - let path = if v.0.starts_with("/") { - std::path::PathBuf::from(v.0) - } else { - //FontSeeker::from_string(v.0) - panic!("required FontSeeker"); + let path = match self.font_paths.get(font_name) { + Some(res) => res, + _ => { + let s = fc::from_string(font_name); + self.font_paths.insert(font_name, s); + self.font_paths.get(font_name).unwrap() + } }; let fontref = Box::leak(Box::new( FontLoader::from_path(path).expect("unable to load font"), )); - for size in &v.1 { - let font = draw::Font::new(fontref, *size); - self.fonts.insert((font_name, ComparableF32(*size)), font); + for (size, preload) in &v.1 { + if let Some(font) = self.fonts.get_mut(&(font_name, ComparableF32(*size))) { + font.add_str_to_cache(preload); + } else { + let mut font = draw::Font::new(fontref, *size); + font.add_str_to_cache(preload); + self.fonts.insert((font_name, ComparableF32(*size)), font); + } } } } diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs index 6b40547..3786d8a 100644 --- a/src/keyboard/mod.rs +++ b/src/keyboard/mod.rs @@ -12,8 +12,10 @@ //! calloop event loop. Not doing so will prevent key repetition to work //! (but the rest of the functionnality will not be affected). -use std::default::Default; -use std::os::unix::io::{AsRawFd, OwnedFd}; +use std::{ + default::Default, + os::unix::io::{AsRawFd, OwnedFd}, +}; use wayland_client::{protocol::wl_keyboard, WEnum}; use xkbcommon::xkb; @@ -24,6 +26,7 @@ mod state; use self::state::KbState; pub use self::state::ModifiersState; +#[derive(Debug)] pub struct KeyEvent { pub serial: u32, pub time: u32, diff --git a/src/keyboard/state.rs b/src/keyboard/state.rs index ba5ed64..300a5f6 100644 --- a/src/keyboard/state.rs +++ b/src/keyboard/state.rs @@ -50,6 +50,9 @@ impl ModifiersState { } } +// Safety: No. +unsafe impl Send for KbState {} + impl KbState { pub(crate) fn update_modifiers( &mut self, diff --git a/src/main.rs b/src/main.rs index a101746..09a8851 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ mod keyboard; mod state; mod utils; mod widgets; +mod event; use wayland_client::{Connection, QueueHandle, WaylandSource}; @@ -18,44 +19,17 @@ use chrono::{Duration, Local, Timelike}; use buffer::BufferView; use color::Color; use fonts::{FontMap, MaybeFontMap}; -use state::{Event, Events, State}; +use state::State; +use event::{Event, Events}; use widgets::{ - Backlight, Battery, Calendar, Clock, Date, Geometry, HorizontalLayout, IndexedLayout, + Audio, Backlight, Battery, Calendar, Clock, Date, Geometry, HorizontalLayout, IndexedLayout, Interface, InvertedHorizontalLayout, Line, Margin, VerticalLayout, Widget, }; use std::{rc::Rc, thread}; fn main() { - let font_thread = thread::spawn(move || { - let mut fm = FontMap::new(); - // Hard-coding font paths make things a lot faster - fm.queue_font_path("sans", "/usr/share/fonts/noto/NotoSans-Regular.ttf", 128.); - fm.queue_font_path("sans", "/usr/share/fonts/noto/NotoSans-Regular.ttf", 48.); - fm.queue_font_path("sans", "/usr/share/fonts/noto/NotoSans-Regular.ttf", 24.); - fm.queue_font_path( - "monospace", - "/usr/share/fonts/noto/NotoSansMono-Regular.ttf", - 32., - ); - fm.queue_font_path( - "monospace", - "/usr/share/fonts/noto/NotoSansMono-Regular.ttf", - 12., - ); - fm.queue_font_path( - "monospace", - "/usr/share/fonts/noto/NotoSansMono-Regular.ttf", - 12. * 1.5, - ); - fm.queue_font_path( - "monospace", - "/usr/share/fonts/noto/NotoSansMono-Regular.ttf", - 12. * 2.0, - ); - fm.load_fonts(); - fm - }); +// let now = Local::now(); let conn = Connection::connect_to_env().unwrap(); let event_queue = conn.new_event_queue(); @@ -66,32 +40,6 @@ fn main() { let (ping_sender, ping_source) = calloop::ping::make_ping().unwrap(); - let events = Events::new(ping_sender); - - let clock = Box::new(Clock::new("sans", 128.)); -// let calendar = Box::new(Calendar::new("monospace", 12.0, 1, -1)); - let date = Box::new(Date::new("sans", 48.)); - let line = Box::new(Line::new()); - let launcher = Box::new(Interface::new("monospace", 32.)); - let battery = Box::new(Battery::new(events.clone(), "sans", 24.)); - let backlight = Box::new(Backlight::new("intel_backlight", "sans", 24.)); - - let widgets: Vec> = - vec![clock, date, battery, backlight, line, launcher]; - let layout = Rc::new(VerticalLayout::new(vec![ - HorizontalLayout::new(vec![ - IndexedLayout::new(0), - Margin::new(IndexedLayout::new(1), (16, 8, 0, 0)), - VerticalLayout::new(vec![ - Margin::new(IndexedLayout::new(2), (16, 8, 8, 0)), - Margin::new(IndexedLayout::new(3), (16, 8, 8, 0)), - ]), - ]), - IndexedLayout::new(4), - IndexedLayout::new(5), -// InvertedHorizontalLayout::new(vec![IndexedLayout::new(5), IndexedLayout::new(6)]), - ])); - let mut event_loop: EventLoop = EventLoop::try_new().expect("Failed to initialize the event loop!"); @@ -136,6 +84,79 @@ fn main() { }) .expect("Failed to insert event source!"); + let events = Events::new(ping_sender); + + let mut fm = FontMap::new(); + + // Todo: Cache in a file + fm.add_font_path("sans", "/usr/share/fonts/noto/NotoSans-Regular.ttf".to_string()); + fm.add_font_path("monospace", "/usr/share/fonts/noto/NotoSansMono-Regular.ttf".to_string()); + + let clock = Box::new(Clock::new(&mut fm, "sans", 128.)); + let calendar = Box::new(Calendar::new(&mut fm, "monospace", 12.0, 1, -1)); + let date = Box::new(Date::new(&mut fm, "sans", 48.)); + let line = Box::new(Line::new(1)); + let launcher = Box::new(Interface::new(events.clone(), &mut fm, "monospace", 32.)); + let battery = Box::new(Battery::new(events.clone(), &mut fm, "sans", 24.)); + let backlight = Box::new(Backlight::new("intel_backlight", &mut fm, "sans", 24.)); + let audio = Box::new(Audio::new(events.clone(), &mut fm, "sans", 24.)); + + let widgets: Vec> = + vec![clock, date, battery, backlight, audio, line, calendar, launcher]; + + let layout = Rc::new(VerticalLayout::new(vec![ + HorizontalLayout::new(vec![ + IndexedLayout::new(0), + Margin::new(IndexedLayout::new(1), (16, 16, 0, 0)), + VerticalLayout::new(vec![ + Margin::new(IndexedLayout::new(2), (16, 8, 8, 0)), + Margin::new(IndexedLayout::new(3), (16, 8, 8, 0)), + Margin::new(IndexedLayout::new(4), (16, 8, 8, 0)), + ]), + ]), + IndexedLayout::new(5), + InvertedHorizontalLayout::new(vec![IndexedLayout::new(6), IndexedLayout::new(7)]), + ])); + + /* alt look + let clock = Box::new(Clock::new(&mut fm, "sans", 256.)); + let date = Box::new(Date::new(&mut fm, "sans", 64.)); + let calendar = Box::new(Calendar::new(&mut fm, "monospace", 16.0, 3, 1)); + let launcher = Box::new(Interface::new(events.clone(), &mut fm, "monospace", 32.)); + let battery = Box::new(Battery::new(events.clone(), &mut fm, "sans", 24.)); + let backlight = Box::new(Backlight::new("intel_backlight", &mut fm, "sans", 24.)); + + let widgets: Vec> = + vec![clock, date, battery, backlight, calendar]; + + let layout = Rc::new(Margin::new( + VerticalLayout::new(vec![ + HorizontalLayout::new(vec![ + VerticalLayout::new(vec![ + IndexedLayout::new(1), + IndexedLayout::new(0), + ]), + Margin::new( + VerticalLayout::new(vec![ + Margin::new(IndexedLayout::new(2), (0, 0, 0, 8)), + Margin::new(IndexedLayout::new(3), (0, 0, 0, 8)), + ]), + (88, 0, 0, 0), + ), + ]), + IndexedLayout::new(4), + //IndexedLayout::new(5), + ]), (20, 20, 20, 20)) + ); + */ + + let font_thread = thread::Builder::new() + .name("fontloader".to_string()) + .spawn(move || { + fm.load_fonts(); + fm + }).unwrap(); + let mut state = State::new(widgets, layout, MaybeFontMap::Waiting(font_thread), events); let mut damage = Vec::new(); @@ -174,16 +195,16 @@ fn main() { (state.dimensions.0 as u32, state.dimensions.1 as u32), ); - let bg = Color::new(0., 0., 0., 1.); +// eprintln!("time: {}", (Local::now() - now).num_milliseconds()); let surface = state.base_surface.as_ref().unwrap().clone(); if force { + // Other compositors probably need this. + //bufview.memset(Color::BLACK); for (idx, widget) in state.widgets.iter_mut().enumerate() { if force || widget.get_dirty() { let geo = widget.geometry(); - bufview.subgeometry(geo).unwrap().memset(&bg); - - let mut subview = bufview.subgeometry(geo).unwrap(); + let mut subview = bufview.subgeometry(geo); damage[idx] = widget.draw(&mut state.fonts.unwrap().borrow_mut(), &mut subview); //draw_box(&mut subview, &Color::new(1.0, 0.5, 0.0, 1.0), (geo.width, geo.height)); } @@ -196,10 +217,10 @@ fn main() { drew = true; let old_damage = damage[idx]; - bufview.subgeometry(old_damage).unwrap().memset(&bg); + bufview.subgeometry(old_damage).memset(Color::BLACK); let geo = widget.geometry(); - let mut subview = bufview.subgeometry(geo).unwrap(); + let mut subview = bufview.subgeometry(geo); let new_damage = widget.draw(&mut state.fonts.unwrap().borrow_mut(), &mut subview); @@ -223,5 +244,7 @@ fn main() { surface.attach(Some(&buf.buffer), 0, 0); surface.commit(); +// eprintln!("draw time: {}", (Local::now() - now).num_milliseconds()); +// std::process::exit(0); } } diff --git a/src/state.rs b/src/state.rs index 506e29f..b6c1eb2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -5,12 +5,10 @@ use std::{ sync::{Arc, Mutex}, }; -use calloop::ping::Ping; - use wayland_client::{ protocol::{ wl_buffer, wl_compositor, wl_keyboard, wl_registry, wl_seat, wl_shm, wl_shm_pool, - wl_surface, + wl_surface, wl_pointer, }, Connection, Dispatch, QueueHandle, WEnum, }; @@ -23,46 +21,11 @@ use wayland_protocols::xdg::{ use crate::{ buffer::BufferManager, fonts::{FontMap, MaybeFontMap}, - keyboard::{KeyEvent, Keyboard}, + keyboard::Keyboard, widgets::{Geometry, Layout, Widget, WidgetUpdater}, + event::{Event, Events, PointerEvent, PointerButton}, }; -pub enum Event { - NewMinute, - PowerUpdate, - KeyEvent(KeyEvent), - TokenUpdate(String), -} - -pub struct Events { - dirty: bool, - events: Vec, - ping: Ping, -} - -impl Events { - pub fn new(ping: Ping) -> Arc> { - Arc::new(Mutex::new(Events { - dirty: false, - events: Vec::new(), - ping, - })) - } - - pub fn add_event(&mut self, ev: Event) { - self.events.push(ev); - if !self.dirty { - self.dirty = true; - self.ping.ping(); - } - } - - pub fn flush(&mut self) -> Vec { - self.dirty = false; - self.events.drain(..).collect() - } -} - pub struct State { pub running: bool, pub dirty: bool, @@ -77,6 +40,7 @@ pub struct State { pub bufmgr: BufferManager, pub widgets: Vec>, keyboard: Keyboard, + pointer: Option<(f64, f64)>, pub fonts: MaybeFontMap, pub events: Arc>, layout: Rc>, @@ -102,6 +66,7 @@ impl State { dimensions: (320, 240), bufmgr: BufferManager::new(), keyboard: Keyboard::new(), + pointer: None, widgets, fonts, events, @@ -380,6 +345,9 @@ impl Dispatch for State { if capabilities.contains(wl_seat::Capability::Keyboard) { seat.get_keyboard(qh, ()); } + if capabilities.contains(wl_seat::Capability::Pointer) { + seat.get_pointer(qh, ()); + } } } } @@ -431,6 +399,84 @@ impl Dispatch for State { } } +const BTN_LEFT: u32 = 0x110; +const BTN_RIGHT: u32 = 0x111; +const BTN_MIDDLE: u32 = 0x112; + +impl Dispatch for State { + fn event( + state: &mut Self, + _: &wl_pointer::WlPointer, + event: wl_pointer::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + match event { + wl_pointer::Event::Enter { surface_x, surface_y, .. } => { + state.pointer = Some((surface_x, surface_y)); + }, + wl_pointer::Event::Motion { surface_x, surface_y, .. } => { + state.pointer = Some((surface_x, surface_y)); + }, + wl_pointer::Event::Leave { .. } => { + state.pointer = None + }, + wl_pointer::Event::Axis { axis, value, .. } => match state.pointer { + Some((x, y)) => { + let axis = match axis { + WEnum::Value(wl_pointer::Axis::VerticalScroll) => PointerButton::ScrollVertical(value), + WEnum::Value(wl_pointer::Axis::HorizontalScroll) => PointerButton::ScrollHorizontal(value), + _ => return, + }; + let pos = (x as u32, y as u32); + for widget in state.widgets.iter_mut() { + let geo = widget.geometry(); + if geo.contains(pos) { + let pos = (pos.0 - geo.x, pos.1 - geo.y); + let ev = Event::PointerEvent(PointerEvent{ + button: axis, + pos: pos, + }); + widget.event(&ev); + state.dirty = true; + break; + } + } + }, + _ => (), + }, + wl_pointer::Event::Button { button, state: button_state, .. } => match (button, button_state, state.pointer) { + (button, WEnum::Value(wl_pointer::ButtonState::Pressed), Some((x, y))) => { + let button = match button { + BTN_LEFT => PointerButton::Left, + BTN_RIGHT => PointerButton::Right, + BTN_MIDDLE => PointerButton::Middle, + _ => return, + }; + let pos = (x as u32, y as u32); + for widget in state.widgets.iter_mut() { + let geo = widget.geometry(); + if geo.contains(pos) { + + let pos = (pos.0 - geo.x, pos.1 - geo.y); + let ev = Event::PointerEvent(PointerEvent{ + button: button, + pos: pos, + }); + widget.event(&ev); + state.dirty = true; + break; + } + } + }, + _ => (), + }, + _ => (), + } + } +} + impl Dispatch for State { fn event( _: &mut Self, diff --git a/src/utils/desktop.rs b/src/utils/desktop.rs index 8457685..9b14706 100644 --- a/src/utils/desktop.rs +++ b/src/utils/desktop.rs @@ -1,12 +1,16 @@ -use ini::{Ini, ParseOption}; -use itertools::Itertools; -use std::cmp::Ordering; -use std::env; -use std::error::Error; -use std::io::Error as io_error; -use std::io::ErrorKind; +use std::{ + cmp::Ordering, + collections::HashSet, + env, + error::Error, + fs::read_to_string, + io::{Error as io_error, ErrorKind}, +}; + use walkdir::WalkDir; +use crate::utils::inish; + #[derive(Clone, Debug, Eq, Hash)] pub struct Desktop { pub entry_type: String, @@ -21,23 +25,18 @@ pub struct Desktop { impl Desktop { fn parse(f: &str) -> Result> { - let file = Ini::load_from_file_opt( - f, - ParseOption { - enabled_quote: false, - enabled_escape: false, - }, - )?; - match file.section(Some("Desktop Entry")) { - Some(desktop) => Ok(Desktop { - entry_type: desktop.get("Type").unwrap_or(&"".to_string()).to_string(), - name: desktop.get("Name").unwrap_or(&"".to_string()).to_string(), - term: desktop.get("Terminal").unwrap_or(&"".to_string()) == "true", - no_display: desktop.get("NoDisplay").unwrap_or(&"".to_string()) == "true", - hidden: desktop.get("Hidden").unwrap_or(&"".to_string()) == "true", - exec: desktop.get("Exec").map(|x| x.to_string()), - url: desktop.get("URL").map(|x| x.to_string()), - keywords: desktop + let s = read_to_string(f)?; + let config = inish::parse(&s)?; + match config.get("Desktop Entry") { + Some(section) => Ok(Desktop { + entry_type: section.get("Type").unwrap_or(&"").to_string(), + name: section.get("Name").unwrap_or(&"").to_string(), + term: section.get("Terminal").unwrap_or(&"") == &"true", + no_display: section.get("NoDisplay").unwrap_or(&"") == &"true", + hidden: section.get("Hidden").unwrap_or(&"") == &"true", + exec: section.get("Exec").map(|x| x.to_string()), + url: section.get("URL").map(|x| x.to_string()), + keywords: section .get("Keywords") .map(|x| { x.split(';') @@ -53,20 +52,6 @@ impl Desktop { ))), } } - - fn parse_dir(d: &str) -> Result, Box> { - let mut files: Vec = Vec::with_capacity(16); - for entry in WalkDir::new(d) { - let entry = entry?; - let path = entry.path(); - - if let Ok(d) = Desktop::parse(path.to_str().unwrap()) { - files.push(d) - } - } - - Ok(files) - } } impl Ord for Desktop { @@ -99,14 +84,29 @@ pub fn load_desktop_files() -> Vec { None => "/usr/local/share:/usr/share".to_string(), }; - std::iter::once(xdg_data_home.as_str()) - .chain(xdg_data_dirs.split(':')) - .map(|p| Desktop::parse_dir(&format!("{}/applications", p))) - .filter_map(Result::ok) - .flatten() - .filter(|d| { - !d.hidden && !d.no_display && (d.entry_type == "Application" || d.entry_type == "Link") - }) - .unique_by(|x| x.name.clone()) - .collect() + let dirs = std::iter::once(xdg_data_home.as_str()).chain(xdg_data_dirs.split(':')); + + let mut desktop: Vec = Vec::new(); + let mut seen: HashSet = HashSet::new(); + + for dir in dirs { + for entry in WalkDir::new(format!("{}/applications", dir)) { + if let Ok(entry) = entry { + if let Ok(d) = Desktop::parse(entry.path().to_str().unwrap()) { + if d.hidden + || d.no_display + || (d.entry_type != "Application" && d.entry_type != "Link") + { + continue; + } + if !seen.contains(&d.name) { + seen.insert(d.name.to_string()); + desktop.push(d); + } + } + } + } + } + + desktop } diff --git a/src/utils/inish.rs b/src/utils/inish.rs new file mode 100644 index 0000000..13bb056 --- /dev/null +++ b/src/utils/inish.rs @@ -0,0 +1,134 @@ +use std::{collections::HashMap, error::Error}; + +fn parse_field<'a>(line: &'a str) -> Result<(&'a str, &'a str), Box> { + let split = match line.find('=') { + Some(v) => v, + None => return Err("expected equals sign on line, but found none".into()), + }; + + let (key, value) = line.split_at(split); + let (key, value) = (key.trim(), value.trim_matches('=').trim()); + + Ok((key, value)) +} + +type InishSection<'a> = HashMap<&'a str, &'a str>; +type Inish<'a> = HashMap<&'a str, InishSection<'a>>; + +pub fn parse<'a>(s: &'a str) -> Result, Box> { + let mut sections: Inish<'a> = HashMap::new(); + let mut current_section = HashMap::new(); + let mut current_section_name = ""; + + for line in s.lines() { + let line = line.trim(); + let mut chars = line.chars(); + let start = chars.next(); + let end = chars.last(); + match (start, end) { + (Some('#'), _) => continue, + (Some('['), Some(']')) => { + sections.insert(current_section_name, current_section); + current_section = HashMap::new(); + let len = line.bytes().count(); + current_section_name = &line[1..len - 1].trim(); + } + (Some('['), v) => { + return Err(format!( + "expected Some(']') to terminate section name, but got {:?}", + v + ) + .into()); + } + _ if line.is_empty() => continue, + _ => { + let (key, value) = parse_field(line)?; + current_section.insert(key, value); + } + } + } + + sections.insert(current_section_name, current_section); + Ok(sections) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sections() { + let config = parse( + " +[section_a] +[section_b] +", + ) + .expect("config didn't parse"); + + if !config.contains_key("section_a") { + panic!("named section did not exist"); + } + + if !config.contains_key("section_b") { + panic!("named section did not exist"); + } + + if !config.contains_key("") { + panic!("unnamed section did not exist"); + } + } + + #[test] + fn fields() { + let config = parse( + " +field_outside = 'hello' +[section_a] +field_a = 1234 +", + ) + .expect("config didn't parse"); + + let section_a = match config.get("section_a") { + Some(v) => v, + None => panic!("named section did not exist"), + }; + + assert_eq!(section_a.get("field_a"), Some(&"1234")); + + let section_unnamed = match config.get("") { + Some(v) => v, + None => panic!("unnamed section did not exist"), + }; + + assert_eq!(section_unnamed.get("field_outside"), Some(&"'hello'")); + } + + #[test] + fn whitespaces() { + let config = parse( + " + + field outside = 'hello' + +[ section a ] + field a = 1234 ", + ) + .expect("config didn't parse"); + + let section_a = match config.get("section a") { + Some(v) => v, + None => panic!("named section did not exist"), + }; + + assert_eq!(section_a.get("field a"), Some(&"1234")); + + let section_unnamed = match config.get("") { + Some(v) => v, + None => panic!("unnamed section did not exist"), + }; + + assert_eq!(section_unnamed.get("field outside"), Some(&"'hello'")); + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 6cf24c7..6dbe5d9 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1 +1,2 @@ pub mod desktop; +mod inish; diff --git a/src/widgets/audio.rs b/src/widgets/audio.rs new file mode 100644 index 0000000..99c0f3c --- /dev/null +++ b/src/widgets/audio.rs @@ -0,0 +1,238 @@ + +use std::{ + sync::{Arc, Mutex}, + thread, + error::Error, + rc::Rc, + cell::RefCell, + os::fd::RawFd, +}; + +use nix::unistd::{ + pipe, write, read, +}; + +use crate::{ + color::Color, + event::{Event, Events, PointerButton}, + fonts::FontMap, + widgets::bar_widget::{BarWidget, BarWidgetImpl}, +}; + +use libpulse_binding::volume::ChannelVolumes; + +fn monitor(events: Arc>, inner: Arc>, pipe: RawFd) -> Result<(), Box> { + use libpulse_binding::{ + mainloop::{ + self, + standard::{ + IterateResult, + }, + api::Mainloop, + events::io::FlagSet as IoFlagSet, + }, + callbacks::ListResult, + context::{ + Context, + FlagSet, + subscribe::{InterestMaskSet, Facility}, + State, + }, + proplist::{properties, Proplist}, + volume::Volume, + }; + + let mut proplist = Proplist::new().unwrap(); + proplist + .set_str(properties::APPLICATION_NAME, "wldash") + .unwrap(); + + let mut mainloop = mainloop::standard::Mainloop::new().unwrap(); + let context = Rc::new(RefCell::new(Context::new_with_proplist(&mainloop, "wldash_context", &proplist).unwrap())); + context.borrow_mut().connect(None, FlagSet::NOFLAGS, None)?; + + loop { + mainloop.iterate(false); + match context.borrow().get_state() { + State::Ready => break, + State::Failed | State::Terminated => panic!("damn"), + _ => (), + } + } + + let local_context = context.clone(); + let local_inner = inner.clone(); + let local_events = events.clone(); + context + .borrow_mut() + .set_subscribe_callback(Some(Box::new(move |facility, _operation, index| { + match facility { + Some(Facility::Sink) => { + let introspector = local_context.borrow_mut().introspect(); + let inner = local_inner.clone(); + let events = local_events.clone(); + introspector.get_sink_info_by_index(index, move |res| { + match res { + ListResult::End | ListResult::Error => (), + ListResult::Item(sink_info) => if let Some(_) = &sink_info.name { + let avg = sink_info.volume.avg().0 as f32 / Volume::NORMAL.0 as f32; + let mut inner = inner.lock().unwrap(); + inner.volume = avg; + inner.mute = sink_info.mute; + inner.dirty = true; + drop(inner); + let mut events = events.lock().unwrap(); + events.add_event(Event::AudioUpdate); + }, + } + }); + }, + _ => (), + } + }))); + + context + .borrow_mut() + .subscribe(InterestMaskSet::SINK, |_| {}); + + let introspector = context.borrow_mut().introspect(); + let local_inner = inner.clone(); + introspector.get_sink_info_by_name("@DEFAULT_SINK@", move |res| { + match res { + ListResult::End | ListResult::Error => (), + ListResult::Item(sink_info) => if let Some(_) = &sink_info.name { + let avg = sink_info.volume.avg().0 as f32 / Volume::NORMAL.0 as f32; + let mut inner = local_inner.lock().unwrap(); + inner.volume = avg; + inner.channel_volume = Some(sink_info.volume); + inner.mute = sink_info.mute; + inner.dirty = true; + drop(inner); + let mut events = events.lock().unwrap(); + events.add_event(Event::AudioUpdate); + }, + } + }); + drop(introspector); + + let local_context = context.clone(); + let local_inner = inner.clone(); + let event_source = mainloop.new_io_event(pipe as i32, IoFlagSet::INPUT, Box::new(move |_, _, _| { + let mut buf = [0; 8]; + _ = read(pipe, &mut buf); + + let mut inner = local_inner.lock().unwrap(); + let mut introspector = local_context.borrow_mut().introspect(); + match inner.last_click.take() { + Some(Change::Volume(volume)) => { + // do something + let mut vol = inner.channel_volume.unwrap(); + vol.scale(Volume((volume * Volume::NORMAL.0 as f32).round() as u32)); + introspector.set_sink_volume_by_name("@DEFAULT_SINK@", &vol, None); + }, + Some(Change::VolumeInc(value)) => { + let mut vol = inner.channel_volume.unwrap(); + // apply step to volumes + let step = (value * Volume::NORMAL.0 as f32).round() as i32; + if step > 0 { + vol.inc_clamp(Volume(step as u32), Volume::NORMAL); + } else { + vol.decrease(Volume(-step as u32)); + } + // HACK: It would be better to only update the volume from feedback, but axis + // events fire fast and require the last set volume immediately for reference. + inner.channel_volume = Some(vol); + introspector.set_sink_volume_by_name("@DEFAULT_SINK@", &vol, None); + }, + Some(Change::ToggleMute) => { + introspector.set_sink_mute_by_name("@DEFAULT_SINK@", !inner.mute, None); + }, + None => (), + } + })); + + loop { + match mainloop.iterate(true) { + IterateResult::Quit(_) | IterateResult::Err(_) => break, + _ => (), + } + } + + // Need to keep the event source alive + _ = event_source; + + Ok(()) +} + +fn start_monitor(events: Arc>, inner: Arc>, pipe: RawFd) { + thread::Builder::new() + .name("audiomon".to_string()) + .spawn(move || { monitor(events, inner, pipe).unwrap() }) + .unwrap(); +} + +enum Change { + Volume(f32), + VolumeInc(f32), + ToggleMute, +} + +pub struct InnerAudio { + volume: f32, + channel_volume: Option, + mute: bool, + dirty: bool, + last_click: Option, + pipe: RawFd, +} + +pub struct Audio { + inner: Arc>, +} + +impl Audio { + pub fn new(events: Arc>, fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget { + let (a, b) = pipe().unwrap(); + let audio = Audio { + inner: Arc::new(Mutex::new(InnerAudio{ + volume: 0.0, + channel_volume: None, + mute: false, + dirty: false, + last_click: None, + pipe: b, + })), + }; + start_monitor(events, audio.inner.clone(), a); + BarWidget::new(Box::new(audio), fm, font, size) + } +} + +impl BarWidgetImpl for Audio { + fn get_dirty(&self) -> bool { + self.inner.lock().unwrap().dirty + } + fn name(&self) -> &'static str { + "volume" + } + fn value(&self) -> f32 { + self.inner.lock().unwrap().volume + } + fn color(&self) -> Color { + match self.inner.lock().unwrap().mute { + true => Color::YELLOW, + false => Color::WHITE, + } + } + fn click(&mut self, pos: f32, btn: PointerButton) { + let mut inner = self.inner.lock().unwrap(); + inner.last_click = match btn { + PointerButton::Left => Some(Change::Volume(pos)), + PointerButton::Right => Some(Change::ToggleMute), + PointerButton::ScrollVertical(val) => Some(Change::VolumeInc((val / 512.) as f32)), + PointerButton::ScrollHorizontal(val) => Some(Change::VolumeInc((val / 100.) as f32)), + _ => None, + }; + _ = write(inner.pipe, &[0]); + } +} diff --git a/src/widgets/backlight.rs b/src/widgets/backlight.rs index 1226ce8..947134d 100644 --- a/src/widgets/backlight.rs +++ b/src/widgets/backlight.rs @@ -6,6 +6,7 @@ use std::{ use crate::{ color::Color, + fonts::FontMap, widgets::bar_widget::{BarWidget, BarWidgetImpl}, }; @@ -28,7 +29,7 @@ impl Backlight { self.max = read_file_as_u64(self.device_path.join("max_brightness").as_path()).unwrap(); } - pub fn new(path: &str, font: &'static str, size: f32) -> BarWidget { + pub fn new(path: &str, fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget { let mut dev = Backlight { device_path: Path::new("/sys/class/backlight").to_path_buf().join(path), cur: 0, @@ -36,7 +37,7 @@ impl Backlight { }; dev.update(); - BarWidget::new(Box::new(dev), font, size) + BarWidget::new(Box::new(dev), fm, font, size) } } @@ -53,6 +54,6 @@ impl BarWidgetImpl for Backlight { self.cur as f32 / self.max as f32 } fn color(&self) -> Color { - Color::new(1.0, 1.0, 1.0, 1.0) + Color::WHITE } } diff --git a/src/widgets/bar_widget.rs b/src/widgets/bar_widget.rs index e10e23f..2338938 100644 --- a/src/widgets/bar_widget.rs +++ b/src/widgets/bar_widget.rs @@ -6,6 +6,7 @@ use crate::{ draw::{draw_bar, draw_box}, fonts::FontMap, widgets::{Geometry, Widget}, + event::{Event, PointerButton}, }; pub trait BarWidgetImpl { @@ -15,6 +16,7 @@ pub trait BarWidgetImpl { fn name(&self) -> &'static str; fn value(&self) -> f32; fn color(&self) -> Color; + fn click(&mut self, _pos: f32, _btn: PointerButton) {} } pub struct BarWidget { @@ -25,7 +27,8 @@ pub struct BarWidget { } impl BarWidget { - pub fn new(inner_widget: Box, font: &'static str, size: f32) -> BarWidget { + pub fn new(inner_widget: Box, fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget { + fm.queue_font(font, size, inner_widget.name()); BarWidget { geometry: Default::default(), inner_widget, @@ -46,10 +49,10 @@ impl Widget for BarWidget { fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { let font = fonts.get_font(self.font, self.size); - let fg = Color::new(1., 1., 1., 1.); - let bg = Color::new(0., 0., 0., 1.); + let fg = Color::WHITE; + let bg = Color::BLACK; - font.auto_draw_text(view, &bg, &fg, self.inner_widget.name()) + font.draw_text(view, bg, fg, self.inner_widget.name()) .unwrap(); let size = self.size.ceil() as u32; let bar_offset = 4 * size; @@ -57,8 +60,8 @@ impl Widget for BarWidget { let c = self.inner_widget.color(); draw_bar( - &mut view.offset((bar_offset, 0)).unwrap(), - &c, + &mut view.offset((bar_offset, 0)), + c, self.geometry.width - bar_offset, size, val, @@ -66,8 +69,8 @@ impl Widget for BarWidget { .unwrap(); draw_box( - &mut view.offset((bar_offset, 0)).unwrap(), - &c, + &mut view.offset((bar_offset, 0)), + c, (self.geometry.width - bar_offset, size), ) .unwrap(); @@ -88,4 +91,18 @@ impl Widget for BarWidget { }; self.geometry } + + fn event(&mut self, event: &Event) { + match event { + Event::PointerEvent(ev) => { + let offset = 4 * self.size.ceil() as u32; + if ev.pos.0 >= offset { + let val = (ev.pos.0 - offset) as f32 / (self.geometry.width - offset) as f32; + self.inner_widget.click(val, ev.button); + } + }, + _ => (), + } + } + } diff --git a/src/widgets/battery.rs b/src/widgets/battery.rs index 4eeaea2..77209b9 100644 --- a/src/widgets/battery.rs +++ b/src/widgets/battery.rs @@ -1,110 +1,81 @@ use std::{ sync::{Arc, Mutex}, thread, - time::Duration, -}; - -use dbus::blocking::{ - stdintf::org_freedesktop_dbus::{Properties, PropertiesPropertiesChanged}, - LocalConnection, + error::Error, }; use crate::{ color::Color, - state::{Event, Events}, + event::{Event, Events}, + fonts::FontMap, widgets::bar_widget::{BarWidget, BarWidgetImpl}, }; -enum UpowerBatteryState { - Charging, - Discharging, - Empty, - Full, - NotCharging, - Unknown, -} +use upower_dbus::BatteryState; struct InnerBattery { value: f32, - state: UpowerBatteryState, + state: BatteryState, dirty: bool, } +async fn monitor(inner: Arc>, events: Arc>) -> Result<(), Box> { + use zbus::Connection; + use upower_dbus::UPowerProxy; + use tokio_stream::StreamExt; + + let conn = Connection::system().await?; + let proxy = UPowerProxy::new(&conn).await?; + + let device = proxy.get_display_device().await?; + + let local_inner = inner.clone(); + let local_events = events.clone(); + let mut percent_stream = device.receive_percentage_changed().await; + let a = tokio::spawn(async move { + while let Some(percent) = percent_stream.next().await { + let percent = percent.get().await.unwrap() as f32 / 100.; + let mut inner = local_inner.lock().unwrap(); + inner.value = percent; + inner.dirty = true; + drop(inner); + + let mut events = local_events.lock().unwrap(); + events.add_event(Event::PowerUpdate); + } + }); + + let local_inner = inner.clone(); + let local_events = events.clone(); + let mut state_stream = device.receive_state_changed().await; + let b = tokio::spawn(async move { + while let Some(state) = state_stream.next().await { + let state = state.get().await.unwrap(); + let mut inner = local_inner.lock().unwrap(); + inner.state = state; + inner.dirty = true; + drop(inner); + + let mut events = local_events.lock().unwrap(); + events.add_event(Event::PowerUpdate); + } + }); + a.await?; + b.await?; + + Ok(()) +} + fn start_monitor(inner: Arc>, events: Arc>) { + use tokio::runtime; thread::Builder::new() .name("battmon".to_string()) .spawn(move || { - let conn = LocalConnection::new_system().unwrap(); - let device_path = "/org/freedesktop/UPower/devices/DisplayDevice"; - let proxy = conn.with_proxy( - "org.freedesktop.UPower", - device_path, - Duration::from_millis(500), - ); - - let capacity: f64 = proxy - .get("org.freedesktop.UPower.Device", "Percentage") - .expect("unable to get property"); - let state: u32 = proxy - .get("org.freedesktop.UPower.Device", "State") - .expect("unable to get state"); - - { - let mut inner = inner.lock().unwrap(); - inner.value = capacity as f32 / 100.; - inner.state = match state { - 1 => UpowerBatteryState::Charging, - 2 => UpowerBatteryState::Discharging, - 3 => UpowerBatteryState::Empty, - 4 => UpowerBatteryState::Full, - 5 => UpowerBatteryState::NotCharging, - 6 => UpowerBatteryState::Discharging, - _ => UpowerBatteryState::Unknown, - } - } - - proxy - .match_signal( - move |c: PropertiesPropertiesChanged, - _: &LocalConnection, - _: &dbus::Message| { - if c.interface_name != "org.freedesktop.UPower.Device" { - return true; - } - let mut inner = inner.lock().unwrap(); - for (key, value) in c.changed_properties { - match key.as_str() { - "State" => { - inner.state = match value.0.as_u64().unwrap() { - 1 => UpowerBatteryState::Charging, - 2 => UpowerBatteryState::Discharging, - 3 => UpowerBatteryState::Empty, - 4 => UpowerBatteryState::Full, - 5 => UpowerBatteryState::NotCharging, - 6 => UpowerBatteryState::Discharging, - _ => UpowerBatteryState::Unknown, - }; - inner.dirty = true; - let mut events = events.lock().unwrap(); - events.add_event(Event::PowerUpdate); - } - "Percentage" => { - inner.value = value.0.as_f64().unwrap() as f32 / 100.; - inner.dirty = true; - let mut events = events.lock().unwrap(); - events.add_event(Event::PowerUpdate); - } - _ => (), - } - } - true - }, - ) + let rt = runtime::Builder::new_current_thread() + .enable_io() + .build() .unwrap(); - - loop { - conn.process(Duration::from_millis(60000)).unwrap(); - } + rt.block_on(async move { monitor(inner, events).await }).unwrap(); }) .unwrap(); } @@ -114,16 +85,16 @@ pub struct Battery { } impl Battery { - pub fn new(events: Arc>, font: &'static str, size: f32) -> BarWidget { + pub fn new(events: Arc>, fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget { let battery = Battery { inner: Arc::new(Mutex::new(InnerBattery { value: 0., - state: UpowerBatteryState::Unknown, + state: BatteryState::Unknown, dirty: false, })), }; start_monitor(battery.inner.clone(), events); - BarWidget::new(Box::new(battery), font, size) + BarWidget::new(Box::new(battery), fm, font, size) } } @@ -142,20 +113,14 @@ impl BarWidgetImpl for Battery { fn color(&self) -> Color { let inner = self.inner.lock().unwrap(); match inner.state { - UpowerBatteryState::Discharging | UpowerBatteryState::Unknown => { - if inner.value > 0.25 { - Color::new(1.0, 1.0, 1.0, 1.0) - } else if inner.value > 0.1 { - Color::new(1.0, 0.5, 0.0, 1.0) - } else { - Color::new(1.0, 0.0, 0.0, 1.0) - } - } - UpowerBatteryState::Charging | UpowerBatteryState::Full => { - Color::new(0.5, 1.0, 0.5, 1.0) - } - UpowerBatteryState::NotCharging | UpowerBatteryState::Empty => { - Color::new(1.0, 0.5, 0.5, 1.0) + BatteryState::Charging | BatteryState::FullyCharged => Color::LIGHTGREEN, + BatteryState::PendingCharge => Color::LIGHTRED, + _ => if inner.value > 0.25 { + Color::WHITE + } else if inner.value > 0.1 { + Color::DARKORANGE + } else { + Color::RED } } } diff --git a/src/widgets/calendar.rs b/src/widgets/calendar.rs index f16f96f..68e3832 100644 --- a/src/widgets/calendar.rs +++ b/src/widgets/calendar.rs @@ -4,7 +4,7 @@ use crate::{ buffer::BufferView, color::Color, fonts::FontMap, - state::Event, + event::Event, widgets::{Geometry, Widget}, }; @@ -14,18 +14,32 @@ pub struct Calendar { font: &'static str, sections_x: i32, sections_y: i32, + shown_sections_x: u32, + shown_sections_y: u32, size: f32, dirty: bool, geometry: Geometry, } +const DAY_FACTOR: f32 = 1.; +const YEAR_FACTOR: f32 = 1.5; +const MONTH_FACTOR: f32 = 2.; +const DATE_FACTOR: f32 = 2.; +const LINE_HEIGHT: f32 = 1.8; + impl Calendar { - pub fn new(font: &'static str, size: f32, sections_x: i32, sections_y: i32) -> Calendar { + pub fn new(fm: &mut FontMap, font: &'static str, size: f32, sections_x: i32, sections_y: i32) -> Calendar { + fm.queue_font(font, size, "ADEFHIMNORSTUW"); + fm.queue_font(font, size * 1.5, "0123456789"); + fm.queue_font(font, size * 2.0, "0123456789 ADFJMOSabcehgilmnoprstuvy"); + Calendar { font, size, sections_x, sections_y, + shown_sections_x: 0, + shown_sections_y: 0, dirty: true, geometry: Default::default(), } @@ -38,10 +52,62 @@ impl Calendar { orig: NaiveDate, mut time: NaiveDate, ) { - let black = Color::new(0., 0., 0., 1.); - let white = Color::new(1., 1., 1., 1.); - let dim = Color::new(0.8, 0.8, 0.8, 1.); - let dimmer = Color::new(0.75, 0.75, 0.75, 1.); + let black = Color::BLACK; + let white = Color::WHITE; + let dim = Color::GREY80; + let dimmer = Color::GREY75; + + let mut y_off = 1; + // TODO: Fix offsets: + // - Headline font is not correctly accounted for + + // + // Draw the week day + // + for idx in 1..8 { + let day_font = fonts.get_font(self.font, self.size * DAY_FACTOR); + let wk_chr = match idx { + 1 => "MON", + 2 => "TUE", + 3 => "WED", + 4 => "THU", + 5 => "FRI", + 6 => "SAT", + 7 => "SUN", + _ => panic!("impossible value"), + }; + + day_font + .draw_text( + &mut view + .offset(( + idx * (self.size * 2.5).ceil() as u32 + (self.size / 5.).ceil() as u32, + y_off * (self.size * LINE_HEIGHT).ceil() as u32 + + (self.size * 1.).ceil() as u32, + )), + black, + white, + &wk_chr, + ) + .unwrap(); + } + + // + // Draw the month + // + if time.year() != orig.year() { + let year_font = fonts.get_font(self.font, self.size * YEAR_FACTOR); + year_font + .draw_text( + &mut view.offset(((self.size * 17.).ceil() as u32, 0)), + black, + dim, + &format!("{:}", time.year()), + ) + .unwrap(); + } + + let cal_font = fonts.get_font(self.font, self.size * DATE_FACTOR); let month_str = match time.month() { 1 => "January", 2 => "February", @@ -57,62 +123,8 @@ impl Calendar { 12 => "December", _ => panic!("impossible value"), }; - - - let mut y_off = 1; - // TODO: Fix offsets: - // - Headline font is not correctly accounted for - - // - // Draw the week day - // - for idx in 1..8 { - let day_font = fonts.get_font(self.font, self.size); - let wk_chr = match idx { - 1 => "MON", - 2 => "TUE", - 3 => "WED", - 4 => "THU", - 5 => "FRI", - 6 => "SAT", - 7 => "SUN", - _ => panic!("impossible value"), - }; - - day_font - .auto_draw_text( - &mut view - .offset(( - idx * (self.size * 2.5).ceil() as u32 + (self.size / 5.).ceil() as u32, - y_off * (self.size * 1.8).ceil() as u32 - + (self.size * 1.).ceil() as u32, - )) - .unwrap(), - &black, - &white, - &wk_chr, - ) - .unwrap(); - } - - // - // Draw the month - // - if time.year() != orig.year() { - let year_font = fonts.get_font(self.font, self.size * 1.5); - year_font - .auto_draw_text( - &mut view.offset(((self.size * 17.).ceil() as u32, 0)).unwrap(), - &black, - &dim, - &format!("{:}", time.year()), - ) - .unwrap(); - } - - let cal_font = fonts.get_font(self.font, self.size * 2.); cal_font - .auto_draw_text(&mut view, &black, &white, month_str) + .draw_text(&mut view, black, white, month_str) .unwrap(); let mut done = false; @@ -133,16 +145,15 @@ impl Calendar { // let wk = time.iso_week(); cal_font - .auto_draw_text( + .draw_text( &mut view .offset(( 0, - y_off * (self.size * 1.8).ceil() as u32 - + (self.size * 2.).ceil() as u32, - )) - .unwrap(), - &black, - &dimmer, + y_off * (self.size * LINE_HEIGHT).ceil() as u32 + + (self.size * MONTH_FACTOR).ceil() as u32, + )), + black, + dimmer, &format!("{:02}", wk.week()), ) .unwrap(); @@ -153,21 +164,21 @@ impl Calendar { // while x_pos < 8 { let c = if time.day() == orig.day() && time.month() == orig.month() { - Color::new(1.0, 1.0, 1.0, 1.0) + Color::WHITE } else { - Color::new(0.5, 0.5, 0.5, 1.0) + Color::GREY50 }; cal_font - .auto_draw_text( + .draw_text( &mut view .offset(( x_pos * (self.size * 2.5).ceil() as u32, - y_off * (self.size * 1.8).ceil() as u32 + (self.size * 2.) as u32, - )) - .unwrap(), - &black, - &c, + y_off * (self.size * LINE_HEIGHT).ceil() as u32 + + (self.size * MONTH_FACTOR).ceil() as u32, + )), + black, + c, &format!("{:02}", time.day()), ) .unwrap(); @@ -205,35 +216,33 @@ impl<'a> Widget for Calendar { } fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { + if self.shown_sections_y == 0 || self.shown_sections_x == 0 { + return Geometry { + x: self.geometry.x, + y: self.geometry.y, + width: 0, + height: 0, + }; + } + let time = Local::now().naive_local().date(); let cal_height = (self.size * 16.25).ceil() as u32; - let cal_width = (7. * self.size * 3. + self.size * 2.).ceil() as u32; + let cal_width = (7. * self.size * 2.5 + self.size * 2.).ceil() as u32; let cal_pad = (3. * self.size).ceil() as u32; - let sections_y = if self.sections_y > 0 { - self.sections_y as u32 - } else { - self.geometry.height / cal_height - }; - let sections_x = if self.sections_x > 0 { - self.sections_x as u32 - } else { - self.geometry.width / (cal_height + cal_pad) - }; let mut t = time.with_day(1).unwrap(); - let cals = sections_y * sections_x - 1; - if cals >=3 { + let cals = self.shown_sections_y * self.shown_sections_x; + if cals >= 3 { t = t.pred_opt().unwrap().with_day(1).unwrap(); } - for ydx in 0..sections_y { - for idx in 0..sections_x { + for ydx in 0..self.shown_sections_y { + for idx in 0..self.shown_sections_x { self.draw_month( fonts, &mut view - .offset(((cal_width + cal_pad) * idx, (cal_height) * ydx)) - .unwrap(), + .offset(((cal_width + cal_pad) * idx, (cal_height) * ydx)), time, t, ); @@ -249,17 +258,26 @@ impl<'a> Widget for Calendar { } fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry { - let cal_width = (7. * self.size * 3. + self.size * 2.).ceil() as u32; - let cal_height = (self.size * 16.25).ceil() as u32; // TODO: Calculate from font sizes + let cal_width = 7 * (self.size * 2.5).ceil() as u32 + (self.size * 2.).ceil() as u32; + let cal_height = (self.size * MONTH_FACTOR).ceil() as u32 + + (self.size * LINE_HEIGHT * 8.).ceil() as u32; let cal_pad = (self.size * 3.).ceil() as u32; - let width = if self.sections_x > 0 { + + let possible_sections_x = geometry.width / (cal_width + cal_pad); + let possible_sections_y = geometry.height / cal_height; + + let width = if self.sections_x > 0 && self.sections_x < possible_sections_x as i32 { + self.shown_sections_x = self.sections_x as u32; cal_width * self.sections_x as u32 + cal_pad * (self.sections_x as u32 - 1) } else { - (geometry.width / (cal_width + cal_pad)) * (cal_width + cal_pad) + self.shown_sections_x = possible_sections_x; + (geometry.width / (cal_width + cal_pad)) * (cal_width + cal_pad) }; - let height = if self.sections_y > 0 { + let height = if self.sections_y > 0 && self.sections_y < possible_sections_y as i32 { + self.shown_sections_y = self.sections_y as u32; cal_height * self.sections_y as u32 } else { + self.shown_sections_y = possible_sections_y; (geometry.height / cal_height) * cal_height }; self.geometry = Geometry { diff --git a/src/widgets/clock.rs b/src/widgets/clock.rs index 0fb807c..8af1941 100644 --- a/src/widgets/clock.rs +++ b/src/widgets/clock.rs @@ -4,7 +4,7 @@ use crate::{ buffer::BufferView, color::Color, fonts::FontMap, - state::Event, + event::Event, widgets::{Geometry, Widget}, }; @@ -20,7 +20,8 @@ pub struct Clock { } impl<'a> Clock { - pub fn new(font: &'static str, size: f32) -> Clock { + pub fn new(fm: &mut FontMap, font: &'static str, size: f32) -> Clock { + fm.queue_font(font, size, "0123456789: "); Clock { font, size, @@ -52,16 +53,16 @@ impl<'a> Widget for Clock { fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { let time = Local::now().naive_local(); - let fg = Color::new(1., 1., 1., 1.); - let bg = Color::new(0., 0., 0., 1.); + let fg = Color::WHITE; + let bg = Color::BLACK; let digit = self.digit_width; let colon = self.colon_width; let font = fonts.get_font(self.font, self.size); font.draw_text_fixed_width( view, - &bg, - &fg, + bg, + fg, &[digit, digit, colon, digit, digit], &format!("{:02}:{:02}", time.hour(), time.minute()), ) diff --git a/src/widgets/date.rs b/src/widgets/date.rs index 1c9d806..cf67bc6 100644 --- a/src/widgets/date.rs +++ b/src/widgets/date.rs @@ -4,7 +4,7 @@ use crate::{ buffer::BufferView, color::Color, fonts::FontMap, - state::Event, + event::Event, widgets::{Geometry, Widget}, }; @@ -18,7 +18,8 @@ pub struct Date { } impl Date { - pub fn new(font: &'static str, size: f32) -> Date { + pub fn new(fm: &mut FontMap, font: &'static str, size: f32) -> Date { + fm.queue_font(font, size, "0123456789/, adehinortuFMSTW"); Date { font, size, @@ -48,14 +49,13 @@ impl<'a> Widget for Date { fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { let time = Local::now().naive_local(); - let fg = Color::new(1., 1., 1., 1.); - let bg = Color::new(0., 0., 0., 1.); - let mut date_line = view.offset((0, 8)).unwrap(); + let fg = Color::WHITE; + let bg = Color::BLACK; let font = fonts.get_font(self.font, self.size); - font.auto_draw_text( - &mut date_line, - &bg, - &fg, + font.draw_text( + view, + bg, + fg, &format!( "{}, {:02}/{:02}/{:4}", time.weekday(), @@ -69,13 +69,14 @@ impl<'a> Widget for Date { self.geometry } - fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry { + fn geometry_update(&mut self, fonts: &mut FontMap, geometry: &Geometry) -> Geometry { let width = (3. + 2. + 2. + 2. + 4.) * self.size / 2.; + let font = fonts.get_font(self.font, self.size); self.geometry = Geometry { x: geometry.x, y: geometry.y, width: width.ceil() as u32, - height: self.size.ceil() as u32, + height: font.height().ceil() as u32, }; self.geometry } diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs index 80c9e82..bcdadb5 100644 --- a/src/widgets/launcher.rs +++ b/src/widgets/launcher.rs @@ -12,7 +12,7 @@ use crate::{ color::Color, fonts::FontMap, keyboard::{keysyms, KeyEvent}, - state::Event, + event::{Event, Events, PointerEvent, PointerButton}, utils::desktop::{load_desktop_files, Desktop}, widgets::{Geometry, Widget}, }; @@ -205,14 +205,20 @@ struct Launcher { } impl Launcher { - fn new() -> Launcher { + fn new(events: Arc>) -> Launcher { let options = Arc::new(Mutex::new(Vec::new())); { let options = Arc::clone(&options); - thread::spawn(move || { - let mut options = options.lock().unwrap(); - *options = load_desktop_files(); - }); + thread::Builder::new() + .name("desktopini".to_string()) + .spawn(move || { + let mut options = options.lock().unwrap(); + *options = load_desktop_files(); + drop(options); + let mut events = events.lock().unwrap(); + events.add_event(Event::LauncherUpdate); + }) + .unwrap(); } Launcher { @@ -280,31 +286,31 @@ impl InterfaceWidget for Launcher { fonts: &mut FontMap, view: &mut BufferView, ) -> Geometry { - let fg = Color::new(1., 1., 1., 1.); - let bg = Color::new(0., 0., 0., 1.); + let fg = Color::WHITE; + let bg = Color::BLACK; let line_height = intf.size.ceil() as u32; // Draw line let mut prompt_offset = intf.geometry.height - line_height; - let mut prompt_line = view.offset((0, prompt_offset)).unwrap(); + let mut prompt_line = view.offset((0, prompt_offset)); let font = fonts.get_font(intf.font, intf.size); let mut x_max = font .auto_draw_text_with_cursor( &mut prompt_line, - &bg, - &fg, + bg, + fg, &format!(" {}", &intf.prompt.input), intf.prompt.cursor + 3, ) .unwrap(); - font.auto_draw_text(&mut prompt_line, &bg, &fg, ">") + font.auto_draw_text(&mut prompt_line, bg, fg, ">") .unwrap(); // Draw entries - let dimfg = Color::new(0.5, 0.5, 0.5, 1.0); + let dimfg = Color::GREY50; prompt_offset -= 16; for (idx, m) in self.matches.iter().enumerate() { @@ -312,7 +318,7 @@ impl InterfaceWidget for Launcher { break; } prompt_offset -= line_height; - let mut line = view.offset((0, prompt_offset)).unwrap(); + let mut line = view.offset((0, prompt_offset)); if idx == intf.selection { let fuzzy_matcher = SkimMatcherV2::default(); @@ -323,20 +329,20 @@ impl InterfaceWidget for Launcher { let mut colors = Vec::with_capacity(m.name.len()); for pos in 0..m.name.len() { if indices.contains(&pos) { - colors.push(Color::new(1.0, 0.65, 0., 1.0)); + colors.push(Color::LIGHTORANGE); } else { - colors.push(Color::new(0.75, 0.75, 0.75, 1.0)); + colors.push(Color::GREY75); } } x_max = max( x_max, - font.auto_draw_text_individual_colors(&mut line, &bg, &colors, &m.name) + font.auto_draw_text_individual_colors(&mut line, bg, &colors, &m.name) .unwrap(), ); } else { x_max = max( x_max, - font.auto_draw_text(&mut line, &bg, &dimfg, &m.name) + font.auto_draw_text(&mut line, bg, dimfg, &m.name) .unwrap(), ); } @@ -379,27 +385,27 @@ impl InterfaceWidget for Shell { fonts: &mut FontMap, view: &mut BufferView, ) -> Geometry { - let fg = Color::new(1., 1., 1., 1.); - let bg = Color::new(0., 0., 0., 1.); + let fg = Color::WHITE; + let bg = Color::BLACK; let line_height = intf.size.ceil() as u32; // Draw line let prompt_offset = intf.geometry.height - line_height; - let mut prompt_line = view.offset((0, prompt_offset)).unwrap(); + let mut prompt_line = view.offset((0, prompt_offset)); // Draw prompt let font = fonts.get_font(intf.font, intf.size); let x_max = font .auto_draw_text_with_cursor( &mut prompt_line, - &bg, - &fg, + bg, + fg, &format!(" {}", &intf.prompt.input), intf.prompt.cursor + 3, ) .unwrap(); - font.auto_draw_text(&mut prompt_line, &bg, &Color::new(1., 0.75, 0.5, 1.), "!") + font.auto_draw_text(&mut prompt_line, bg, Color::BUFF, "!") .unwrap(); Geometry { @@ -440,45 +446,45 @@ impl InterfaceWidget for Calc { fonts: &mut FontMap, view: &mut BufferView, ) -> Geometry { - let fg = Color::new(1., 1., 1., 1.); - let bg = Color::new(0., 0., 0., 1.); + let fg = Color::WHITE; + let bg = Color::BLACK; let line_height = intf.size.ceil() as u32; // Draw line let mut prompt_offset = intf.geometry.height - (line_height); - let mut prompt_line = view.offset((0, prompt_offset)).unwrap(); + let mut prompt_line = view.offset((0, prompt_offset)); // Draw prompt let font = fonts.get_font(intf.font, intf.size); let res_off = font .auto_draw_text_with_cursor( &mut prompt_line, - &bg, - &fg, + bg, + fg, &format!(" {} ", &intf.prompt.input), intf.prompt.cursor + 3, ) .unwrap(); - font.auto_draw_text(&mut prompt_line, &bg, &Color::new(1., 0.75, 0.5, 1.), "=") + font.auto_draw_text(&mut prompt_line, bg, Color::BUFF, "=") .unwrap(); if let Some(res) = &self.result { - let resfg = Color::new(1.0, 0.65, 0., 1.0); - let mut result_line = view.offset((res_off.0, prompt_offset)).unwrap(); - font.auto_draw_text(&mut result_line, &bg, &resfg, &format!(" = {}", &res)) + let resfg = Color::LIGHTORANGE; + let mut result_line = view.offset((res_off.0, prompt_offset)); + font.auto_draw_text(&mut result_line, bg, resfg, &format!(" = {}", &res)) .unwrap(); } for m in self.old.iter().rev() { - let dimfg = Color::new(0.5, 0.5, 0.5, 1.0); + let dimfg = Color::GREY50; if prompt_offset < line_height { break; } prompt_offset -= line_height; - let mut result_line = view.offset((0, prompt_offset)).unwrap(); - font.auto_draw_text(&mut result_line, &bg, &dimfg, &m) + let mut result_line = view.offset((0, prompt_offset)); + font.auto_draw_text(&mut result_line, bg, dimfg, &m) .unwrap(); } @@ -512,9 +518,14 @@ pub struct Interface { } impl Interface { - pub fn new(font: &'static str, size: f32) -> Interface { + pub fn new(events: Arc>, fm: &mut FontMap, font: &'static str, size: f32) -> Interface { + fm.queue_font( + font, size, + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 >=!,.-/()", + ); + Interface { - launcher: Launcher::new(), + launcher: Launcher::new(events), shell: Shell {}, calc: Calc { old: Vec::new(), @@ -531,6 +542,23 @@ impl Interface { } } + fn pointer_input(&mut self, event: &PointerEvent) { + if let PointerButton::Left = event.button { + let line_height = self.inner.size.ceil() as u32; + let height = self.inner.geometry.height - line_height - 16; + let lines = height / line_height; + + let offset = height % line_height; + let pos = if event.pos.1 >= offset { + event.pos.1 - offset + } else { + return; + }; + self.inner.selection = (lines - pos / line_height - 1) as usize; + self.inner.dirty = true; + } + } + fn keyboard_input(&mut self, event: &KeyEvent) { if event.state != WEnum::Value(wl_keyboard::KeyState::Pressed) { return; @@ -616,6 +644,11 @@ impl Widget for Interface { fn event(&mut self, event: &Event) { match event { Event::KeyEvent(e) => self.keyboard_input(e), + Event::PointerEvent(e) => self.pointer_input(e), + Event::LauncherUpdate => { + self.inner.dirty = true; + self.launcher.update(&self.inner); + }, Event::TokenUpdate(t) => self.launcher.next_token = Some(t.to_string()), _ => (), } diff --git a/src/widgets/layout.rs b/src/widgets/layout.rs new file mode 100644 index 0000000..c952a97 --- /dev/null +++ b/src/widgets/layout.rs @@ -0,0 +1,174 @@ +use std::cmp::max; + +use crate::{ + fonts::FontMap, + state::State, + widgets::{Geometry}, +}; + +pub trait Layout { + fn geometry_update( + &self, + fonts: &mut FontMap, + geometry: &Geometry, + state: &mut State, + ) -> Geometry; +} + +pub struct HorizontalLayout { + pub widgets: Vec>, +} + +impl HorizontalLayout { + pub fn new(widgets: Vec>) -> Box { + Box::new(HorizontalLayout { widgets }) + } +} + +impl Layout for HorizontalLayout { + fn geometry_update( + &self, + fonts: &mut FontMap, + geometry: &Geometry, + state: &mut State, + ) -> Geometry { + let mut geo = geometry.clone(); + let mut max_width = 0; + let mut max_height = 0; + for w in self.widgets.iter() { + let result = w.geometry_update(fonts, &geo, state); + geo.x = result.x + result.width; + geo.width -= result.width; + max_width += result.width; + max_height = max(result.height, max_height); + } + geo.width = max_width; + geo.height = max_height; + geo + } +} + +pub struct VerticalLayout { + pub widgets: Vec>, +} + +impl VerticalLayout { + pub fn new(widgets: Vec>) -> Box { + Box::new(VerticalLayout { widgets }) + } +} + +impl Layout for VerticalLayout { + fn geometry_update( + &self, + fonts: &mut FontMap, + geometry: &Geometry, + state: &mut State, + ) -> Geometry { + let mut geo = geometry.clone(); + let mut max_width = 0; + let mut max_height = 0; + for w in self.widgets.iter() { + let result = w.geometry_update(fonts, &geo, state); + geo.y = result.y + result.height; + geo.height -= result.height; + max_width = max(result.width, max_width); + max_height += result.height; + } + geo.width = max_width; + geo.height = max_height; + geo + } +} + +pub trait WidgetUpdater { + fn geometry_update(&mut self, idx: usize, fonts: &mut FontMap, geometry: &Geometry) + -> Geometry; +} + +pub struct IndexedLayout { + pub widget_idx: usize, +} + +impl IndexedLayout { + pub fn new(widget_idx: usize) -> Box { + Box::new(IndexedLayout { widget_idx }) + } +} + +impl Layout for IndexedLayout { + fn geometry_update( + &self, + fonts: &mut FontMap, + geometry: &Geometry, + state: &mut State, + ) -> Geometry { + state.geometry_update(self.widget_idx, fonts, geometry) + } +} + +pub struct Margin { + pub widget: Box, + pub margin: (u32, u32, u32, u32), +} + +impl Margin { + pub fn new(widget: Box, margin: (u32, u32, u32, u32)) -> Box { + Box::new(Margin { widget, margin }) + } +} + +impl Layout for Margin { + fn geometry_update( + &self, + fonts: &mut FontMap, + geometry: &Geometry, + state: &mut State, + ) -> Geometry { + let geo = Geometry { + x: geometry.x + self.margin.0, + y: geometry.y + self.margin.1, + width: geometry.width - self.margin.0 - self.margin.2, + height: geometry.height - self.margin.1 - self.margin.3, + }; + let out = self.widget.geometry_update(fonts, &geo, state); + Geometry { + x: out.x - self.margin.0, + y: out.y - self.margin.1, + width: out.width + self.margin.0 + self.margin.2, + height: out.height + self.margin.1 + self.margin.3, + } + } +} + +pub struct InvertedHorizontalLayout { + pub widgets: Vec>, +} + +impl InvertedHorizontalLayout { + pub fn new(widgets: Vec>) -> Box { + Box::new(InvertedHorizontalLayout { widgets }) + } +} + +impl Layout for InvertedHorizontalLayout { + fn geometry_update( + &self, + fonts: &mut FontMap, + geometry: &Geometry, + state: &mut State, + ) -> Geometry { + let mut geo = geometry.clone(); + let mut max_height = 0; + for w in self.widgets.iter() { + let mut temp_geo = geo.clone(); + let temp_result = w.geometry_update(fonts, &temp_geo, state); + temp_geo.x = geo.x + (geo.width - temp_result.width); + let result = w.geometry_update(fonts, &temp_geo, state); + geo.width -= result.width; + max_height = max(result.height, max_height); + } + geo.height = max_height; + geo + } +} diff --git a/src/widgets/line.rs b/src/widgets/line.rs index 705097b..faa9750 100644 --- a/src/widgets/line.rs +++ b/src/widgets/line.rs @@ -7,14 +7,17 @@ use crate::{ widgets::{Geometry, Widget}, }; -#[derive(Default)] pub struct Line { geometry: Geometry, + thickness: u32, } impl Line { - pub fn new() -> Line { - Default::default() + pub fn new(thickness: u32) -> Line { + Line{ + geometry: Default::default(), + thickness, + } } } @@ -23,16 +26,15 @@ impl Widget for Line { self.geometry } fn draw(&mut self, _fonts: &mut FontMap, view: &mut BufferView) -> Geometry { - view.memset(&Color::new(1., 1., 1., 1.)); + view.memset(Color::WHITE); self.geometry } - fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry { self.geometry = Geometry { x: geometry.x, y: geometry.y, width: geometry.width, - height: 1, + height: self.thickness, }; self.geometry } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index ac08074..eb74676 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,3 +1,4 @@ +mod audio; mod backlight; mod bar_widget; mod battery; @@ -7,7 +8,9 @@ mod date; mod launcher; mod line; mod widget; +mod layout; +pub use audio::Audio; pub use backlight::Backlight; pub use battery::Battery; pub use calendar::Calendar; @@ -15,7 +18,5 @@ pub use clock::Clock; pub use date::Date; pub use launcher::Interface; pub use line::Line; -pub use widget::{ - Geometry, HorizontalLayout, IndexedLayout, InvertedHorizontalLayout, Layout, Margin, - VerticalLayout, Widget, WidgetUpdater, -}; +pub use layout::{VerticalLayout, HorizontalLayout, IndexedLayout, InvertedHorizontalLayout, Layout, Margin, WidgetUpdater}; +pub use widget::{Geometry, Widget}; diff --git a/src/widgets/widget.rs b/src/widgets/widget.rs index c63f8e6..b097854 100644 --- a/src/widgets/widget.rs +++ b/src/widgets/widget.rs @@ -6,7 +6,7 @@ use std::{ use crate::{ buffer::BufferView, fonts::FontMap, - state::{Event, State}, + event::Event, }; #[derive(Debug, Default, Clone, Copy)] @@ -47,6 +47,11 @@ impl Geometry { height: max(self.height + self.y, other.height + other.height) - y, } } + + pub fn contains(&self, position: (u32, u32)) -> bool { + position.0 >= self.x && position.0 < self.x + self.width && + position.1 >= self.y && position.1 < self.y + self.height + } } pub trait Widget { @@ -59,169 +64,3 @@ pub trait Widget { fn event(&mut self, _event: &Event) {} } -pub trait Layout { - fn geometry_update( - &self, - fonts: &mut FontMap, - geometry: &Geometry, - state: &mut State, - ) -> Geometry; -} -pub struct HorizontalLayout { - pub widgets: Vec>, -} - -impl HorizontalLayout { - pub fn new(widgets: Vec>) -> Box { - Box::new(HorizontalLayout { widgets }) - } -} - -impl Layout for HorizontalLayout { - fn geometry_update( - &self, - fonts: &mut FontMap, - geometry: &Geometry, - state: &mut State, - ) -> Geometry { - let mut geo = geometry.clone(); - let mut max_width = 0; - let mut max_height = 0; - for w in self.widgets.iter() { - let result = w.geometry_update(fonts, &geo, state); - geo.x = result.x + result.width; - geo.width -= result.width; - max_width += result.width; - max_height = max(result.height, max_height); - } - geo.width = max_width; - geo.height = max_height; - geo - } -} - -pub struct VerticalLayout { - pub widgets: Vec>, -} - -impl VerticalLayout { - pub fn new(widgets: Vec>) -> Box { - Box::new(VerticalLayout { widgets }) - } -} - -impl Layout for VerticalLayout { - fn geometry_update( - &self, - fonts: &mut FontMap, - geometry: &Geometry, - state: &mut State, - ) -> Geometry { - let mut geo = geometry.clone(); - let mut max_width = 0; - let mut max_height = 0; - for w in self.widgets.iter() { - let result = w.geometry_update(fonts, &geo, state); - geo.y = result.y + result.height; - geo.height -= result.height; - max_width = max(result.width, max_width); - max_height += result.height; - } - geo.width = max_width; - geo.height = max_height; - geo - } -} - -pub trait WidgetUpdater { - fn geometry_update(&mut self, idx: usize, fonts: &mut FontMap, geometry: &Geometry) - -> Geometry; -} - -pub struct IndexedLayout { - pub widget_idx: usize, -} - -impl IndexedLayout { - pub fn new(widget_idx: usize) -> Box { - Box::new(IndexedLayout { widget_idx }) - } -} - -impl Layout for IndexedLayout { - fn geometry_update( - &self, - fonts: &mut FontMap, - geometry: &Geometry, - state: &mut State, - ) -> Geometry { - state.geometry_update(self.widget_idx, fonts, geometry) - } -} - -pub struct Margin { - pub widget: Box, - pub margin: (u32, u32, u32, u32), -} - -impl Margin { - pub fn new(widget: Box, margin: (u32, u32, u32, u32)) -> Box { - Box::new(Margin { widget, margin }) - } -} - -impl Layout for Margin { - fn geometry_update( - &self, - fonts: &mut FontMap, - geometry: &Geometry, - state: &mut State, - ) -> Geometry { - let geo = Geometry { - x: geometry.x + self.margin.0, - y: geometry.y + self.margin.1, - width: geometry.width - self.margin.0 - self.margin.2, - height: geometry.height - self.margin.1 - self.margin.3, - }; - - let out = self.widget.geometry_update(fonts, &geo, state); - Geometry { - x: out.x - self.margin.0, - y: out.y - self.margin.1, - width: out.width + self.margin.0 + self.margin.2, - height: out.height + self.margin.1 + self.margin.3, - } - } -} - -pub struct InvertedHorizontalLayout { - pub widgets: Vec>, -} - -impl InvertedHorizontalLayout { - pub fn new(widgets: Vec>) -> Box { - Box::new(InvertedHorizontalLayout { widgets }) - } -} - -impl Layout for InvertedHorizontalLayout { - fn geometry_update( - &self, - fonts: &mut FontMap, - geometry: &Geometry, - state: &mut State, - ) -> Geometry { - let mut geo = geometry.clone(); - let mut max_height = 0; - for w in self.widgets.iter() { - let mut temp_geo = geo.clone(); - let temp_result = w.geometry_update(fonts, &temp_geo, state); - temp_geo.x = geo.x + (geo.width - temp_result.width); - let result = w.geometry_update(fonts, &temp_geo, state); - geo.width -= result.width; - max_height = max(result.height, max_height); - } - geo.height = max_height; - geo - } -} From 3a2badbfd9b1ab6d5ffc4263d6794ab376fd6534 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Sat, 4 Feb 2023 14:42:29 +0100 Subject: [PATCH 11/57] wlr-layer-shell support --- Cargo.lock | 14 ++++++ Cargo.toml | 1 + src/main.rs | 9 ++-- src/state.rs | 127 ++++++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 131 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 95766ba..ea685d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1489,6 +1489,19 @@ dependencies = [ "wayland-scanner", ] +[[package]] +name = "wayland-protocols-wlr" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce991093320e4a6a525876e6b629ab24da25f9baef0c2e0080ad173ec89588a" +dependencies = [ + "bitflags", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + [[package]] name = "wayland-scanner" version = "0.30.0" @@ -1630,6 +1643,7 @@ dependencies = [ "walkdir", "wayland-client", "wayland-protocols", + "wayland-protocols-wlr", "xkbcommon", "zbus", ] diff --git a/Cargo.toml b/Cargo.toml index 8bf8b4b..c218727 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ fontloading = ["fontconfig"] [dependencies] wayland-client = { version = "0.30", features = ["calloop", "log"] } wayland-protocols = { version = "0.30", features = ["client", "unstable", "staging"] } +wayland-protocols-wlr = { version = "0.1", features = ["client"] } nix = "0.26" memmap = "0.7" fontconfig = { version = "0.6.0", optional = true } diff --git a/src/main.rs b/src/main.rs index 09a8851..cb53f4a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,7 +19,7 @@ use chrono::{Duration, Local, Timelike}; use buffer::BufferView; use color::Color; use fonts::{FontMap, MaybeFontMap}; -use state::State; +use state::{State, OperationMode}; use event::{Event, Events}; use widgets::{ Audio, Backlight, Battery, Calendar, Clock, Date, Geometry, HorizontalLayout, IndexedLayout, @@ -157,7 +157,7 @@ fn main() { fm }).unwrap(); - let mut state = State::new(widgets, layout, MaybeFontMap::Waiting(font_thread), events); + let mut state = State::new(OperationMode::XdgToplevel, widgets, layout, MaybeFontMap::Waiting(font_thread), events); let mut damage = Vec::new(); for _ in 0..state.widgets.len() { @@ -199,8 +199,9 @@ fn main() { let surface = state.base_surface.as_ref().unwrap().clone(); if force { - // Other compositors probably need this. - //bufview.memset(Color::BLACK); + if state.needs_memset { + bufview.memset(Color::BLACK); + } for (idx, widget) in state.widgets.iter_mut().enumerate() { if force || widget.get_dirty() { let geo = widget.geometry(); diff --git a/src/state.rs b/src/state.rs index b6c1eb2..aa50038 100644 --- a/src/state.rs +++ b/src/state.rs @@ -18,6 +18,10 @@ use wayland_protocols::xdg::{ shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base}, }; +use wayland_protocols_wlr::layer_shell::v1::client::{ + zwlr_layer_shell_v1, zwlr_layer_surface_v1, +}; + use crate::{ buffer::BufferManager, fonts::{FontMap, MaybeFontMap}, @@ -26,11 +30,20 @@ use crate::{ event::{Event, Events, PointerEvent, PointerButton}, }; +pub enum OperationMode { + LayerSurface((u32, u32)), + XdgToplevel, +} + pub struct State { + mode: OperationMode, pub running: bool, pub dirty: bool, activated: bool, + pub needs_memset: bool, pub base_surface: Option, + layer_shell: Option, + layer_surface: Option, wm_base: Option, xdg_surface: Option<(xdg_surface::XdgSurface, xdg_toplevel::XdgToplevel)>, wl_shm: Option, @@ -48,6 +61,7 @@ pub struct State { impl State { pub fn new( + mode: OperationMode, widgets: Vec>, layout: Rc>, fonts: MaybeFontMap, @@ -57,7 +71,10 @@ impl State { running: true, dirty: true, activated: false, + needs_memset: true, base_surface: None, + layer_shell: None, + layer_surface: None, wm_base: None, xdg_surface: None, wl_shm: None, @@ -67,6 +84,7 @@ impl State { bufmgr: BufferManager::new(), keyboard: Keyboard::new(), pointer: None, + mode, widgets, fonts, events, @@ -97,6 +115,31 @@ impl State { _ => (), } } + + fn init_xdg_surface(&mut self, qh: &QueueHandle) { + let wm_base = self.wm_base.as_ref().unwrap(); + let base_surface = self.base_surface.as_ref().unwrap(); + + let xdg_surface = wm_base.get_xdg_surface(base_surface, qh, ()); + let toplevel = xdg_surface.get_toplevel(qh, ()); + toplevel.set_title("wldash".into()); + + base_surface.commit(); + + self.xdg_surface = Some((xdg_surface, toplevel)); + } + + fn init_layer_surface(&mut self, qh: &QueueHandle, size: (u32, u32)) { + let layer_shell = self.layer_shell.as_ref().unwrap(); + let base_surface = self.base_surface.as_ref().unwrap(); + + let layer_surface = layer_shell.get_layer_surface(base_surface, None, zwlr_layer_shell_v1::Layer::Top, "launcher".to_string(), qh, ()); + layer_surface.set_keyboard_interactivity(zwlr_layer_surface_v1::KeyboardInteractivity::Exclusive); + layer_surface.set_size(size.0, size.1); + + base_surface.commit(); + self.layer_surface = Some(layer_surface); + } } impl WidgetUpdater for State { @@ -126,23 +169,26 @@ impl Dispatch for State { match &interface[..] { "wl_compositor" => { let compositor = - registry.bind::(name, 4, qh, ()); + registry.bind::(name, 5, qh, ()); let surface = compositor.create_surface(qh, ()); state.base_surface = Some(surface); state.activate(qh); - if state.wm_base.is_some() && state.xdg_surface.is_none() { - state.init_xdg_surface(qh); + if state.wm_base.is_some() && state.xdg_surface.is_none() && state.layer_surface.is_none() { + match state.mode { + OperationMode::XdgToplevel => state.init_xdg_surface(qh), + OperationMode::LayerSurface(size) => state.init_layer_surface(qh, size), + } } } "wl_shm" => { state.wl_shm = Some(registry.bind::(name, 1, qh, ())); } "wl_seat" => { - registry.bind::(name, 1, qh, ()); + registry.bind::(name, 8, qh, ()); } - "xdg_wm_base" => { - let wm_base = registry.bind::(name, 1, qh, ()); + "xdg_wm_base" => if let OperationMode::XdgToplevel = state.mode { + let wm_base = registry.bind::(name, 2, qh, ()); state.wm_base = Some(wm_base); if state.base_surface.is_some() && state.xdg_surface.is_none() { @@ -155,6 +201,21 @@ impl Dispatch for State { ); state.activate(qh); } + "zwlr_layer_shell_v1" => if let OperationMode::LayerSurface(size) = state.mode { + state.layer_shell = Some( + registry.bind::(name, 4, qh, ()), + ); + if state.base_surface.is_some() && state.layer_surface.is_none() { + state.init_layer_surface(qh, size); + } + }, + "clay_control_v1" => { + // Buffers are zeroed by default, which is equivalent to zero alpha black. This + // is not a particularly good background, but memsetting is slow. On clay, + // toplevels that adhere to their requested dimensions only have black behind + // them, so we can save the memset in this case, speeding things up. + state.needs_memset = false; + } _ => {} } } @@ -226,18 +287,52 @@ impl Dispatch for State { } } -impl State { - fn init_xdg_surface(&mut self, qh: &QueueHandle) { - let wm_base = self.wm_base.as_ref().unwrap(); - let base_surface = self.base_surface.as_ref().unwrap(); +impl Dispatch for State { + fn event( + _: &mut Self, + _: &zwlr_layer_shell_v1::ZwlrLayerShellV1, + _: zwlr_layer_shell_v1::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) {} +} - let xdg_surface = wm_base.get_xdg_surface(base_surface, qh, ()); - let toplevel = xdg_surface.get_toplevel(qh, ()); - toplevel.set_title("wldash".into()); +impl Dispatch for State { + fn event( + state: &mut Self, + layer_surface: &zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, + event: zwlr_layer_surface_v1::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + if let zwlr_layer_surface_v1::Event::Configure { serial, width, height } = event { + if (width, height) == (0, 0) { + return; + } + layer_surface.ack_configure(serial); + state.configured = true; + state.dirty = true; - base_surface.commit(); - - self.xdg_surface = Some((xdg_surface, toplevel)); + if state.dimensions != (width as i32, height as i32) { + state.bufmgr.clear_buffers(); + state.dimensions = (width as i32, height as i32); + state.fonts.resolve(); + let fonts = state.fonts.unwrap(); + let layout = state.layout.clone(); + layout.geometry_update( + &mut fonts.borrow_mut(), + &Geometry { + x: 0, + y: 0, + width: width as u32, + height: height as u32, + }, + state, + ); + } + } } } From 74591902e9090e157f804754f93f0eae840bd632 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Sat, 4 Feb 2023 16:52:18 +0100 Subject: [PATCH 12/57] Support toggle between V1 and V2 UIs --- src/main.rs | 147 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 92 insertions(+), 55 deletions(-) diff --git a/src/main.rs b/src/main.rs index cb53f4a..bb25b62 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,13 +23,47 @@ use state::{State, OperationMode}; use event::{Event, Events}; use widgets::{ Audio, Backlight, Battery, Calendar, Clock, Date, Geometry, HorizontalLayout, IndexedLayout, - Interface, InvertedHorizontalLayout, Line, Margin, VerticalLayout, Widget, + Interface, InvertedHorizontalLayout, Line, Margin, VerticalLayout, Widget, Layout, }; -use std::{rc::Rc, thread}; +use std::{ + env, + rc::Rc, + thread +}; + +enum UIVersion { + V1, + V2, +} fn main() { // let now = Local::now(); + + let mut args = env::args(); + // Skip program name + _ = args.next(); + + let mut version = UIVersion::V2; + let mut mode = OperationMode::XdgToplevel; + loop { + match args.next() { + Some(ref s) if s == "layer_shell" => { + let width = args.next().map(|x| x.parse::().ok()).flatten(); + let height = args.next().map(|x| x.parse::().ok()).flatten(); + match (width, height) { + (Some(w), Some(h)) => mode = OperationMode::LayerSurface((w, h)), + _ => panic!("invalid arguments"), + } + }, + Some(ref s) if s == "xdg_shell" => mode = OperationMode::XdgToplevel, + Some(ref s) if s == "v1" => version = UIVersion::V1, + Some(ref s) if s == "v2" => version = UIVersion::V2, + Some(_) => panic!("unknown argument"), + None => break, + } + } + let conn = Connection::connect_to_env().unwrap(); let event_queue = conn.new_event_queue(); @@ -92,63 +126,66 @@ fn main() { fm.add_font_path("sans", "/usr/share/fonts/noto/NotoSans-Regular.ttf".to_string()); fm.add_font_path("monospace", "/usr/share/fonts/noto/NotoSansMono-Regular.ttf".to_string()); - let clock = Box::new(Clock::new(&mut fm, "sans", 128.)); - let calendar = Box::new(Calendar::new(&mut fm, "monospace", 12.0, 1, -1)); - let date = Box::new(Date::new(&mut fm, "sans", 48.)); - let line = Box::new(Line::new(1)); - let launcher = Box::new(Interface::new(events.clone(), &mut fm, "monospace", 32.)); - let battery = Box::new(Battery::new(events.clone(), &mut fm, "sans", 24.)); - let backlight = Box::new(Backlight::new("intel_backlight", &mut fm, "sans", 24.)); - let audio = Box::new(Audio::new(events.clone(), &mut fm, "sans", 24.)); - let widgets: Vec> = - vec![clock, date, battery, backlight, audio, line, calendar, launcher]; + let (widgets, layout): (Vec>, Rc>) = match version { + UIVersion::V1 => { + let clock = Box::new(Clock::new(&mut fm, "sans", 256.)); + let calendar = Box::new(Calendar::new(&mut fm, "monospace", 18.0, -1, 1)); + let date = Box::new(Date::new(&mut fm, "sans", 48.)); + let launcher = Box::new(Interface::new(events.clone(), &mut fm, "monospace", 32.)); + let battery = Box::new(Battery::new(events.clone(), &mut fm, "sans", 24.)); + let backlight = Box::new(Backlight::new("intel_backlight", &mut fm, "sans", 24.)); + let audio = Box::new(Audio::new(events.clone(), &mut fm, "sans", 24.)); - let layout = Rc::new(VerticalLayout::new(vec![ - HorizontalLayout::new(vec![ - IndexedLayout::new(0), - Margin::new(IndexedLayout::new(1), (16, 16, 0, 0)), - VerticalLayout::new(vec![ - Margin::new(IndexedLayout::new(2), (16, 8, 8, 0)), - Margin::new(IndexedLayout::new(3), (16, 8, 8, 0)), - Margin::new(IndexedLayout::new(4), (16, 8, 8, 0)), - ]), - ]), - IndexedLayout::new(5), - InvertedHorizontalLayout::new(vec![IndexedLayout::new(6), IndexedLayout::new(7)]), - ])); - - /* alt look - let clock = Box::new(Clock::new(&mut fm, "sans", 256.)); - let date = Box::new(Date::new(&mut fm, "sans", 64.)); - let calendar = Box::new(Calendar::new(&mut fm, "monospace", 16.0, 3, 1)); - let launcher = Box::new(Interface::new(events.clone(), &mut fm, "monospace", 32.)); - let battery = Box::new(Battery::new(events.clone(), &mut fm, "sans", 24.)); - let backlight = Box::new(Backlight::new("intel_backlight", &mut fm, "sans", 24.)); - - let widgets: Vec> = - vec![clock, date, battery, backlight, calendar]; - - let layout = Rc::new(Margin::new( - VerticalLayout::new(vec![ - HorizontalLayout::new(vec![ - VerticalLayout::new(vec![ - IndexedLayout::new(1), - IndexedLayout::new(0), - ]), - Margin::new( + let v: Vec> = vec![clock, date, battery, backlight, audio, calendar, launcher]; + let l = Rc::new(Margin::new( + VerticalLayout::new(vec![ + HorizontalLayout::new(vec![ VerticalLayout::new(vec![ - Margin::new(IndexedLayout::new(2), (0, 0, 0, 8)), - Margin::new(IndexedLayout::new(3), (0, 0, 0, 8)), + IndexedLayout::new(1), + IndexedLayout::new(0), ]), - (88, 0, 0, 0), - ), + Margin::new( + VerticalLayout::new(vec![ + Margin::new(IndexedLayout::new(2), (0, 0, 0, 8)), + Margin::new(IndexedLayout::new(3), (0, 0, 0, 8)), + Margin::new(IndexedLayout::new(4), (0, 0, 0, 8)), + ]), + (88, 0, 0, 0), + ), + ]), + IndexedLayout::new(5), + IndexedLayout::new(6), + ]), (20, 20, 20, 20)) + ); + (v, l) + } + UIVersion::V2 => { + let clock = Box::new(Clock::new(&mut fm, "sans", 128.)); + let calendar = Box::new(Calendar::new(&mut fm, "monospace", 12.0, 1, -1)); + let date = Box::new(Date::new(&mut fm, "sans", 48.)); + let line = Box::new(Line::new(1)); + let launcher = Box::new(Interface::new(events.clone(), &mut fm, "monospace", 32.)); + let battery = Box::new(Battery::new(events.clone(), &mut fm, "sans", 24.)); + let backlight = Box::new(Backlight::new("intel_backlight", &mut fm, "sans", 24.)); + let audio = Box::new(Audio::new(events.clone(), &mut fm, "sans", 24.)); + let v: Vec> = vec![clock, date, battery, backlight, audio, line, calendar, launcher]; + let l = Rc::new(VerticalLayout::new(vec![ + HorizontalLayout::new(vec![ + IndexedLayout::new(0), + Margin::new(IndexedLayout::new(1), (16, 16, 0, 0)), + VerticalLayout::new(vec![ + Margin::new(IndexedLayout::new(2), (16, 8, 8, 0)), + Margin::new(IndexedLayout::new(3), (16, 8, 8, 0)), + Margin::new(IndexedLayout::new(4), (16, 8, 8, 0)), + ]), ]), - IndexedLayout::new(4), - //IndexedLayout::new(5), - ]), (20, 20, 20, 20)) - ); - */ + IndexedLayout::new(5), + InvertedHorizontalLayout::new(vec![IndexedLayout::new(6), IndexedLayout::new(7)]), + ])); + (v, l) + } + }; let font_thread = thread::Builder::new() .name("fontloader".to_string()) @@ -157,7 +194,7 @@ fn main() { fm }).unwrap(); - let mut state = State::new(OperationMode::XdgToplevel, widgets, layout, MaybeFontMap::Waiting(font_thread), events); + let mut state = State::new(mode, widgets, layout, MaybeFontMap::Waiting(font_thread), events); let mut damage = Vec::new(); for _ in 0..state.widgets.len() { From 69959e86dca34b23b91c79236e7c31a5a30bd64c Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Sat, 4 Feb 2023 16:52:32 +0100 Subject: [PATCH 13/57] Fix memset hack --- src/state.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/state.rs b/src/state.rs index aa50038..a60343d 100644 --- a/src/state.rs +++ b/src/state.rs @@ -214,7 +214,9 @@ impl Dispatch for State { // is not a particularly good background, but memsetting is slow. On clay, // toplevels that adhere to their requested dimensions only have black behind // them, so we can save the memset in this case, speeding things up. - state.needs_memset = false; + if let OperationMode::XdgToplevel = state.mode { + state.needs_memset = false; + } } _ => {} } From 6f0ca3682625edcc9a831294aca973c0f2a67408 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Mon, 6 Feb 2023 18:34:07 +0100 Subject: [PATCH 14/57] Moar speed --- Cargo.lock | 731 +----------------------- Cargo.toml | 6 +- src/buffer.rs | 31 +- src/color.rs | 23 +- src/draw.rs | 25 +- src/keyboard/mod.rs | 21 +- src/main.rs | 26 +- src/state.rs | 283 ++++++--- src/widgets/alsaaudio.rs | 112 ++++ src/widgets/backlight.rs | 2 +- src/widgets/bar_widget.rs | 5 +- src/widgets/battery.rs | 160 +++--- src/widgets/calendar.rs | 7 +- src/widgets/clock.rs | 2 - src/widgets/date.rs | 2 - src/widgets/launcher.rs | 20 +- src/widgets/mod.rs | 6 +- src/widgets/{audio.rs => pulseaudio.rs} | 16 +- 18 files changed, 497 insertions(+), 981 deletions(-) create mode 100644 src/widgets/alsaaudio.rs rename src/widgets/{audio.rs => pulseaudio.rs} (96%) diff --git a/Cargo.lock b/Cargo.lock index ea685d4..67cdc4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,15 +8,6 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330223a1aecc308757b9926e9391c9b47f8ef2dbd8aea9df88312aea18c5e8d6" -[[package]] -name = "aho-corasick" -version = "0.7.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" -dependencies = [ - "memchr", -] - [[package]] name = "android_system_properties" version = "0.1.5" @@ -26,89 +17,6 @@ dependencies = [ "libc", ] -[[package]] -name = "async-broadcast" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b19760fa2b7301cf235360ffd6d3558b1ed4249edd16d6cca8d690cee265b95" -dependencies = [ - "event-listener", - "futures-core", - "parking_lot", -] - -[[package]] -name = "async-executor" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" -dependencies = [ - "async-lock", - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "slab", -] - -[[package]] -name = "async-io" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" -dependencies = [ - "async-lock", - "autocfg", - "concurrent-queue", - "futures-lite", - "libc", - "log", - "parking", - "polling", - "slab", - "socket2", - "waker-fn", - "windows-sys", -] - -[[package]] -name = "async-lock" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" -dependencies = [ - "event-listener", - "futures-lite", -] - -[[package]] -name = "async-recursion" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b015a331cc64ebd1774ba119538573603427eaace0a1950c423ab971f903796" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "async-task" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" - -[[package]] -name = "async-trait" -version = "0.1.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff18d764974428cf3a9328e23fc5c986f5fbed46e6cd4cdf42544df5d297ec1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -136,18 +44,6 @@ version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" - [[package]] name = "calloop" version = "0.10.5" @@ -198,15 +94,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "concurrent-queue" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "const-cstr" version = "0.3.0" @@ -228,15 +115,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crossbeam-utils" -version = "0.8.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" -dependencies = [ - "cfg-if", -] - [[package]] name = "crypto-common" version = "0.1.6" @@ -292,14 +170,14 @@ dependencies = [ ] [[package]] -name = "derivative" -version = "2.2.0" +name = "dbus" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" dependencies = [ - "proc-macro2", - "quote", - "syn", + "libc", + "libdbus-sys", + "winapi", ] [[package]] @@ -312,26 +190,6 @@ dependencies = [ "crypto-common", ] -[[package]] -name = "dirs" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "dlib" version = "0.5.0" @@ -353,42 +211,6 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" -[[package]] -name = "enumflags2" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" -dependencies = [ - "enumflags2_derive", - "serde", -] - -[[package]] -name = "enumflags2_derive" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58dc3c5e468259f19f2d46304a6b28f1c3d034442e14b322d2b850e36f6d5ae" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "fastrand" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" -dependencies = [ - "instant", -] - [[package]] name = "fontconfig" version = "0.6.0" @@ -398,59 +220,6 @@ dependencies = [ "yeslogic-fontconfig-sys", ] -[[package]] -name = "futures-core" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" - -[[package]] -name = "futures-io" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" - -[[package]] -name = "futures-lite" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - -[[package]] -name = "futures-sink" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" - -[[package]] -name = "futures-task" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" - -[[package]] -name = "futures-util" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" -dependencies = [ - "futures-core", - "futures-sink", - "futures-task", - "pin-project-lite", - "pin-utils", - "slab", -] - [[package]] name = "fuzzy-matcher" version = "0.3.7" @@ -470,23 +239,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "getrandom" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", -] - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - [[package]] name = "iana-time-zone" version = "0.1.53" @@ -511,15 +263,6 @@ dependencies = [ "cxx-build", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - [[package]] name = "io-lifetimes" version = "1.0.3" @@ -551,6 +294,15 @@ version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +[[package]] +name = "libdbus-sys" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f8d7ae751e1cb825c840ae5e682f59b098cdfd213c350ac268b61449a5f58a0" +dependencies = [ + "pkg-config", +] + [[package]] name = "libloading" version = "0.7.4" @@ -597,16 +349,6 @@ dependencies = [ "cc", ] -[[package]] -name = "lock_api" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" version = "0.4.17" @@ -659,18 +401,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "mio" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" -dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", -] - [[package]] name = "nix" version = "0.25.1" @@ -682,7 +412,6 @@ dependencies = [ "cfg-if", "libc", "memoffset 0.6.5", - "pin-utils", ] [[package]] @@ -768,16 +497,6 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" -[[package]] -name = "ordered-stream" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4eb9ba3f3e42dbdd3b7b122de5ca169c81e93d561eb900da3a8c99bcfcf381a" -dependencies = [ - "futures-core", - "pin-project-lite", -] - [[package]] name = "owned_ttf_parser" version = "0.15.2" @@ -787,35 +506,6 @@ dependencies = [ "ttf-parser", ] -[[package]] -name = "parking" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys", -] - [[package]] name = "pest" version = "2.5.2" @@ -860,12 +550,6 @@ dependencies = [ "sha1", ] -[[package]] -name = "pin-project-lite" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" - [[package]] name = "pin-utils" version = "0.1.0" @@ -878,37 +562,6 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" -[[package]] -name = "polling" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6" -dependencies = [ - "autocfg", - "cfg-if", - "libc", - "log", - "wepoll-ffi", - "windows-sys", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "proc-macro-crate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" -dependencies = [ - "once_cell", - "thiserror", - "toml", -] - [[package]] name = "proc-macro2" version = "1.0.47" @@ -936,36 +589,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - [[package]] name = "rcalc_lib" version = "1.0.0" @@ -982,52 +605,6 @@ dependencies = [ "pest_derive", ] -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_users" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" -dependencies = [ - "getrandom", - "redox_syscall", - "thiserror", -] - -[[package]] -name = "regex" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - [[package]] name = "rusttype" version = "0.9.3" @@ -1053,49 +630,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - [[package]] name = "scratch" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" -[[package]] -name = "serde" -version = "1.0.152" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.152" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_repr" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "sha1" version = "0.10.5" @@ -1113,15 +653,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" -[[package]] -name = "slab" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" -dependencies = [ - "autocfg", -] - [[package]] name = "slotmap" version = "1.0.6" @@ -1137,16 +668,6 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" -[[package]] -name = "socket2" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "static_assertions" version = "1.1.0" @@ -1164,20 +685,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "tempfile" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" -dependencies = [ - "cfg-if", - "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", -] - [[package]] name = "termcolor" version = "1.1.3" @@ -1223,79 +730,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi", "winapi", ] -[[package]] -name = "tokio" -version = "1.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb" -dependencies = [ - "autocfg", - "bytes", - "libc", - "memchr", - "mio", - "pin-project-lite", - "socket2", - "tracing", - "windows-sys", -] - -[[package]] -name = "tokio-stream" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - -[[package]] -name = "tracing" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" -dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" -dependencies = [ - "once_cell", -] - [[package]] name = "ttf-parser" version = "0.15.2" @@ -1314,16 +752,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" -[[package]] -name = "uds_windows" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d" -dependencies = [ - "tempfile", - "winapi", -] - [[package]] name = "unicode-ident" version = "1.0.5" @@ -1342,17 +770,6 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" -[[package]] -name = "upower_dbus" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0b1d77de98ab2e5187f5fa2b045b8c4eecfc49e5ccd07f16f4c89f008116f8c" -dependencies = [ - "serde", - "serde_repr", - "zbus", -] - [[package]] name = "vec_map" version = "0.8.2" @@ -1365,12 +782,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - [[package]] name = "walkdir" version = "2.3.2" @@ -1388,12 +799,6 @@ version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - [[package]] name = "wasm-bindgen" version = "0.2.83" @@ -1524,15 +929,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "wepoll-ffi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" -dependencies = [ - "cc", -] - [[package]] name = "winapi" version = "0.3.9" @@ -1628,6 +1024,7 @@ dependencies = [ "bitflags", "calloop", "chrono", + "dbus", "fontconfig", "fuzzy-matcher", "libpulse-binding", @@ -1636,16 +1033,12 @@ dependencies = [ "rcalc_lib", "rusttype", "shlex", - "tokio", - "tokio-stream", "unicode-segmentation", - "upower_dbus", "walkdir", "wayland-client", "wayland-protocols", "wayland-protocols-wlr", "xkbcommon", - "zbus", ] [[package]] @@ -1669,93 +1062,3 @@ dependencies = [ "once_cell", "pkg-config", ] - -[[package]] -name = "zbus" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "379d587c0ccb632d1179cf44082653f682842f0535f0fdfaefffc34849cc855e" -dependencies = [ - "async-broadcast", - "async-executor", - "async-io", - "async-lock", - "async-recursion", - "async-task", - "async-trait", - "byteorder", - "derivative", - "dirs", - "enumflags2", - "event-listener", - "futures-core", - "futures-sink", - "futures-util", - "hex", - "lazy_static", - "nix 0.25.1", - "once_cell", - "ordered-stream", - "rand", - "serde", - "serde_repr", - "sha1", - "static_assertions", - "tokio", - "tracing", - "uds_windows", - "winapi", - "zbus_macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zbus_macros" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66492a2e90c0df7190583eccb8424aa12eb7ff06edea415a4fff6688fae18cf8" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "regex", - "syn", -] - -[[package]] -name = "zbus_names" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34f314916bd89bdb9934154627fab152f4f28acdda03e7c4c68181b214fe7e3" -dependencies = [ - "serde", - "static_assertions", - "zvariant", -] - -[[package]] -name = "zvariant" -version = "3.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "576cc41e65c7f283e5460f5818073e68fb1f1631502b969ef228c2e03c862efb" -dependencies = [ - "byteorder", - "enumflags2", - "libc", - "serde", - "static_assertions", - "zvariant_derive", -] - -[[package]] -name = "zvariant_derive" -version = "3.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fd4aafc0dee96ae7242a24249ce9babf21e1562822f03df650d4e68c20e41ed" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] diff --git a/Cargo.toml b/Cargo.toml index c218727..9580b5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,11 +31,9 @@ fuzzy-matcher = "0.3" shlex = "1.1" xkbcommon = { version = "0.5", features = ["wayland"] } -upower_dbus = "0.3" -zbus = { version = "3.7", default-features = false, features = ["tokio"] } -tokio = { version = "1.24", default-features = false, features = ["rt"] } -tokio-stream = "0.1" rcalc_lib = "1.0" +#alsa = { version = "0.7", optional = true } +dbus = "0.9" libpulse-binding = "2.26" diff --git a/src/buffer.rs b/src/buffer.rs index 4be85fa..07117a8 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -244,31 +244,30 @@ impl<'a> BufferView<'a> { } } - pub fn limit(&mut self, dimensions: (u32, u32)) -> BufferView { - let bounds = self.get_bounds(); - if cfg!(debug_assertions) && (dimensions.0 > bounds.2 || dimensions.1 > bounds.3) { - panic!("cannot create offset outside buffer: {:?} > {:?}", dimensions, bounds); - } - - BufferView { - view: self.view, - dimensions: self.dimensions, - subdimensions: Some((bounds.0, bounds.1, dimensions.0, dimensions.1)), - } - } - pub fn memset(&mut self, c: Color) { if let Some(subdim) = self.subdimensions { for y in subdim.1..(subdim.1 + subdim.3) { - for x in subdim.0..(subdim.0 + subdim.2) { - self.view[(x + y * self.dimensions.0) as usize] = c.0; - } + let start = (subdim.0 + y * self.dimensions.0) as usize; + let end = start + subdim.2 as usize; + self.view[start..end].fill(c.0); } } else { self.view.fill(c.0); } } + pub fn clear(&mut self) { + if let Some(subdim) = self.subdimensions { + for y in subdim.1..(subdim.1 + subdim.3) { + let start = (subdim.0 + y * self.dimensions.0) as usize; + let end = start + subdim.2 as usize; + self.view[start..end].fill(0x0); + } + } else { + self.view.fill(0x0); + } + } + pub fn put(&mut self, pos: (u32, u32), c: Color) { let true_pos = if let Some(subdim) = self.subdimensions { if cfg!(debug_assertions) && (pos.0 >= subdim.2 || pos.1 >= subdim.3) { diff --git a/src/color.rs b/src/color.rs index f911120..88d5989 100644 --- a/src/color.rs +++ b/src/color.rs @@ -75,18 +75,11 @@ fn blend_f32(a: f32, b: f32, r: f32) -> f32 { a + ((b - a) * r) } */ -#[inline] -fn blend_u8(a: u8, b: u8, r: f32) -> u32 { - let af = a as f32; - let bf = b as f32; - (af + ((bf - af) * r)) as u32 -} #[derive(Copy, Clone)] pub struct Color(pub u32); impl Color { - pub const BLACK: Color = Color(0xFF000000); pub const WHITE: Color = Color(0xFFFFFFFF); pub const GREY50: Color = Color(0xFF7F7F7F); pub const GREY75: Color = Color(0xFFBFBFBF); @@ -104,17 +97,17 @@ impl Color { Color((opacity as u32) << 24 | (red as u32) << 16 | (green as u32) << 8 | (blue as u32)) } - fn opacity(self) -> u8 {((self.0 >> 24) & 0xFF) as u8 } fn red(self) -> u8 {((self.0 >> 16) & 0xFF) as u8 } fn green(self) -> u8 {((self.0 >> 8) & 0xFF) as u8 } fn blue(self) -> u8 {(self.0 & 0xFF) as u8 } - pub fn blend(self, other: Color, ratio: f32) -> Color { - Color( - blend_u8(self.opacity(), other.opacity(), ratio) << 24 | - blend_u8(self.red(), other.red(), ratio) << 16 | - blend_u8(self.green(), other.green(), ratio) << 8 | - blend_u8(self.blue(), other.blue(), ratio) - ) + pub fn alpha(self, alpha: f32) -> Color { + unsafe { + Color( + (255. * alpha).to_int_unchecked::() << 24 | + (self.red() as f32 * alpha).to_int_unchecked::() << 16 | + (self.green() as f32 * alpha).to_int_unchecked::() << 8 | + (self.blue() as f32 * alpha).to_int_unchecked::()) + } } } diff --git a/src/draw.rs b/src/draw.rs index 3bafd4a..37b71d8 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -47,7 +47,7 @@ impl CachedGlyph { } } - fn draw(&self, buf: &mut BufferView, pos: (i32, i32), bg: Color, c: Color) { + fn draw(&self, buf: &mut BufferView, pos: (i32, i32), c: Color) { let mut x = 0; let mut y = 0; for v in &self.render { @@ -56,7 +56,7 @@ impl CachedGlyph { (x + pos.0 + self.origin.0) as u32, (y + pos.1 + self.origin.1) as u32, ), - bg.blend(c, *v), + c.alpha(*v) ); if x == self.dimensions.0 as i32 - 1 { @@ -102,7 +102,6 @@ impl<'a> Font<'a> { pub fn draw_text( &self, buf: &mut BufferView, - bg: Color, c: Color, s: &str, ) -> Result<(u32, u32), ::std::io::Error> { @@ -125,7 +124,7 @@ impl<'a> Font<'a> { } } for glyph in glyphs { - glyph.draw(buf, (x_off, -off), bg, c); + glyph.draw(buf, (x_off, -off), c); x_off += glyph.advance; } @@ -135,7 +134,6 @@ impl<'a> Font<'a> { pub fn draw_text_with_cursor( &self, buf: &mut BufferView, - bg: Color, c: Color, s: &str, cursor: usize, @@ -164,7 +162,7 @@ impl<'a> Font<'a> { if i == cursor { self.draw_cursor(buf, c, x_off as u32, height)?; } - glyph.draw(buf, (x_off, -off), bg, c); + glyph.draw(buf, (x_off, -off), c); x_off += glyph.advance; } if cursor == glyphs.len() { @@ -212,30 +210,27 @@ impl<'a> Font<'a> { pub fn auto_draw_text( &mut self, buf: &mut BufferView, - bg: Color, c: Color, s: &str, ) -> Result<(u32, u32), ::std::io::Error> { self.add_str_to_cache(s); - self.draw_text(buf, bg, c, s) + self.draw_text(buf, c, s) } pub fn auto_draw_text_with_cursor( &mut self, buf: &mut BufferView, - bg: Color, c: Color, s: &str, cursor: usize, ) -> Result<(u32, u32), ::std::io::Error> { self.add_str_to_cache(s); - self.draw_text_with_cursor(buf, bg, c, s, cursor) + self.draw_text_with_cursor(buf, c, s, cursor) } pub fn draw_text_fixed_width( &self, buf: &mut BufferView, - bg: Color, c: Color, distances: &[u32], s: &str, @@ -259,7 +254,7 @@ impl<'a> Font<'a> { } } for (idx, glyph) in glyphs.into_iter().enumerate() { - glyph.draw(buf, (x_off, -off), bg, c); + glyph.draw(buf, (x_off, -off), c); x_off += distances[idx] as i32; } @@ -269,7 +264,6 @@ impl<'a> Font<'a> { pub fn draw_text_individual_colors( &self, buf: &mut BufferView, - bg: Color, color: &[Color], s: &str, ) -> Result<(u32, u32), ::std::io::Error> { @@ -292,7 +286,7 @@ impl<'a> Font<'a> { } } for (idx, glyph) in glyphs.into_iter().enumerate() { - glyph.draw(buf, (x_off, -off), bg, color[idx]); + glyph.draw(buf, (x_off, -off), color[idx]); x_off += glyph.advance; } @@ -302,12 +296,11 @@ impl<'a> Font<'a> { pub fn auto_draw_text_individual_colors( &mut self, buf: &mut BufferView, - bg: Color, color: &[Color], s: &str, ) -> Result<(u32, u32), ::std::io::Error> { self.add_str_to_cache(s); - self.draw_text_individual_colors(buf, bg, color, s) + self.draw_text_individual_colors(buf, color, s) } } diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs index 3786d8a..aef408b 100644 --- a/src/keyboard/mod.rs +++ b/src/keyboard/mod.rs @@ -40,20 +40,18 @@ pub struct KeyEvent { pub struct Keyboard { state: Option, + fd: Option<(OwnedFd, u32)>, } impl Keyboard { pub fn new() -> Keyboard { - Keyboard { state: None } + Keyboard { state: None, fd: None, } } pub fn keymap(&mut self, format: WEnum, fd: OwnedFd, size: u32) { match format { WEnum::Value(wl_keyboard::KeymapFormat::XkbV1) => { - self.state = Some( - KbState::new_from_fd(fd.as_raw_fd(), size as usize) - .expect("unable to load keymap"), - ); + self.fd = Some((fd, size)); } WEnum::Value(wl_keyboard::KeymapFormat::NoKeymap) => { // TODO: how to handle this (hopefully never occuring) case? @@ -62,6 +60,19 @@ impl Keyboard { } } + pub fn realize(&mut self) { + if self.state.is_some() { + return; + } + + if let Some((fd, size)) = &self.fd { + self.state = Some( + KbState::new_from_fd(fd.as_raw_fd(), *size as usize) + .expect("unable to load keymap"), + ); + } + } + pub fn key( &mut self, serial: u32, diff --git a/src/main.rs b/src/main.rs index bb25b62..043d357 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,12 +17,11 @@ use calloop::{ use chrono::{Duration, Local, Timelike}; use buffer::BufferView; -use color::Color; use fonts::{FontMap, MaybeFontMap}; use state::{State, OperationMode}; use event::{Event, Events}; use widgets::{ - Audio, Backlight, Battery, Calendar, Clock, Date, Geometry, HorizontalLayout, IndexedLayout, + PulseAudio, Backlight, Battery, Calendar, Clock, Date, Geometry, HorizontalLayout, IndexedLayout, Interface, InvertedHorizontalLayout, Line, Margin, VerticalLayout, Widget, Layout, }; @@ -38,8 +37,6 @@ enum UIVersion { } fn main() { -// let now = Local::now(); - let mut args = env::args(); // Skip program name _ = args.next(); @@ -135,7 +132,7 @@ fn main() { let launcher = Box::new(Interface::new(events.clone(), &mut fm, "monospace", 32.)); let battery = Box::new(Battery::new(events.clone(), &mut fm, "sans", 24.)); let backlight = Box::new(Backlight::new("intel_backlight", &mut fm, "sans", 24.)); - let audio = Box::new(Audio::new(events.clone(), &mut fm, "sans", 24.)); + let audio = Box::new(PulseAudio::new(events.clone(), &mut fm, "sans", 24.)); let v: Vec> = vec![clock, date, battery, backlight, audio, calendar, launcher]; let l = Rc::new(Margin::new( @@ -168,7 +165,7 @@ fn main() { let launcher = Box::new(Interface::new(events.clone(), &mut fm, "monospace", 32.)); let battery = Box::new(Battery::new(events.clone(), &mut fm, "sans", 24.)); let backlight = Box::new(Backlight::new("intel_backlight", &mut fm, "sans", 24.)); - let audio = Box::new(Audio::new(events.clone(), &mut fm, "sans", 24.)); + let audio = Box::new(PulseAudio::new(events.clone(), &mut fm, "sans", 24.)); let v: Vec> = vec![clock, date, battery, backlight, audio, line, calendar, launcher]; let l = Rc::new(VerticalLayout::new(vec![ HorizontalLayout::new(vec![ @@ -232,30 +229,24 @@ fn main() { (state.dimensions.0 as u32, state.dimensions.1 as u32), ); -// eprintln!("time: {}", (Local::now() - now).num_milliseconds()); - - let surface = state.base_surface.as_ref().unwrap().clone(); + let surface = state.main_surface.wl_surface.as_ref().unwrap().clone(); if force { - if state.needs_memset { - bufview.memset(Color::BLACK); - } for (idx, widget) in state.widgets.iter_mut().enumerate() { if force || widget.get_dirty() { let geo = widget.geometry(); let mut subview = bufview.subgeometry(geo); damage[idx] = widget.draw(&mut state.fonts.unwrap().borrow_mut(), &mut subview); - //draw_box(&mut subview, &Color::new(1.0, 0.5, 0.0, 1.0), (geo.width, geo.height)); } } surface.damage_buffer(0, 0, 0x7FFFFFFF, 0x7FFFFFFF) } else { let mut drew = false; for (idx, widget) in state.widgets.iter_mut().enumerate() { - if force || widget.get_dirty() { + if widget.get_dirty() { drew = true; let old_damage = damage[idx]; - bufview.subgeometry(old_damage).memset(Color::BLACK); + bufview.subgeometry(old_damage).clear(); let geo = widget.geometry(); let mut subview = bufview.subgeometry(geo); @@ -271,7 +262,6 @@ fn main() { combined_damage.width as i32, combined_damage.height as i32, ); - //draw_box(&mut subview, &Color::new(1.0, 0.5, 0.0, 1.0), (geo.width, geo.height)); } } if !drew { @@ -282,7 +272,7 @@ fn main() { surface.attach(Some(&buf.buffer), 0, 0); surface.commit(); -// eprintln!("draw time: {}", (Local::now() - now).num_milliseconds()); -// std::process::exit(0); + conn.flush().unwrap(); + state.keyboard.realize(); } } diff --git a/src/state.rs b/src/state.rs index a60343d..5831c38 100644 --- a/src/state.rs +++ b/src/state.rs @@ -3,19 +3,29 @@ use std::{ process::exit, rc::Rc, sync::{Arc, Mutex}, + default::Default, }; use wayland_client::{ protocol::{ - wl_buffer, wl_compositor, wl_keyboard, wl_registry, wl_seat, wl_shm, wl_shm_pool, - wl_surface, wl_pointer, + wl_buffer, wl_compositor, wl_subcompositor, wl_keyboard, wl_registry, wl_seat, wl_shm, wl_shm_pool, + wl_surface, wl_subsurface, wl_pointer, }, Connection, Dispatch, QueueHandle, WEnum, }; -use wayland_protocols::xdg::{ - activation::v1::client::{xdg_activation_token_v1, xdg_activation_v1}, - shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base}, +use wayland_protocols::{ + xdg::{ + activation::v1::client::{xdg_activation_token_v1, xdg_activation_v1}, + shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base}, + }, + wp::{ + single_pixel_buffer::v1::client::wp_single_pixel_buffer_manager_v1, + viewporter::client::{ + wp_viewport, + wp_viewporter, + }, + }, }; use wayland_protocols_wlr::layer_shell::v1::client::{ @@ -30,29 +40,46 @@ use crate::{ event::{Event, Events, PointerEvent, PointerButton}, }; +#[derive(Debug)] pub enum OperationMode { LayerSurface((u32, u32)), XdgToplevel, } +#[derive(Default)] +pub struct MainSurface { + pub wl_surface: Option, + xdg_surface: Option<(xdg_surface::XdgSurface, xdg_toplevel::XdgToplevel)>, + layer_surface: Option, +} + +#[derive(Default)] +pub struct BackgroundSurface { + wl_surface: Option, + subsurface: Option, + viewport: Option, +} + +#[derive(Default)] +pub struct Protocols { + wl_shm: Option, + xdg_activation: Option, + viewporter: Option, +} + pub struct State { mode: OperationMode, + pub main_surface: MainSurface, + pub bg_surface: BackgroundSurface, + pub protocols: Protocols, pub running: bool, pub dirty: bool, activated: bool, - pub needs_memset: bool, - pub base_surface: Option, - layer_shell: Option, - layer_surface: Option, - wm_base: Option, - xdg_surface: Option<(xdg_surface::XdgSurface, xdg_toplevel::XdgToplevel)>, - wl_shm: Option, - xdg_activation: Option, pub configured: bool, pub dimensions: (i32, i32), pub bufmgr: BufferManager, pub widgets: Vec>, - keyboard: Keyboard, + pub keyboard: Keyboard, pointer: Option<(f64, f64)>, pub fonts: MaybeFontMap, pub events: Arc>, @@ -68,17 +95,12 @@ impl State { events: Arc>, ) -> State { State { + protocols: Default::default(), + bg_surface: Default::default(), + main_surface: Default::default(), running: true, dirty: true, activated: false, - needs_memset: true, - base_surface: None, - layer_shell: None, - layer_surface: None, - wm_base: None, - xdg_surface: None, - wl_shm: None, - xdg_activation: None, configured: false, dimensions: (320, 240), bufmgr: BufferManager::new(), @@ -94,7 +116,7 @@ impl State { pub fn add_buffer(&mut self, qh: &QueueHandle) { self.bufmgr.add_buffer( - self.wl_shm.as_ref().expect("missing wl_shm"), + self.protocols.wl_shm.as_ref().expect("missing wl_shm"), self.dimensions, qh, ); @@ -105,7 +127,7 @@ impl State { Ok(token) => token, Err(_) => return, }; - match (&self.base_surface, &self.xdg_activation) { + match (&self.main_surface.wl_surface, &self.protocols.xdg_activation) { (Some(surface), Some(activation)) => { activation.activate(key, surface); let activation_token = activation.get_activation_token(qh, ()); @@ -115,31 +137,6 @@ impl State { _ => (), } } - - fn init_xdg_surface(&mut self, qh: &QueueHandle) { - let wm_base = self.wm_base.as_ref().unwrap(); - let base_surface = self.base_surface.as_ref().unwrap(); - - let xdg_surface = wm_base.get_xdg_surface(base_surface, qh, ()); - let toplevel = xdg_surface.get_toplevel(qh, ()); - toplevel.set_title("wldash".into()); - - base_surface.commit(); - - self.xdg_surface = Some((xdg_surface, toplevel)); - } - - fn init_layer_surface(&mut self, qh: &QueueHandle, size: (u32, u32)) { - let layer_shell = self.layer_shell.as_ref().unwrap(); - let base_surface = self.base_surface.as_ref().unwrap(); - - let layer_surface = layer_shell.get_layer_surface(base_surface, None, zwlr_layer_shell_v1::Layer::Top, "launcher".to_string(), qh, ()); - layer_surface.set_keyboard_interactivity(zwlr_layer_surface_v1::KeyboardInteractivity::Exclusive); - layer_surface.set_size(size.0, size.1); - - base_surface.commit(); - self.layer_surface = Some(layer_surface); - } } impl WidgetUpdater for State { @@ -170,54 +167,88 @@ impl Dispatch for State { "wl_compositor" => { let compositor = registry.bind::(name, 5, qh, ()); - let surface = compositor.create_surface(qh, ()); - state.base_surface = Some(surface); + let main_surface = compositor.create_surface(qh, ()); + let bg_surface = compositor.create_surface(qh, ()); + state.main_surface.wl_surface = Some(main_surface); + state.bg_surface.wl_surface = Some(bg_surface); state.activate(qh); + } + "wl_subcompositor" => { + let subcompositor = + registry.bind::(name, 1, qh, ()); - if state.wm_base.is_some() && state.xdg_surface.is_none() && state.layer_surface.is_none() { - match state.mode { - OperationMode::XdgToplevel => state.init_xdg_surface(qh), - OperationMode::LayerSurface(size) => state.init_layer_surface(qh, size), - } + match (&state.main_surface.wl_surface, &state.bg_surface.wl_surface) { + (Some(parent), Some(child)) => { + let subsurface = subcompositor.get_subsurface(child, parent, qh, ()); + subsurface.place_below(parent); + state.bg_surface.subsurface = Some(subsurface); + }, + _ => todo!("handle early subcompositor creation"), } } "wl_shm" => { - state.wl_shm = Some(registry.bind::(name, 1, qh, ())); + state.protocols.wl_shm = Some(registry.bind::(name, 1, qh, ())); } "wl_seat" => { registry.bind::(name, 8, qh, ()); } "xdg_wm_base" => if let OperationMode::XdgToplevel = state.mode { let wm_base = registry.bind::(name, 2, qh, ()); - state.wm_base = Some(wm_base); - if state.base_surface.is_some() && state.xdg_surface.is_none() { - state.init_xdg_surface(qh); + match &state.main_surface.wl_surface { + Some(surface) => { + let xdg_surface = wm_base.get_xdg_surface(surface, qh, ()); + let toplevel = xdg_surface.get_toplevel(qh, ()); + toplevel.set_title("wldash".into()); + + surface.commit(); + + state.main_surface.xdg_surface = Some((xdg_surface, toplevel)); + } + _ => todo!("handle early xdg_shell creation"), } } "xdg_activation_v1" => { - state.xdg_activation = Some( + state.protocols.xdg_activation = Some( registry.bind::(name, 1, qh, ()), ); state.activate(qh); } "zwlr_layer_shell_v1" => if let OperationMode::LayerSurface(size) = state.mode { - state.layer_shell = Some( - registry.bind::(name, 4, qh, ()), - ); - if state.base_surface.is_some() && state.layer_surface.is_none() { - state.init_layer_surface(qh, size); + let layer_shell = registry.bind::(name, 4, qh, ()); + match &state.main_surface.wl_surface { + Some(surface) => { + let layer_surface = layer_shell.get_layer_surface(surface, None, zwlr_layer_shell_v1::Layer::Top, "launcher".to_string(), qh, ()); + layer_surface.set_keyboard_interactivity(zwlr_layer_surface_v1::KeyboardInteractivity::Exclusive); + layer_surface.set_size(size.0, size.1); + + surface.commit(); + state.main_surface.layer_surface = Some(layer_surface); + } + _ => todo!("handle early layer_shell creation"), } }, - "clay_control_v1" => { - // Buffers are zeroed by default, which is equivalent to zero alpha black. This - // is not a particularly good background, but memsetting is slow. On clay, - // toplevels that adhere to their requested dimensions only have black behind - // them, so we can save the memset in this case, speeding things up. - if let OperationMode::XdgToplevel = state.mode { - state.needs_memset = false; + "wp_viewporter" => { + let viewporter = registry.bind::(name, 1, qh, ()); + match &state.bg_surface.wl_surface { + Some(surface) => { + state.bg_surface.viewport = Some(viewporter.get_viewport(surface, qh, ())); + }, + _ => todo!("handle early viewporter creation"), } - } + state.protocols.viewporter = Some(viewporter); + }, + "wp_single_pixel_buffer_manager_v1" => { + let singlepixel = registry.bind::(name, 1, qh, ()); + match &state.bg_surface.wl_surface { + Some(surface) => { + let buffer = singlepixel.create_u32_rgba_buffer(0, 0, 0, 0xFFFFFFFF, qh, ()); + surface.attach(Some(&buffer), 0, 0); + surface.damage_buffer(0, 0, 1, 1); + }, + _ => todo!("handle early single_pixel_buffer creation"), + } + }, _ => {} } } @@ -236,6 +267,18 @@ impl Dispatch for State { } } +impl Dispatch for State { + fn event( + _: &mut Self, + _: &wl_subcompositor::WlSubcompositor, + _: wl_subcompositor::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + impl Dispatch for State { fn event( _: &mut Self, @@ -248,6 +291,18 @@ impl Dispatch for State { } } +impl Dispatch for State { + fn event( + _: &mut Self, + _: &wl_subsurface::WlSubsurface, + _: wl_subsurface::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + impl Dispatch for State { fn event( _: &mut Self, @@ -317,22 +372,33 @@ impl Dispatch for State { state.configured = true; state.dirty = true; - if state.dimensions != (width as i32, height as i32) { - state.bufmgr.clear_buffers(); - state.dimensions = (width as i32, height as i32); - state.fonts.resolve(); - let fonts = state.fonts.unwrap(); - let layout = state.layout.clone(); - layout.geometry_update( - &mut fonts.borrow_mut(), - &Geometry { - x: 0, - y: 0, - width: width as u32, - height: height as u32, - }, - state, - ); + let dim = (width as i32, height as i32); + + if state.dimensions == dim { + return; + } + state.bufmgr.clear_buffers(); + state.dimensions = dim; + state.fonts.resolve(); + let fonts = state.fonts.unwrap(); + let layout = state.layout.clone(); + layout.geometry_update( + &mut fonts.borrow_mut(), + &Geometry { + x: 0, + y: 0, + width: width as u32, + height: height as u32, + }, + state, + ); + + match (&state.bg_surface.wl_surface, &state.bg_surface.viewport) { + (Some(surface), Some(viewport)) => { + viewport.set_destination(dim.0, dim.1); + surface.commit(); + }, + _ => todo!("handle early layer shell creation"), } } } @@ -496,6 +562,7 @@ impl Dispatch for State { } } +// TODO: Move to dedicated pointer module const BTN_LEFT: u32 = 0x110; const BTN_RIGHT: u32 = 0x111; const BTN_MIDDLE: u32 = 0x112; @@ -574,6 +641,42 @@ impl Dispatch for State { } } +impl Dispatch for State { + fn event( + _: &mut Self, + _: &wp_single_pixel_buffer_manager_v1::WpSinglePixelBufferManagerV1, + _: wp_single_pixel_buffer_manager_v1::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for State { + fn event( + _: &mut Self, + _: &wp_viewporter::WpViewporter, + _: wp_viewporter::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for State { + fn event( + _: &mut Self, + _: &wp_viewport::WpViewport, + _: wp_viewport::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + impl Dispatch for State { fn event( _: &mut Self, diff --git a/src/widgets/alsaaudio.rs b/src/widgets/alsaaudio.rs new file mode 100644 index 0000000..4ca6291 --- /dev/null +++ b/src/widgets/alsaaudio.rs @@ -0,0 +1,112 @@ +use std::{ + thread, + sync::{Arc, Mutex}, +}; + +use crate::{ + color::Color, + fonts::FontMap, + event::{Event, Events, PointerButton}, + widgets::bar_widget::{BarWidget, BarWidgetImpl}, +}; + +use alsa::mixer::{ + Mixer, Selem, SelemChannelId, SelemId, +}; + +#[inline] +fn alsa_volume_to_f32(volume: i64, min: i64, max: i64) -> f32 { + (volume - min) as f32 / (max - min) as f32 +} + +#[inline] +fn f32_to_alsa_volume(volume: f32, min: i64, max: i64) -> i64 { + min + ((max - min) as f32 * volume) as i64 +} + +fn get_master_volume(selem: &mut Selem) -> f32 { + let (min, max) = selem.get_playback_volume_range(); + selem + .get_playback_volume(SelemChannelId::mono()) + .map(|volume| alsa_volume_to_f32(volume, min, max)) + .unwrap() +} + +fn get_mute(selem: &mut Selem) -> bool { + let res = selem.get_playback_switch(SelemChannelId::FrontLeft); + false +} + +fn set_master_volume(selem: &mut Selem, volume: f32) { + let (min, max) = selem.get_playback_volume_range(); + let volume = f32_to_alsa_volume(volume, min, max); + selem.set_playback_volume_all(volume).unwrap(); +} + +fn start_monitor(inner: Arc>) { + thread::Builder::new() + .name("audiomon".to_string()) + .spawn(move || { + let mixer = Mixer::new("default", true).unwrap(); + let id = SelemId::new("Master", 0); + let mut selem = mixer.find_selem(&id).unwrap(); + + let mut inner = inner.lock().unwrap(); + inner.volume = get_master_volume(&mut selem); + inner.mute = get_mute(&mut selem); + }) + .unwrap(); +} + +struct Inner { + volume: f32, + mute: bool, +} + +pub struct AlsaAudio { + inner: Arc>, +} + +impl AlsaAudio { + pub fn new(fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget { + let inner = Arc::new(Mutex::new(Inner{ + mute: false, + volume: 0., + })); + start_monitor(inner.clone()); + let dev = AlsaAudio{ inner }; + BarWidget::new(Box::new(dev), fm, font, size) + } +} + +impl BarWidgetImpl for AlsaAudio { + fn name(&self) -> &'static str { + "audio" + } + fn get_dirty(&self) -> bool { + true + } + fn value(&mut self) -> f32 { + let inner = self.inner.lock().unwrap(); + inner.volume + } + fn color(&self) -> Color { + let inner = self.inner.lock().unwrap(); + if inner.mute { + Color::DARKORANGE + } else { + Color::WHITE + } + } + fn click(&mut self, pos: f32, btn: PointerButton) { + let inner = self.inner.lock().unwrap(); + match btn { +// PointerButton::Left => inner.set_master_volume(pos), + //PointerButton::Right => Some(Change::ToggleMute), + //PointerButton::ScrollVertical(val) => Some(Change::VolumeInc((val / 512.) as f32)), + //PointerButton::ScrollHorizontal(val) => Some(Change::VolumeInc((val / 100.) as f32)), + _ => (), + }; + } + +} diff --git a/src/widgets/backlight.rs b/src/widgets/backlight.rs index 947134d..e13a771 100644 --- a/src/widgets/backlight.rs +++ b/src/widgets/backlight.rs @@ -45,7 +45,7 @@ impl BarWidgetImpl for Backlight { fn name(&self) -> &'static str { "backlight" } - fn value(&self) -> f32 { + fn value(&mut self) -> f32 { if self.cur > self.max { // what. return 1.0; diff --git a/src/widgets/bar_widget.rs b/src/widgets/bar_widget.rs index 2338938..6ee3f39 100644 --- a/src/widgets/bar_widget.rs +++ b/src/widgets/bar_widget.rs @@ -14,7 +14,7 @@ pub trait BarWidgetImpl { false } fn name(&self) -> &'static str; - fn value(&self) -> f32; + fn value(&mut self) -> f32; fn color(&self) -> Color; fn click(&mut self, _pos: f32, _btn: PointerButton) {} } @@ -50,9 +50,8 @@ impl Widget for BarWidget { fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { let font = fonts.get_font(self.font, self.size); let fg = Color::WHITE; - let bg = Color::BLACK; - font.draw_text(view, bg, fg, self.inner_widget.name()) + font.draw_text(view, fg, self.inner_widget.name()) .unwrap(); let size = self.size.ceil() as u32; let bar_offset = 4 * size; diff --git a/src/widgets/battery.rs b/src/widgets/battery.rs index 77209b9..69cca10 100644 --- a/src/widgets/battery.rs +++ b/src/widgets/battery.rs @@ -1,81 +1,111 @@ use std::{ sync::{Arc, Mutex}, thread, - error::Error, + time::Duration, +}; + +use dbus::blocking::{ + stdintf::org_freedesktop_dbus::{Properties, PropertiesPropertiesChanged}, + LocalConnection, }; use crate::{ color::Color, - event::{Event, Events}, fonts::FontMap, + event::{Event, Events}, widgets::bar_widget::{BarWidget, BarWidgetImpl}, }; -use upower_dbus::BatteryState; +enum UpowerBatteryState { + Charging, + Discharging, + Empty, + Full, + NotCharging, + Unknown, +} struct InnerBattery { value: f32, - state: BatteryState, + state: UpowerBatteryState, dirty: bool, } -async fn monitor(inner: Arc>, events: Arc>) -> Result<(), Box> { - use zbus::Connection; - use upower_dbus::UPowerProxy; - use tokio_stream::StreamExt; - - let conn = Connection::system().await?; - let proxy = UPowerProxy::new(&conn).await?; - - let device = proxy.get_display_device().await?; - - let local_inner = inner.clone(); - let local_events = events.clone(); - let mut percent_stream = device.receive_percentage_changed().await; - let a = tokio::spawn(async move { - while let Some(percent) = percent_stream.next().await { - let percent = percent.get().await.unwrap() as f32 / 100.; - let mut inner = local_inner.lock().unwrap(); - inner.value = percent; - inner.dirty = true; - drop(inner); - - let mut events = local_events.lock().unwrap(); - events.add_event(Event::PowerUpdate); - } - }); - - let local_inner = inner.clone(); - let local_events = events.clone(); - let mut state_stream = device.receive_state_changed().await; - let b = tokio::spawn(async move { - while let Some(state) = state_stream.next().await { - let state = state.get().await.unwrap(); - let mut inner = local_inner.lock().unwrap(); - inner.state = state; - inner.dirty = true; - drop(inner); - - let mut events = local_events.lock().unwrap(); - events.add_event(Event::PowerUpdate); - } - }); - a.await?; - b.await?; - - Ok(()) -} - fn start_monitor(inner: Arc>, events: Arc>) { - use tokio::runtime; thread::Builder::new() .name("battmon".to_string()) .spawn(move || { - let rt = runtime::Builder::new_current_thread() - .enable_io() - .build() + let conn = LocalConnection::new_system().unwrap(); + let device_path = "/org/freedesktop/UPower/devices/DisplayDevice"; + let proxy = conn.with_proxy( + "org.freedesktop.UPower", + device_path, + Duration::from_millis(500), + ); + + let capacity: f64 = proxy + .get("org.freedesktop.UPower.Device", "Percentage") + .expect("unable to get property"); + let state: u32 = proxy + .get("org.freedesktop.UPower.Device", "State") + .expect("unable to get state"); + + { + let mut inner = inner.lock().unwrap(); + inner.value = capacity as f32 / 100.; + inner.state = match state { + 1 => UpowerBatteryState::Charging, + 2 => UpowerBatteryState::Discharging, + 3 => UpowerBatteryState::Empty, + 4 => UpowerBatteryState::Full, + 5 => UpowerBatteryState::NotCharging, + 6 => UpowerBatteryState::Discharging, + _ => UpowerBatteryState::Unknown, + } + } + + proxy + .match_signal( + move |c: PropertiesPropertiesChanged, + _: &LocalConnection, + _: &dbus::Message| { + if c.interface_name != "org.freedesktop.UPower.Device" { + return true; + } + let mut inner = inner.lock().unwrap(); + for (key, value) in c.changed_properties { + match key.as_str() { + "State" => { + inner.state = match value.0.as_u64().unwrap() { + 1 => UpowerBatteryState::Charging, + 2 => UpowerBatteryState::Discharging, + 3 => UpowerBatteryState::Empty, + 4 => UpowerBatteryState::Full, + 5 => UpowerBatteryState::NotCharging, + 6 => UpowerBatteryState::Discharging, + _ => UpowerBatteryState::Unknown, + }; + inner.dirty = true; + let mut events = events.lock().unwrap(); + events.add_event(Event::PowerUpdate); + } + "Percentage" => { + inner.value = value.0.as_f64().unwrap() as f32 / 100.; + inner.dirty = true; + let mut events = events.lock().unwrap(); + events.add_event(Event::PowerUpdate); + } + _ => (), + } + } + true + }, + ) .unwrap(); - rt.block_on(async move { monitor(inner, events).await }).unwrap(); + + loop { + conn.process(Duration::from_millis(60000)).unwrap(); + } }) .unwrap(); } @@ -89,7 +119,7 @@ impl Battery { let battery = Battery { inner: Arc::new(Mutex::new(InnerBattery { value: 0., - state: BatteryState::Unknown, + state: UpowerBatteryState::Unknown, dirty: false, })), }; @@ -105,7 +135,7 @@ impl BarWidgetImpl for Battery { fn name(&self) -> &'static str { "battery" } - fn value(&self) -> f32 { + fn value(&mut self) -> f32 { let mut inner = self.inner.lock().unwrap(); inner.dirty = false; inner.value @@ -113,15 +143,11 @@ impl BarWidgetImpl for Battery { fn color(&self) -> Color { let inner = self.inner.lock().unwrap(); match inner.state { - BatteryState::Charging | BatteryState::FullyCharged => Color::LIGHTGREEN, - BatteryState::PendingCharge => Color::LIGHTRED, - _ => if inner.value > 0.25 { - Color::WHITE - } else if inner.value > 0.1 { - Color::DARKORANGE - } else { - Color::RED - } + UpowerBatteryState::Charging | UpowerBatteryState::Full => Color::LIGHTGREEN, + UpowerBatteryState::NotCharging => Color::LIGHTRED, + _ if inner.value > 0.25 => Color::WHITE, + _ if inner.value > 0.1 => Color::DARKORANGE, + _ => Color::RED, } } } diff --git a/src/widgets/calendar.rs b/src/widgets/calendar.rs index 68e3832..79b2d8f 100644 --- a/src/widgets/calendar.rs +++ b/src/widgets/calendar.rs @@ -52,7 +52,6 @@ impl Calendar { orig: NaiveDate, mut time: NaiveDate, ) { - let black = Color::BLACK; let white = Color::WHITE; let dim = Color::GREY80; let dimmer = Color::GREY75; @@ -85,7 +84,6 @@ impl Calendar { y_off * (self.size * LINE_HEIGHT).ceil() as u32 + (self.size * 1.).ceil() as u32, )), - black, white, &wk_chr, ) @@ -100,7 +98,6 @@ impl Calendar { year_font .draw_text( &mut view.offset(((self.size * 17.).ceil() as u32, 0)), - black, dim, &format!("{:}", time.year()), ) @@ -124,7 +121,7 @@ impl Calendar { _ => panic!("impossible value"), }; cal_font - .draw_text(&mut view, black, white, month_str) + .draw_text(&mut view, white, month_str) .unwrap(); let mut done = false; @@ -152,7 +149,6 @@ impl Calendar { y_off * (self.size * LINE_HEIGHT).ceil() as u32 + (self.size * MONTH_FACTOR).ceil() as u32, )), - black, dimmer, &format!("{:02}", wk.week()), ) @@ -177,7 +173,6 @@ impl Calendar { y_off * (self.size * LINE_HEIGHT).ceil() as u32 + (self.size * MONTH_FACTOR).ceil() as u32, )), - black, c, &format!("{:02}", time.day()), ) diff --git a/src/widgets/clock.rs b/src/widgets/clock.rs index 8af1941..074a151 100644 --- a/src/widgets/clock.rs +++ b/src/widgets/clock.rs @@ -54,14 +54,12 @@ impl<'a> Widget for Clock { fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { let time = Local::now().naive_local(); let fg = Color::WHITE; - let bg = Color::BLACK; let digit = self.digit_width; let colon = self.colon_width; let font = fonts.get_font(self.font, self.size); font.draw_text_fixed_width( view, - bg, fg, &[digit, digit, colon, digit, digit], &format!("{:02}:{:02}", time.hour(), time.minute()), diff --git a/src/widgets/date.rs b/src/widgets/date.rs index cf67bc6..ee89797 100644 --- a/src/widgets/date.rs +++ b/src/widgets/date.rs @@ -50,11 +50,9 @@ impl<'a> Widget for Date { fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry { let time = Local::now().naive_local(); let fg = Color::WHITE; - let bg = Color::BLACK; let font = fonts.get_font(self.font, self.size); font.draw_text( view, - bg, fg, &format!( "{}, {:02}/{:02}/{:4}", diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs index bcdadb5..0884327 100644 --- a/src/widgets/launcher.rs +++ b/src/widgets/launcher.rs @@ -287,7 +287,6 @@ impl InterfaceWidget for Launcher { view: &mut BufferView, ) -> Geometry { let fg = Color::WHITE; - let bg = Color::BLACK; let line_height = intf.size.ceil() as u32; @@ -299,14 +298,13 @@ impl InterfaceWidget for Launcher { let mut x_max = font .auto_draw_text_with_cursor( &mut prompt_line, - bg, fg, &format!(" {}", &intf.prompt.input), intf.prompt.cursor + 3, ) .unwrap(); - font.auto_draw_text(&mut prompt_line, bg, fg, ">") + font.auto_draw_text(&mut prompt_line, fg, ">") .unwrap(); // Draw entries @@ -336,13 +334,13 @@ impl InterfaceWidget for Launcher { } x_max = max( x_max, - font.auto_draw_text_individual_colors(&mut line, bg, &colors, &m.name) + font.auto_draw_text_individual_colors(&mut line, &colors, &m.name) .unwrap(), ); } else { x_max = max( x_max, - font.auto_draw_text(&mut line, bg, dimfg, &m.name) + font.auto_draw_text(&mut line, dimfg, &m.name) .unwrap(), ); } @@ -386,7 +384,6 @@ impl InterfaceWidget for Shell { view: &mut BufferView, ) -> Geometry { let fg = Color::WHITE; - let bg = Color::BLACK; let line_height = intf.size.ceil() as u32; @@ -399,13 +396,12 @@ impl InterfaceWidget for Shell { let x_max = font .auto_draw_text_with_cursor( &mut prompt_line, - bg, fg, &format!(" {}", &intf.prompt.input), intf.prompt.cursor + 3, ) .unwrap(); - font.auto_draw_text(&mut prompt_line, bg, Color::BUFF, "!") + font.auto_draw_text(&mut prompt_line, Color::BUFF, "!") .unwrap(); Geometry { @@ -447,7 +443,6 @@ impl InterfaceWidget for Calc { view: &mut BufferView, ) -> Geometry { let fg = Color::WHITE; - let bg = Color::BLACK; let line_height = intf.size.ceil() as u32; @@ -460,20 +455,19 @@ impl InterfaceWidget for Calc { let res_off = font .auto_draw_text_with_cursor( &mut prompt_line, - bg, fg, &format!(" {} ", &intf.prompt.input), intf.prompt.cursor + 3, ) .unwrap(); - font.auto_draw_text(&mut prompt_line, bg, Color::BUFF, "=") + font.auto_draw_text(&mut prompt_line, Color::BUFF, "=") .unwrap(); if let Some(res) = &self.result { let resfg = Color::LIGHTORANGE; let mut result_line = view.offset((res_off.0, prompt_offset)); - font.auto_draw_text(&mut result_line, bg, resfg, &format!(" = {}", &res)) + font.auto_draw_text(&mut result_line, resfg, &format!(" = {}", &res)) .unwrap(); } @@ -484,7 +478,7 @@ impl InterfaceWidget for Calc { } prompt_offset -= line_height; let mut result_line = view.offset((0, prompt_offset)); - font.auto_draw_text(&mut result_line, bg, dimfg, &m) + font.auto_draw_text(&mut result_line, dimfg, &m) .unwrap(); } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index eb74676..6f51598 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,4 +1,5 @@ -mod audio; +//mod alsaaudio; +mod pulseaudio; mod backlight; mod bar_widget; mod battery; @@ -10,7 +11,8 @@ mod line; mod widget; mod layout; -pub use audio::Audio; +//pub use alsaaudio::AlsaAudio; +pub use pulseaudio::PulseAudio; pub use backlight::Backlight; pub use battery::Battery; pub use calendar::Calendar; diff --git a/src/widgets/audio.rs b/src/widgets/pulseaudio.rs similarity index 96% rename from src/widgets/audio.rs rename to src/widgets/pulseaudio.rs index 99c0f3c..b59f952 100644 --- a/src/widgets/audio.rs +++ b/src/widgets/pulseaudio.rs @@ -177,7 +177,7 @@ enum Change { ToggleMute, } -pub struct InnerAudio { +struct InnerAudio { volume: f32, channel_volume: Option, mute: bool, @@ -186,14 +186,14 @@ pub struct InnerAudio { pipe: RawFd, } -pub struct Audio { +pub struct PulseAudio { inner: Arc>, } -impl Audio { +impl PulseAudio { pub fn new(events: Arc>, fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget { let (a, b) = pipe().unwrap(); - let audio = Audio { + let audio = PulseAudio { inner: Arc::new(Mutex::new(InnerAudio{ volume: 0.0, channel_volume: None, @@ -208,15 +208,17 @@ impl Audio { } } -impl BarWidgetImpl for Audio { +impl BarWidgetImpl for PulseAudio { fn get_dirty(&self) -> bool { self.inner.lock().unwrap().dirty } fn name(&self) -> &'static str { "volume" } - fn value(&self) -> f32 { - self.inner.lock().unwrap().volume + fn value(&mut self) -> f32 { + let mut inner = self.inner.lock().unwrap(); + inner.dirty = false; + inner.volume } fn color(&self) -> Color { match self.inner.lock().unwrap().mute { From b9f4e740620a053c3bb3274339320e8cca00f84a Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Mon, 6 Feb 2023 18:52:31 +0100 Subject: [PATCH 15/57] calc: Improve history --- src/main.rs | 3 +-- src/widgets/launcher.rs | 55 +++++++++++++++++++++++++++-------------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/main.rs b/src/main.rs index 043d357..db27d26 100644 --- a/src/main.rs +++ b/src/main.rs @@ -119,11 +119,10 @@ fn main() { let mut fm = FontMap::new(); - // Todo: Cache in a file + // TODO: Cache in a file fm.add_font_path("sans", "/usr/share/fonts/noto/NotoSans-Regular.ttf".to_string()); fm.add_font_path("monospace", "/usr/share/fonts/noto/NotoSansMono-Regular.ttf".to_string()); - let (widgets, layout): (Vec>, Rc>) = match version { UIVersion::V1 => { let clock = Box::new(Clock::new(&mut fm, "sans", 256.)); diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs index 0884327..a1db345 100644 --- a/src/widgets/launcher.rs +++ b/src/widgets/launcher.rs @@ -103,6 +103,11 @@ impl Prompt { } } + fn set(&mut self, v: &str) { + self.input = v.to_string(); + self.cursor = self.input.len(); + } + fn append(&mut self, v: &str) { if self.input.len() == 0 { match v { @@ -188,7 +193,7 @@ impl Prompt { } trait InterfaceWidget { - fn trigger(&mut self, intf: &InnerInterface); + fn trigger(&mut self, intf: &mut InnerInterface); fn draw( &mut self, intf: &InnerInterface, @@ -239,7 +244,7 @@ impl Launcher { } impl InterfaceWidget for Launcher { - fn trigger(&mut self, intf: &InnerInterface) { + fn trigger(&mut self, intf: &mut InnerInterface) { if self.matches.len() > intf.selection { let d = &self.matches[intf.selection]; if let Some(exec) = &d.exec { @@ -362,7 +367,7 @@ impl InterfaceWidget for Launcher { struct Shell(); impl InterfaceWidget for Shell { - fn trigger(&mut self, intf: &InnerInterface) { + fn trigger(&mut self, intf: &mut InnerInterface) { let args = vec![ "/bin/sh".to_string(), "-c".to_string(), @@ -414,14 +419,21 @@ impl InterfaceWidget for Shell { } struct Calc { - old: Vec, + old: Vec<(String, String)>, result: Option, } impl InterfaceWidget for Calc { - fn trigger(&mut self, intf: &InnerInterface) { - if let Some(res) = &self.result { - self.old.push(format!("{} = {}", &intf.prompt.input, res)); + fn trigger(&mut self, intf: &mut InnerInterface) { + if intf.selection == 0 { + if let Some(res) = &self.result { + self.old.push((intf.prompt.input.to_string(), res.to_string())); + } + } else if self.old.len() >= intf.selection { + let (input, res) = &self.old[self.old.len() - intf.selection]; + self.result = Some(res.to_string()); + intf.prompt.set(input); + intf.selection = 0; } } @@ -452,7 +464,7 @@ impl InterfaceWidget for Calc { // Draw prompt let font = fonts.get_font(intf.font, intf.size); - let res_off = font + font .auto_draw_text_with_cursor( &mut prompt_line, fg, @@ -464,27 +476,34 @@ impl InterfaceWidget for Calc { font.auto_draw_text(&mut prompt_line, Color::BUFF, "=") .unwrap(); + prompt_offset -= line_height; if let Some(res) = &self.result { - let resfg = Color::LIGHTORANGE; - let mut result_line = view.offset((res_off.0, prompt_offset)); - font.auto_draw_text(&mut result_line, resfg, &format!(" = {}", &res)) - .unwrap(); + let c = if intf.selection == 0 { + Color::LIGHTORANGE + } else { + Color::GREY50 + }; + let mut result_line = view.offset((0, prompt_offset)); + font.auto_draw_text(&mut result_line, c, res).unwrap(); } - for m in self.old.iter().rev() { - let dimfg = Color::GREY50; + for (idx, (input, res)) in self.old.iter().rev().enumerate() { if prompt_offset < line_height { break; } + let c = if idx + 1 == intf.selection { + Color::GREY75 + } else { + Color::GREY50 + }; prompt_offset -= line_height; let mut result_line = view.offset((0, prompt_offset)); - font.auto_draw_text(&mut result_line, dimfg, &m) - .unwrap(); + font.auto_draw_text(&mut result_line, c, &format!("{} = {}", input, res)).unwrap(); } let content_height = min( intf.geometry.height, - (self.old.len() + 1) as u32 * line_height + 16, + (self.old.len() + 2) as u32 * line_height + 16, ); Geometry { x: intf.geometry.x, @@ -588,7 +607,7 @@ impl Interface { self.inner.selection = 0; widget.update(&self.inner); } - keysyms::XKB_KEY_Return => widget.trigger(&self.inner), + keysyms::XKB_KEY_Return => widget.trigger(&mut self.inner), keysyms::XKB_KEY_Left => self.inner.prompt.move_cursor(-1), keysyms::XKB_KEY_Right => self.inner.prompt.move_cursor(1), keysyms::XKB_KEY_Up => { From 3c69a31030abbfb1368a8ed3fe71ec93fe44ed14 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Tue, 7 Feb 2023 00:36:06 +0100 Subject: [PATCH 16/57] calc: Store history --- src/widgets/launcher.rs | 74 +++++++++++++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 10 deletions(-) diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs index a1db345..19983b7 100644 --- a/src/widgets/launcher.rs +++ b/src/widgets/launcher.rs @@ -1,6 +1,9 @@ use std::{ cmp::{max, min, Ordering}, collections::HashMap, + env, + io::Write, + fs::{read_to_string, File}, default::Default, process::{exit, Command}, sync::{Arc, Mutex}, @@ -419,18 +422,71 @@ impl InterfaceWidget for Shell { } struct Calc { - old: Vec<(String, String)>, + old: Arc>>, result: Option, } +impl Calc { + fn new() -> Calc { + let old: Arc>> = Arc::new(Mutex::new(Vec::new())); + let old_local = old.clone(); + thread::Builder::new() + .name("calc".to_string()) + .spawn(move || { + let home = env::var_os("HOME").unwrap().into_string().unwrap(); + let s = match read_to_string(format!("{}/.cache/wldash/calc", home)) { + Ok(s) => s, + _ => return, + }; + + let mut old = old_local.lock().unwrap(); + for line in s.lines() { + let pos = match line.find("=") { + Some(v) => v, + _ => continue, + }; + let (input, res) = line.split_at(pos); + let (input, res) = (input.trim(), res[1..].trim()); + old.push((input.to_string(), res.to_string())); + } + }) + .unwrap(); + + Calc{ + old, + result: None, + } + } + + fn sync(&self) { + let home = env::var_os("HOME").unwrap().into_string().unwrap(); + let mut f = match File::create(format!("{}/.cache/wldash/calc", home)) { + Ok(f) => f, + _ => return, + }; + + let old = self.old.lock().unwrap(); + for (input, res) in old.iter() { + write!(f, "{}={}\n", input, res).unwrap(); + } + f.sync_data().unwrap(); + } +} + impl InterfaceWidget for Calc { fn trigger(&mut self, intf: &mut InnerInterface) { + let mut old = self.old.lock().unwrap(); if intf.selection == 0 { if let Some(res) = &self.result { - self.old.push((intf.prompt.input.to_string(), res.to_string())); + old.push((intf.prompt.input.to_string(), res.to_string())); + while old.len() > 32 { + old.remove(0); + } + drop(old); + self.sync(); } - } else if self.old.len() >= intf.selection { - let (input, res) = &self.old[self.old.len() - intf.selection]; + } else if old.len() >= intf.selection { + let (input, res) = &old[old.len() - intf.selection]; self.result = Some(res.to_string()); intf.prompt.set(input); intf.selection = 0; @@ -454,6 +510,7 @@ impl InterfaceWidget for Calc { fonts: &mut FontMap, view: &mut BufferView, ) -> Geometry { + let old = self.old.lock().unwrap(); let fg = Color::WHITE; let line_height = intf.size.ceil() as u32; @@ -487,7 +544,7 @@ impl InterfaceWidget for Calc { font.auto_draw_text(&mut result_line, c, res).unwrap(); } - for (idx, (input, res)) in self.old.iter().rev().enumerate() { + for (idx, (input, res)) in old.iter().rev().enumerate() { if prompt_offset < line_height { break; } @@ -503,7 +560,7 @@ impl InterfaceWidget for Calc { let content_height = min( intf.geometry.height, - (self.old.len() + 2) as u32 * line_height + 16, + (old.len() + 2) as u32 * line_height + 16, ); Geometry { x: intf.geometry.x, @@ -540,10 +597,7 @@ impl Interface { Interface { launcher: Launcher::new(events), shell: Shell {}, - calc: Calc { - old: Vec::new(), - result: None, - }, + calc: Calc::new(), inner: InnerInterface { font, size, From 6e90b1f0e82c3cd798a903df134c51487f9adfb1 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Wed, 8 Feb 2023 22:53:41 +0100 Subject: [PATCH 17/57] More better --- src/buffer.rs | 42 +++--- src/color.rs | 119 ++++------------- src/draw.rs | 36 ++--- src/event.rs | 1 - src/fonts.rs | 20 ++- src/keyboard/mod.rs | 5 +- src/main.rs | 68 ++++++---- src/state.rs | 176 ++++++++++++++++--------- src/widgets/bar_widget.rs | 24 +++- src/widgets/battery.rs | 54 ++++---- src/widgets/calendar.rs | 271 +++++++++++++++++++++++--------------- src/widgets/clock.rs | 14 +- src/widgets/date.rs | 13 +- src/widgets/launcher.rs | 59 +++++---- src/widgets/layout.rs | 71 +++++++++- src/widgets/line.rs | 13 +- src/widgets/mod.rs | 13 +- src/widgets/pulseaudio.rs | 177 +++++++++++++------------ src/widgets/widget.rs | 16 +-- 19 files changed, 681 insertions(+), 511 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index 07117a8..e63363a 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -199,15 +199,16 @@ impl<'a> BufferView<'a> { } } - pub fn subdimensions( - &mut self, - subdimensions: (u32, u32, u32, u32), - ) -> BufferView { + pub fn subdimensions(&mut self, subdimensions: (u32, u32, u32, u32)) -> BufferView { let bounds = self.get_bounds(); - if cfg!(debug_assertions) && (subdimensions.0 + subdimensions.2 > bounds.2 - || subdimensions.1 + subdimensions.3 > bounds.3) + if cfg!(debug_assertions) + && (subdimensions.0 + subdimensions.2 > bounds.2 + || subdimensions.1 + subdimensions.3 > bounds.3) { - panic!("cannot create subdimensions larger than buffer: {:?} > {:?}", subdimensions, bounds); + panic!( + "cannot create subdimensions larger than buffer: {:?} > {:?}", + subdimensions, bounds + ); } BufferView { @@ -229,7 +230,10 @@ impl<'a> BufferView<'a> { pub fn offset(&mut self, offset: (u32, u32)) -> BufferView { let bounds = self.get_bounds(); if cfg!(debug_assertions) && (offset.0 > bounds.2 || offset.1 > bounds.3) { - panic!("cannot create offset outside buffer: {:?} > {:?}", offset, bounds); + panic!( + "cannot create offset outside buffer: {:?} > {:?}", + offset, bounds + ); } BufferView { @@ -268,19 +272,15 @@ impl<'a> BufferView<'a> { } } - pub fn put(&mut self, pos: (u32, u32), c: Color) { - let true_pos = if let Some(subdim) = self.subdimensions { - if cfg!(debug_assertions) && (pos.0 >= subdim.2 || pos.1 >= subdim.3) { - panic!("put({:?}) is not within subdimensions of buffer ({:?})", pos, subdim); - } - (pos.0 + subdim.0, pos.1 + subdim.1) - } else { - if cfg!(debug_assertions) && (pos.0 >= self.dimensions.0 || pos.1 >= self.dimensions.1) { - panic!("put({:?}) is not within dimensions of buffer ({:?})", pos, self.dimensions); - } - pos - }; + #[inline] + pub fn put_raw(&mut self, pos: (u32, u32), c: Color) { + self.view[(pos.0 + pos.1 * self.dimensions.0) as usize] = c.0 + } - self.view[(true_pos.0 + (true_pos.1 * self.dimensions.0)) as usize] = c.0; + #[inline] + pub fn put_line_raw(&mut self, pos: (u32, u32), len: u32, c: Color) { + let start = (pos.0 + (pos.1 * self.dimensions.0)) as usize; + let len = len as usize; + self.view[start..start + len].fill(c.0); } } diff --git a/src/color.rs b/src/color.rs index 88d5989..5694ae8 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,113 +1,42 @@ -//use serde::{Deserialize, Serialize}; -/* -//#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)] -pub struct Color { - red: f32, - green: f32, - blue: f32, - opacity: f32, -} - -impl Color { - pub fn new(red: f32, green: f32, blue: f32, opacity: f32) -> Color { - let (red, green, blue, opacity) = clamp(red, green, blue, opacity); - Color { - red, - green, - blue, - opacity, - } - } - - pub fn blend(&self, other: &Color, ratio: f32) -> Color { - let ratio = clamp_f32(ratio, 0.0, 1.0); - - Color { - red: blend_f32(self.red, other.red, ratio), - green: blend_f32(self.green, other.green, ratio), - blue: blend_f32(self.blue, other.blue, ratio), - opacity: blend_f32(self.opacity, other.opacity, ratio), - } - } - - #[inline] - pub fn as_argb8888(&self) -> u32 { - ((255.0 * self.opacity) as u32 & 0xFF) << 24 - | ((255.0 * self.red) as u32 & 0xFF) << 16 - | ((255.0 * self.green) as u32 & 0xFF) << 8 - | ((255.0 * self.blue) as u32 & 0xFF) - } - - #[inline] - pub fn as_integer(&self) -> IntegerColor { - IntegerColor(self.as_argb8888()) - } -} - -#[inline] -fn clamp(r: f32, g: f32, b: f32, o: f32) -> (f32, f32, f32, f32) { - clamp_naive(r, g, b, o) -} - -#[inline] -fn clamp_naive(r: f32, g: f32, b: f32, o: f32) -> (f32, f32, f32, f32) { - ( - clamp_f32(r, 0.0, 1.0), - clamp_f32(g, 0.0, 1.0), - clamp_f32(b, 0.0, 1.0), - clamp_f32(o, 0.0, 1.0), - ) -} - -#[inline] -fn clamp_f32(v: f32, a: f32, b: f32) -> f32 { - if v > b { - b - } else if v < a { - a - } else { - v - } -} - -#[inline] -fn blend_f32(a: f32, b: f32, r: f32) -> f32 { - a + ((b - a) * r) -} -*/ - #[derive(Copy, Clone)] pub struct Color(pub u32); impl Color { - pub const WHITE: Color = Color(0xFFFFFFFF); - pub const GREY50: Color = Color(0xFF7F7F7F); - pub const GREY75: Color = Color(0xFFBFBFBF); - pub const GREY80: Color = Color(0xFFCCCCCC); - pub const RED: Color = Color(0xFFFF0000); - pub const YELLOW: Color = Color(0xFFFFFF00); - pub const LIGHTGREEN: Color = Color(0xFF7FFF7F); - pub const LIGHTRED: Color = Color(0xFFFF7F7F); + pub const WHITE: Color = Color(0xFFFFFFFF); + pub const GREY35: Color = Color(0xFF595959); + pub const GREY50: Color = Color(0xFF7F7F7F); + pub const GREY75: Color = Color(0xFFBFBFBF); + pub const GREY80: Color = Color(0xFFCCCCCC); + pub const RED: Color = Color(0xFFFF0000); + pub const YELLOW: Color = Color(0xFFFFFF00); + pub const LIGHTGREEN: Color = Color(0xFF7FFF7F); pub const LIGHTORANGE: Color = Color(0xFFFFBF00); - pub const DARKORANGE: Color = Color(0xFFFF7F00); - pub const BUFF: Color = Color(0xFFFFBF7F); + pub const DARKORANGE: Color = Color(0xFFFF7F00); + pub const BUFF: Color = Color(0xFFFFBF7F); #[allow(unused)] pub fn new(red: u8, green: u8, blue: u8, opacity: u8) -> Color { Color((opacity as u32) << 24 | (red as u32) << 16 | (green as u32) << 8 | (blue as u32)) } - fn red(self) -> u8 {((self.0 >> 16) & 0xFF) as u8 } - fn green(self) -> u8 {((self.0 >> 8) & 0xFF) as u8 } - fn blue(self) -> u8 {(self.0 & 0xFF) as u8 } + fn red(self) -> u8 { + ((self.0 >> 16) & 0xFF) as u8 + } + fn green(self) -> u8 { + ((self.0 >> 8) & 0xFF) as u8 + } + fn blue(self) -> u8 { + (self.0 & 0xFF) as u8 + } pub fn alpha(self, alpha: f32) -> Color { unsafe { Color( - (255. * alpha).to_int_unchecked::() << 24 | - (self.red() as f32 * alpha).to_int_unchecked::() << 16 | - (self.green() as f32 * alpha).to_int_unchecked::() << 8 | - (self.blue() as f32 * alpha).to_int_unchecked::()) + (255. * alpha).to_int_unchecked::() << 24 + | (self.red() as f32 * alpha).to_int_unchecked::() << 16 + | (self.green() as f32 * alpha).to_int_unchecked::() << 8 + | (self.blue() as f32 * alpha).to_int_unchecked::(), + ) } } } diff --git a/src/draw.rs b/src/draw.rs index 37b71d8..5f46a1d 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -21,7 +21,6 @@ impl CachedGlyph { if let Some(bounding_box) = glyph.pixel_bounding_box() { let origin = (bounding_box.min.x, bounding_box.min.y); - let dimensions = ( (bounding_box.max.x - bounding_box.min.x) as u32, (bounding_box.max.y - bounding_box.min.y) as u32, @@ -50,13 +49,15 @@ impl CachedGlyph { fn draw(&self, buf: &mut BufferView, pos: (i32, i32), c: Color) { let mut x = 0; let mut y = 0; + let bounds = buf.get_bounds(); + let offset = (bounds.0 as i32, bounds.1 as i32); for v in &self.render { - let _ = buf.put( + buf.put_raw( ( - (x + pos.0 + self.origin.0) as u32, - (y + pos.1 + self.origin.1) as u32, + (x + pos.0 + self.origin.0 + offset.0) as u32, + (y + pos.1 + self.origin.1 + offset.1) as u32, ), - c.alpha(*v) + c.alpha(*v), ); if x == self.dimensions.0 as i32 - 1 { @@ -180,8 +181,9 @@ impl<'a> Font<'a> { height: u32, ) -> Result<(), ::std::io::Error> { // draw cursor - for i in 1..height { - buf.put((offset, i), c); + let bounds = buf.get_bounds(); + for i in bounds.1 + 1..height + bounds.1 { + buf.put_raw((bounds.0 + offset, i), c); } Ok(()) } @@ -305,13 +307,12 @@ impl<'a> Font<'a> { } pub fn draw_box(buf: &mut BufferView, c: Color, dim: (u32, u32)) -> Result<(), ::std::io::Error> { - for x in 0..dim.0 { - buf.put((x, 0), c); - buf.put((x, dim.1 - 1), c); - } - for y in 0..dim.1 { - buf.put((0, y), c); - buf.put((dim.0 - 1, y), c); + let bounds = buf.get_bounds(); + buf.put_line_raw((bounds.0, bounds.1), dim.0, c); + buf.put_line_raw((bounds.0, bounds.1 + dim.1 - 1), dim.0, c); + for y in bounds.1..dim.1 + bounds.1 { + buf.put_raw((bounds.0, y), c); + buf.put_raw((bounds.0 + dim.0 - 1, y), c); } Ok(()) @@ -328,10 +329,9 @@ pub fn draw_bar( if fill_pos > length { fill_pos = length; } - for y in 0..height { - for x in 0..fill_pos { - let _ = buf.put((x, y), color); - } + let bounds = buf.get_bounds(); + for y in bounds.1..height + bounds.1 { + buf.put_line_raw((bounds.0, y), fill_pos, color); } Ok(()) diff --git a/src/event.rs b/src/event.rs index eb2bab0..eb0811e 100644 --- a/src/event.rs +++ b/src/event.rs @@ -58,4 +58,3 @@ impl Events { self.events.drain(..).collect() } } - diff --git a/src/fonts.rs b/src/fonts.rs index 29c13a2..308cbe3 100644 --- a/src/fonts.rs +++ b/src/fonts.rs @@ -3,15 +3,7 @@ use crate::draw; use rusttype::Font; use std::{ - cell::RefCell, - collections::HashMap, - fs::File, - hash, - io::Read, - mem, - path::Path, - rc::Rc, - thread, + cell::RefCell, collections::HashMap, fs::File, hash, io::Read, mem, path::Path, rc::Rc, thread, }; /// FontRef is used to store Fonts on widgets. @@ -24,7 +16,12 @@ mod fc { /// Given a string, it matches it to a font file and returns its path. pub(crate) fn from_string(name: &str) -> String { let fc = FontConfig::new().unwrap(); - fc.find(name, None).unwrap().path.to_str().unwrap().to_string() + fc.find(name, None) + .unwrap() + .path + .to_str() + .unwrap() + .to_string() } } @@ -98,7 +95,8 @@ impl FontMap { match self.required_fonts.get_mut(font_name) { Some(v) => v.1.push((size, preload)), None => { - self.required_fonts.insert(font_name, (font_name, vec![(size, preload)])); + self.required_fonts + .insert(font_name, (font_name, vec![(size, preload)])); } } } diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs index aef408b..7281a50 100644 --- a/src/keyboard/mod.rs +++ b/src/keyboard/mod.rs @@ -45,7 +45,10 @@ pub struct Keyboard { impl Keyboard { pub fn new() -> Keyboard { - Keyboard { state: None, fd: None, } + Keyboard { + state: None, + fd: None, + } } pub fn keymap(&mut self, format: WEnum, fd: OwnedFd, size: u32) { diff --git a/src/main.rs b/src/main.rs index db27d26..1d1a619 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,12 @@ mod buffer; mod color; mod draw; +mod event; mod fonts; mod keyboard; mod state; mod utils; mod widgets; -mod event; use wayland_client::{Connection, QueueHandle, WaylandSource}; @@ -17,19 +17,15 @@ use calloop::{ use chrono::{Duration, Local, Timelike}; use buffer::BufferView; -use fonts::{FontMap, MaybeFontMap}; -use state::{State, OperationMode}; use event::{Event, Events}; +use fonts::{FontMap, MaybeFontMap}; +use state::{OperationMode, State}; use widgets::{ - PulseAudio, Backlight, Battery, Calendar, Clock, Date, Geometry, HorizontalLayout, IndexedLayout, - Interface, InvertedHorizontalLayout, Line, Margin, VerticalLayout, Widget, Layout, + Backlight, Battery, Calendar, Clock, Date, Geometry, HorizontalLayout, IndexedLayout, + Interface, InvertedHorizontalLayout, Layout, Line, Margin, PulseAudio, VerticalLayout, Widget, }; -use std::{ - env, - rc::Rc, - thread -}; +use std::{env, rc::Rc, thread}; enum UIVersion { V1, @@ -52,7 +48,7 @@ fn main() { (Some(w), Some(h)) => mode = OperationMode::LayerSurface((w, h)), _ => panic!("invalid arguments"), } - }, + } Some(ref s) if s == "xdg_shell" => mode = OperationMode::XdgToplevel, Some(ref s) if s == "v1" => version = UIVersion::V1, Some(ref s) if s == "v2" => version = UIVersion::V2, @@ -120,27 +116,31 @@ fn main() { let mut fm = FontMap::new(); // TODO: Cache in a file - fm.add_font_path("sans", "/usr/share/fonts/noto/NotoSans-Regular.ttf".to_string()); - fm.add_font_path("monospace", "/usr/share/fonts/noto/NotoSansMono-Regular.ttf".to_string()); + fm.add_font_path( + "sans", + "/usr/share/fonts/noto/NotoSans-Regular.ttf".to_string(), + ); + fm.add_font_path( + "monospace", + "/usr/share/fonts/noto/NotoSansMono-Regular.ttf".to_string(), + ); let (widgets, layout): (Vec>, Rc>) = match version { UIVersion::V1 => { let clock = Box::new(Clock::new(&mut fm, "sans", 256.)); - let calendar = Box::new(Calendar::new(&mut fm, "monospace", 18.0, -1, 1)); + let calendar = Box::new(Calendar::new(&mut fm, "monospace", "sans", 36.0, 3, 1)); let date = Box::new(Date::new(&mut fm, "sans", 48.)); let launcher = Box::new(Interface::new(events.clone(), &mut fm, "monospace", 32.)); let battery = Box::new(Battery::new(events.clone(), &mut fm, "sans", 24.)); - let backlight = Box::new(Backlight::new("intel_backlight", &mut fm, "sans", 24.)); + let backlight = Box::new(Backlight::new("intel_backlight", &mut fm, "sans", 24.)); let audio = Box::new(PulseAudio::new(events.clone(), &mut fm, "sans", 24.)); - let v: Vec> = vec![clock, date, battery, backlight, audio, calendar, launcher]; + let v: Vec> = + vec![clock, date, battery, backlight, audio, calendar, launcher]; let l = Rc::new(Margin::new( VerticalLayout::new(vec![ HorizontalLayout::new(vec![ - VerticalLayout::new(vec![ - IndexedLayout::new(1), - IndexedLayout::new(0), - ]), + VerticalLayout::new(vec![IndexedLayout::new(1), IndexedLayout::new(0)]), Margin::new( VerticalLayout::new(vec![ Margin::new(IndexedLayout::new(2), (0, 0, 0, 8)), @@ -152,20 +152,23 @@ fn main() { ]), IndexedLayout::new(5), IndexedLayout::new(6), - ]), (20, 20, 20, 20)) - ); + ]), + (20, 20, 20, 20), + )); (v, l) } UIVersion::V2 => { let clock = Box::new(Clock::new(&mut fm, "sans", 128.)); - let calendar = Box::new(Calendar::new(&mut fm, "monospace", 12.0, 1, -1)); + let calendar = Box::new(Calendar::new(&mut fm, "monospace", "sans", 24.0, 1, -1)); let date = Box::new(Date::new(&mut fm, "sans", 48.)); let line = Box::new(Line::new(1)); let launcher = Box::new(Interface::new(events.clone(), &mut fm, "monospace", 32.)); let battery = Box::new(Battery::new(events.clone(), &mut fm, "sans", 24.)); - let backlight = Box::new(Backlight::new("intel_backlight", &mut fm, "sans", 24.)); + let backlight = Box::new(Backlight::new("intel_backlight", &mut fm, "sans", 24.)); let audio = Box::new(PulseAudio::new(events.clone(), &mut fm, "sans", 24.)); - let v: Vec> = vec![clock, date, battery, backlight, audio, line, calendar, launcher]; + let v: Vec> = vec![ + clock, date, battery, backlight, audio, line, calendar, launcher, + ]; let l = Rc::new(VerticalLayout::new(vec![ HorizontalLayout::new(vec![ IndexedLayout::new(0), @@ -186,11 +189,18 @@ fn main() { let font_thread = thread::Builder::new() .name("fontloader".to_string()) .spawn(move || { - fm.load_fonts(); - fm - }).unwrap(); + fm.load_fonts(); + fm + }) + .unwrap(); - let mut state = State::new(mode, widgets, layout, MaybeFontMap::Waiting(font_thread), events); + let mut state = State::new( + mode, + widgets, + layout, + MaybeFontMap::Waiting(font_thread), + events, + ); let mut damage = Vec::new(); for _ in 0..state.widgets.len() { diff --git a/src/state.rs b/src/state.rs index 5831c38..d8e0b32 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,43 +1,39 @@ use std::{ + cmp::max, + default::Default, env, process::exit, rc::Rc, sync::{Arc, Mutex}, - default::Default, }; use wayland_client::{ protocol::{ - wl_buffer, wl_compositor, wl_subcompositor, wl_keyboard, wl_registry, wl_seat, wl_shm, wl_shm_pool, - wl_surface, wl_subsurface, wl_pointer, + wl_buffer, wl_compositor, wl_keyboard, wl_pointer, wl_registry, wl_seat, wl_shm, + wl_shm_pool, wl_subcompositor, wl_subsurface, wl_surface, }, Connection, Dispatch, QueueHandle, WEnum, }; use wayland_protocols::{ + wp::{ + single_pixel_buffer::v1::client::wp_single_pixel_buffer_manager_v1, + viewporter::client::{wp_viewport, wp_viewporter}, + }, xdg::{ activation::v1::client::{xdg_activation_token_v1, xdg_activation_v1}, shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base}, }, - wp::{ - single_pixel_buffer::v1::client::wp_single_pixel_buffer_manager_v1, - viewporter::client::{ - wp_viewport, - wp_viewporter, - }, - }, }; -use wayland_protocols_wlr::layer_shell::v1::client::{ - zwlr_layer_shell_v1, zwlr_layer_surface_v1, -}; +use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1}; use crate::{ buffer::BufferManager, + event::{Event, Events, PointerButton, PointerEvent}, fonts::{FontMap, MaybeFontMap}, keyboard::Keyboard, widgets::{Geometry, Layout, Widget, WidgetUpdater}, - event::{Event, Events, PointerEvent, PointerButton}, }; #[derive(Debug)] @@ -127,7 +123,10 @@ impl State { Ok(token) => token, Err(_) => return, }; - match (&self.main_surface.wl_surface, &self.protocols.xdg_activation) { + match ( + &self.main_surface.wl_surface, + &self.protocols.xdg_activation, + ) { (Some(surface), Some(activation)) => { activation.activate(key, surface); let activation_token = activation.get_activation_token(qh, ()); @@ -148,6 +147,10 @@ impl WidgetUpdater for State { ) -> Geometry { self.widgets[idx].geometry_update(fonts, geometry) } + + fn minimum_size(&mut self, idx: usize, fonts: &mut FontMap) -> Geometry { + self.widgets[idx].minimum_size(fonts) + } } impl Dispatch for State { @@ -182,30 +185,34 @@ impl Dispatch for State { let subsurface = subcompositor.get_subsurface(child, parent, qh, ()); subsurface.place_below(parent); state.bg_surface.subsurface = Some(subsurface); - }, + } _ => todo!("handle early subcompositor creation"), } } "wl_shm" => { - state.protocols.wl_shm = Some(registry.bind::(name, 1, qh, ())); + state.protocols.wl_shm = + Some(registry.bind::(name, 1, qh, ())); } "wl_seat" => { registry.bind::(name, 8, qh, ()); } - "xdg_wm_base" => if let OperationMode::XdgToplevel = state.mode { - let wm_base = registry.bind::(name, 2, qh, ()); + "xdg_wm_base" => { + if let OperationMode::XdgToplevel = state.mode { + let wm_base = + registry.bind::(name, 2, qh, ()); - match &state.main_surface.wl_surface { - Some(surface) => { - let xdg_surface = wm_base.get_xdg_surface(surface, qh, ()); - let toplevel = xdg_surface.get_toplevel(qh, ()); - toplevel.set_title("wldash".into()); + match &state.main_surface.wl_surface { + Some(surface) => { + let xdg_surface = wm_base.get_xdg_surface(surface, qh, ()); + let toplevel = xdg_surface.get_toplevel(qh, ()); + toplevel.set_title("wldash".into()); - surface.commit(); + surface.commit(); - state.main_surface.xdg_surface = Some((xdg_surface, toplevel)); + state.main_surface.xdg_surface = Some((xdg_surface, toplevel)); + } + _ => todo!("handle early xdg_shell creation"), } - _ => todo!("handle early xdg_shell creation"), } } "xdg_activation_v1" => { @@ -214,41 +221,63 @@ impl Dispatch for State { ); state.activate(qh); } - "zwlr_layer_shell_v1" => if let OperationMode::LayerSurface(size) = state.mode { - let layer_shell = registry.bind::(name, 4, qh, ()); - match &state.main_surface.wl_surface { - Some(surface) => { - let layer_surface = layer_shell.get_layer_surface(surface, None, zwlr_layer_shell_v1::Layer::Top, "launcher".to_string(), qh, ()); - layer_surface.set_keyboard_interactivity(zwlr_layer_surface_v1::KeyboardInteractivity::Exclusive); - layer_surface.set_size(size.0, size.1); + "zwlr_layer_shell_v1" => { + if let OperationMode::LayerSurface(size) = state.mode { + let layer_shell = registry + .bind::(name, 4, qh, ()); + state.fonts.resolve(); + let fonts = state.fonts.unwrap(); + let layout = state.layout.clone(); + let min_size = layout.minimum_size(&mut fonts.borrow_mut(), state); + match &state.main_surface.wl_surface { + Some(surface) => { + let layer_surface = layer_shell.get_layer_surface( + surface, + None, + zwlr_layer_shell_v1::Layer::Top, + "launcher".to_string(), + qh, + (), + ); + layer_surface.set_keyboard_interactivity( + zwlr_layer_surface_v1::KeyboardInteractivity::Exclusive, + ); + layer_surface.set_size( + max(size.0, min_size.width), + max(size.1, min_size.height), + ); - surface.commit(); - state.main_surface.layer_surface = Some(layer_surface); + surface.commit(); + state.main_surface.layer_surface = Some(layer_surface); + } + _ => todo!("handle early layer_shell creation"), } - _ => todo!("handle early layer_shell creation"), } - }, + } "wp_viewporter" => { - let viewporter = registry.bind::(name, 1, qh, ()); + let viewporter = + registry.bind::(name, 1, qh, ()); match &state.bg_surface.wl_surface { Some(surface) => { - state.bg_surface.viewport = Some(viewporter.get_viewport(surface, qh, ())); - }, + state.bg_surface.viewport = + Some(viewporter.get_viewport(surface, qh, ())); + } _ => todo!("handle early viewporter creation"), } state.protocols.viewporter = Some(viewporter); - }, + } "wp_single_pixel_buffer_manager_v1" => { let singlepixel = registry.bind::(name, 1, qh, ()); match &state.bg_surface.wl_surface { Some(surface) => { - let buffer = singlepixel.create_u32_rgba_buffer(0, 0, 0, 0xFFFFFFFF, qh, ()); + let buffer = + singlepixel.create_u32_rgba_buffer(0, 0, 0, 0xFFFFFFFF, qh, ()); surface.attach(Some(&buffer), 0, 0); surface.damage_buffer(0, 0, 1, 1); - }, + } _ => todo!("handle early single_pixel_buffer creation"), } - }, + } _ => {} } } @@ -352,7 +381,8 @@ impl Dispatch for State { _: &(), _: &Connection, _: &QueueHandle, - ) {} + ) { + } } impl Dispatch for State { @@ -364,7 +394,12 @@ impl Dispatch for State { _: &Connection, _: &QueueHandle, ) { - if let zwlr_layer_surface_v1::Event::Configure { serial, width, height } = event { + if let zwlr_layer_surface_v1::Event::Configure { + serial, + width, + height, + } = event + { if (width, height) == (0, 0) { return; } @@ -397,7 +432,7 @@ impl Dispatch for State { (Some(surface), Some(viewport)) => { viewport.set_destination(dim.0, dim.1); surface.commit(); - }, + } _ => todo!("handle early layer shell creation"), } } @@ -577,20 +612,30 @@ impl Dispatch for State { _: &QueueHandle, ) { match event { - wl_pointer::Event::Enter { surface_x, surface_y, .. } => { + wl_pointer::Event::Enter { + surface_x, + surface_y, + .. + } => { state.pointer = Some((surface_x, surface_y)); - }, - wl_pointer::Event::Motion { surface_x, surface_y, .. } => { + } + wl_pointer::Event::Motion { + surface_x, + surface_y, + .. + } => { state.pointer = Some((surface_x, surface_y)); - }, - wl_pointer::Event::Leave { .. } => { - state.pointer = None - }, - wl_pointer::Event::Axis { axis, value, .. } => match state.pointer { + } + wl_pointer::Event::Leave { .. } => state.pointer = None, + wl_pointer::Event::Axis { axis, value, .. } => match state.pointer { Some((x, y)) => { let axis = match axis { - WEnum::Value(wl_pointer::Axis::VerticalScroll) => PointerButton::ScrollVertical(value), - WEnum::Value(wl_pointer::Axis::HorizontalScroll) => PointerButton::ScrollHorizontal(value), + WEnum::Value(wl_pointer::Axis::VerticalScroll) => { + PointerButton::ScrollVertical(value) + } + WEnum::Value(wl_pointer::Axis::HorizontalScroll) => { + PointerButton::ScrollHorizontal(value) + } _ => return, }; let pos = (x as u32, y as u32); @@ -598,7 +643,7 @@ impl Dispatch for State { let geo = widget.geometry(); if geo.contains(pos) { let pos = (pos.0 - geo.x, pos.1 - geo.y); - let ev = Event::PointerEvent(PointerEvent{ + let ev = Event::PointerEvent(PointerEvent { button: axis, pos: pos, }); @@ -607,10 +652,14 @@ impl Dispatch for State { break; } } - }, + } _ => (), }, - wl_pointer::Event::Button { button, state: button_state, .. } => match (button, button_state, state.pointer) { + wl_pointer::Event::Button { + button, + state: button_state, + .. + } => match (button, button_state, state.pointer) { (button, WEnum::Value(wl_pointer::ButtonState::Pressed), Some((x, y))) => { let button = match button { BTN_LEFT => PointerButton::Left, @@ -622,9 +671,8 @@ impl Dispatch for State { for widget in state.widgets.iter_mut() { let geo = widget.geometry(); if geo.contains(pos) { - let pos = (pos.0 - geo.x, pos.1 - geo.y); - let ev = Event::PointerEvent(PointerEvent{ + let ev = Event::PointerEvent(PointerEvent { button: button, pos: pos, }); @@ -633,7 +681,7 @@ impl Dispatch for State { break; } } - }, + } _ => (), }, _ => (), diff --git a/src/widgets/bar_widget.rs b/src/widgets/bar_widget.rs index 6ee3f39..a4adc37 100644 --- a/src/widgets/bar_widget.rs +++ b/src/widgets/bar_widget.rs @@ -4,9 +4,9 @@ use crate::{ buffer::BufferView, color::Color, draw::{draw_bar, draw_box}, + event::{Event, PointerButton}, fonts::FontMap, widgets::{Geometry, Widget}, - event::{Event, PointerButton}, }; pub trait BarWidgetImpl { @@ -27,7 +27,12 @@ pub struct BarWidget { } impl BarWidget { - pub fn new(inner_widget: Box, fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget { + pub fn new( + inner_widget: Box, + fm: &mut FontMap, + font: &'static str, + size: f32, + ) -> BarWidget { fm.queue_font(font, size, inner_widget.name()); BarWidget { geometry: Default::default(), @@ -51,8 +56,7 @@ impl Widget for BarWidget { let font = fonts.get_font(self.font, self.size); let fg = Color::WHITE; - font.draw_text(view, fg, self.inner_widget.name()) - .unwrap(); + font.draw_text(view, fg, self.inner_widget.name()).unwrap(); let size = self.size.ceil() as u32; let bar_offset = 4 * size; let val = self.inner_widget.value(); @@ -91,6 +95,15 @@ impl Widget for BarWidget { self.geometry } + fn minimum_size(&mut self, _fonts: &mut FontMap) -> Geometry { + Geometry { + x: 0, + y: 0, + width: self.size.ceil() as u32 * 6, + height: self.size.ceil() as u32, + } + } + fn event(&mut self, event: &Event) { match event { Event::PointerEvent(ev) => { @@ -99,9 +112,8 @@ impl Widget for BarWidget { let val = (ev.pos.0 - offset) as f32 / (self.geometry.width - offset) as f32; self.inner_widget.click(val, ev.button); } - }, + } _ => (), } } - } diff --git a/src/widgets/battery.rs b/src/widgets/battery.rs index 69cca10..53c1dc3 100644 --- a/src/widgets/battery.rs +++ b/src/widgets/battery.rs @@ -11,18 +11,33 @@ use dbus::blocking::{ use crate::{ color::Color, - fonts::FontMap, event::{Event, Events}, + fonts::FontMap, widgets::bar_widget::{BarWidget, BarWidgetImpl}, }; enum UpowerBatteryState { + Unknown, Charging, Discharging, Empty, - Full, - NotCharging, - Unknown, + FullyCharged, + PendingCharge, + PendingDischarge, +} + +impl UpowerBatteryState { + fn from_dbus(val: u64) -> UpowerBatteryState { + match val { + 1 => UpowerBatteryState::Charging, + 2 => UpowerBatteryState::Discharging, + 3 => UpowerBatteryState::Empty, + 4 => UpowerBatteryState::FullyCharged, + 5 => UpowerBatteryState::PendingCharge, + 6 => UpowerBatteryState::PendingDischarge, + _ => UpowerBatteryState::Unknown, + } + } } struct InnerBattery { @@ -53,15 +68,7 @@ fn start_monitor(inner: Arc>, events: Arc>) { { let mut inner = inner.lock().unwrap(); inner.value = capacity as f32 / 100.; - inner.state = match state { - 1 => UpowerBatteryState::Charging, - 2 => UpowerBatteryState::Discharging, - 3 => UpowerBatteryState::Empty, - 4 => UpowerBatteryState::Full, - 5 => UpowerBatteryState::NotCharging, - 6 => UpowerBatteryState::Discharging, - _ => UpowerBatteryState::Unknown, - } + inner.state = UpowerBatteryState::from_dbus(state as u64); } proxy @@ -76,15 +83,8 @@ fn start_monitor(inner: Arc>, events: Arc>) { for (key, value) in c.changed_properties { match key.as_str() { "State" => { - inner.state = match value.0.as_u64().unwrap() { - 1 => UpowerBatteryState::Charging, - 2 => UpowerBatteryState::Discharging, - 3 => UpowerBatteryState::Empty, - 4 => UpowerBatteryState::Full, - 5 => UpowerBatteryState::NotCharging, - 6 => UpowerBatteryState::Discharging, - _ => UpowerBatteryState::Unknown, - }; + inner.state = + UpowerBatteryState::from_dbus(value.0.as_u64().unwrap()); inner.dirty = true; let mut events = events.lock().unwrap(); events.add_event(Event::PowerUpdate); @@ -115,7 +115,12 @@ pub struct Battery { } impl Battery { - pub fn new(events: Arc>, fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget { + pub fn new( + events: Arc>, + fm: &mut FontMap, + font: &'static str, + size: f32, + ) -> BarWidget { let battery = Battery { inner: Arc::new(Mutex::new(InnerBattery { value: 0., @@ -143,8 +148,7 @@ impl BarWidgetImpl for Battery { fn color(&self) -> Color { let inner = self.inner.lock().unwrap(); match inner.state { - UpowerBatteryState::Charging | UpowerBatteryState::Full => Color::LIGHTGREEN, - UpowerBatteryState::NotCharging => Color::LIGHTRED, + UpowerBatteryState::Charging | UpowerBatteryState::FullyCharged => Color::LIGHTGREEN, _ if inner.value > 0.25 => Color::WHITE, _ if inner.value > 0.1 => Color::DARKORANGE, _ => Color::RED, diff --git a/src/widgets/calendar.rs b/src/widgets/calendar.rs index 79b2d8f..ef9b73e 100644 --- a/src/widgets/calendar.rs +++ b/src/widgets/calendar.rs @@ -3,8 +3,8 @@ use std::default::Default; use crate::{ buffer::BufferView, color::Color, - fonts::FontMap, event::Event, + fonts::FontMap, widgets::{Geometry, Widget}, }; @@ -12,6 +12,7 @@ use chrono::{Datelike, Local, NaiveDate}; pub struct Calendar { font: &'static str, + month_font: &'static str, sections_x: i32, sections_y: i32, shown_sections_x: u32, @@ -19,22 +20,35 @@ pub struct Calendar { size: f32, dirty: bool, geometry: Geometry, + year_width: u32, + date_width: u32, + day_width: u32, } -const DAY_FACTOR: f32 = 1.; -const YEAR_FACTOR: f32 = 1.5; -const MONTH_FACTOR: f32 = 2.; -const DATE_FACTOR: f32 = 2.; -const LINE_HEIGHT: f32 = 1.8; +const DAY_FACTOR: f32 = 0.6; +const YEAR_FACTOR: f32 = 0.75; +const MONTH_FACTOR: f32 = 1.25; +const DATE_FACTOR: f32 = 1.; + +const LINE_HEIGHT: f32 = 0.85; impl Calendar { - pub fn new(fm: &mut FontMap, font: &'static str, size: f32, sections_x: i32, sections_y: i32) -> Calendar { - fm.queue_font(font, size, "ADEFHIMNORSTUW"); - fm.queue_font(font, size * 1.5, "0123456789"); - fm.queue_font(font, size * 2.0, "0123456789 ADFJMOSabcehgilmnoprstuvy"); + pub fn new( + fm: &mut FontMap, + font: &'static str, + month_font: &'static str, + size: f32, + sections_x: i32, + sections_y: i32, + ) -> Calendar { + fm.queue_font(font, size * DAY_FACTOR, "ADEFHIMNORSTUW"); + fm.queue_font(font, size * YEAR_FACTOR, "0123456789"); + fm.queue_font(font, size * DATE_FACTOR, "0123456789"); + fm.queue_font(month_font, size * MONTH_FACTOR, "ADFJMOSabcehgilmnoprstuvy"); Calendar { font, + month_font, size, sections_x, sections_y, @@ -42,6 +56,9 @@ impl Calendar { shown_sections_y: 0, dirty: true, geometry: Default::default(), + date_width: 0, + day_width: 0, + year_width: 0, } } @@ -52,39 +69,33 @@ impl Calendar { orig: NaiveDate, mut time: NaiveDate, ) { - let white = Color::WHITE; - let dim = Color::GREY80; - let dimmer = Color::GREY75; - - let mut y_off = 1; - // TODO: Fix offsets: - // - Headline font is not correctly accounted for - + let cal_width = 8 * self.date_width + 7 * (self.date_width / 2); // // Draw the week day // + let day_offset = (self.date_width - self.day_width) / 2; + let date_offset = self.date_width + self.date_width / 2; for idx in 1..8 { let day_font = fonts.get_font(self.font, self.size * DAY_FACTOR); - let wk_chr = match idx { - 1 => "MON", - 2 => "TUE", - 3 => "WED", - 4 => "THU", - 5 => "FRI", - 6 => "SAT", - 7 => "SUN", + let (wk_chr, c) = match idx { + 1 => ("MON", Color::WHITE), + 2 => ("TUE", Color::WHITE), + 3 => ("WED", Color::WHITE), + 4 => ("THU", Color::WHITE), + 5 => ("FRI", Color::WHITE), + 6 => ("SAT", Color::GREY80), + 7 => ("SUN", Color::GREY80), _ => panic!("impossible value"), }; day_font .draw_text( - &mut view - .offset(( - idx * (self.size * 2.5).ceil() as u32 + (self.size / 5.).ceil() as u32, - y_off * (self.size * LINE_HEIGHT).ceil() as u32 - + (self.size * 1.).ceil() as u32, - )), - white, + &mut view.offset(( + idx * date_offset + day_offset, + (self.size * MONTH_FACTOR).ceil() as u32 + + (self.size * DAY_FACTOR * 0.2).ceil() as u32, + )), + c, &wk_chr, ) .unwrap(); @@ -93,18 +104,7 @@ impl Calendar { // // Draw the month // - if time.year() != orig.year() { - let year_font = fonts.get_font(self.font, self.size * YEAR_FACTOR); - year_font - .draw_text( - &mut view.offset(((self.size * 17.).ceil() as u32, 0)), - dim, - &format!("{:}", time.year()), - ) - .unwrap(); - } - - let cal_font = fonts.get_font(self.font, self.size * DATE_FACTOR); + let month_font = fonts.get_font(self.month_font, self.size * MONTH_FACTOR); let month_str = match time.month() { 1 => "January", 2 => "February", @@ -120,59 +120,62 @@ impl Calendar { 12 => "December", _ => panic!("impossible value"), }; - cal_font - .draw_text(&mut view, white, month_str) + month_font + .draw_text(&mut view, Color::WHITE, month_str) .unwrap(); - let mut done = false; - while !done { - let mut x_pos = 0; - - // - // Find the start of this week - // - let mut wkday = time.weekday(); - while wkday != chrono::Weekday::Mon { - x_pos += 1; - wkday = wkday.pred(); - } + // + // Draw the year + // + if time.year() != orig.year() { + let year_font = fonts.get_font(self.font, self.size * YEAR_FACTOR); + year_font + .draw_text( + &mut view.offset((cal_width - self.year_width, 0)), + Color::GREY80, + &format!("{:}", time.year()), + ) + .unwrap(); + } + let cal_font = fonts.get_font(self.font, self.size * DATE_FACTOR); + 'row: for y_off in 1.. { // // Draw the week number // let wk = time.iso_week(); cal_font .draw_text( - &mut view - .offset(( - 0, - y_off * (self.size * LINE_HEIGHT).ceil() as u32 - + (self.size * MONTH_FACTOR).ceil() as u32, - )), - dimmer, + &mut view.offset(( + 0, + y_off * (self.size * LINE_HEIGHT).ceil() as u32 + + (self.size * MONTH_FACTOR).ceil() as u32, + )), + Color::GREY75, &format!("{:02}", wk.week()), ) .unwrap(); - x_pos += 1; // // Draw the dates // - while x_pos < 8 { + let x_off = time.weekday().number_from_monday(); + for x_pos in x_off..8 { let c = if time.day() == orig.day() && time.month() == orig.month() { Color::WHITE - } else { + } else if x_pos < 6 { Color::GREY50 + } else { + Color::GREY35 }; cal_font .draw_text( - &mut view - .offset(( - x_pos * (self.size * 2.5).ceil() as u32, - y_off * (self.size * LINE_HEIGHT).ceil() as u32 - + (self.size * MONTH_FACTOR).ceil() as u32, - )), + &mut view.offset(( + x_pos * date_offset, + y_off * (self.size * LINE_HEIGHT).ceil() as u32 + + (self.size * MONTH_FACTOR).ceil() as u32, + )), c, &format!("{:02}", time.day()), ) @@ -180,14 +183,10 @@ impl Calendar { let t = time.with_day(time.day() + 1); if t.is_none() { - done = true; - break; + break 'row; } time = t.unwrap(); - x_pos += 1; } - - y_off += 1; } } } @@ -222,9 +221,10 @@ impl<'a> Widget for Calendar { let time = Local::now().naive_local().date(); - let cal_height = (self.size * 16.25).ceil() as u32; - let cal_width = (7. * self.size * 2.5 + self.size * 2.).ceil() as u32; - let cal_pad = (3. * self.size).ceil() as u32; + let cal_height = + (self.size * MONTH_FACTOR).ceil() as u32 + (self.size * LINE_HEIGHT * 8.).ceil() as u32; + let cal_width = 8 * self.date_width + 7 * (self.date_width / 2); + let cal_pad = self.date_width * 2; let mut t = time.with_day(1).unwrap(); let cals = self.shown_sections_y * self.shown_sections_x; @@ -236,8 +236,7 @@ impl<'a> Widget for Calendar { for idx in 0..self.shown_sections_x { self.draw_month( fonts, - &mut view - .offset(((cal_width + cal_pad) * idx, (cal_height) * ydx)), + &mut view.offset(((cal_width + cal_pad) * idx, cal_height * ydx)), time, t, ); @@ -252,35 +251,91 @@ impl<'a> Widget for Calendar { self.geometry } - fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry { - let cal_width = 7 * (self.size * 2.5).ceil() as u32 + (self.size * 2.).ceil() as u32; - let cal_height = (self.size * MONTH_FACTOR).ceil() as u32 - + (self.size * LINE_HEIGHT * 8.).ceil() as u32; - let cal_pad = (self.size * 3.).ceil() as u32; + fn geometry_update(&mut self, fonts: &mut FontMap, geometry: &Geometry) -> Geometry { + let year_font = fonts.get_font(self.font, self.size * YEAR_FACTOR); + self.year_width = year_font.auto_widest("0123456789").unwrap() * 4; + let cal_font = fonts.get_font(self.font, self.size * DATE_FACTOR); + self.date_width = cal_font.auto_widest("0123456789").unwrap() * 2; + let day_font = fonts.get_font(self.font, self.size * DAY_FACTOR); + self.day_width = day_font.auto_widest("ADEFHIMNORSTUW").unwrap() * 3; - let possible_sections_x = geometry.width / (cal_width + cal_pad); - let possible_sections_y = geometry.height / cal_height; + let cal_width = 8 * self.date_width + 7 * (self.date_width / 2); + let cal_height = + (self.size * MONTH_FACTOR).ceil() as u32 + (self.size * LINE_HEIGHT * 8.).ceil() as u32; + + let cal_pad = self.date_width * 2; + + if geometry.width < cal_width || geometry.height < cal_height { + self.geometry = Default::default(); + return self.geometry; + } + + let possible_sections_x = 1 + (geometry.width - cal_width) / (cal_width + cal_pad); + let possible_sections_y = 1 + (geometry.height - cal_height) / cal_height; + + self.shown_sections_x = + if self.sections_x > 0 && self.sections_x < possible_sections_x as i32 { + self.sections_x as u32 + } else { + possible_sections_x + }; + self.shown_sections_y = + if self.sections_y > 0 && self.sections_y < possible_sections_y as i32 { + self.sections_y as u32 + } else { + possible_sections_y + }; - let width = if self.sections_x > 0 && self.sections_x < possible_sections_x as i32 { - self.shown_sections_x = self.sections_x as u32; - cal_width * self.sections_x as u32 + cal_pad * (self.sections_x as u32 - 1) - } else { - self.shown_sections_x = possible_sections_x; - (geometry.width / (cal_width + cal_pad)) * (cal_width + cal_pad) - }; - let height = if self.sections_y > 0 && self.sections_y < possible_sections_y as i32 { - self.shown_sections_y = self.sections_y as u32; - cal_height * self.sections_y as u32 - } else { - self.shown_sections_y = possible_sections_y; - (geometry.height / cal_height) * cal_height - }; self.geometry = Geometry { x: geometry.x, y: geometry.y, - width: width, - height: height, + width: cal_width * self.shown_sections_x as u32 + + cal_pad * (self.shown_sections_x as u32 - 1), + height: cal_height * self.shown_sections_y as u32, }; self.geometry } + + fn minimum_size(&mut self, fonts: &mut FontMap) -> Geometry { + let cal_font = fonts.get_font(self.font, self.size * DATE_FACTOR); + let date_width = cal_font.auto_widest("0123456789").unwrap() * 2; + + let cal_width = 8 * date_width + 7 * (date_width / 2); + let cal_height = + (self.size * MONTH_FACTOR).ceil() as u32 + (self.size * LINE_HEIGHT * 8.).ceil() as u32; + let cal_pad = date_width * 2; + + let sections_x = match self.sections_x { + -1 => 1, + 0 => { + return Geometry { + x: 0, + y: 0, + width: 0, + height: 0, + } + } + x => x, + }; + let sections_y = match self.sections_y { + -1 => 1, + 0 => { + return Geometry { + x: 0, + y: 0, + width: 0, + height: 0, + } + } + y => y, + }; + let width = cal_width * sections_x as u32 + cal_pad * (sections_x as u32 - 1); + let height = cal_height * sections_y as u32; + Geometry { + x: 0, + y: 0, + width: width, + height: height, + } + } } diff --git a/src/widgets/clock.rs b/src/widgets/clock.rs index 074a151..9f932ce 100644 --- a/src/widgets/clock.rs +++ b/src/widgets/clock.rs @@ -3,8 +3,8 @@ use std::default::Default; use crate::{ buffer::BufferView, color::Color, - fonts::FontMap, event::Event, + fonts::FontMap, widgets::{Geometry, Widget}, }; @@ -81,4 +81,16 @@ impl<'a> Widget for Clock { }; self.geometry } + + fn minimum_size(&mut self, fonts: &mut FontMap) -> Geometry { + let font = fonts.get_font(self.font, self.size); + let digit_width = font.auto_widest("0123456789").unwrap(); + let colon_width = font.auto_widest(":").unwrap(); + Geometry { + x: 0, + y: 0, + width: digit_width * 4 + colon_width, + height: self.size.ceil() as u32, + } + } } diff --git a/src/widgets/date.rs b/src/widgets/date.rs index ee89797..2dce5c9 100644 --- a/src/widgets/date.rs +++ b/src/widgets/date.rs @@ -3,8 +3,8 @@ use std::default::Default; use crate::{ buffer::BufferView, color::Color, - fonts::FontMap, event::Event, + fonts::FontMap, widgets::{Geometry, Widget}, }; @@ -78,4 +78,15 @@ impl<'a> Widget for Date { }; self.geometry } + + fn minimum_size(&mut self, fonts: &mut FontMap) -> Geometry { + let width = (3. + 2. + 2. + 2. + 4.) * self.size / 2.; + let font = fonts.get_font(self.font, self.size); + Geometry { + x: 0, + y: 0, + width: width.ceil() as u32, + height: font.height().ceil() as u32, + } + } } diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs index 19983b7..4df5af8 100644 --- a/src/widgets/launcher.rs +++ b/src/widgets/launcher.rs @@ -1,10 +1,10 @@ use std::{ cmp::{max, min, Ordering}, collections::HashMap, - env, - io::Write, - fs::{read_to_string, File}, default::Default, + env, + fs::{read_to_string, File}, + io::Write, process::{exit, Command}, sync::{Arc, Mutex}, thread, @@ -13,9 +13,9 @@ use std::{ use crate::{ buffer::BufferView, color::Color, + event::{Event, Events, PointerButton, PointerEvent}, fonts::FontMap, keyboard::{keysyms, KeyEvent}, - event::{Event, Events, PointerEvent, PointerButton}, utils::desktop::{load_desktop_files, Desktop}, widgets::{Geometry, Widget}, }; @@ -312,8 +312,7 @@ impl InterfaceWidget for Launcher { ) .unwrap(); - font.auto_draw_text(&mut prompt_line, fg, ">") - .unwrap(); + font.auto_draw_text(&mut prompt_line, fg, ">").unwrap(); // Draw entries let dimfg = Color::GREY50; @@ -348,8 +347,7 @@ impl InterfaceWidget for Launcher { } else { x_max = max( x_max, - font.auto_draw_text(&mut line, dimfg, &m.name) - .unwrap(), + font.auto_draw_text(&mut line, dimfg, &m.name).unwrap(), ); } } @@ -452,10 +450,7 @@ impl Calc { }) .unwrap(); - Calc{ - old, - result: None, - } + Calc { old, result: None } } fn sync(&self) { @@ -521,18 +516,18 @@ impl InterfaceWidget for Calc { // Draw prompt let font = fonts.get_font(intf.font, intf.size); - font - .auto_draw_text_with_cursor( - &mut prompt_line, - fg, - &format!(" {} ", &intf.prompt.input), - intf.prompt.cursor + 3, - ) - .unwrap(); + font.auto_draw_text_with_cursor( + &mut prompt_line, + fg, + &format!(" {} ", &intf.prompt.input), + intf.prompt.cursor + 3, + ) + .unwrap(); font.auto_draw_text(&mut prompt_line, Color::BUFF, "=") .unwrap(); + prompt_offset -= 16; prompt_offset -= line_height; if let Some(res) = &self.result { let c = if intf.selection == 0 { @@ -555,7 +550,8 @@ impl InterfaceWidget for Calc { }; prompt_offset -= line_height; let mut result_line = view.offset((0, prompt_offset)); - font.auto_draw_text(&mut result_line, c, &format!("{} = {}", input, res)).unwrap(); + font.auto_draw_text(&mut result_line, c, &format!("{} = {}", input, res)) + .unwrap(); } let content_height = min( @@ -588,9 +584,15 @@ pub struct Interface { } impl Interface { - pub fn new(events: Arc>, fm: &mut FontMap, font: &'static str, size: f32) -> Interface { + pub fn new( + events: Arc>, + fm: &mut FontMap, + font: &'static str, + size: f32, + ) -> Interface { fm.queue_font( - font, size, + font, + size, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 >=!,.-/()", ); @@ -708,6 +710,15 @@ impl Widget for Interface { self.inner.geometry } + fn minimum_size(&mut self, _fonts: &mut FontMap) -> Geometry { + Geometry { + x: 0, + y: 0, + width: 256, + height: (self.inner.size.ceil() as u32) * 4 + 16, + } + } + fn event(&mut self, event: &Event) { match event { Event::KeyEvent(e) => self.keyboard_input(e), @@ -715,7 +726,7 @@ impl Widget for Interface { Event::LauncherUpdate => { self.inner.dirty = true; self.launcher.update(&self.inner); - }, + } Event::TokenUpdate(t) => self.launcher.next_token = Some(t.to_string()), _ => (), } diff --git a/src/widgets/layout.rs b/src/widgets/layout.rs index c952a97..fb958ed 100644 --- a/src/widgets/layout.rs +++ b/src/widgets/layout.rs @@ -1,10 +1,6 @@ use std::cmp::max; -use crate::{ - fonts::FontMap, - state::State, - widgets::{Geometry}, -}; +use crate::{fonts::FontMap, state::State, widgets::Geometry}; pub trait Layout { fn geometry_update( @@ -13,6 +9,8 @@ pub trait Layout { geometry: &Geometry, state: &mut State, ) -> Geometry; + + fn minimum_size(&self, fonts: &mut FontMap, state: &mut State) -> Geometry; } pub struct HorizontalLayout { @@ -46,6 +44,22 @@ impl Layout for HorizontalLayout { geo.height = max_height; geo } + + fn minimum_size(&self, fonts: &mut FontMap, state: &mut State) -> Geometry { + let mut max_width = 0; + let mut max_height = 0; + for w in self.widgets.iter() { + let result = w.minimum_size(fonts, state); + max_width += result.x + result.width; + max_height = max(result.y + result.height, max_height); + } + Geometry { + width: max_width, + height: max_height, + x: 0, + y: 0, + } + } } pub struct VerticalLayout { @@ -79,11 +93,28 @@ impl Layout for VerticalLayout { geo.height = max_height; geo } + + fn minimum_size(&self, fonts: &mut FontMap, state: &mut State) -> Geometry { + let mut max_width = 0; + let mut max_height = 0; + for w in self.widgets.iter() { + let result = w.minimum_size(fonts, state); + max_width = max(result.x + result.width, max_width); + max_height += result.y + result.height; + } + Geometry { + width: max_width, + height: max_height, + x: 0, + y: 0, + } + } } pub trait WidgetUpdater { fn geometry_update(&mut self, idx: usize, fonts: &mut FontMap, geometry: &Geometry) -> Geometry; + fn minimum_size(&mut self, idx: usize, fonts: &mut FontMap) -> Geometry; } pub struct IndexedLayout { @@ -105,6 +136,10 @@ impl Layout for IndexedLayout { ) -> Geometry { state.geometry_update(self.widget_idx, fonts, geometry) } + + fn minimum_size(&self, fonts: &mut FontMap, state: &mut State) -> Geometry { + state.minimum_size(self.widget_idx, fonts) + } } pub struct Margin { @@ -139,6 +174,16 @@ impl Layout for Margin { height: out.height + self.margin.1 + self.margin.3, } } + + fn minimum_size(&self, fonts: &mut FontMap, state: &mut State) -> Geometry { + let size = self.widget.minimum_size(fonts, state); + Geometry { + width: size.x + size.width + self.margin.0 + self.margin.2, + height: size.y + size.height + self.margin.1 + self.margin.3, + x: 0, + y: 0, + } + } } pub struct InvertedHorizontalLayout { @@ -171,4 +216,20 @@ impl Layout for InvertedHorizontalLayout { geo.height = max_height; geo } + + fn minimum_size(&self, fonts: &mut FontMap, state: &mut State) -> Geometry { + let mut max_width = 0; + let mut max_height = 0; + for w in self.widgets.iter() { + let result = w.minimum_size(fonts, state); + max_width += result.x + result.width; + max_height = max(result.y + result.height, max_height); + } + Geometry { + width: max_width, + height: max_height, + x: 0, + y: 0, + } + } } diff --git a/src/widgets/line.rs b/src/widgets/line.rs index faa9750..354752b 100644 --- a/src/widgets/line.rs +++ b/src/widgets/line.rs @@ -14,7 +14,7 @@ pub struct Line { impl Line { pub fn new(thickness: u32) -> Line { - Line{ + Line { geometry: Default::default(), thickness, } @@ -25,10 +25,12 @@ impl Widget for Line { fn geometry(&self) -> Geometry { self.geometry } + fn draw(&mut self, _fonts: &mut FontMap, view: &mut BufferView) -> Geometry { view.memset(Color::WHITE); self.geometry } + fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry { self.geometry = Geometry { x: geometry.x, @@ -38,4 +40,13 @@ impl Widget for Line { }; self.geometry } + + fn minimum_size(&mut self, _fonts: &mut FontMap) -> Geometry { + Geometry { + x: 0, + y: 0, + width: 0, + height: self.thickness, + } + } } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 6f51598..4b7dd68 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,5 +1,4 @@ //mod alsaaudio; -mod pulseaudio; mod backlight; mod bar_widget; mod battery; @@ -7,18 +6,22 @@ mod calendar; mod clock; mod date; mod launcher; -mod line; -mod widget; mod layout; +mod line; +mod pulseaudio; +mod widget; //pub use alsaaudio::AlsaAudio; -pub use pulseaudio::PulseAudio; pub use backlight::Backlight; pub use battery::Battery; pub use calendar::Calendar; pub use clock::Clock; pub use date::Date; pub use launcher::Interface; +pub use layout::{ + HorizontalLayout, IndexedLayout, InvertedHorizontalLayout, Layout, Margin, VerticalLayout, + WidgetUpdater, +}; pub use line::Line; -pub use layout::{VerticalLayout, HorizontalLayout, IndexedLayout, InvertedHorizontalLayout, Layout, Margin, WidgetUpdater}; +pub use pulseaudio::PulseAudio; pub use widget::{Geometry, Widget}; diff --git a/src/widgets/pulseaudio.rs b/src/widgets/pulseaudio.rs index b59f952..0b03e44 100644 --- a/src/widgets/pulseaudio.rs +++ b/src/widgets/pulseaudio.rs @@ -1,16 +1,13 @@ - use std::{ + cell::RefCell, + error::Error, + os::fd::RawFd, + rc::Rc, sync::{Arc, Mutex}, thread, - error::Error, - rc::Rc, - cell::RefCell, - os::fd::RawFd, }; -use nix::unistd::{ - pipe, write, read, -}; +use nix::unistd::{pipe, read, write}; use crate::{ color::Color, @@ -21,22 +18,19 @@ use crate::{ use libpulse_binding::volume::ChannelVolumes; -fn monitor(events: Arc>, inner: Arc>, pipe: RawFd) -> Result<(), Box> { +fn monitor( + events: Arc>, + inner: Arc>, + pipe: RawFd, +) -> Result<(), Box> { use libpulse_binding::{ - mainloop::{ - self, - standard::{ - IterateResult, - }, - api::Mainloop, - events::io::FlagSet as IoFlagSet, - }, callbacks::ListResult, context::{ - Context, - FlagSet, - subscribe::{InterestMaskSet, Facility}, - State, + subscribe::{Facility, InterestMaskSet}, + Context, FlagSet, State, + }, + mainloop::{ + self, api::Mainloop, events::io::FlagSet as IoFlagSet, standard::IterateResult, }, proplist::{properties, Proplist}, volume::Volume, @@ -48,7 +42,9 @@ fn monitor(events: Arc>, inner: Arc>, pipe: RawF .unwrap(); let mut mainloop = mainloop::standard::Mainloop::new().unwrap(); - let context = Rc::new(RefCell::new(Context::new_with_proplist(&mainloop, "wldash_context", &proplist).unwrap())); + let context = Rc::new(RefCell::new( + Context::new_with_proplist(&mainloop, "wldash_context", &proplist).unwrap(), + )); context.borrow_mut().connect(None, FlagSet::NOFLAGS, None)?; loop { @@ -63,33 +59,31 @@ fn monitor(events: Arc>, inner: Arc>, pipe: RawF let local_context = context.clone(); let local_inner = inner.clone(); let local_events = events.clone(); - context - .borrow_mut() - .set_subscribe_callback(Some(Box::new(move |facility, _operation, index| { - match facility { - Some(Facility::Sink) => { - let introspector = local_context.borrow_mut().introspect(); - let inner = local_inner.clone(); - let events = local_events.clone(); - introspector.get_sink_info_by_index(index, move |res| { - match res { - ListResult::End | ListResult::Error => (), - ListResult::Item(sink_info) => if let Some(_) = &sink_info.name { - let avg = sink_info.volume.avg().0 as f32 / Volume::NORMAL.0 as f32; - let mut inner = inner.lock().unwrap(); - inner.volume = avg; - inner.mute = sink_info.mute; - inner.dirty = true; - drop(inner); - let mut events = events.lock().unwrap(); - events.add_event(Event::AudioUpdate); - }, + context.borrow_mut().set_subscribe_callback(Some(Box::new( + move |facility, _operation, index| match facility { + Some(Facility::Sink) => { + let introspector = local_context.borrow_mut().introspect(); + let inner = local_inner.clone(); + let events = local_events.clone(); + introspector.get_sink_info_by_index(index, move |res| match res { + ListResult::End | ListResult::Error => (), + ListResult::Item(sink_info) => { + if let Some(_) = &sink_info.name { + let avg = sink_info.volume.avg().0 as f32 / Volume::NORMAL.0 as f32; + let mut inner = inner.lock().unwrap(); + inner.volume = avg; + inner.mute = sink_info.mute; + inner.dirty = true; + drop(inner); + let mut events = events.lock().unwrap(); + events.add_event(Event::AudioUpdate); } - }); - }, - _ => (), + } + }); } - }))); + _ => (), + }, + ))); context .borrow_mut() @@ -97,10 +91,10 @@ fn monitor(events: Arc>, inner: Arc>, pipe: RawF let introspector = context.borrow_mut().introspect(); let local_inner = inner.clone(); - introspector.get_sink_info_by_name("@DEFAULT_SINK@", move |res| { - match res { - ListResult::End | ListResult::Error => (), - ListResult::Item(sink_info) => if let Some(_) = &sink_info.name { + introspector.get_sink_info_by_name("@DEFAULT_SINK@", move |res| match res { + ListResult::End | ListResult::Error => (), + ListResult::Item(sink_info) => { + if let Some(_) = &sink_info.name { let avg = sink_info.volume.avg().0 as f32 / Volume::NORMAL.0 as f32; let mut inner = local_inner.lock().unwrap(); inner.volume = avg; @@ -110,46 +104,50 @@ fn monitor(events: Arc>, inner: Arc>, pipe: RawF drop(inner); let mut events = events.lock().unwrap(); events.add_event(Event::AudioUpdate); - }, + } } }); drop(introspector); let local_context = context.clone(); let local_inner = inner.clone(); - let event_source = mainloop.new_io_event(pipe as i32, IoFlagSet::INPUT, Box::new(move |_, _, _| { - let mut buf = [0; 8]; - _ = read(pipe, &mut buf); + let event_source = mainloop.new_io_event( + pipe as i32, + IoFlagSet::INPUT, + Box::new(move |_, _, _| { + let mut buf = [0; 8]; + _ = read(pipe, &mut buf); - let mut inner = local_inner.lock().unwrap(); - let mut introspector = local_context.borrow_mut().introspect(); - match inner.last_click.take() { - Some(Change::Volume(volume)) => { - // do something - let mut vol = inner.channel_volume.unwrap(); - vol.scale(Volume((volume * Volume::NORMAL.0 as f32).round() as u32)); - introspector.set_sink_volume_by_name("@DEFAULT_SINK@", &vol, None); - }, - Some(Change::VolumeInc(value)) => { - let mut vol = inner.channel_volume.unwrap(); - // apply step to volumes - let step = (value * Volume::NORMAL.0 as f32).round() as i32; - if step > 0 { - vol.inc_clamp(Volume(step as u32), Volume::NORMAL); - } else { - vol.decrease(Volume(-step as u32)); + let mut inner = local_inner.lock().unwrap(); + let mut introspector = local_context.borrow_mut().introspect(); + match inner.last_click.take() { + Some(Change::Volume(volume)) => { + // do something + let mut vol = inner.channel_volume.unwrap(); + vol.scale(Volume((volume * Volume::NORMAL.0 as f32).round() as u32)); + introspector.set_sink_volume_by_name("@DEFAULT_SINK@", &vol, None); } - // HACK: It would be better to only update the volume from feedback, but axis - // events fire fast and require the last set volume immediately for reference. - inner.channel_volume = Some(vol); - introspector.set_sink_volume_by_name("@DEFAULT_SINK@", &vol, None); - }, - Some(Change::ToggleMute) => { - introspector.set_sink_mute_by_name("@DEFAULT_SINK@", !inner.mute, None); - }, - None => (), - } - })); + Some(Change::VolumeInc(value)) => { + let mut vol = inner.channel_volume.unwrap(); + // apply step to volumes + let step = (value * Volume::NORMAL.0 as f32).round() as i32; + if step > 0 { + vol.inc_clamp(Volume(step as u32), Volume::NORMAL); + } else { + vol.decrease(Volume(-step as u32)); + } + // HACK: It would be better to only update the volume from feedback, but axis + // events fire fast and require the last set volume immediately for reference. + inner.channel_volume = Some(vol); + introspector.set_sink_volume_by_name("@DEFAULT_SINK@", &vol, None); + } + Some(Change::ToggleMute) => { + introspector.set_sink_mute_by_name("@DEFAULT_SINK@", !inner.mute, None); + } + None => (), + } + }), + ); loop { match mainloop.iterate(true) { @@ -167,7 +165,7 @@ fn monitor(events: Arc>, inner: Arc>, pipe: RawF fn start_monitor(events: Arc>, inner: Arc>, pipe: RawFd) { thread::Builder::new() .name("audiomon".to_string()) - .spawn(move || { monitor(events, inner, pipe).unwrap() }) + .spawn(move || monitor(events, inner, pipe).unwrap()) .unwrap(); } @@ -191,10 +189,15 @@ pub struct PulseAudio { } impl PulseAudio { - pub fn new(events: Arc>, fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget { + pub fn new( + events: Arc>, + fm: &mut FontMap, + font: &'static str, + size: f32, + ) -> BarWidget { let (a, b) = pipe().unwrap(); let audio = PulseAudio { - inner: Arc::new(Mutex::new(InnerAudio{ + inner: Arc::new(Mutex::new(InnerAudio { volume: 0.0, channel_volume: None, mute: false, @@ -229,7 +232,7 @@ impl BarWidgetImpl for PulseAudio { fn click(&mut self, pos: f32, btn: PointerButton) { let mut inner = self.inner.lock().unwrap(); inner.last_click = match btn { - PointerButton::Left => Some(Change::Volume(pos)), + PointerButton::Left => Some(Change::Volume(pos)), PointerButton::Right => Some(Change::ToggleMute), PointerButton::ScrollVertical(val) => Some(Change::VolumeInc((val / 512.) as f32)), PointerButton::ScrollHorizontal(val) => Some(Change::VolumeInc((val / 100.) as f32)), diff --git a/src/widgets/widget.rs b/src/widgets/widget.rs index b097854..69341cb 100644 --- a/src/widgets/widget.rs +++ b/src/widgets/widget.rs @@ -3,11 +3,7 @@ use std::{ fmt, }; -use crate::{ - buffer::BufferView, - fonts::FontMap, - event::Event, -}; +use crate::{buffer::BufferView, event::Event, fonts::FontMap}; #[derive(Debug, Default, Clone, Copy)] pub struct Geometry { @@ -49,8 +45,10 @@ impl Geometry { } pub fn contains(&self, position: (u32, u32)) -> bool { - position.0 >= self.x && position.0 < self.x + self.width && - position.1 >= self.y && position.1 < self.y + self.height + position.0 >= self.x + && position.0 < self.x + self.width + && position.1 >= self.y + && position.1 < self.y + self.height } } @@ -60,7 +58,9 @@ pub trait Widget { } fn geometry(&self) -> Geometry; fn geometry_update(&mut self, fonts: &mut FontMap, geometry: &Geometry) -> Geometry; + fn minimum_size(&mut self, _fonts: &mut FontMap) -> Geometry { + Default::default() + } fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry; fn event(&mut self, _event: &Event) {} } - From 45e5a750f9c0f605034450c3e2e6266476ed9ac4 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Wed, 8 Feb 2023 22:56:52 +0100 Subject: [PATCH 18/57] calendar: Fix hiding when shrunk --- src/widgets/calendar.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/widgets/calendar.rs b/src/widgets/calendar.rs index ef9b73e..2451115 100644 --- a/src/widgets/calendar.rs +++ b/src/widgets/calendar.rs @@ -266,6 +266,8 @@ impl<'a> Widget for Calendar { let cal_pad = self.date_width * 2; if geometry.width < cal_width || geometry.height < cal_height { + self.shown_sections_x = 0; + self.shown_sections_y = 0; self.geometry = Default::default(); return self.geometry; } From 353ed1f189db72a82689adac52318e7ff30f2c8e Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Wed, 8 Feb 2023 22:57:06 +0100 Subject: [PATCH 19/57] launcher: Shrink margin --- src/widgets/launcher.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs index 4df5af8..118626a 100644 --- a/src/widgets/launcher.rs +++ b/src/widgets/launcher.rs @@ -316,7 +316,7 @@ impl InterfaceWidget for Launcher { // Draw entries let dimfg = Color::GREY50; - prompt_offset -= 16; + prompt_offset -= 8; for (idx, m) in self.matches.iter().enumerate() { if prompt_offset < line_height { @@ -354,7 +354,7 @@ impl InterfaceWidget for Launcher { let content_height = min( intf.geometry.height, - (self.matches.len() + 1) as u32 * line_height + 16, + (self.matches.len() + 1) as u32 * line_height + 8, ); Geometry { x: intf.geometry.x, @@ -527,7 +527,7 @@ impl InterfaceWidget for Calc { font.auto_draw_text(&mut prompt_line, Color::BUFF, "=") .unwrap(); - prompt_offset -= 16; + prompt_offset -= 8; prompt_offset -= line_height; if let Some(res) = &self.result { let c = if intf.selection == 0 { @@ -556,7 +556,7 @@ impl InterfaceWidget for Calc { let content_height = min( intf.geometry.height, - (old.len() + 2) as u32 * line_height + 16, + (old.len() + 2) as u32 * line_height + 8, ); Geometry { x: intf.geometry.x, @@ -614,7 +614,7 @@ impl Interface { fn pointer_input(&mut self, event: &PointerEvent) { if let PointerButton::Left = event.button { let line_height = self.inner.size.ceil() as u32; - let height = self.inner.geometry.height - line_height - 16; + let height = self.inner.geometry.height - line_height - 8; let lines = height / line_height; let offset = height % line_height; @@ -715,7 +715,7 @@ impl Widget for Interface { x: 0, y: 0, width: 256, - height: (self.inner.size.ceil() as u32) * 4 + 16, + height: (self.inner.size.ceil() as u32) * 4 + 8, } } From db085168dcf1c136ce10ac19935d738e734779e9 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Sat, 11 Feb 2023 18:24:34 +0100 Subject: [PATCH 20/57] config: Add config support --- Cargo.lock | 69 ++++++++++++ Cargo.toml | 3 + src/config.rs | 299 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 101 +++++------------ 4 files changed, 399 insertions(+), 73 deletions(-) create mode 100644 src/config.rs diff --git a/Cargo.lock b/Cargo.lock index 67cdc4d..bc7e17c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -239,6 +239,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -263,6 +269,16 @@ dependencies = [ "cxx-build", ] +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "io-lifetimes" version = "1.0.3" @@ -273,6 +289,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + [[package]] name = "js-sys" version = "0.3.60" @@ -615,6 +637,12 @@ dependencies = [ "owned_ttf_parser", ] +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + [[package]] name = "same-file" version = "1.0.6" @@ -636,6 +664,39 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yaml" +version = "0.9.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb06d4b6cdaef0e0c51fa881acb721bed3c924cfaa71d9c94a3b771dfdf6567" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha1" version = "0.10.5" @@ -770,6 +831,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "unsafe-libyaml" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2" + [[package]] name = "vec_map" version = "0.8.2" @@ -1032,6 +1099,8 @@ dependencies = [ "nix 0.26.1", "rcalc_lib", "rusttype", + "serde", + "serde_yaml", "shlex", "unicode-segmentation", "walkdir", diff --git a/Cargo.toml b/Cargo.toml index 9580b5a..84c3f05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,3 +37,6 @@ rcalc_lib = "1.0" #alsa = { version = "0.7", optional = true } dbus = "0.9" libpulse-binding = "2.26" + +serde = { version = "1.0", features = ["derive"] } +serde_yaml = "0.9" diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..2aa18ef --- /dev/null +++ b/src/config.rs @@ -0,0 +1,299 @@ +use serde::{self, Deserialize, Serialize}; + +use std::collections::HashMap; +use std::default::Default; +use std::sync::{Arc, Mutex}; + +use crate::{ + event::Events, + fonts::FontMap, + widgets::{ + Backlight, Battery, Calendar, Clock, Date, HorizontalLayout, IndexedLayout, Interface, + InvertedHorizontalLayout, Layout, Line, Margin, PulseAudio, VerticalLayout, Widget as RealWidget, + }, +}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub enum Widget { + Margin { + margins: (u32, u32, u32, u32), + widget: Box, + }, + HorizontalLayout(Vec), + InvertedHorizontalLayout(Vec), + VerticalLayout(Vec), + Line(u32), + Clock { + font: Option, + font_size: f32, + }, + Date { + font: Option, + font_size: f32, + }, + Calendar { + font_primary: Option, + font_secondary: Option, + font_size: f32, + sections_x: i32, + sections_y: i32, + }, + Launcher { + font: Option, + font_size: f32, + }, + Battery { + font: Option, + font_size: f32, + }, + Backlight { + device: Option, + font: Option, + font_size: f32, + }, + PulseAudio { + font: Option, + font_size: f32, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Config { + pub font_paths: Option>, + pub widget: Option, +} + +impl Default for Config { + fn default() -> Config { + Config{ + font_paths: None, + widget: None, + } + } +} + +fn leak_or_default(v: Option, d: &'static str) -> &'static str { + match v { + Some(v) => Box::leak(v.into_boxed_str()), + None => d, + } +} + +impl Default for Widget { + fn default() -> Widget { + Self::v2() + } +} + +impl Widget { + pub fn v1() -> Widget { + Widget::Margin { + margins: (20, 20, 20, 20), + widget: Box::new(Widget::VerticalLayout(vec![ + Widget::HorizontalLayout(vec![ + Widget::VerticalLayout(vec![ + Widget::Date { + font: None, + font_size: 48., + }, + Widget::Clock { + font: None, + font_size: 256., + }, + ]), + Widget::Margin { + margins: (88, 0, 0, 0), + widget: Box::new(Widget::VerticalLayout(vec![ + Widget::Margin { + margins: (0, 0, 0, 8), + widget: Box::new(Widget::Battery { + font: None, + font_size: 24., + }), + }, + Widget::Margin { + margins: (0, 0, 0, 8), + widget: Box::new(Widget::Backlight { + device: None, + font: None, + font_size: 24., + }), + }, + Widget::Margin { + margins: (0, 0, 0, 8), + widget: Box::new(Widget::PulseAudio { + font: None, + font_size: 24., + }), + }, + ])), + }, + ]), + Widget::Calendar { + font_primary: None, + font_secondary: None, + font_size: 36., + sections_x: 3, + sections_y: 1, + }, + Widget::Launcher { + font: None, + font_size: 32., + }, + ])), + } + } + + pub fn v2() -> Widget { + Widget::VerticalLayout(vec![ + Widget::HorizontalLayout(vec![ + Widget::Clock { + font: None, + font_size: 128., + }, + Widget::Margin { + margins: (16, 16, 0, 0), + widget: Box::new(Widget::Date { + font: None, + font_size: 48., + }), + }, + Widget::VerticalLayout(vec![ + Widget::Margin { + margins: (16, 8, 8, 0), + widget: Box::new(Widget::Battery { + font: None, + font_size: 24., + }), + }, + Widget::Margin { + margins: (16, 8, 8, 0), + widget: Box::new(Widget::Backlight { + device: None, + font: None, + font_size: 24., + }), + }, + Widget::Margin { + margins: (16, 8, 8, 0), + widget: Box::new(Widget::PulseAudio { + font: None, + font_size: 24., + }), + }, + ]), + ]), + Widget::Line(1), + Widget::InvertedHorizontalLayout(vec![ + Widget::Calendar { + font_primary: None, + font_secondary: None, + font_size: 24., + sections_x: 1, + sections_y: -1, + }, + Widget::Launcher { + font: None, + font_size: 32., + }, + ]), + ]) + } + + pub fn construct_widgets( + self, + v: &mut Vec>, + mut fm: &mut FontMap, + events: &Arc>, + ) { + match self { + Widget::Margin { widget, .. } => widget.construct_widgets(v, fm, events), + Widget::HorizontalLayout(widgets) => widgets + .into_iter() + .for_each(|w| w.construct_widgets(v, fm, events)), + Widget::InvertedHorizontalLayout(widgets) => widgets + .into_iter() + .for_each(|w| w.construct_widgets(v, fm, events)), + Widget::VerticalLayout(widgets) => widgets + .into_iter() + .for_each(|w| w.construct_widgets(v, fm, events)), + Widget::Line(width) => v.push(Box::new(Line::new(width))), + Widget::Clock { font, font_size } => v.push(Box::new(Clock::new( + &mut fm, + leak_or_default(font, "sans"), + font_size, + ))), + Widget::Date { font, font_size } => v.push(Box::new(Date::new( + &mut fm, + leak_or_default(font, "sans"), + font_size, + ))), + Widget::Calendar { + font_primary, + font_secondary, + font_size, + sections_x, + sections_y, + } => v.push(Box::new(Calendar::new( + &mut fm, + leak_or_default(font_primary, "monospace"), + leak_or_default(font_secondary, "sans"), + font_size, + sections_x, + sections_y, + ))), + Widget::Launcher { font, font_size } => v.push(Box::new(Interface::new( + events.clone(), + &mut fm, + leak_or_default(font, "sans"), + font_size, + ))), + Widget::Battery { font, font_size } => v.push(Box::new(Battery::new( + events.clone(), + &mut fm, + leak_or_default(font, "sans"), + font_size, + ))), + Widget::Backlight { + device, + font, + font_size, + } => v.push(Box::new(Backlight::new( + leak_or_default(device, "intel_backlight"), + &mut fm, + leak_or_default(font, "sans"), + font_size, + ))), + Widget::PulseAudio { font, font_size } => v.push(Box::new(PulseAudio::new( + events.clone(), + &mut fm, + Box::leak(font.unwrap_or_else(|| "sans".to_string()).into_boxed_str()), + font_size, + ))), + } + } + + pub fn construct_layout(&self, idx: &mut usize) -> Box { + match self { + Widget::Margin { margins, widget } => { + Margin::new(widget.construct_layout(idx), *margins) + } + Widget::HorizontalLayout(widgets) => { + HorizontalLayout::new(widgets.iter().map(|w| w.construct_layout(idx)).collect()) + } + Widget::InvertedHorizontalLayout(widgets) => InvertedHorizontalLayout::new( + widgets.iter().map(|w| w.construct_layout(idx)).collect(), + ), + Widget::VerticalLayout(widgets) => { + VerticalLayout::new(widgets.iter().map(|w| w.construct_layout(idx)).collect()) + } + _ => IndexedLayout::new({ + let i = *idx; + *idx += 1; + i + }), + } + } +} diff --git a/src/main.rs b/src/main.rs index 1d1a619..e1f6783 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod buffer; mod color; +mod config; mod draw; mod event; mod fonts; @@ -8,6 +9,8 @@ mod state; mod utils; mod widgets; +use std::fs::File; + use wayland_client::{Connection, QueueHandle, WaylandSource}; use calloop::{ @@ -17,13 +20,11 @@ use calloop::{ use chrono::{Duration, Local, Timelike}; use buffer::BufferView; +use config::{Config, Widget as ConfigWidget}; use event::{Event, Events}; use fonts::{FontMap, MaybeFontMap}; use state::{OperationMode, State}; -use widgets::{ - Backlight, Battery, Calendar, Clock, Date, Geometry, HorizontalLayout, IndexedLayout, - Interface, InvertedHorizontalLayout, Layout, Line, Margin, PulseAudio, VerticalLayout, Widget, -}; +use widgets::{Geometry, Widget}; use std::{env, rc::Rc, thread}; @@ -115,77 +116,31 @@ fn main() { let mut fm = FontMap::new(); - // TODO: Cache in a file - fm.add_font_path( - "sans", - "/usr/share/fonts/noto/NotoSans-Regular.ttf".to_string(), - ); - fm.add_font_path( - "monospace", - "/usr/share/fonts/noto/NotoSansMono-Regular.ttf".to_string(), - ); - - let (widgets, layout): (Vec>, Rc>) = match version { - UIVersion::V1 => { - let clock = Box::new(Clock::new(&mut fm, "sans", 256.)); - let calendar = Box::new(Calendar::new(&mut fm, "monospace", "sans", 36.0, 3, 1)); - let date = Box::new(Date::new(&mut fm, "sans", 48.)); - let launcher = Box::new(Interface::new(events.clone(), &mut fm, "monospace", 32.)); - let battery = Box::new(Battery::new(events.clone(), &mut fm, "sans", 24.)); - let backlight = Box::new(Backlight::new("intel_backlight", &mut fm, "sans", 24.)); - let audio = Box::new(PulseAudio::new(events.clone(), &mut fm, "sans", 24.)); - - let v: Vec> = - vec![clock, date, battery, backlight, audio, calendar, launcher]; - let l = Rc::new(Margin::new( - VerticalLayout::new(vec![ - HorizontalLayout::new(vec![ - VerticalLayout::new(vec![IndexedLayout::new(1), IndexedLayout::new(0)]), - Margin::new( - VerticalLayout::new(vec![ - Margin::new(IndexedLayout::new(2), (0, 0, 0, 8)), - Margin::new(IndexedLayout::new(3), (0, 0, 0, 8)), - Margin::new(IndexedLayout::new(4), (0, 0, 0, 8)), - ]), - (88, 0, 0, 0), - ), - ]), - IndexedLayout::new(5), - IndexedLayout::new(6), - ]), - (20, 20, 20, 20), - )); - (v, l) - } - UIVersion::V2 => { - let clock = Box::new(Clock::new(&mut fm, "sans", 128.)); - let calendar = Box::new(Calendar::new(&mut fm, "monospace", "sans", 24.0, 1, -1)); - let date = Box::new(Date::new(&mut fm, "sans", 48.)); - let line = Box::new(Line::new(1)); - let launcher = Box::new(Interface::new(events.clone(), &mut fm, "monospace", 32.)); - let battery = Box::new(Battery::new(events.clone(), &mut fm, "sans", 24.)); - let backlight = Box::new(Backlight::new("intel_backlight", &mut fm, "sans", 24.)); - let audio = Box::new(PulseAudio::new(events.clone(), &mut fm, "sans", 24.)); - let v: Vec> = vec![ - clock, date, battery, backlight, audio, line, calendar, launcher, - ]; - let l = Rc::new(VerticalLayout::new(vec![ - HorizontalLayout::new(vec![ - IndexedLayout::new(0), - Margin::new(IndexedLayout::new(1), (16, 16, 0, 0)), - VerticalLayout::new(vec![ - Margin::new(IndexedLayout::new(2), (16, 8, 8, 0)), - Margin::new(IndexedLayout::new(3), (16, 8, 8, 0)), - Margin::new(IndexedLayout::new(4), (16, 8, 8, 0)), - ]), - ]), - IndexedLayout::new(5), - InvertedHorizontalLayout::new(vec![IndexedLayout::new(6), IndexedLayout::new(7)]), - ])); - (v, l) - } + let home = env::var_os("HOME").unwrap().into_string().unwrap(); + let config: Config = match File::open(format!("{}/.config/wldash/config.yml", home)) { + Ok(f) => serde_yaml::from_reader(f).unwrap(), + Err(_) => Default::default(), }; + let widget = match config.widget { + Some(w) => w, + None => match version { + UIVersion::V1 => ConfigWidget::v1(), + UIVersion::V2 => ConfigWidget::v2(), + }, + }; + + if let Some(font_paths) = config.font_paths { + for (key, value) in font_paths.into_iter() { + fm.add_font_path(Box::leak(key.into_boxed_str()), value); + } + } + + let layout = Rc::new(widget.construct_layout(&mut 0)); + let mut widgets: Vec> = Vec::new(); + widget.construct_widgets(&mut widgets, &mut fm, &events); + + let font_thread = thread::Builder::new() .name("fontloader".to_string()) .spawn(move || { From 9e48eb053d0ac3d203bf7c0c52b3e431b32f7e8e Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Sun, 12 Feb 2023 17:02:44 +0100 Subject: [PATCH 21/57] config: Generate configs --- Cargo.toml | 4 - src/config.rs | 161 ++++++++++++++++++++++++++++----------- src/fonts.rs | 32 ++++---- src/main.rs | 50 +++++------- src/state.rs | 7 +- src/widgets/backlight.rs | 8 ++ src/widgets/battery.rs | 21 +++++ 7 files changed, 179 insertions(+), 104 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 84c3f05..34c3fcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,10 +8,6 @@ edition = "2021" lto = "fat" panic = "abort" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[features] -fontloading = ["fontconfig"] - [dependencies] wayland-client = { version = "0.30", features = ["calloop", "log"] } wayland-protocols = { version = "0.30", features = ["client", "unstable", "staging"] } diff --git a/src/config.rs b/src/config.rs index 2aa18ef..86491ca 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,7 +6,7 @@ use std::sync::{Arc, Mutex}; use crate::{ event::Events, - fonts::FontMap, + fonts::{FontMap, find_font}, widgets::{ Backlight, Battery, Calendar, Clock, Date, HorizontalLayout, IndexedLayout, Interface, InvertedHorizontalLayout, Layout, Line, Margin, PulseAudio, VerticalLayout, Widget as RealWidget, @@ -14,7 +14,14 @@ use crate::{ }; #[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "snake_case")] +pub enum OperationMode { + LayerSurface((u32, u32)), + XdgToplevel, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "snake_case")] pub enum Widget { Margin { margins: (u32, u32, u32, u32), @@ -58,38 +65,47 @@ pub enum Widget { }, } +impl Default for OperationMode { + fn default() -> OperationMode { + OperationMode::LayerSurface((1024, 768)) + } +} + #[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "snake_case")] pub struct Config { pub font_paths: Option>, - pub widget: Option, + pub widget: Widget, + pub mode: OperationMode, } impl Default for Config { fn default() -> Config { Config{ font_paths: None, - widget: None, + widget: Default::default(), + mode: Default::default(), } } } -fn leak_or_default(v: Option, d: &'static str) -> &'static str { - match v { - Some(v) => Box::leak(v.into_boxed_str()), - None => d, - } -} +impl Config { + pub fn generate_v1() -> Config { + let has_battery = Battery::detect(); + let has_backlight = Backlight::detect("intel_backlight"); + let sans = find_font("sans"); + let monospace = find_font("monospace"); -impl Default for Widget { - fn default() -> Widget { - Self::v2() - } -} + let mut bars = Vec::new(); + if has_battery { + bars.push(Widget::Margin{ margins: (0, 0, 0, 8), widget: Box::new(Widget::Battery { font: None, font_size: 24. })}); + } + if has_backlight { + bars.push(Widget::Margin{ margins: (0, 0, 0, 8), widget: Box::new(Widget::Backlight { device: Some("intel_backlight".to_string()), font: None, font_size: 24. })}); + } + bars.push(Widget::Margin{ margins: (0, 0, 0, 8), widget: Box::new(Widget::PulseAudio { font: None, font_size: 24. })}); -impl Widget { - pub fn v1() -> Widget { - Widget::Margin { + let widget = Widget::Margin { margins: (20, 20, 20, 20), widget: Box::new(Widget::VerticalLayout(vec![ Widget::HorizontalLayout(vec![ @@ -105,30 +121,7 @@ impl Widget { ]), Widget::Margin { margins: (88, 0, 0, 0), - widget: Box::new(Widget::VerticalLayout(vec![ - Widget::Margin { - margins: (0, 0, 0, 8), - widget: Box::new(Widget::Battery { - font: None, - font_size: 24., - }), - }, - Widget::Margin { - margins: (0, 0, 0, 8), - widget: Box::new(Widget::Backlight { - device: None, - font: None, - font_size: 24., - }), - }, - Widget::Margin { - margins: (0, 0, 0, 8), - widget: Box::new(Widget::PulseAudio { - font: None, - font_size: 24., - }), - }, - ])), + widget: Box::new(Widget::VerticalLayout(bars)), }, ]), Widget::Calendar { @@ -143,10 +136,88 @@ impl Widget { font_size: 32., }, ])), + }; + + let map = HashMap::from([ + ("sans".to_string(), sans), + ("monospace".to_string(), monospace), + ]); + + Config{ + font_paths: Some(map), + widget: widget, + mode: OperationMode::LayerSurface((1024, 768)), } } - pub fn v2() -> Widget { + pub fn generate_v2(clay: bool) -> Config { + let has_battery = Battery::detect(); + let has_backlight = Backlight::detect("intel_backlight"); + let sans = find_font("sans"); + let monospace = find_font("monospace"); + + let mut bars = Vec::new(); + if has_battery { + bars.push(Widget::Margin{ margins: (16, 8, 8, 0), widget: Box::new(Widget::Battery { font: None, font_size: 24. })}); + } + if has_backlight { + bars.push(Widget::Margin{ margins: (16, 8, 8, 0), widget: Box::new(Widget::Backlight { device: Some("intel_backlight".to_string()), font: None, font_size: 24. })}); + } + bars.push(Widget::Margin{ margins: (16, 8, 8, 0), widget: Box::new(Widget::PulseAudio { font: None, font_size: 24. })}); + + let widget = Widget::VerticalLayout(vec![ + Widget::HorizontalLayout(vec![ + Widget::Clock { + font: None, + font_size: 128., + }, + Widget::Margin { + margins: (16, 16, 0, 0), + widget: Box::new(Widget::Date { + font: None, + font_size: 48., + }), + }, + Widget::VerticalLayout(bars), + ]), + Widget::Line(1), + Widget::InvertedHorizontalLayout(vec![ + Widget::Calendar { + font_primary: None, + font_secondary: None, + font_size: 24., + sections_x: 1, + sections_y: -1, + }, + Widget::Launcher { + font: None, + font_size: 32., + }, + ]), + ]); + + let map = HashMap::from([ + ("sans".to_string(), sans), + ("monospace".to_string(), monospace), + ]); + + Config{ + font_paths: Some(map), + widget: widget, + mode: if clay { OperationMode::XdgToplevel } else { OperationMode::LayerSurface((1024, 768)) }, + } + } +} + +fn leak_or_default(v: Option, d: &'static str) -> &'static str { + match v { + Some(v) => Box::leak(v.into_boxed_str()), + None => d, + } +} + +impl Default for Widget { + fn default() -> Widget { Widget::VerticalLayout(vec![ Widget::HorizontalLayout(vec![ Widget::Clock { @@ -201,7 +272,9 @@ impl Widget { ]), ]) } +} +impl Widget { pub fn construct_widgets( self, v: &mut Vec>, diff --git a/src/fonts.rs b/src/fonts.rs index 308cbe3..831cfc8 100644 --- a/src/fonts.rs +++ b/src/fonts.rs @@ -9,27 +9,21 @@ use std::{ /// FontRef is used to store Fonts on widgets. pub type FontRef<'a> = &'a rusttype::Font<'a>; -#[cfg(feature = "fontloading")] -mod fc { +#[cfg(feature = "fontconfig")] +pub(crate) fn find_font(name: &str) -> String { use fontconfig::Fontconfig as FontConfig; - /// Acts like fc-match. - /// Given a string, it matches it to a font file and returns its path. - pub(crate) fn from_string(name: &str) -> String { - let fc = FontConfig::new().unwrap(); - fc.find(name, None) - .unwrap() - .path - .to_str() - .unwrap() - .to_string() - } + let fc = FontConfig::new().unwrap(); + fc.find(name, None) + .unwrap() + .path + .to_str() + .unwrap() + .to_string() } -#[cfg(not(feature = "fontloading"))] -mod fc { - pub(crate) fn from_string(_name: &str) -> String { - panic!("fontloading feature not enabled"); - } +#[cfg(not(feature = "fontconfig"))] +pub(crate) fn find_font(_name: &str) -> String { + panic!("fontconfig not enabled so font search not available"); } /// FontLoader is a marker struct that is used to load files @@ -110,7 +104,7 @@ impl FontMap { let path = match self.font_paths.get(font_name) { Some(res) => res, _ => { - let s = fc::from_string(font_name); + let s = find_font(font_name); self.font_paths.insert(font_name, s); self.font_paths.get(font_name).unwrap() } diff --git a/src/main.rs b/src/main.rs index e1f6783..ce57e6f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,39 +20,36 @@ use calloop::{ use chrono::{Duration, Local, Timelike}; use buffer::BufferView; -use config::{Config, Widget as ConfigWidget}; +use config::Config; use event::{Event, Events}; use fonts::{FontMap, MaybeFontMap}; -use state::{OperationMode, State}; +use state::State; use widgets::{Geometry, Widget}; use std::{env, rc::Rc, thread}; -enum UIVersion { - V1, - V2, -} - fn main() { let mut args = env::args(); // Skip program name _ = args.next(); - let mut version = UIVersion::V2; - let mut mode = OperationMode::XdgToplevel; loop { match args.next() { - Some(ref s) if s == "layer_shell" => { - let width = args.next().map(|x| x.parse::().ok()).flatten(); - let height = args.next().map(|x| x.parse::().ok()).flatten(); - match (width, height) { - (Some(w), Some(h)) => mode = OperationMode::LayerSurface((w, h)), - _ => panic!("invalid arguments"), + Some(ref s) if s == "generate" => { + let config = match args.next() { + Some(ref s) if s == "v1" => Config::generate_v1(), + Some(ref s) if s == "v2" => Config::generate_v2(false), + Some(ref s) if s == "clay" => Config::generate_v2(true), + None => Config::generate_v2(false), + Some(s) => panic!("unknown argument: {}", s), + }; + let home = env::var_os("HOME").unwrap().into_string().unwrap(); + match File::create(format!("{}/.config/wldash/config.yml", home)) { + Ok(f) => serde_yaml::to_writer(f, &config).unwrap(), + Err(_) => panic!("uh"), } - } - Some(ref s) if s == "xdg_shell" => mode = OperationMode::XdgToplevel, - Some(ref s) if s == "v1" => version = UIVersion::V1, - Some(ref s) if s == "v2" => version = UIVersion::V2, + std::process::exit(0); + }, Some(_) => panic!("unknown argument"), None => break, } @@ -122,24 +119,15 @@ fn main() { Err(_) => Default::default(), }; - let widget = match config.widget { - Some(w) => w, - None => match version { - UIVersion::V1 => ConfigWidget::v1(), - UIVersion::V2 => ConfigWidget::v2(), - }, - }; - if let Some(font_paths) = config.font_paths { for (key, value) in font_paths.into_iter() { fm.add_font_path(Box::leak(key.into_boxed_str()), value); } } - let layout = Rc::new(widget.construct_layout(&mut 0)); + let layout = Rc::new(config.widget.construct_layout(&mut 0)); let mut widgets: Vec> = Vec::new(); - widget.construct_widgets(&mut widgets, &mut fm, &events); - + config.widget.construct_widgets(&mut widgets, &mut fm, &events); let font_thread = thread::Builder::new() .name("fontloader".to_string()) @@ -150,7 +138,7 @@ fn main() { .unwrap(); let mut state = State::new( - mode, + config.mode, widgets, layout, MaybeFontMap::Waiting(font_thread), diff --git a/src/state.rs b/src/state.rs index d8e0b32..487f7a8 100644 --- a/src/state.rs +++ b/src/state.rs @@ -29,6 +29,7 @@ use wayland_protocols::{ use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1}; use crate::{ + config::OperationMode, buffer::BufferManager, event::{Event, Events, PointerButton, PointerEvent}, fonts::{FontMap, MaybeFontMap}, @@ -36,12 +37,6 @@ use crate::{ widgets::{Geometry, Layout, Widget, WidgetUpdater}, }; -#[derive(Debug)] -pub enum OperationMode { - LayerSurface((u32, u32)), - XdgToplevel, -} - #[derive(Default)] pub struct MainSurface { pub wl_surface: Option, diff --git a/src/widgets/backlight.rs b/src/widgets/backlight.rs index e13a771..de74c7a 100644 --- a/src/widgets/backlight.rs +++ b/src/widgets/backlight.rs @@ -39,6 +39,14 @@ impl Backlight { dev.update(); BarWidget::new(Box::new(dev), fm, font, size) } + + pub fn detect(path: &str) -> bool { + let path = Path::new("/sys/class/backlight").to_path_buf().join(path); + match read_file_as_u64(path.join("brightness").as_path()) { + Ok(_) => true, + Err(_) => false, + } + } } impl BarWidgetImpl for Backlight { diff --git a/src/widgets/battery.rs b/src/widgets/battery.rs index 53c1dc3..ef2c1d5 100644 --- a/src/widgets/battery.rs +++ b/src/widgets/battery.rs @@ -2,6 +2,7 @@ use std::{ sync::{Arc, Mutex}, thread, time::Duration, + error::Error, }; use dbus::blocking::{ @@ -46,6 +47,19 @@ struct InnerBattery { dirty: bool, } +fn get_battery_state() -> Result> { + let conn = LocalConnection::new_system()?; + let device_path = "/org/freedesktop/UPower/devices/DisplayDevice"; + let proxy = conn.with_proxy( + "org.freedesktop.UPower", + device_path, + Duration::from_millis(500), + ); + let state: u32 = proxy + .get("org.freedesktop.UPower.Device", "State")?; + Ok(UpowerBatteryState::from_dbus(state as u64)) +} + fn start_monitor(inner: Arc>, events: Arc>) { thread::Builder::new() .name("battmon".to_string()) @@ -131,6 +145,13 @@ impl Battery { start_monitor(battery.inner.clone(), events); BarWidget::new(Box::new(battery), fm, font, size) } + + pub fn detect() -> bool { + match get_battery_state() { + Ok(_) => true, + Err(_) => false, + } + } } impl BarWidgetImpl for Battery { From 8826cab89dd0ac24b568228cbebf96267a83a3d1 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Mon, 13 Feb 2023 17:39:54 +0100 Subject: [PATCH 22/57] keyboard: Add keyrepeat handling --- src/keyboard/mod.rs | 12 +-- src/keyboard/repeat.rs | 169 +++++++++++++++++++++++++++++++++++++++++ src/main.rs | 14 ++++ src/state.rs | 24 +++++- 4 files changed, 206 insertions(+), 13 deletions(-) create mode 100644 src/keyboard/repeat.rs diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs index 7281a50..ca7a82e 100644 --- a/src/keyboard/mod.rs +++ b/src/keyboard/mod.rs @@ -22,14 +22,14 @@ use xkbcommon::xkb; pub mod keysyms; mod state; +mod repeat; use self::state::KbState; pub use self::state::ModifiersState; +pub use self::repeat::{KeyRepeatSource, RepeatMessage}; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct KeyEvent { - pub serial: u32, - pub time: u32, pub rawkey: u32, pub keysym: u32, pub state: WEnum, @@ -78,8 +78,6 @@ impl Keyboard { pub fn key( &mut self, - serial: u32, - time: u32, key: u32, key_state: WEnum, ) -> KeyEvent { @@ -120,8 +118,6 @@ impl Keyboard { keysym: sym, state: key_state, modifiers: state.mods_state(), - serial, - time, utf8, repeats, } @@ -133,8 +129,6 @@ impl Keyboard { modifiers: Default::default(), utf8: None, repeats: false, - serial, - time, } } } diff --git a/src/keyboard/repeat.rs b/src/keyboard/repeat.rs new file mode 100644 index 0000000..a08a1c5 --- /dev/null +++ b/src/keyboard/repeat.rs @@ -0,0 +1,169 @@ +use std::time::Duration; + +use calloop::{ + channel::{self, Channel}, + timer::{TimeoutAction, Timer}, + EventSource, Poll, PostAction, Readiness, Token, TokenFactory, +}; +use wayland_client::{ + protocol::wl_keyboard, WEnum, +}; + +use super::KeyEvent; + +#[derive(Debug)] +pub enum RepeatMessage { + StopRepeat, + KeyEvent(KeyEvent), + RepeatInfo((u32, u32)) +} + +#[derive(Debug)] +pub struct KeyRepeatSource { + channel: Channel, + timer: Timer, + rate: Duration, + delay: Duration, + disabled: bool, + key: Option, +} + +impl KeyRepeatSource { + pub fn new(ch: channel::Channel) -> KeyRepeatSource { + KeyRepeatSource{ + channel: ch, + timer: Timer::immediate(), + delay: Duration::ZERO, + rate: Duration::ZERO, + disabled: true, + key: None, + } + } +} + +impl EventSource for KeyRepeatSource { + type Event = KeyEvent; + type Metadata = (); + type Ret = (); + type Error = calloop::Error; + + fn process_events( + &mut self, + readiness: Readiness, + token: Token, + mut callback: F, + ) -> calloop::Result + where + F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret, + { + let mut removed = false; + + let timer = &mut self.timer; + let rate = &mut self.rate; + let delay = &mut self.delay; + let key = &mut self.key; + let disabled = &mut self.disabled; + + let mut reregister = false; + + let mut next_deadline = std::time::Instant::now(); + + // Check if the key repeat should stop + let channel_pa = self + .channel + .process_events(readiness, token, |event, _| { + match event { + channel::Event::Msg(message) => { + match message { + RepeatMessage::StopRepeat => { + key.take(); + } + RepeatMessage::KeyEvent(event) => match event.state { + WEnum::Value(wl_keyboard::KeyState::Pressed) => { + key.replace(event); + reregister = true; + + next_deadline = std::time::Instant::now() + *delay; + timer.set_deadline(next_deadline); + } + WEnum::Value(wl_keyboard::KeyState::Released) => match key { + Some(k) if k.keysym == event.keysym => { + key.take(); + } + _ => (), + }, + _ => (), + } + RepeatMessage::RepeatInfo((new_rate, new_delay)) => { + *rate = Duration::from_micros(1_000_000 / new_rate as u64); + *delay = Duration::from_millis(new_delay as u64); + *disabled = false; + if key.is_some() { + next_deadline = std::time::Instant::now() + *delay; + timer.set_deadline(next_deadline); + } + } + } + } + + channel::Event::Closed => { + removed = true; + } + } + }) + .map_err(|err| calloop::Error::OtherError(Box::new(err)))?; + + // Keyboard was destroyed + if removed { + return Ok(PostAction::Remove); + } + + // Re-register the timer to start it again + if reregister { + return Ok(PostAction::Reregister); + } + + let timer_pa = timer.process_events(readiness, token, |_event, _| { + if self.disabled || key.is_none() { + return TimeoutAction::Drop; + } + + // Invoke the event + callback(key.as_ref().unwrap().clone(), &mut ()); + next_deadline += *rate; + TimeoutAction::ToInstant(next_deadline) + })?; + + // Only disable or remove if both want to, otherwise continue or re-register + Ok(match (timer_pa, channel_pa) { + (PostAction::Disable, PostAction::Disable) => PostAction::Disable, + (PostAction::Remove, PostAction::Remove) => PostAction::Remove, + (PostAction::Reregister, _) | (_, PostAction::Reregister) => PostAction::Reregister, + _ => PostAction::Continue, + }) + } + + fn register( + &mut self, + poll: &mut Poll, + token_factory: &mut TokenFactory, + ) -> calloop::Result<()> { + self.channel.register(poll, token_factory)?; + self.timer.register(poll, token_factory) + } + + fn reregister( + &mut self, + poll: &mut Poll, + token_factory: &mut TokenFactory, + ) -> calloop::Result<()> { + self.channel.reregister(poll, token_factory)?; + self.timer.reregister(poll, token_factory) + } + + fn unregister(&mut self, poll: &mut Poll) -> calloop::Result<()> { + self.channel.unregister(poll)?; + self.timer.unregister(poll) + } +} + diff --git a/src/main.rs b/src/main.rs index ce57e6f..7aed68c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,7 @@ use event::{Event, Events}; use fonts::{FontMap, MaybeFontMap}; use state::State; use widgets::{Geometry, Widget}; +use keyboard::{KeyRepeatSource, RepeatMessage}; use std::{env, rc::Rc, thread}; @@ -64,6 +65,7 @@ fn main() { display.get_registry(&qhandle, ()); let (ping_sender, ping_source) = calloop::ping::make_ping().unwrap(); + let (keyrepeat_sender, keyrepeat_channel) = calloop::channel::channel::(); let mut event_loop: EventLoop = EventLoop::try_new().expect("Failed to initialize the event loop!"); @@ -109,6 +111,17 @@ fn main() { }) .expect("Failed to insert event source!"); + let keyrepeat_source = KeyRepeatSource::new(keyrepeat_channel); + handle + .insert_source(keyrepeat_source, |event, _metadata, state| { + let ev = Event::KeyEvent(event); + for widget in state.widgets.iter_mut() { + widget.event(&ev); + } + state.dirty = true; + }) + .expect("Failed to insert keyrepeat source!"); + let events = Events::new(ping_sender); let mut fm = FontMap::new(); @@ -143,6 +156,7 @@ fn main() { layout, MaybeFontMap::Waiting(font_thread), events, + keyrepeat_sender, ); let mut damage = Vec::new(); diff --git a/src/state.rs b/src/state.rs index 487f7a8..8c61827 100644 --- a/src/state.rs +++ b/src/state.rs @@ -33,10 +33,12 @@ use crate::{ buffer::BufferManager, event::{Event, Events, PointerButton, PointerEvent}, fonts::{FontMap, MaybeFontMap}, - keyboard::Keyboard, + keyboard::{Keyboard, RepeatMessage}, widgets::{Geometry, Layout, Widget, WidgetUpdater}, }; +use calloop::channel::Sender; + #[derive(Default)] pub struct MainSurface { pub wl_surface: Option, @@ -71,6 +73,7 @@ pub struct State { pub bufmgr: BufferManager, pub widgets: Vec>, pub keyboard: Keyboard, + pub keyrepeat_sender: Sender, pointer: Option<(f64, f64)>, pub fonts: MaybeFontMap, pub events: Arc>, @@ -84,6 +87,7 @@ impl State { layout: Rc>, fonts: MaybeFontMap, events: Arc>, + keyrepeat_sender: Sender, ) -> State { State { protocols: Default::default(), @@ -97,6 +101,7 @@ impl State { bufmgr: BufferManager::new(), keyboard: Keyboard::new(), pointer: None, + keyrepeat_sender, mode, widgets, fonts, @@ -555,21 +560,29 @@ impl Dispatch for State { _: &QueueHandle, ) { match event { + wl_keyboard::Event::Leave { .. }=> { + state.keyrepeat_sender.send(RepeatMessage::StopRepeat).unwrap(); + } wl_keyboard::Event::Key { - serial, - time, key, state: kbstate, + .. } => { if key == 1 { state.running = false; return; } - let k = state.keyboard.key(serial, time, key, kbstate); + let k = state.keyboard.key(key, kbstate); + let repeats = k.repeats; let ev = Event::KeyEvent(k); for widget in state.widgets.iter_mut() { widget.event(&ev); } + if repeats { + if let Event::KeyEvent(k) = ev { + state.keyrepeat_sender.send(RepeatMessage::KeyEvent(k)).unwrap(); + } + } state.dirty = true; } wl_keyboard::Event::Modifiers { @@ -587,6 +600,9 @@ impl Dispatch for State { // TODO: Keymap loading blocks xdg configure, consider delaying state.keyboard.keymap(format, fd, size); } + wl_keyboard::Event::RepeatInfo { rate, delay } => { + state.keyrepeat_sender.send(RepeatMessage::RepeatInfo((rate as u32, delay as u32))).unwrap(); + } _ => (), } } From 022f6a719a2ac5c51425d06fe98648cfde230fbc Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Mon, 13 Feb 2023 18:49:44 +0100 Subject: [PATCH 23/57] cargo fmt --- src/config.rs | 67 ++++++++++++++++++++++++++++++++------- src/keyboard/mod.rs | 10 ++---- src/keyboard/repeat.rs | 71 +++++++++++++++++++----------------------- src/main.rs | 8 +++-- src/state.rs | 19 ++++++++--- src/widgets/battery.rs | 5 ++- 6 files changed, 111 insertions(+), 69 deletions(-) diff --git a/src/config.rs b/src/config.rs index 86491ca..08e2902 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,10 +6,11 @@ use std::sync::{Arc, Mutex}; use crate::{ event::Events, - fonts::{FontMap, find_font}, + fonts::{find_font, FontMap}, widgets::{ Backlight, Battery, Calendar, Clock, Date, HorizontalLayout, IndexedLayout, Interface, - InvertedHorizontalLayout, Layout, Line, Margin, PulseAudio, VerticalLayout, Widget as RealWidget, + InvertedHorizontalLayout, Layout, Line, Margin, PulseAudio, VerticalLayout, + Widget as RealWidget, }, }; @@ -81,7 +82,7 @@ pub struct Config { impl Default for Config { fn default() -> Config { - Config{ + Config { font_paths: None, widget: Default::default(), mode: Default::default(), @@ -98,12 +99,31 @@ impl Config { let mut bars = Vec::new(); if has_battery { - bars.push(Widget::Margin{ margins: (0, 0, 0, 8), widget: Box::new(Widget::Battery { font: None, font_size: 24. })}); + bars.push(Widget::Margin { + margins: (0, 0, 0, 8), + widget: Box::new(Widget::Battery { + font: None, + font_size: 24., + }), + }); } if has_backlight { - bars.push(Widget::Margin{ margins: (0, 0, 0, 8), widget: Box::new(Widget::Backlight { device: Some("intel_backlight".to_string()), font: None, font_size: 24. })}); + bars.push(Widget::Margin { + margins: (0, 0, 0, 8), + widget: Box::new(Widget::Backlight { + device: Some("intel_backlight".to_string()), + font: None, + font_size: 24., + }), + }); } - bars.push(Widget::Margin{ margins: (0, 0, 0, 8), widget: Box::new(Widget::PulseAudio { font: None, font_size: 24. })}); + bars.push(Widget::Margin { + margins: (0, 0, 0, 8), + widget: Box::new(Widget::PulseAudio { + font: None, + font_size: 24., + }), + }); let widget = Widget::Margin { margins: (20, 20, 20, 20), @@ -143,7 +163,7 @@ impl Config { ("monospace".to_string(), monospace), ]); - Config{ + Config { font_paths: Some(map), widget: widget, mode: OperationMode::LayerSurface((1024, 768)), @@ -158,12 +178,31 @@ impl Config { let mut bars = Vec::new(); if has_battery { - bars.push(Widget::Margin{ margins: (16, 8, 8, 0), widget: Box::new(Widget::Battery { font: None, font_size: 24. })}); + bars.push(Widget::Margin { + margins: (16, 8, 8, 0), + widget: Box::new(Widget::Battery { + font: None, + font_size: 24., + }), + }); } if has_backlight { - bars.push(Widget::Margin{ margins: (16, 8, 8, 0), widget: Box::new(Widget::Backlight { device: Some("intel_backlight".to_string()), font: None, font_size: 24. })}); + bars.push(Widget::Margin { + margins: (16, 8, 8, 0), + widget: Box::new(Widget::Backlight { + device: Some("intel_backlight".to_string()), + font: None, + font_size: 24., + }), + }); } - bars.push(Widget::Margin{ margins: (16, 8, 8, 0), widget: Box::new(Widget::PulseAudio { font: None, font_size: 24. })}); + bars.push(Widget::Margin { + margins: (16, 8, 8, 0), + widget: Box::new(Widget::PulseAudio { + font: None, + font_size: 24., + }), + }); let widget = Widget::VerticalLayout(vec![ Widget::HorizontalLayout(vec![ @@ -201,10 +240,14 @@ impl Config { ("monospace".to_string(), monospace), ]); - Config{ + Config { font_paths: Some(map), widget: widget, - mode: if clay { OperationMode::XdgToplevel } else { OperationMode::LayerSurface((1024, 768)) }, + mode: if clay { + OperationMode::XdgToplevel + } else { + OperationMode::LayerSurface((1024, 768)) + }, } } } diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs index ca7a82e..ac1021e 100644 --- a/src/keyboard/mod.rs +++ b/src/keyboard/mod.rs @@ -21,12 +21,12 @@ use wayland_client::{protocol::wl_keyboard, WEnum}; use xkbcommon::xkb; pub mod keysyms; -mod state; mod repeat; +mod state; +pub use self::repeat::{KeyRepeatSource, RepeatMessage}; use self::state::KbState; pub use self::state::ModifiersState; -pub use self::repeat::{KeyRepeatSource, RepeatMessage}; #[derive(Debug, Clone)] pub struct KeyEvent { @@ -76,11 +76,7 @@ impl Keyboard { } } - pub fn key( - &mut self, - key: u32, - key_state: WEnum, - ) -> KeyEvent { + pub fn key(&mut self, key: u32, key_state: WEnum) -> KeyEvent { if let Some(state) = &mut self.state { let (sym, utf8, repeats) = { // Get the values to generate a key event diff --git a/src/keyboard/repeat.rs b/src/keyboard/repeat.rs index a08a1c5..318e647 100644 --- a/src/keyboard/repeat.rs +++ b/src/keyboard/repeat.rs @@ -5,9 +5,7 @@ use calloop::{ timer::{TimeoutAction, Timer}, EventSource, Poll, PostAction, Readiness, Token, TokenFactory, }; -use wayland_client::{ - protocol::wl_keyboard, WEnum, -}; +use wayland_client::{protocol::wl_keyboard, WEnum}; use super::KeyEvent; @@ -15,7 +13,7 @@ use super::KeyEvent; pub enum RepeatMessage { StopRepeat, KeyEvent(KeyEvent), - RepeatInfo((u32, u32)) + RepeatInfo((u32, u32)), } #[derive(Debug)] @@ -30,7 +28,7 @@ pub struct KeyRepeatSource { impl KeyRepeatSource { pub fn new(ch: channel::Channel) -> KeyRepeatSource { - KeyRepeatSource{ + KeyRepeatSource { channel: ch, timer: Timer::immediate(), delay: Duration::ZERO, @@ -71,44 +69,40 @@ impl EventSource for KeyRepeatSource { // Check if the key repeat should stop let channel_pa = self .channel - .process_events(readiness, token, |event, _| { - match event { - channel::Event::Msg(message) => { - match message { - RepeatMessage::StopRepeat => { + .process_events(readiness, token, |event, _| match event { + channel::Event::Msg(message) => match message { + RepeatMessage::StopRepeat => { + key.take(); + } + RepeatMessage::KeyEvent(event) => match event.state { + WEnum::Value(wl_keyboard::KeyState::Pressed) => { + key.replace(event); + reregister = true; + + next_deadline = std::time::Instant::now() + *delay; + timer.set_deadline(next_deadline); + } + WEnum::Value(wl_keyboard::KeyState::Released) => match key { + Some(k) if k.keysym == event.keysym => { key.take(); } - RepeatMessage::KeyEvent(event) => match event.state { - WEnum::Value(wl_keyboard::KeyState::Pressed) => { - key.replace(event); - reregister = true; - - next_deadline = std::time::Instant::now() + *delay; - timer.set_deadline(next_deadline); - } - WEnum::Value(wl_keyboard::KeyState::Released) => match key { - Some(k) if k.keysym == event.keysym => { - key.take(); - } - _ => (), - }, - _ => (), - } - RepeatMessage::RepeatInfo((new_rate, new_delay)) => { - *rate = Duration::from_micros(1_000_000 / new_rate as u64); - *delay = Duration::from_millis(new_delay as u64); - *disabled = false; - if key.is_some() { - next_deadline = std::time::Instant::now() + *delay; - timer.set_deadline(next_deadline); - } - } + _ => (), + }, + _ => (), + }, + RepeatMessage::RepeatInfo((new_rate, new_delay)) => { + *rate = Duration::from_micros(1_000_000 / new_rate as u64); + *delay = Duration::from_millis(new_delay as u64); + *disabled = false; + if key.is_some() { + next_deadline = std::time::Instant::now() + *delay; + timer.set_deadline(next_deadline); } } + }, - channel::Event::Closed => { - removed = true; - } + channel::Event::Closed => { + removed = true; } }) .map_err(|err| calloop::Error::OtherError(Box::new(err)))?; @@ -166,4 +160,3 @@ impl EventSource for KeyRepeatSource { self.timer.unregister(poll) } } - diff --git a/src/main.rs b/src/main.rs index 7aed68c..2b0ea5a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,9 +23,9 @@ use buffer::BufferView; use config::Config; use event::{Event, Events}; use fonts::{FontMap, MaybeFontMap}; +use keyboard::{KeyRepeatSource, RepeatMessage}; use state::State; use widgets::{Geometry, Widget}; -use keyboard::{KeyRepeatSource, RepeatMessage}; use std::{env, rc::Rc, thread}; @@ -50,7 +50,7 @@ fn main() { Err(_) => panic!("uh"), } std::process::exit(0); - }, + } Some(_) => panic!("unknown argument"), None => break, } @@ -140,7 +140,9 @@ fn main() { let layout = Rc::new(config.widget.construct_layout(&mut 0)); let mut widgets: Vec> = Vec::new(); - config.widget.construct_widgets(&mut widgets, &mut fm, &events); + config + .widget + .construct_widgets(&mut widgets, &mut fm, &events); let font_thread = thread::Builder::new() .name("fontloader".to_string()) diff --git a/src/state.rs b/src/state.rs index 8c61827..4a8af78 100644 --- a/src/state.rs +++ b/src/state.rs @@ -29,8 +29,8 @@ use wayland_protocols::{ use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1}; use crate::{ - config::OperationMode, buffer::BufferManager, + config::OperationMode, event::{Event, Events, PointerButton, PointerEvent}, fonts::{FontMap, MaybeFontMap}, keyboard::{Keyboard, RepeatMessage}, @@ -560,8 +560,11 @@ impl Dispatch for State { _: &QueueHandle, ) { match event { - wl_keyboard::Event::Leave { .. }=> { - state.keyrepeat_sender.send(RepeatMessage::StopRepeat).unwrap(); + wl_keyboard::Event::Leave { .. } => { + state + .keyrepeat_sender + .send(RepeatMessage::StopRepeat) + .unwrap(); } wl_keyboard::Event::Key { key, @@ -580,7 +583,10 @@ impl Dispatch for State { } if repeats { if let Event::KeyEvent(k) = ev { - state.keyrepeat_sender.send(RepeatMessage::KeyEvent(k)).unwrap(); + state + .keyrepeat_sender + .send(RepeatMessage::KeyEvent(k)) + .unwrap(); } } state.dirty = true; @@ -601,7 +607,10 @@ impl Dispatch for State { state.keyboard.keymap(format, fd, size); } wl_keyboard::Event::RepeatInfo { rate, delay } => { - state.keyrepeat_sender.send(RepeatMessage::RepeatInfo((rate as u32, delay as u32))).unwrap(); + state + .keyrepeat_sender + .send(RepeatMessage::RepeatInfo((rate as u32, delay as u32))) + .unwrap(); } _ => (), } diff --git a/src/widgets/battery.rs b/src/widgets/battery.rs index ef2c1d5..61121e6 100644 --- a/src/widgets/battery.rs +++ b/src/widgets/battery.rs @@ -1,8 +1,8 @@ use std::{ + error::Error, sync::{Arc, Mutex}, thread, time::Duration, - error::Error, }; use dbus::blocking::{ @@ -55,8 +55,7 @@ fn get_battery_state() -> Result> { device_path, Duration::from_millis(500), ); - let state: u32 = proxy - .get("org.freedesktop.UPower.Device", "State")?; + let state: u32 = proxy.get("org.freedesktop.UPower.Device", "State")?; Ok(UpowerBatteryState::from_dbus(state as u64)) } From 59e2f658dbade0a5ca53cdad63c92aeb9d3a6edf Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Mon, 13 Feb 2023 19:43:15 +0100 Subject: [PATCH 24/57] keyboard: Rename realize to resolve --- src/keyboard/mod.rs | 2 +- src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs index ac1021e..a3afa4c 100644 --- a/src/keyboard/mod.rs +++ b/src/keyboard/mod.rs @@ -63,7 +63,7 @@ impl Keyboard { } } - pub fn realize(&mut self) { + pub fn resolve(&mut self) { if self.state.is_some() { return; } diff --git a/src/main.rs b/src/main.rs index 2b0ea5a..111fdee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -241,6 +241,6 @@ fn main() { surface.attach(Some(&buf.buffer), 0, 0); surface.commit(); conn.flush().unwrap(); - state.keyboard.realize(); + state.keyboard.resolve(); } } From b09f642fd6890c341289fec1e305af149b28ff74 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Mon, 13 Feb 2023 19:43:36 +0100 Subject: [PATCH 25/57] Avoid panic on very long prompt lines --- src/draw.rs | 16 ++++++++++++---- src/widgets/launcher.rs | 3 ++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/draw.rs b/src/draw.rs index 5f46a1d..1de21ae 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -47,10 +47,15 @@ impl CachedGlyph { } fn draw(&self, buf: &mut BufferView, pos: (i32, i32), c: Color) { + let bounds = buf.get_bounds(); + if pos.0 as u32 + self.dimensions.0 > bounds.2 { + return; + } + + let offset = (bounds.0 as i32, bounds.1 as i32); + let width = self.dimensions.0 as i32 - 1; let mut x = 0; let mut y = 0; - let bounds = buf.get_bounds(); - let offset = (bounds.0 as i32, bounds.1 as i32); for v in &self.render { buf.put_raw( ( @@ -60,7 +65,7 @@ impl CachedGlyph { c.alpha(*v), ); - if x == self.dimensions.0 as i32 - 1 { + if x == width { y += 1; x = 0; } else { @@ -173,7 +178,7 @@ impl<'a> Font<'a> { Ok((x_off as u32, self.size as u32)) } - pub fn draw_cursor( + fn draw_cursor( &self, buf: &mut BufferView, c: Color, @@ -182,6 +187,9 @@ impl<'a> Font<'a> { ) -> Result<(), ::std::io::Error> { // draw cursor let bounds = buf.get_bounds(); + if offset + 1 > bounds.2 { + return Ok(()); + } for i in bounds.1 + 1..height + bounds.1 { buf.put_raw((bounds.0 + offset, i), c); } diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs index 118626a..43ac8de 100644 --- a/src/widgets/launcher.rs +++ b/src/widgets/launcher.rs @@ -356,10 +356,11 @@ impl InterfaceWidget for Launcher { intf.geometry.height, (self.matches.len() + 1) as u32 * line_height + 8, ); + let content_width = min(intf.geometry.width, x_max.0 + 1); Geometry { x: intf.geometry.x, y: intf.geometry.y + intf.geometry.height - content_height, - width: x_max.0 + 1, + width: content_width, height: content_height, } } From 8202963a6f1e64b6f1791bc827728c73598dcbf6 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Tue, 14 Feb 2023 00:04:19 +0100 Subject: [PATCH 26/57] Add vertical line --- src/config.rs | 44 +++++++++++++++++++++++++++----------------- src/widgets/line.rs | 20 +++++++++++++++----- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/src/config.rs b/src/config.rs index 08e2902..58eb3f4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -31,7 +31,8 @@ pub enum Widget { HorizontalLayout(Vec), InvertedHorizontalLayout(Vec), VerticalLayout(Vec), - Line(u32), + HorizontalLine(u32), + VerticalLine(u32), Clock { font: Option, font_size: f32, @@ -219,15 +220,19 @@ impl Config { }, Widget::VerticalLayout(bars), ]), - Widget::Line(1), + Widget::HorizontalLine(1), Widget::InvertedHorizontalLayout(vec![ - Widget::Calendar { - font_primary: None, - font_secondary: None, - font_size: 24., - sections_x: 1, - sections_y: -1, + Widget::Margin { + margins: (16, 8, 16, 0), + widget: Box::new(Widget::Calendar { + font_primary: None, + font_secondary: None, + font_size: 24., + sections_x: 1, + sections_y: if clay { -1 } else { 3 }, + }), }, + Widget::VerticalLine(1), Widget::Launcher { font: None, font_size: 32., @@ -246,7 +251,7 @@ impl Config { mode: if clay { OperationMode::XdgToplevel } else { - OperationMode::LayerSurface((1024, 768)) + OperationMode::LayerSurface((1024, 1)) }, } } @@ -299,15 +304,19 @@ impl Default for Widget { }, ]), ]), - Widget::Line(1), + Widget::HorizontalLine(1), Widget::InvertedHorizontalLayout(vec![ - Widget::Calendar { - font_primary: None, - font_secondary: None, - font_size: 24., - sections_x: 1, - sections_y: -1, + Widget::Margin { + margins: (16, 8, 16, 0), + widget: Box::new(Widget::Calendar { + font_primary: None, + font_secondary: None, + font_size: 24., + sections_x: 1, + sections_y: -1, + }), }, + Widget::VerticalLine(1), Widget::Launcher { font: None, font_size: 32., @@ -335,7 +344,8 @@ impl Widget { Widget::VerticalLayout(widgets) => widgets .into_iter() .for_each(|w| w.construct_widgets(v, fm, events)), - Widget::Line(width) => v.push(Box::new(Line::new(width))), + Widget::HorizontalLine(width) => v.push(Box::new(Line::new(width, false))), + Widget::VerticalLine(width) => v.push(Box::new(Line::new(width, true))), Widget::Clock { font, font_size } => v.push(Box::new(Clock::new( &mut fm, leak_or_default(font, "sans"), diff --git a/src/widgets/line.rs b/src/widgets/line.rs index 354752b..a177121 100644 --- a/src/widgets/line.rs +++ b/src/widgets/line.rs @@ -10,13 +10,15 @@ use crate::{ pub struct Line { geometry: Geometry, thickness: u32, + vertical: bool, } impl Line { - pub fn new(thickness: u32) -> Line { + pub fn new(thickness: u32, vertical: bool) -> Line { Line { geometry: Default::default(), thickness, + vertical, } } } @@ -35,8 +37,16 @@ impl Widget for Line { self.geometry = Geometry { x: geometry.x, y: geometry.y, - width: geometry.width, - height: self.thickness, + width: if self.vertical { + self.thickness + } else { + geometry.width + }, + height: if self.vertical { + geometry.height + } else { + self.thickness + }, }; self.geometry } @@ -45,8 +55,8 @@ impl Widget for Line { Geometry { x: 0, y: 0, - width: 0, - height: self.thickness, + width: if self.vertical { self.thickness } else { 1 }, + height: if self.vertical { 1 } else { self.thickness }, } } } From 5097faab66792884e5e527a9192a272fe20cf5d2 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Tue, 14 Feb 2023 00:04:58 +0100 Subject: [PATCH 27/57] launcher: Save prompt on exit --- src/event.rs | 1 + src/keyboard/keysyms.rs | 3 ++- src/state.rs | 6 ++++-- src/widgets/launcher.rs | 21 +++++++++++++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/event.rs b/src/event.rs index eb0811e..b9b2b9d 100644 --- a/src/event.rs +++ b/src/event.rs @@ -28,6 +28,7 @@ pub enum Event { KeyEvent(KeyEvent), PointerEvent(PointerEvent), TokenUpdate(String), + FocusLost, } pub struct Events { diff --git a/src/keyboard/keysyms.rs b/src/keyboard/keysyms.rs index 898303f..7b413fd 100644 --- a/src/keyboard/keysyms.rs +++ b/src/keyboard/keysyms.rs @@ -3,7 +3,7 @@ pub const XKB_KEY_BackSpace: u32 = 0xff08; /* Back space, back char */ //pub const XKB_KEY_Tab: u32 = 0xff09; pub const XKB_KEY_Return: u32 = 0xff0d; /* Return, enter */ -//pub const XKB_KEY_Escape: u32 = 0xff1b; +pub const XKB_KEY_Escape: u32 = 0xff1b; pub const XKB_KEY_Delete: u32 = 0xffff; /* Delete, rubout */ pub const XKB_KEY_Home: u32 = 0xff50; pub const XKB_KEY_Left: u32 = 0xff51; /* Move left, left arrow */ @@ -17,4 +17,5 @@ pub const XKB_KEY_a: u32 = 0x0061; /* U+0061 LATIN SMALL LETTER A */ pub const XKB_KEY_e: u32 = 0x0065; /* U+0065 LATIN SMALL LETTER E */ //pub const XKB_KEY_c: u32 = 0x0063; /* U+0063 LATIN SMALL LETTER C */ pub const XKB_KEY_k: u32 = 0x006b; /* U+006B LATIN SMALL LETTER K */ +pub const XKB_KEY_r: u32 = 0x0072; /* U+0072 LATIN SMALL LETTER R */ pub const XKB_KEY_u: u32 = 0x0075; /* U+0075 LATIN SMALL LETTER U */ diff --git a/src/state.rs b/src/state.rs index 4a8af78..992e8ad 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2,7 +2,6 @@ use std::{ cmp::max, default::Default, env, - process::exit, rc::Rc, sync::{Arc, Mutex}, }; @@ -498,7 +497,10 @@ impl Dispatch for State { if activated { state.activated = true; } else if state.activated && !activated { - exit(0); + let ev = Event::FocusLost; + for widget in state.widgets.iter_mut() { + widget.event(&ev); + } } if state.dimensions != (width, height) { diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs index 43ac8de..1531062 100644 --- a/src/widgets/launcher.rs +++ b/src/widgets/launcher.rs @@ -629,6 +629,19 @@ impl Interface { } } + fn exit(&mut self) { + if self.inner.prompt.input.len() == 0 { + std::process::exit(0); + } + + let home = env::var_os("HOME").unwrap().into_string().unwrap(); + if let Ok(mut f) = File::create(format!("{}/.cache/wldash/prompt", home)) { + write!(f, "{}", self.inner.prompt.input).unwrap(); + f.sync_data().unwrap(); + } + std::process::exit(0); + } + fn keyboard_input(&mut self, event: &KeyEvent) { if event.state != WEnum::Value(wl_keyboard::KeyState::Pressed) { return; @@ -652,6 +665,12 @@ impl Interface { self.inner.selection = 0; widget.update(&self.inner); } + keysyms::XKB_KEY_r if event.modifiers.ctrl => { + let home = env::var_os("HOME").unwrap().into_string().unwrap(); + if let Ok(s) = read_to_string(format!("{}/.cache/wldash/prompt", home)) { + self.inner.prompt.set(&s); + } + } keysyms::XKB_KEY_Home => self.inner.prompt.home(), keysyms::XKB_KEY_End => self.inner.prompt.end(), keysyms::XKB_KEY_BackSpace => { @@ -664,6 +683,7 @@ impl Interface { self.inner.selection = 0; widget.update(&self.inner); } + keysyms::XKB_KEY_Escape => self.exit(), keysyms::XKB_KEY_Return => widget.trigger(&mut self.inner), keysyms::XKB_KEY_Left => self.inner.prompt.move_cursor(-1), keysyms::XKB_KEY_Right => self.inner.prompt.move_cursor(1), @@ -724,6 +744,7 @@ impl Widget for Interface { match event { Event::KeyEvent(e) => self.keyboard_input(e), Event::PointerEvent(e) => self.pointer_input(e), + Event::FocusLost => self.exit(), Event::LauncherUpdate => { self.inner.dirty = true; self.launcher.update(&self.inner); From 6fac465c3a5c7de8faf9c94e7261318d93f011eb Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Tue, 14 Feb 2023 00:28:11 +0100 Subject: [PATCH 28/57] calendar: Highlight current week --- src/widgets/calendar.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/calendar.rs b/src/widgets/calendar.rs index 2451115..bb4b9fa 100644 --- a/src/widgets/calendar.rs +++ b/src/widgets/calendar.rs @@ -151,7 +151,7 @@ impl Calendar { y_off * (self.size * LINE_HEIGHT).ceil() as u32 + (self.size * MONTH_FACTOR).ceil() as u32, )), - Color::GREY75, + if wk == orig.iso_week() { Color::WHITE } else { Color::GREY75 }, &format!("{:02}", wk.week()), ) .unwrap(); From 764a1e3444d847413a6a448fe513dea68eed740d Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Tue, 14 Feb 2023 00:30:46 +0100 Subject: [PATCH 29/57] config: Remove default configuration --- src/config.rs | 72 --------------------------------------------------- src/main.rs | 2 +- 2 files changed, 1 insertion(+), 73 deletions(-) diff --git a/src/config.rs b/src/config.rs index 58eb3f4..b0a6011 100644 --- a/src/config.rs +++ b/src/config.rs @@ -81,16 +81,6 @@ pub struct Config { pub mode: OperationMode, } -impl Default for Config { - fn default() -> Config { - Config { - font_paths: None, - widget: Default::default(), - mode: Default::default(), - } - } -} - impl Config { pub fn generate_v1() -> Config { let has_battery = Battery::detect(); @@ -264,68 +254,6 @@ fn leak_or_default(v: Option, d: &'static str) -> &'static str { } } -impl Default for Widget { - fn default() -> Widget { - Widget::VerticalLayout(vec![ - Widget::HorizontalLayout(vec![ - Widget::Clock { - font: None, - font_size: 128., - }, - Widget::Margin { - margins: (16, 16, 0, 0), - widget: Box::new(Widget::Date { - font: None, - font_size: 48., - }), - }, - Widget::VerticalLayout(vec![ - Widget::Margin { - margins: (16, 8, 8, 0), - widget: Box::new(Widget::Battery { - font: None, - font_size: 24., - }), - }, - Widget::Margin { - margins: (16, 8, 8, 0), - widget: Box::new(Widget::Backlight { - device: None, - font: None, - font_size: 24., - }), - }, - Widget::Margin { - margins: (16, 8, 8, 0), - widget: Box::new(Widget::PulseAudio { - font: None, - font_size: 24., - }), - }, - ]), - ]), - Widget::HorizontalLine(1), - Widget::InvertedHorizontalLayout(vec![ - Widget::Margin { - margins: (16, 8, 16, 0), - widget: Box::new(Widget::Calendar { - font_primary: None, - font_secondary: None, - font_size: 24., - sections_x: 1, - sections_y: -1, - }), - }, - Widget::VerticalLine(1), - Widget::Launcher { - font: None, - font_size: 32., - }, - ]), - ]) - } -} - impl Widget { pub fn construct_widgets( self, diff --git a/src/main.rs b/src/main.rs index 111fdee..143d048 100644 --- a/src/main.rs +++ b/src/main.rs @@ -129,7 +129,7 @@ fn main() { let home = env::var_os("HOME").unwrap().into_string().unwrap(); let config: Config = match File::open(format!("{}/.config/wldash/config.yml", home)) { Ok(f) => serde_yaml::from_reader(f).unwrap(), - Err(_) => Default::default(), + Err(_) => panic!("configuration file missing"), }; if let Some(font_paths) = config.font_paths { From 54e4ba16dd73cb59a0979434e570dc7165a84f69 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Tue, 14 Feb 2023 10:23:12 +0100 Subject: [PATCH 30/57] Add InvertedVerticalLayout --- src/config.rs | 11 +++- src/widgets/layout.rs | 137 ++++++++++++++++++++++++++++-------------- src/widgets/mod.rs | 4 +- 3 files changed, 104 insertions(+), 48 deletions(-) diff --git a/src/config.rs b/src/config.rs index b0a6011..9066650 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,8 +9,8 @@ use crate::{ fonts::{find_font, FontMap}, widgets::{ Backlight, Battery, Calendar, Clock, Date, HorizontalLayout, IndexedLayout, Interface, - InvertedHorizontalLayout, Layout, Line, Margin, PulseAudio, VerticalLayout, - Widget as RealWidget, + InvertedHorizontalLayout, InvertedVerticalLayout, Layout, Line, Margin, PulseAudio, + VerticalLayout, Widget as RealWidget, }, }; @@ -31,6 +31,7 @@ pub enum Widget { HorizontalLayout(Vec), InvertedHorizontalLayout(Vec), VerticalLayout(Vec), + InvertedVerticalLayout(Vec), HorizontalLine(u32), VerticalLine(u32), Clock { @@ -272,6 +273,9 @@ impl Widget { Widget::VerticalLayout(widgets) => widgets .into_iter() .for_each(|w| w.construct_widgets(v, fm, events)), + Widget::InvertedVerticalLayout(widgets) => widgets + .into_iter() + .for_each(|w| w.construct_widgets(v, fm, events)), Widget::HorizontalLine(width) => v.push(Box::new(Line::new(width, false))), Widget::VerticalLine(width) => v.push(Box::new(Line::new(width, true))), Widget::Clock { font, font_size } => v.push(Box::new(Clock::new( @@ -343,6 +347,9 @@ impl Widget { Widget::VerticalLayout(widgets) => { VerticalLayout::new(widgets.iter().map(|w| w.construct_layout(idx)).collect()) } + Widget::InvertedVerticalLayout(widgets) => InvertedVerticalLayout::new( + widgets.iter().map(|w| w.construct_layout(idx)).collect(), + ), _ => IndexedLayout::new({ let i = *idx; *idx += 1; diff --git a/src/widgets/layout.rs b/src/widgets/layout.rs index fb958ed..1009fad 100644 --- a/src/widgets/layout.rs +++ b/src/widgets/layout.rs @@ -142,50 +142,6 @@ impl Layout for IndexedLayout { } } -pub struct Margin { - pub widget: Box, - pub margin: (u32, u32, u32, u32), -} - -impl Margin { - pub fn new(widget: Box, margin: (u32, u32, u32, u32)) -> Box { - Box::new(Margin { widget, margin }) - } -} - -impl Layout for Margin { - fn geometry_update( - &self, - fonts: &mut FontMap, - geometry: &Geometry, - state: &mut State, - ) -> Geometry { - let geo = Geometry { - x: geometry.x + self.margin.0, - y: geometry.y + self.margin.1, - width: geometry.width - self.margin.0 - self.margin.2, - height: geometry.height - self.margin.1 - self.margin.3, - }; - let out = self.widget.geometry_update(fonts, &geo, state); - Geometry { - x: out.x - self.margin.0, - y: out.y - self.margin.1, - width: out.width + self.margin.0 + self.margin.2, - height: out.height + self.margin.1 + self.margin.3, - } - } - - fn minimum_size(&self, fonts: &mut FontMap, state: &mut State) -> Geometry { - let size = self.widget.minimum_size(fonts, state); - Geometry { - width: size.x + size.width + self.margin.0 + self.margin.2, - height: size.y + size.height + self.margin.1 + self.margin.3, - x: 0, - y: 0, - } - } -} - pub struct InvertedHorizontalLayout { pub widgets: Vec>, } @@ -233,3 +189,96 @@ impl Layout for InvertedHorizontalLayout { } } } + +pub struct InvertedVerticalLayout { + pub widgets: Vec>, +} + +impl InvertedVerticalLayout { + pub fn new(widgets: Vec>) -> Box { + Box::new(InvertedVerticalLayout { widgets }) + } +} + +impl Layout for InvertedVerticalLayout { + fn geometry_update( + &self, + fonts: &mut FontMap, + geometry: &Geometry, + state: &mut State, + ) -> Geometry { + let mut geo = geometry.clone(); + let mut max_width = 0; + for w in self.widgets.iter() { + let mut temp_geo = geo.clone(); + let temp_result = w.geometry_update(fonts, &temp_geo, state); + temp_geo.y = geo.y + (geo.height - temp_result.height); + let result = w.geometry_update(fonts, &temp_geo, state); + geo.height -= result.height; + max_width = max(result.width, max_width); + } + geo.width = max_width; + geo + } + + fn minimum_size(&self, fonts: &mut FontMap, state: &mut State) -> Geometry { + let mut max_width = 0; + let mut max_height = 0; + for w in self.widgets.iter() { + let result = w.minimum_size(fonts, state); + max_width = max(result.x + result.width, max_width); + max_height += result.y + result.height; + } + Geometry { + width: max_width, + height: max_height, + x: 0, + y: 0, + } + } +} + +pub struct Margin { + pub widget: Box, + pub margin: (u32, u32, u32, u32), +} + +impl Margin { + pub fn new(widget: Box, margin: (u32, u32, u32, u32)) -> Box { + Box::new(Margin { widget, margin }) + } +} + +impl Layout for Margin { + fn geometry_update( + &self, + fonts: &mut FontMap, + geometry: &Geometry, + state: &mut State, + ) -> Geometry { + let geo = Geometry { + x: geometry.x + self.margin.0, + y: geometry.y + self.margin.1, + width: geometry.width - self.margin.0 - self.margin.2, + height: geometry.height - self.margin.1 - self.margin.3, + }; + let out = self.widget.geometry_update(fonts, &geo, state); + Geometry { + x: out.x - self.margin.0, + y: out.y - self.margin.1, + width: out.width + self.margin.0 + self.margin.2, + height: out.height + self.margin.1 + self.margin.3, + } + } + + fn minimum_size(&self, fonts: &mut FontMap, state: &mut State) -> Geometry { + let size = self.widget.minimum_size(fonts, state); + Geometry { + width: size.x + size.width + self.margin.0 + self.margin.2, + height: size.y + size.height + self.margin.1 + self.margin.3, + x: 0, + y: 0, + } + } +} + diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 4b7dd68..cbcf071 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -19,8 +19,8 @@ pub use clock::Clock; pub use date::Date; pub use launcher::Interface; pub use layout::{ - HorizontalLayout, IndexedLayout, InvertedHorizontalLayout, Layout, Margin, VerticalLayout, - WidgetUpdater, + HorizontalLayout, IndexedLayout, InvertedHorizontalLayout, InvertedVerticalLayout, Layout, + Margin, VerticalLayout, WidgetUpdater, }; pub use line::Line; pub use pulseaudio::PulseAudio; From 9d1f310f12269ed8674a1b5e6bb6c6b0559f969e Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Tue, 14 Feb 2023 10:23:20 +0100 Subject: [PATCH 31/57] cargo fmt --- src/widgets/calendar.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/widgets/calendar.rs b/src/widgets/calendar.rs index bb4b9fa..b7ea90e 100644 --- a/src/widgets/calendar.rs +++ b/src/widgets/calendar.rs @@ -151,7 +151,11 @@ impl Calendar { y_off * (self.size * LINE_HEIGHT).ceil() as u32 + (self.size * MONTH_FACTOR).ceil() as u32, )), - if wk == orig.iso_week() { Color::WHITE } else { Color::GREY75 }, + if wk == orig.iso_week() { + Color::WHITE + } else { + Color::GREY75 + }, &format!("{:02}", wk.week()), ) .unwrap(); From a975da5074d3cd1e8ffefcff2ddd8b1830a78c3a Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Tue, 14 Feb 2023 23:52:54 +0100 Subject: [PATCH 32/57] config: Apply margins to default --- src/config.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/config.rs b/src/config.rs index 9066650..ecbaa41 100644 --- a/src/config.rs +++ b/src/config.rs @@ -198,12 +198,15 @@ impl Config { let widget = Widget::VerticalLayout(vec![ Widget::HorizontalLayout(vec![ - Widget::Clock { - font: None, - font_size: 128., + Widget::Margin { + margins: (8, 0, 0, 0), + widget: Box::new(Widget::Clock { + font: None, + font_size: 128., + }), }, Widget::Margin { - margins: (16, 16, 0, 0), + margins: (16, 64, 0, 0), widget: Box::new(Widget::Date { font: None, font_size: 48., @@ -224,9 +227,12 @@ impl Config { }), }, Widget::VerticalLine(1), - Widget::Launcher { - font: None, - font_size: 32., + Widget::Margin { + margins: (8, 0, 0, 0), + widget: Box::new(Widget::Launcher { + font: None, + font_size: 32., + }), }, ]), ]); From 3a77696569ff3ca888342e1dbf9520a935c05ed2 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Wed, 1 Mar 2023 11:48:25 +0100 Subject: [PATCH 33/57] calendar: Missed preload of 'N' --- src/widgets/calendar.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/calendar.rs b/src/widgets/calendar.rs index b7ea90e..bfa3b77 100644 --- a/src/widgets/calendar.rs +++ b/src/widgets/calendar.rs @@ -44,7 +44,7 @@ impl Calendar { fm.queue_font(font, size * DAY_FACTOR, "ADEFHIMNORSTUW"); fm.queue_font(font, size * YEAR_FACTOR, "0123456789"); fm.queue_font(font, size * DATE_FACTOR, "0123456789"); - fm.queue_font(month_font, size * MONTH_FACTOR, "ADFJMOSabcehgilmnoprstuvy"); + fm.queue_font(month_font, size * MONTH_FACTOR, "ADFJMNOSabcehgilmnoprstuvy"); Calendar { font, From 66e66e19ad980cf8fc627d16e15462fc35ac98d5 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Tue, 4 Apr 2023 01:53:02 +0200 Subject: [PATCH 34/57] launcher: Cache desktop files --- Cargo.lock | 149 +++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + src/main.rs | 12 ++-- src/utils/desktop.rs | 37 +++++----- src/utils/mod.rs | 1 + src/utils/xdg.rs | 39 +++++++++++ src/widgets/launcher.rs | 27 +++++--- 7 files changed, 234 insertions(+), 32 deletions(-) create mode 100644 src/utils/xdg.rs diff --git a/Cargo.lock b/Cargo.lock index bc7e17c..5d7a96b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,17 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330223a1aecc308757b9926e9391c9b47f8ef2dbd8aea9df88312aea18c5e8d6" +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -211,6 +222,15 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fontconfig" version = "0.6.0" @@ -239,12 +259,31 @@ dependencies = [ "version_check", ] +[[package]] +name = "halfbrown" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2a3c70a9c00cc1ee87b54e89f9505f73bb17d63f1b25c9a462ba8ef885444f" +dependencies = [ + "hashbrown 0.13.2", + "serde", +] + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -276,7 +315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", ] [[package]] @@ -310,6 +349,70 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lexical-core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", + "lexical-write-float", + "lexical-write-integer", +] + +[[package]] +name = "lexical-parse-float" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" +dependencies = [ + "lexical-parse-integer", + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-parse-integer" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" +dependencies = [ + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-util" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "lexical-write-float" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accabaa1c4581f05a3923d1b4cfd124c329352288b7b9da09e766b0668116862" +dependencies = [ + "lexical-util", + "lexical-write-integer", + "static_assertions", +] + +[[package]] +name = "lexical-write-integer" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b6f3d1f4422866b68192d62f77bc5c700bee84f3069f2469d7bc8c77852446" +dependencies = [ + "lexical-util", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.138" @@ -684,6 +787,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_yaml" version = "0.9.17" @@ -714,6 +828,26 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +[[package]] +name = "simd-json" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e3375b6c3d8c048ba09c8b4b6c3f1d3f35e06b71db07d231c323943a949e1b8" +dependencies = [ + "halfbrown", + "lexical-core", + "serde", + "serde_json", + "simdutf8", + "value-trait", +] + +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + [[package]] name = "slotmap" version = "1.0.6" @@ -837,6 +971,18 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2" +[[package]] +name = "value-trait" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "995de1aa349a0dc50f4aa40870dce12961a30229027230bad09acd2843edbe9e" +dependencies = [ + "float-cmp", + "halfbrown", + "itoa", + "ryu", +] + [[package]] name = "vec_map" version = "0.8.2" @@ -1102,6 +1248,7 @@ dependencies = [ "serde", "serde_yaml", "shlex", + "simd-json", "unicode-segmentation", "walkdir", "wayland-client", diff --git a/Cargo.toml b/Cargo.toml index 34c3fcb..c3d038c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,3 +36,4 @@ libpulse-binding = "2.26" serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.9" +simd-json = "0.7" diff --git a/src/main.rs b/src/main.rs index 143d048..7ac3e6b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,7 @@ use fonts::{FontMap, MaybeFontMap}; use keyboard::{KeyRepeatSource, RepeatMessage}; use state::State; use widgets::{Geometry, Widget}; +use utils::{xdg, desktop::{load_desktop_files, write_desktop_cache}}; use std::{env, rc::Rc, thread}; @@ -44,13 +45,17 @@ fn main() { None => Config::generate_v2(false), Some(s) => panic!("unknown argument: {}", s), }; - let home = env::var_os("HOME").unwrap().into_string().unwrap(); - match File::create(format!("{}/.config/wldash/config.yml", home)) { + match File::create(format!("{}/wldash/config.yml", xdg::config_folder())) { Ok(f) => serde_yaml::to_writer(f, &config).unwrap(), Err(_) => panic!("uh"), } std::process::exit(0); } + Some(ref s) if s == "desktop-refresh" => { + let v = load_desktop_files(); + write_desktop_cache(&v).unwrap(); + std::process::exit(0); + } Some(_) => panic!("unknown argument"), None => break, } @@ -126,8 +131,7 @@ fn main() { let mut fm = FontMap::new(); - let home = env::var_os("HOME").unwrap().into_string().unwrap(); - let config: Config = match File::open(format!("{}/.config/wldash/config.yml", home)) { + let config: Config = match File::open(format!("{}/wldash/config.yml", xdg::config_folder())) { Ok(f) => serde_yaml::from_reader(f).unwrap(), Err(_) => panic!("configuration file missing"), }; diff --git a/src/utils/desktop.rs b/src/utils/desktop.rs index 9b14706..a63c58f 100644 --- a/src/utils/desktop.rs +++ b/src/utils/desktop.rs @@ -1,17 +1,19 @@ use std::{ cmp::Ordering, collections::HashSet, - env, error::Error, - fs::read_to_string, + fs::{read_to_string, File}, io::{Error as io_error, ErrorKind}, }; +use serde::{self, Deserialize, Serialize}; +use simd_json; use walkdir::WalkDir; -use crate::utils::inish; +use crate::utils::{inish, xdg}; -#[derive(Clone, Debug, Eq, Hash)] +#[derive(Serialize, Deserialize, Clone, Debug, Eq, Hash)] +#[serde(rename_all = "snake_case")] pub struct Desktop { pub entry_type: String, pub name: String, @@ -72,19 +74,22 @@ impl PartialEq for Desktop { } } +pub fn load_desktop_cache() -> Result, Box> { + match File::open(format!("{}/wldash/desktop.json", xdg::cache_folder())) { + Ok(f) => simd_json::from_reader(f).map_err(|e| e.into()), + Err(e) => Err(e.into()) + } +} + +pub fn write_desktop_cache(v: &Vec) -> Result<(), Box> { + match File::create(format!("{}/wldash/desktop.json", xdg::cache_folder())) { + Ok(f) => simd_json::to_writer(f, v).map_err(|e| e.into()), + Err(e) => Err(e.into()) + } +} + pub fn load_desktop_files() -> Vec { - let home = env::var_os("HOME").unwrap().into_string().unwrap(); - - let xdg_data_home = match env::var_os("XDG_DATA_HOME") { - Some(s) => s.into_string().unwrap(), - None => format!("{}/.local/share", home), - }; - let xdg_data_dirs = match env::var_os("XDG_DATA_DIRS") { - Some(s) => s.into_string().unwrap(), - None => "/usr/local/share:/usr/share".to_string(), - }; - - let dirs = std::iter::once(xdg_data_home.as_str()).chain(xdg_data_dirs.split(':')); + let dirs = xdg::data_folders(); let mut desktop: Vec = Vec::new(); let mut seen: HashSet = HashSet::new(); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 6dbe5d9..bb7cf87 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,2 +1,3 @@ pub mod desktop; +pub mod xdg; mod inish; diff --git a/src/utils/xdg.rs b/src/utils/xdg.rs new file mode 100644 index 0000000..f5a2662 --- /dev/null +++ b/src/utils/xdg.rs @@ -0,0 +1,39 @@ +use std::env; + +pub fn cache_folder() -> String { + match env::var_os("XDG_CACHE_HOME") { + Some(s) => s.into_string().unwrap(), + None => { + let home = env::var_os("HOME").unwrap().into_string().unwrap(); + format!("{}/.cache", home) + } + } +} + + +pub fn config_folder() -> String { + match env::var_os("XDG_CONFIG_HOME") { + Some(s) => s.into_string().unwrap(), + None => { + let home = env::var_os("HOME").unwrap().into_string().unwrap(); + format!("{}/.config", home) + } + } +} + +pub fn data_folders() -> Vec { + let xdg_data_home = match env::var_os("XDG_DATA_HOME") { + Some(s) => s.into_string().unwrap(), + None => { + let home = env::var_os("HOME").unwrap().into_string().unwrap(); + format!("{}/.local/share", home) + } + }; + let xdg_data_dirs = match env::var_os("XDG_DATA_DIRS") { + Some(s) => s.into_string().unwrap(), + None => "/usr/local/share:/usr/share".to_string(), + }; + + // Eww. + std::iter::once(xdg_data_home.to_string()).chain(xdg_data_dirs.split(':').map(|x| x.to_string())).collect() +} diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs index 1531062..c312afa 100644 --- a/src/widgets/launcher.rs +++ b/src/widgets/launcher.rs @@ -2,7 +2,6 @@ use std::{ cmp::{max, min, Ordering}, collections::HashMap, default::Default, - env, fs::{read_to_string, File}, io::Write, process::{exit, Command}, @@ -16,7 +15,10 @@ use crate::{ event::{Event, Events, PointerButton, PointerEvent}, fonts::FontMap, keyboard::{keysyms, KeyEvent}, - utils::desktop::{load_desktop_files, Desktop}, + utils::{ + desktop::{load_desktop_files, load_desktop_cache, write_desktop_cache, Desktop}, + xdg, + }, widgets::{Geometry, Widget}, }; @@ -221,7 +223,14 @@ impl Launcher { .name("desktopini".to_string()) .spawn(move || { let mut options = options.lock().unwrap(); - *options = load_desktop_files(); + *options = match load_desktop_cache() { + Ok(v) => v, + Err(_) => { + let v = load_desktop_files(); + write_desktop_cache(&v).unwrap(); + v + } + }; drop(options); let mut events = events.lock().unwrap(); events.add_event(Event::LauncherUpdate); @@ -432,8 +441,7 @@ impl Calc { thread::Builder::new() .name("calc".to_string()) .spawn(move || { - let home = env::var_os("HOME").unwrap().into_string().unwrap(); - let s = match read_to_string(format!("{}/.cache/wldash/calc", home)) { + let s = match read_to_string(format!("{}/wldash/calc", xdg::cache_folder())) { Ok(s) => s, _ => return, }; @@ -455,8 +463,7 @@ impl Calc { } fn sync(&self) { - let home = env::var_os("HOME").unwrap().into_string().unwrap(); - let mut f = match File::create(format!("{}/.cache/wldash/calc", home)) { + let mut f = match File::create(format!("{}/wldash/calc", xdg::cache_folder())) { Ok(f) => f, _ => return, }; @@ -634,8 +641,7 @@ impl Interface { std::process::exit(0); } - let home = env::var_os("HOME").unwrap().into_string().unwrap(); - if let Ok(mut f) = File::create(format!("{}/.cache/wldash/prompt", home)) { + if let Ok(mut f) = File::create(format!("{}/wldash/prompt", xdg::cache_folder())) { write!(f, "{}", self.inner.prompt.input).unwrap(); f.sync_data().unwrap(); } @@ -666,8 +672,7 @@ impl Interface { widget.update(&self.inner); } keysyms::XKB_KEY_r if event.modifiers.ctrl => { - let home = env::var_os("HOME").unwrap().into_string().unwrap(); - if let Ok(s) = read_to_string(format!("{}/.cache/wldash/prompt", home)) { + if let Ok(s) = read_to_string(format!("{}/wldash/prompt", xdg::cache_folder())) { self.inner.prompt.set(&s); } } From 6351cc242df2a455fcdaeb45ec8cdd120094be0f Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Tue, 4 Apr 2023 01:53:29 +0200 Subject: [PATCH 35/57] launcher: Use XDG_ACTIVATION_TOKEN for shell cmds --- src/widgets/launcher.rs | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs index c312afa..fdfd410 100644 --- a/src/widgets/launcher.rs +++ b/src/widgets/launcher.rs @@ -375,7 +375,27 @@ impl InterfaceWidget for Launcher { } } -struct Shell(); +struct Shell { + next_token: Option, +} + + +impl Shell { + fn new() -> Shell { + Shell { + next_token: None, + } + } + + fn exec(&self, args: Vec) { + let mut cmd = Command::new(args[0].clone()); + if let Some(token) = &self.next_token { + cmd.env("XDG_ACTIVATION_TOKEN", token); + } + cmd.args(&args[1..]).spawn().unwrap(); + exit(0); + } +} impl InterfaceWidget for Shell { fn trigger(&mut self, intf: &mut InnerInterface) { @@ -384,11 +404,7 @@ impl InterfaceWidget for Shell { "-c".to_string(), intf.prompt.input.clone(), ]; - Command::new(args[0].clone()) - .args(&args[1..]) - .spawn() - .unwrap(); - exit(0); + self.exec(args); } fn update(&mut self, _intf: &InnerInterface) {} @@ -606,7 +622,7 @@ impl Interface { Interface { launcher: Launcher::new(events), - shell: Shell {}, + shell: Shell::new(), calc: Calc::new(), inner: InnerInterface { font, @@ -754,7 +770,10 @@ impl Widget for Interface { self.inner.dirty = true; self.launcher.update(&self.inner); } - Event::TokenUpdate(t) => self.launcher.next_token = Some(t.to_string()), + Event::TokenUpdate(t) => { + self.launcher.next_token = Some(t.to_string()); + self.shell.next_token = Some(t.to_string()); + } _ => (), } } From 558d528f70bece1bcecc1049c862d928d46ba1f6 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Sat, 20 May 2023 17:34:52 +0200 Subject: [PATCH 36/57] Fix xdg_toplevel background --- src/state.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/state.rs b/src/state.rs index 992e8ad..cf786f5 100644 --- a/src/state.rs +++ b/src/state.rs @@ -270,7 +270,7 @@ impl Dispatch for State { match &state.bg_surface.wl_surface { Some(surface) => { let buffer = - singlepixel.create_u32_rgba_buffer(0, 0, 0, 0xFFFFFFFF, qh, ()); + singlepixel.create_u32_rgba_buffer(0, 0, 0, 0xFAFAFAFA, qh, ()); surface.attach(Some(&buffer), 0, 0); surface.damage_buffer(0, 0, 1, 1); } @@ -504,6 +504,14 @@ impl Dispatch for State { } if state.dimensions != (width, height) { + match (&state.bg_surface.wl_surface, &state.bg_surface.viewport) { + (Some(surface), Some(viewport)) => { + viewport.set_destination(width, height); + surface.commit(); + } + _ => todo!("handle early xdg creation"), + } + state.bufmgr.clear_buffers(); state.dimensions = (width, height); state.fonts.resolve(); From ebeb21f3c2a68b3bb615b182022e0e6da1780cac Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Tue, 22 Aug 2023 13:17:08 +0200 Subject: [PATCH 37/57] Fix fontmap resolution on sway --- src/state.rs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/state.rs b/src/state.rs index cf786f5..3647a07 100644 --- a/src/state.rs +++ b/src/state.rs @@ -199,7 +199,6 @@ impl Dispatch for State { if let OperationMode::XdgToplevel = state.mode { let wm_base = registry.bind::(name, 2, qh, ()); - match &state.main_surface.wl_surface { Some(surface) => { let xdg_surface = wm_base.get_xdg_surface(surface, qh, ()); @@ -395,12 +394,15 @@ impl Dispatch for State { ) { if let zwlr_layer_surface_v1::Event::Configure { serial, - width, - height, + mut width, + mut height, } = event { if (width, height) == (0, 0) { - return; + if state.configured { + return; + } + (width, height) = (640, 480); } layer_surface.ack_configure(serial); state.configured = true; @@ -408,12 +410,11 @@ impl Dispatch for State { let dim = (width as i32, height as i32); - if state.dimensions == dim { + if state.configured && state.dimensions == dim { return; } state.bufmgr.clear_buffers(); state.dimensions = dim; - state.fonts.resolve(); let fonts = state.fonts.unwrap(); let layout = state.layout.clone(); layout.geometry_update( @@ -481,13 +482,16 @@ impl Dispatch for State { ) { match event { xdg_toplevel::Event::Configure { - width, - height, + mut width, + mut height, states, .. } => { if (width, height) == (0, 0) { - return; + if state.configured { + return; + } + (width, height) = (640, 480); } let activated = states .iter() @@ -503,7 +507,7 @@ impl Dispatch for State { } } - if state.dimensions != (width, height) { + if !state.configured || state.dimensions != (width, height) { match (&state.bg_surface.wl_surface, &state.bg_surface.viewport) { (Some(surface), Some(viewport)) => { viewport.set_destination(width, height); @@ -585,6 +589,7 @@ impl Dispatch for State { state.running = false; return; } + state.keyboard.resolve(); let k = state.keyboard.key(key, kbstate); let repeats = k.repeats; let ev = Event::KeyEvent(k); From b451a2de31ce7c112fa17594432fc6ca6be9c354 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Tue, 22 Aug 2023 14:39:20 +0200 Subject: [PATCH 38/57] Fix keyboard resolve on sway --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7ac3e6b..9fc4fed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -210,7 +210,7 @@ fn main() { damage[idx] = widget.draw(&mut state.fonts.unwrap().borrow_mut(), &mut subview); } } - surface.damage_buffer(0, 0, 0x7FFFFFFF, 0x7FFFFFFF) + surface.damage_buffer(0, 0, 0x7FFFFFFF, 0x7FFFFFFF); } else { let mut drew = false; for (idx, widget) in state.widgets.iter_mut().enumerate() { @@ -238,6 +238,7 @@ fn main() { } if !drew { buf.release(); + conn.flush().unwrap(); continue; } } @@ -245,6 +246,5 @@ fn main() { surface.attach(Some(&buf.buffer), 0, 0); surface.commit(); conn.flush().unwrap(); - state.keyboard.resolve(); } } From c01c810c105d9bf612939fd5d93b7c737b3f5f71 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Thu, 28 Sep 2023 23:40:20 +0200 Subject: [PATCH 39/57] Configurable config file --- src/main.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 9fc4fed..2270edf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,8 +35,16 @@ fn main() { // Skip program name _ = args.next(); + let mut config_file = format!("{}/wldash/config.yml", xdg::config_folder()); + loop { match args.next() { + Some(ref s) if s == "--config" => { + match args.next() { + Some(ref s) => config_file = s.clone(), + None => panic!("missing argument to --config"), + } + } Some(ref s) if s == "generate" => { let config = match args.next() { Some(ref s) if s == "v1" => Config::generate_v1(), @@ -45,7 +53,7 @@ fn main() { None => Config::generate_v2(false), Some(s) => panic!("unknown argument: {}", s), }; - match File::create(format!("{}/wldash/config.yml", xdg::config_folder())) { + match File::create(config_file) { Ok(f) => serde_yaml::to_writer(f, &config).unwrap(), Err(_) => panic!("uh"), } @@ -131,7 +139,7 @@ fn main() { let mut fm = FontMap::new(); - let config: Config = match File::open(format!("{}/wldash/config.yml", xdg::config_folder())) { + let config: Config = match File::open(config_file) { Ok(f) => serde_yaml::from_reader(f).unwrap(), Err(_) => panic!("configuration file missing"), }; From 8835708b31f400e66a1d1a8765edbcd1b9ee9a11 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Fri, 10 Nov 2023 13:58:56 +0100 Subject: [PATCH 40/57] Make background configurable --- src/config.rs | 3 +++ src/main.rs | 1 + src/state.rs | 7 +++++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index ecbaa41..1015846 100644 --- a/src/config.rs +++ b/src/config.rs @@ -80,6 +80,7 @@ pub struct Config { pub font_paths: Option>, pub widget: Widget, pub mode: OperationMode, + pub background: Option, } impl Config { @@ -159,6 +160,7 @@ impl Config { font_paths: Some(map), widget: widget, mode: OperationMode::LayerSurface((1024, 768)), + background: Some(0xFAFAFAFA), } } @@ -250,6 +252,7 @@ impl Config { } else { OperationMode::LayerSurface((1024, 1)) }, + background: Some(0xFAFAFAFA), } } } diff --git a/src/main.rs b/src/main.rs index 2270edf..e8b8340 100644 --- a/src/main.rs +++ b/src/main.rs @@ -166,6 +166,7 @@ fn main() { let mut state = State::new( config.mode, + config.background, widgets, layout, MaybeFontMap::Waiting(font_thread), diff --git a/src/state.rs b/src/state.rs index 3647a07..0aea95d 100644 --- a/src/state.rs +++ b/src/state.rs @@ -61,6 +61,7 @@ pub struct Protocols { pub struct State { mode: OperationMode, + background: Option, pub main_surface: MainSurface, pub bg_surface: BackgroundSurface, pub protocols: Protocols, @@ -82,6 +83,7 @@ pub struct State { impl State { pub fn new( mode: OperationMode, + background: Option, widgets: Vec>, layout: Rc>, fonts: MaybeFontMap, @@ -102,6 +104,7 @@ impl State { pointer: None, keyrepeat_sender, mode, + background, widgets, fonts, events, @@ -264,12 +267,12 @@ impl Dispatch for State { } state.protocols.viewporter = Some(viewporter); } - "wp_single_pixel_buffer_manager_v1" => { + "wp_single_pixel_buffer_manager_v1" if state.background.is_some() => { let singlepixel = registry.bind::(name, 1, qh, ()); match &state.bg_surface.wl_surface { Some(surface) => { let buffer = - singlepixel.create_u32_rgba_buffer(0, 0, 0, 0xFAFAFAFA, qh, ()); + singlepixel.create_u32_rgba_buffer(0, 0, 0, state.background.unwrap(), qh, ()); surface.attach(Some(&buffer), 0, 0); surface.damage_buffer(0, 0, 1, 1); } From 921938cc0d034cb4ff1597727c678c5223a2cd0e Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Fri, 10 Nov 2023 13:59:09 +0100 Subject: [PATCH 41/57] Resolve keyboard after first draw --- src/main.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main.rs b/src/main.rs index e8b8340..76b13b7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -255,5 +255,8 @@ fn main() { surface.attach(Some(&buf.buffer), 0, 0); surface.commit(); conn.flush().unwrap(); + + // Now is a good as time as any to load the keymap + state.keyboard.resolve(); } } From 84c241a90eca667139d4b3828b4e95b1cd5d2386 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Fri, 10 Nov 2023 14:00:06 +0100 Subject: [PATCH 42/57] keyboard: Use xkb_common::keysyms --- src/keyboard/keysyms.rs | 21 --------------------- src/keyboard/mod.rs | 3 ++- src/widgets/launcher.rs | 30 +++++++++++++++--------------- 3 files changed, 17 insertions(+), 37 deletions(-) delete mode 100644 src/keyboard/keysyms.rs diff --git a/src/keyboard/keysyms.rs b/src/keyboard/keysyms.rs deleted file mode 100644 index 7b413fd..0000000 --- a/src/keyboard/keysyms.rs +++ /dev/null @@ -1,21 +0,0 @@ -#![allow(missing_docs, non_upper_case_globals, unused_parens, clippy::all)] - -pub const XKB_KEY_BackSpace: u32 = 0xff08; /* Back space, back char */ -//pub const XKB_KEY_Tab: u32 = 0xff09; -pub const XKB_KEY_Return: u32 = 0xff0d; /* Return, enter */ -pub const XKB_KEY_Escape: u32 = 0xff1b; -pub const XKB_KEY_Delete: u32 = 0xffff; /* Delete, rubout */ -pub const XKB_KEY_Home: u32 = 0xff50; -pub const XKB_KEY_Left: u32 = 0xff51; /* Move left, left arrow */ -pub const XKB_KEY_Up: u32 = 0xff52; /* Move up, up arrow */ -pub const XKB_KEY_Right: u32 = 0xff53; /* Move right, right arrow */ -pub const XKB_KEY_Down: u32 = 0xff54; /* Move down, down arrow */ -pub const XKB_KEY_End: u32 = 0xff57; /* EOL */ - -//pub const XKB_KEY_ISO_Left_Tab: u32 = 0xfe20; -pub const XKB_KEY_a: u32 = 0x0061; /* U+0061 LATIN SMALL LETTER A */ -pub const XKB_KEY_e: u32 = 0x0065; /* U+0065 LATIN SMALL LETTER E */ -//pub const XKB_KEY_c: u32 = 0x0063; /* U+0063 LATIN SMALL LETTER C */ -pub const XKB_KEY_k: u32 = 0x006b; /* U+006B LATIN SMALL LETTER K */ -pub const XKB_KEY_r: u32 = 0x0072; /* U+0072 LATIN SMALL LETTER R */ -pub const XKB_KEY_u: u32 = 0x0075; /* U+0075 LATIN SMALL LETTER U */ diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs index a3afa4c..e3b6781 100644 --- a/src/keyboard/mod.rs +++ b/src/keyboard/mod.rs @@ -20,7 +20,8 @@ use std::{ use wayland_client::{protocol::wl_keyboard, WEnum}; use xkbcommon::xkb; -pub mod keysyms; +pub use xkbcommon::xkb::keysyms; + mod repeat; mod state; diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs index fdfd410..c5ce594 100644 --- a/src/widgets/launcher.rs +++ b/src/widgets/launcher.rs @@ -675,43 +675,43 @@ impl Interface { PromptMode::Calc => &mut self.calc as &mut dyn InterfaceWidget, }; match event.keysym { - keysyms::XKB_KEY_a if event.modifiers.ctrl => self.inner.prompt.home(), - keysyms::XKB_KEY_e if event.modifiers.ctrl => self.inner.prompt.end(), - keysyms::XKB_KEY_u if event.modifiers.ctrl => { + keysyms::KEY_a if event.modifiers.ctrl => self.inner.prompt.home(), + keysyms::KEY_e if event.modifiers.ctrl => self.inner.prompt.end(), + keysyms::KEY_u if event.modifiers.ctrl => { self.inner.prompt.clear_left(); self.inner.selection = 0; widget.update(&self.inner); } - keysyms::XKB_KEY_k if event.modifiers.ctrl => { + keysyms::KEY_k if event.modifiers.ctrl => { self.inner.prompt.clear_right(); self.inner.selection = 0; widget.update(&self.inner); } - keysyms::XKB_KEY_r if event.modifiers.ctrl => { + keysyms::KEY_r if event.modifiers.ctrl => { if let Ok(s) = read_to_string(format!("{}/wldash/prompt", xdg::cache_folder())) { self.inner.prompt.set(&s); } } - keysyms::XKB_KEY_Home => self.inner.prompt.home(), - keysyms::XKB_KEY_End => self.inner.prompt.end(), - keysyms::XKB_KEY_BackSpace => { + keysyms::KEY_Home => self.inner.prompt.home(), + keysyms::KEY_End => self.inner.prompt.end(), + keysyms::KEY_BackSpace => { self.inner.prompt.backspace(); self.inner.selection = 0; widget.update(&self.inner); } - keysyms::XKB_KEY_Delete => { + keysyms::KEY_Delete => { self.inner.prompt.delete(); self.inner.selection = 0; widget.update(&self.inner); } - keysyms::XKB_KEY_Escape => self.exit(), - keysyms::XKB_KEY_Return => widget.trigger(&mut self.inner), - keysyms::XKB_KEY_Left => self.inner.prompt.move_cursor(-1), - keysyms::XKB_KEY_Right => self.inner.prompt.move_cursor(1), - keysyms::XKB_KEY_Up => { + keysyms::KEY_Escape => self.exit(), + keysyms::KEY_Return => widget.trigger(&mut self.inner), + keysyms::KEY_Left => self.inner.prompt.move_cursor(-1), + keysyms::KEY_Right => self.inner.prompt.move_cursor(1), + keysyms::KEY_Up => { self.inner.selection += 1; } - keysyms::XKB_KEY_Down => { + keysyms::KEY_Down => { if self.inner.selection > 0 { self.inner.selection -= 1; } From a882e26e2b3746486bcc789df186f896ae82f987 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Fri, 10 Nov 2023 14:00:48 +0100 Subject: [PATCH 43/57] widgets/backlight: Set support --- src/widgets/backlight.rs | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/widgets/backlight.rs b/src/widgets/backlight.rs index de74c7a..2edec0f 100644 --- a/src/widgets/backlight.rs +++ b/src/widgets/backlight.rs @@ -1,11 +1,12 @@ use std::{ - fs, - io::{Error, ErrorKind}, + fs::{self, OpenOptions}, + io::{Write, Error, ErrorKind}, path::{Path, PathBuf}, }; use crate::{ color::Color, + event::PointerButton, fonts::FontMap, widgets::bar_widget::{BarWidget, BarWidgetImpl}, }; @@ -17,10 +18,16 @@ fn read_file_as_u64(path: &Path) -> Result { .map_err(|_e| Error::new(ErrorKind::Other, "unable to parse value")) } +fn write_file_as_u64(path: &Path, value: u64) -> Result<(), Error> { + let mut file = OpenOptions::new().write(true).open(path)?; + file.write_fmt(format_args!("{}", value)) +} + pub struct Backlight { device_path: PathBuf, cur: u64, max: u64, + dirty: bool } impl Backlight { @@ -34,6 +41,7 @@ impl Backlight { device_path: Path::new("/sys/class/backlight").to_path_buf().join(path), cur: 0, max: 0, + dirty: true, }; dev.update(); @@ -47,21 +55,38 @@ impl Backlight { Err(_) => false, } } + + pub fn set(&mut self, brightness: f32) { + let val = (self.max as f32 * brightness) as u64; + write_file_as_u64(self.device_path.join("brightness").as_path(), val); + self.cur = read_file_as_u64(self.device_path.join("brightness").as_path()).unwrap(); + } } impl BarWidgetImpl for Backlight { fn name(&self) -> &'static str { "backlight" } + fn get_dirty(&self) -> bool { + self.dirty + } fn value(&mut self) -> f32 { if self.cur > self.max { // what. return 1.0; } + self.dirty = false; self.cur as f32 / self.max as f32 } fn color(&self) -> Color { Color::WHITE } + fn click(&mut self, pos: f32, btn: PointerButton) { + self.dirty = true; + match btn { + PointerButton::Left => self.set(pos), + _ => (), + }; + } } From 1d1f7d3a8e66d405e43e4009bef89cdc22750967 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Fri, 10 Nov 2023 14:01:14 +0100 Subject: [PATCH 44/57] draw: Fix a bug I forgot the details of --- src/draw.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/draw.rs b/src/draw.rs index 1de21ae..fdc6812 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -163,7 +163,7 @@ impl<'a> Font<'a> { } } - let height = buf.get_bounds().3; + let height = self.size.ceil() as u32; for (i, glyph) in glyphs.iter().enumerate() { if i == cursor { self.draw_cursor(buf, c, x_off as u32, height)?; From c169843bcbd6ee9e6997fe78622cbda5753b44e5 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Fri, 10 Nov 2023 15:10:10 +0100 Subject: [PATCH 45/57] Add basic server operation --- src/config.rs | 3 +++ src/main.rs | 65 +++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/config.rs b/src/config.rs index 1015846..e6b05e1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -81,6 +81,7 @@ pub struct Config { pub widget: Widget, pub mode: OperationMode, pub background: Option, + pub server: bool, } impl Config { @@ -161,6 +162,7 @@ impl Config { widget: widget, mode: OperationMode::LayerSurface((1024, 768)), background: Some(0xFAFAFAFA), + server: false, } } @@ -253,6 +255,7 @@ impl Config { OperationMode::LayerSurface((1024, 1)) }, background: Some(0xFAFAFAFA), + server: false, } } } diff --git a/src/main.rs b/src/main.rs index 76b13b7..a9b3555 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,8 +9,6 @@ mod state; mod utils; mod widgets; -use std::fs::File; - use wayland_client::{Connection, QueueHandle, WaylandSource}; use calloop::{ @@ -25,10 +23,20 @@ use event::{Event, Events}; use fonts::{FontMap, MaybeFontMap}; use keyboard::{KeyRepeatSource, RepeatMessage}; use state::State; +use utils::{ + desktop::{load_desktop_files, write_desktop_cache}, + xdg, +}; use widgets::{Geometry, Widget}; -use utils::{xdg, desktop::{load_desktop_files, write_desktop_cache}}; -use std::{env, rc::Rc, thread}; +use std::{ + env, + fs::File, + io::{BufRead, BufReader, Write}, + os::unix::net::{UnixListener, UnixStream}, + rc::Rc, + thread, +}; fn main() { let mut args = env::args(); @@ -39,12 +47,10 @@ fn main() { loop { match args.next() { - Some(ref s) if s == "--config" => { - match args.next() { - Some(ref s) => config_file = s.clone(), - None => panic!("missing argument to --config"), - } - } + Some(ref s) if s == "--config" => match args.next() { + Some(ref s) => config_file = s.clone(), + None => panic!("missing argument to --config"), + }, Some(ref s) if s == "generate" => { let config = match args.next() { Some(ref s) if s == "v1" => Config::generate_v1(), @@ -135,6 +141,11 @@ fn main() { }) .expect("Failed to insert keyrepeat source!"); + let socket_path = match env::var("XDG_RUNTIME_DIR") { + Ok(dir) => dir + "/wldash", + Err(_) => "/tmp/wldash".to_string(), + }; + let events = Events::new(ping_sender); let mut fm = FontMap::new(); @@ -144,6 +155,40 @@ fn main() { Err(_) => panic!("configuration file missing"), }; + if config.server { + if let Ok(mut socket) = UnixStream::connect(socket_path.clone()) { + socket.write_all(b"kill\n").unwrap(); + return; + }; + + let _ = std::fs::remove_file(socket_path.clone()); + let _ = thread::Builder::new() + .name("ipc_server".to_string()) + .spawn(move || { + let listener = UnixListener::bind(socket_path.clone()).unwrap(); + loop { + match listener.accept() { + Ok((stream, _)) => { + let _ = thread::Builder::new().name("ipc_client".to_string()).spawn( + move || { + let r = BufReader::new(stream); + for line in r.lines() { + match line { + Ok(ref cmd) if cmd == "kill" => { + std::process::exit(0); + } + _ => {} + } + } + }, + ); + } + _ => (), + } + } + }); + } + if let Some(font_paths) = config.font_paths { for (key, value) in font_paths.into_iter() { fm.add_font_path(Box::leak(key.into_boxed_str()), value); From 7ee58d8d68739c7f480a8b8aaa4506499fcbbe35 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Fri, 10 Nov 2023 15:22:37 +0100 Subject: [PATCH 46/57] server: Make optional --- src/config.rs | 6 +++--- src/main.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/config.rs b/src/config.rs index e6b05e1..7bf9f1d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -81,7 +81,7 @@ pub struct Config { pub widget: Widget, pub mode: OperationMode, pub background: Option, - pub server: bool, + pub server: Option, } impl Config { @@ -162,7 +162,7 @@ impl Config { widget: widget, mode: OperationMode::LayerSurface((1024, 768)), background: Some(0xFAFAFAFA), - server: false, + server: None, } } @@ -255,7 +255,7 @@ impl Config { OperationMode::LayerSurface((1024, 1)) }, background: Some(0xFAFAFAFA), - server: false, + server: None, } } } diff --git a/src/main.rs b/src/main.rs index a9b3555..a189eba 100644 --- a/src/main.rs +++ b/src/main.rs @@ -155,7 +155,7 @@ fn main() { Err(_) => panic!("configuration file missing"), }; - if config.server { + if let Some(true) = config.server { if let Ok(mut socket) = UnixStream::connect(socket_path.clone()) { socket.write_all(b"kill\n").unwrap(); return; From 57768868840f9372f8b844814b2980ca6b4c51d8 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Tue, 30 Jan 2024 00:56:12 +0100 Subject: [PATCH 47/57] keyboard: Fix repeat deadline handling The calloop timer is only responsible for ensuring wakeup at the right time, but we can also be woken up before time so we need to check the deadline. At the same time, We may end up being significantly delayed past the deadline if something else was slow. If we are too far behind, reset the base of the repeat timer by just offsetting from the current time. --- src/keyboard/repeat.rs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/keyboard/repeat.rs b/src/keyboard/repeat.rs index 318e647..41b984b 100644 --- a/src/keyboard/repeat.rs +++ b/src/keyboard/repeat.rs @@ -64,7 +64,7 @@ impl EventSource for KeyRepeatSource { let mut reregister = false; - let mut next_deadline = std::time::Instant::now(); + let now = std::time::Instant::now(); // Check if the key repeat should stop let channel_pa = self @@ -78,9 +78,7 @@ impl EventSource for KeyRepeatSource { WEnum::Value(wl_keyboard::KeyState::Pressed) => { key.replace(event); reregister = true; - - next_deadline = std::time::Instant::now() + *delay; - timer.set_deadline(next_deadline); + timer.set_deadline(now + *delay); } WEnum::Value(wl_keyboard::KeyState::Released) => match key { Some(k) if k.keysym == event.keysym => { @@ -95,8 +93,7 @@ impl EventSource for KeyRepeatSource { *delay = Duration::from_millis(new_delay as u64); *disabled = false; if key.is_some() { - next_deadline = std::time::Instant::now() + *delay; - timer.set_deadline(next_deadline); + timer.set_deadline(now + *delay); } } }, @@ -117,14 +114,28 @@ impl EventSource for KeyRepeatSource { return Ok(PostAction::Reregister); } - let timer_pa = timer.process_events(readiness, token, |_event, _| { + let timer_pa = timer.process_events(readiness, token, |deadline, _| { if self.disabled || key.is_none() { return TimeoutAction::Drop; } + if deadline - now > std::time::Duration::from_millis(5) { + // We have not yet reached the deadline within our tolerance + return TimeoutAction::ToInstant(deadline); + } // Invoke the event callback(key.as_ref().unwrap().clone(), &mut ()); - next_deadline += *rate; + + let mut next_deadline = deadline; + if now - deadline > *rate * 2 { + // We're way behind, let's just set a new time + println!("KeyRepeatSource::process_events: More than two repeat cycles behind, resetting"); + next_deadline = now + *rate; + } else { + while next_deadline < now { + next_deadline += *rate; + } + } TimeoutAction::ToInstant(next_deadline) })?; From f09abf1d536dca48319dd3a781afba2567003ef4 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Sun, 12 Jan 2025 22:47:16 +0100 Subject: [PATCH 48/57] backlight: Auto-detect type --- src/config.rs | 17 ++++++++++++----- src/widgets/backlight.rs | 33 +++++++++++++++++++++++---------- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/config.rs b/src/config.rs index 7bf9f1d..2322059 100644 --- a/src/config.rs +++ b/src/config.rs @@ -87,7 +87,7 @@ pub struct Config { impl Config { pub fn generate_v1() -> Config { let has_battery = Battery::detect(); - let has_backlight = Backlight::detect("intel_backlight"); + let has_backlight = Backlight::detect(); let sans = find_font("sans"); let monospace = find_font("monospace"); @@ -105,7 +105,7 @@ impl Config { bars.push(Widget::Margin { margins: (0, 0, 0, 8), widget: Box::new(Widget::Backlight { - device: Some("intel_backlight".to_string()), + device: None, font: None, font_size: 24., }), @@ -168,7 +168,7 @@ impl Config { pub fn generate_v2(clay: bool) -> Config { let has_battery = Battery::detect(); - let has_backlight = Backlight::detect("intel_backlight"); + let has_backlight = Backlight::detect(); let sans = find_font("sans"); let monospace = find_font("monospace"); @@ -186,7 +186,7 @@ impl Config { bars.push(Widget::Margin { margins: (16, 8, 8, 0), widget: Box::new(Widget::Backlight { - device: Some("intel_backlight".to_string()), + device: None, font: None, font_size: 24., }), @@ -267,6 +267,13 @@ fn leak_or_default(v: Option, d: &'static str) -> &'static str { } } +fn leak_or_none(v: Option) -> Option<&'static str> { + match v { + Some(v) => Some(Box::leak(v.into_boxed_str())), + None => None, + } +} + impl Widget { pub fn construct_widgets( self, @@ -331,7 +338,7 @@ impl Widget { font, font_size, } => v.push(Box::new(Backlight::new( - leak_or_default(device, "intel_backlight"), + leak_or_none(device), &mut fm, leak_or_default(font, "sans"), font_size, diff --git a/src/widgets/backlight.rs b/src/widgets/backlight.rs index 2edec0f..21853fd 100644 --- a/src/widgets/backlight.rs +++ b/src/widgets/backlight.rs @@ -1,9 +1,11 @@ use std::{ fs::{self, OpenOptions}, - io::{Write, Error, ErrorKind}, + io::{Error, ErrorKind, Write}, path::{Path, PathBuf}, }; +use walkdir::WalkDir; + use crate::{ color::Color, event::PointerButton, @@ -27,7 +29,7 @@ pub struct Backlight { device_path: PathBuf, cur: u64, max: u64, - dirty: bool + dirty: bool, } impl Backlight { @@ -36,9 +38,19 @@ impl Backlight { self.max = read_file_as_u64(self.device_path.join("max_brightness").as_path()).unwrap(); } - pub fn new(path: &str, fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget { + pub fn new(path: Option<&str>, fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget { + let mut device_path = None; + if let Some(path) = path { + device_path = Some(Path::new("/sys/class/backlight").to_path_buf().join(path)); + } else { + for entry in WalkDir::new("/sys/class/backlight") { + if let Ok(entry) = entry { + device_path = Some(entry.path().to_path_buf()); + } + } + } let mut dev = Backlight { - device_path: Path::new("/sys/class/backlight").to_path_buf().join(path), + device_path: device_path.expect("did not find backlight device"), cur: 0, max: 0, dirty: true, @@ -48,17 +60,18 @@ impl Backlight { BarWidget::new(Box::new(dev), fm, font, size) } - pub fn detect(path: &str) -> bool { - let path = Path::new("/sys/class/backlight").to_path_buf().join(path); - match read_file_as_u64(path.join("brightness").as_path()) { - Ok(_) => true, - Err(_) => false, + pub fn detect() -> bool { + for entry in WalkDir::new("/sys/class/backlight") { + if entry.is_ok() { + return true; + } } + return false; } pub fn set(&mut self, brightness: f32) { let val = (self.max as f32 * brightness) as u64; - write_file_as_u64(self.device_path.join("brightness").as_path(), val); + let _ = write_file_as_u64(self.device_path.join("brightness").as_path(), val); self.cur = read_file_as_u64(self.device_path.join("brightness").as_path()).unwrap(); } } From 2b3048facaa981d81c227f9867d408eac8c20e92 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Sun, 12 Jan 2025 22:50:00 +0100 Subject: [PATCH 49/57] Rework the initial wayland init a bit --- src/buffer.rs | 9 +- src/main.rs | 26 ++++- src/state.rs | 291 ++++++++++++++++++++++++++++---------------------- 3 files changed, 192 insertions(+), 134 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index e63363a..e015914 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -82,16 +82,18 @@ pub struct ShmBuffer { pub mmap: MmapMut, pub buffer: wl_buffer::WlBuffer, pub refcnt: u32, + pub id: u32, } impl ShmBuffer { - pub fn new( + fn new( shm: &wl_shm::WlShm, qh: &QueueHandle, width: i32, height: i32, stride: i32, format: wl_shm::Format, + id: u32, ) -> io::Result where D: Dispatch + Dispatch + 'static, @@ -108,6 +110,7 @@ impl ShmBuffer { file, mmap, buffer, + id, }) } @@ -122,12 +125,14 @@ impl ShmBuffer { pub struct BufferManager { pub buffers: Vec, + pub next_id: u32, } impl BufferManager { pub fn new() -> BufferManager { BufferManager { buffers: Vec::new(), + next_id: 0, } } @@ -162,8 +167,10 @@ impl BufferManager { dimensions.1, dimensions.0 * 4, wl_shm::Format::Argb8888, + self.next_id, ) .expect("unable to add buffer"); + self.next_id += 1; BufferView::new(&mut buf.mmap, (dimensions.0 as u32, dimensions.1 as u32)); self.buffers.push(buf); } diff --git a/src/main.rs b/src/main.rs index a189eba..d1a6dae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -82,6 +82,7 @@ fn main() { let display = conn.display(); display.get_registry(&qhandle, ()); + display.sync(&qhandle, ()); let (ping_sender, ping_source) = calloop::ping::make_ping().unwrap(); let (keyrepeat_sender, keyrepeat_channel) = calloop::channel::channel::(); @@ -224,12 +225,21 @@ fn main() { damage.push(Geometry::new()); } + // Initial setup + // + while state.running && !state.ready { + event_loop + .dispatch(None, &mut state) + .expect("Could not dispatch event loop"); + } + state.check_registry(&qhandle); + while state.running { event_loop .dispatch(None, &mut state) .expect("Could not dispatch event loop"); - if !state.configured || !state.dirty { + if !state.configured || !state.dirty || !state.ready { continue; } @@ -243,8 +253,16 @@ fn main() { let buf = match state.bufmgr.next_buffer() { Some(b) => b, None => { - // We are still waiting for our buffer - continue; + if state.bufmgr.buffers.len() >= 3 { + continue; + } + state.add_buffer(&qhandle); + match state.bufmgr.next_buffer() { + Some(b) => b, + None => { + continue; + } + } } }; @@ -297,7 +315,9 @@ fn main() { } } + state.ready = false; surface.attach(Some(&buf.buffer), 0, 0); + surface.frame(&qhandle, ()); surface.commit(); conn.flush().unwrap(); diff --git a/src/state.rs b/src/state.rs index 0aea95d..da2a611 100644 --- a/src/state.rs +++ b/src/state.rs @@ -9,7 +9,7 @@ use std::{ use wayland_client::{ protocol::{ wl_buffer, wl_compositor, wl_keyboard, wl_pointer, wl_registry, wl_seat, wl_shm, - wl_shm_pool, wl_subcompositor, wl_subsurface, wl_surface, + wl_shm_pool, wl_subcompositor, wl_subsurface, wl_surface, wl_callback, }, Connection, Dispatch, QueueHandle, WEnum, }; @@ -54,9 +54,14 @@ pub struct BackgroundSurface { #[derive(Default)] pub struct Protocols { + wl_compositor: Option, + wl_subcompositor: Option, wl_shm: Option, + xdg_wm_base: Option, xdg_activation: Option, + zwlr_layer_shell: Option, viewporter: Option, + wp_single_pixel_buffer_manager: Option, } pub struct State { @@ -66,6 +71,7 @@ pub struct State { pub bg_surface: BackgroundSurface, pub protocols: Protocols, pub running: bool, + pub ready: bool, pub dirty: bool, activated: bool, pub configured: bool, @@ -109,6 +115,7 @@ impl State { fonts, events, layout, + ready: false, } } @@ -155,6 +162,84 @@ impl WidgetUpdater for State { } } +impl State { + pub fn check_registry(&mut self, qh: &QueueHandle) { + let wl_compositor = self.protocols.wl_compositor.as_ref().expect("wl_compositor missing"); + let main_surface = wl_compositor.create_surface(qh, ()); + let bg_surface = wl_compositor.create_surface(qh, ()); + self.main_surface.wl_surface = Some(main_surface.clone()); + self.bg_surface.wl_surface = Some(bg_surface.clone()); + self.activate(qh); + + let wl_subcompositor = self.protocols.wl_subcompositor.as_ref().expect("wl_subcompositor missing"); + let subsurface = wl_subcompositor.get_subsurface(&bg_surface, &main_surface, qh, ()); + subsurface.place_below(&main_surface); + self.bg_surface.subsurface = Some(subsurface.clone()); + + match self.mode { + OperationMode::XdgToplevel => { + self.fonts.resolve(); + let xdg_wm_base = self.protocols.xdg_wm_base.as_ref().expect("xdg_wm_base missing"); + let xdg_surface = xdg_wm_base.get_xdg_surface(&main_surface, qh, ()); + let toplevel = xdg_surface.get_toplevel(qh, ()); + toplevel.set_title("wldash".into()); + main_surface.commit(); + + self.main_surface.xdg_surface = Some((xdg_surface, toplevel)); + } + OperationMode::LayerSurface(size) => { + self.fonts.resolve(); + let layer_shell = self.protocols.zwlr_layer_shell.as_ref().expect("zwlr_layer_shell_v1 missing"); + let layer_surface = layer_shell.get_layer_surface( + &main_surface, + None, + zwlr_layer_shell_v1::Layer::Top, + "launcher".to_string(), + qh, + (), + ); + let fonts = self.fonts.unwrap(); + let layout = self.layout.clone(); + let min_size = layout.minimum_size(&mut fonts.borrow_mut(), self); + layer_surface.set_keyboard_interactivity( + zwlr_layer_surface_v1::KeyboardInteractivity::Exclusive, + ); + layer_surface.set_anchor( + zwlr_layer_surface_v1::Anchor::Left + .union(zwlr_layer_surface_v1::Anchor::Bottom) + .union(zwlr_layer_surface_v1::Anchor::Right) + .union(zwlr_layer_surface_v1::Anchor::Top) + ); + layer_surface.set_size( + max(size.0, min_size.width), + max(size.1, min_size.height), + ); + + main_surface.commit(); + self.main_surface.layer_surface = Some(layer_surface); + } + } + + let viewporter = self.protocols.viewporter.as_ref().expect("wp_viewporter missing"); + let viewport = viewporter.get_viewport(&bg_surface, qh, ()); + self.bg_surface.viewport = Some(viewport); + + if let Some(background) = self.background { + let wp_single_pixel_buffer_manager = self.protocols.wp_single_pixel_buffer_manager.as_ref().expect("wp_single_pixel_buffer_manager_v1 missing"); + let buffer = wp_single_pixel_buffer_manager.create_u32_rgba_buffer( + 0, + 0, + 0, + background, + qh, + (), + ); + bg_surface.attach(Some(&buffer), 0, 0); + bg_surface.damage_buffer(0, 0, 1, 1); + } + } +} + impl Dispatch for State { fn event( state: &mut Self, @@ -170,26 +255,12 @@ impl Dispatch for State { { match &interface[..] { "wl_compositor" => { - let compositor = - registry.bind::(name, 5, qh, ()); - let main_surface = compositor.create_surface(qh, ()); - let bg_surface = compositor.create_surface(qh, ()); - state.main_surface.wl_surface = Some(main_surface); - state.bg_surface.wl_surface = Some(bg_surface); - state.activate(qh); + state.protocols.wl_compositor = + Some(registry.bind::(name, 5, qh, ())); } "wl_subcompositor" => { - let subcompositor = - registry.bind::(name, 1, qh, ()); - - match (&state.main_surface.wl_surface, &state.bg_surface.wl_surface) { - (Some(parent), Some(child)) => { - let subsurface = subcompositor.get_subsurface(child, parent, qh, ()); - subsurface.place_below(parent); - state.bg_surface.subsurface = Some(subsurface); - } - _ => todo!("handle early subcompositor creation"), - } + state.protocols.wl_subcompositor = + Some(registry.bind::(name, 1, qh, ())); } "wl_shm" => { state.protocols.wl_shm = @@ -200,84 +271,27 @@ impl Dispatch for State { } "xdg_wm_base" => { if let OperationMode::XdgToplevel = state.mode { - let wm_base = - registry.bind::(name, 2, qh, ()); - match &state.main_surface.wl_surface { - Some(surface) => { - let xdg_surface = wm_base.get_xdg_surface(surface, qh, ()); - let toplevel = xdg_surface.get_toplevel(qh, ()); - toplevel.set_title("wldash".into()); - - surface.commit(); - - state.main_surface.xdg_surface = Some((xdg_surface, toplevel)); - } - _ => todo!("handle early xdg_shell creation"), - } + state.protocols.xdg_wm_base = + Some(registry.bind::(name, 2, qh, ())); } } "xdg_activation_v1" => { state.protocols.xdg_activation = Some( registry.bind::(name, 1, qh, ()), ); - state.activate(qh); } "zwlr_layer_shell_v1" => { - if let OperationMode::LayerSurface(size) = state.mode { - let layer_shell = registry - .bind::(name, 4, qh, ()); - state.fonts.resolve(); - let fonts = state.fonts.unwrap(); - let layout = state.layout.clone(); - let min_size = layout.minimum_size(&mut fonts.borrow_mut(), state); - match &state.main_surface.wl_surface { - Some(surface) => { - let layer_surface = layer_shell.get_layer_surface( - surface, - None, - zwlr_layer_shell_v1::Layer::Top, - "launcher".to_string(), - qh, - (), - ); - layer_surface.set_keyboard_interactivity( - zwlr_layer_surface_v1::KeyboardInteractivity::Exclusive, - ); - layer_surface.set_size( - max(size.0, min_size.width), - max(size.1, min_size.height), - ); - - surface.commit(); - state.main_surface.layer_surface = Some(layer_surface); - } - _ => todo!("handle early layer_shell creation"), - } + if let OperationMode::LayerSurface(_) = state.mode { + state.protocols.zwlr_layer_shell = Some(registry + .bind::(name, 4, qh, ())); } } "wp_viewporter" => { - let viewporter = - registry.bind::(name, 1, qh, ()); - match &state.bg_surface.wl_surface { - Some(surface) => { - state.bg_surface.viewport = - Some(viewporter.get_viewport(surface, qh, ())); - } - _ => todo!("handle early viewporter creation"), - } - state.protocols.viewporter = Some(viewporter); + state.protocols.viewporter = + Some(registry.bind::(name, 1, qh, ())); } - "wp_single_pixel_buffer_manager_v1" if state.background.is_some() => { - let singlepixel = registry.bind::(name, 1, qh, ()); - match &state.bg_surface.wl_surface { - Some(surface) => { - let buffer = - singlepixel.create_u32_rgba_buffer(0, 0, 0, state.background.unwrap(), qh, ()); - surface.attach(Some(&buffer), 0, 0); - surface.damage_buffer(0, 0, 1, 1); - } - _ => todo!("handle early single_pixel_buffer creation"), - } + "wp_single_pixel_buffer_manager_v1" => { + state.protocols.wp_single_pixel_buffer_manager = Some(registry.bind::(name, 1, qh, ())); } _ => {} } @@ -285,6 +299,24 @@ impl Dispatch for State { } } +impl Dispatch for State { + fn event( + state: &mut Self, + _: &wl_callback::WlCallback, + event: wl_callback::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + match event { + wl_callback::Event::Done { callback_data: _ } => { + state.ready = true + }, + _ => {}, + } + } +} + impl Dispatch for State { fn event( _: &mut Self, @@ -395,49 +427,51 @@ impl Dispatch for State { _: &Connection, _: &QueueHandle, ) { - if let zwlr_layer_surface_v1::Event::Configure { - serial, - mut width, - mut height, - } = event - { - if (width, height) == (0, 0) { - if state.configured { + match event { + zwlr_layer_surface_v1::Event::Configure { + serial, + mut width, + mut height, + } => { + if (width, height) == (0, 0) { + if state.configured { + return; + } + (width, height) = (640, 480); + } + layer_surface.ack_configure(serial); + state.configured = true; + state.dirty = true; + + let dim = (width as i32, height as i32); + + if state.configured && state.dimensions == dim { return; } - (width, height) = (640, 480); - } - layer_surface.ack_configure(serial); - state.configured = true; - state.dirty = true; + state.bufmgr.clear_buffers(); + state.dimensions = dim; + let fonts = state.fonts.unwrap(); + let layout = state.layout.clone(); + layout.geometry_update( + &mut fonts.borrow_mut(), + &Geometry { + x: 0, + y: 0, + width: width as u32, + height: height as u32, + }, + state, + ); - let dim = (width as i32, height as i32); - - if state.configured && state.dimensions == dim { - return; + let surface = state.bg_surface.wl_surface.as_ref().expect("bg surface was not ready"); + let viewport = state.bg_surface.viewport.as_ref().expect("bg viewport was not ready"); + viewport.set_destination(dim.0, dim.1); + surface.commit(); } - state.bufmgr.clear_buffers(); - state.dimensions = dim; - let fonts = state.fonts.unwrap(); - let layout = state.layout.clone(); - layout.geometry_update( - &mut fonts.borrow_mut(), - &Geometry { - x: 0, - y: 0, - width: width as u32, - height: height as u32, - }, - state, - ); - - match (&state.bg_surface.wl_surface, &state.bg_surface.viewport) { - (Some(surface), Some(viewport)) => { - viewport.set_destination(dim.0, dim.1); - surface.commit(); - } - _ => todo!("handle early layer shell creation"), + zwlr_layer_surface_v1::Event::Closed => { + std::process::exit(0); } + _ => (), } } } @@ -511,13 +545,10 @@ impl Dispatch for State { } if !state.configured || state.dimensions != (width, height) { - match (&state.bg_surface.wl_surface, &state.bg_surface.viewport) { - (Some(surface), Some(viewport)) => { - viewport.set_destination(width, height); - surface.commit(); - } - _ => todo!("handle early xdg creation"), - } + let surface = state.bg_surface.wl_surface.as_ref().expect("bg surface was not ready"); + let viewport = state.bg_surface.viewport.as_ref().expect("bg viewportw as not ready"); + viewport.set_destination(width, height); + surface.commit(); state.bufmgr.clear_buffers(); state.dimensions = (width, height); From bc9ba0045c48b3ac729f9f97915edb5ecba13ea3 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Sun, 12 Jan 2025 22:51:45 +0100 Subject: [PATCH 50/57] launcher: Support launch_cmd config --- src/config.rs | 6 +++++- src/widgets/launcher.rs | 29 ++++++++++++++++------------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/config.rs b/src/config.rs index 2322059..94ba503 100644 --- a/src/config.rs +++ b/src/config.rs @@ -52,6 +52,7 @@ pub enum Widget { Launcher { font: Option, font_size: f32, + launch_cmd: Option, }, Battery { font: Option, @@ -148,6 +149,7 @@ impl Config { Widget::Launcher { font: None, font_size: 32., + launch_cmd: None, }, ])), }; @@ -236,6 +238,7 @@ impl Config { widget: Box::new(Widget::Launcher { font: None, font_size: 32., + launch_cmd: None, }), }, ]), @@ -321,11 +324,12 @@ impl Widget { sections_x, sections_y, ))), - Widget::Launcher { font, font_size } => v.push(Box::new(Interface::new( + Widget::Launcher { font, font_size, launch_cmd } => v.push(Box::new(Interface::new( events.clone(), &mut fm, leak_or_default(font, "sans"), font_size, + launch_cmd, ))), Widget::Battery { font, font_size } => v.push(Box::new(Battery::new( events.clone(), diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs index c5ce594..a6be498 100644 --- a/src/widgets/launcher.rs +++ b/src/widgets/launcher.rs @@ -212,10 +212,11 @@ struct Launcher { next_token: Option, options: Arc>>, matches: Vec, + launch_cmd: Option, } impl Launcher { - fn new(events: Arc>) -> Launcher { + fn new(events: Arc>, launch_cmd: Option) -> Launcher { let options = Arc::new(Mutex::new(Vec::new())); { let options = Arc::clone(&options); @@ -242,15 +243,22 @@ impl Launcher { next_token: None, options: options, matches: Vec::new(), + launch_cmd, } } - fn exec(&self, args: Vec) { - let mut cmd = Command::new(args[0].clone()); + fn exec(&self, cmdline: &str) { + let mut cmd = Command::new("/bin/sh"); if let Some(token) = &self.next_token { cmd.env("XDG_ACTIVATION_TOKEN", token); } - cmd.args(&args[1..]).spawn().unwrap(); + cmd.arg("-c"); + if let Some(l) = &self.launch_cmd { + cmd.arg(format!("{} {}", l, cmdline)); + } else { + cmd.arg(cmdline); + } + cmd.spawn().unwrap(); exit(0); } } @@ -266,10 +274,7 @@ impl InterfaceWidget for Launcher { .replace("%u", "") .replace("%U", ""); - let lexed = shlex::split(&exec).unwrap(); - if !lexed.is_empty() { - self.exec(lexed); - } + self.exec(&exec); } } } @@ -379,12 +384,9 @@ struct Shell { next_token: Option, } - impl Shell { fn new() -> Shell { - Shell { - next_token: None, - } + Shell { next_token: None } } fn exec(&self, args: Vec) { @@ -613,6 +615,7 @@ impl Interface { fm: &mut FontMap, font: &'static str, size: f32, + launch_cmd: Option ) -> Interface { fm.queue_font( font, @@ -621,7 +624,7 @@ impl Interface { ); Interface { - launcher: Launcher::new(events), + launcher: Launcher::new(events, launch_cmd), shell: Shell::new(), calc: Calc::new(), inner: InnerInterface { From f337ce6e957180733c82eb48026d6293989263cb Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Sun, 12 Jan 2025 23:08:24 +0100 Subject: [PATCH 51/57] layer-shell: Change to overlay --- src/state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/state.rs b/src/state.rs index da2a611..9751c88 100644 --- a/src/state.rs +++ b/src/state.rs @@ -193,7 +193,7 @@ impl State { let layer_surface = layer_shell.get_layer_surface( &main_surface, None, - zwlr_layer_shell_v1::Layer::Top, + zwlr_layer_shell_v1::Layer::Overlay, "launcher".to_string(), qh, (), From 9031361dd17c7f07aae76e6251d09c893ba705a4 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Sun, 12 Jan 2025 23:10:00 +0100 Subject: [PATCH 52/57] Cargo update --- Cargo.lock | 658 +++++++++++++++++++++++++++++------------------------ Cargo.toml | 19 +- 2 files changed, 373 insertions(+), 304 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5d7a96b..cd31566 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,24 +1,31 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "ab_glyph_rasterizer" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330223a1aecc308757b9926e9391c9b47f8ef2dbd8aea9df88312aea18c5e8d6" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" dependencies = [ "cfg-if", "once_cell", "version_check", + "zerocopy", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -42,25 +49,26 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "calloop" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a59225be45a478d772ce015d9743e49e92798ece9e34eda9a6aa2a6a7f40192" +checksum = "52e0d00eb1ea24371a97d2da6201c6747a633dc6dc1988ef503403b4c59504a8" dependencies = [ + "bitflags", "log", "nix 0.25.1", "slotmap", @@ -70,9 +78,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.77" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -82,46 +93,29 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.23" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" dependencies = [ + "android-tzdata", "iana-time-zone", "js-sys", - "num-integer", "num-traits", - "time", "wasm-bindgen", - "winapi", + "windows-targets 0.52.0", ] -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - -[[package]] -name = "const-cstr" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3d0b5ff30645a68f35ece8cea4556ca14ef8a1651455f789a099a0513532a6" - [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -137,47 +131,13 @@ dependencies = [ ] [[package]] -name = "cxx" -version = "1.0.83" +name = "cstr" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6" +checksum = "8aa998c33a6d3271e3678950a22134cd7dd27cef86dee1b611b5b14207d1d90b" dependencies = [ "proc-macro2", "quote", - "syn", ] [[package]] @@ -193,9 +153,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", @@ -203,9 +163,9 @@ dependencies = [ [[package]] name = "dlib" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ "libloading", ] @@ -222,6 +182,12 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "float-cmp" version = "0.9.0" @@ -233,9 +199,9 @@ dependencies = [ [[package]] name = "fontconfig" -version = "0.6.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b0653582d9c82645f95bacd0dc87b49a3126f7a9633be8cafa9c6d17d525847" +checksum = "c9b79b619a4ae048ea79e927376b1d10294979bda195b0c052fc958be96c62d9" dependencies = [ "yeslogic-fontconfig-sys", ] @@ -251,30 +217,37 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] -name = "halfbrown" -version = "0.1.18" +name = "getrandom" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e2a3c70a9c00cc1ee87b54e89f9505f73bb17d63f1b25c9a462ba8ef885444f" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "halfbrown" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5681137554ddff44396e5f149892c769d45301dd9aa19c51602a89ee214cb0ec" dependencies = [ "hashbrown 0.13.2", "serde", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.13.2" @@ -285,60 +258,72 @@ dependencies = [ ] [[package]] -name = "iana-time-zone" -version = "0.1.53" +name = "hashbrown" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "hermit-abi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" + +[[package]] +name = "iana-time-zone" +version = "0.1.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "winapi", + "windows-core", ] [[package]] name = "iana-time-zone-haiku" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "cxx", - "cxx-build", + "cc", ] [[package]] name = "indexmap" -version = "1.9.2" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b" dependencies = [ - "autocfg", - "hashbrown 0.12.3", + "equivalent", + "hashbrown 0.14.3", ] [[package]] name = "io-lifetimes" -version = "1.0.3" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ + "hermit-abi", "libc", "windows-sys", ] [[package]] name = "itoa" -version = "1.0.5" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" dependencies = [ "wasm-bindgen", ] @@ -415,34 +400,34 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.138" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libdbus-sys" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f8d7ae751e1cb825c840ae5e682f59b098cdfd213c350ac268b61449a5f58a0" +checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" dependencies = [ "pkg-config", ] [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" dependencies = [ "cfg-if", - "winapi", + "windows-sys", ] [[package]] name = "libpulse-binding" -version = "2.27.1" +version = "2.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1745b20bfc194ac12ef828f144f0ec2d4a7fe993281fa3567a0bd4969aee6890" +checksum = "ed3557a2dfc380c8f061189a01c6ae7348354e0c9886038dc6c171219c08eaff" dependencies = [ "bitflags", "libc", @@ -454,9 +439,9 @@ dependencies = [ [[package]] name = "libpulse-sys" -version = "1.20.1" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2191e6880818d1df4cf72eac8e91dce7a5a52ba0da4b2a5cdafabc22b937eadb" +checksum = "bc19e110fbf42c17260d30f6d3dc545f58491c7830d38ecb9aaca96e26067a9b" dependencies = [ "libc", "num-derive", @@ -465,29 +450,17 @@ dependencies = [ "winapi", ] -[[package]] -name = "link-cplusplus" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" -dependencies = [ - "cc", -] - [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memmap" @@ -501,9 +474,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.5.8" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc" +checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6" dependencies = [ "libc", ] @@ -541,16 +514,15 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.1" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ "bitflags", "cfg-if", "libc", "memoffset 0.7.1", "pin-utils", - "static_assertions", ] [[package]] @@ -582,7 +554,7 @@ checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -609,18 +581,18 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] [[package]] name = "once_cell" -version = "1.16.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "owned_ttf_parser" @@ -633,19 +605,20 @@ dependencies = [ [[package]] name = "pest" -version = "2.5.2" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f6e86fb9e7026527a0d46bc308b841d73170ef8f443e1807f6ef88526a816d4" +checksum = "1f200d8d83c44a45b21764d1916299752ca035d15ecd46faca3e9a2a2bf6ad06" dependencies = [ + "memchr", "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.5.2" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96504449aa860c8dcde14f9fba5c58dc6658688ca1fe363589d6327b8662c603" +checksum = "bcd6ab1236bbdb3a49027e920e693192ebfe8913f6d60e294de57463a493cfde" dependencies = [ "pest", "pest_generator", @@ -653,26 +626,26 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.5.2" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "798e0220d1111ae63d66cb66a5dcb3fc2d986d520b98e49e1852bfdb11d7c5e7" +checksum = "2a31940305ffc96863a735bef7c7994a00b325a7138fdbc5bda0f1a0476d3275" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] name = "pest_meta" -version = "2.5.2" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "984298b75898e30a843e278a9f2452c31e349a073a0ce6fd950a12a74464e065" +checksum = "a7ff62f5259e53b78d1af898941cdcdccfae7385cf7d793a6e55de5d05bb4b7d" dependencies = [ "once_cell", "pest", - "sha1", + "sha2", ] [[package]] @@ -683,33 +656,33 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" -version = "0.23.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11bafc859c6815fbaffbbbf4229ecb767ac913fecb27f9ad4343662e9ef099ea" +checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.21" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -730,6 +703,26 @@ dependencies = [ "pest_derive", ] +[[package]] +name = "ref-cast" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4846d4c50d1721b1a3bef8af76924eef20d5e723647333798c1b519b3a9473f" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "rusttype" version = "0.9.3" @@ -742,9 +735,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "same-file" @@ -761,37 +754,31 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" -[[package]] -name = "scratch" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" - [[package]] name = "serde" -version = "1.0.152" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] name = "serde_json" -version = "1.0.95" +version = "1.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" dependencies = [ "itoa", "ryu", @@ -800,9 +787,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.17" +version = "0.9.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb06d4b6cdaef0e0c51fa881acb721bed3c924cfaa71d9c94a3b771dfdf6567" +checksum = "adf8a49373e98a4c5f0ceb5d05aa7c648d75f63774981ed95b7c7443bbd50c6e" dependencies = [ "indexmap", "itoa", @@ -812,10 +799,10 @@ dependencies = [ ] [[package]] -name = "sha1" -version = "0.10.5" +name = "sha2" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -824,18 +811,20 @@ dependencies = [ [[package]] name = "shlex" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "simd-json" -version = "0.7.0" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3375b6c3d8c048ba09c8b4b6c3f1d3f35e06b71db07d231c323943a949e1b8" +checksum = "2faf8f101b9bc484337a6a6b0409cf76c139f2fb70a9e3aee6b6774be7bfbf76" dependencies = [ + "getrandom", "halfbrown", "lexical-core", + "ref-cast", "serde", "serde_json", "simdutf8", @@ -850,18 +839,18 @@ checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" [[package]] name = "slotmap" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" dependencies = [ "version_check", ] [[package]] name = "smallvec" -version = "1.10.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "static_assertions" @@ -871,9 +860,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" -version = "1.0.105" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -881,54 +870,46 @@ dependencies = [ ] [[package]] -name = "termcolor" -version = "1.1.3" +name = "syn" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ - "winapi-util", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ + "cfg-if", "once_cell", ] -[[package]] -name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi", - "winapi", -] - [[package]] name = "ttf-parser" version = "0.15.2" @@ -937,45 +918,39 @@ checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-segmentation" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" - -[[package]] -name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unsafe-libyaml" -version = "0.2.5" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" [[package]] name = "value-trait" -version = "0.5.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995de1aa349a0dc50f4aa40870dce12961a30229027230bad09acd2843edbe9e" +checksum = "dad8db98c1e677797df21ba03fca7d3bf9bec3ca38db930954e4fe6e1ea27eb4" dependencies = [ "float-cmp", "halfbrown", @@ -997,26 +972,25 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" -version = "2.3.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", - "winapi", "winapi-util", ] [[package]] name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1024,24 +998,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.48", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1049,33 +1023,33 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" [[package]] name = "wayland-backend" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb23bfea266c92bb051ea36cce0eb1a52b743dc1c5f168021947eda79764656d" +checksum = "41b48e27457e8da3b2260ac60d0a94512f5cba36448679f3747c0865b7893ed8" dependencies = [ "cc", "downcast-rs", "io-lifetimes", - "nix 0.26.1", + "nix 0.26.4", "scoped-tls", "smallvec", "wayland-sys", @@ -1083,23 +1057,23 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.30.0" +version = "0.30.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a925bd68c8b652af4e6f11a32410bd31bf84061c5ef279ed081520c60f203b4" +checksum = "489c9654770f674fc7e266b3c579f4053d7551df0ceb392f153adb1f9ed06ac8" dependencies = [ "bitflags", "calloop", "log", - "nix 0.26.1", + "nix 0.26.4", "wayland-backend", "wayland-scanner", ] [[package]] name = "wayland-protocols" -version = "0.30.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fefbeb8a360abe67ab7c2efe1d297a1a50ee011f5460791bc18870c26bb84e2" +checksum = "3b28101e5ca94f70461a6c2d610f76d85ad223d042dd76585ab23d3422dd9b4d" dependencies = [ "bitflags", "wayland-backend", @@ -1122,9 +1096,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.30.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4834c14b3edf1d9986c83ca79b1e7e3afbe9874c7c144702f6467063259ce45d" +checksum = "b9b873b257fbc32ec909c0eb80dea312076a67014e65e245f5eb69a6b8ab330e" dependencies = [ "proc-macro2", "quick-xml", @@ -1160,9 +1134,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -1174,67 +1148,141 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" -version = "0.42.0" +name = "windows-core" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "wldash2" version = "0.1.0" dependencies = [ - "bitflags", "calloop", "chrono", "dbus", @@ -1242,7 +1290,7 @@ dependencies = [ "fuzzy-matcher", "libpulse-binding", "memmap", - "nix 0.26.1", + "nix 0.26.4", "rcalc_lib", "rusttype", "serde", @@ -1259,9 +1307,9 @@ dependencies = [ [[package]] name = "xkbcommon" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbee136714379ab22da0280207fdb7f47e0bb940adea97731b65598b8c7a92e" +checksum = "52db25b599e92bf6e3904134618728eeb7b49a5a4f38f107f92399bb9c496b88" dependencies = [ "libc", "memmap2", @@ -1269,12 +1317,32 @@ dependencies = [ [[package]] name = "yeslogic-fontconfig-sys" -version = "4.0.1" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec657fd32bbcbeaef5c7bc8e10b3db95b143fab8db0a50079773dbf936fd4f73" +checksum = "ffb6b23999a8b1a997bf47c7bb4d19ad4029c3327bb3386ebe0a5ff584b33c7a" dependencies = [ - "const-cstr", + "cstr", "dlib", "once_cell", "pkg-config", ] + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] diff --git a/Cargo.toml b/Cargo.toml index c3d038c..df92354 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,28 +12,29 @@ panic = "abort" wayland-client = { version = "0.30", features = ["calloop", "log"] } wayland-protocols = { version = "0.30", features = ["client", "unstable", "staging"] } wayland-protocols-wlr = { version = "0.1", features = ["client"] } -nix = "0.26" +nix = "0.26" # 0.27 is breaking memmap = "0.7" -fontconfig = { version = "0.6.0", optional = true } +fontconfig = { version = "0.8.0", optional = true } rusttype = "0.9" -calloop = { version = "0.10" } +# fontdue = "0.7" +calloop = { version = "0.10" } # update breaks chrono = "0.4" -walkdir = "2.3" -bitflags = "1.3" +walkdir = "2.4" unicode-segmentation = "1.10" fuzzy-matcher = "0.3" -shlex = "1.1" +shlex = "1.3" -xkbcommon = { version = "0.5", features = ["wayland"] } +xkbcommon = { version = "0.5", features = ["wayland"] } # update breaks rcalc_lib = "1.0" #alsa = { version = "0.7", optional = true } dbus = "0.9" -libpulse-binding = "2.26" +libpulse-binding = "2.28" serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.9" -simd-json = "0.7" +simd-json = "0.13" + From 40aec97f799eb474114333b0e7e5eb5cce21c2e7 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Sun, 12 Jan 2025 23:10:21 +0100 Subject: [PATCH 53/57] Cargo format --- src/config.rs | 6 ++- src/state.rs | 105 +++++++++++++++++++++++++++------------- src/utils/desktop.rs | 4 +- src/utils/mod.rs | 2 +- src/utils/xdg.rs | 5 +- src/widgets/calendar.rs | 6 ++- src/widgets/launcher.rs | 4 +- src/widgets/layout.rs | 1 - 8 files changed, 89 insertions(+), 44 deletions(-) diff --git a/src/config.rs b/src/config.rs index 94ba503..62a6115 100644 --- a/src/config.rs +++ b/src/config.rs @@ -324,7 +324,11 @@ impl Widget { sections_x, sections_y, ))), - Widget::Launcher { font, font_size, launch_cmd } => v.push(Box::new(Interface::new( + Widget::Launcher { + font, + font_size, + launch_cmd, + } => v.push(Box::new(Interface::new( events.clone(), &mut fm, leak_or_default(font, "sans"), diff --git a/src/state.rs b/src/state.rs index 9751c88..17b5f03 100644 --- a/src/state.rs +++ b/src/state.rs @@ -8,8 +8,8 @@ use std::{ use wayland_client::{ protocol::{ - wl_buffer, wl_compositor, wl_keyboard, wl_pointer, wl_registry, wl_seat, wl_shm, - wl_shm_pool, wl_subcompositor, wl_subsurface, wl_surface, wl_callback, + wl_buffer, wl_callback, wl_compositor, wl_keyboard, wl_pointer, wl_registry, wl_seat, + wl_shm, wl_shm_pool, wl_subcompositor, wl_subsurface, wl_surface, }, Connection, Dispatch, QueueHandle, WEnum, }; @@ -61,7 +61,8 @@ pub struct Protocols { xdg_activation: Option, zwlr_layer_shell: Option, viewporter: Option, - wp_single_pixel_buffer_manager: Option, + wp_single_pixel_buffer_manager: + Option, } pub struct State { @@ -164,14 +165,22 @@ impl WidgetUpdater for State { impl State { pub fn check_registry(&mut self, qh: &QueueHandle) { - let wl_compositor = self.protocols.wl_compositor.as_ref().expect("wl_compositor missing"); + let wl_compositor = self + .protocols + .wl_compositor + .as_ref() + .expect("wl_compositor missing"); let main_surface = wl_compositor.create_surface(qh, ()); let bg_surface = wl_compositor.create_surface(qh, ()); self.main_surface.wl_surface = Some(main_surface.clone()); self.bg_surface.wl_surface = Some(bg_surface.clone()); self.activate(qh); - let wl_subcompositor = self.protocols.wl_subcompositor.as_ref().expect("wl_subcompositor missing"); + let wl_subcompositor = self + .protocols + .wl_subcompositor + .as_ref() + .expect("wl_subcompositor missing"); let subsurface = wl_subcompositor.get_subsurface(&bg_surface, &main_surface, qh, ()); subsurface.place_below(&main_surface); self.bg_surface.subsurface = Some(subsurface.clone()); @@ -179,7 +188,11 @@ impl State { match self.mode { OperationMode::XdgToplevel => { self.fonts.resolve(); - let xdg_wm_base = self.protocols.xdg_wm_base.as_ref().expect("xdg_wm_base missing"); + let xdg_wm_base = self + .protocols + .xdg_wm_base + .as_ref() + .expect("xdg_wm_base missing"); let xdg_surface = xdg_wm_base.get_xdg_surface(&main_surface, qh, ()); let toplevel = xdg_surface.get_toplevel(qh, ()); toplevel.set_title("wldash".into()); @@ -189,7 +202,11 @@ impl State { } OperationMode::LayerSurface(size) => { self.fonts.resolve(); - let layer_shell = self.protocols.zwlr_layer_shell.as_ref().expect("zwlr_layer_shell_v1 missing"); + let layer_shell = self + .protocols + .zwlr_layer_shell + .as_ref() + .expect("zwlr_layer_shell_v1 missing"); let layer_surface = layer_shell.get_layer_surface( &main_surface, None, @@ -208,32 +225,31 @@ impl State { zwlr_layer_surface_v1::Anchor::Left .union(zwlr_layer_surface_v1::Anchor::Bottom) .union(zwlr_layer_surface_v1::Anchor::Right) - .union(zwlr_layer_surface_v1::Anchor::Top) - ); - layer_surface.set_size( - max(size.0, min_size.width), - max(size.1, min_size.height), + .union(zwlr_layer_surface_v1::Anchor::Top), ); + layer_surface.set_size(max(size.0, min_size.width), max(size.1, min_size.height)); main_surface.commit(); self.main_surface.layer_surface = Some(layer_surface); } } - let viewporter = self.protocols.viewporter.as_ref().expect("wp_viewporter missing"); + let viewporter = self + .protocols + .viewporter + .as_ref() + .expect("wp_viewporter missing"); let viewport = viewporter.get_viewport(&bg_surface, qh, ()); self.bg_surface.viewport = Some(viewport); if let Some(background) = self.background { - let wp_single_pixel_buffer_manager = self.protocols.wp_single_pixel_buffer_manager.as_ref().expect("wp_single_pixel_buffer_manager_v1 missing"); - let buffer = wp_single_pixel_buffer_manager.create_u32_rgba_buffer( - 0, - 0, - 0, - background, - qh, - (), - ); + let wp_single_pixel_buffer_manager = self + .protocols + .wp_single_pixel_buffer_manager + .as_ref() + .expect("wp_single_pixel_buffer_manager_v1 missing"); + let buffer = + wp_single_pixel_buffer_manager.create_u32_rgba_buffer(0, 0, 0, background, qh, ()); bg_surface.attach(Some(&buffer), 0, 0); bg_surface.damage_buffer(0, 0, 1, 1); } @@ -259,8 +275,9 @@ impl Dispatch for State { Some(registry.bind::(name, 5, qh, ())); } "wl_subcompositor" => { - state.protocols.wl_subcompositor = - Some(registry.bind::(name, 1, qh, ())); + state.protocols.wl_subcompositor = Some( + registry.bind::(name, 1, qh, ()), + ); } "wl_shm" => { state.protocols.wl_shm = @@ -282,8 +299,14 @@ impl Dispatch for State { } "zwlr_layer_shell_v1" => { if let OperationMode::LayerSurface(_) = state.mode { - state.protocols.zwlr_layer_shell = Some(registry - .bind::(name, 4, qh, ())); + state.protocols.zwlr_layer_shell = Some( + registry.bind::( + name, + 4, + qh, + (), + ), + ); } } "wp_viewporter" => { @@ -309,10 +332,8 @@ impl Dispatch for State { _: &QueueHandle, ) { match event { - wl_callback::Event::Done { callback_data: _ } => { - state.ready = true - }, - _ => {}, + wl_callback::Event::Done { callback_data: _ } => state.ready = true, + _ => {} } } } @@ -463,8 +484,16 @@ impl Dispatch for State { state, ); - let surface = state.bg_surface.wl_surface.as_ref().expect("bg surface was not ready"); - let viewport = state.bg_surface.viewport.as_ref().expect("bg viewport was not ready"); + let surface = state + .bg_surface + .wl_surface + .as_ref() + .expect("bg surface was not ready"); + let viewport = state + .bg_surface + .viewport + .as_ref() + .expect("bg viewport was not ready"); viewport.set_destination(dim.0, dim.1); surface.commit(); } @@ -545,8 +574,16 @@ impl Dispatch for State { } if !state.configured || state.dimensions != (width, height) { - let surface = state.bg_surface.wl_surface.as_ref().expect("bg surface was not ready"); - let viewport = state.bg_surface.viewport.as_ref().expect("bg viewportw as not ready"); + let surface = state + .bg_surface + .wl_surface + .as_ref() + .expect("bg surface was not ready"); + let viewport = state + .bg_surface + .viewport + .as_ref() + .expect("bg viewportw as not ready"); viewport.set_destination(width, height); surface.commit(); diff --git a/src/utils/desktop.rs b/src/utils/desktop.rs index a63c58f..5074760 100644 --- a/src/utils/desktop.rs +++ b/src/utils/desktop.rs @@ -77,14 +77,14 @@ impl PartialEq for Desktop { pub fn load_desktop_cache() -> Result, Box> { match File::open(format!("{}/wldash/desktop.json", xdg::cache_folder())) { Ok(f) => simd_json::from_reader(f).map_err(|e| e.into()), - Err(e) => Err(e.into()) + Err(e) => Err(e.into()), } } pub fn write_desktop_cache(v: &Vec) -> Result<(), Box> { match File::create(format!("{}/wldash/desktop.json", xdg::cache_folder())) { Ok(f) => simd_json::to_writer(f, v).map_err(|e| e.into()), - Err(e) => Err(e.into()) + Err(e) => Err(e.into()), } } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index bb7cf87..9f478dc 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,3 +1,3 @@ pub mod desktop; -pub mod xdg; mod inish; +pub mod xdg; diff --git a/src/utils/xdg.rs b/src/utils/xdg.rs index f5a2662..9146912 100644 --- a/src/utils/xdg.rs +++ b/src/utils/xdg.rs @@ -10,7 +10,6 @@ pub fn cache_folder() -> String { } } - pub fn config_folder() -> String { match env::var_os("XDG_CONFIG_HOME") { Some(s) => s.into_string().unwrap(), @@ -35,5 +34,7 @@ pub fn data_folders() -> Vec { }; // Eww. - std::iter::once(xdg_data_home.to_string()).chain(xdg_data_dirs.split(':').map(|x| x.to_string())).collect() + std::iter::once(xdg_data_home.to_string()) + .chain(xdg_data_dirs.split(':').map(|x| x.to_string())) + .collect() } diff --git a/src/widgets/calendar.rs b/src/widgets/calendar.rs index bfa3b77..ff3c30b 100644 --- a/src/widgets/calendar.rs +++ b/src/widgets/calendar.rs @@ -44,7 +44,11 @@ impl Calendar { fm.queue_font(font, size * DAY_FACTOR, "ADEFHIMNORSTUW"); fm.queue_font(font, size * YEAR_FACTOR, "0123456789"); fm.queue_font(font, size * DATE_FACTOR, "0123456789"); - fm.queue_font(month_font, size * MONTH_FACTOR, "ADFJMNOSabcehgilmnoprstuvy"); + fm.queue_font( + month_font, + size * MONTH_FACTOR, + "ADFJMNOSabcehgilmnoprstuvy", + ); Calendar { font, diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs index a6be498..322d5e3 100644 --- a/src/widgets/launcher.rs +++ b/src/widgets/launcher.rs @@ -16,7 +16,7 @@ use crate::{ fonts::FontMap, keyboard::{keysyms, KeyEvent}, utils::{ - desktop::{load_desktop_files, load_desktop_cache, write_desktop_cache, Desktop}, + desktop::{load_desktop_cache, load_desktop_files, write_desktop_cache, Desktop}, xdg, }, widgets::{Geometry, Widget}, @@ -615,7 +615,7 @@ impl Interface { fm: &mut FontMap, font: &'static str, size: f32, - launch_cmd: Option + launch_cmd: Option, ) -> Interface { fm.queue_font( font, diff --git a/src/widgets/layout.rs b/src/widgets/layout.rs index 1009fad..21f6c18 100644 --- a/src/widgets/layout.rs +++ b/src/widgets/layout.rs @@ -281,4 +281,3 @@ impl Layout for Margin { } } } - From 9fd054fc2cbe7457f89c4864e2025459f9ab1fe4 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Thu, 27 Feb 2025 15:10:07 +0100 Subject: [PATCH 54/57] Fix damage issue when multiple buffers are in use wldash was originally written to expect server-side shm buffer optimizations, which make it so only a single buffer has to be in use. This simplifies damage tracking and repaint needs. Support for using multiple buffers was added to support e.g., jay, but the damage handling was not in place for this to work. Fix that by tracking damage per buffer. --- src/buffer.rs | 2 ++ src/main.rs | 24 ++++++++++++++---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index e015914..f48077c 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -83,6 +83,7 @@ pub struct ShmBuffer { pub buffer: wl_buffer::WlBuffer, pub refcnt: u32, pub id: u32, + pub last_damage: Vec, } impl ShmBuffer { @@ -111,6 +112,7 @@ impl ShmBuffer { mmap, buffer, id, + last_damage: Vec::new(), }) } diff --git a/src/main.rs b/src/main.rs index d1a6dae..cd04469 100644 --- a/src/main.rs +++ b/src/main.rs @@ -220,11 +220,6 @@ fn main() { keyrepeat_sender, ); - let mut damage = Vec::new(); - for _ in 0..state.widgets.len() { - damage.push(Geometry::new()); - } - // Initial setup // while state.running && !state.ready { @@ -249,7 +244,7 @@ fn main() { force = true; } - // We expect to see the usual shm optimization + let mut bufcnt = state.bufmgr.buffers.len(); let buf = match state.bufmgr.next_buffer() { Some(b) => b, None => { @@ -257,6 +252,8 @@ fn main() { continue; } state.add_buffer(&qhandle); + bufcnt = state.bufmgr.buffers.len(); + match state.bufmgr.next_buffer() { Some(b) => b, None => { @@ -266,6 +263,12 @@ fn main() { } }; + if buf.last_damage.len() == 0 { + for _ in 0..state.widgets.len() { + buf.last_damage.push(Geometry::new()); + } + } + state.dirty = false; buf.acquire(); let mut bufview = BufferView::new( @@ -279,17 +282,18 @@ fn main() { if force || widget.get_dirty() { let geo = widget.geometry(); let mut subview = bufview.subgeometry(geo); - damage[idx] = widget.draw(&mut state.fonts.unwrap().borrow_mut(), &mut subview); + buf.last_damage[idx] = + widget.draw(&mut state.fonts.unwrap().borrow_mut(), &mut subview); } } surface.damage_buffer(0, 0, 0x7FFFFFFF, 0x7FFFFFFF); } else { let mut drew = false; for (idx, widget) in state.widgets.iter_mut().enumerate() { - if widget.get_dirty() { + if bufcnt > 1 || widget.get_dirty() { drew = true; - let old_damage = damage[idx]; + let old_damage = buf.last_damage[idx]; bufview.subgeometry(old_damage).clear(); let geo = widget.geometry(); @@ -298,7 +302,7 @@ fn main() { let new_damage = widget.draw(&mut state.fonts.unwrap().borrow_mut(), &mut subview); let combined_damage = new_damage.expand(old_damage); - damage[idx] = new_damage; + buf.last_damage[idx] = new_damage; surface.damage_buffer( combined_damage.x as i32, From 7e112ddd1f2f263b7ecf591206c9bf8bb3b64156 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Thu, 27 Feb 2025 16:27:35 +0100 Subject: [PATCH 55/57] keyboard: Update to xkbcommon 0.8 --- Cargo.lock | 15 +++++++++++---- Cargo.toml | 2 +- src/keyboard/mod.rs | 30 +++++++++++++++++++++++------- src/keyboard/state.rs | 12 +++++++----- 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd31566..91ce587 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -474,9 +474,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.7.1" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", ] @@ -1307,14 +1307,21 @@ dependencies = [ [[package]] name = "xkbcommon" -version = "0.5.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52db25b599e92bf6e3904134618728eeb7b49a5a4f38f107f92399bb9c496b88" +checksum = "8d66ca9352cbd4eecbbc40871d8a11b4ac8107cfc528a6e14d7c19c69d0e1ac9" dependencies = [ "libc", "memmap2", + "xkeysym", ] +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + [[package]] name = "yeslogic-fontconfig-sys" version = "5.0.0" diff --git a/Cargo.toml b/Cargo.toml index df92354..b34d337 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ unicode-segmentation = "1.10" fuzzy-matcher = "0.3" shlex = "1.3" -xkbcommon = { version = "0.5", features = ["wayland"] } # update breaks +xkbcommon = { version = "0.8", features = ["wayland"] } rcalc_lib = "1.0" diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs index e3b6781..4ee4efa 100644 --- a/src/keyboard/mod.rs +++ b/src/keyboard/mod.rs @@ -39,23 +39,40 @@ pub struct KeyEvent { pub repeats: bool, } +pub struct KeymapDescription { + fd: OwnedFd, + size: u32, +} + +impl KeymapDescription { + fn new(fd: OwnedFd, size: u32) -> KeymapDescription { + KeymapDescription { fd: fd, size: size } + } +} + +impl Clone for KeymapDescription { + fn clone(&self) -> Self { + KeymapDescription::new(self.fd.try_clone().expect("unable to clone fd"), self.size) + } +} + pub struct Keyboard { state: Option, - fd: Option<(OwnedFd, u32)>, + desc: Option, } impl Keyboard { pub fn new() -> Keyboard { Keyboard { state: None, - fd: None, + desc: None, } } pub fn keymap(&mut self, format: WEnum, fd: OwnedFd, size: u32) { match format { WEnum::Value(wl_keyboard::KeymapFormat::XkbV1) => { - self.fd = Some((fd, size)); + self.desc = Some(KeymapDescription::new(fd, size)); } WEnum::Value(wl_keyboard::KeymapFormat::NoKeymap) => { // TODO: how to handle this (hopefully never occuring) case? @@ -69,10 +86,9 @@ impl Keyboard { return; } - if let Some((fd, size)) = &self.fd { + if let Some(desc) = self.desc.take() { self.state = Some( - KbState::new_from_fd(fd.as_raw_fd(), *size as usize) - .expect("unable to load keymap"), + KbState::new_from_fd(desc.fd, desc.size as usize).expect("unable to load keymap"), ); } } @@ -107,7 +123,7 @@ impl Keyboard { } else { None }; - let repeats = state.key_repeats(key + 8); + let repeats = state.key_repeats(xkb::Keycode::new(key + 8)); (sym, utf8, repeats) }; KeyEvent { diff --git a/src/keyboard/state.rs b/src/keyboard/state.rs index 300a5f6..bca7f8e 100644 --- a/src/keyboard/state.rs +++ b/src/keyboard/state.rs @@ -1,4 +1,4 @@ -use std::{env, os::unix::io::RawFd}; +use std::{env, os::unix::io::OwnedFd}; use xkbcommon::xkb; @@ -71,16 +71,18 @@ impl KbState { } pub(crate) fn get_one_sym_raw(&mut self, keycode: u32) -> u32 { - self.xkb_state.key_get_one_sym(keycode + 8) + self.xkb_state + .key_get_one_sym(xkb::Keycode::new(keycode + 8)) + .raw() } pub(crate) fn get_utf8_raw(&mut self, keycode: u32) -> Option { - Some(self.xkb_state.key_get_utf8(keycode + 8)) + Some(self.xkb_state.key_get_utf8(xkb::Keycode::new(keycode + 8))) } pub(crate) fn compose_feed(&mut self, keysym: u32) -> Option { match &mut self.xkb_compose_state { - Some(compose_state) => Some(compose_state.feed(keysym)), + Some(compose_state) => Some(compose_state.feed(xkb::Keysym::new(keysym))), None => None, } } @@ -99,7 +101,7 @@ impl KbState { } } - pub(crate) fn new_from_fd(fd: RawFd, size: usize) -> Result { + pub(crate) fn new_from_fd(fd: OwnedFd, size: usize) -> Result { let xkb_context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS); let xkb_keymap = unsafe { xkb::Keymap::new_from_fd( From 83179d2c7e6ccdb9435dd86f22eb71eaca3fc738 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Thu, 27 Feb 2025 16:27:53 +0100 Subject: [PATCH 56/57] state: Increase xdg_toplevel default dimensions The previous values are too small for the default toplevel layout. --- src/state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/state.rs b/src/state.rs index 17b5f03..b36f5e1 100644 --- a/src/state.rs +++ b/src/state.rs @@ -557,7 +557,7 @@ impl Dispatch for State { if state.configured { return; } - (width, height) = (640, 480); + (width, height) = (1280, 720); } let activated = states .iter() From a8cd777ca98a2c7241c0c47bcbcf03a04b37a450 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Thu, 27 Feb 2025 16:28:19 +0100 Subject: [PATCH 57/57] Clean up command-line arguments, usage text --- src/main.rs | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index cd04469..4562212 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,6 +38,21 @@ use std::{ thread, }; +fn print_usage() { + println!( + " +usage: wldash [OPTIONS] + +OPTIONS: + --config CONFIG_FILE use the specified config file + (~/.config/wldash/config.yml by default) + --write-default-config [v1|v2|toplevel] generate and write a new config file + (v2 by default) + --desktop-refresh refresh desktop file cache +" + ); +} + fn main() { let mut args = env::args(); // Skip program name @@ -51,11 +66,11 @@ fn main() { Some(ref s) => config_file = s.clone(), None => panic!("missing argument to --config"), }, - Some(ref s) if s == "generate" => { + Some(ref s) if s == "--write-default-config" => { let config = match args.next() { Some(ref s) if s == "v1" => Config::generate_v1(), Some(ref s) if s == "v2" => Config::generate_v2(false), - Some(ref s) if s == "clay" => Config::generate_v2(true), + Some(ref s) if s == "toplevel" => Config::generate_v2(true), None => Config::generate_v2(false), Some(s) => panic!("unknown argument: {}", s), }; @@ -65,12 +80,19 @@ fn main() { } std::process::exit(0); } - Some(ref s) if s == "desktop-refresh" => { + Some(ref s) if s == "--desktop-refresh" => { let v = load_desktop_files(); write_desktop_cache(&v).unwrap(); std::process::exit(0); } - Some(_) => panic!("unknown argument"), + Some(ref s) if s == "--help" => { + print_usage(); + std::process::exit(0); + } + Some(_) => { + print_usage(); + std::process::exit(1); + } None => break, } }