diff --git a/.gitignore b/.gitignore index 53eaa21..ea8c4bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ /target -**/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock index f9f07c7..91ce587 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,45 +1,30 @@ # 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.7.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" dependencies = [ - "getrandom", + "cfg-if", "once_cell", "version_check", + "zerocopy", ] [[package]] -name = "alsa" -version = "0.7.0" +name = "android-tzdata" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8512c9117059663fb5606788fbca3619e2a91dac0e3fe516242eab1fa6be5e44" -dependencies = [ - "alsa-sys", - "bitflags", - "libc", - "nix 0.24.3", -] - -[[package]] -name = "alsa-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" -dependencies = [ - "libc", - "pkg-config", -] +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" @@ -64,30 +49,41 @@ 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 = "byteorder" -version = "1.4.3" +name = "calloop" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "52e0d00eb1ea24371a97d2da6201c6747a633dc6dc1988ef503403b4c59504a8" +dependencies = [ + "bitflags", + "log", + "nix 0.25.1", + "slotmap", + "thiserror", + "vec_map", +] [[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" @@ -97,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", ] @@ -152,64 +131,31 @@ 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]] name = "dbus" -version = "0.6.5" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48b5f0f36f1eebe901b0e6bee369a77ed3396334bf3f09abd46454a576f71819" +checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" dependencies = [ "libc", "libdbus-sys", + "winapi", ] [[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", @@ -217,19 +163,13 @@ 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", ] -[[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" @@ -243,37 +183,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" [[package]] -name = "either" -version = "1.8.0" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "errno" -version = "0.2.8" +name = "float-cmp" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", + "num-traits", ] [[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", ] @@ -289,9 +217,9 @@ 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", @@ -299,88 +227,103 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "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" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ "ahash", ] [[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", + "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 = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +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", ] @@ -392,35 +335,99 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] -name = "libc" -version = "0.2.138" +name = "lexical-core" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +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.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libdbus-sys" -version = "0.2.2" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c185b5b7ad900923ef3a8ff594083d4d9b5aea80bb4f32b8342363138c0d456b" +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.26.0" +version = "2.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17be42160017e0ae993c03bfdab4ecb6f82ce3f8d515bd8da8fdf18d10703663" +checksum = "ed3557a2dfc380c8f061189a01c6ae7348354e0c9886038dc6c171219c08eaff" dependencies = [ "bitflags", "libc", @@ -432,9 +439,9 @@ dependencies = [ [[package]] name = "libpulse-sys" -version = "1.19.3" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "991e6bd0efe2a36e6534e136e7996925e4c1a8e35b7807fe533f2beffff27c30" +checksum = "bc19e110fbf42c17260d30f6d3dc545f58491c7830d38ecb9aaca96e26067a9b" dependencies = [ "libc", "num-derive", @@ -443,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 = "linux-raw-sys" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" - [[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.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memmap" @@ -477,6 +472,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "memmap2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.6.5" @@ -497,10 +501,11 @@ dependencies = [ [[package]] name = "nix" -version = "0.24.3" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" dependencies = [ + "autocfg", "bitflags", "cfg-if", "libc", @@ -509,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]] @@ -550,7 +554,7 @@ checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -577,38 +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" - -[[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 = "os_pipe" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6a252f1f8c11e84b3ab59d7a488e48e4478a93937e027076638c49536204639" -dependencies = [ - "libc", - "windows-sys", -] +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "owned_ttf_parser" @@ -621,19 +605,20 @@ dependencies = [ [[package]] name = "pest" -version = "2.5.1" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc8bed3549e0f9b0a2a78bf7c0018237a2cdf085eecbbc048e52612438e4e9d0" +checksum = "1f200d8d83c44a45b21764d1916299752ca035d15ecd46faca3e9a2a2bf6ad06" dependencies = [ + "memchr", "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.5.1" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdc078600d06ff90d4ed238f0119d84ab5d43dbaad278b0e33a8820293b32344" +checksum = "bcd6ab1236bbdb3a49027e920e693192ebfe8913f6d60e294de57463a493cfde" dependencies = [ "pest", "pest_generator", @@ -641,26 +626,26 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.5.1" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a1af60b1c4148bb269006a750cff8e2ea36aff34d2d96cf7be0b14d1bed23c" +checksum = "2a31940305ffc96863a735bef7c7994a00b325a7138fdbc5bda0f1a0476d3275" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] name = "pest_meta" -version = "2.5.1" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fec8605d59fc2ae0c6c1aefc0c7c7a9769732017c0ce07f7a9cfffa7b4404f20" +checksum = "a7ff62f5259e53b78d1af898941cdcdccfae7385cf7d793a6e55de5d05bb4b7d" dependencies = [ "once_cell", "pest", - "sha1", + "sha2", ] [[package]] @@ -671,24 +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 = "quote" -version = "1.0.21" +name = "quick-xml" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -710,27 +704,23 @@ dependencies = [ ] [[package]] -name = "rust-ini" -version = "0.18.0" +name = "ref-cast" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +checksum = "c4846d4c50d1721b1a3bef8af76924eef20d5e723647333798c1b519b3a9473f" dependencies = [ - "cfg-if", - "ordered-multimap", + "ref-cast-impl", ] [[package]] -name = "rustix" -version = "0.36.4" +name = "ref-cast-impl" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb93e85278e08bb5788653183213d3a60fc242b10cb9be96586f5a73dcb67c23" +checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys", + "proc-macro2", + "quote", + "syn 2.0.48", ] [[package]] @@ -745,9 +735,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "same-file" @@ -759,36 +749,36 @@ dependencies = [ ] [[package]] -name = "scratch" -version = "1.0.2" +name = "scoped-tls" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "serde" -version = "1.0.148" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.148" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.48", ] [[package]] name = "serde_json" -version = "1.0.89" +version = "1.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" dependencies = [ "itoa", "ryu", @@ -797,9 +787,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.14" +version = "0.9.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d232d893b10de3eb7258ff01974d6ee20663d8e833263c99409d4b13a0209da" +checksum = "adf8a49373e98a4c5f0ceb5d05aa7c648d75f63774981ed95b7c7443bbd50c6e" dependencies = [ "indexmap", "itoa", @@ -809,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", @@ -821,15 +811,46 @@ 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.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2faf8f101b9bc484337a6a6b0409cf76c139f2fb70a9e3aee6b6774be7bfbf76" +dependencies = [ + "getrandom", + "halfbrown", + "lexical-core", + "ref-cast", + "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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +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" @@ -839,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", @@ -849,63 +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 0.10.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "timerfd" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0664936efa25f2bbe03ca25b62c50f5f492abec07e59d6dcf45131014b33483f" -dependencies = [ - "rustix", -] - [[package]] name = "ttf-parser" version = "0.15.2" @@ -914,39 +918,51 @@ checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" [[package]] name = "typenum" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +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.4" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e5fa573d8ac5f1a856f8d7be41d390ee973daf97c806b2c1a465e4e1406e68" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" + +[[package]] +name = "value-trait" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad8db98c1e677797df21ba03fca7d3bf9bec3ca38db930954e4fe6e1ea27eb4" +dependencies = [ + "float-cmp", + "halfbrown", + "itoa", + "ryu", +] + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" @@ -956,21 +972,14 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -979,9 +988,9 @@ 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", @@ -989,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", @@ -1014,79 +1023,96 @@ 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-client" -version = "0.29.5" +name = "wayland-backend" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" +checksum = "41b48e27457e8da3b2260ac60d0a94512f5cba36448679f3747c0865b7893ed8" dependencies = [ - "bitflags", + "cc", "downcast-rs", - "libc", - "nix 0.24.3", - "wayland-commons", - "wayland-scanner", - "wayland-sys", -] - -[[package]] -name = "wayland-commons" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" -dependencies = [ - "nix 0.24.3", - "once_cell", + "io-lifetimes", + "nix 0.26.4", + "scoped-tls", "smallvec", "wayland-sys", ] [[package]] -name = "wayland-protocols" -version = "0.29.5" +name = "wayland-client" +version = "0.30.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" +checksum = "489c9654770f674fc7e266b3c579f4053d7551df0ceb392f153adb1f9ed06ac8" dependencies = [ "bitflags", + "calloop", + "log", + "nix 0.26.4", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b28101e5ca94f70461a6c2d610f76d85ad223d042dd76585ab23d3422dd9b4d" +dependencies = [ + "bitflags", + "wayland-backend", "wayland-client", - "wayland-commons", + "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.29.5" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" +checksum = "b9b873b257fbc32ec909c0eb80dea312076a67014e65e245f5eb69a6b8ab330e" dependencies = [ "proc-macro2", + "quick-xml", "quote", - "xml-rs", ] [[package]] name = "wayland-sys" -version = "0.29.5" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" +checksum = "96b2a02ac608e07132978689a6f9bf4214949c85998c247abadd4f4129b1aa06" dependencies = [ + "dlib", + "log", "pkg-config", ] @@ -1108,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", ] @@ -1122,108 +1148,208 @@ 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 = "wldash" -version = "0.3.0" +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 = [ - "alsa", - "bitflags", - "byteorder", + "calloop", "chrono", "dbus", - "dlib", "fontconfig", "fuzzy-matcher", - "itertools", - "lazy_static", "libpulse-binding", "memmap", - "nix 0.26.1", - "os_pipe", + "nix 0.26.4", "rcalc_lib", - "rust-ini", "rusttype", "serde", - "serde_json", "serde_yaml", "shlex", - "timerfd", + "simd-json", "unicode-segmentation", "walkdir", "wayland-client", "wayland-protocols", + "wayland-protocols-wlr", + "xkbcommon", ] [[package]] -name = "xml-rs" -version = "0.8.4" +name = "xkbcommon" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" +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 = "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 faabe14..b34d337 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,50 +1,40 @@ [package] -name = "wldash" -version = "0.3.0" -authors = ["Kenny Levinsen "] +name = "wldash2" +version = "0.1.0" edition = "2021" [profile.release] +#debug = true lto = "fat" panic = "abort" -opt-level = "z" - -[features] -default = ["alsa-widget", "pulseaudio-widget", "yaml-cfg", "json-cfg"] -damage_debug = [] - -yaml-cfg = ["serde_yaml"] -json-cfg = ["serde_json"] - -alsa-widget = ["alsa"] -pulseaudio-widget = ["libpulse-binding"] [dependencies] -byteorder = "1.4" -bitflags = "1.3" -dlib = "0.5" -rusttype = "^0.9" -chrono = "0.4" -nix = "0.26" +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" # 0.27 is breaking memmap = "0.7" -os_pipe = "1.1" -wayland-client = { version = "0.29"} -wayland-protocols = { version = "0.29", features = ["client", "unstable_protocols"] } -dbus = "0.6" +fontconfig = { version = "0.8.0", optional = true } +rusttype = "0.9" +# fontdue = "0.7" +calloop = { version = "0.10" } # update breaks +chrono = "0.4" + +walkdir = "2.4" + +unicode-segmentation = "1.10" fuzzy-matcher = "0.3" -lazy_static = "1.4" +shlex = "1.3" + +xkbcommon = { version = "0.8", features = ["wayland"] } + rcalc_lib = "1.0" -rust-ini = "0.18" -shlex = "1.1" -timerfd = "1.4.0" -walkdir = "2.3" -itertools = "0.10" + +#alsa = { version = "0.7", optional = true } +dbus = "0.9" +libpulse-binding = "2.28" serde = { version = "1.0", features = ["derive"] } -serde_json = { version = "1.0", optional = true } -serde_yaml = { version = "0.9", optional = true } +serde_yaml = "0.9" +simd-json = "0.13" -libpulse-binding = { version = "2.26", optional = true } -alsa = { version = "0.7.0", optional = true } -fontconfig = "0.6.0" -unicode-segmentation = "1.10.0" diff --git a/config.json b/config.json deleted file mode 100644 index 62c5d0c..0000000 --- a/config.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "widget": { - "margin": { - "margins": [20,20,20,20], - "widget": { - "verticalLayout": [{ - "horizontalLayout": [{ - "margin": { - "margins": [0,88,0,32], - "widget": { - "verticalLayout": [ - {"date": {"font_size": 64.0}}, - {"clock": {"font_size": 256.0}} - ] - } - } - },{ - "verticalLayout": [{ - "margin": { - "margins": [0,0,0,8], - "widget": {"battery": {"font_size": 24.0,"length": 600}} - } - },{ - "margin": { - "margins": [0,0,0,8], - "widget": {"backlight": {"device": "intel_backlight", "font_size": 24.0,"length": 600}} - } - },{ - "margin": { - "margins": [0,0,0,8], - "widget": {"pulseAudio": {"font_size": 24.0,"length": 600}} - } - }] - } - ]}, - {"calendar": {"font_size": 16.0,"sections": 3}}, - {"launcher": { - "font_size": 32.0, - "length": 1200, - "app_opener": "swaymsg exec -- ", - "term_opener": "foot --", - "url_opener": "xdg-open " - }} - ]} - } - }, - "outputMode": "active", - "scale": 1, - "background": { - "red": 0, - "green": 0, - "blue": 0, - "opacity": 0.9 - } -} diff --git a/config.yaml b/config.yaml deleted file mode 100644 index 583ec55..0000000 --- a/config.yaml +++ /dev/null @@ -1,50 +0,0 @@ -outputMode: active -scale: 1 -background: - red: 0.0 - green: 0.0 - blue: 0.0 - opacity: 0.9 -widget: - margin: - margins: [20, 20, 20, 20] - widget: - verticalLayout: - - horizontalLayout: - - margin: - margins: [0, 88, 0, 32] - widget: - verticalLayout: - - date: - font_size: 64.0 - - clock: - font_size: 256.0 - - verticalLayout: - - margin: - margins: [0, 0, 0, 8] - widget: - battery: - font_size: 24.0 - length: 600 - - margin: - margins: [0, 0, 0, 8] - widget: - backlight: - device: "intel_backlight" - font_size: 24.0 - length: 600 - - margin: - margins: [0, 0, 0, 8] - widget: - pulseAudio: - font_size: 24.0 - length: 600 - - calendar: - font_size: 16.0 - sections: 3 - - launcher: - font_size: 32.0 - length: 1200 - app_opener: "swaymsg exec --" - term_opener: "foot --" - url_opener: "xdg-open " diff --git a/src/app.rs b/src/app.rs deleted file mode 100644 index d3d5dc8..0000000 --- a/src/app.rs +++ /dev/null @@ -1,600 +0,0 @@ -use std::collections::VecDeque; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::mpsc::Sender; -use std::sync::{Arc, Mutex}; - -use chrono::{Duration, Local, NaiveDateTime}; - -use crate::keyboard::{keysyms, map_keyboard, Event as KbEvent, KeyState, ModifiersState}; - -use wayland_client::protocol::{wl_compositor, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface}; -use wayland_client::{Display, EventQueue, GlobalEvent, GlobalManager, Main}; -use wayland_protocols::wlr::unstable::layer_shell::v1::client::{ - zwlr_layer_shell_v1, zwlr_layer_surface_v1, -}; - -use crate::buffer::Buffer; -use crate::color::Color; -use crate::widget::{DrawContext, WaitContext, Widget}; - -use crate::cmd::Cmd; -use crate::doublemempool::DoubleMemPool; - -#[derive(Debug)] -pub enum OutputMode { - Active, - All, -} - -struct AppInner { - compositor: Option>, - surfaces: Vec>, - shell_surfaces: Vec>, - configured_surfaces: Arc>, - outputs: Vec<(u32, Main)>, - shell: Option>, - draw_tx: Sender, - output_mode: OutputMode, - visible: bool, - scale: u32, -} - -impl AppInner { - fn new(tx: Sender, output_mode: OutputMode, scale: u32) -> AppInner { - AppInner { - compositor: None, - surfaces: Vec::new(), - shell_surfaces: Vec::new(), - configured_surfaces: Arc::new(Mutex::new(0)), - outputs: Vec::new(), - shell: None, - draw_tx: tx, - output_mode, - visible: true, - scale, - } - } - - fn add_shell_surface( - compositor: &wl_compositor::WlCompositor, - shell: &zwlr_layer_shell_v1::ZwlrLayerShellV1, - scale: u32, - configured_surfaces: Arc>, - tx: Sender, - output: Option<&wl_output::WlOutput>, - ) -> ( - Main, - Main, - ) { - let surface = compositor.create_surface(); - - let this_is_stupid = AtomicBool::new(false); - - let shell_surface = shell.get_layer_surface( - &surface, - output, - zwlr_layer_shell_v1::Layer::Overlay, - "".to_string(), - ); - shell_surface.quick_assign(move |layer, event, _| match event { - zwlr_layer_surface_v1::Event::Configure { serial, .. } => { - if this_is_stupid - .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) - .is_ok() - { - *(configured_surfaces.lock().unwrap()) += 1; - layer.ack_configure(serial); - tx.send(Cmd::ForceDraw).unwrap(); - } - } - _ => unreachable!(), - }); - - shell_surface.set_keyboard_interactivity(zwlr_layer_surface_v1::KeyboardInteractivity::Exclusive); - shell_surface.set_size(1, 1); - surface.set_buffer_scale(scale as i32); - surface.commit(); - (surface, shell_surface) - } - - fn outputs_changed(&mut self) { - let shell = match self.shell { - Some(ref shell) => shell.to_owned(), - None => return, - }; - let compositor = match self.compositor { - Some(ref c) => c.to_owned(), - None => return, - }; - - for shell_surface in self.shell_surfaces.iter() { - shell_surface.destroy(); - } - for surface in self.surfaces.iter() { - surface.destroy(); - } - - self.configured_surfaces = Arc::new(Mutex::new(0)); - - if !self.visible { - self.surfaces = Vec::new(); - self.shell_surfaces = Vec::new(); - return; - } - - match self.output_mode { - OutputMode::Active => { - if !self.shell_surfaces.is_empty() { - return; - } - - let (surface, shell_surface) = AppInner::add_shell_surface( - &compositor, - &shell, - self.scale, - self.configured_surfaces.clone(), - self.draw_tx.clone(), - None, - ); - self.surfaces = vec![surface]; - self.shell_surfaces = vec![shell_surface]; - } - OutputMode::All => { - let mut surfaces = Vec::new(); - let mut shell_surfaces = Vec::new(); - for output in self.outputs.iter() { - let (surface, shell_surface) = AppInner::add_shell_surface( - &compositor, - &shell, - self.scale, - self.configured_surfaces.clone(), - self.draw_tx.clone(), - Some(&output.1), - ); - surfaces.push(surface); - shell_surfaces.push(shell_surface); - } - self.surfaces = surfaces; - self.shell_surfaces = shell_surfaces; - } - } - self.draw_tx.send(Cmd::ForceDraw).unwrap(); - } - - fn add_output(&mut self, id: u32, output: Main) { - self.outputs.push((id, output)); - self.outputs_changed(); - } - - fn remove_output(&mut self, id: u32) { - let old_output = self.outputs.iter().find(|(output_id, _)| *output_id == id); - if let Some(output) = old_output { - let new_outputs = self - .outputs - .iter() - .filter(|(output_id, _)| *output_id != id) - .map(|(x, y)| (*x, y.clone())) - .collect(); - if output.1.as_ref().version() >= 3 { - output.1.release() - } - self.outputs = new_outputs; - self.outputs_changed(); - } - } - - fn set_compositor(&mut self, compositor: Option>) { - self.compositor = compositor - } - - fn set_shell(&mut self, shell: Option>) { - self.shell = shell - } -} - -struct AppKeyboard { - current: Option, - delay: i32, - rate: i32, - next: Option, -} - -pub struct App<'a> { - pools: DoubleMemPool, - display: Display, - event_queue: EventQueue, - cmd_queue: Arc>>, - widget: Option>, - bg: Color, - inner: Arc>, - last_damage: Option>, - last_dim: (u32, u32), - keyboard: Arc>, -} - -impl<'a> App<'a> { - pub fn redraw(&mut self, force: bool) -> Result<(), ::std::io::Error> { - let widget = match self.widget { - Some(ref mut widget) => widget, - None => return Ok(()), - }; - - let inner = self.inner.lock().unwrap(); - let time = Local::now().naive_local(); - - if !inner.visible - || inner.shell_surfaces.len() != *inner.configured_surfaces.lock().unwrap() - || inner.surfaces.is_empty() - { - // Not ready yet - return Ok(()); - } - - let (last, pool) = match self.pools.pool() { - Some((last, pool)) => (last, pool), - None => { - self.pools.never_mind(); - return Ok(()); - } - }; - - let size = widget.size(); - let size_changed = self.last_dim != size; - let force = force | size_changed; - - // resize the pool if relevant - pool.resize((4 * size.0 * size.1) as usize) - .expect("Failed to resize the memory pool."); - let mmap = pool.mmap(); - let mut buf = Buffer::new(mmap, size); - - // Copy old damage - let force = match (force, &self.last_damage) { - (false, Some(d)) => { - let lastmmap = last.mmap(); - let last = Buffer::new(lastmmap, size); - - if cfg!(feature = "damage_debug") { - buf.memset(&Color::new(0.5, 0.75, 0.75, 1.0)); - } - for d in d { - last.copy_to(&mut buf, d.clone()); - } - false - } - _ => true, - }; - - if force { - buf.memset(&self.bg); - } - let report = widget.draw( - &mut DrawContext { - buf: &mut buf, - bg: &self.bg, - time, - force, - }, - (0, 0), - size, - )?; - - mmap.flush().unwrap(); - - if !force && !report.full_damage && report.damage.is_empty() { - // Nothing to do - self.pools.never_mind(); - return Ok(()); - } - - // get a buffer and attach it - let new_buffer = pool.buffer( - 0, - report.width as i32, - report.height as i32, - 4 * size.0 as i32, - wl_shm::Format::Argb8888, - ); - if size_changed { - for shell_surface in inner.shell_surfaces.iter() { - shell_surface.set_size(size.0 / inner.scale, size.1 / inner.scale); - } - } - for surface in inner.surfaces.iter() { - surface.attach(Some(&new_buffer), 0, 0); - if cfg!(feature = "damage_debug") || force || report.full_damage { - surface.damage_buffer(0, 0, size.0 as i32, size.1 as i32); - } else { - for d in report.damage.iter() { - surface.damage_buffer(d.0, d.1, d.2, d.3); - } - } - surface.commit(); - } - self.last_damage = if force || report.full_damage { - Some(vec![(0, 0, size.0 as i32, size.1 as i32)]) - } else { - Some(report.damage) - }; - self.last_dim = size; - Ok(()) - } - - pub fn hide(&mut self) { - let mut inner = self.inner.lock().unwrap(); - inner.visible = false; - self.last_dim = (0, 0); - inner.outputs_changed(); - } - - pub fn show(&mut self) { - let mut inner = self.inner.lock().unwrap(); - inner.visible = true; - self.last_dim = (0, 0); - inner.outputs_changed(); - } - - pub fn cmd_queue(&self) -> Arc>> { - self.cmd_queue.clone() - } - - pub fn flush_display(&mut self) { - self.display.flush().expect("unable to flush display"); - } - - pub fn event_queue(&mut self) -> &mut EventQueue { - &mut self.event_queue - } - - pub fn get_widget(&mut self) -> &mut Box { - self.widget.as_mut().unwrap() - } - - pub fn set_widget(&mut self, w: Box) -> Result<(), ::std::io::Error> { - self.widget = Some(w); - self.redraw(true) - } - - pub fn set_keyboard_repeat(&mut self, ctx: &mut WaitContext) { - let kbd = self.keyboard.lock().unwrap(); - if let Some(t) = kbd.next { - ctx.set_time(t); - } - } - - pub fn key_repeat(&mut self) -> Option { - let time = Local::now().naive_local(); - let mut kbd = self.keyboard.lock().unwrap(); - if let Some(target) = kbd.next { - if time >= target { - let cmd = kbd.current.as_ref().unwrap().clone(); - kbd.next = Some(time + Duration::milliseconds(kbd.rate.into())); - return Some(cmd); - } - } - None - } - - pub fn new(tx: Sender, output_mode: OutputMode, bg: Color, scale: u32) -> App<'a> { - let inner = Arc::new(Mutex::new(AppInner::new(tx, output_mode, scale))); - - // - // Set up modules - // - - let cmd_queue = Arc::new(Mutex::new(VecDeque::new())); - - let display = Display::connect_to_env().unwrap(); - let mut event_queue = display.create_event_queue(); - let display_wrapper = (*display).clone().attach(event_queue.token()); - - // - // Set up global manager - // - let inner_global = inner.clone(); - let manager = - GlobalManager::new_with_cb(&display_wrapper, move |event, registry, _| match event { - GlobalEvent::New { - id, - ref interface, - version, - } => { - if let "wl_output" = &interface[..] { - let output = registry.bind(std::cmp::min(version, 3), id); - inner_global.lock().unwrap().add_output(id, output); - } - } - GlobalEvent::Removed { id, ref interface } => { - if let "wl_output" = &interface[..] { - inner_global.lock().unwrap().remove_output(id); - } - } - }); - - // double sync to retrieve the global list - // and the globals metadata - event_queue.sync_roundtrip(&mut (), |_, _, _| {}).unwrap(); - event_queue.sync_roundtrip(&mut (), |_, _, _| {}).unwrap(); - - // wl_compositor - let compositor = manager - .instantiate_range(1, 4) - .expect("server didn't advertise `wl_compositor`"); - - inner.lock().unwrap().set_compositor(Some(compositor)); - - // wl_shm - let shm: Main = manager - .instantiate_range(1, 1) - .expect("server didn't advertise `wl_shm`"); - - let pools = DoubleMemPool::new(shm).expect("Failed to create a memory pool !"); - - // - // Get our seat - // - let seat: Main = manager.instantiate_range(1, 6).unwrap(); - event_queue.sync_roundtrip(&mut (), |_, _, _| {}).unwrap(); - - // - // Keyboard processing - // - let kbd_clone = cmd_queue.clone(); - let modifiers_state = Arc::new(Mutex::new(ModifiersState { - ctrl: false, - alt: false, - shift: false, - caps_lock: false, - logo: false, - num_lock: false, - })); - - let keyboard = Arc::new(Mutex::new(AppKeyboard { - current: None, - delay: 0, - rate: 0, - next: None, - })); - - let kb2 = keyboard.clone(); - - map_keyboard(&seat, None, move |event: KbEvent, _, _| match event { - KbEvent::Key { - keysym, - utf8, - state, - .. - } => { - if let KeyState::Pressed = state { - match keysym { - keysyms::XKB_KEY_Escape => kbd_clone.lock().unwrap().push_back(Cmd::Exit), - keysyms::XKB_KEY_c if modifiers_state.lock().unwrap().ctrl => { - kbd_clone.lock().unwrap().push_back(Cmd::Exit) - } - v => { - let ev = Cmd::Keyboard { - key: v, - key_state: state, - modifiers_state: *modifiers_state.lock().unwrap(), - interpreted: utf8, - }; - let mut kbd = kb2.lock().unwrap(); - kbd.current = Some(ev.clone()); - kbd.next = if kbd.delay > 0 { - Some( - Local::now().naive_local() - + Duration::milliseconds(kbd.delay.into()), - ) - } else { - None - }; - drop(kbd); - kbd_clone.lock().unwrap().push_back(ev); - } - } - } else { - kb2.lock().unwrap().next = None; - } - } - KbEvent::Leave { .. } => { - kb2.lock().unwrap().next = None; - } - KbEvent::RepeatInfo { delay, rate } => { - let mut kbd = kb2.lock().unwrap(); - kbd.delay = delay; - kbd.rate = rate; - } - KbEvent::Modifiers { modifiers } => *modifiers_state.lock().unwrap() = modifiers, - _ => (), - }) - .expect("could not map keyboard"); - - // - // Prepare shell so that we can create our shell surface - // - inner - .lock() - .unwrap() - .set_shell(Some(if let Ok(layer) = manager.instantiate_exact(1) { - layer - } else { - panic!("server didn't advertise `zwlr_layer_shell_v1`"); - })); - - event_queue.sync_roundtrip(&mut (), |_, _, _| {}).unwrap(); - - // - // Cursor processing - // - let pointer_clone = cmd_queue.clone(); - let pointer = seat.get_pointer(); - let mut pos: (u32, u32) = (0, 0); - let mut vert_scroll: f64 = 0.0; - let mut horiz_scroll: f64 = 0.0; - let mut btn: u32 = 0; - let mut btn_clicked = false; - pointer.quick_assign(move |_, event, _| match event { - wl_pointer::Event::Enter { - surface_x, - surface_y, - .. - } => { - pos = (surface_x as u32, surface_y as u32); - } - wl_pointer::Event::Leave { .. } => { - pos = (0, 0); - } - wl_pointer::Event::Motion { - surface_x, - surface_y, - .. - } => { - pos = (surface_x as u32 * scale, surface_y as u32 * scale); - } - wl_pointer::Event::Axis { axis, value, .. } => { - if axis == wl_pointer::Axis::VerticalScroll { - vert_scroll += value; - } - } - wl_pointer::Event::Button { button, state, .. } => { - if let wl_pointer::ButtonState::Released = state { - btn = button; - btn_clicked = true; - } - } - wl_pointer::Event::Frame => { - if vert_scroll != 0.0 || horiz_scroll != 0.0 { - pointer_clone.lock().unwrap().push_back(Cmd::MouseScroll { - scroll: (horiz_scroll, vert_scroll), - pos, - }); - vert_scroll = 0.0; - horiz_scroll = 0.0; - } - if btn_clicked { - pointer_clone - .lock() - .unwrap() - .push_back(Cmd::MouseClick { btn, pos }); - btn_clicked = false; - } - } - _ => {} - }); - - display.flush().unwrap(); - - App { - display, - event_queue, - cmd_queue, - pools, - widget: None, - bg, - inner, - last_damage: None, - last_dim: (0, 0), - keyboard, - } - } -} diff --git a/src/buffer.rs b/src/buffer.rs index e5a56e7..f48077c 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,22 +1,205 @@ +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 crate::color::Color; +use wayland_client::{ + protocol::{wl_buffer, wl_shm, wl_shm_pool}, + Dispatch, QueueHandle, +}; -pub struct Buffer<'a> { - buf: &'a mut MmapMut, +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, + pub id: u32, + pub last_damage: Vec, +} + +impl ShmBuffer { + 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, + { + 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, + id, + last_damage: Vec::new(), + }) + } + + pub fn acquire(&mut self) { + self.refcnt += 1; + } + + pub fn release(&mut self) { + self.refcnt -= 1; + } +} + +pub struct BufferManager { + pub buffers: Vec, + pub next_id: u32, +} + +impl BufferManager { + pub fn new() -> BufferManager { + BufferManager { + buffers: Vec::new(), + next_id: 0, + } + } + + 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, + 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); + } +} + +pub struct BufferView<'a> { + view: &'a mut [u32], dimensions: (u32, u32), subdimensions: Option<(u32, u32, u32, u32)>, } -impl<'a> Buffer<'a> { - pub fn new(buf: &'a mut MmapMut, dimensions: (u32, u32)) -> Buffer { - Buffer { - buf, +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 { + view, dimensions, subdimensions: None, } } + #[inline] pub fn get_bounds(&self) -> (u32, u32, u32, u32) { if let Some(subdim) = self.subdimensions { subdim @@ -25,68 +208,20 @@ impl<'a> Buffer<'a> { } } - pub fn get_signed_bounds(&self) -> (i32, i32, i32, i32) { - if let Some(subdim) = self.subdimensions { - ( - subdim.0 as i32, - subdim.1 as i32, - subdim.2 as i32, - subdim.3 as i32, - ) - } else { - (0, 0, self.dimensions.0 as i32, self.dimensions.1 as i32) - } - } - - pub fn copy_to(&self, other: &mut Buffer, (x, y, width, height): (i32, i32, i32, i32)) { - debug_assert!(self.dimensions == other.dimensions); - debug_assert!(self.subdimensions.is_none() && other.subdimensions.is_none()); - - if x == 0 && width as u32 == self.dimensions.0 { - // Full-width copy - let offset = y as isize * self.dimensions.0 as isize; - let n = height as usize * self.dimensions.0 as usize; - unsafe { - std::ptr::copy( - (self.buf.as_ptr() as *const u32).offset(offset), - (other.buf.as_mut_ptr() as *mut u32).offset(offset), - n, - ); - } - } else { - // Row-by-row copy - for cur_y in y as isize..(y + height) as isize { - let offset = x as isize + cur_y as isize * self.dimensions.0 as isize; - unsafe { - std::ptr::copy( - (self.buf.as_ptr() as *const u32).offset(offset), - (other.buf.as_mut_ptr() as *mut u32).offset(offset), - width as usize, - ); - } - } - } - } - - pub fn subdimensions( - &mut self, - subdimensions: (u32, u32, u32, u32), - ) -> Result { + pub fn subdimensions(&mut self, subdimensions: (u32, u32, u32, u32)) -> 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(Buffer { - buf: self.buf, + BufferView { + view: self.view, dimensions: self.dimensions, subdimensions: Some(( subdimensions.0 + bounds.0, @@ -94,23 +229,24 @@ impl<'a> Buffer<'a> { subdimensions.2, subdimensions.3, )), - }) + } } - pub fn offset(&mut self, offset: (u32, u32)) -> 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)) -> 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(Buffer { - buf: self.buf, + BufferView { + view: self.view, dimensions: self.dimensions, subdimensions: Some(( offset.0 + bounds.0, @@ -118,63 +254,42 @@ impl<'a> Buffer<'a> { bounds.2 - offset.0, bounds.3 - offset.1, )), - }) - } - - 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)) = - c.as_argb8888(); - } - } - } - } 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)) = c.as_argb8888(); - } - } } } - 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 - ), - )); + pub fn memset(&mut self, c: Color) { + 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(c.0); } - (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 - ), - )); + 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); } - pos - }; + } else { + self.view.fill(0x0); + } + } - 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(); - }; + #[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 + } - Ok(()) + #[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/cmd.rs b/src/cmd.rs deleted file mode 100644 index c2fdc10..0000000 --- a/src/cmd.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::keyboard::{KeyState, ModifiersState}; - -#[derive(Clone)] -pub enum Cmd { - Exit, - Draw, - ForceDraw, - ToggleVisible, - MouseClick { - btn: u32, - pos: (u32, u32), - }, - MouseScroll { - scroll: (f64, f64), - pos: (u32, u32), - }, - KeyboardTest, - Keyboard { - key: u32, - key_state: KeyState, - modifiers_state: ModifiersState, - interpreted: Option, - }, -} diff --git a/src/color.rs b/src/color.rs index c8563ac..5694ae8 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,71 +1,42 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)] -pub struct Color { - red: f32, - green: f32, - blue: f32, - opacity: f32, -} +#[derive(Copy, Clone)] +pub struct Color(pub u32); 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 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); + + #[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 + } + + 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::(), + ) } } - - 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/config.rs b/src/config.rs index 59d4ac5..62a6115 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,29 +1,39 @@ -use crate::cmd::Cmd; -use crate::color::Color; -use crate::widget; -use crate::{ - fonts::{FontMap, FontRef}, - widgets, -}; -use chrono::NaiveDateTime; -use serde::{Deserialize, Serialize}; +use serde::{self, Deserialize, Serialize}; + +use std::collections::HashMap; use std::default::Default; -use std::{collections::HashMap, sync::mpsc::Sender}; +use std::sync::{Arc, Mutex}; + +use crate::{ + event::Events, + fonts::{find_font, FontMap}, + widgets::{ + Backlight, Battery, Calendar, Clock, Date, HorizontalLayout, IndexedLayout, Interface, + InvertedHorizontalLayout, InvertedVerticalLayout, Layout, Line, Margin, PulseAudio, + VerticalLayout, Widget as RealWidget, + }, +}; #[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), widget: Box, }, - Fixed { - width: u32, - height: u32, - widget: Box, - }, HorizontalLayout(Vec), + InvertedHorizontalLayout(Vec), VerticalLayout(Vec), + InvertedVerticalLayout(Vec), + HorizontalLine(u32), + VerticalLine(u32), Clock { font: Option, font_size: f32, @@ -36,315 +46,342 @@ pub enum Widget { font_primary: Option, font_secondary: Option, font_size: f32, - sections: u32, + sections_x: i32, + sections_y: i32, }, Launcher { font: Option, font_size: f32, - length: u32, - #[serde(default)] - app_opener: String, - #[serde(default)] - term_opener: String, - #[serde(default)] - url_opener: String, + launch_cmd: Option, }, Battery { font: Option, font_size: f32, - length: u32, }, Backlight { - #[serde(default)] - device: String, + device: Option, font: Option, font_size: f32, - length: u32, }, - #[cfg(feature = "pulseaudio-widget")] PulseAudio { font: Option, font_size: f32, - length: u32, - }, - #[cfg(feature = "alsa-widget")] - AlsaSound { - font: Option, - font_size: f32, - length: u32, }, } +impl Default for OperationMode { + fn default() -> OperationMode { + OperationMode::LayerSurface((1024, 768)) + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "snake_case")] +pub struct Config { + pub font_paths: Option>, + pub widget: Widget, + pub mode: OperationMode, + pub background: Option, + pub server: Option, +} + +impl Config { + pub fn generate_v1() -> Config { + let has_battery = Battery::detect(); + let has_backlight = Backlight::detect(); + let sans = find_font("sans"); + let monospace = find_font("monospace"); + + 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: None, + 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), + 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(bars)), + }, + ]), + Widget::Calendar { + font_primary: None, + font_secondary: None, + font_size: 36., + sections_x: 3, + sections_y: 1, + }, + Widget::Launcher { + font: None, + font_size: 32., + launch_cmd: None, + }, + ])), + }; + + let map = HashMap::from([ + ("sans".to_string(), sans), + ("monospace".to_string(), monospace), + ]); + + Config { + font_paths: Some(map), + widget: widget, + mode: OperationMode::LayerSurface((1024, 768)), + background: Some(0xFAFAFAFA), + server: None, + } + } + + pub fn generate_v2(clay: bool) -> Config { + let has_battery = Battery::detect(); + let has_backlight = Backlight::detect(); + 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: None, + 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::Margin { + margins: (8, 0, 0, 0), + widget: Box::new(Widget::Clock { + font: None, + font_size: 128., + }), + }, + Widget::Margin { + margins: (16, 64, 0, 0), + widget: Box::new(Widget::Date { + font: None, + font_size: 48., + }), + }, + Widget::VerticalLayout(bars), + ]), + 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: if clay { -1 } else { 3 }, + }), + }, + Widget::VerticalLine(1), + Widget::Margin { + margins: (8, 0, 0, 0), + widget: Box::new(Widget::Launcher { + font: None, + font_size: 32., + launch_cmd: None, + }), + }, + ]), + ]); + + 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, 1)) + }, + background: Some(0xFAFAFAFA), + server: None, + } + } +} + +fn leak_or_default(v: Option, d: &'static str) -> &'static str { + match v { + Some(v) => Box::leak(v.into_boxed_str()), + None => d, + } +} + +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<'a>( + pub fn construct_widgets( self, - time: NaiveDateTime, - tx: Sender, - fonts: &'a FontMap, - ) -> Option> { + v: &mut Vec>, + mut fm: &mut FontMap, + events: &Arc>, + ) { match self { - Widget::Margin { margins, widget } => match widget.construct(time, tx, fonts) { - Some(w) => Some(widget::Margin::new(margins, w)), - None => None, - }, - Widget::Fixed { - width, - height, - widget, - } => match widget.construct(time, tx, fonts) { - Some(w) => Some(widget::Fixed::new((width, height), w)), - None => None, - }, - Widget::HorizontalLayout(widgets) => Some(widget::HorizontalLayout::new( - widgets - .into_iter() - .map(|x| x.construct(time, tx.clone(), fonts)) - .filter(|x| x.is_some()) - .map(|x| x.unwrap()) - .collect(), - )), - Widget::VerticalLayout(widgets) => Some(widget::VerticalLayout::new( - widgets - .into_iter() - .map(|x| x.construct(time, tx.clone(), fonts)) - .filter(|x| x.is_some()) - .map(|x| x.unwrap()) - .collect(), - )), - Widget::Clock { font, font_size } => match widgets::clock::Clock::new( - time, - get_font(&font.or_else(|| Some("sans".to_string())).unwrap(), &fonts), + 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::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( + &mut fm, + leak_or_default(font, "sans"), font_size, - ) { - Ok(w) => Some(w), - Err(_) => None, - }, - Widget::Date { font, font_size } => match widgets::date::Date::new( - time, - get_font(&font.or_else(|| Some("sans".to_string())).unwrap(), &fonts), + ))), + Widget::Date { font, font_size } => v.push(Box::new(Date::new( + &mut fm, + leak_or_default(font, "sans"), font_size, - ) { - Ok(w) => Some(w), - Err(_) => None, - }, + ))), Widget::Calendar { font_primary, font_secondary, font_size, - sections, - } => Some(widgets::calendar::Calendar::new( - time, - get_font( - &font_primary.or_else(|| Some("sans".to_string())).unwrap(), - &fonts, - ), - get_font( - &font_secondary.or_else(|| Some("mono".to_string())).unwrap(), - &fonts, - ), + 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, - )), + sections_x, + sections_y, + ))), Widget::Launcher { font, font_size, - length, - app_opener, - term_opener, - url_opener, - } => Some(widgets::launcher::Launcher::new( - get_font(&font.or_else(|| Some("sans".to_string())).unwrap(), &fonts), + launch_cmd, + } => v.push(Box::new(Interface::new( + events.clone(), + &mut fm, + leak_or_default(font, "sans"), font_size, - length, - tx, - app_opener, - term_opener, - if url_opener.is_empty() { - "xdg_open ".to_string() - } else { - url_opener - }, - )), - Widget::Battery { - font, + launch_cmd, + ))), + Widget::Battery { font, font_size } => v.push(Box::new(Battery::new( + events.clone(), + &mut fm, + leak_or_default(font, "sans"), font_size, - length, - } => { - match widgets::battery::UpowerBattery::new( - get_font(&font.or_else(|| Some("sans".to_string())).unwrap(), &fonts), - font_size, - length, - tx, - ) { - Ok(w) => Some(w), - Err(_) => None, - } - } + ))), Widget::Backlight { device, font, font_size, - length, - } => { - let d = if device == "" { - "intel_backlight" - } else { - &device - }; - match widgets::backlight::Backlight::new( - d, - get_font(&font.or_else(|| Some("sans".to_string())).unwrap(), &fonts), - font_size, - length, - ) { - Ok(w) => Some(w), - Err(_) => None, - } - } - #[cfg(feature = "pulseaudio-widget")] - Widget::PulseAudio { - font, + } => v.push(Box::new(Backlight::new( + leak_or_none(device), + &mut fm, + leak_or_default(font, "sans"), font_size, - length, - } => { - match widgets::audio::PulseAudio::new( - get_font(&font.or_else(|| Some("sans".to_string())).unwrap(), &fonts), - font_size, - length, - tx, - ) { - Ok(w) => Some(w), - Err(_) => None, - } - } - #[cfg(feature = "alsa-widget")] - Widget::AlsaSound { - font, + ))), + 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, - length, - } => { - match widgets::audio::Alsa::new( - get_font(&font.or_else(|| Some("sans".to_string())).unwrap(), &fonts), - font_size, - length, - ) { - Ok(w) => Some(w), - Err(_) => None, - } + ))), + } + } + + 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()) + } + Widget::InvertedVerticalLayout(widgets) => InvertedVerticalLayout::new( + widgets.iter().map(|w| w.construct_layout(idx)).collect(), + ), + _ => IndexedLayout::new({ + let i = *idx; + *idx += 1; + i + }), } } } - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub enum OutputMode { - All, - Active, -} - -impl Default for OutputMode { - fn default() -> Self { - OutputMode::Active - } -} - -fn default_fonts() -> HashMap { - let mut map = HashMap::with_capacity(2); - map.insert("mono".to_string(), "mono".to_string()); - map.insert("sans".to_string(), "sans".to_string()); - map -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct Config { - pub output_mode: OutputMode, - pub scale: u32, - pub background: Color, - pub widget: Widget, - - #[serde(default = "default_fonts")] - pub fonts: HashMap, -} - -impl Default for Config { - fn default() -> Self { - Config { - widget: Widget::Margin { - margins: (20, 20, 20, 20), - widget: Box::new(Widget::VerticalLayout(vec![ - Widget::HorizontalLayout(vec![ - Widget::Margin { - margins: (0, 88, 0, 32), - widget: Box::new(Widget::VerticalLayout(vec![ - Widget::Date { - font: None, - font_size: 64.0, - }, - Widget::Clock { - font: None, - font_size: 256.0, - }, - ])), - }, - Widget::VerticalLayout(vec![ - Widget::Margin { - margins: (0, 0, 0, 8), - widget: Box::new(Widget::Battery { - font: None, - font_size: 24.0, - length: 0, - }), - }, - Widget::Margin { - margins: (0, 0, 0, 8), - widget: Box::new(Widget::Backlight { - device: "intel_backlight".to_string(), - font: None, - font_size: 24.0, - length: 0, - }), - }, - #[cfg(feature = "pulseaudio-widget")] - Widget::Margin { - margins: (0, 0, 0, 8), - widget: Box::new(Widget::PulseAudio { - font: None, - font_size: 24.0, - length: 0, - }), - }, - ]), - ]), - Widget::Calendar { - font_primary: None, - font_secondary: None, - font_size: 16.0, - sections: 3, - }, - Widget::Launcher { - font: None, - font_size: 32.0, - length: 0, - app_opener: "".to_string(), - term_opener: "".to_string(), - url_opener: "".to_string(), - }, - ])), - }, - output_mode: Default::default(), - scale: 1, - background: Color::new(0.0, 0.0, 0.0, 0.9), - fonts: default_fonts(), - } - } -} - -#[inline] -fn get_font<'a>(name: &str, map: &'a FontMap) -> FontRef<'a> { - match map.get(name) { - Some(f) => f, - None => panic!("Font {} is missing from the config", name), - } -} diff --git a/src/configfmt.rs b/src/configfmt.rs deleted file mode 100644 index 8c3d280..0000000 --- a/src/configfmt.rs +++ /dev/null @@ -1,61 +0,0 @@ -use serde::{de::DeserializeOwned, Serialize}; -use std::io::BufRead; - -pub enum ConfigFmt { - #[cfg(feature = "yaml-cfg")] - Yaml, - #[cfg(feature = "json-cfg")] - Json, -} - -pub const CONFIG_NAMES: &[&str] = &[ - #[cfg(feature = "yaml-cfg")] - "config.yaml", - #[cfg(feature = "json-cfg")] - "config.json", -]; - -impl ConfigFmt { - #[inline] - pub fn new(ext: &str) -> Option { - match ext { - #[cfg(feature = "yaml-cfg")] - "yaml" => Some(ConfigFmt::Yaml), - #[cfg(feature = "json-cfg")] - "json" => Some(ConfigFmt::Json), - _ => None, - } - } - #[inline] - pub fn from_reader(&self, r: B) -> T { - match self { - #[cfg(feature = "yaml-cfg")] - ConfigFmt::Yaml => serde_yaml::from_reader(r).unwrap(), - #[cfg(feature = "json-cfg")] - ConfigFmt::Json => serde_json::from_reader(r).unwrap(), - } - } - #[inline] - pub fn to_string(&self, src: &T) -> String { - match self { - #[cfg(feature = "yaml-cfg")] - ConfigFmt::Yaml => serde_yaml::to_string(&src).unwrap(), - #[cfg(feature = "json-cfg")] - ConfigFmt::Json => serde_json::to_string_pretty(&src).unwrap(), - } - } -} - -impl Default for ConfigFmt { - fn default() -> Self { - // attributes require curly braces to work - #[cfg(feature = "yaml-cfg")] - { - ConfigFmt::Yaml - } - #[cfg(all(feature = "json-cfg", not(feature = "yaml-cfg")))] - { - ConfigFmt::Json - } - } -} diff --git a/src/data.rs b/src/data.rs deleted file mode 100644 index 8992b2f..0000000 --- a/src/data.rs +++ /dev/null @@ -1,57 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::env; -use std::error::Error; -use std::fs::{create_dir_all, File, OpenOptions}; -use std::path::PathBuf; - -#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Default)] -pub struct Data { - pub entries: HashMap, -} - -impl Data { - pub fn load() -> Result> { - Ok(serde_yaml::from_reader(Self::read_file()?)?) - } - - pub fn save(&self) -> Result<(), Box> { - Ok(serde_yaml::to_writer(Self::write_file()?, self)?) - } - - fn read_file() -> Result> { - Ok(OpenOptions::new() - .create(true) - .write(true) - .read(true) - .open(Self::path()?)?) - } - - fn write_file() -> Result> { - Ok(OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(Self::path()?)?) - } - - fn path() -> Result> { - let xdg_cache = match env::var_os("XDG_CACHE_HOME") { - Some(s) => s - .into_string() - .map_err(|_| "Unable to resolve $XDG_CACHE_HOME")?, - None => format!( - "{}/.cache", - env::var_os("HOME") - .ok_or("Unable to resolve $HOME")? - .into_string() - .map_err(|_| "Unable to resolve $HOME")? - ), - }; - - let cache_dir = PathBuf::from(xdg_cache).join("wldash"); - create_dir_all(&cache_dir)?; - - Ok(cache_dir.join("data.yaml")) - } -} diff --git a/src/desktop.rs b/src/desktop.rs deleted file mode 100644 index af2c36e..0000000 --- a/src/desktop.rs +++ /dev/null @@ -1,113 +0,0 @@ -extern crate ini; -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/doublemempool.rs b/src/doublemempool.rs deleted file mode 100644 index 20c0064..0000000 --- a/src/doublemempool.rs +++ /dev/null @@ -1,261 +0,0 @@ -use std::{ - cell::RefCell, - ffi::CStr, - fs::File, - io, - os::unix::io::{FromRawFd, RawFd}, - rc::Rc, - 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}, - Main, -}; - -/// A wrapper handling an SHM memory pool backed by a shared memory file -/// -/// This wrapper handles for you the creation of the shared memory file and its synchronization -/// with the protocol. -/// -/// Mempool internally tracks the release of the buffers by the compositor. As such, creating a buffer -/// that is not commited to a surface (and then never released by the server) would cause the Mempool -/// to be stuck believing it is still in use. -/// -/// Mempool will also handle the destruction of buffers and as such the `destroy()` method should not -/// be used on buffers created from Mempool. -/// -/// Overwriting the contents of the memory pool before it is completely freed may cause graphical -/// glitches due to the possible corruption of data while the compositor is reading it. -/// -/// Mempool requires a callback that will be called when the pool becomes free, this -/// happens when all the pools buffers are released by the server. -pub struct MemPool { - file: File, - len: usize, - pool: Main, - buffer_count: Rc>, - mmap: MmapMut, - callback: Rc>, -} - -impl MemPool { - /// Create a new memory pool associated with given shm - pub fn new(shm: Main, callback: F) -> io::Result - where - F: FnMut(wayland_client::DispatchData) + 'static, - { - let mem_fd = create_shm_fd()?; - let mem_file = unsafe { File::from_raw_fd(mem_fd) }; - mem_file.set_len(128)?; - - let pool = shm.create_pool(mem_fd, 128); - - let mmap = unsafe { MmapMut::map_mut(&mem_file).unwrap() }; - - Ok(MemPool { - file: mem_file, - len: 128, - pool, - buffer_count: Rc::new(RefCell::new(0)), - mmap, - callback: Rc::new(RefCell::new(callback)), - }) - } - - /// Resize the memory pool - /// - /// This affect the size as it is seen by the wayland server. Even - /// if you extend the temporary file size by writing to it, you need to - /// call this method otherwise the server won't see the new size. - /// - /// Memory pools can only be extented, as such this method will do nothing - /// if the requested new size is smaller than the current size. - /// - /// This method allows you to ensure the underlying pool is large enough to - /// hold what you want to write to it. - pub fn resize(&mut self, newsize: usize) -> io::Result<()> { - if newsize > self.len { - self.file.set_len(newsize as u64)?; - self.pool.resize(newsize as i32); - self.len = newsize; - self.mmap = unsafe { MmapMut::map_mut(&self.file).unwrap() }; - } - Ok(()) - } - - /// Create a new buffer to this pool - /// - /// The parameters are: - /// - /// - `offset`: the offset (in bytes) from the beginning of the pool at which this - /// buffer starts - /// - `width`: the width of this buffer (in pixels) - /// - `height`: the height of this buffer (in pixels) - /// - `stride`: distance (in bytes) between the beginning of a row and the next one - /// - `format`: the encoding format of the pixels. Using a format that was not - /// advertised to the `wl_shm` global by the server is a protocol error and will - /// terminate your connection - pub fn buffer( - &self, - offset: i32, - width: i32, - height: i32, - stride: i32, - format: wl_shm::Format, - ) -> wl_buffer::WlBuffer { - *self.buffer_count.borrow_mut() += 1; - let my_buffer_count = self.buffer_count.clone(); - let my_callback = self.callback.clone(); - let buffer = self - .pool - .create_buffer(offset, width, height, stride, format); - buffer.quick_assign(move |buffer, event, dispatch_data| match event { - wl_buffer::Event::Release => { - buffer.destroy(); - let new_count = { - // borrow the buffer_count for as short as possible, in case - // the user wants to create a new buffer from the callback - let mut my_buffer_count = my_buffer_count.borrow_mut(); - *my_buffer_count -= 1; - *my_buffer_count - }; - if new_count == 0 { - (&mut *my_callback.borrow_mut())(dispatch_data); - } - } - _ => unreachable!(), - }); - (*buffer).clone().detach() - } - - /// Uses the memmap crate to map the underlying shared memory file - pub fn mmap(&mut self) -> &mut MmapMut { - &mut self.mmap - } - - /// Returns true if the pool contains buffers that are currently in use by the server - pub fn is_used(&self) -> bool { - *self.buffer_count.borrow() != 0 - } -} - -impl Drop for MemPool { - fn drop(&mut self) { - self.pool.destroy(); - } -} - -impl io::Write for MemPool { - fn write(&mut self, buf: &[u8]) -> io::Result { - io::Write::write(&mut self.file, buf) - } - fn flush(&mut self) -> io::Result<()> { - io::Write::flush(&mut self.file) - } -} - -impl io::Seek for MemPool { - fn seek(&mut self, pos: io::SeekFrom) -> io::Result { - io::Seek::seek(&mut self.file, pos) - } -} - -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"smithay-client-toolkit\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!( - "/smithay-client-toolkit-{}", - 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!( - "/smithay-client-toolkit-{}", - sys_time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos() - ); - continue; - } - Err(Errno::EINTR) => continue, - Err(errno) => return Err(io::Error::from(errno)), - } - } -} - -pub struct DoubleMemPool { - pool1: MemPool, - pool2: MemPool, - switch: bool, -} - -impl DoubleMemPool { - pub fn new(shm: Main) -> ::std::io::Result { - Ok(DoubleMemPool { - pool1: MemPool::new(shm.clone(), move |_| {})?, - pool2: MemPool::new(shm.clone(), move |_| {})?, - switch: false, - }) - } - - pub fn pool(&mut self) -> Option<(&mut MemPool, &mut MemPool)> { - let switch = self.switch; - self.switch = !self.switch; - let (last, cur) = if !switch { - (&mut self.pool2, &mut self.pool1) - } else { - (&mut self.pool1, &mut self.pool2) - }; - - if cur.is_used() { - None - } else { - Some((last, cur)) - } - } - - pub fn never_mind(&mut self) { - self.switch = !self.switch; - } -} diff --git a/src/draw.rs b/src/draw.rs index 08afd54..fdc6812 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -1,4 +1,4 @@ -use crate::buffer::Buffer; +use crate::buffer::BufferView; use crate::{color::Color, fonts::FontRef}; use rusttype::{point, Scale}; @@ -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, @@ -47,19 +46,26 @@ impl CachedGlyph { } } - fn draw(&self, buf: &mut Buffer, pos: (i32, i32), bg: &Color, c: &Color) { + 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; 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, ), - &bg.blend(&c, *v), + c.alpha(*v), ); - if x == self.dimensions.0 as i32 - 1 { + if x == width { y += 1; x = 0; } else { @@ -84,6 +90,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() { @@ -95,9 +107,8 @@ impl<'a> Font<'a> { pub fn draw_text( &self, - buf: &mut Buffer, - bg: &Color, - c: &Color, + buf: &mut BufferView, + c: Color, s: &str, ) -> Result<(u32, u32), ::std::io::Error> { let mut x_off = 0; @@ -119,7 +130,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; } @@ -128,9 +139,8 @@ impl<'a> Font<'a> { pub fn draw_text_with_cursor( &self, - buf: &mut Buffer, - bg: &Color, - c: &Color, + buf: &mut BufferView, + c: Color, s: &str, cursor: usize, ) -> Result<(u32, u32), ::std::io::Error> { @@ -153,12 +163,12 @@ 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)?; } - glyph.draw(buf, (x_off, -off), bg, c); + glyph.draw(buf, (x_off, -off), c); x_off += glyph.advance; } if cursor == glyphs.len() { @@ -168,16 +178,20 @@ impl<'a> Font<'a> { Ok((x_off as u32, self.size as u32)) } - pub fn draw_cursor( + fn draw_cursor( &self, - buf: &mut Buffer, - c: &Color, + 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)? + 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); } Ok(()) } @@ -205,32 +219,29 @@ impl<'a> Font<'a> { pub fn auto_draw_text( &mut self, - buf: &mut Buffer, - bg: &Color, - c: &Color, + buf: &mut BufferView, + 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 Buffer, - bg: &Color, - c: &Color, + buf: &mut BufferView, + 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 Buffer, - bg: &Color, - c: &Color, + buf: &mut BufferView, + c: Color, distances: &[u32], s: &str, ) -> Result<(u32, u32), ::std::io::Error> { @@ -253,7 +264,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; } @@ -262,8 +273,7 @@ impl<'a> Font<'a> { pub fn draw_text_individual_colors( &self, - buf: &mut Buffer, - bg: &Color, + buf: &mut BufferView, color: &[Color], s: &str, ) -> Result<(u32, u32), ::std::io::Error> { @@ -286,7 +296,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; } @@ -295,32 +305,30 @@ impl<'a> Font<'a> { pub fn auto_draw_text_individual_colors( &mut self, - buf: &mut Buffer, - bg: &Color, + buf: &mut BufferView, 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) } } -pub fn draw_box(buf: &mut Buffer, 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)?; +pub fn draw_box(buf: &mut BufferView, c: Color, dim: (u32, u32)) -> Result<(), ::std::io::Error> { + 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(()) } pub fn draw_bar( - buf: &mut Buffer, - color: &Color, + buf: &mut BufferView, + color: Color, length: u32, height: u32, fill: f32, @@ -329,10 +337,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 new file mode 100644 index 0000000..b9b2b9d --- /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), + FocusLost, +} + +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 a349dd5..831cfc8 100644 --- a/src/fonts.rs +++ b/src/fonts.rs @@ -1,30 +1,29 @@ //! Utility module for fonts -use fontconfig::Fontconfig as FontConfig; +use crate::draw; use rusttype::Font; use std::{ - collections::HashMap, - fs::File, - io::Read, - path::{Path, PathBuf}, + cell::RefCell, collections::HashMap, fs::File, hash, io::Read, mem, path::Path, rc::Rc, thread, }; -/// FontMap is used to store different font configurations -pub type FontMap<'a> = HashMap>; - /// 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; +#[cfg(feature = "fontconfig")] +pub(crate) fn find_font(name: &str) -> String { + use fontconfig::Fontconfig as FontConfig; + let fc = FontConfig::new().unwrap(); + fc.find(name, None) + .unwrap() + .path + .to_str() + .unwrap() + .to_string() +} -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 - } +#[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 @@ -45,3 +44,116 @@ impl FontLoader { 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>>, + 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(&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, preload)), + None => { + self.required_fonts + .insert(font_name, (font_name, vec![(size, preload)])); + } + } + } + + 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 = match self.font_paths.get(font_name) { + Some(res) => res, + _ => { + let s = find_font(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, 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); + } + } + } + } + + 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") + } +} + +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/keyboard/ffi.rs b/src/keyboard/ffi.rs deleted file mode 100644 index 1dfc902..0000000 --- a/src/keyboard/ffi.rs +++ /dev/null @@ -1,269 +0,0 @@ -#![allow(dead_code, non_camel_case_types, clippy::identity_op)] -#![cfg_attr(rustfmt, rustfmt_skip)] - -use std::os::raw::{c_char, c_int, c_void, c_uint}; - -pub const XKB_MOD_NAME_SHIFT : &[u8] = b"Shift\0"; -pub const XKB_MOD_NAME_CAPS : &[u8] = b"Lock\0"; -pub const XKB_MOD_NAME_CTRL : &[u8] = b"Control\0"; -pub const XKB_MOD_NAME_ALT : &[u8] = b"Mod1\0"; -pub const XKB_MOD_NAME_NUM : &[u8] = b"Mod2\0"; -pub const XKB_MOD_NAME_LOGO : &[u8] = b"Mod4\0"; - -pub const XKB_LED_NAME_CAPS : &[u8] = b"Caps Lock\0"; -pub const XKB_LED_NAME_NUM : &[u8] = b"Num Lock\0"; -pub const XKB_LED_NAME_SCROLL : &[u8] = b"Scroll Lock\0"; - -pub struct xkb_context; -pub struct xkb_keymap; -pub struct xkb_state; -pub struct xkb_compose_table; -pub struct xkb_compose_state; - -pub type xkb_keycode_t = u32; -pub type xkb_keysym_t = u32; -pub type xkb_layout_index_t = u32; -pub type xkb_layout_mask_t = u32; -pub type xkb_level_index_t = u32; -pub type xkb_mod_index_t = u32; -pub type xkb_mod_mask_t = u32; -pub type xkb_led_index_t = u32; -pub type xkb_led_mask_t = u32; - -pub const XKB_KEYCODE_INVALID :u32 = 0xffff_ffff; -pub const XKB_LAYOUT_INVALID :u32 = 0xffff_ffff; -pub const XKB_LEVEL_INVALID :u32 = 0xffff_ffff; -pub const XKB_MOD_INVALID :u32 = 0xffff_ffff; -pub const XKB_LED_INVALID :u32 = 0xffff_ffff; -pub const XKB_KEYCODE_MAX :u32 = 0xffff_fffe; - -#[repr(C)] -#[derive(Copy, Clone)] -pub struct xkb_rule_names { - pub rules: *const c_char, - pub model: *const c_char , - pub layout: *const c_char, - pub variant: *const c_char, - pub options: *const c_char, -} - -#[repr(C)] -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum xkb_keysym_flags { - /** Do not apply any flags. */ - XKB_KEYSYM_NO_FLAGS = 0, - /** Find keysym by case-insensitive search. */ - XKB_KEYSYM_CASE_INSENSITIVE = (1 << 0) -} - -#[repr(C)] -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum xkb_context_flags { - /** Do not apply any context flags. */ - XKB_CONTEXT_NO_FLAGS = 0, - /** Create this context with an empty include path. */ - XKB_CONTEXT_NO_DEFAULT_INCLUDES = (1 << 0), - /** - * Don't take RMLVO names from the environment. - * @since 0.3.0 - */ - XKB_CONTEXT_NO_ENVIRONMENT_NAMES = (1 << 1) -} - -#[repr(C)] -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum xkb_log_level { - /** Log critical internal errors only. */ - XKB_LOG_LEVEL_CRITICAL = 10, - /** Log all errors. */ - XKB_LOG_LEVEL_ERROR = 20, - /** Log warnings and errors. */ - XKB_LOG_LEVEL_WARNING = 30, - /** Log information, warnings, and errors. */ - XKB_LOG_LEVEL_INFO = 40, - /** Log everything. */ - XKB_LOG_LEVEL_DEBUG = 50 -} - -#[repr(C)] -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum xkb_keymap_compile_flags { - /** Do not apply any flags. */ - XKB_KEYMAP_COMPILE_NO_FLAGS = 0, -} - -#[repr(C)] -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum xkb_keymap_format { - /** Cannot be used for creation */ - XKB_KEYMAP_USE_ORIGINAL_FORMAT = 0, - /** The current/classic XKB text format, as generated by xkbcomp -xkb. */ - XKB_KEYMAP_FORMAT_TEXT_V1 = 1, -} - -#[repr(C)] -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum xkb_key_direction { - /** The key was released. */ - XKB_KEY_UP, - /** The key was pressed. */ - XKB_KEY_DOWN -} - -#[repr(C)] -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum xkb_compose_compile_flags { - XKB_COMPOSE_COMPILE_NO_FLAGS = 0 -} - -#[repr(C)] -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum xkb_compose_format { - XKB_COMPOSE_FORMAT_TEXT_V1 = 1 -} - -#[repr(C)] -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum xkb_compose_state_flags { - XKB_COMPOSE_STATE_NO_FLAGS = 0 -} - -#[repr(C)] -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum xkb_compose_status { - XKB_COMPOSE_NOTHING, - XKB_COMPOSE_COMPOSING, - XKB_COMPOSE_COMPOSED, - XKB_COMPOSE_CANCELLED -} - -#[repr(C)] -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum xkb_compose_feed_result { - XKB_COMPOSE_FEED_IGNORED, - XKB_COMPOSE_FEED_ACCEPTED -} - -bitflags::bitflags!( - pub struct xkb_state_component: u32 { - /** Depressed modifiers, i.e. a key is physically holding them. */ - const XKB_STATE_MODS_DEPRESSED = (1 << 0); - /** Latched modifiers, i.e. will be unset after the next non-modifier - * key press. */ - const XKB_STATE_MODS_LATCHED = (1 << 1); - /** Locked modifiers, i.e. will be unset after the key provoking the - * lock has been pressed again. */ - const XKB_STATE_MODS_LOCKED = (1 << 2); - /** Effective modifiers, i.e. currently active and affect key - * processing (derived from the other state components). - * Use this unless you explictly care how the state came about. */ - const XKB_STATE_MODS_EFFECTIVE = (1 << 3); - /** Depressed layout, i.e. a key is physically holding it. */ - const XKB_STATE_LAYOUT_DEPRESSED = (1 << 4); - /** Latched layout, i.e. will be unset after the next non-modifier - * key press. */ - const XKB_STATE_LAYOUT_LATCHED = (1 << 5); - /** Locked layout, i.e. will be unset after the key provoking the lock - * has been pressed again. */ - const XKB_STATE_LAYOUT_LOCKED = (1 << 6); - /** Effective layout, i.e. currently active and affects key processing - * (derived from the other state components). - * Use this unless you explictly care how the state came about. */ - const XKB_STATE_LAYOUT_EFFECTIVE = (1 << 7); - /** LEDs (derived from the other state components). */ - const XKB_STATE_LEDS = (1 << 8); - } -); - -dlopen_external_library!(XkbCommon, -functions: - fn xkb_keysym_get_name(xkb_keysym_t, *mut c_char, usize) -> c_int, - fn xkb_keysym_from_name(*const c_char, xkb_keysym_flags) -> xkb_keysym_t, - fn xkb_keysym_to_utf8(xkb_keysym_t, *mut c_char, usize) -> c_int, - fn xkb_keysym_to_utf32(xkb_keysym_t) -> u32, - fn xkb_context_new(xkb_context_flags) -> *mut xkb_context, - fn xkb_context_ref(*mut xkb_context) -> *mut xkb_context, - fn xkb_context_unref(*mut xkb_context) -> (), - fn xkb_context_set_user_data(*mut xkb_context, *mut c_void) -> (), - fn xkb_context_get_user_data(*mut xkb_context) -> *mut c_void, - fn xkb_context_include_path_append(*mut xkb_context, *const c_char) -> c_int, - fn xkb_context_include_path_append_default(*mut xkb_context) -> c_int, - fn xkb_context_include_path_reset_defaults(*mut xkb_context) -> c_int, - fn xkb_context_include_path_clear(*mut xkb_context) -> (), - fn xkb_context_num_include_paths(*mut xkb_context) -> c_uint, - fn xkb_context_include_path_get(*mut xkb_context, c_uint) -> *const c_char, - fn xkb_context_set_log_level(*mut xkb_context, xkb_log_level) -> (), - fn xkb_context_get_log_level(*mut xkb_context) -> xkb_log_level, - fn xkb_context_set_log_verbosity(*mut xkb_context, c_int) -> (), - fn xkb_context_get_log_verbosity(*mut xkb_context) -> c_int, - fn xkb_keymap_new_from_names(*mut xkb_context, - *const xkb_rule_names, - xkb_keymap_compile_flags - ) -> *mut xkb_keymap, - fn xkb_keymap_new_from_string(*mut xkb_context, - *const c_char, - xkb_keymap_format, - xkb_keymap_compile_flags - ) -> *mut xkb_keymap, - fn xkb_keymap_new_from_buffer(*mut xkb_context, - *const c_char, - usize, - xkb_keymap_format, - xkb_keymap_compile_flags - ) -> *mut xkb_keymap, - fn xkb_keymap_ref(*mut xkb_keymap) -> *mut xkb_keymap, - fn xkb_keymap_unref(*mut xkb_keymap) -> (), - fn xkb_keymap_get_as_string(*mut xkb_keymap, xkb_keymap_format) -> *const c_char, - fn xkb_keymap_key_repeats(*mut xkb_keymap, xkb_keycode_t) -> c_int, - - fn xkb_state_new(*mut xkb_keymap) -> *mut xkb_state, - fn xkb_state_ref(*mut xkb_state) -> *mut xkb_state, - fn xkb_state_unref(*mut xkb_state) -> (), - fn xkb_state_update_mask(*mut xkb_state, - xkb_mod_mask_t, - xkb_mod_mask_t, - xkb_mod_mask_t, - xkb_layout_index_t, - xkb_layout_index_t, - xkb_layout_index_t - ) -> xkb_state_component, - fn xkb_state_update_key(*mut xkb_state, - xkb_keycode_t, - xkb_key_direction - ) -> xkb_state_component, - fn xkb_state_key_get_syms(*mut xkb_state, - xkb_keycode_t, - *const *mut xkb_keysym_t - ) -> c_int, - fn xkb_state_key_get_utf8(*mut xkb_state, - xkb_keycode_t, - *mut c_char, - usize - ) -> c_int, - fn xkb_state_key_get_utf32(*mut xkb_state, xkb_keycode_t) -> u32, - fn xkb_state_key_get_one_sym(*mut xkb_state, xkb_keycode_t) -> xkb_keysym_t, - fn xkb_state_mod_name_is_active(*mut xkb_state, *const c_char, xkb_state_component) -> c_int, - fn xkb_compose_table_new_from_locale(*mut xkb_context, *const c_char, xkb_compose_compile_flags) -> *mut xkb_compose_table, - fn xkb_compose_table_unref(*mut xkb_compose_table) -> (), - fn xkb_compose_state_new(*mut xkb_compose_table, xkb_compose_state_flags) -> *mut xkb_compose_state, - fn xkb_compose_state_unref(*mut xkb_compose_state) -> (), - fn xkb_compose_state_feed(*mut xkb_compose_state, xkb_keysym_t) -> xkb_compose_feed_result, - fn xkb_compose_state_reset(*mut xkb_compose_state) -> (), - fn xkb_compose_state_get_status(*mut xkb_compose_state) -> xkb_compose_status, - fn xkb_compose_state_get_utf8(*mut xkb_compose_state, *mut c_char, usize) -> c_int, - fn xkb_compose_state_get_one_sym(*mut xkb_compose_state) -> xkb_keysym_t, -); - -lazy_static::lazy_static!( - pub static ref XKBCOMMON_OPTION: Option = { - unsafe { - XkbCommon::open("libxkbcommon.so.0") - .or_else(|_| XkbCommon::open("libxkbcommon.so")) - .ok() - } - }; - pub static ref XKBCOMMON_HANDLE: &'static XkbCommon = { - XKBCOMMON_OPTION.as_ref().expect("Library libxkbcommon.so could not be loaded.") - }; -); - diff --git a/src/keyboard/keysyms.rs b/src/keyboard/keysyms.rs deleted file mode 100644 index 088ec42..0000000 --- a/src/keyboard/keysyms.rs +++ /dev/null @@ -1,17 +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_Right: u32 = 0xff53; /* Move right, right 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 index 602f819..4ee4efa 100644 --- a/src/keyboard/mod.rs +++ b/src/keyboard/mod.rs @@ -12,333 +12,149 @@ //! calloop event loop. Not doing so will prevent key repetition to work //! (but the rest of the functionnality will not be affected). -use std::{cell::RefCell, os::unix::io::RawFd, rc::Rc}; - -use byteorder::{ByteOrder, NativeEndian}; - -pub use wayland_client::protocol::wl_keyboard::KeyState; -use wayland_client::{ - protocol::{wl_keyboard, wl_seat, wl_surface}, - Attached, +use std::{ + default::Default, + os::unix::io::{AsRawFd, OwnedFd}, }; -mod ffi; -pub mod keysyms; +use wayland_client::{protocol::wl_keyboard, WEnum}; +use xkbcommon::xkb; + +pub use xkbcommon::xkb::keysyms; + +mod repeat; mod state; +pub use self::repeat::{KeyRepeatSource, RepeatMessage}; use self::state::KbState; -pub use self::state::{ModifiersState, RMLVO}; +pub use self::state::ModifiersState; -#[derive(Debug)] -/// An error that occurred while trying to initialize a mapped keyboard -pub enum Error { - /// libxkbcommon is not available - XKBNotFound, - /// Provided RMLVO specified a keymap that would not be loaded - BadNames, +#[derive(Debug, Clone)] +pub struct KeyEvent { + pub rawkey: u32, + pub keysym: u32, + pub state: WEnum, + pub modifiers: ModifiersState, + pub utf8: Option, + pub repeats: bool, } -/// Events received from a mapped keyboard -pub enum Event<'a> { - /// The keyboard focus has entered a surface - Enter { - /// serial number of the event - serial: u32, - /// surface that was entered - surface: wl_surface::WlSurface, - /// raw values of the currently pressed keys - rawkeys: &'a [u32], - /// interpreted symbols of the currently pressed keys - keysyms: &'a [u32], - }, - /// The keyboard focus has left a surface - Leave { - /// serial number of the event - serial: u32, - /// surface that was left - surface: wl_surface::WlSurface, - }, - /// The key modifiers have changed state - Modifiers { - /// current state of the modifiers - modifiers: ModifiersState, - }, - /// A key event occurred - Key { - /// serial number of the event - serial: u32, - /// time at which the keypress occurred - time: u32, - /// raw value of the key - rawkey: u32, - /// interpreted symbol of the key - keysym: u32, - /// new state of the key - state: KeyState, - /// utf8 interpretation of the entered text - /// - /// will always be `None` on key release events - utf8: Option, - repeats: bool, - }, - RepeatInfo { - rate: i32, - delay: i32, - }, +pub struct KeymapDescription { + fd: OwnedFd, + size: u32, } -/// Implement a keyboard for keymap translation with key repetition -/// -/// This requires you to provide a callback to receive the events after they -/// have been interpreted with the keymap. -/// -/// The keymap will be loaded from the provided RMLVO rules, or from the compositor -/// provided keymap if `None`. -/// -/// Returns an error if xkbcommon could not be initialized, the RMLVO specification -/// contained invalid values, or if the provided seat does not have keyboard capability. -/// -/// **Note:** This adapter does not handle key repetition. See `map_keyboard_repeat` for that. -pub fn map_keyboard( - seat: &Attached, - rmlvo: Option, - callback: F, -) -> Result -where - F: FnMut(Event<'_>, wl_keyboard::WlKeyboard, wayland_client::DispatchData<'_>) + 'static, -{ - let keyboard = seat.get_keyboard(); - - let state = Rc::new(RefCell::new( - rmlvo - .map(KbState::from_rmlvo) - .unwrap_or_else(KbState::new)?, - )); - - let callback = Rc::new(RefCell::new(callback)); - - // prepare the handler - let mut kbd_handler = KbdHandler { callback, state }; - - keyboard.quick_assign(move |keyboard, event, data| { - kbd_handler.event(keyboard.detach(), event, data) - }); - - Ok(keyboard.detach()) +impl KeymapDescription { + fn new(fd: OwnedFd, size: u32) -> KeymapDescription { + KeymapDescription { fd: fd, size: size } + } } -/* - * Classic handling - */ - -type KbdCallback = dyn FnMut(Event<'_>, wl_keyboard::WlKeyboard, wayland_client::DispatchData<'_>); - -struct KbdHandler { - state: Rc>, - callback: Rc>, +impl Clone for KeymapDescription { + fn clone(&self) -> Self { + KeymapDescription::new(self.fd.try_clone().expect("unable to clone fd"), self.size) + } } -impl KbdHandler { - fn event( - &mut self, - kbd: wl_keyboard::WlKeyboard, - event: wl_keyboard::Event, - dispatch_data: wayland_client::DispatchData, - ) { - use wl_keyboard::Event; +pub struct Keyboard { + state: Option, + desc: Option, +} - match event { - Event::Keymap { format, fd, size } => self.keymap(kbd, format, fd, size), - Event::Enter { - serial, - surface, - keys, - } => self.enter(kbd, serial, surface, keys, dispatch_data), - Event::Leave { serial, surface } => self.leave(kbd, serial, surface, dispatch_data), - Event::Key { - serial, - time, - key, - state, - } => self.key(kbd, serial, time, key, state, dispatch_data), - Event::Modifiers { - mods_depressed, - mods_latched, - mods_locked, - group, - .. - } => self.modifiers( - kbd, - mods_depressed, - mods_latched, - mods_locked, - group, - dispatch_data, - ), - Event::RepeatInfo { rate, delay } => self.repeat_info(kbd, rate, delay, dispatch_data), - _ => {} +impl Keyboard { + pub fn new() -> Keyboard { + Keyboard { + state: None, + desc: None, } } - fn keymap( - &mut self, - _: wl_keyboard::WlKeyboard, - format: wl_keyboard::KeymapFormat, - fd: RawFd, - size: u32, - ) { - let mut state = self.state.borrow_mut(); - if state.locked() { - // state is locked, ignore keymap updates - return; - } - if state.ready() { - // new keymap, we first deinit to free resources - unsafe { - state.de_init(); - } - } + pub fn keymap(&mut self, format: WEnum, fd: OwnedFd, size: u32) { match format { - wl_keyboard::KeymapFormat::XkbV1 => unsafe { - state.init_with_fd(fd, size as usize); - }, - wl_keyboard::KeymapFormat::NoKeymap => { + WEnum::Value(wl_keyboard::KeymapFormat::XkbV1) => { + self.desc = Some(KeymapDescription::new(fd, size)); + } + WEnum::Value(wl_keyboard::KeymapFormat::NoKeymap) => { // TODO: how to handle this (hopefully never occuring) case? } _ => unreachable!(), } } - fn enter( - &mut self, - object: wl_keyboard::WlKeyboard, - serial: u32, - surface: wl_surface::WlSurface, - keys: Vec, - dispatch_data: wayland_client::DispatchData, - ) { - let mut state = self.state.borrow_mut(); - let rawkeys = keys - .chunks_exact(4) - .map(NativeEndian::read_u32) - .collect::>(); - let keys: Vec = rawkeys.iter().map(|k| state.get_one_sym_raw(*k)).collect(); - (&mut *self.callback.borrow_mut())( - Event::Enter { - serial, - surface, - rawkeys: &rawkeys, - keysyms: &keys, - }, - object, - dispatch_data, - ); - } + pub fn resolve(&mut self) { + if self.state.is_some() { + return; + } - fn leave( - &mut self, - object: wl_keyboard::WlKeyboard, - serial: u32, - surface: wl_surface::WlSurface, - dispatch_data: wayland_client::DispatchData, - ) { - (&mut *self.callback.borrow_mut())(Event::Leave { serial, surface }, object, dispatch_data); - } - - fn key( - &mut self, - object: wl_keyboard::WlKeyboard, - serial: u32, - time: u32, - key: u32, - key_state: wl_keyboard::KeyState, - dispatch_data: wayland_client::DispatchData, - ) { - let (sym, utf8, repeats) = { - let mut state = self.state.borrow_mut(); - // Get the values to generate a key event - let sym = state.get_one_sym_raw(key); - let utf8 = if key_state == wl_keyboard::KeyState::Pressed { - match state.compose_feed(sym) { - Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED) => { - if let Some(status) = state.compose_status() { - match status { - ffi::xkb_compose_status::XKB_COMPOSE_COMPOSED => { - state.compose_get_utf8() - } - ffi::xkb_compose_status::XKB_COMPOSE_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 = unsafe { state.key_repeats(key + 8) }; - (sym, utf8, repeats) - }; - - (&mut *self.callback.borrow_mut())( - Event::Key { - serial, - time, - rawkey: key, - keysym: sym, - state: key_state, - utf8, - repeats, - }, - object, - dispatch_data, - ); - } - - fn modifiers( - &mut self, - object: wl_keyboard::WlKeyboard, - mods_depressed: u32, - mods_latched: u32, - mods_locked: u32, - group: u32, - dispatch_data: wayland_client::DispatchData, - ) { - { - let mut state = self.state.borrow_mut(); - state.update_modifiers(mods_depressed, mods_latched, mods_locked, group); - (&mut *self.callback.borrow_mut())( - Event::Modifiers { - modifiers: state.mods_state(), - }, - object, - dispatch_data, + if let Some(desc) = self.desc.take() { + self.state = Some( + KbState::new_from_fd(desc.fd, desc.size as usize).expect("unable to load keymap"), ); } } - fn repeat_info( + 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 + 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(xkb::Keycode::new(key + 8)); + (sym, utf8, repeats) + }; + KeyEvent { + rawkey: key, + keysym: sym, + state: key_state, + modifiers: state.mods_state(), + utf8, + repeats, + } + } else { + KeyEvent { + rawkey: key, + keysym: 0, + state: key_state, + modifiers: Default::default(), + utf8: None, + repeats: false, + } + } + } + + pub fn modifiers( &mut self, - object: wl_keyboard::WlKeyboard, - rate: i32, - delay: i32, - dispatch_data: wayland_client::DispatchData, + mods_depressed: u32, + mods_latched: u32, + mods_locked: u32, + group: u32, ) { - (&mut *self.callback.borrow_mut())( - Event::RepeatInfo { rate, delay }, - object, - dispatch_data, - ); + if let Some(state) = &mut self.state { + state.update_modifiers(mods_depressed, mods_latched, mods_locked, group); + } } } diff --git a/src/keyboard/repeat.rs b/src/keyboard/repeat.rs new file mode 100644 index 0000000..41b984b --- /dev/null +++ b/src/keyboard/repeat.rs @@ -0,0 +1,173 @@ +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 now = 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; + timer.set_deadline(now + *delay); + } + 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() { + timer.set_deadline(now + *delay); + } + } + }, + + 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, |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 ()); + + 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) + })?; + + // 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/keyboard/state.rs b/src/keyboard/state.rs index a5996a4..bca7f8e 100644 --- a/src/keyboard/state.rs +++ b/src/keyboard/state.rs @@ -1,48 +1,14 @@ -use std::{ - env, - ffi::CString, - fs::File, - os::raw::c_char, - os::unix::{ - ffi::OsStringExt, - io::{FromRawFd, RawFd}, - }, - ptr, -}; +use std::{env, os::unix::io::OwnedFd}; -use memmap::MmapOptions; - -use super::ffi::{self, xkb_state_component, XKBCOMMON_HANDLE as XKBH}; -use super::Error; +use xkbcommon::xkb; pub struct KbState { - xkb_context: *mut ffi::xkb_context, - xkb_keymap: *mut ffi::xkb_keymap, - xkb_state: *mut ffi::xkb_state, - xkb_compose_table: *mut ffi::xkb_compose_table, - xkb_compose_state: *mut ffi::xkb_compose_state, + xkb_context: xkb::Context, + xkb_keymap: xkb::Keymap, + xkb_state: xkb::State, + xkb_compose_table: Option, + xkb_compose_state: Option, mods_state: ModifiersState, - locked: bool, -} - -/// The RMLVO description of a keymap -/// -/// All fields are optional, and the system default -/// will be used if set to `None`. -pub struct RMLVO { - /// The rules file to use - pub rules: Option, - /// The keyboard model by which to interpret keycodes and LEDs - pub model: Option, - /// A comma separated list of layouts (languages) to include in the keymap - pub layout: Option, - /// A comma separated list of variants, one per layout, which may modify or - /// augment the respective layout in various ways - pub variant: Option, - /// A comma separated list of options, through which the user specifies - /// non-layout related preferences, like which key combinations are - /// used for switching layouts, or which key is the Compose key. - pub options: Option, } /// Represents the current state of the keyboard modifiers @@ -74,52 +40,19 @@ impl ModifiersState { ModifiersState::default() } - fn update_with(&mut self, state: *mut ffi::xkb_state) { - self.ctrl = unsafe { - (XKBH.xkb_state_mod_name_is_active)( - state, - ffi::XKB_MOD_NAME_CTRL.as_ptr() as *const c_char, - xkb_state_component::XKB_STATE_MODS_EFFECTIVE, - ) > 0 - }; - self.alt = unsafe { - (XKBH.xkb_state_mod_name_is_active)( - state, - ffi::XKB_MOD_NAME_ALT.as_ptr() as *const c_char, - xkb_state_component::XKB_STATE_MODS_EFFECTIVE, - ) > 0 - }; - self.shift = unsafe { - (XKBH.xkb_state_mod_name_is_active)( - state, - ffi::XKB_MOD_NAME_SHIFT.as_ptr() as *const c_char, - xkb_state_component::XKB_STATE_MODS_EFFECTIVE, - ) > 0 - }; - self.caps_lock = unsafe { - (XKBH.xkb_state_mod_name_is_active)( - state, - ffi::XKB_MOD_NAME_CAPS.as_ptr() as *const c_char, - xkb_state_component::XKB_STATE_MODS_EFFECTIVE, - ) > 0 - }; - self.logo = unsafe { - (XKBH.xkb_state_mod_name_is_active)( - state, - ffi::XKB_MOD_NAME_LOGO.as_ptr() as *const c_char, - xkb_state_component::XKB_STATE_MODS_EFFECTIVE, - ) > 0 - }; - self.num_lock = unsafe { - (XKBH.xkb_state_mod_name_is_active)( - state, - ffi::XKB_MOD_NAME_NUM.as_ptr() as *const c_char, - xkb_state_component::XKB_STATE_MODS_EFFECTIVE, - ) > 0 - }; + 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); } } +// Safety: No. +unsafe impl Send for KbState {} + impl KbState { pub(crate) fn update_modifiers( &mut self, @@ -128,157 +61,74 @@ impl KbState { mods_locked: u32, group: u32, ) { - if !self.ready() { - return; - } - let mask = unsafe { - (XKBH.xkb_state_update_mask)( - self.xkb_state, - mods_depressed, - mods_latched, - mods_locked, - 0, - 0, - group, - ) - }; - if mask.contains(xkb_state_component::XKB_STATE_MODS_EFFECTIVE) { + 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); + self.mods_state.update_with(&self.xkb_state); } } pub(crate) fn get_one_sym_raw(&mut self, keycode: u32) -> u32 { - if !self.ready() { - return 0; - } - unsafe { (XKBH.xkb_state_key_get_one_sym)(self.xkb_state, 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 { - if !self.ready() { - return None; - } - let size = unsafe { - (XKBH.xkb_state_key_get_utf8)(self.xkb_state, keycode + 8, ptr::null_mut(), 0) - } + 1; - if size <= 1 { - return None; - }; - let mut buffer = Vec::with_capacity(size as usize); - unsafe { - buffer.set_len(size as usize); - (XKBH.xkb_state_key_get_utf8)( - self.xkb_state, - keycode + 8, - buffer.as_mut_ptr() as *mut _, - size as usize, - ); - }; - // remove the final `\0` - buffer.pop(); - // libxkbcommon will always provide valid UTF8 - Some(unsafe { String::from_utf8_unchecked(buffer) }) + Some(self.xkb_state.key_get_utf8(xkb::Keycode::new(keycode + 8))) } - pub(crate) fn compose_feed(&mut self, keysym: u32) -> Option { - if !self.ready() || self.xkb_compose_state.is_null() { - return None; + pub(crate) fn compose_feed(&mut self, keysym: u32) -> Option { + match &mut self.xkb_compose_state { + Some(compose_state) => Some(compose_state.feed(xkb::Keysym::new(keysym))), + None => None, } - Some(unsafe { (XKBH.xkb_compose_state_feed)(self.xkb_compose_state, keysym) }) } - pub(crate) fn compose_status(&mut self) -> Option { - if !self.ready() || self.xkb_compose_state.is_null() { - return None; + pub(crate) fn compose_status(&mut self) -> Option { + match &mut self.xkb_compose_state { + Some(compose_state) => Some(compose_state.status()), + None => None, } - Some(unsafe { (XKBH.xkb_compose_state_get_status)(self.xkb_compose_state) }) } pub(crate) fn compose_get_utf8(&mut self) -> Option { - if !self.ready() || self.xkb_compose_state.is_null() { - return None; + match &mut self.xkb_compose_state { + Some(compose_state) => compose_state.utf8(), + None => None, } - let size = unsafe { - (XKBH.xkb_compose_state_get_utf8)(self.xkb_compose_state, ptr::null_mut(), 0) - } + 1; - if size <= 1 { - return None; - }; - let mut buffer = Vec::with_capacity(size as usize); - unsafe { - buffer.set_len(size as usize); - (XKBH.xkb_compose_state_get_utf8)( - self.xkb_compose_state, - buffer.as_mut_ptr() as *mut _, - size as usize, - ); - }; - // remove the final `\0` - buffer.pop(); - // libxkbcommon will always provide valid UTF8 - Some(unsafe { String::from_utf8_unchecked(buffer) }) } - pub(crate) fn new() -> Result { - let xkbh = match ffi::XKBCOMMON_OPTION.as_ref() { - Some(h) => h, - None => return Err(Error::XKBNotFound), + 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( + &xkb_context, + fd, + size, + xkb::KEYMAP_FORMAT_TEXT_V1, + xkb::KEYMAP_COMPILE_NO_FLAGS, + )? + .unwrap() }; - let xkb_context = - unsafe { (xkbh.xkb_context_new)(ffi::xkb_context_flags::XKB_CONTEXT_NO_FLAGS) }; - if xkb_context.is_null() { - return Err(Error::XKBNotFound); - } - + let xkb_state = xkb::State::new(&xkb_keymap); let mut me = KbState { xkb_context, - xkb_keymap: ptr::null_mut(), - xkb_state: ptr::null_mut(), - xkb_compose_table: ptr::null_mut(), - xkb_compose_state: ptr::null_mut(), + xkb_keymap, + xkb_state, + xkb_compose_table: None, + xkb_compose_state: None, mods_state: ModifiersState::new(), - locked: false, }; - unsafe { - me.init_compose(); - } + me.init_compose(); Ok(me) } - pub(crate) fn from_rmlvo(rmlvo: RMLVO) -> Result { - fn to_cstring(s: Option) -> Result, Error> { - s.map_or(Ok(None), |s| CString::new(s).map(Option::Some)) - .map_err(|_| Error::BadNames) - } - - let mut state = KbState::new()?; - - let rules = to_cstring(rmlvo.rules)?; - let model = to_cstring(rmlvo.model)?; - let layout = to_cstring(rmlvo.layout)?; - let variant = to_cstring(rmlvo.variant)?; - let options = to_cstring(rmlvo.options)?; - - let xkb_names = ffi::xkb_rule_names { - rules: rules.map_or(ptr::null(), |s| s.as_ptr()), - model: model.map_or(ptr::null(), |s| s.as_ptr()), - layout: layout.map_or(ptr::null(), |s| s.as_ptr()), - variant: variant.map_or(ptr::null(), |s| s.as_ptr()), - options: options.map_or(ptr::null(), |s| s.as_ptr()), - }; - - unsafe { - state.init_with_rmlvo(xkb_names)?; - } - - state.locked = true; - Ok(state) - } - - pub(crate) unsafe fn init_compose(&mut self) { + 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")) @@ -286,99 +136,24 @@ impl KbState { .or_else(|| env::var_os("LANG")) .and_then(|v| if v.is_empty() { None } else { Some(v) }) .unwrap_or_else(|| "C".into()); - let locale = CString::new(locale.into_vec()).unwrap(); - let compose_table = (XKBH.xkb_compose_table_new_from_locale)( - self.xkb_context, - locale.as_ptr(), - ffi::xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS, - ); + let compose_table = match xkb::compose::Table::new_from_locale( + &self.xkb_context, + &locale, + xkb::compose::COMPILE_NO_FLAGS, + ) { + Ok(t) => t, + Err(_) => return, + }; - if compose_table.is_null() { - // init of compose table failed, continue without compose - return; - } + let compose_state = xkb::compose::State::new(&compose_table, xkb::compose::STATE_NO_FLAGS); - let compose_state = (XKBH.xkb_compose_state_new)( - compose_table, - ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS, - ); - - if compose_state.is_null() { - // init of compose state failed, continue without compose - (XKBH.xkb_compose_table_unref)(compose_table); - return; - } - - self.xkb_compose_table = compose_table; - self.xkb_compose_state = compose_state; + self.xkb_compose_table = Some(compose_table); + self.xkb_compose_state = Some(compose_state); } - pub(crate) unsafe fn post_init(&mut self, xkb_keymap: *mut ffi::xkb_keymap) { - let xkb_state = (XKBH.xkb_state_new)(xkb_keymap); - self.xkb_keymap = xkb_keymap; - self.xkb_state = xkb_state; - self.mods_state.update_with(xkb_state); - } - - pub(crate) unsafe fn de_init(&mut self) { - (XKBH.xkb_state_unref)(self.xkb_state); - self.xkb_state = ptr::null_mut(); - (XKBH.xkb_keymap_unref)(self.xkb_keymap); - self.xkb_keymap = ptr::null_mut(); - } - - pub(crate) unsafe fn init_with_fd(&mut self, fd: RawFd, size: usize) { - let map = MmapOptions::new() - .len(size) - .map(&File::from_raw_fd(fd)) - .unwrap(); - - let xkb_keymap = (XKBH.xkb_keymap_new_from_string)( - self.xkb_context, - map.as_ptr() as *const _, - ffi::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1, - ffi::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS, - ); - - if xkb_keymap.is_null() { - panic!("Received invalid keymap from compositor."); - } - - self.post_init(xkb_keymap); - } - - pub(crate) unsafe fn init_with_rmlvo( - &mut self, - names: ffi::xkb_rule_names, - ) -> Result<(), Error> { - let xkb_keymap = (XKBH.xkb_keymap_new_from_names)( - self.xkb_context, - &names, - ffi::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS, - ); - - if xkb_keymap.is_null() { - return Err(Error::BadNames); - } - - self.post_init(xkb_keymap); - - Ok(()) - } - - pub(crate) unsafe fn key_repeats(&mut self, xkb_keycode_t: ffi::xkb_keycode_t) -> bool { - (XKBH.xkb_keymap_key_repeats)(self.xkb_keymap, xkb_keycode_t) == 1 - } - - #[inline] - pub(crate) fn ready(&self) -> bool { - !self.xkb_state.is_null() - } - - #[inline] - pub(crate) fn locked(&self) -> bool { - self.locked + pub(crate) fn key_repeats(&mut self, keycode: xkb::Keycode) -> bool { + self.xkb_keymap.key_repeats(keycode) } #[inline] @@ -386,15 +161,3 @@ impl KbState { self.mods_state } } - -impl Drop for KbState { - fn drop(&mut self) { - unsafe { - (XKBH.xkb_compose_state_unref)(self.xkb_compose_state); - (XKBH.xkb_compose_table_unref)(self.xkb_compose_table); - (XKBH.xkb_state_unref)(self.xkb_state); - (XKBH.xkb_keymap_unref)(self.xkb_keymap); - (XKBH.xkb_context_unref)(self.xkb_context); - } - } -} diff --git a/src/main.rs b/src/main.rs index 3d36fc6..4562212 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,389 +1,353 @@ -use std::env; -use std::fs::File; -use std::io::{BufRead, BufReader, Read, Write}; -use std::os::unix::io::AsRawFd; -use std::os::unix::net::{UnixListener, UnixStream}; -use std::{collections::HashMap, sync::mpsc::channel}; - -use chrono::{Duration, Local}; -use nix::poll::{poll, PollFd, PollFlags}; -use os_pipe::pipe; -use timerfd::{SetTimeFlags, TimerFd, TimerState}; - -mod app; mod buffer; -mod cmd; mod color; mod config; -mod configfmt; -mod data; -mod desktop; -mod doublemempool; mod draw; +mod event; mod fonts; mod keyboard; -mod widget; +mod state; +mod utils; mod widgets; -#[macro_use] -extern crate dlib; +use wayland_client::{Connection, QueueHandle, WaylandSource}; -use app::{App, OutputMode}; -use cmd::Cmd; +use calloop::{ + timer::{TimeoutAction, Timer}, + EventLoop, +}; +use chrono::{Duration, Local, Timelike}; + +use buffer::BufferView; use config::Config; -use configfmt::ConfigFmt; -use fonts::{FontLoader, FontMap, FontSeeker}; -use widget::WaitContext; +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}; -enum Mode { - Start, - Daemonize, - StartOrKill, - ToggleVisible, - PrintConfig(ConfigFmt), +use std::{ + env, + fs::File, + io::{BufRead, BufReader, Write}, + os::unix::net::{UnixListener, UnixStream}, + rc::Rc, + 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 + _ = 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 == "--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 == "toplevel" => Config::generate_v2(true), + None => Config::generate_v2(false), + Some(s) => panic!("unknown argument: {}", s), + }; + match File::create(config_file) { + 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(ref s) if s == "--help" => { + print_usage(); + std::process::exit(0); + } + Some(_) => { + print_usage(); + std::process::exit(1); + } + None => break, + } + } + + 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, ()); + display.sync(&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!"); + + 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| { + 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!"); + + 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; + 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 + // 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 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 socket_path = match env::var("XDG_RUNTIME_DIR") { Ok(dir) => dir + "/wldash", Err(_) => "/tmp/wldash".to_string(), }; - let config_home = match env::var("XDG_CONFIG_HOME") { - Ok(dir) => dir + "/wldash", - Err(_) => match env::var("HOME") { - Ok(home) => home + "/.config/wldash", - _ => panic!("unable to find user folder"), - }, + + let events = Events::new(ping_sender); + + let mut fm = FontMap::new(); + + let config: Config = match File::open(config_file) { + Ok(f) => serde_yaml::from_reader(f).unwrap(), + Err(_) => panic!("configuration file missing"), }; - // From all existing files take the first readable one and write it's extension to `ext` - let mut ext = [0x0; 8]; - let file = configfmt::CONFIG_NAMES - .iter() - .map(|name| std::path::Path::new(&config_home).join(name)) - .filter_map(|path| { - match File::open(&path) { - Ok(file) => { - let e = path.extension().and_then(|e| e.to_str())?; - let len = e.len(); - let from = len.saturating_sub(8); // the longest possible extension - ext[0..len - from].copy_from_slice(&e.as_bytes()[from..]); - Some(file) - } - Err(_) => None, - } - }) - .next(); - - let fmt = std::str::from_utf8(&ext) - .ok() - .and_then(ConfigFmt::new) - .unwrap_or_default(); - - let config: Config = file - .map(|f| fmt.from_reader(BufReader::new(f))) - .unwrap_or_default(); - - let scale = config.scale; - - let fonts: FontMap = { - let load_font = |font_name| { - let path = FontSeeker::from_string(font_name); - FontLoader::from_path(&path).expect(&format!("Loading {} failed", path.display())) + if let Some(true) = config.server { + if let Ok(mut socket) = UnixStream::connect(socket_path.clone()) { + socket.write_all(b"kill\n").unwrap(); + return; }; - config - .fonts - .iter() - .map(|(key, val)| (key.clone(), load_font(val))) - .collect::>() - }; - - let mut args = env::args(); - let _ = args.next(); - let mode = match args.next() { - Some(arg) => match arg.as_str() { - "start" => Mode::Daemonize, - "start-or-kill" => Mode::StartOrKill, - "toggle-visible" => Mode::ToggleVisible, - "print-config" => Mode::PrintConfig(fmt), - s => { - let p = "print-config-"; - let l = p.len(); - let fmt = if s.starts_with(p) { - ConfigFmt::new(&s[l..]) - } else { - None - }; - if let Some(fmt) = fmt { - Mode::PrintConfig(fmt) - } else { - eprintln!("unsupported sub-command {}", s); - std::process::exit(1); - } - } - }, - None => Mode::Start, - }; - if args.next().is_some() { - // total = args.count + 2 (the two we skipped + the rest) - eprintln!("expected 0 or 1 arguments, got {}", args.count() + 2); - std::process::exit(1); - } - - let mut daemon = false; - - match mode { - Mode::ToggleVisible => { - if let Ok(mut socket) = UnixStream::connect(socket_path) { - socket.write_all(b"toggle_visible\n").unwrap(); - return; - }; - eprintln!("wldash is not running"); - std::process::exit(1); - } - Mode::StartOrKill => { - if let Ok(mut socket) = UnixStream::connect(socket_path.clone()) { - socket.write_all(b"kill\n").unwrap(); - return; - }; - } - Mode::Start => { - if UnixStream::connect(socket_path.clone()).is_ok() { - eprintln!("wldash is already running"); - std::process::exit(1); - }; - } - Mode::Daemonize => { - if UnixStream::connect(socket_path.clone()).is_ok() { - eprintln!("wldash is already running"); - std::process::exit(1); - }; - daemon = true; - } - Mode::PrintConfig(fmt) => { - println!("{}", fmt.to_string(&config)); - std::process::exit(0); - } - } - - let _ = std::fs::remove_file(socket_path.clone()); - let listener = UnixListener::bind(socket_path.clone()).unwrap(); - - let output_mode = match config.output_mode { - config::OutputMode::All => OutputMode::All, - config::OutputMode::Active => OutputMode::Active, - }; - - let background = config.background; - - let (tx_draw, rx_draw) = channel(); - let tx_draw_mod = tx_draw.clone(); - - // Print, write to a file, or send to an HTTP server. - let widget = config - .widget - .construct(Local::now().naive_local(), tx_draw_mod, &fonts) - .expect("no widget configured"); - - let mut app = App::new(tx_draw, output_mode, background, scale); - if daemon { - app.hide(); - } else { - app.show(); - } - app.set_widget(widget).unwrap(); - - let (mut rx_pipe, mut tx_pipe) = pipe().unwrap(); - let ipc_pipe = tx_pipe.try_clone().unwrap(); - - let worker_queue = app.cmd_queue(); - let _ = std::thread::Builder::new() - .name("cmd_proxy".to_string()) - .spawn(move || loop { - let cmd = rx_draw.recv().unwrap(); - worker_queue.lock().unwrap().push_back(cmd); - tx_pipe.write_all(&[0x1]).unwrap(); - }); - - let mut timer = TimerFd::new().unwrap(); - let ev_fd = PollFd::new( - app.event_queue().display().get_connection_fd(), - PollFlags::POLLIN, - ); - let rx_fd = PollFd::new(rx_pipe.as_raw_fd(), PollFlags::POLLIN); - let tm_fd = PollFd::new(timer.as_raw_fd(), PollFlags::POLLIN); - let ipc_fd = PollFd::new(listener.as_raw_fd(), PollFlags::POLLIN); - - app.cmd_queue().lock().unwrap().push_back(Cmd::Draw); - - let mut visible = !daemon; - let mut wait_ctx = WaitContext { - fds: Vec::new(), - target_time: None, - }; - - let q = app.cmd_queue(); - loop { - let cmd = q.lock().unwrap().pop_front(); - match cmd { - Some(cmd) => match cmd { - Cmd::Draw => { - app.redraw(false).expect("Failed to draw"); - app.flush_display(); - } - Cmd::KeyboardTest => { - if let Some(cmd) = app.key_repeat() { - q.lock().unwrap().push_back(cmd); - } - } - Cmd::ForceDraw => { - app.redraw(true).expect("Failed to draw"); - app.flush_display(); - } - Cmd::MouseClick { btn, pos } => { - app.get_widget().mouse_click(btn, pos); - q.lock().unwrap().push_back(Cmd::Draw); - } - Cmd::MouseScroll { scroll, pos } => { - app.get_widget().mouse_scroll(scroll, pos); - q.lock().unwrap().push_back(Cmd::Draw); - } - Cmd::Keyboard { - key, - key_state, - modifiers_state, - interpreted, - } => { - app.get_widget() - .keyboard_input(key, modifiers_state, key_state, interpreted); - q.lock().unwrap().push_back(Cmd::Draw); - } - Cmd::ToggleVisible => { - visible = !visible; - if visible { - app.get_widget().enter(); - app.show(); - } else { - app.hide(); - app.get_widget().leave(); - } - app.flush_display(); - } - Cmd::Exit => { - if daemon { - visible = false; - app.hide(); - app.get_widget().leave(); - app.flush_display(); - } else { - let _ = std::fs::remove_file(socket_path); - return; - } - } - }, - None => { - app.flush_display(); - - wait_ctx.fds.clear(); - wait_ctx.fds.push(ev_fd); - wait_ctx.fds.push(rx_fd); - wait_ctx.fds.push(ipc_fd); - wait_ctx.target_time = None; - - app.get_widget().wait(&mut wait_ctx); - app.set_keyboard_repeat(&mut wait_ctx); - - if let Some(target_time) = wait_ctx.target_time { - let n = Local::now().naive_local(); - let sleep = if target_time > n { - target_time - n - } else { - Duration::seconds(0) - }; - timer.set_state( - TimerState::Oneshot(sleep.to_std().unwrap()), - SetTimeFlags::Default, - ); - wait_ctx.fds.push(tm_fd); - } - - poll(&mut wait_ctx.fds, -1).unwrap(); - - if wait_ctx.fds[0] - .revents() - .unwrap() - .contains(PollFlags::POLLIN) - { - if let Some(guard) = app.event_queue().prepare_read() { - if let Err(e) = guard.read_events() { - if e.kind() != ::std::io::ErrorKind::WouldBlock { - eprintln!( - "Error while trying to read from the wayland socket: {:?}", - e - ); - } - } - } - - app.event_queue() - .dispatch_pending(&mut (), |_, _, _| {}) - .expect("Failed to dispatch all messages."); - } - - if wait_ctx.fds[1] - .revents() - .unwrap() - .contains(PollFlags::POLLIN) - { - let mut v = [0x00]; - rx_pipe.read_exact(&mut v).unwrap(); - } - - if wait_ctx.fds[2] - .revents() - .unwrap() - .contains(PollFlags::POLLIN) - { - if let Ok((stream, _)) = listener.accept() { - let client_queue = q.clone(); - let mut client_pipe = ipc_pipe.try_clone().unwrap(); - let _ = std::thread::Builder::new() - .name("ipc_client".to_string()) - .spawn(move || { - let r = BufReader::new(stream); - for line in r.lines() { - match line { - Ok(v) => match v.as_str() { - "kill" => { - client_queue.lock().unwrap().push_back(Cmd::Exit) + 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); } - "toggle_visible" => client_queue - .lock() - .unwrap() - .push_back(Cmd::ToggleVisible), - v => eprintln!("unknown command: {}", v), - }, - Err(_) => return, + _ => {} + } } - client_pipe.write_all(&[0x1]).unwrap(); - } - }); + }, + ); + } + _ => (), } } + }); + } - if wait_ctx.target_time.is_some() - && wait_ctx.fds[wait_ctx.fds.len() - 1] - .revents() - .unwrap() - .contains(PollFlags::POLLIN) - { - timer.read(); - let mut qq = q.lock().unwrap(); - qq.push_back(Cmd::KeyboardTest); - qq.push_back(Cmd::Draw); + 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(config.widget.construct_layout(&mut 0)); + let mut widgets: Vec> = Vec::new(); + config + .widget + .construct_widgets(&mut widgets, &mut fm, &events); + + let font_thread = thread::Builder::new() + .name("fontloader".to_string()) + .spawn(move || { + fm.load_fonts(); + fm + }) + .unwrap(); + + let mut state = State::new( + config.mode, + config.background, + widgets, + layout, + MaybeFontMap::Waiting(font_thread), + events, + keyrepeat_sender, + ); + + // 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 || !state.ready { + continue; + } + + let mut force = false; + if state.bufmgr.buffers.len() == 0 { + state.add_buffer(&qhandle); + force = true; + } + + let mut bufcnt = state.bufmgr.buffers.len(); + let buf = match state.bufmgr.next_buffer() { + Some(b) => b, + None => { + if state.bufmgr.buffers.len() >= 3 { + continue; + } + state.add_buffer(&qhandle); + bufcnt = state.bufmgr.buffers.len(); + + match state.bufmgr.next_buffer() { + Some(b) => b, + None => { + continue; + } } } + }; + + 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( + &mut buf.mmap, + (state.dimensions.0 as u32, state.dimensions.1 as u32), + ); + + let surface = state.main_surface.wl_surface.as_ref().unwrap().clone(); + if force { + for (idx, widget) in state.widgets.iter_mut().enumerate() { + if force || widget.get_dirty() { + let geo = widget.geometry(); + let mut subview = bufview.subgeometry(geo); + 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 bufcnt > 1 || widget.get_dirty() { + drew = true; + + let old_damage = buf.last_damage[idx]; + bufview.subgeometry(old_damage).clear(); + + let geo = widget.geometry(); + let mut subview = bufview.subgeometry(geo); + + let new_damage = + widget.draw(&mut state.fonts.unwrap().borrow_mut(), &mut subview); + let combined_damage = new_damage.expand(old_damage); + buf.last_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, + ); + } + } + if !drew { + buf.release(); + conn.flush().unwrap(); + continue; + } + } + + state.ready = false; + surface.attach(Some(&buf.buffer), 0, 0); + surface.frame(&qhandle, ()); + surface.commit(); + conn.flush().unwrap(); + + // Now is a good as time as any to load the keymap + state.keyboard.resolve(); } } diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..b36f5e1 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,865 @@ +use std::{ + cmp::max, + default::Default, + env, + rc::Rc, + sync::{Arc, Mutex}, +}; + +use wayland_client::{ + protocol::{ + 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, +}; + +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}, + }, +}; + +use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1}; + +use crate::{ + buffer::BufferManager, + config::OperationMode, + event::{Event, Events, PointerButton, PointerEvent}, + fonts::{FontMap, MaybeFontMap}, + keyboard::{Keyboard, RepeatMessage}, + widgets::{Geometry, Layout, Widget, WidgetUpdater}, +}; + +use calloop::channel::Sender; + +#[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_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 { + mode: OperationMode, + background: Option, + pub main_surface: MainSurface, + pub bg_surface: BackgroundSurface, + pub protocols: Protocols, + pub running: bool, + pub ready: bool, + pub dirty: bool, + activated: bool, + pub configured: bool, + pub dimensions: (i32, i32), + pub bufmgr: BufferManager, + pub widgets: Vec>, + pub keyboard: Keyboard, + pub keyrepeat_sender: Sender, + pointer: Option<(f64, f64)>, + pub fonts: MaybeFontMap, + pub events: Arc>, + layout: Rc>, +} + +impl State { + pub fn new( + mode: OperationMode, + background: Option, + widgets: Vec>, + layout: Rc>, + fonts: MaybeFontMap, + events: Arc>, + keyrepeat_sender: Sender, + ) -> State { + State { + protocols: Default::default(), + bg_surface: Default::default(), + main_surface: Default::default(), + running: true, + dirty: true, + activated: false, + configured: false, + dimensions: (320, 240), + bufmgr: BufferManager::new(), + keyboard: Keyboard::new(), + pointer: None, + keyrepeat_sender, + mode, + background, + widgets, + fonts, + events, + layout, + ready: false, + } + } + + pub fn add_buffer(&mut self, qh: &QueueHandle) { + self.bufmgr.add_buffer( + self.protocols.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.main_surface.wl_surface, + &self.protocols.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 minimum_size(&mut self, idx: usize, fonts: &mut FontMap) -> Geometry { + self.widgets[idx].minimum_size(fonts) + } +} + +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::Overlay, + "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, + registry: &wl_registry::WlRegistry, + event: wl_registry::Event, + _: &(), + _: &Connection, + qh: &QueueHandle, + ) { + if let wl_registry::Event::Global { + name, interface, .. + } = event + { + match &interface[..] { + "wl_compositor" => { + state.protocols.wl_compositor = + Some(registry.bind::(name, 5, qh, ())); + } + "wl_subcompositor" => { + state.protocols.wl_subcompositor = Some( + registry.bind::(name, 1, qh, ()), + ); + } + "wl_shm" => { + 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 { + state.protocols.xdg_wm_base = + Some(registry.bind::(name, 2, qh, ())); + } + } + "xdg_activation_v1" => { + state.protocols.xdg_activation = Some( + registry.bind::(name, 1, qh, ()), + ); + } + "zwlr_layer_shell_v1" => { + if let OperationMode::LayerSurface(_) = state.mode { + state.protocols.zwlr_layer_shell = Some( + registry.bind::( + name, + 4, + qh, + (), + ), + ); + } + } + "wp_viewporter" => { + state.protocols.viewporter = + Some(registry.bind::(name, 1, qh, ())); + } + "wp_single_pixel_buffer_manager_v1" => { + state.protocols.wp_single_pixel_buffer_manager = Some(registry.bind::(name, 1, qh, ())); + } + _ => {} + } + } + } +} + +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, + _: &wl_compositor::WlCompositor, + _: wl_compositor::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for State { + fn event( + _: &mut Self, + _: &wl_subcompositor::WlSubcompositor, + _: wl_subcompositor::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_subsurface::WlSubsurface, + _: wl_subsurface::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 Dispatch for State { + fn event( + _: &mut Self, + _: &zwlr_layer_shell_v1::ZwlrLayerShellV1, + _: zwlr_layer_shell_v1::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + layer_surface: &zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, + event: zwlr_layer_surface_v1::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + 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; + } + 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 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(); + } + zwlr_layer_surface_v1::Event::Closed => { + std::process::exit(0); + } + _ => (), + } + } +} + +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 { + mut width, + mut height, + states, + .. + } => { + if (width, height) == (0, 0) { + if state.configured { + return; + } + (width, height) = (1280, 720); + } + 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 { + let ev = Event::FocusLost; + for widget in state.widgets.iter_mut() { + widget.event(&ev); + } + } + + 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"); + viewport.set_destination(width, height); + surface.commit(); + + state.bufmgr.clear_buffers(); + state.dimensions = (width, height); + 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, + ); + } + } + 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, ()); + } + if capabilities.contains(wl_seat::Capability::Pointer) { + seat.get_pointer(qh, ()); + } + } + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + _: &wl_keyboard::WlKeyboard, + event: wl_keyboard::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + match event { + wl_keyboard::Event::Leave { .. } => { + state + .keyrepeat_sender + .send(RepeatMessage::StopRepeat) + .unwrap(); + } + wl_keyboard::Event::Key { + key, + state: kbstate, + .. + } => { + if key == 1 { + state.running = false; + return; + } + state.keyboard.resolve(); + 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 { + mods_depressed, + mods_latched, + mods_locked, + group, + .. + } => { + state + .keyboard + .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); + } + wl_keyboard::Event::RepeatInfo { rate, delay } => { + state + .keyrepeat_sender + .send(RepeatMessage::RepeatInfo((rate as u32, delay as u32))) + .unwrap(); + } + _ => (), + } + } +} + +// TODO: Move to dedicated pointer module +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, + _: &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, + _: &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 } => { + let ev = Event::TokenUpdate(token); + for widget in state.widgets.iter_mut() { + widget.event(&ev); + } + } + _ => (), + } + } +} diff --git a/src/utils/desktop.rs b/src/utils/desktop.rs new file mode 100644 index 0000000..5074760 --- /dev/null +++ b/src/utils/desktop.rs @@ -0,0 +1,117 @@ +use std::{ + cmp::Ordering, + collections::HashSet, + error::Error, + 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, xdg}; + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, Hash)] +#[serde(rename_all = "snake_case")] +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 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(';') + .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", + ))), + } + } +} + +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_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 dirs = xdg::data_folders(); + + 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 new file mode 100644 index 0000000..9f478dc --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,3 @@ +pub mod desktop; +mod inish; +pub mod xdg; diff --git a/src/utils/xdg.rs b/src/utils/xdg.rs new file mode 100644 index 0000000..9146912 --- /dev/null +++ b/src/utils/xdg.rs @@ -0,0 +1,40 @@ +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/widget.rs b/src/widget.rs deleted file mode 100644 index 36df21b..0000000 --- a/src/widget.rs +++ /dev/null @@ -1,458 +0,0 @@ -use crate::buffer::Buffer; -use crate::color::Color; -pub use crate::keyboard::{KeyState, ModifiersState}; -use chrono::NaiveDateTime; -use nix::poll::PollFd; - -pub struct DrawContext<'a> { - pub buf: &'a mut Buffer<'a>, - pub bg: &'a Color, - pub time: NaiveDateTime, - pub force: bool, -} - -#[derive(Debug)] -pub struct DrawReport { - pub width: u32, - pub height: u32, - pub damage: Vec<(i32, i32, i32, i32)>, - pub full_damage: bool, -} - -impl DrawReport { - pub fn empty(width: u32, height: u32) -> DrawReport { - DrawReport { - width, - height, - damage: Vec::new(), - full_damage: false, - } - } -} - -pub struct WaitContext { - pub fds: Vec, - pub target_time: Option, -} - -impl WaitContext { - pub fn set_time(&mut self, new_time: NaiveDateTime) { - if let Some(ot) = self.target_time { - if new_time < ot { - self.target_time = Some(new_time); - } - } else { - self.target_time = Some(new_time); - } - } -} - -pub trait Widget { - fn wait(&mut self, ctx: &mut WaitContext); - fn enter(&mut self); - fn leave(&mut self); - fn size(&self) -> (u32, u32); - fn draw( - &mut self, - ctx: &mut DrawContext, - pos: (u32, u32), - expansion: (u32, u32), - ) -> Result; - - fn keyboard_input( - &mut self, - keysym: u32, - modifier_state: ModifiersState, - key_state: KeyState, - interpreted: Option, - ); - fn mouse_click(&mut self, button: u32, pos: (u32, u32)); - fn mouse_scroll(&mut self, scroll: (f64, f64), pos: (u32, u32)); -} - -pub struct VerticalLayout<'a> { - pub children: Vec>, -} - -#[allow(dead_code)] -impl<'a> VerticalLayout<'a> { - pub fn new(children: Vec>) -> Box { - Box::new(VerticalLayout { children }) - } -} - -impl<'a> Widget for VerticalLayout<'a> { - fn wait(&mut self, ctx: &mut WaitContext) { - for child in &mut self.children { - child.wait(ctx); - } - } - fn enter(&mut self) { - for child in &mut self.children { - child.enter(); - } - } - fn leave(&mut self) { - for child in &mut self.children { - child.leave(); - } - } - fn size(&self) -> (u32, u32) { - let mut width = 0; - let mut height = 0; - - for child in &self.children { - let size = child.size(); - if size.0 > width { - width = size.0; - } - height += size.1; - } - - (width, height) - } - - fn draw( - &mut self, - ctx: &mut DrawContext, - pos: (u32, u32), - expansion: (u32, u32), - ) -> Result { - let mut offset = 0; - let mut width = 0; - let mut damage = Vec::new(); - let mut full_damage = false; - for child in &mut self.children { - let mut report = child.draw( - ctx, - (pos.0, offset + pos.1), - (expansion.0, expansion.1 - offset), - )?; - if report.width > width { - width = report.width - } - offset += report.height; - full_damage |= report.full_damage; - damage.append(&mut report.damage); - } - - Ok(DrawReport { - width, - height: offset, - damage, - full_damage, - }) - } - - fn keyboard_input( - &mut self, - keysym: u32, - modifier_state: ModifiersState, - key_state: KeyState, - interpreted: Option, - ) { - for child in &mut self.children { - child.keyboard_input(keysym, modifier_state, key_state, interpreted.clone()); - } - } - - fn mouse_click(&mut self, button: u32, pos: (u32, u32)) { - let mut height = 0; - - for child in &mut self.children { - let size = child.size(); - if pos.1 >= height && pos.1 < height + size.1 { - let pos = (pos.0, pos.1 - height); - child.mouse_click(button, pos); - return; - } - height += size.1; - } - } - - fn mouse_scroll(&mut self, scroll: (f64, f64), pos: (u32, u32)) { - let mut height = 0; - - for child in &mut self.children { - let size = child.size(); - if pos.1 >= height && pos.1 < height + size.1 { - let pos = (pos.0, pos.1 - height); - child.mouse_scroll(scroll, pos); - return; - } - height += size.1; - } - } -} - -pub struct HorizontalLayout<'a> { - pub children: Vec>, -} - -#[allow(dead_code)] -impl<'a> HorizontalLayout<'a> { - pub fn new(children: Vec>) -> Box { - Box::new(HorizontalLayout { children }) - } - - fn height(&self) -> u32 { - let mut height = 0; - - for child in &self.children { - let size = child.size(); - if size.1 > height { - height = size.1; - } - } - - height - } -} - -impl<'a> Widget for HorizontalLayout<'a> { - fn wait(&mut self, ctx: &mut WaitContext) { - for child in &mut self.children { - child.wait(ctx); - } - } - fn enter(&mut self) { - for child in &mut self.children { - child.enter(); - } - } - fn leave(&mut self) { - for child in &mut self.children { - child.leave(); - } - } - fn size(&self) -> (u32, u32) { - let mut width = 0; - let mut height = 0; - - for child in &self.children { - let size = child.size(); - if size.1 > height { - height = size.1; - } - width += size.0; - } - - (width, height) - } - - fn draw( - &mut self, - ctx: &mut DrawContext, - pos: (u32, u32), - expansion: (u32, u32), - ) -> Result { - let mut offset = 0; - let mut height = 0; - let mut damage = Vec::new(); - let mut full_damage = false; - for child in &mut self.children { - let mut report = child.draw( - ctx, - (offset + pos.0, pos.1), - (expansion.0 - offset, expansion.1), - )?; - if report.height > height { - height = report.height - } - offset += report.width; - full_damage |= report.full_damage; - damage.append(&mut report.damage); - } - - Ok(DrawReport { - width: offset, - height, - damage, - full_damage, - }) - } - - fn keyboard_input( - &mut self, - keysym: u32, - modifier_state: ModifiersState, - key_state: KeyState, - interpreted: Option, - ) { - for child in &mut self.children { - child.keyboard_input(keysym, modifier_state, key_state, interpreted.clone()); - } - } - - fn mouse_click(&mut self, button: u32, pos: (u32, u32)) { - let mut width = 0; - - for child in &mut self.children { - let size = child.size(); - if pos.0 >= width && pos.0 < width + size.0 { - let pos = (pos.0 - width, pos.1); - child.mouse_click(button, pos); - return; - } - - width += size.0; - } - } - - fn mouse_scroll(&mut self, scroll: (f64, f64), pos: (u32, u32)) { - let mut width = 0; - - for child in &mut self.children { - let size = child.size(); - if pos.0 >= width && pos.0 < width + size.0 { - let pos = (pos.0 - width, pos.1); - child.mouse_scroll(scroll, pos); - return; - } - - width += size.0; - } - } -} - -pub struct Margin<'a> { - pub child: Box, - pub margins: (u32, u32, u32, u32), // left, right, top, bottom -} - -#[allow(dead_code)] -impl<'a> Margin<'a> { - pub fn new(margins: (u32, u32, u32, u32), child: Box) -> Box { - Box::new(Margin { child, margins }) - } -} - -impl<'a> Widget for Margin<'a> { - fn wait(&mut self, ctx: &mut WaitContext) { - self.child.wait(ctx) - } - fn enter(&mut self) { - self.child.enter() - } - fn leave(&mut self) { - self.child.leave() - } - fn size(&self) -> (u32, u32) { - let size = self.child.size(); - ( - size.0 + self.margins.0 + self.margins.1, - size.1 + self.margins.2 + self.margins.3, - ) - } - - fn draw( - &mut self, - ctx: &mut DrawContext, - pos: (u32, u32), - expansion: (u32, u32), - ) -> Result { - let expansion = ( - expansion.0 - self.margins.0 - self.margins.1, - expansion.1 - self.margins.2 - self.margins.3, - ); - let report = self.child.draw( - ctx, - (pos.0 + self.margins.0, pos.1 + self.margins.2), - expansion, - )?; - Ok(DrawReport { - width: report.width + self.margins.0 + self.margins.1, - height: report.height + self.margins.2 + self.margins.3, - damage: report.damage, - full_damage: report.full_damage, - }) - } - - fn keyboard_input( - &mut self, - keysym: u32, - modifier_state: ModifiersState, - key_state: KeyState, - interpreted: Option, - ) { - self.child - .keyboard_input(keysym, modifier_state, key_state, interpreted); - } - - fn mouse_click(&mut self, button: u32, pos: (u32, u32)) { - let pos = ( - pos.0.saturating_sub(self.margins.0), - pos.1.saturating_sub(self.margins.2), - ); - self.child.mouse_click(button, pos); - } - - fn mouse_scroll(&mut self, scroll: (f64, f64), pos: (u32, u32)) { - let pos = ( - pos.0.saturating_sub(self.margins.0), - pos.1.saturating_sub(self.margins.2), - ); - self.child.mouse_scroll(scroll, pos); - } -} - -pub struct Fixed<'a> { - pub child: Box, - pub size: (u32, u32), -} - -#[allow(dead_code)] -impl<'a> Fixed<'a> { - pub fn new(size: (u32, u32), child: Box) -> Box { - Box::new(Fixed { child, size }) - } -} - -impl<'a> Widget for Fixed<'a> { - fn wait(&mut self, ctx: &mut WaitContext) { - self.child.wait(ctx) - } - fn enter(&mut self) { - self.child.enter() - } - fn leave(&mut self) { - self.child.leave() - } - fn size(&self) -> (u32, u32) { - self.size - } - - fn draw( - &mut self, - ctx: &mut DrawContext, - pos: (u32, u32), - expansion: (u32, u32), - ) -> Result { - let report = self.child.draw(ctx, pos, expansion)?; - Ok(DrawReport { - width: self.size.0, - height: self.size.1, - damage: report.damage, - full_damage: report.full_damage, - }) - } - - fn keyboard_input( - &mut self, - keysym: u32, - modifier_state: ModifiersState, - key_state: KeyState, - interpreted: Option, - ) { - self.child - .keyboard_input(keysym, modifier_state, key_state, interpreted); - } - - fn mouse_click(&mut self, button: u32, pos: (u32, u32)) { - self.child.mouse_click(button, pos); - } - - fn mouse_scroll(&mut self, scroll: (f64, f64), pos: (u32, u32)) { - self.child.mouse_scroll(scroll, pos); - } -} 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/audio/alsa_sound.rs b/src/widgets/audio/alsa_sound.rs deleted file mode 100644 index 0679447..0000000 --- a/src/widgets/audio/alsa_sound.rs +++ /dev/null @@ -1,115 +0,0 @@ -use crate::color::Color; -use crate::widget::WaitContext; -use crate::{ - fonts::FontRef, - widgets::bar_widget::{BarWidget, BarWidgetImpl}, -}; - -use alsa::mixer::{Mixer, Selem, SelemChannelId, SelemId}; - -const CARD_NAME: &str = "default"; -const SELEM_NAME: &str = "Master"; -const SELEM_ID: u32 = 0u32; - -#[inline] -fn alsa_error_to_io_error(fail: &'static str, err: &alsa::Error) -> ::std::io::Error { - let kind = ::std::io::ErrorKind::Other; - let func = err.func(); - ::std::io::Error::new(kind, format!("{}: {}{}", fail, func, err.errno())) -} - -#[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 -} - -pub struct Alsa { - pub mixer: Mixer, -} - -impl Alsa { - pub fn new(font: FontRef, font_size: f32, length: u32) -> ::std::io::Result> { - let mixer = Mixer::new(CARD_NAME, true) - .map_err(|err| alsa_error_to_io_error("Failed to create ALSA mixer", &err))?; - let mixer = Self { mixer }; - mixer.get_master_volume()?; // probing - Ok(BarWidget::new_simple( - font, - font_size, - length, - Box::new(mixer), - )) - } - #[inline] - pub fn get_master(&self) -> ::std::io::Result { - let master_id = SelemId::new(SELEM_NAME, SELEM_ID); - match self.mixer.find_selem(&master_id) { - Some(master) => Ok(master), - None => { - let kind = ::std::io::ErrorKind::NotFound; - let desc = "`Master` not found"; - Err(::std::io::Error::new(kind, desc)) - } - } - } - pub fn get_master_volume(&self) -> ::std::io::Result { - let master = self.get_master()?; - let (min, max) = master.get_playback_volume_range(); - master - .get_playback_volume(SelemChannelId::mono()) - .map(|volume| alsa_volume_to_f32(volume, min, max)) - .map_err(|e| alsa_error_to_io_error("Failed to get `Master` volume", &e)) - } - pub fn set_master_volume(&self, volume: f32) -> ::std::io::Result<()> { - let master = self.get_master()?; - let (min, max) = master.get_playback_volume_range(); - let volume = f32_to_alsa_volume(volume, min, max); - master - .set_playback_volume_all(volume) - .map_err(|e| alsa_error_to_io_error("Failed to set `Master` volume", &e)) - } - pub fn inc_master_volume(&self, diff: f32) -> ::std::io::Result<()> { - let master = self.get_master()?; - let (min, max) = master.get_playback_volume_range(); - master - .get_playback_volume(SelemChannelId::mono()) - .and_then(|volume| { - let volume = alsa_volume_to_f32(volume, min, max); - let volume = f32_to_alsa_volume(volume + diff, min, max); - master.set_playback_volume_all(volume) - }) - .map_err(|e| alsa_error_to_io_error("Failed to inc/dec `Master` volume", &e)) - } -} - -impl BarWidgetImpl for Alsa { - fn wait(&mut self, _: &mut WaitContext) {} - fn name(&self) -> &str { - "volume" - } - fn value(&self) -> f32 { - match self.get_master_volume() { - Ok(v) => v, - Err(e) => panic!("{}", e), - } - } - fn color(&self) -> Color { - Color::new(1.0, 1.0, 1.0, 1.0) - } - fn inc(&mut self, diff: f32) { - if let Err(e) = self.inc_master_volume(diff) { - panic!("{}", e) - } - } - fn set(&mut self, abs: f32) { - if let Err(e) = self.set_master_volume(abs) { - panic!("{}", e) - } - } - fn toggle(&mut self) {} -} diff --git a/src/widgets/audio/mod.rs b/src/widgets/audio/mod.rs deleted file mode 100644 index 49ca3a3..0000000 --- a/src/widgets/audio/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -#[cfg(feature = "alsa-widget")] -mod alsa_sound; -#[cfg(feature = "alsa-widget")] -pub use alsa_sound::Alsa; - -#[cfg(feature = "pulseaudio-widget")] -mod pulse_sound; -#[cfg(feature = "pulseaudio-widget")] -pub use pulse_sound::PulseAudio; diff --git a/src/widgets/audio/pulse_sound.rs b/src/widgets/audio/pulse_sound.rs deleted file mode 100644 index 57af15f..0000000 --- a/src/widgets/audio/pulse_sound.rs +++ /dev/null @@ -1,544 +0,0 @@ -use crate::cmd::Cmd; -use crate::color::Color; -use crate::widget::WaitContext; -use crate::{ - fonts::FontRef, - widgets::bar_widget::{BarWidget, BarWidgetImpl}, -}; - -use std::cell::RefCell; -use std::collections::HashMap; -use std::ops::Deref; -use std::rc::Rc; -use std::sync::mpsc::{channel, Sender}; -use std::sync::{Arc, Mutex}; -use std::thread; - -use libpulse_binding::callbacks::ListResult; -use libpulse_binding::context::{ - introspect::ServerInfo, introspect::SinkInfo, subscribe::Facility, subscribe::InterestMaskSet, - subscribe::Operation as SubscribeOperation, Context, FlagSet, State as PulseState, -}; -use libpulse_binding::mainloop::standard::IterateResult; -use libpulse_binding::mainloop::standard::Mainloop; -use libpulse_binding::proplist::{properties, Proplist}; -use libpulse_binding::volume::{ChannelVolumes, Volume}; - -struct PulseAudioConnection { - mainloop: Rc>, - context: Rc>, -} - -#[derive(Clone)] -struct PulseAudioSinkInfo { - volume: ChannelVolumes, - mute: bool, -} - -struct PulseAudioClient { - sender: Sender, - default_sink: String, - sinks: HashMap, -} - -enum PulseAudioClientRequest { - GetDefaultDevice(Option>), - GetSinkInfoByIndex(Option>, u32), - GetSinkInfoByName(Option>, String), - SetSinkVolumeByName(Option>, String, ChannelVolumes), - SetSinkMuteByName(Option>, String, bool), -} - -#[derive(Debug)] -struct PulseAudioSoundDeviceInner { - name: Option, - volume: Option, - volume_avg: f32, - muted: bool, - default_sink: String, -} - -struct PulseAudioSoundDevice { - client: Arc>, - inner: Arc>, -} - -impl PulseAudioConnection { - fn new() -> Result { - let mut proplist = Proplist::new().unwrap(); - proplist - .set_str(properties::APPLICATION_NAME, "wldash") - .unwrap(); - - let mainloop = Rc::new(RefCell::new(Mainloop::new().unwrap())); - - let context = Rc::new(RefCell::new( - Context::new_with_proplist(mainloop.borrow().deref(), "wldash_context", &proplist) - .unwrap(), - )); - - context - .borrow_mut() - .connect(None, FlagSet::NOFLAGS, None) - .map_err(|_e| { - ::std::io::Error::new( - ::std::io::ErrorKind::Other, - "unable to connect to pulseaudio context", - ) - })?; - - let mut connection = PulseAudioConnection { mainloop, context }; - - // Wait for context to be ready - loop { - connection.iterate(false)?; - match connection.context.borrow().get_state() { - PulseState::Ready => { - break; - } - PulseState::Failed | PulseState::Terminated => {} - _ => {} - } - } - - Ok(connection) - } - - fn iterate(&mut self, blocking: bool) -> Result<(), ::std::io::Error> { - match self.mainloop.borrow_mut().iterate(blocking) { - IterateResult::Quit(_) | IterateResult::Err(_) => Err(::std::io::Error::new( - ::std::io::ErrorKind::Other, - "unable to iterate pulseaudio state", - )), - IterateResult::Success(_) => Ok(()), - } - } -} - -impl PulseAudioClient { - fn new(listener: F) -> Result>, ::std::io::Error> - where - F: Fn(Arc>) -> (), - F: Send + 'static + Clone, - { - let (tx, rx) = channel(); - - let client = Arc::new(Mutex::new(PulseAudioClient { - sender: tx, - default_sink: "@DEFAULT_SINK@".to_string(), - sinks: HashMap::new(), - })); - - let loop_client = client.clone(); - let (tx1, rx1) = channel(); - let _ = thread::Builder::new() - .name("pa_writer".to_string()) - .spawn(move || { - let mut conn = match PulseAudioConnection::new() { - Ok(v) => { - tx1.send(true).unwrap(); - v - } - Err(_) => { - tx1.send(false).unwrap(); - return; - } - }; - - // make sure mainloop dispatched everything - for _ in 0..10 { - conn.iterate(false).unwrap(); - } - - loop { - let cl = loop_client.clone(); - let l = listener.clone(); - match rx.recv() { - Err(_) => return, - Ok(req) => { - let mut introspector = conn.context.borrow_mut().introspect(); - - let done = Arc::new(Mutex::new(false)); - let done_reader = done.clone(); - let s = match req { - PulseAudioClientRequest::GetDefaultDevice(s) => { - introspector.get_server_info(move |info| { - *done.lock().unwrap() = true; - PulseAudioClient::server_info_callback( - cl.clone(), - l.clone(), - info, - ); - }); - s - } - PulseAudioClientRequest::GetSinkInfoByIndex(s, index) => { - introspector.get_sink_info_by_index(index, move |res| { - *done.lock().unwrap() = true; - PulseAudioClient::sink_info_callback( - cl.clone(), - l.clone(), - res, - ); - }); - s - } - PulseAudioClientRequest::GetSinkInfoByName(s, name) => { - introspector.get_sink_info_by_name(&name, move |res| { - *done.lock().unwrap() = true; - PulseAudioClient::sink_info_callback( - cl.clone(), - l.clone(), - res, - ); - }); - s - } - PulseAudioClientRequest::SetSinkVolumeByName(s, name, volumes) => { - introspector.set_sink_volume_by_name( - &name, - &volumes, - Some(Box::new(move |_| { - *done.lock().unwrap() = true; - })), - ); - s - } - PulseAudioClientRequest::SetSinkMuteByName(s, name, mute) => { - introspector.set_sink_mute_by_name( - &name, - mute, - Some(Box::new(move |_| { - *done.lock().unwrap() = true; - })), - ); - s - } - }; - - // send request and receive response - loop { - if *done_reader.lock().unwrap() { - break; - } - conn.iterate(true).unwrap(); - } - - if let Some(s) = s { - let _ = s.send(true); - } - } - } - } - }); - - // subscribe - let cl2 = client.clone(); - let (tx2, rx2) = channel(); - let _ = thread::Builder::new() - .name("pa_reader".to_string()) - .spawn(move || { - let conn = match PulseAudioConnection::new() { - Ok(v) => { - tx2.send(true).unwrap(); - v - } - Err(_) => { - tx2.send(false).unwrap(); - return; - } - }; - // subcribe for events - conn.context - .borrow_mut() - .set_subscribe_callback(Some(Box::new(move |facility, operation, index| { - cl2.lock() - .unwrap() - .subscribe_callback(facility, operation, index) - }))); - conn.context - .borrow_mut() - .subscribe(InterestMaskSet::SERVER | InterestMaskSet::SINK, |_| {}); - - conn.mainloop.borrow_mut().run().unwrap(); - }); - - let pa_writer_res = rx1.recv().unwrap(); - let pa_reader_res = rx2.recv().unwrap(); - - if !pa_writer_res || !pa_reader_res { - return Err(::std::io::Error::new( - ::std::io::ErrorKind::Other, - "unable to start pulseaudio thread", - )); - } - - Ok(client) - } - - fn send(&self, request: PulseAudioClientRequest) -> Result<(), ::std::io::Error> { - self.sender.send(request).map_err(|_e| { - ::std::io::Error::new( - ::std::io::ErrorKind::Other, - "unable to send pulseaudio request", - ) - }) - } - - fn server_info_callback(s: Arc>, listener: F, server_info: &ServerInfo) - where - F: Fn(Arc>) -> (), - F: Send + 'static, - { - match server_info.default_sink_name.clone() { - None => {} - Some(default_sink) => { - (*s.lock().unwrap()).default_sink = default_sink.into(); - listener(s); - } - } - } - - fn sink_info_callback(s: Arc>, listener: F, result: ListResult<&SinkInfo>) - where - F: Fn(Arc>) -> (), - F: Send + 'static, - { - match result { - ListResult::End | ListResult::Error => {} - ListResult::Item(sink_info) => match sink_info.name.clone() { - None => {} - Some(name) => { - let info = PulseAudioSinkInfo { - volume: sink_info.volume, - mute: sink_info.mute, - }; - s.lock().unwrap().sinks.insert(name.into(), info); - listener(s); - } - }, - } - } - - fn subscribe_callback( - &self, - facility: Option, - _operation: Option, - index: u32, - ) { - match facility { - None => {} - Some(facility) => match facility { - Facility::Server => { - let _ = self.send(PulseAudioClientRequest::GetDefaultDevice(None)); - } - Facility::Sink => { - let _ = self.send(PulseAudioClientRequest::GetSinkInfoByIndex(None, index)); - } - _ => {} - }, - } - } -} - -impl PulseAudioSoundDevice { - fn new(listener: F) -> Result - where - F: Fn() -> (), - F: Send + 'static + Clone, - { - let inner = Arc::new(Mutex::new(PulseAudioSoundDeviceInner { - name: None, - volume: None, - volume_avg: 0.0, - muted: false, - default_sink: "@DEFAULT_SINK@".to_string(), - })); - - let cb_inner = inner.clone(); - let client = PulseAudioClient::new(move |client| { - let mut inner = cb_inner.lock().unwrap(); - inner.default_sink = client.lock().unwrap().default_sink.clone(); - let name = inner - .name - .clone() - .unwrap_or_else(|| inner.default_sink.clone()); - let sink_info = match client.lock().unwrap().sinks.get(&name) { - None => return, - Some(sink_info) => (*sink_info).clone(), - }; - - inner.volume = Some(sink_info.volume); - inner.volume_avg = sink_info.volume.avg().0 as f32 / Volume::NORMAL.0 as f32; - inner.muted = sink_info.mute; - - listener(); - })?; - - let cl = client.clone(); - let (tx, rx) = channel(); - { - let cl = client.lock().unwrap(); - cl.send(PulseAudioClientRequest::GetDefaultDevice(Some(tx)))?; - } - rx.recv().unwrap(); - let name = { - let cl = client.lock().unwrap(); - cl.default_sink.to_string() - }; - (*inner.lock().unwrap()).name = Some(name.clone()); - let device = PulseAudioSoundDevice { client: cl, inner }; - let (tx, rx) = channel(); - { - let cl = client.lock().unwrap(); - cl.send(PulseAudioClientRequest::GetSinkInfoByName(Some(tx), name))?; - } - rx.recv().unwrap(); - - Ok(device) - } - - fn muted(&self) -> bool { - let inner = self.inner.lock().unwrap(); - inner.muted - } - - fn volume(&self) -> f32 { - let inner = self.inner.lock().unwrap(); - inner.volume_avg - } - - fn inc_volume(&mut self, step: f32) -> Result<(), ::std::io::Error> { - let mut inner = self.inner.lock().unwrap(); - let mut volume = match inner.volume { - Some(volume) => volume, - None => { - return Err(::std::io::Error::new( - ::std::io::ErrorKind::Other, - "unable to set volume", - )) - } - }; - - // apply step to volumes - let step = (step * Volume::NORMAL.0 as f32).round() as i32; - if step > 0 { - volume.inc_clamp(Volume(step as u32), Volume::MAX); - } else { - volume.decrease(Volume(-step as u32)); - } - - let name = inner - .name - .clone() - .unwrap_or_else(|| inner.default_sink.clone()); - - // update volumes - inner.volume = Some(volume); - inner.volume_avg = volume.avg().0 as f32 / Volume::NORMAL.0 as f32; - self.client - .lock() - .unwrap() - .send(PulseAudioClientRequest::SetSinkVolumeByName( - None, name, volume, - ))?; - Ok(()) - } - - fn set_volume(&mut self, val: f32) -> Result<(), ::std::io::Error> { - let mut inner = self.inner.lock().unwrap(); - let mut volume = match inner.volume { - Some(volume) => volume, - None => { - return Err(::std::io::Error::new( - ::std::io::ErrorKind::Other, - "unable to set volume", - )) - } - }; - - // apply step to volumes - volume.scale(Volume((val * Volume::NORMAL.0 as f32).round() as u32)); - - let name = inner - .name - .clone() - .unwrap_or_else(|| inner.default_sink.clone()); - - // update volumes - inner.volume = Some(volume); - inner.volume_avg = volume.avg().0 as f32 / Volume::NORMAL.0 as f32; - self.client - .lock() - .unwrap() - .send(PulseAudioClientRequest::SetSinkVolumeByName( - None, name, volume, - ))?; - Ok(()) - } - - fn toggle(&mut self) -> Result<(), ::std::io::Error> { - let mut inner = self.inner.lock().unwrap(); - inner.muted = !inner.muted; - self.client - .lock() - .unwrap() - .send(PulseAudioClientRequest::SetSinkMuteByName( - None, - inner - .name - .clone() - .unwrap_or_else(|| inner.default_sink.clone()), - inner.muted, - ))?; - Ok(()) - } -} - -pub struct PulseAudio { - device: PulseAudioSoundDevice, -} - -impl PulseAudio { - pub fn new( - font: FontRef, - font_size: f32, - length: u32, - sender: Sender, - ) -> Result, ::std::io::Error> { - BarWidget::new(font, font_size, length, move |dirty| { - let device = PulseAudioSoundDevice::new(move || { - *dirty.lock().unwrap() = true; - sender.send(Cmd::Draw).unwrap(); - })?; - Ok(Box::new(PulseAudio { device })) - }) - } -} - -impl BarWidgetImpl for PulseAudio { - fn wait(&mut self, _: &mut WaitContext) {} - fn name(&self) -> &str { - "volume" - } - fn value(&self) -> f32 { - self.device.volume() - } - fn color(&self) -> Color { - let muted = self.device.muted(); - if muted { - Color::new(1.0, 1.0, 0.0, 1.0) - } else { - Color::new(1.0, 1.0, 1.0, 1.0) - } - } - fn inc(&mut self, inc: f32) { - self.device.inc_volume(inc).unwrap(); - } - fn set(&mut self, val: f32) { - self.device.set_volume(val).unwrap(); - } - fn toggle(&mut self) { - self.device.toggle().unwrap(); - } -} diff --git a/src/widgets/backlight.rs b/src/widgets/backlight.rs index 8a15874..21853fd 100644 --- a/src/widgets/backlight.rs +++ b/src/widgets/backlight.rs @@ -1,21 +1,17 @@ -use crate::color::Color; -use crate::widget::WaitContext; -use crate::{ - fonts::FontRef, - widgets::bar_widget::{BarWidget, BarWidgetImpl}, +use std::{ + fs::{self, OpenOptions}, + io::{Error, ErrorKind, Write}, + path::{Path, PathBuf}, }; -use std::fs; -use std::fs::OpenOptions; -use std::io::Write; -use std::io::{Error, ErrorKind}; -use std::path::{Path, PathBuf}; +use walkdir::WalkDir; -pub struct Backlight { - device_path: PathBuf, - cur_brightness: u64, - max_brightness: u64, -} +use crate::{ + color::Color, + event::PointerButton, + fonts::FontMap, + widgets::bar_widget::{BarWidget, BarWidgetImpl}, +}; fn read_file_as_u64(path: &Path) -> Result { let mut s = fs::read_to_string(path)?; @@ -29,113 +25,81 @@ fn write_file_as_u64(path: &Path, value: u64) -> Result<(), Error> { file.write_fmt(format_args!("{}", value)) } +pub struct Backlight { + device_path: PathBuf, + cur: u64, + max: u64, + dirty: bool, +} + impl Backlight { - pub fn update(&mut self) -> Result<(), Error> { - self.cur_brightness = read_file_as_u64(self.device_path.join("brightness").as_path())?; - self.max_brightness = read_file_as_u64(self.device_path.join("max_brightness").as_path())?; - Ok(()) + 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 sync(&mut self) -> Result<(), Error> { - write_file_as_u64( - self.device_path.join("brightness").as_path(), - self.cur_brightness, - )?; - self.update()?; - Ok(()) - } - - pub fn brightness(&self) -> f32 { - if self.cur_brightness > self.max_brightness { - // what. - return 1.0; - } - - self.cur_brightness as f32 / self.max_brightness as f32 - } - - pub fn set(&mut self, val: f32) -> Result<(), Error> { - self.cur_brightness = (self.max_brightness as f32 * val) as u64; - Ok(()) - } - - pub fn add(&mut self, diff: f32) -> Result<(), Error> { - let inc = (self.max_brightness as f32 * diff) as i64; - - self.cur_brightness = if self.cur_brightness as i64 + inc < 1 { - 1 - } else if self.cur_brightness as i64 + inc > self.max_brightness as i64 { - self.max_brightness + 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 { - (self.cur_brightness as i64 + inc) as u64 + 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: device_path.expect("did not find backlight device"), + cur: 0, + max: 0, + dirty: true, }; - Ok(()) + dev.update(); + BarWidget::new(Box::new(dev), fm, font, size) } - pub fn new<'a>( - path: &str, - font: FontRef<'a>, - font_size: f32, - length: u32, - ) -> Result>, Error> { - let mut dev = Backlight { - device_path: Path::new("/sys/class/backlight").to_path_buf().join(path), - cur_brightness: 0, - max_brightness: 0, - }; + pub fn detect() -> bool { + for entry in WalkDir::new("/sys/class/backlight") { + if entry.is_ok() { + return true; + } + } + return false; + } - dev.update()?; - - Ok(BarWidget::new_simple( - font, - font_size, - length, - Box::new(dev), - )) + pub fn set(&mut self, brightness: f32) { + let val = (self.max as f32 * brightness) as u64; + 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(); } } impl BarWidgetImpl for Backlight { - fn wait(&mut self, _: &mut WaitContext) {} - fn name(&self) -> &str { + fn name(&self) -> &'static str { "backlight" } - fn value(&self) -> f32 { - self.brightness() + 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::new(1.0, 1.0, 1.0, 1.0) + Color::WHITE } - fn inc(&mut self, inc: f32) { - self.add(inc).unwrap(); - match self.sync() { - Ok(val) => val, - Err(err) => { - eprintln!("Error while trying to change brightness: {}", err); - } - } - } - fn set(&mut self, val: f32) { - self.set(val).unwrap(); - match self.sync() { - Ok(val) => val, - Err(err) => { - eprintln!("Error while trying to change brightness: {}", err); - } - } - } - fn toggle(&mut self) { - self.cur_brightness = if self.cur_brightness == 1 { - self.max_brightness - } else { - 1 + fn click(&mut self, pos: f32, btn: PointerButton) { + self.dirty = true; + match btn { + PointerButton::Left => self.set(pos), + _ => (), }; - match self.sync() { - Ok(val) => val, - Err(err) => { - eprintln!("Error while trying to change brightness: {}", err); - } - } } } diff --git a/src/widgets/bar_widget.rs b/src/widgets/bar_widget.rs index 3ca0fa3..a4adc37 100644 --- a/src/widgets/bar_widget.rs +++ b/src/widgets/bar_widget.rs @@ -1,163 +1,119 @@ -use crate::color::Color; -use crate::draw::{draw_bar, draw_box, Font}; +use std::{cmp::min, default::Default}; + use crate::{ - fonts::FontRef, - widget::{DrawContext, DrawReport, KeyState, ModifiersState, WaitContext, Widget}, + buffer::BufferView, + color::Color, + draw::{draw_bar, draw_box}, + event::{Event, PointerButton}, + fonts::FontMap, + widgets::{Geometry, Widget}, }; -use std::sync::{Arc, Mutex}; - pub trait BarWidgetImpl { - fn wait(&mut self, ctx: &mut WaitContext); - fn name(&self) -> &str; - fn value(&self) -> f32; + fn get_dirty(&self) -> bool { + false + } + fn name(&self) -> &'static str; + fn value(&mut self) -> f32; fn color(&self) -> Color; - fn inc(&mut self, inc: f32); - fn set(&mut self, val: f32); - fn toggle(&mut self); + fn click(&mut self, _pos: f32, _btn: PointerButton) {} } -pub struct BarWidget<'a> { - bar_impl: Box, - font: Font<'a>, - font_size: u32, - length: u32, - dirty: Arc>, +pub struct BarWidget { + font: &'static str, + size: f32, + geometry: Geometry, + inner_widget: Box, } -impl<'a> BarWidget<'a> { - pub fn new_simple( - font: FontRef, - font_size: f32, - length: u32, - w: Box, - ) -> Box { - let mut font = Font::new(font, font_size); - font.add_str_to_cache(w.name()); - - Box::new(BarWidget { - bar_impl: w, - dirty: Arc::new(Mutex::new(true)), +impl 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, font, - font_size: font_size as u32, - length, - }) - } - - pub fn new( - font: FontRef<'a>, - font_size: f32, - length: u32, - f: F, - ) -> Result, ::std::io::Error> - where - F: FnOnce(Arc>) -> Result, ::std::io::Error>, - F: Send + 'a + Clone, - { - let dirty = Arc::new(Mutex::new(true)); - let im = f(dirty.clone())?; - - let mut font = Font::new(font, font_size); - font.add_str_to_cache(im.name()); - - Ok(Box::new(BarWidget { - bar_impl: im, - dirty, - font, - font_size: font_size as u32, - length, - })) + size, + } } } -impl<'a> Widget for BarWidget<'a> { - fn wait(&mut self, ctx: &mut WaitContext) { - self.bar_impl.wait(ctx); +impl Widget for BarWidget { + fn geometry(&self) -> Geometry { + self.geometry } - fn enter(&mut self) {} - fn leave(&mut self) {} - fn size(&self) -> (u32, u32) { - (self.length, self.font_size) + + fn get_dirty(&self) -> bool { + self.inner_widget.get_dirty() } - fn draw( - &mut self, - ctx: &mut DrawContext, - pos: (u32, u32), - expansion: (u32, u32), - ) -> Result { - if self.length == 0 { - self.length = expansion.0; - } - let (width, height) = (self.length, self.font_size); - { - let mut d = self.dirty.lock().unwrap(); - if !*d && !ctx.force { - return Ok(DrawReport::empty(width, height)); - } - *d = false; - } - let buf = &mut ctx.buf.subdimensions((pos.0, pos.1, width, height))?; - buf.memset(ctx.bg); + 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 c = Color::new(1.0, 1.0, 1.0, 1.0); - self.font.draw_text(buf, ctx.bg, &c, self.bar_impl.name())?; + 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(); - let c = self.bar_impl.color(); - let bar_off = 5 * self.font_size; - let mut val = self.bar_impl.value(); + let c = self.inner_widget.color(); draw_bar( - &mut buf.offset((bar_off, 0))?, - &c, - width - bar_off, - self.font_size, + &mut view.offset((bar_offset, 0)), + c, + self.geometry.width - bar_offset, + size, val, - )?; - let mut iter = 1.0; - while val > 1.0 { - let c = &Color::new(0.75 / iter, 0.25 / iter, 0.25 / iter, 1.0); - val -= 1.0; - iter += 1.0; - draw_bar( - &mut buf.offset((bar_off, 0))?, - &c, - width - bar_off, - self.font_size, - val, - )?; - } + ) + .unwrap(); + draw_box( - &mut buf.offset((bar_off, 0))?, - &c, - (width - bar_off, self.font_size), - )?; - Ok(DrawReport { - width, - height, - damage: vec![buf.get_signed_bounds()], - full_damage: false, - }) + &mut view.offset((bar_offset, 0)), + c, + (self.geometry.width - bar_offset, size), + ) + .unwrap(); + self.geometry } - fn keyboard_input(&mut self, _: u32, _: ModifiersState, _: KeyState, _: Option) {} - fn mouse_click(&mut self, button: u32, (x, _): (u32, u32)) { - *self.dirty.lock().unwrap() = true; - match button { - 272 => { - let offset = 5 * self.font_size; - if x > offset { - self.bar_impl - .set(((x - offset) + 1) as f32 / (self.length - offset) as f32); + 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: self.size.ceil() as u32, + }; + 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) => { + 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); } } - 273 => self.bar_impl.toggle(), - x => { - dbg!(x); - } + _ => (), } } - fn mouse_scroll(&mut self, (_, y): (f64, f64), _: (u32, u32)) { - *self.dirty.lock().unwrap() = true; - self.bar_impl.inc(y as f32 / -800.0); - } } diff --git a/src/widgets/battery.rs b/src/widgets/battery.rs index 94e4eca..61121e6 100644 --- a/src/widgets/battery.rs +++ b/src/widgets/battery.rs @@ -1,240 +1,177 @@ -use crate::cmd::Cmd; -use crate::color::Color; -use crate::widget::WaitContext; +use std::{ + error::Error, + sync::{Arc, Mutex}, + thread, + time::Duration, +}; + +use dbus::blocking::{ + stdintf::org_freedesktop_dbus::{Properties, PropertiesPropertiesChanged}, + LocalConnection, +}; + use crate::{ - fonts::FontRef, + color::Color, + event::{Event, Events}, + fonts::FontMap, widgets::bar_widget::{BarWidget, BarWidgetImpl}, }; -use std::sync::mpsc::Sender; -use std::sync::{Arc, Mutex}; - -use nix::poll::{PollFd, PollFlags}; - -fn get_upower_property( - con: &dbus::Connection, - device_path: &str, - property: &str, -) -> Result { - let msg = dbus::Message::new_method_call( - "org.freedesktop.UPower", - device_path, - "org.freedesktop.DBus.Properties", - "Get", - ) - .map_err(|_| { - ::std::io::Error::new( - ::std::io::ErrorKind::Other, - "could not send make dbus method call", - ) - })? - .append2( - dbus::MessageItem::Str("org.freedesktop.UPower.Device".to_string()), - dbus::MessageItem::Str(property.to_string()), - ); - - con.send_with_reply_and_block(msg, 1000).map_err(|_| { - ::std::io::Error::new(::std::io::ErrorKind::Other, "could not send dbus message") - }) -} - -// The widget is created on a different thread than where it will be used, so -// we need it to be Send. However, dbus::Connection has a void*, so it's not -// auto-derived. So, we make a small wrapper where we make it Send. -struct DbusConnection(dbus::Connection); - -impl std::convert::AsRef for DbusConnection { - fn as_ref(&self) -> &dbus::Connection { - &self.0 - } -} - -unsafe impl Send for DbusConnection {} - -pub struct UpowerBattery { - device_path: String, - sender: Sender, - dirty: Arc>, - state: UpowerBatteryState, - capacity: f64, - con: DbusConnection, - watch: dbus::Watch, -} - enum UpowerBatteryState { + Unknown, Charging, Discharging, Empty, - Full, - NotCharging, - Unknown, + FullyCharged, + PendingCharge, + PendingDischarge, } -impl UpowerBattery { - pub fn from_device( - dirty: Arc>, - sender: Sender, - device: &str, - ) -> Result { - let con = dbus::Connection::get_private(dbus::BusType::System).map_err(|_| { - ::std::io::Error::new(::std::io::ErrorKind::Other, "unable to open dbus") - })?; - let device_path = format!("/org/freedesktop/UPower/devices/{}", device); - - let rule = format!( - "type='signal',\ - path='{}',\ - interface='org.freedesktop.DBus.Properties',\ - member='PropertiesChanged'", - &device_path - ); - con.add_match(&rule).map_err(|_| { - ::std::io::Error::new( - ::std::io::ErrorKind::Other, - "unable to add match rule to dbus connection", - ) - })?; - let upower_type: dbus::arg::Variant = - match get_upower_property(&con, &device_path, "Type")?.get1() { - Some(v) => v, - None => { - return Err(::std::io::Error::new( - ::std::io::ErrorKind::Other, - "no such upower device", - )); - } - }; - - // https://upower.freedesktop.org/docs/Device.html#Device:Type - if upower_type.0 != 2 { - return Err(::std::io::Error::new( - ::std::io::ErrorKind::Other, - "UPower device is not a battery.", - )); +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, } - - let capacity: f64 = match get_upower_property(&con, &device_path, "Percentage")? - .get1::>() - { - Some(v) => v.0, - None => { - return Err(::std::io::Error::new( - ::std::io::ErrorKind::Other, - "no such upower device", - )); - } - }; - let state: UpowerBatteryState = match get_upower_property(&con, &device_path, "State")? - .get1::>() - { - Some(v) => match v.0 { - 1 => UpowerBatteryState::Charging, - 2 => UpowerBatteryState::Discharging, - 3 => UpowerBatteryState::Empty, - 4 => UpowerBatteryState::Full, - 5 => UpowerBatteryState::NotCharging, - 6 => UpowerBatteryState::Discharging, - _ => UpowerBatteryState::Unknown, - }, - None => { - return Err(::std::io::Error::new( - ::std::io::ErrorKind::Other, - "no such upower device", - )); - } - }; - - let fds = con.watch_fds(); - if fds.len() != 1 { - return Err(::std::io::Error::new( - ::std::io::ErrorKind::Other, - "expected 1 watch fd from dbus", - )); - } - - Ok(UpowerBattery { - device_path, - con: DbusConnection(con), - dirty, - sender, - capacity, - state, - watch: fds[0], - }) - } - - pub fn new( - font: FontRef, - font_size: f32, - length: u32, - sender: Sender, - ) -> Result, ::std::io::Error> { - BarWidget::new(font, font_size, length, move |dirty| { - let d = UpowerBattery::from_device(dirty, sender, "DisplayDevice")?; - Ok(Box::new(d)) - }) } } -impl BarWidgetImpl for UpowerBattery { - fn wait(&mut self, ctx: &mut WaitContext) { - for _ in self - .con - .as_ref() - .watch_handle(self.watch.fd(), dbus::WatchEvent::Readable as u32) - { - let capacity = get_upower_property(self.con.as_ref(), &self.device_path, "Percentage") - .unwrap() - .get1::>() - .unwrap() - .0; - let state = match get_upower_property(self.con.as_ref(), &self.device_path, "State") - .unwrap() - .get1::>() - .unwrap() - .0 +struct InnerBattery { + value: f32, + state: UpowerBatteryState, + 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()) + .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"); + { - 1 => UpowerBatteryState::Charging, - 2 => UpowerBatteryState::Discharging, - 3 => UpowerBatteryState::Empty, - 4 => UpowerBatteryState::Full, - 5 => UpowerBatteryState::NotCharging, - 6 => UpowerBatteryState::Discharging, - _ => UpowerBatteryState::Unknown, - }; - self.state = state; - self.capacity = capacity; - *self.dirty.lock().unwrap() = true; - self.sender.send(Cmd::Draw).unwrap(); - } + let mut inner = inner.lock().unwrap(); + inner.value = capacity as f32 / 100.; + inner.state = UpowerBatteryState::from_dbus(state as u64); + } - ctx.fds - .push(PollFd::new(self.watch.fd(), PollFlags::POLLIN)); + 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 = + UpowerBatteryState::from_dbus(value.0.as_u64().unwrap()); + 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(); + + loop { + conn.process(Duration::from_millis(60000)).unwrap(); + } + }) + .unwrap(); +} + +pub struct Battery { + inner: Arc>, +} + +impl Battery { + 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, + dirty: false, + })), + }; + start_monitor(battery.inner.clone(), events); + BarWidget::new(Box::new(battery), fm, font, size) } - fn name(&self) -> &str { + + pub fn detect() -> bool { + match get_battery_state() { + Ok(_) => true, + Err(_) => false, + } + } +} + +impl BarWidgetImpl for Battery { + fn get_dirty(&self) -> bool { + self.inner.lock().unwrap().dirty + } + fn name(&self) -> &'static str { "battery" } - fn value(&self) -> f32 { - (self.capacity as f32) / 100.0 + fn value(&mut self) -> f32 { + let mut inner = self.inner.lock().unwrap(); + inner.dirty = false; + inner.value } fn color(&self) -> Color { - match self.state { - UpowerBatteryState::Discharging | UpowerBatteryState::Unknown => { - if self.capacity > 10.0 { - 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) - } + let inner = self.inner.lock().unwrap(); + match inner.state { + UpowerBatteryState::Charging | UpowerBatteryState::FullyCharged => Color::LIGHTGREEN, + _ if inner.value > 0.25 => Color::WHITE, + _ if inner.value > 0.1 => Color::DARKORANGE, + _ => Color::RED, } } - fn inc(&mut self, _: f32) {} - fn set(&mut self, _: f32) {} - fn toggle(&mut self) {} } diff --git a/src/widgets/calendar.rs b/src/widgets/calendar.rs index 63e2407..ff3c30b 100644 --- a/src/widgets/calendar.rs +++ b/src/widgets/calendar.rs @@ -1,37 +1,114 @@ -use crate::buffer::Buffer; -use crate::color::Color; -use crate::draw::Font; +use std::default::Default; + use crate::{ - fonts::FontRef, - widget::{DrawContext, DrawReport, KeyState, ModifiersState, WaitContext, Widget}, + buffer::BufferView, + color::Color, + event::Event, + fonts::FontMap, + widgets::{Geometry, Widget}, }; -use chrono::{Datelike, NaiveDate, NaiveDateTime}; +use chrono::{Datelike, Local, NaiveDate}; -pub struct Calendar<'a> { - cur_date: NaiveDate, +pub struct Calendar { + font: &'static str, + month_font: &'static str, + sections_x: i32, + sections_y: i32, + shown_sections_x: u32, + shown_sections_y: u32, + size: f32, dirty: bool, - offset: f64, - sections: u32, - font_size: u32, - calendar_cache: Font<'a>, - month_cache: Font<'a>, - year_cache: Font<'a>, - day_cache: Font<'a>, + geometry: Geometry, + year_width: u32, + date_width: u32, + day_width: u32, } -impl<'a> Calendar<'a> { - fn draw_month( - &self, - buf: &mut Buffer, - background_color: &Color, - orig: NaiveDate, - time: NaiveDate, - ) -> Result<(i32, i32, i32, i32), ::std::io::Error> { - let mut time = time; - let mut y_off = 1; - let mut done = false; +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, + 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, + "ADFJMNOSabcehgilmnoprstuvy", + ); + + Calendar { + font, + month_font, + size, + sections_x, + sections_y, + shown_sections_x: 0, + shown_sections_y: 0, + dirty: true, + geometry: Default::default(), + date_width: 0, + day_width: 0, + year_width: 0, + } + } + + fn draw_month( + &mut self, + fonts: &mut FontMap, + mut view: &mut BufferView, + orig: NaiveDate, + mut time: NaiveDate, + ) { + 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, 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 * date_offset + day_offset, + (self.size * MONTH_FACTOR).ceil() as u32 + + (self.size * DAY_FACTOR * 0.2).ceil() as u32, + )), + c, + &wk_chr, + ) + .unwrap(); + } + + // + // Draw the month + // + let month_font = fonts.get_font(self.month_font, self.size * MONTH_FACTOR); let month_str = match time.month() { 1 => "January", 2 => "February", @@ -47,234 +124,228 @@ impl<'a> Calendar<'a> { 12 => "December", _ => panic!("impossible value"), }; + month_font + .draw_text(&mut view, Color::WHITE, month_str) + .unwrap(); // - // Draw the of the month + // Draw the year // - - self.month_cache.draw_text( - buf, - background_color, - &Color::new(1.0, 1.0, 1.0, 1.0), - month_str, - )?; if time.year() != orig.year() { - self.year_cache.draw_text( - &mut buf.offset((self.font_size * 20, 0))?, - background_color, - &Color::new(0.8, 0.8, 0.8, 1.0), - &format!("{:}", time.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(); } - // - // Draw the week day - // - - for idx in 1..8 { - let wk_chr = match idx { - 1 => "MON", - 2 => "TUE", - 3 => "WED", - 4 => "THU", - 5 => "FRI", - 6 => "SAT", - 7 => "SUN", - _ => panic!("impossible value"), - }; - - self.day_cache.draw_text( - &mut buf.offset(( - idx * self.font_size * 3 + self.font_size / 5, - y_off * self.font_size * 2 + self.font_size * 4, - ))?, - background_color, - &Color::new(1.0, 1.0, 1.0, 1.0), - &wk_chr, - )?; - } - - y_off += 1; - - 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(); - } - + 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(); - self.calendar_cache.draw_text( - &mut buf.offset((0, y_off * self.font_size * 2 + self.font_size * 4))?, - background_color, - &Color::new(0.75, 0.75, 0.75, 1.0), - &format!("{:02}", wk.week()), - )?; - x_pos += 1; + cal_font + .draw_text( + &mut view.offset(( + 0, + 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 + }, + &format!("{:02}", wk.week()), + ) + .unwrap(); // // 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::new(1.0, 1.0, 1.0, 1.0) + Color::WHITE + } else if x_pos < 6 { + Color::GREY50 } else { - Color::new(0.5, 0.5, 0.5, 1.0) + Color::GREY35 }; - self.calendar_cache.draw_text( - &mut buf.offset(( - x_pos * self.font_size * 3, - y_off * self.font_size * 2 + self.font_size * 4, - ))?, - background_color, - &c, - &format!("{:02}", time.day()), - )?; + cal_font + .draw_text( + &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()), + ) + .unwrap(); 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; } - Ok(buf.get_signed_bounds()) } } -impl<'a> Calendar<'a> { - pub fn new( - time: NaiveDateTime, - font_primary: FontRef<'a>, - font_secondary: FontRef<'a>, - font_size: f32, - sections: u32, - ) -> Box> { - let mut calendar_cache = Font::new(font_secondary, font_size * 2.0); - calendar_cache.add_str_to_cache("0123456789"); - let mut month_cache = Font::new(font_primary, font_size * 4.0); - month_cache.add_str_to_cache("JanuryFebMchApilJgstSmOoNvD"); - let mut year_cache = Font::new(font_primary, font_size * 1.5); - year_cache.add_str_to_cache("-0123456789"); - let mut day_cache = Font::new(font_primary, font_size); - day_cache.add_str_to_cache("MONTUEWDHFRISA"); - Box::new(Calendar { - cur_date: time.date(), - dirty: true, - offset: 0.0, - sections, - font_size: font_size as u32, - calendar_cache, - month_cache, - year_cache, - day_cache, - }) - } -} - -impl<'a> Widget for Calendar<'a> { - fn wait(&mut self, _: &mut WaitContext) {} - fn enter(&mut self) {} - fn leave(&mut self) {} - - fn size(&self) -> (u32, u32) { - let cal_width = 7 * self.font_size * 3 + self.font_size * 2; - let cal_pad = self.font_size * 3; - ( - cal_width * self.sections + cal_pad * (self.sections - 1), - (self.font_size as f32 * 21.5) as u32, - ) +impl<'a> Widget for Calendar { + fn get_dirty(&self) -> bool { + self.dirty } - fn draw( - &mut self, - ctx: &mut DrawContext, - pos: (u32, u32), - _expansion: (u32, u32), - ) -> Result { - let (width, height) = self.size(); - if ctx.time.date() == self.cur_date && !ctx.force && !self.dirty { - return Ok(DrawReport::empty(width, height)); + fn geometry(&self) -> Geometry { + self.geometry + } + + fn event(&mut self, event: &Event) { + match event { + Event::NewMinute => { + self.dirty = true; + } + _ => (), } - self.dirty = false; - self.cur_date = ctx.time.date(); + } + + 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 * 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 buf = &mut ctx.buf.subdimensions((pos.0, pos.1, width, height))?; - buf.memset(ctx.bg); - let time = ctx.time.date(); let mut t = time.with_day(1).unwrap(); - let o = (self.offset / 100.0) as i32; - - let cals = self.sections - 1; - let pre_cals = cals / 2; - for _ in 0..pre_cals { + let cals = self.shown_sections_y * self.shown_sections_x; + if cals >= 3 { t = t.pred_opt().unwrap().with_day(1).unwrap(); } - if o != 0 { - let mut month = (t.month() - 1) as i32 + o; - let mut year = t.year(); - while month > 11 { - year += 1; - month -= 12; + 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)), + 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() + }; } - while month < 0 { - year -= 1; - month += 12; - } - t = t - .with_year(year) - .unwrap() - .with_month((month + 1) as u32) - .unwrap(); } - let cal_pad = 7 * self.font_size * 3 + self.font_size * 5; - for idx in 0..self.sections { - self.draw_month(&mut buf.offset((cal_pad * idx, 0))?, ctx.bg, time, t)?; + self.dirty = false; + self.geometry + } - t = if t.month() == 12 { - t.with_year(t.year() + 1).unwrap().with_month(1).unwrap() + 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 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.shown_sections_x = 0; + self.shown_sections_y = 0; + 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 { - t.with_month(t.month() + 1).unwrap() + possible_sections_x }; - } - Ok(DrawReport { - width, - height, - damage: vec![buf.get_signed_bounds()], - full_damage: false, - }) + 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 + }; + + self.geometry = Geometry { + x: geometry.x, + y: geometry.y, + 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 keyboard_input(&mut self, _: u32, _: ModifiersState, _: KeyState, _: Option) {} - fn mouse_click(&mut self, _: u32, (x, _): (u32, u32)) { - let cal_pad = 7 * self.font_size * 3 + self.font_size * 5; - let cals = self.sections - 1; - let pre_cals = cals / 2; - if x < pre_cals * cal_pad { - self.offset -= 100.0; - } else if x >= (pre_cals + 1) * cal_pad { - self.offset += 100.0; - } else { - self.offset = 0.0; + 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, } - self.dirty = true; - } - fn mouse_scroll(&mut self, (_, y): (f64, f64), _: (u32, u32)) { - self.offset += y; - self.dirty = true; } } diff --git a/src/widgets/clock.rs b/src/widgets/clock.rs index 7704b6a..9f932ce 100644 --- a/src/widgets/clock.rs +++ b/src/widgets/clock.rs @@ -1,92 +1,96 @@ -use crate::color::Color; -use crate::draw::Font; +use std::default::Default; + use crate::{ - fonts::FontRef, - widget::{DrawContext, DrawReport, KeyState, ModifiersState, WaitContext, Widget}, + buffer::BufferView, + color::Color, + event::Event, + fonts::FontMap, + widgets::{Geometry, Widget}, }; -use chrono::{Duration, NaiveDateTime, Timelike}; +use chrono::{Local, Timelike}; -pub struct Clock<'a> { - cur_time: NaiveDateTime, - clock_cache: Font<'a>, +pub struct Clock { + font: &'static str, size: f32, - digit: u32, - colon: u32, + dirty: bool, + geometry: Geometry, + digit_width: u32, + colon_width: u32, } -impl<'a> Clock<'a> { - pub fn new(time: NaiveDateTime, font: FontRef, size: f32) -> ::std::io::Result> { - let mut clock_cache = Font::new(font, size); - clock_cache.add_str_to_cache("0123456789:"); - - let digit = clock_cache.auto_widest("123456789")?; - let colon = clock_cache.auto_widest(":")?; - - Ok(Box::new(Clock { - cur_time: time, - clock_cache, +impl<'a> Clock { + pub fn new(fm: &mut FontMap, font: &'static str, size: f32) -> Clock { + fm.queue_font(font, size, "0123456789: "); + Clock { + font, size, - digit, - colon, - })) - } -} - -impl<'a> Widget for Clock<'a> { - fn wait(&mut self, ctx: &mut WaitContext) { - let target = (self.cur_time + Duration::seconds(60)) - .with_second(0) - .unwrap() - .with_nanosecond(0) - .unwrap(); - ctx.set_time(target); - } - fn enter(&mut self) {} - fn leave(&mut self) {} - fn size(&self) -> (u32, u32) { - (self.digit * 4 + self.colon, self.size.ceil() as u32) - } - - fn draw( - &mut self, - ctx: &mut DrawContext, - pos: (u32, u32), - _expansion: (u32, u32), - ) -> Result { - let (width, height) = self.size(); - if !(ctx.time.date() != self.cur_time.date() - || ctx.time.hour() != self.cur_time.hour() - || ctx.time.minute() != self.cur_time.minute() - || ctx.force) - { - return Ok(DrawReport::empty(width, height)); + dirty: true, + geometry: Default::default(), + digit_width: 0, + colon_width: 0, } + } +} - self.cur_time = ctx.time; - - let buf = &mut ctx.buf.subdimensions((pos.0, pos.1, width, height))?; - buf.memset(ctx.bg); - - let digit = self.digit; - let colon = self.colon; - self.clock_cache.draw_text_fixed_width( - buf, - ctx.bg, - &Color::new(1.0, 1.0, 1.0, 1.0), - &[digit, digit, colon, digit, digit], - &format!("{:02}:{:02}", ctx.time.hour(), ctx.time.minute()), - )?; - - Ok(DrawReport { - width, - height, - damage: vec![(pos.0 as i32, pos.1 as i32, width as i32, height as i32)], - full_damage: false, - }) +impl<'a> Widget for Clock { + fn get_dirty(&self) -> bool { + self.dirty } - fn keyboard_input(&mut self, _: u32, _: ModifiersState, _: KeyState, _: Option) {} - fn mouse_click(&mut self, _: u32, _: (u32, u32)) {} - fn mouse_scroll(&mut self, _: (f64, f64), _: (u32, u32)) {} + 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(); + let fg = Color::WHITE; + + 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, + fg, + &[digit, digit, colon, digit, digit], + &format!("{:02}:{:02}", time.hour(), time.minute()), + ) + .unwrap(); + self.dirty = false; + self.geometry + } + + fn geometry_update(&mut self, fonts: &mut FontMap, geometry: &Geometry) -> Geometry { + 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: self.size.ceil() as u32, + }; + 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 0bdca7f..2dce5c9 100644 --- a/src/widgets/date.rs +++ b/src/widgets/date.rs @@ -1,95 +1,92 @@ -use crate::color::Color; -use crate::draw::Font; +use std::default::Default; + use crate::{ - fonts::FontRef, - widget::{DrawContext, DrawReport, KeyState, ModifiersState, WaitContext, Widget}, + buffer::BufferView, + color::Color, + event::Event, + fonts::FontMap, + widgets::{Geometry, Widget}, }; -use chrono::{Datelike, NaiveDateTime}; +use chrono::{Datelike, Local}; -pub struct Date<'a> { - cur_time: NaiveDateTime, - date_cache: Font<'a>, +pub struct Date { + font: &'static str, size: f32, - ch_width: u32, - digit_width: u32, - spacing_width: u32, + dirty: bool, + geometry: Geometry, } -impl<'a> Date<'a> { - pub fn new(time: NaiveDateTime, font: FontRef, size: f32) -> ::std::io::Result> { - let mut date_cache = Font::new(font, size); - let chs = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - let digits = "0123456789,/ "; - let spacings = ",/ "; - - date_cache.add_str_to_cache(chs); - date_cache.add_str_to_cache(digits); - date_cache.add_str_to_cache(spacings); - let ch_width = date_cache.auto_widest(chs)?; - let digit_width = date_cache.auto_widest(digits)?; - let spacing_width = date_cache.auto_widest(spacings)?; - - Ok(Box::new(Date { - cur_time: time, - date_cache, +impl Date { + pub fn new(fm: &mut FontMap, font: &'static str, size: f32) -> Date { + fm.queue_font(font, size, "0123456789/, adehinortuFMSTW"); + Date { + font, size, - ch_width, - digit_width, - spacing_width, - })) - } -} - -impl<'a> Widget for Date<'a> { - fn wait(&mut self, _: &mut WaitContext) {} - fn enter(&mut self) {} - fn leave(&mut self) {} - fn size(&self) -> (u32, u32) { - ( - (3 * self.ch_width + 8 * self.digit_width + 4 * self.spacing_width) as u32, - self.size.ceil() as u32, - ) - } - - fn draw( - &mut self, - ctx: &mut DrawContext, - pos: (u32, u32), - _expansion: (u32, u32), - ) -> Result { - let (width, height) = self.size(); - - if !(ctx.time.date() != self.cur_time.date() || ctx.force) { - return Ok(DrawReport::empty(width, height)); + dirty: true, + geometry: Default::default(), } + } +} - self.cur_time = ctx.time; - - let buf = &mut ctx.buf.subdimensions((pos.0, pos.1, width, height))?; - buf.memset(ctx.bg); - self.date_cache.draw_text( - buf, - ctx.bg, - &Color::new(1.0, 1.0, 1.0, 1.0), - &format!( - "{:?}, {:02}/{:02}/{:4}", - ctx.time.weekday(), - ctx.time.day(), - ctx.time.month(), - ctx.time.year() - ), - )?; - - Ok(DrawReport { - width, - height, - damage: vec![(pos.0 as i32, pos.1 as i32, width as i32, height as i32)], - full_damage: false, - }) +impl<'a> Widget for Date { + fn get_dirty(&self) -> bool { + self.dirty } - fn keyboard_input(&mut self, _: u32, _: ModifiersState, _: KeyState, _: Option) {} - fn mouse_click(&mut self, _: u32, _: (u32, u32)) {} - fn mouse_scroll(&mut self, _: (f64, f64), _: (u32, u32)) {} + 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(); + let fg = Color::WHITE; + let font = fonts.get_font(self.font, self.size); + font.draw_text( + view, + fg, + &format!( + "{}, {:02}/{:02}/{:4}", + time.weekday(), + time.day(), + time.month(), + time.year() + ), + ) + .unwrap(); + self.dirty = false; + self.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: font.height().ceil() as u32, + }; + 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 5e9c5dc..322d5e3 100644 --- a/src/widgets/launcher.rs +++ b/src/widgets/launcher.rs @@ -1,232 +1,40 @@ -use crate::buffer::Buffer; -use crate::cmd::Cmd; -use crate::color::Color; -use crate::desktop::{load_desktop_files, Desktop}; -use crate::draw::Font; -use crate::{ - fonts::FontRef, - widget::{DrawContext, DrawReport, KeyState, ModifiersState, WaitContext, Widget}, +use std::{ + cmp::{max, min, Ordering}, + collections::HashMap, + default::Default, + fs::{read_to_string, File}, + io::Write, + process::{exit, Command}, + sync::{Arc, Mutex}, + thread, }; -use std::cell::RefCell; -use std::cmp::Ordering; -use std::collections::HashMap; -use std::process::Command; -use std::sync::mpsc::Sender; +use crate::{ + buffer::BufferView, + color::Color, + event::{Event, Events, PointerButton, PointerEvent}, + fonts::FontMap, + keyboard::{keysyms, KeyEvent}, + utils::{ + desktop::{load_desktop_cache, load_desktop_files, write_desktop_cache, Desktop}, + xdg, + }, + widgets::{Geometry, Widget}, +}; -use crate::data::Data; -use crate::keyboard::keysyms; use fuzzy_matcher::skim::SkimMatcherV2; use fuzzy_matcher::FuzzyMatcher; use unicode_segmentation::UnicodeSegmentation; - -pub struct Launcher<'a> { - cursor: usize, - options: Vec, - term_opener: String, - app_opener: String, - url_opener: String, - matches: Vec, - input: String, - result: Option, - offset: usize, - font: RefCell>, - font_size: u32, - length: u32, - dirty: bool, - tx: Sender, - counter: Data, -} - -impl<'a> Launcher<'a> { - pub fn new( - font: FontRef, - font_size: f32, - length: u32, - listener: Sender, - app: String, - term: String, - url: String, - ) -> Box { - Box::new(Launcher { - cursor: 0, - options: load_desktop_files(), - term_opener: term, - app_opener: app, - url_opener: url, - matches: vec![], - input: "".to_string(), - result: None, - offset: 0, - font: RefCell::new(Font::new(font, font_size)), - font_size: font_size as u32, - length, - dirty: true, - tx: listener, - counter: Data::load().unwrap_or_default(), - }) - } - - fn draw_launcher( - &self, - buf: &mut Buffer, - bg: &Color, - width: u32, - ) -> Result<(), ::std::io::Error> { - let mut x_off = if !self.input.is_empty() { - let c = if self.matches.is_empty() { - Color::new(1.0, 0.5, 0.5, 1.0) - } else { - Color::new(1.0, 1.0, 1.0, 1.0) - }; - - let dim = self.font.borrow_mut().auto_draw_text_with_cursor( - buf, - bg, - &c, - &self.input, - self.cursor, - )?; - - dim.0 + self.font_size / 4 - } else { - 0 - }; - - let mut width_remaining: i32 = (width - x_off) as i32; - let fuzzy_matcher = SkimMatcherV2::default(); - for (idx, m) in self.matches.iter().enumerate() { - let mut b = match buf.offset((x_off, 0)) { - Ok(b) => b, - Err(_) => break, - }; - let size = if idx == self.offset { - let (_, indices) = fuzzy_matcher - .fuzzy_indices(&m.name.to_lowercase(), &self.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, 1.0, 1.0, 1.0)); - } else { - colors.push(Color::new(0.75, 0.75, 0.75, 1.0)); - } - } - self.font - .borrow_mut() - .auto_draw_text_individual_colors(&mut b, bg, &colors, &m.name)? - } else { - self.font.borrow_mut().auto_draw_text( - &mut b, - bg, - &Color::new(0.5, 0.5, 0.5, 1.0), - &m.name, - )? - }; - - x_off += size.0 + self.font_size / 2; - width_remaining -= (size.0 + self.font_size / 2) as i32; - - if width_remaining < 0 { - break; - } - } - - Ok(()) - } - - fn draw_calc(&self, buf: &mut Buffer, bg: &Color) -> Result<(), ::std::io::Error> { - let x_off = self - .font - .borrow_mut() - .auto_draw_text(buf, bg, &Color::new(1.0, 1.0, 0.0, 1.0), "=")? - .0 - + self.font_size / 4; - - let x_off = if !self.input.is_empty() { - let dim = self.font.borrow_mut().auto_draw_text_with_cursor( - &mut buf.offset((x_off, 0))?, - bg, - &Color::new(1.0, 1.0, 1.0, 1.0), - &self.input[1..], - self.cursor - 1, - )?; - - x_off + dim.0 + self.font_size / 4 - } else { - 0 - }; - - if let Some(result) = &self.result { - self.font.borrow_mut().auto_draw_text( - &mut buf.offset((x_off, 0))?, - bg, - &Color::new(0.75, 0.75, 0.75, 1.0), - &format!(" = {:}", result), - )?; - } - - Ok(()) - } - - fn draw_shell(&self, buf: &mut Buffer, bg: &Color) -> Result<(), ::std::io::Error> { - let x_off = self - .font - .borrow_mut() - .auto_draw_text( - &mut buf.offset((0, 0))?, - bg, - &Color::new(1.0, 1.0, 0.0, 1.0), - "!", - )? - .0 - + self.font_size / 4; - - if !self.input.is_empty() { - self.font.borrow_mut().auto_draw_text_with_cursor( - &mut buf.offset((x_off, 0))?, - bg, - &Color::new(1.0, 1.0, 1.0, 1.0), - &self.input[1..], - self.cursor - 1, - )?; - }; - - Ok(()) - } -} - -fn calc(s: &str) -> Result { - rcalc_lib::parse::eval(s, &mut rcalc_lib::parse::CalcState::new()) - .map(|x| format!("{}", x)) - .map_err(|x| format!("{}", x)) -} - -fn wlcopy(s: &str) -> Result<(), String> { - let mut child = std::process::Command::new("wl-copy") - .stdin(std::process::Stdio::null()) - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) - .arg(s) - .spawn() - .map_err(|_| "wl-copy not available".to_string())?; - child - .wait() - .map_err(|_| "unable to run wl-copy".to_string())?; - Ok(()) -} +use wayland_client::{protocol::wl_keyboard, WEnum}; struct Matcher { matches: HashMap, - counter: Data, } impl Matcher { - fn new(counter: Data) -> Self { + fn new() -> Self { Self { matches: HashMap::new(), - counter, } } @@ -254,16 +62,9 @@ impl Matcher { .collect::>(); m.sort_by(|(ma1, d1), (ma2, d2)| { - let count1 = self.counter.entries.get(&d1.name).unwrap_or(&0); - let count2 = self.counter.entries.get(&d2.name).unwrap_or(&0); - - if ma1 + count1 > ma2 + count2 { + if ma1 > ma2 { Ordering::Less - } else if ma1 + count1 < ma2 + count2 { - Ordering::Greater - } else if d1.name.len() < d2.name.len() { - Ordering::Less - } else if d1.name.len() > d2.name.len() { + } else if ma1 < ma2 { Ordering::Greater } else { d1.cmp(d2) @@ -274,236 +75,709 @@ impl Matcher { } } -impl<'a> Widget for Launcher<'a> { - fn wait(&mut self, _: &mut WaitContext) {} - fn enter(&mut self) {} - fn leave(&mut self) { - self.input = "".to_string(); - self.cursor = 0; - self.offset = 0; - self.result = None; - self.dirty = true; +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 size(&self) -> (u32, u32) { - (self.length, self.font_size) + 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 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 { + "!" => { + 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.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 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; + } + } + + fn home(&mut self) { + self.cursor = 0; + } + + fn end(&mut self) { + self.cursor = self.input.len(); + } +} + +trait InterfaceWidget { + fn trigger(&mut self, intf: &mut 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, + launch_cmd: Option, +} + +impl Launcher { + fn new(events: Arc>, launch_cmd: Option) -> Launcher { + let options = Arc::new(Mutex::new(Vec::new())); + { + let options = Arc::clone(&options); + thread::Builder::new() + .name("desktopini".to_string()) + .spawn(move || { + let mut options = options.lock().unwrap(); + *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); + }) + .unwrap(); + } + + Launcher { + next_token: None, + options: options, + matches: Vec::new(), + launch_cmd, + } + } + + 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.arg("-c"); + if let Some(l) = &self.launch_cmd { + cmd.arg(format!("{} {}", l, cmdline)); + } else { + cmd.arg(cmdline); + } + cmd.spawn().unwrap(); + exit(0); + } +} + +impl InterfaceWidget for Launcher { + 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 { + let exec = exec + .replace("%f", "") + .replace("%F", "") + .replace("%u", "") + .replace("%U", ""); + + self.exec(&exec); + } + } + } + + 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, - ctx: &mut DrawContext, - pos: (u32, u32), - expansion: (u32, u32), - ) -> Result { - if self.length == 0 { - self.length = expansion.0; - } - let (width, height) = (self.length, self.font_size); - if !self.dirty && !ctx.force { - return Ok(DrawReport::empty(width, height)); - } - self.dirty = false; + intf: &InnerInterface, + fonts: &mut FontMap, + view: &mut BufferView, + ) -> Geometry { + let fg = Color::WHITE; - match self.input.chars().next() { - Some('=') => { - if self.input.len() > 1 { - match calc(&self.input.chars().skip(1).collect::()) { - Ok(v) => self.result = Some(v), - Err(_) => self.result = None, + 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)); + + let font = fonts.get_font(intf.font, intf.size); + let mut x_max = 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, fg, ">").unwrap(); + + // Draw entries + let dimfg = Color::GREY50; + prompt_offset -= 8; + + 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)); + + 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::LIGHTORANGE); + } else { + colors.push(Color::GREY75); } } + x_max = max( + x_max, + font.auto_draw_text_individual_colors(&mut line, &colors, &m.name) + .unwrap(), + ); + } else { + x_max = max( + x_max, + font.auto_draw_text(&mut line, dimfg, &m.name).unwrap(), + ); } - Some('!') => (), - _ => { - let mut matcher = Matcher::new(self.counter.clone()); + } - for desktop in self.options.iter() { - matcher.try_match( - desktop.clone(), - &desktop.name.to_lowercase(), - &self.input.to_lowercase(), - 1.0, - ); - for keyword in desktop.keywords.iter() { - matcher.try_match( - desktop.clone(), - &keyword.to_lowercase(), - &self.input.to_lowercase(), - 0.5, - ); - } + let content_height = min( + 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: content_width, + height: content_height, + } + } +} + +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) { + let args = vec![ + "/bin/sh".to_string(), + "-c".to_string(), + intf.prompt.input.clone(), + ]; + self.exec(args); + } + + fn update(&mut self, _intf: &InnerInterface) {} + + fn draw( + &mut self, + intf: &InnerInterface, + fonts: &mut FontMap, + view: &mut BufferView, + ) -> Geometry { + let fg = Color::WHITE; + + 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)); + + // Draw prompt + let font = fonts.get_font(intf.font, intf.size); + let x_max = 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(); + + Geometry { + x: intf.geometry.x, + y: intf.geometry.y + intf.geometry.height - line_height, + width: x_max.0 + 1, + height: line_height, + } + } +} + +struct Calc { + 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 s = match read_to_string(format!("{}/wldash/calc", xdg::cache_folder())) { + 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(); - self.matches = matcher.matches(); - } + Calc { old, result: None } + } + + fn sync(&self) { + let mut f = match File::create(format!("{}/wldash/calc", xdg::cache_folder())) { + Ok(f) => f, + _ => return, }; - let buf = &mut ctx.buf.subdimensions((pos.0, pos.1, width, height))?; - buf.memset(ctx.bg); + let old = self.old.lock().unwrap(); + for (input, res) in old.iter() { + write!(f, "{}={}\n", input, res).unwrap(); + } + f.sync_data().unwrap(); + } +} - match self.input.chars().next() { - Some('=') => self.draw_calc(buf, ctx.bg), - Some('!') => self.draw_shell(buf, ctx.bg), - _ => self.draw_launcher(buf, ctx.bg, width), - }?; - - Ok(DrawReport { - width, - height, - damage: vec![buf.get_signed_bounds()], - full_damage: false, - }) +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 { + old.push((intf.prompt.input.to_string(), res.to_string())); + while old.len() > 32 { + old.remove(0); + } + drop(old); + self.sync(); + } + } 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; + } } - fn keyboard_input( + 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, - key: u32, - modifiers: ModifiersState, - _: KeyState, - interpreted: Option, - ) { - match key { - keysyms::XKB_KEY_u if modifiers.ctrl => self.leave(), - keysyms::XKB_KEY_a if modifiers.ctrl => { - self.cursor = 0; - self.dirty = true; - } - keysyms::XKB_KEY_e if modifiers.ctrl => { - self.cursor = self.input.len(); - self.dirty = true; - } - keysyms::XKB_KEY_Home => { - self.cursor = 0; - self.dirty = true; - } - keysyms::XKB_KEY_End => { - self.cursor = self.input.len(); - self.dirty = true; - } - keysyms::XKB_KEY_BackSpace => { - 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); - self.offset = 0; - self.result = None; - self.dirty = true - } - } - keysyms::XKB_KEY_Delete => { - 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); - self.dirty = true; - } - } - keysyms::XKB_KEY_Return => { - match self.input.chars().next() { - Some('=') => { - if let Some(ref v) = self.result { - let _ = wlcopy(&v); - } - } - Some('!') => { - self.cursor = 0; - let _ = Command::new("sh") - .arg("-c") - .arg(self.input.chars().skip(1).collect::()) - .spawn(); - self.tx.send(Cmd::Exit).unwrap(); - } - _ => { - if self.matches.len() > self.offset { - let d = &self.matches[self.offset]; - if let Some(exec) = &d.exec { - let exec = exec - .replace("%f", "") - .replace("%F", "") - .replace("%u", "") - .replace("%U", ""); - let prefix = if d.term { - &self.term_opener - } else { - &self.app_opener - }; + intf: &InnerInterface, + fonts: &mut FontMap, + view: &mut BufferView, + ) -> Geometry { + let old = self.old.lock().unwrap(); + let fg = Color::WHITE; - let mut lexed = shlex::split(&exec).unwrap(); - let lexed = if !prefix.is_empty() { - let mut prefix = shlex::split(prefix).unwrap(); - prefix.append(&mut lexed); - prefix - } else { - lexed - }; + let line_height = intf.size.ceil() as u32; - *self.counter.entries.entry(d.name.clone()).or_insert(0) += 1; - self.counter.save().expect("Unable to save data"); + // Draw line + let mut prompt_offset = intf.geometry.height - (line_height); + let mut prompt_line = view.offset((0, prompt_offset)); - if !lexed.is_empty() { - let _ = - Command::new(lexed[0].clone()).args(&lexed[1..]).spawn(); - self.tx.send(Cmd::Exit).unwrap(); - } - } - if let Some(url) = &d.url { - if !self.url_opener.is_empty() { - let mut lexed = shlex::split(&self.url_opener).unwrap(); - lexed.push(url.to_string()); - if !lexed.is_empty() { - let _ = Command::new(lexed[0].clone()) - .args(&lexed[1..]) - .spawn(); - self.tx.send(Cmd::Exit).unwrap(); - } - } - } - } - } - }; + // 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(&mut prompt_line, Color::BUFF, "=") + .unwrap(); + + prompt_offset -= 8; + prompt_offset -= line_height; + if let Some(res) = &self.result { + 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 (idx, (input, res)) in old.iter().rev().enumerate() { + if prompt_offset < line_height { + break; } - keysyms::XKB_KEY_Tab => { - if !self.matches.is_empty() && self.offset < self.matches.len() - 1 { - self.offset += 1; - self.dirty = true; + 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, c, &format!("{} = {}", input, res)) + .unwrap(); + } + + let content_height = min( + intf.geometry.height, + (old.len() + 2) as u32 * line_height + 8, + ); + Geometry { + x: intf.geometry.x, + y: intf.geometry.y + intf.geometry.height - content_height, + width: intf.geometry.width, + height: content_height, + } + } +} + +struct InnerInterface { + font: &'static str, + size: f32, + dirty: bool, + geometry: Geometry, + selection: usize, + prompt: Prompt, +} + +pub struct Interface { + launcher: Launcher, + shell: Shell, + calc: Calc, + inner: InnerInterface, +} + +impl Interface { + pub fn new( + events: Arc>, + fm: &mut FontMap, + font: &'static str, + size: f32, + launch_cmd: Option, + ) -> Interface { + fm.queue_font( + font, + size, + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 >=!,.-/()", + ); + + Interface { + launcher: Launcher::new(events, launch_cmd), + shell: Shell::new(), + calc: Calc::new(), + inner: InnerInterface { + font, + size, + dirty: false, + geometry: Default::default(), + selection: 0, + prompt: Prompt::new(), + }, + } + } + + 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 - 8; + 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 exit(&mut self) { + if self.inner.prompt.input.len() == 0 { + std::process::exit(0); + } + + if let Ok(mut f) = File::create(format!("{}/wldash/prompt", xdg::cache_folder())) { + 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; + } + 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::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::KEY_k if event.modifiers.ctrl => { + self.inner.prompt.clear_right(); + self.inner.selection = 0; + widget.update(&self.inner); + } + 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_ISO_Left_Tab => { - if !self.matches.is_empty() && self.offset > 0 { - self.offset -= 1; - self.dirty = true; - } + 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_Left => { - if self.cursor > 0 { - self.cursor -= 1; - self.dirty = true; - } + keysyms::KEY_Delete => { + self.inner.prompt.delete(); + self.inner.selection = 0; + widget.update(&self.inner); } - keysyms::XKB_KEY_Right => { - if self.cursor < self.input.len() { - self.cursor += 1; - self.dirty = true; + 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::KEY_Down => { + if self.inner.selection > 0 { + self.inner.selection -= 1; } } _ => { - if let Some(v) = interpreted { - 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; - self.offset = 0; - self.result = None; - self.dirty = true; + 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 mouse_click(&mut self, _: u32, _: (u32, u32)) {} - fn mouse_scroll(&mut self, _: (f64, f64), _: (u32, u32)) {} +} + +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 minimum_size(&mut self, _fonts: &mut FontMap) -> Geometry { + Geometry { + x: 0, + y: 0, + width: 256, + height: (self.inner.size.ceil() as u32) * 4 + 8, + } + } + + fn event(&mut self, event: &Event) { + 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); + } + Event::TokenUpdate(t) => { + self.launcher.next_token = Some(t.to_string()); + self.shell.next_token = Some(t.to_string()); + } + _ => (), + } + } } diff --git a/src/widgets/layout.rs b/src/widgets/layout.rs new file mode 100644 index 0000000..21f6c18 --- /dev/null +++ b/src/widgets/layout.rs @@ -0,0 +1,283 @@ +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; + + fn minimum_size(&self, fonts: &mut FontMap, 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 + } + + 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 { + 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 + } + + 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 { + 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) + } + + fn minimum_size(&self, fonts: &mut FontMap, state: &mut State) -> Geometry { + state.minimum_size(self.widget_idx, fonts) + } +} + +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 + } + + 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 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/line.rs b/src/widgets/line.rs new file mode 100644 index 0000000..a177121 --- /dev/null +++ b/src/widgets/line.rs @@ -0,0 +1,62 @@ +use std::default::Default; + +use crate::{ + buffer::BufferView, + color::Color, + fonts::FontMap, + widgets::{Geometry, Widget}, +}; + +pub struct Line { + geometry: Geometry, + thickness: u32, + vertical: bool, +} + +impl Line { + pub fn new(thickness: u32, vertical: bool) -> Line { + Line { + geometry: Default::default(), + thickness, + vertical, + } + } +} + +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, + y: geometry.y, + width: if self.vertical { + self.thickness + } else { + geometry.width + }, + height: if self.vertical { + geometry.height + } else { + self.thickness + }, + }; + self.geometry + } + + fn minimum_size(&mut self, _fonts: &mut FontMap) -> Geometry { + Geometry { + x: 0, + y: 0, + width: if self.vertical { self.thickness } else { 1 }, + height: if self.vertical { 1 } else { self.thickness }, + } + } +} diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index b388f09..cbcf071 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,10 +1,27 @@ -pub mod backlight; -pub mod bar_widget; -pub mod battery; -pub mod calendar; -pub mod clock; -pub mod date; -pub mod launcher; +//mod alsaaudio; +mod backlight; +mod bar_widget; +mod battery; +mod calendar; +mod clock; +mod date; +mod launcher; +mod layout; +mod line; +mod pulseaudio; +mod widget; -#[cfg(any(feature = "alsa-widget", feature = "pulseaudio-widget"))] -pub mod audio; +//pub use alsaaudio::AlsaAudio; +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, InvertedVerticalLayout, Layout, + Margin, VerticalLayout, WidgetUpdater, +}; +pub use line::Line; +pub use pulseaudio::PulseAudio; +pub use widget::{Geometry, Widget}; diff --git a/src/widgets/pulseaudio.rs b/src/widgets/pulseaudio.rs new file mode 100644 index 0000000..0b03e44 --- /dev/null +++ b/src/widgets/pulseaudio.rs @@ -0,0 +1,243 @@ +use std::{ + cell::RefCell, + error::Error, + os::fd::RawFd, + rc::Rc, + sync::{Arc, Mutex}, + thread, +}; + +use nix::unistd::{pipe, read, write}; + +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::{ + callbacks::ListResult, + context::{ + subscribe::{Facility, InterestMaskSet}, + Context, FlagSet, State, + }, + mainloop::{ + self, api::Mainloop, events::io::FlagSet as IoFlagSet, standard::IterateResult, + }, + 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, +} + +struct InnerAudio { + volume: f32, + channel_volume: Option, + mute: bool, + dirty: bool, + last_click: Option, + pipe: RawFd, +} + +pub struct PulseAudio { + inner: Arc>, +} + +impl PulseAudio { + 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 { + 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 PulseAudio { + fn get_dirty(&self) -> bool { + self.inner.lock().unwrap().dirty + } + fn name(&self) -> &'static str { + "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 { + 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/widget.rs b/src/widgets/widget.rs new file mode 100644 index 0000000..69341cb --- /dev/null +++ b/src/widgets/widget.rs @@ -0,0 +1,66 @@ +use std::{ + cmp::{max, min}, + fmt, +}; + +use crate::{buffer::BufferView, event::Event, fonts::FontMap}; + +#[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 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 { + fn get_dirty(&self) -> bool { + false + } + 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) {} +}