diff --git a/.cargo/config.toml b/.cargo/config.toml index 6fedd36..0e72960 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,9 +1,13 @@ +[profile.dev] +panic = "abort" # No std requirement + [profile.release] -overflow-checks = true # I prefer panics over wraps +panic = "abort" # No std requirement [profile.lto] +panic = "abort" # No std requirement codegen-units = 1 # Better optimization debug = false # Inherits from release so should actually do nothing but whatever inherits = "release" lto = true # link time optimization -strip = true # smaller binary +strip = true # smaller binary \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0b562c7..494c8fe 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Run clippy on all feature permutations - run: cargo hack clippy --feature-powerset -- -D warnings + run: cargo hack clippy -p pgwm-app --feature-powerset -- -D warnings deny: runs-on: ubuntu-latest container: marcusgrass/pgwm-check:latest @@ -29,11 +29,11 @@ jobs: - name: Run tests on core with all feature permutations run: cargo hack test -p pgwm-core --feature-powerset - name: Run tests on project with default features - run: cargo test + run: cargo test --all-features -p pgwm-core makefile: runs-on: ubuntu-latest container: marcusgrass/pgwm-check:latest steps: - uses: actions/checkout@v3 - name: Run makefile tests - run: ./.local/check_make.sh + run: ./.local/make_check.sh diff --git a/.local/check_make.sh b/.local/make_check.sh similarity index 95% rename from .local/check_make.sh rename to .local/make_check.sh index d127048..743b5cd 100755 --- a/.local/check_make.sh +++ b/.local/make_check.sh @@ -13,8 +13,8 @@ check_clean() { } check_build() { - if [ ! -f "target/$1/pgwm" ]; then - echo "Build did not produce a binary at expected location target/$1/pgwm" + if [ ! -f "target/x86_64-unknown-linux-gnu/$1/pgwm" ]; then + echo "Build did not produce a binary at expected location target/x86_64-unknown-linux-gnu/$1/pgwm" exit 1 fi } diff --git a/.local/check.sh b/.local/wm_check.sh similarity index 75% rename from .local/check.sh rename to .local/wm_check.sh index 2247b08..65e99a4 100755 --- a/.local/check.sh +++ b/.local/wm_check.sh @@ -1,10 +1,9 @@ #!/bin/bash set -ex # Deny all warnings here, becomes a pain to scroll back otherwise -cargo hack clippy --feature-powerset -- -D warnings +cargo hack clippy -p pgwm-app --feature-powerset -- -D warnings # Running all modules like this causes a lot of rebuilds which take a lot of time cargo hack test -p pgwm-core --feature-powerset -# Test all modules with default features as well -cargo test +cargo test -p pgwm-app --all-features # Make sure dependencies don't have any advisories or weird licensing cargo deny --all-features --frozen --locked check diff --git a/Cargo.lock b/Cargo.lock index d82e730..e12b547 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,27 +4,27 @@ version = 3 [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" dependencies = [ "memchr", ] [[package]] name = "atoi" -version = "1.0.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c57d12312ff59c811c0643f4d80830505833c9ffaebd193d819392b265be8e" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" dependencies = [ "num-traits", ] [[package]] name = "atomic-polyfill" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14bf7b4f565e5e717d7a7a65b2a05c0b8c96e4db636d6f780f03b15108cdd1b" +checksum = "9c041a8d9751a520ee19656232a18971f18946a7900f1520ee4400002244dd89" dependencies = [ "critical-section", ] @@ -62,21 +62,6 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bstr" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" -dependencies = [ - "memchr", -] - [[package]] name = "byteorder" version = "1.4.3" @@ -89,11 +74,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "compiler_builtins" +version = "0.1.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989b2c1ca6e90ad06fdc69d1d1862fa28d27a977be6d92ae2fa762cf61fe0b10" + [[package]] name = "cortex-m" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd20d4ac4aa86f4f75f239d59e542ef67de87cce2c282818dc6e84155d3ea126" +checksum = "70858629a458fdfd39f9675c4dc309411f2a3f83bede76988d81bf1a0ecee9e0" dependencies = [ "bare-metal 0.2.5", "bitfield", @@ -113,6 +104,14 @@ dependencies = [ "riscv", ] +[[package]] +name = "dlmalloc" +version = "0.2.4" +source = "git+https://github.com/MarcusGrass/dlmalloc-rs.git?rev=a8e9fc0d2c03a06810530a48abd37fecc71e8109#a8e9fc0d2c03a06810530a48abd37fecc71e8109" +dependencies = [ + "sc", +] + [[package]] name = "embedded-hal" version = "0.2.7" @@ -126,8 +125,9 @@ dependencies = [ [[package]] name = "fontdue" version = "0.7.2" -source = "git+https://github.com/MarcusGrass/fontdue?branch=rasterize-oneshot#f11491c93bdd013e208e085c54811e0ce1fdd828" +source = "git+https://github.com/MarcusGrass/fontdue?rev=3a2819fba5f8b3bb6ffd9207200b19910c399d05#3a2819fba5f8b3bb6ffd9207200b19910c399d05" dependencies = [ + "hashbrown", "ttf-parser", ] @@ -140,11 +140,17 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "heapless" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f6733da246dc2af610133c8be0667170fd68e8ca5630936b520300eee8846f9" +checksum = "db04bc24a18b9ea980628ecf00e6c0264f3c1426dac36c00cb49b6fbad8b0743" dependencies = [ "atomic-polyfill", "hash32", @@ -154,29 +160,17 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "itoa" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" - [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "libc" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" - [[package]] name = "lock_api" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg", "scopeguard", @@ -203,17 +197,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae" -[[package]] -name = "nix" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f17df307904acd05aa8e32e97bb20f2a0df1728bbc2d771ae8f9a90463441e9" -dependencies = [ - "bitflags", - "cfg-if", - "libc", -] - [[package]] name = "num-traits" version = "0.2.15" @@ -224,25 +207,32 @@ dependencies = [ ] [[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +name = "pgwm" +version = "0.1.0" dependencies = [ - "libc", + "compiler_builtins", + "dlmalloc", + "pgwm-app", + "tiny-std", + "unix-print", ] [[package]] -name = "pgwm" +name = "pgwm-app" version = "0.1.0" dependencies = [ "fontdue", + "hashbrown", "heapless", "pgwm-core", + "pgwm-utils", + "rusl", "smallmap", - "thiserror", "time", - "x11rb", + "tiny-std", + "unix-print", + "xcb-rust-connection", + "xcb-rust-protocol", ] [[package]] @@ -250,41 +240,49 @@ name = "pgwm-core" version = "0.1.0" dependencies = [ "atoi", - "bstr", "heapless", + "pgwm-utils", "serde", "smallmap", - "thiserror", "time", "tiny-bench", + "tiny-std", "toml", "x11-keysyms", - "x11rb", + "xcb-rust-connection", + "xcb-rust-protocol", +] + +[[package]] +name = "pgwm-utils" +version = "0.1.0" +dependencies = [ + "unix-print", ] [[package]] name = "proc-macro2" -version = "1.0.40" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] [[package]] name = "regex" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" dependencies = [ "aho-corasick", "memchr", @@ -293,9 +291,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "riscv" @@ -318,6 +316,15 @@ dependencies = [ "regex", ] +[[package]] +name = "rusl" +version = "0.1.0" +source = "git+https://github.com/MarcusGrass/tiny-std.git?rev=bbd7244a48d6f4e168ae35137222cbca75840be1#bbd7244a48d6f4e168ae35137222cbca75840be1" +dependencies = [ + "sc", + "unix-print", +] + [[package]] name = "rustc_version" version = "0.2.3" @@ -333,9 +340,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.12", + "semver 1.0.14", ] +[[package]] +name = "sc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "010e18bd3bfd1d45a7e666b236c78720df0d9a7698ebaa9c1c559961eb60a38b" + [[package]] name = "scopeguard" version = "1.1.0" @@ -353,9 +366,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" [[package]] name = "semver-parser" @@ -365,18 +378,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.139" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.139" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1d3230c1de7932af58ad8ffbe1d784bd55efd5a9d84ac24f69c72d83543dfb" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" dependencies = [ "proc-macro2", "quote", @@ -386,10 +399,10 @@ dependencies = [ [[package]] name = "smallmap" version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2df0ae8eb5af9e6e00be6ba531cc6860ba93d5226d6d1d4f80ead510a5d6296e" +source = "git+https://github.com/MarcusGrass/smallmap?rev=a0e7399d09a22753ef9c80aa1f1a34009b364357#a0e7399d09a22753ef9c80aa1f1a34009b364357" dependencies = [ "rustc_version 0.2.3", + "serde", ] [[package]] @@ -409,9 +422,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "syn" -version = "1.0.98" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" dependencies = [ "proc-macro2", "quote", @@ -419,62 +432,74 @@ dependencies = [ ] [[package]] -name = "thiserror" -version = "1.0.31" +name = "time" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ - "thiserror-impl", + "time-core", ] [[package]] -name = "thiserror-impl" -version = "1.0.31" +name = "time-core" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] -name = "time" -version = "0.3.11" +name = "tiny-bench" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" -dependencies = [ - "itoa", - "libc", - "num_threads", -] +checksum = "0b5d366a619c58cc621169d481d049b286553c87ba10c17f1ceef2274f9b8ab7" [[package]] -name = "tiny-bench" +name = "tiny-std" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a19870d3f2b29a057cc6d2a87b30fee84a62903339fdd1fc24f36e0bcd7707" +source = "git+https://github.com/MarcusGrass/tiny-std.git?rev=bbd7244a48d6f4e168ae35137222cbca75840be1#bbd7244a48d6f4e168ae35137222cbca75840be1" +dependencies = [ + "rusl", + "sc", + "unix-print", +] [[package]] name = "toml" version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +source = "git+https://github.com/MarcusGrass/toml?rev=fdc79fc9858c8d60cdcf805208ef4e8bcd8ac57b#fdc79fc9858c8d60cdcf805208ef4e8bcd8ac57b" +dependencies = [ + "hashbrown", + "serde", + "toml_datetime", +] + +[[package]] +name = "toml_datetime" +version = "0.5.0" +source = "git+https://github.com/MarcusGrass/toml?rev=fdc79fc9858c8d60cdcf805208ef4e8bcd8ac57b#fdc79fc9858c8d60cdcf805208ef4e8bcd8ac57b" dependencies = [ "serde", ] [[package]] name = "ttf-parser" -version = "0.15.2" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" +checksum = "375812fa44dab6df41c195cd2f7fecb488f6c09fbaafb62807488cefab642bff" [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unix-print" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50e1866b3de196f1329f6a805771eee750651c83bbebd5dff159e5f033cc16f" +dependencies = [ + "sc", +] [[package]] name = "vcell" @@ -500,14 +525,26 @@ dependencies = [ [[package]] name = "x11-keysyms" version = "0.1.0" -source = "git+https://github.com/MarcusGrass/x11-keysyms?rev=4b0f0e3516100666d18df4b6e8eb520b318e1dc9#4b0f0e3516100666d18df4b6e8eb520b318e1dc9" +source = "git+https://github.com/MarcusGrass/x11-keysyms?rev=da4634c071fdc547772cd4dc7892d857b29dd6e5#da4634c071fdc547772cd4dc7892d857b29dd6e5" [[package]] -name = "x11rb" -version = "0.10.0" -source = "git+https://github.com/MarcusGrass/x11rb?branch=master#360cfe38931a31733b9ee01dfa8171cf0c09e126" +name = "xcb-rust-connection" +version = "0.1.0" +source = "git+https://github.com/MarcusGrass/xcb-rust.git?rev=d7110cc13b5768f3164ff328f80d0bf6d5d3deed#d7110cc13b5768f3164ff328f80d0bf6d5d3deed" dependencies = [ - "heapless", - "nix", + "rusl", + "sc", "smallmap", + "tiny-std", + "unix-print", + "xcb-rust-protocol", +] + +[[package]] +name = "xcb-rust-protocol" +version = "0.1.0" +source = "git+https://github.com/MarcusGrass/xcb-rust.git?rev=d7110cc13b5768f3164ff328f80d0bf6d5d3deed#d7110cc13b5768f3164ff328f80d0bf6d5d3deed" +dependencies = [ + "tiny-std", + "unix-print", ] diff --git a/Cargo.toml b/Cargo.toml index 354c329..5384f06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,11 @@ [workspace] -members = ["pgwm", "pgwm-core"] +members = ["pgwm", "pgwm-app", "pgwm-core", "pgwm-utils"] [patch.crates-io] -fontdue = { git = "https://github.com/MarcusGrass/fontdue", branch = "rasterize-oneshot" } -x11rb = { git = "https://github.com/MarcusGrass/x11rb", branch = "master"} +fontdue = { git = "https://github.com/MarcusGrass/fontdue", rev = "3a2819fba5f8b3bb6ffd9207200b19910c399d05" } +smallmap = { git = "https://github.com/MarcusGrass/smallmap", rev = "a0e7399d09a22753ef9c80aa1f1a34009b364357" } +toml = { git = "https://github.com/MarcusGrass/toml", rev = "fdc79fc9858c8d60cdcf805208ef4e8bcd8ac57b" } +rusl = { git = "https://github.com/MarcusGrass/tiny-std.git", rev = "bbd7244a48d6f4e168ae35137222cbca75840be1"} +tiny-std = { git = "https://github.com/MarcusGrass/tiny-std.git", rev = "bbd7244a48d6f4e168ae35137222cbca75840be1"} +xcb-rust-connection = { git = "https://github.com/MarcusGrass/xcb-rust.git", rev = "d7110cc13b5768f3164ff328f80d0bf6d5d3deed" } +xcb-rust-protocol = { git = "https://github.com/MarcusGrass/xcb-rust.git", rev = "d7110cc13b5768f3164ff328f80d0bf6d5d3deed" } diff --git a/Changelog.md b/Changelog.md index 7edf250..6163090 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,7 +3,6 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). - ## [Unreleased] ### Fixed @@ -11,6 +10,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed +## [v0.3.0] - +### Fixed + - Fullscreening causing crashing in some cases, because the destroyed window was cached and then reused + +### Added + - Generated new xcb to Rust code and used that instead + - Replace stdlib with tiny-std + +### Changed + - Removed all libc dependencies, requiring nightly to run until [naked function stabilization](https://github.com/rust-lang/rust/pull/93587) + - Using Dlmalloc as allocator + - Changed WM to be no_std, with the above change, another feature [that seems to be moving towards stabilization was added](https://github.com/rust-lang/rust/pull/102318) + - Removed or patched dependencies needing libc or not being no_std compatible + - Moved entrypoint to [pgwm](pgwm), +moved the main WM from a binary project to a library project [pgwm-app](pgwm-app) + - Changed configuration parsing of char-remap, now uses regular map-parsing + + ## [v0.2.0] - 2022-07-09 ### Fixed diff --git a/LICENSE b/GPL-LICENSE similarity index 100% rename from LICENSE rename to GPL-LICENSE diff --git a/README.md b/README.md index a5f68a6..f479476 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PGWM, a DWM-inspired tiling window manager written in pure safe Rust +# PGWM, a DWM-inspired tiling window manager written in pure Rust [![Latest workflow](https://github.com/MarcusGrass/pgwm/workflows/CI/badge.svg)](https://github.com/MarcusGrass/pgwm/actions) The WM is to my understanding compliant with the [wm-spec](https://specifications.freedesktop.org/wm-spec/wm-spec-1.3.html). @@ -9,6 +9,7 @@ Big shout out to [x11rb](https://github.com/psychon/x11rb) which has great safe Even Jetbrains IDE's work without freaking out assuming `setwmname LG3D` is in your `~/.xinitrc`. + # Why I love to build my old tools, so after a few years of running up against bugs that bothered my workflow and features that I wanted but were missing in other tiling WMs @@ -81,13 +82,12 @@ if there is, please let me know by creating an issue. ![multi-monitor-tabbed-float](demo3.png) # How to build -To build locally platform build essentials, is required +To build locally, platform build essentials is required see the [min building dockerfile](.docker/minimal-build.dockerfile). -To run the same test as the ci locally libssl and perl is also required, +To run the same test as the ci locally, libssl and perl is also required, [see the ci dockerfile](.docker/full-checks.dockerfile). - -The project is tested on x86_64-unknown-linux-gnu but "should" run on any *nix system. +The project is tested on x86_64-unknown-linux-gnu but "should" run on more *nix systems. ## Install a Rust toolchain https://www.rust-lang.org/tools/install @@ -96,6 +96,15 @@ https://www.rust-lang.org/tools/install git clone https://github.com/MarcusGrass/pgwm.git ## Build the project +As of 0.3.0 the WM runs in pure Rust with no libc dependencies. In effect this means that the binary will always be +statically linked. It also sadly means that the nightly toolchain needs to compile it, and that some use of unsafety is needed. +Mostly this comes from direct syscalls, which will always be unsafe, as well as the WM entrypoint since we don't include start files. + +Another side effect of this is that a target needs to be specified for build-scripts in dependencies to run correctly +since the binary will be statically linked anyway it just defaults to --target x86_64-unknown-linux-gnu in [the build script](build_wm.sh). + +`lld` is required, if you don't want to change [the small build script](build_wm.sh) and remove it as the default linker there. + The project builds default with xinerama support, a status-bar, and support for a config-file. To compile without either, disable default features. To build with max optimizations use --profile=lto. @@ -145,30 +154,24 @@ make uninstall CLEAN_CONFIG=1 How to build as a regular Rust project. #### With default features -`cargo build --release` +`./build_wm.sh -r` or -`cargo build --profile=lto` +`./build_wm.sh --profile=lto` #### With no default features -`cargo build --release --no-default-features` +`./build_wm.sh --release --no-default-features` or -`cargo build --profile=lto --no-default-features` +`./build_wm.sh --profile=lto --no-default-features` #### Example of some additional features -`cargo build --release --no-default-features --features xinerama,status-bar` +`./build_wm.sh --release --no-default-features --features xinerama,status-bar` or -`cargo build --profile=lto --no-default-features --features xinerama,status-bar` - -#### Using cargo install -Installs the binary to `$HOME/.cargo/bin` -`cargo install --profile=lto --path pgwm` -Remember to add cargo bin to path if you haven't already -`PATH="$HOME/.cargo/bin:$PATH"` +`./build_wm.sh --profile=lto --no-default-features --features xinerama,status-bar` ### Edit .xinitrc or other file specifying WM entrypoint -If built with `cargo build` The binary ends up in target/release/pgwm or target/lto/pgwm +If built with `./build_wm.sh` The binary ends up in target/x86_64-unknown-linux-gnu/release/pgwm or target/x86_64-unknown-linux-gnu/lto/pgwm Replace the (probably) last line of .xinitrc with -`exec $BINARY_LOCATION` $BINARY_LOCATION being the path to the pgwm binary, or just `pgwm` if using `cargo install`. +`exec $BINARY_LOCATION` $BINARY_LOCATION being the path to the pgwm binary. # Changing configuration ## Config file @@ -239,5 +242,7 @@ All this being said, it's measured for the running WM binary, all operations on this WM binary could be perfectly efficient but slamming the x11 server with requests that it has problems processing. Although I have not noticed any such behaviour. +Note: post migration, pre rip out libc, 931K binary size + # Licensing -This project is licensed under [GPL v3](LICENSE). +This project is licensed under [GPL v3](GPL-LICENSE) \ No newline at end of file diff --git a/Todo.md b/Todo.md new file mode 100644 index 0000000..585189e --- /dev/null +++ b/Todo.md @@ -0,0 +1,2 @@ +1. Reap child processes +2. Weird windows disappearing sometimes, mostly happens with Firefox on restarts \ No newline at end of file diff --git a/build_wm.sh b/build_wm.sh new file mode 100755 index 0000000..951b23d --- /dev/null +++ b/build_wm.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -e + +RUSTFLAGS='-C panic=abort -C link-arg=-nostartfiles' cargo b -p pgwm --target x86_64-unknown-linux-gnu "$@" \ No newline at end of file diff --git a/configure b/configure index 8186bd4..b6da85a 100755 --- a/configure +++ b/configure @@ -58,10 +58,10 @@ DESTDIR :=%s CONFIG_DIR :=%s PROFILE :=%s build: -\t@cargo build --profile=\$(PROFILE) -p \$(APP) +\t@./build_wm.sh --profile=\$(PROFILE) install: \t@test -d \$(DESTDIR) || echo \"Install directory \$(DESTDIR) not found, will try to create it\"; mkdir -p \$(DESTDIR) -\t@install target/\$(PROFILE)/\$(APP) \$(DESTDIR)/\$(APP) +\t@install target/x86_64-unknown-linux-gnu/\$(PROFILE)/\$(APP) \$(DESTDIR)/\$(APP) \t@if [ -f \$(CONFIG_DIR)/pgwm.toml ]; then \ \t\techo \"Configuration file exists, will not override with default\"; \ \telif [ -d \$(CONFIG_DIR) ]; then \ diff --git a/deny.toml b/deny.toml index a5558af..d74e5e2 100644 --- a/deny.toml +++ b/deny.toml @@ -1,8 +1,8 @@ targets = [ { triple = "x86_64-unknown-linux-gnu" }, { triple = "x86_64-unknown-linux-musl" }, - { triple = "aarch64-apple-darwin" }, - { triple = "x86_64-apple-darwin" }, + { triple = "aarch64-unknown-linux-gnu" }, + { triple = "aarch64-unknown-linux-musl" }, ] [advisories] @@ -25,9 +25,13 @@ skip = [ [sources] allow-git = [ - "https://github.com/MarcusGrass/x11-keysyms", + "https://github.com/MarcusGrass/dlmalloc-rs", "https://github.com/MarcusGrass/fontdue", - "https://github.com/MarcusGrass/x11rb", + "https://github.com/MarcusGrass/smallmap", + "https://github.com/MarcusGrass/toml", + "https://github.com/MarcusGrass/x11-keysyms", + "https://github.com/MarcusGrass/tiny-std", + "https://github.com/MarcusGrass/xcb-rust", ] [licenses] @@ -39,8 +43,15 @@ copyleft = "deny" allow = [ "Apache-2.0", "MIT", + "Unicode-DFS-2016", ] exceptions = [ { name = "pgwm", allow = ["GPL-3.0"] }, + { name = "pgwm-app", allow = ["GPL-3.0"] }, { name = "pgwm-core", allow = ["GPL-3.0"] }, + { name = "pgwm-utils", allow = ["GPL-3.0"] }, + { name = "xcb-rust-connection", allow = ["MPL-2.0"] }, + { name = "xcb-rust-protocol", allow = ["MPL-2.0"] }, + { name = "tiny-std", allow = ["MPL-2.0"] }, + { name = "rusl", allow = ["MPL-2.0"] }, ] diff --git a/pgwm-app/Cargo.toml b/pgwm-app/Cargo.toml new file mode 100644 index 0000000..ca8da82 --- /dev/null +++ b/pgwm-app/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "pgwm-app" +version = "0.1.0" +edition = "2021" +license = "GPL-3.0" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +config-file = ["pgwm-core/config-file"] +default = ["xinerama", "status-bar", "config-file"] +debug = ["pgwm-core/debug", "xcb-rust-connection/debug", "xcb-rust-protocol/debug", "pgwm-utils/debug"] +xinerama = ["xcb-rust-connection/xinerama", "xcb-rust-protocol/render"] +status-bar = ["pgwm-core/status-bar", "time"] +perf-test = [] + +[dependencies] +hashbrown = { version = "0.12.3", default-features = false } +heapless = { version = "0.7.16", default-features = false } +fontdue = { version = "0.7.2", default-features = false, features = ["simd"] } +pgwm-core = { path = "../pgwm-core" } +pgwm-utils = { path = "../pgwm-utils" } +smallmap = { version = "1.4.0", default-features = false } +time = { version = "0.3.17", optional = true, default-features = false } +unix-print = "0.1.0" +tiny-std = { version = "0.1" } +rusl = { version = "0.1" } + +xcb-rust-connection = { version = "0.1" } +xcb-rust-protocol = { version = "0.1" } + +[dev-dependencies] +tiny-std = { version = "0.1", features = ["start", "alloc"] } diff --git a/pgwm/emit_stack_sizes.sh b/pgwm-app/emit_stack_sizes.sh similarity index 100% rename from pgwm/emit_stack_sizes.sh rename to pgwm-app/emit_stack_sizes.sh diff --git a/pgwm-app/src/error.rs b/pgwm-app/src/error.rs new file mode 100644 index 0000000..bc963f6 --- /dev/null +++ b/pgwm-app/src/error.rs @@ -0,0 +1,77 @@ +use alloc::ffi::NulError; +use alloc::string::FromUtf8Error; +use core::fmt::Formatter; + +use tiny_std::error::Error as StdError; +use xcb_rust_connection::{ConnectError, ConnectionError}; + +use pgwm_utils::from_error; + +pub(crate) type Result = core::result::Result; + +#[derive(Debug)] +pub(crate) enum Error { + Core(pgwm_core::error::Error), + X11Connect(ConnectError), + X11Connection(ConnectionError), + XcbProto(xcb_rust_protocol::Error), + X11EventParse, + GlyphMismatch, + BecomeWm, + Tiling, + NoAppropriateVisual, + ContentToCstr(NulError), + ConvertToUtf8(FromUtf8Error), + ConvertCoreToUtf8(core::str::Utf8Error), + StateInvalidated, + GracefulShutdown, + FullRestart, + ParseFloat, + FontLoad(&'static str), + BadCharFontMapping(&'static str), + Syscall(StdError), +} +from_error!(pgwm_core::error::Error, Error, Core); +from_error!(ConnectError, Error, X11Connect); +from_error!(ConnectionError, Error, X11Connection); +from_error!(xcb_rust_protocol::Error, Error, XcbProto); +from_error!(NulError, Error, ContentToCstr); +from_error!(FromUtf8Error, Error, ConvertToUtf8); +from_error!(core::str::Utf8Error, Error, ConvertCoreToUtf8); +from_error!(StdError, Error, Syscall); + +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + Error::Core(e) => core::fmt::Display::fmt(e, f), + Error::X11Connect(e) => core::fmt::Display::fmt(e, f), + Error::X11Connection(e) => core::fmt::Display::fmt(e, f), + Error::XcbProto(e) => core::fmt::Display::fmt(e, f), + Error::X11EventParse => f.write_str("Failed to parse event"), + Error::GlyphMismatch => { + f.write_str("Number of glyph ids not corresponding to number of metrics") + } + Error::BecomeWm => f.write_str( + "Could not become wm, access denied, there is likely another WM running", + ), + Error::Tiling => f.write_str( + "Failed to calculate correct tiling dimensions (this is a programming error)", + ), + Error::NoAppropriateVisual => { + f.write_str("Failed to find an appropriate 32 bit depth visual") + } + Error::ContentToCstr(e) => core::fmt::Display::fmt(e, f), + Error::ConvertToUtf8(e) => core::fmt::Display::fmt(e, f), + Error::ConvertCoreToUtf8(e) => core::fmt::Display::fmt(e, f), + Error::StateInvalidated => f.write_str("State Invalidated"), + Error::GracefulShutdown => f.write_str("Exit triggered"), + Error::FullRestart => f.write_str("Restart triggered"), + Error::ParseFloat => f.write_str("Size not parseable as f32"), + Error::FontLoad(s) => f.write_fmt(format_args!("Failed to load font {s}")), + Error::BadCharFontMapping(s) => { + f.write_fmt(format_args!("Invalid char to font mapping {s}")) + } + Error::Syscall(e) => f.write_fmt(format_args!("Syscall error {e}")), + } + } +} diff --git a/pgwm-app/src/lib.rs b/pgwm-app/src/lib.rs new file mode 100644 index 0000000..e082fde --- /dev/null +++ b/pgwm-app/src/lib.rs @@ -0,0 +1,109 @@ +#![warn(clippy::all)] +#![warn(clippy::pedantic)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::needless_pass_by_value)] +#![allow(let_underscore_drop)] +#![allow(clippy::too_many_lines)] +// X11 uses inconsistent integer types fairly interchangeably +#![allow(clippy::cast_lossless)] +#![allow(clippy::cast_possible_truncation)] +#![allow(clippy::cast_sign_loss)] +#![allow(clippy::cast_possible_wrap)] +#![allow(clippy::cast_precision_loss)] +#![allow(clippy::module_name_repetitions)] +// Debug log complaints +#![allow(clippy::used_underscore_binding)] +#![cfg_attr(not(test), no_std)] + +extern crate alloc; + +use unix_print::{unix_eprintln, unix_println}; + +use pgwm_utils::debug; + +use crate::error::Error; +use crate::wm::run_wm; + +pub(crate) mod error; +mod manager; +pub(crate) mod util; +mod wm; +mod x11; + +#[must_use] +pub fn main_loop() -> i32 { + debug!("Starting pgwm"); + if check_cfg() { + return 0; + } + loop { + return match run_wm() { + Ok(_) => { + debug!("Exiting WM"); + 0 + } + Err(e) => { + if let Error::FullRestart = e { + debug!("Restarting WM"); + continue; + } + debug!("Fatal error {e}"); + 1 + } + }; + } +} + +fn check_cfg() -> bool { + let mut arg_iter = tiny_std::env::args(); + let _ = arg_iter.next(); // drain program argument + if let Some(Ok(k)) = arg_iter.next() { + if k == "--check-cfg" { + #[cfg(feature = "config-file")] + match pgwm_core::util::load_cfg::load_cfg( + tiny_std::env::var("XDG_CONFIG_HOME").ok(), + tiny_std::env::var("HOME").ok(), + ) { + Ok(cfg) => { + let collected_fonts = cfg + .fonts + .get_all_font_paths() + .into_iter() + .chain(cfg.char_remap.values().map(|v| v.path.clone())); + for font in collected_fonts { + unix_println!("Checking font at {font}"); + match tiny_std::fs::metadata(&font) { + Ok(meta) => { + if meta.is_file() { + unix_println!("Found font."); + } else { + unix_eprintln!("Invalid configuration, path for font {font} which is not a file"); + return true; + } + } + Err(e) => { + unix_eprintln!("Failed to check font file for font {font} {e}"); + return true; + } + } + } + unix_println!("Configuration valid!"); + } + Err(e) => { + unix_println!("Invalid configuration: {e}"); + } + } + #[cfg(not(feature = "config-file"))] + { + unix_eprintln!( + "Cannot check configuration if not compiled with 'config-file' feature" + ); + return true; + } + } + unix_println!("The only valid argument is `--check-cfg` to check if configuration is valid and can be found"); + true + } else { + false + } +} diff --git a/pgwm/src/manager/bar.rs b/pgwm-app/src/manager/bar.rs similarity index 97% rename from pgwm/src/manager/bar.rs rename to pgwm-app/src/manager/bar.rs index 31f6138..bf67f86 100644 --- a/pgwm/src/manager/bar.rs +++ b/pgwm-app/src/manager/bar.rs @@ -1,6 +1,3 @@ -use crate::error::Result; -use crate::manager::font::FontDrawer; -use crate::x11::call_wrapper::CallWrapper; use pgwm_core::colors::Color; use pgwm_core::config::Fonts; #[cfg(feature = "status-bar")] @@ -8,6 +5,10 @@ use pgwm_core::config::STATUS_BAR_CHECK_CONTENT_LIMIT; use pgwm_core::geometry::Dimensions; use pgwm_core::state::State; +use crate::error::Result; +use crate::manager::font::FontDrawer; +use crate::x11::call_wrapper::CallWrapper; + pub(crate) struct BarManager<'a> { font_drawer: &'a FontDrawer<'a>, fonts: &'a Fonts, @@ -23,7 +24,7 @@ impl<'a> BarManager<'a> { let mon = &state.monitors[mon_ind]; let section = &mon.bar_geometry.window_title_section; let title_position = section.position; - pgwm_core::debug!("Starting window title draw"); + pgwm_utils::debug!("Starting window title draw"); let draw_width = self.font_drawer.draw( call_wrapper, &mon.bar_win, @@ -125,7 +126,7 @@ impl<'a> BarManager<'a> { let mon = &mut state.monitors[mon_ind]; let component = &mon.bar_geometry.workspace.components[ws_ind]; let name = &state.workspaces.get_ws(ws_ind).name; - pgwm_core::debug!("Starting workspace draw"); + pgwm_utils::debug!("Starting workspace draw"); self.font_drawer.draw( call_wrapper, &mon.bar_win, @@ -156,7 +157,7 @@ impl<'a> BarManager<'a> { let mon = &mut state.monitors[mon_ind]; let is_mon_focus = state.focused_mon == mon_ind; let wants_focus = state.workspaces.get_wants_focus_workspaces(); - pgwm_core::debug!("Running clean workspace redraw on mon {mon_ind}"); + pgwm_utils::debug!("Running clean workspace redraw on mon {mon_ind}"); for (ind, ws) in mon.bar_geometry.workspace.components.iter().enumerate() { let name = &ws.text; let bg = if name.contains(state.workspaces.get_ws(ws_ind).name.as_str()) { @@ -294,7 +295,7 @@ impl<'a> BarManager<'a> { mon_ind: usize, state: &mut State, ) -> Result<()> { - pgwm_core::debug!("Starting shortcuts draw"); + pgwm_utils::debug!("Starting shortcuts draw"); let mon = &mut state.monitors[mon_ind]; let pos = mon.bar_geometry.shortcuts.position; let mut offset = pos.start; diff --git a/pgwm/src/manager/draw.rs b/pgwm-app/src/manager/draw.rs similarity index 96% rename from pgwm/src/manager/draw.rs rename to pgwm-app/src/manager/draw.rs index a1ab76c..ccb8842 100644 --- a/pgwm/src/manager/draw.rs +++ b/pgwm-app/src/manager/draw.rs @@ -1,13 +1,15 @@ -use crate::error::{Error, Result}; -use crate::manager::font::FontDrawer; -use crate::x11::call_wrapper::CallWrapper; +use xcb_rust_protocol::proto::xproto::Window; + use pgwm_core::config::{Fonts, WM_NAME_LIMIT, WS_WINDOW_LIMIT}; use pgwm_core::geometry::draw::{Mode, OldDrawMode}; use pgwm_core::geometry::{layout::Layout, Dimensions}; use pgwm_core::push_heapless; use pgwm_core::state::workspace::{ArrangeKind, ManagedWindow}; use pgwm_core::state::State; -use x11rb::protocol::xproto::Window; + +use crate::error::{Error, Result}; +use crate::manager::font::FontDrawer; +use crate::x11::call_wrapper::CallWrapper; pub(crate) struct Drawer<'a> { font_manager: &'a FontDrawer<'a>, @@ -31,7 +33,7 @@ impl<'a> Drawer<'a> { dimensions: Dimensions, state: &mut State, ) -> Result<()> { - pgwm_core::debug!("Drawing floating {window} at {dimensions:?}"); + pgwm_utils::debug!("Drawing floating {window} at {dimensions:?}"); call_wrapper.configure_window(window, dimensions, state.window_border_width, state)?; call_wrapper.send_map(window, state)?; Ok(()) @@ -44,7 +46,7 @@ impl<'a> Drawer<'a> { y: i32, state: &mut State, ) -> Result<()> { - pgwm_core::debug!("Drawing floating {window} at ({x}, {y})"); + pgwm_utils::debug!("Drawing floating {window} at ({x}, {y})"); call_wrapper.move_window(window, x, y, state)?; call_wrapper.send_map(window, state)?; Ok(()) @@ -78,7 +80,7 @@ impl<'a> Drawer<'a> { drop(tiled); self.draw(call_wrapper, mon_ind, targets, state)?; - pgwm_core::debug!("Drawing {} floating on mon = {mon_ind}", floating.len()); + pgwm_utils::debug!("Drawing {} floating on mon = {mon_ind}", floating.len()); for (win, arrange) in floating { if let ArrangeKind::FloatingInactive(rel_x, rel_y) = arrange { let dimensions = state.monitors[mon_ind].dimensions; @@ -146,7 +148,7 @@ impl<'a> Drawer<'a> { layout: Layout, state: &mut State, ) -> Result<()> { - pgwm_core::debug!("Drawing tiled {targets:?} on mon = {mon_ind}"); + pgwm_utils::debug!("Drawing tiled {targets:?} on mon = {mon_ind}"); call_wrapper.send_unmap(state.monitors[mon_ind].tab_bar_win.window.drawable, state)?; let mon_dimensions = state.monitors[mon_ind].dimensions; let tiling_modifiers = &state.workspaces.get_ws(ws_ind).tiling_modifiers; @@ -298,7 +300,7 @@ impl<'a> Drawer<'a> { let text_dimensions = self .font_manager .text_geometry(name, &self.fonts.tab_bar_section); - let text_width = text_dimensions.0 as i16; + let text_width = text_dimensions.0; let draw_name = if split_width >= text_width { name } else { "" }; let center_offset = (split_width - text_width) / 2; @@ -309,7 +311,7 @@ impl<'a> Drawer<'a> { &self.fonts.tab_bar_section, Dimensions::new(split_width, state.tab_bar_height, split_width * i as i16, 0), split_width, - center_offset as i16, + center_offset, 0, bg, state.colors.tab_bar_text, diff --git a/pgwm/src/manager/font.rs b/pgwm-app/src/manager/font.rs similarity index 68% rename from pgwm/src/manager/font.rs rename to pgwm-app/src/manager/font.rs index 180d036..63defed 100644 --- a/pgwm/src/manager/font.rs +++ b/pgwm-app/src/manager/font.rs @@ -1,14 +1,20 @@ -use crate::error::{Error, Result}; -use crate::x11::call_wrapper::CallWrapper; -use fontdue::FontSettings; -use pgwm_core::render::{DoubleBufferedRenderPicture, RenderVisualInfo}; -use std::collections::hash_map::Entry; -use std::collections::HashMap; -use x11rb::protocol::render::{Glyphinfo, Glyphset}; +use alloc::vec; +use alloc::vec::Vec; + +use fontdue::{FontHasherBuilder, FontSettings}; +use hashbrown::hash_map::Entry; +use hashbrown::HashMap; +use smallmap::Map; +use tiny_std::io::Read; +use xcb_rust_protocol::proto::render::{Glyphinfo, Glyphset}; use pgwm_core::colors::Color; use pgwm_core::config::{FontCfg, Fonts}; use pgwm_core::geometry::Dimensions; +use pgwm_core::render::{DoubleBufferedRenderPicture, RenderVisualInfo}; + +use crate::error::{Error, Result}; +use crate::x11::call_wrapper::CallWrapper; pub(crate) struct FontDrawer<'a> { loaded_render_fonts: &'a LoadedFonts<'a>, @@ -51,7 +57,7 @@ impl<'a> FontDrawer<'a> { let mut drawn_width = 0; for chunk in encoded { drawn_width += chunk.width; - let box_shift = (fill_area.height - chunk.font_height as i16) / 2; + let box_shift = (fill_area.height - chunk.font_height) / 2; call_wrapper.draw_glyphs( offset, @@ -61,7 +67,7 @@ impl<'a> FontDrawer<'a> { &chunk.glyph_ids, )?; - offset += chunk.width as i16; + offset += chunk.width; } Ok(drawn_width) @@ -72,9 +78,9 @@ pub(crate) fn load_alloc_fonts<'a>( call_wrapper: &mut CallWrapper, vis_info: &RenderVisualInfo, fonts: &'a Fonts, - char_remap: &'a HashMap, FontCfg>, -) -> Result> { - let mut map = HashMap::new(); + char_remap: &'a Map, FontCfg>, +) -> Result> { + let mut map = HashMap::with_hasher(FontHasherBuilder); let it = fonts .workspace_section .iter() @@ -82,72 +88,121 @@ pub(crate) fn load_alloc_fonts<'a>( .chain(fonts.shortcut_section.iter()) .chain(fonts.tab_bar_section.iter()) .chain(char_remap.values()); + call_wrapper.inner_mut().flush()?; #[cfg(feature = "status-bar")] let it = it.chain(fonts.status_section.iter()); + // Reuse buffer + let mut data = Vec::with_capacity(65536); for f_cfg in it { // Ugly and kind of dumb let mut id = 0; if let Entry::Vacant(v) = map.entry(f_cfg) { - let data = std::fs::read(&f_cfg.path)?; - + let mut file = tiny_std::fs::OpenOptions::new() + .read(true) + .open(&f_cfg.path)?; + data.clear(); + let read_bytes = file.read_to_end(&mut data)?; + crate::debug!("Read {} bytes of font {}", read_bytes, f_cfg.path); let gs = call_wrapper.create_glyphset(vis_info)?; let mut ids = vec![]; let mut infos = vec![]; let mut raw_data = vec![]; - let mut char_map = HashMap::new(); + let mut char_map = HashMap::with_hasher(FontHasherBuilder); let size = f_cfg.size.parse::().map_err(|_| Error::ParseFloat)?; - let rasterized = fontdue::rasterize_all( - data.as_slice(), - size, - FontSettings { - collection_index: 0, - scale: size, // We're just oneshot rasterizing here so the size we're drawing for = scale without waste - }, - ) - .map_err(Error::FontLoad)?; - for data in rasterized.data { - for byte in data.buf { + let raster_iter = + fontdue::RasterIterator::new(&data[..read_bytes], size, FontSettings::default()) + .map_err(|_e| { + crate::debug!("Font load failed {_e}"); + Error::FontLoad("Failed to load font") + })?; + crate::debug!("Loaded font at {}", f_cfg.path); + let mut data = vec![]; + let mut max_height = 0; + // DlMalloc seems to keep our dropped vec on the heap after use, really annoying + for rasterized_char in raster_iter { + let height = + rasterized_char.metrics.height as i16 + rasterized_char.metrics.ymin as i16; + if height > max_height { + max_height = height; + } + data.push(( + rasterized_char.ch, + rasterized_char.metrics, + rasterized_char.buf, + )); + } + for (ch, metrics, buf) in data { + for byte in buf { raw_data.extend_from_slice(&[byte, byte, byte, byte]); } // When placing chars next to each other this is the appropriate width to use - let horizontal_space = data.metrics.advance_width.ceil() as i16; + let horizontal_space = metrics.advance_width as i16; let glyph_info = Glyphinfo { - width: data.metrics.width as u16, - height: data.metrics.height as u16, - x: -data.metrics.xmin as i16, - y: data.metrics.height as i16 - rasterized.max_height as i16 - + data.metrics.ymin as i16, // pt2 + width: metrics.width as u16, + height: metrics.height as u16, + x: -metrics.xmin as i16, + y: metrics.height as i16 - max_height + metrics.ymin as i16, // pt2 x_off: horizontal_space, - y_off: data.metrics.advance_height.ceil() as i16, + y_off: metrics.advance_height as i16, }; ids.push(id as u32); infos.push(glyph_info); char_map.insert( - data.ch, + ch, CharInfo { glyph_id: id, horizontal_space, - height: data.metrics.height as u16, + height: metrics.height as u16, }, ); + let current_out_size = current_out_size(ids.len(), infos.len(), raw_data.len()); + if current_out_size >= 32768 { + call_wrapper.add_glyphs(gs, &ids, &infos, &raw_data)?; + call_wrapper.inner_mut().flush()?; + ids.clear(); + infos.clear(); + raw_data.clear(); + } id += 1; } call_wrapper.add_glyphs(gs, &ids, &infos, &raw_data)?; + call_wrapper.inner_mut().flush()?; + crate::debug!("Added {} glyphs", ids.len()); + crate::debug!( + "Storing loaded font with size > {} bytes", + calculate_font_size(char_map.len()) + ); v.insert(LoadedFont { glyph_set: gs, char_map, - font_height: rasterized.max_height as i16, + font_height: max_height, }); } } Ok(map) } +#[inline] +fn current_out_size(ids_len: usize, infos_len: usize, raw_data_len: usize) -> usize { + core::mem::size_of::() + + core::mem::size_of::() * ids_len + + core::mem::size_of::() * infos_len + + core::mem::size_of::() * raw_data_len +} + +#[cfg(feature = "debug")] +#[inline] +fn calculate_font_size(map_len: usize) -> usize { + core::mem::size_of::() + + core::mem::size_of::() + + (core::mem::size_of::() + core::mem::size_of::()) * map_len +} + pub struct LoadedFonts<'a> { - pub(crate) fonts: HashMap<&'a FontCfg, LoadedFont>, + pub(crate) fonts: HashMap<&'a FontCfg, LoadedFont, FontHasherBuilder>, // Simple key, use smallmap - chars: smallmap::Map, + chars: Map, } struct LoadedChar { @@ -158,11 +213,11 @@ struct LoadedChar { impl<'a> LoadedFonts<'a> { pub(crate) fn new( - fonts: HashMap<&'a FontCfg, LoadedFont>, - char_mapping: &HashMap, FontCfg>, + fonts: HashMap<&'a FontCfg, LoadedFont, FontHasherBuilder>, + char_mapping: &Map, FontCfg>, ) -> Result { - let mut chars = smallmap::Map::new(); - for (char, font) in char_mapping { + let mut chars = Map::new(); + for (char, font) in char_mapping.iter() { let maybe_char = char.chars().next(); match maybe_char { Some(char) => match fonts.get(font) { @@ -201,10 +256,10 @@ impl<'a> LoadedFonts<'a> { if let Some(lchar) = self.chars.get(&char) { if !cur_glyphs.is_empty() { chunks.push(FontEncodedChunk { - width: std::mem::take(&mut cur_width), - font_height: std::mem::take(&mut cur_font_height), + width: core::mem::take(&mut cur_width), + font_height: core::mem::take(&mut cur_font_height), glyph_set: cur_gs.unwrap(), - glyph_ids: std::mem::take(&mut cur_glyphs), + glyph_ids: core::mem::take(&mut cur_glyphs), }); } // Early return if next char would go OOB @@ -239,10 +294,10 @@ impl<'a> LoadedFonts<'a> { } if gs != cur_gs.unwrap() { chunks.push(FontEncodedChunk { - width: std::mem::take(&mut cur_width), + width: core::mem::take(&mut cur_width), font_height: mh, glyph_set: cur_gs.unwrap(), - glyph_ids: std::mem::take(&mut cur_glyphs), + glyph_ids: core::mem::take(&mut cur_glyphs), }); cur_gs = Some(gs); cur_width = 0; @@ -260,7 +315,7 @@ impl<'a> LoadedFonts<'a> { return chunks; } total_width += info.horizontal_space; - cur_width += info.horizontal_space as i16; + cur_width += info.horizontal_space; cur_font_height = mh; cur_glyphs.push(info.glyph_id); } @@ -322,7 +377,7 @@ pub struct FontEncodedChunk { #[allow(clippy::module_name_repetitions)] pub struct LoadedFont { pub glyph_set: Glyphset, - pub char_map: HashMap, + pub char_map: HashMap, pub font_height: i16, } diff --git a/pgwm/src/manager/mod.rs b/pgwm-app/src/manager/mod.rs similarity index 92% rename from pgwm/src/manager/mod.rs rename to pgwm-app/src/manager/mod.rs index 4d04900..634d9b2 100644 --- a/pgwm/src/manager/mod.rs +++ b/pgwm-app/src/manager/mod.rs @@ -1,15 +1,14 @@ -pub(crate) mod bar; -pub(crate) mod draw; -pub(crate) mod font; - -use crate::dbg_win; -use crate::error::{Error, Result}; -use crate::manager::bar::BarManager; -use crate::manager::draw::Drawer; -use crate::x11::call_wrapper::{ - CallWrapper, DimensionsCookie, SingleCardCookie, SupportedAtom, WindowFloatDeduction, - WindowPropertiesCookie, WmStateCookie, +use unix_print::unix_eprintln; +use xcb_rust_protocol::cookie::FixedCookie; +use xcb_rust_protocol::helpers::properties::WmHints; +use xcb_rust_protocol::proto::xproto::{ + ButtonPressEvent, ButtonReleaseEvent, ConfigureNotifyEvent, ConfigureRequestEvent, + DestroyNotifyEvent, EnterNotifyEvent, GetWindowAttributesReply, KeyPressEvent, MapRequestEvent, + MapStateEnum, MotionNotifyEvent, NotifyModeEnum, PropertyNotifyEvent, QueryPointerReply, + UnmapNotifyEvent, VisibilityEnum, VisibilityNotifyEvent, Window, }; +use xcb_rust_protocol::util::AsIter32; + use pgwm_core::config::mouse_map::MouseTarget; #[cfg(feature = "status-bar")] use pgwm_core::config::STATUS_BAR_CHECK_CONTENT_LIMIT; @@ -25,26 +24,31 @@ use pgwm_core::state::workspace::{ ArrangeKind, DeleteResult, FocusStyle, ManagedWindow, Workspaces, }; use pgwm_core::state::{DragPosition, State, WinMarkedForDeath}; -use x11rb::cookie::Cookie; -use x11rb::properties::WmHints; -use x11rb::protocol::xproto::{ - ButtonPressEvent, ButtonReleaseEvent, ConfigureNotifyEvent, ConfigureRequestEvent, - DestroyNotifyEvent, EnterNotifyEvent, GetWindowAttributesReply, KeyPressEvent, MapRequestEvent, - MapState, MotionNotifyEvent, NotifyMode, PropertyNotifyEvent, QueryPointerReply, - UnmapNotifyEvent, Visibility, VisibilityNotifyEvent, Window, + +use crate::dbg_win; +use crate::error::{Error, Result}; +use crate::manager::bar::BarManager; +use crate::manager::draw::Drawer; +use crate::x11::call_wrapper::{ + CallWrapper, DimensionsCookie, SingleCardCookie, SupportedAtom, WindowFloatDeduction, + WindowPropertiesCookie, WmStateCookie, }; +pub(crate) mod bar; +pub(crate) mod draw; +pub(crate) mod font; + pub(crate) struct Manager<'a> { drawer: Drawer<'a>, bar_manager: BarManager<'a>, - cursor_handle: x11rb::cursor::Handle, + cursor_handle: xcb_rust_protocol::helpers::cursor::Handle, } impl<'a> Manager<'a> { pub(crate) fn new( drawer: Drawer<'a>, bar_manager: BarManager<'a>, - cursor_handle: x11rb::cursor::Handle, + cursor_handle: xcb_rust_protocol::helpers::cursor::Handle, ) -> Self { Self { drawer, @@ -56,11 +60,11 @@ impl<'a> Manager<'a> { pub(crate) fn init(&self, call_wrapper: &mut CallWrapper, state: &mut State) -> Result<()> { let ch_wa = call_wrapper.set_root_event_mask(&self.cursor_handle, state)?; ch_wa.check(call_wrapper.inner_mut())?; - pgwm_core::debug!("Set root event mask"); + pgwm_utils::debug!("Set root event mask"); self.bar_manager.draw_static(call_wrapper, state)?; - pgwm_core::debug!("Drew workspace sections"); + pgwm_utils::debug!("Drew workspace sections"); call_wrapper.set_default_manager_props(state)?; - pgwm_core::debug!("Drew default manager properties"); + pgwm_utils::debug!("Drew default manager properties"); Ok(()) } @@ -106,11 +110,11 @@ impl<'a> Manager<'a> { { if let Ok(attr) = attributes.reply(call_wrapper.inner_mut()) { let wm_state = wm_state.await_state(call_wrapper.inner_mut())?; - if !attr.override_redirect + if attr.override_redirect == 0 // If the window is a viewable top level -> manage // Additionally, when the WM starts up, if a WM state is set that's a pretty good // heuristic for whether or not to manage. - && (attr.map_state == MapState::VIEWABLE || wm_state.is_some()) + && (attr.map_state == MapStateEnum::VIEWABLE || wm_state.is_some()) && !state.intern_created_windows.contains(&window) { if transient_cookie.await_card(call_wrapper)?.is_some() { @@ -158,7 +162,7 @@ impl<'a> Manager<'a> { state: &mut State, ) -> Result<()> { state.last_timestamp = event.time; - if let Some(action) = state.get_key_action(event.detail, event.state) { + if let Some(action) = state.get_key_action(event.detail, event.state.0) { self.exec_action( call_wrapper, event.event, @@ -179,7 +183,7 @@ impl<'a> Manager<'a> { action: Action, state: &mut State, ) -> Result<()> { - pgwm_core::debug!("Executing action {action:?}"); + pgwm_utils::debug!("Executing action {action:?}"); match action { Action::Restart => { Self::cleanup(call_wrapper, state)?; @@ -191,14 +195,16 @@ impl<'a> Manager<'a> { } #[cfg_attr(feature = "perf-test", allow(unused_variables))] Action::Spawn(cmd, args) => { - pgwm_core::debug!("Spawning {} with args {:?}", cmd, args); + pgwm_utils::debug!("Spawning {} with args {:?}", cmd, args); #[cfg(not(feature = "perf-test"))] - std::process::Command::new(cmd) - .stdin(std::process::Stdio::null()) - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) - .args(args) - .spawn()?; + { + tiny_std::process::Command::new(cmd) + .args(&args) + .stdin(tiny_std::process::Stdio::Null) + .stdout(tiny_std::process::Stdio::Null) + .stderr(tiny_std::process::Stdio::Null) + .spawn()?; + } } Action::Close => { let win = focus_fallback_origin(origin, state); @@ -288,7 +294,7 @@ impl<'a> Manager<'a> { state.workspaces.send_window_to_front(ws_ind, target); if let Some(mon_ind) = state.find_monitor_hosting_workspace(ws_ind) { self.drawer.draw_on(call_wrapper, mon_ind, false, state)?; - pgwm_core::debug!("Sent {target} to front"); + pgwm_utils::debug!("Sent {target} to front"); self.focus_window(call_wrapper, mon_ind, target, state)?; } } @@ -298,7 +304,7 @@ impl<'a> Manager<'a> { let target_window = focus_fallback_origin(origin, state); if let Some(ws) = state.workspaces.find_ws_containing_window(target_window) { if ws == num { - pgwm_core::debug!("Tried to send to same workspace {}", num); + pgwm_utils::debug!("Tried to send to same workspace {}", num); } else { let properties = if let Some(removed_mw) = self .remove_win_from_state_then_redraw_if_tiled( @@ -332,7 +338,7 @@ impl<'a> Manager<'a> { if let Some(input_focus) = state.input_focus { if let Some(mon_ind) = state.find_monitor_index_of_window(input_focus) { if state.workspaces.un_float_window(input_focus).is_some() { - pgwm_core::debug!("Unfloating on mon {:?}", mon_ind); + pgwm_utils::debug!("Unfloating on mon {:?}", mon_ind); self.drawer.draw_on(call_wrapper, mon_ind, false, state)?; self.focus_window(call_wrapper, mon_ind, input_focus, state)?; } @@ -342,7 +348,7 @@ impl<'a> Manager<'a> { Action::FocusNextWindow => { if let Some(cur) = state.input_focus { if let Some(next) = state.workspaces.next_window(cur) { - pgwm_core::debug!("Focusnext from {:?} to {:?}", cur, next); + pgwm_utils::debug!("Focusnext from {:?} to {:?}", cur, next); self.focus_window(call_wrapper, state.focused_mon, next.window, state)?; } } @@ -405,7 +411,7 @@ impl<'a> Manager<'a> { let dimensions = dimensions.await_dimensions(call_wrapper.inner_mut())?; state.drag_window = Some((origin, DragPosition::new(dimensions.x, dimensions.y, x, y))); - pgwm_core::debug!("Dragging window {}", origin); + pgwm_utils::debug!("Dragging window {}", origin); } else { dimensions.inner.forget(call_wrapper.inner_mut()); } @@ -423,16 +429,16 @@ impl<'a> Manager<'a> { ) -> Result<()> { let props = call_wrapper.get_window_properties(event.window)?; let attrs = call_wrapper.get_window_attributes(event.window)?; - pgwm_core::debug!("MapRequest incoming for sequence {}", event.sequence); + pgwm_utils::debug!("MapRequest incoming for sequence {}", event.sequence); if let Ok(attrs) = attrs.reply(call_wrapper.inner_mut()) { - pgwm_core::debug!("Attributes {attrs:?}"); - if attrs.override_redirect { - pgwm_core::debug!("Override redirect, not managing"); + pgwm_utils::debug!("Attributes {attrs:?}"); + if attrs.override_redirect == 1 { + pgwm_utils::debug!("Override redirect, not managing"); props.forget(call_wrapper); return Ok(()); } } else { - pgwm_core::debug!("No attributes, not managing"); + pgwm_utils::debug!("No attributes, not managing"); props.forget(call_wrapper); return Ok(()); } @@ -453,7 +459,7 @@ impl<'a> Manager<'a> { call_wrapper.set_base_client_properties(win)?; let dimensions_cookie = call_wrapper.get_dimensions(win)?; let properties = window_properties_cookie.await_properties(call_wrapper)?; - pgwm_core::debug!("Managing window {:?}", win); + pgwm_utils::debug!("Managing window {:?}", win); let ws_ind = if let Some(ws_ind) = Self::map_window_class_to_workspace(call_wrapper, win, &state.workspaces)? { @@ -501,7 +507,7 @@ impl<'a> Manager<'a> { draw_on_mon: Option, state: &mut State, ) -> Result<()> { - pgwm_core::debug!("Managing tiled {win} attached to {attached_to:?}"); + pgwm_utils::debug!("Managing tiled {win} attached to {attached_to:?}"); let focus_style = Self::deduce_focus_style(&properties); if let Some(attached_to) = attached_to { if !state.workspaces.add_attached( @@ -511,7 +517,7 @@ impl<'a> Manager<'a> { focus_style, &properties, )? { - pgwm_core::debug!( + pgwm_utils::debug!( "Parent {attached_to} for window {win} not managed, will promote" ); state.workspaces.add_child_to_ws( @@ -554,7 +560,7 @@ impl<'a> Manager<'a> { // Explicitly true and take focus present means it's locally active if take_focus { FocusStyle::LocallyActive - // Explicitly true and no take focus means Passive + // Explicitly true and no take focus means Passive } else { FocusStyle::Passive } @@ -562,7 +568,7 @@ impl<'a> Manager<'a> { // Explicitly false and take focus means Globally active if take_focus { FocusStyle::GloballyActive - // Explicitly false and no take focus means No input + // Explicitly false and no take focus means No input } else { FocusStyle::NoInput } @@ -590,9 +596,9 @@ impl<'a> Manager<'a> { dimensions: Dimensions, state: &mut State, ) -> Result<()> { - pgwm_core::debug!("Managing floating {win} attached to {attached_to:?}"); + pgwm_utils::debug!("Managing floating {win} attached to {attached_to:?}"); let attached_to = if attached_to == Some(state.screen.root) { - pgwm_core::debug!("Parent was root, assigning floating to currently focused monitor"); + pgwm_utils::debug!("Parent was root, assigning floating to currently focused monitor"); let mon_ind = state.focused_mon; let new_parent = if let Some(last_focus) = state.monitors[mon_ind].last_focus { last_focus @@ -602,7 +608,7 @@ impl<'a> Manager<'a> { { first_tiled } else { - pgwm_core::debug!("Promoting window"); + pgwm_utils::debug!("Promoting window"); let ws_ind = state.monitors[mon_ind].hosted_workspace; self.manage_tiled( call_wrapper, @@ -615,7 +621,7 @@ impl<'a> Manager<'a> { )?; return Ok(()); }; - pgwm_core::debug!("Assigned to new parent {new_parent}"); + pgwm_utils::debug!("Assigned to new parent {new_parent}"); Some(new_parent) } else { attached_to @@ -623,9 +629,9 @@ impl<'a> Manager<'a> { let focus_style = Self::deduce_focus_style(&properties); if let Some(attached_to) = attached_to { let parent_dimensions = call_wrapper.get_dimensions(attached_to)?; - pgwm_core::debug!("Found attached {} to parent {}", win, attached_to); + pgwm_utils::debug!("Found attached {} to parent {}", win, attached_to); let parent_dimensions = parent_dimensions.await_dimensions(call_wrapper.inner_mut())?; - pgwm_core::debug!( + pgwm_utils::debug!( "Attached geometry {:?}\nParent geometry {:?}", dimensions, parent_dimensions @@ -641,7 +647,7 @@ impl<'a> Manager<'a> { (parent_dimensions.height - dimensions.height) as f32 / 2f32; let x = parent_dimensions.x as i32 + parent_relative_x_offset as i32; let y = parent_dimensions.y as i32 + parent_relative_y_offset as i32; - pgwm_core::debug!("Remapping attached to ({x}, {y})"); + pgwm_utils::debug!("Remapping attached to ({x}, {y})"); call_wrapper.move_window(win, x, y, state)?; Dimensions::new(dimensions.width, dimensions.height, x as i16, y as i16) @@ -678,6 +684,7 @@ impl<'a> Manager<'a> { Drawer::draw_floating(call_wrapper, win, dimensions, state)?; self.focus_window(call_wrapper, state.focused_mon, win, state)?; + crate::debug!("Drew window"); Ok(()) } @@ -830,8 +837,8 @@ impl<'a> Manager<'a> { let stacked_children = state.workspaces.get_all_tiled_windows(hosted_ws).len(); let bar_width = width / stacked_children as i16; for b in 0..stacked_children { - if event.event_x <= bar_width as i16 * (b + 1) as i16 { - pgwm_core::debug!("Selected bar number {}", b); + if event.event_x <= bar_width * (b + 1) as i16 { + pgwm_utils::debug!("Selected bar number {}", b); if state.workspaces.switch_tab_focus_index(hosted_ws, b) { let dm = state.workspaces.get_draw_mode(hosted_ws); self.drawer.draw_on(call_wrapper, mon_ind, false, state)?; @@ -852,10 +859,11 @@ impl<'a> Manager<'a> { } // Priority, always accept clicks on bar let target = if let Some(target) = state.get_hit_bar_component( - state - .pointer_grabbed - .then(|| event.child) - .unwrap_or(event.event), + if state.pointer_grabbed { + event.child.0 + } else { + event.event + }, event.root_x, mon_ind, ) { @@ -874,20 +882,20 @@ impl<'a> Manager<'a> { } else { if state.pointer_grabbed { // We grab pointer on root window, then the click is on event.child - pgwm_core::debug!("Focus change from pointer grabbed {event:?}"); - self.try_focus_window(call_wrapper, event.child, state)?; + pgwm_utils::debug!("Focus change from pointer grabbed {event:?}"); + self.try_focus_window(call_wrapper, event.child.0, state)?; return Ok(()); } Some(MouseTarget::ClientWindow) }; - pgwm_core::debug!("Button press for target {:?}", target); + pgwm_utils::debug!("Button press for target {:?}", target); if let Some(action) = - target.and_then(|tg| state.get_mouse_action(event.detail, event.state, tg)) + target.and_then(|tg| state.get_mouse_action(event.detail, event.state.0, tg)) { self.exec_action( call_wrapper, - event.child, + event.child.0, InputSource::Mouse(event.event_x, event.event_y), action.clone(), state, @@ -929,7 +937,7 @@ impl<'a> Manager<'a> { state.last_timestamp = event.time; if let Some((win, _drag)) = state.drag_window.take() { let win_dims = call_wrapper.get_dimensions(win)?; - pgwm_core::debug!("Got button release and removed drag window {win}"); + pgwm_utils::debug!("Got button release and removed drag window {win}"); let properties = self .remove_win_from_state_then_redraw_if_tiled(call_wrapper, win, state)? .into_option() @@ -977,36 +985,33 @@ impl<'a> Manager<'a> { call_wrapper.move_window(*win, x, y, state)?; } else if state.pointer_grabbed // Grabbed pointer on root makes the target event.child - && event.child != state.screen.root - && event.child != x11rb::NONE + && event.child.0 != state.screen.root + && event.child.0 != xcb_rust_protocol::NONE && state - .input_focus - .filter(|win| win == &event.child) - .is_none() + .input_focus + .filter(|win| win == &event.child.0) + .is_none() { if let Some(window) = state .workspaces - .get_managed_win(event.child) + .get_managed_win(event.child.0) .map(|mw| mw.window) { self.try_focus_window(call_wrapper, window, state)?; - pgwm_core::debug!("Updated focus to win: {}", window); + pgwm_utils::debug!("Updated focus to win: {}", window); } // No window targeted, check which monitor we're on - } else if event.event == state.screen.root && event.child == x11rb::NONE { + } else if event.event == state.screen.root && event.child.0 == xcb_rust_protocol::NONE { if let Some(mon) = state.find_monitor_at((event.root_x, event.root_y)) { if state.focused_mon != mon { self.focus_mon(call_wrapper, mon, state)?; - pgwm_core::debug!("Updated focus to mon: {mon}"); + pgwm_utils::debug!("Updated focus to mon: {mon}"); } } } Ok(()) } - /** - Only method that blindly refocuses, won't refocus on root because it feels strange as a user - if using the mouse between windows with padding - **/ + pub(crate) fn handle_enter( &self, call_wrapper: &mut CallWrapper, @@ -1014,7 +1019,7 @@ impl<'a> Manager<'a> { state: &mut State, ) -> Result<()> { state.last_timestamp = event.time; - if event.event != state.screen.root && event.mode != NotifyMode::GRAB { + if event.event != state.screen.root && event.mode != NotifyModeEnum::GRAB { self.try_focus_window(call_wrapper, event.event, state)?; } Ok(()) @@ -1024,15 +1029,13 @@ impl<'a> Manager<'a> { pub(crate) fn handle_client_message( &self, call_wrapper: &mut CallWrapper, - event: x11rb::protocol::xproto::ClientMessageEvent, + event: xcb_rust_protocol::proto::xproto::ClientMessageEvent, state: &mut State, ) -> Result<()> { - let atom = if let Some(atom) = call_wrapper.resolve_atom(event.type_) { - atom - } else { - pgwm_core::debug!( + let Some(atom) = call_wrapper.resolve_atom(event.r#type) else { + pgwm_utils::debug!( "Got client message for unresolved atom with name {:?}", - call_wrapper.get_atom_name(event.type_) + call_wrapper.get_atom_name(event.r#type) ); return Ok(()); }; @@ -1050,18 +1053,21 @@ impl<'a> Manager<'a> { SupportedAtom::NetWmState => { // https://specifications.freedesktop.org/wm-spec/1.3/ar01s05.html // 0th byte is 0 => Remove, 1 => Add, 2 => Toggle - let data = event.data.as_data32(); + // Todo: Double check + let atom = event.data.0.as_iter_32().next().unwrap(); + let data = event.data.0.as_iter_32(); + // 1st and 2nd bytes are possible atoms to change - for i in 1..3 { - if let Some(resolved) = call_wrapper.resolve_atom(data[i]) { - pgwm_core::debug!("Resolved atom in position {i} to {resolved:?}"); + for (_i, value) in data.take(3).enumerate() { + if let Some(resolved) = call_wrapper.resolve_atom(value) { + pgwm_utils::debug!("Resolved atom in position {_i} to {resolved:?}"); match resolved.intern_atom { SupportedAtom::NetWmStateModal => { let dimensions = call_wrapper.get_dimensions(event.window)?; if let Some((mon_ind, ws_ind)) = state.find_monitor_and_ws_indices_of_window(event.window) { - match data[0] { + match atom { 0 => { dimensions.inner.forget(call_wrapper.inner_mut()); self.unfloat_window_redraw( @@ -1105,15 +1111,15 @@ impl<'a> Manager<'a> { } } SupportedAtom::NetWmStateFullscreen => { - pgwm_core::debug!( + pgwm_utils::debug!( "Got set fullscreen to {} for window {}", - data[0], + atom, event.window ); if let Some((mon_ind, ws_ind)) = state.find_monitor_and_ws_indices_of_window(event.window) { - match data[0] { + match atom { 0 => { self.unset_fullscreen( call_wrapper, @@ -1155,7 +1161,7 @@ impl<'a> Manager<'a> { if let Some(managed) = state.workspaces.get_managed_win(event.window) { - match data[0] { + match atom { 0 => { self.make_window_not_urgent( call_wrapper, @@ -1195,7 +1201,7 @@ impl<'a> Manager<'a> { } } _ => { - pgwm_core::debug!("Got clientmessage on supported atom {:?}", atom); + pgwm_utils::debug!("Got clientmessage on supported atom {:?}", atom); } } Ok(()) @@ -1266,10 +1272,12 @@ impl<'a> Manager<'a> { call_wrapper.set_net_wm_state(win, mw.properties.net_wm_state)?; } } - pgwm_core::debug!("Client requested focus {win:?} and it was granted"); + pgwm_utils::debug!("Client requested focus {win:?} and it was granted"); } } else { - pgwm_core::debug!("Client requested focus {win:?} denied because it's not managed"); + pgwm_utils::debug!( + "Client requested focus {win:?} denied because it's not managed" + ); } } Ok(()) @@ -1369,7 +1377,7 @@ impl<'a> Manager<'a> { heapless::String::from("pgwm"), state, )?; - pgwm_core::debug!("Focused root on mon = {}", mon_ind); + pgwm_utils::debug!("Focused root on mon = {}", mon_ind); Ok(()) } @@ -1384,7 +1392,7 @@ impl<'a> Manager<'a> { self.focus_window(call_wrapper, mon_ind, win, state)?; Ok(true) } else { - pgwm_core::debug!("Failed to focus {win} not found on a monitor hosted workspace"); + pgwm_utils::debug!("Failed to focus {win} not found on a monitor hosted workspace"); Ok(false) } } @@ -1396,7 +1404,7 @@ impl<'a> Manager<'a> { state: &mut State, ) -> Result<()> { if let Some(focus_candidate) = state.find_first_focus_candidate(mon_ind)? { - pgwm_core::debug!("Found focus candidate {focus_candidate:?}"); + pgwm_utils::debug!("Found focus candidate {focus_candidate:?}"); self.focus_window(call_wrapper, mon_ind, focus_candidate, state) } else { self.focus_root_on_mon(call_wrapper, mon_ind, state) @@ -1455,7 +1463,7 @@ impl<'a> Manager<'a> { } else if let Some(mw) = state.workspaces.get_managed_win(win) { (mw.window, mw.focus_style, mw.properties.name.get_cloned()) } else { - pgwm_core::debug!("Focusing unmanaged window {win}"); + pgwm_utils::debug!("Focusing unmanaged window {win}"); // Unmanaged window if let Ok(properties) = call_wrapper .get_window_properties(win)? @@ -1467,7 +1475,7 @@ impl<'a> Manager<'a> { properties.name.get_cloned(), ) } else { - pgwm_core::debug!("Could not focus unmanaged window {win}"); + pgwm_utils::debug!("Could not focus unmanaged window {win}"); pointer_pos.forget(call_wrapper.inner_mut()); return Ok(()); } @@ -1475,7 +1483,7 @@ impl<'a> Manager<'a> { self.make_window_not_urgent(call_wrapper, win, state)?; Self::highlight_border(call_wrapper, win, state)?; // Highlighting the base window even if a top level transient is focused if let Some(old_focused_mon) = state.update_focused_mon(mon_ind) { - pgwm_core::debug!("Switched focus from {} to {}", old_focused_mon, mon_ind); + pgwm_utils::debug!("Switched focus from {} to {}", old_focused_mon, mon_ind); self.bar_manager.set_workspace_selected_not_focused( call_wrapper, old_focused_mon, @@ -1494,13 +1502,13 @@ impl<'a> Manager<'a> { state.monitors[mon_ind].last_focus.replace(focus_target); state.input_focus.replace(win); - pgwm_core::debug!("Taking focus for {win}"); + pgwm_utils::debug!("Taking focus for {win}"); call_wrapper.take_focus(state.screen.root, win, focus_style, state)?; - pgwm_core::debug!("Getting pointer position"); + pgwm_utils::debug!("Getting pointer position"); let pointer_pos = pointer_pos.reply(call_wrapper.inner_mut())?; Self::capture_pointer_if_outside_window(call_wrapper, focus_target, pointer_pos, state)?; self.update_current_window_title_and_redraw(call_wrapper, mon_ind, name, state)?; - pgwm_core::debug!("Focused {:?} on mon {mon_ind}", focus_target); + pgwm_utils::debug!("Focused {:?} on mon {mon_ind}", focus_target); Ok(()) } @@ -1520,7 +1528,7 @@ impl<'a> Manager<'a> { .filter(|mw| *mw == win) .is_none() { - pgwm_core::debug!("Redrawing tab on focus change"); + pgwm_utils::debug!("Redrawing tab on focus change"); self.drawer.draw_on(call_wrapper, mon_ind, false, state)?; } Ok(true) @@ -1562,7 +1570,7 @@ impl<'a> Manager<'a> { query_pointer_reply: QueryPointerReply, state: &mut State, ) -> Result<()> { - let pointer_on_window = query_pointer_reply.child == window; + let pointer_on_window = query_pointer_reply.child.0 == window; if pointer_on_window { Self::conditional_ungrab_pointer(call_wrapper, state)?; } else { @@ -1582,10 +1590,8 @@ impl<'a> Manager<'a> { if event.window == state.screen.root { return Ok(()); } - let resolved = if let Some(supported_atom) = call_wrapper.resolve_atom(event.atom) { - supported_atom - } else { - pgwm_core::debug!( + let Some(resolved) = call_wrapper.resolve_atom(event.atom) else { + pgwm_utils::debug!( "Got unsupported atom on property change {:?}", call_wrapper.get_atom_name(event.atom) ); @@ -1597,7 +1603,7 @@ impl<'a> Manager<'a> { .get_class_names(event.window)? .await_class_names(call_wrapper.inner_mut())? { - eprintln!( + unix_eprintln!( "Got new class names {class_names:?} for win {}", event.window ); @@ -1622,7 +1628,7 @@ impl<'a> Manager<'a> { let update_title = if let Some(mw) = state.workspaces.get_managed_win_mut(event.window) { if matches!(mw.properties.name, WmName::NetWmName(_)) { - pgwm_core::debug!( + pgwm_utils::debug!( "Not updating window with NetWmName {} to WmName", event.window ); @@ -1679,10 +1685,10 @@ impl<'a> Manager<'a> { if let Ok(hints) = WmHints::get(call_wrapper.inner_mut(), event.window)? .reply(call_wrapper.inner_mut()) { - pgwm_core::debug!("Got new wm hints {hints:?}"); + pgwm_utils::debug!("Got new wm hints {hints:?}"); if hints.urgent { // Making something not urgent happens through focusing - pgwm_core::debug!("Got wm hint for urgency for window {}", event.window); + pgwm_utils::debug!("Got wm hint for urgency for window {}", event.window); self.make_window_urgent(call_wrapper, event.window, state)?; } if let Some(mut mw) = state.workspaces.get_managed_win_mut(event.window) { @@ -1695,7 +1701,7 @@ impl<'a> Manager<'a> { let wm_state = call_wrapper .get_wm_state(event.window)? .await_state(call_wrapper.inner_mut())?; - pgwm_core::debug!( + pgwm_utils::debug!( "Got wm state change for win {} new state {:?}", event.window, wm_state @@ -1712,7 +1718,7 @@ impl<'a> Manager<'a> { if let Some(mw) = state.workspaces.get_managed_win_mut(event.window) { let cur_float_deduction = float_status(&mw.properties, state.screen.root); let new_types = window_types.await_types(call_wrapper)?; - pgwm_core::debug!( + pgwm_utils::debug!( "Win {} got new NetWmWindowTypes {:?}", event.window, new_types @@ -1722,7 +1728,7 @@ impl<'a> Manager<'a> { (new_float_deduction, cur_float_deduction) } else { #[cfg(feature = "debug")] - pgwm_core::debug!( + pgwm_utils::debug!( "Win {} got new NetWmWindowTypes {:?}", event.window, window_types.await_types(call_wrapper) @@ -1773,7 +1779,7 @@ impl<'a> Manager<'a> { } } _ => { - pgwm_core::debug!( + pgwm_utils::debug!( "Got supported atom with no action on property change {:?}", resolved, ); @@ -1809,7 +1815,7 @@ impl<'a> Manager<'a> { for class in class_names { if let Some(ind) = state.workspaces.find_ws_for_window_class_name(class) { if mapped != ind { - pgwm_core::debug!("Remapping from {} to {} on prop change", mapped, ind); + pgwm_utils::debug!("Remapping from {} to {} on prop change", mapped, ind); // We know it's present because of the above check let removed = self .remove_win_from_state_then_redraw_if_tiled(call_wrapper, win, state)? @@ -1838,7 +1844,7 @@ impl<'a> Manager<'a> { event: VisibilityNotifyEvent, state: &mut State, ) -> Result<()> { - if event.state == Visibility::UNOBSCURED { + if event.state == VisibilityEnum::UNOBSCURED { for mon_ind in 0..state.monitors.len() { if state.monitors[mon_ind].bar_win.window.drawable == event.window { self.bar_manager.redraw_on(call_wrapper, mon_ind, state)?; @@ -1856,13 +1862,13 @@ impl<'a> Manager<'a> { state: &mut State, ) -> Result<()> { let recv_prev_ws = state.monitors[recv_mon_ind].hosted_workspace; - pgwm_core::debug!( + pgwm_utils::debug!( "Mapping workspace {} to main window {}", ws_ind, recv_mon_ind ); if recv_prev_ws == ws_ind { - pgwm_core::debug!("Got request to replace ws with itself, skipping."); + pgwm_utils::debug!("Got request to replace ws with itself, skipping."); } else if let Some(send_mon_ind) = state.find_monitor_hosting_workspace(ws_ind) { self.bar_manager .set_workspace_unfocused(call_wrapper, send_mon_ind, ws_ind, state)?; @@ -1897,7 +1903,7 @@ impl<'a> Manager<'a> { mon.last_focus.take(); self.drawer .draw_on(call_wrapper, recv_mon_ind, true, state)?; - pgwm_core::debug!("Updating focus"); + pgwm_utils::debug!("Updating focus"); self.bar_manager .set_workspace_focused(call_wrapper, recv_mon_ind, ws_ind, state)?; self.bar_manager.set_workspace_unfocused( @@ -1919,7 +1925,7 @@ impl<'a> Manager<'a> { let wm_classes = call_wrapper .get_class_names(win)? .await_class_names(call_wrapper.inner_mut())?; - pgwm_core::debug!("WM_CLASS {:?}", wm_classes); + pgwm_utils::debug!("WM_CLASS {:?}", wm_classes); if let Some(wm_classes) = wm_classes { for class in wm_classes { if let Some(ind) = workspaces.find_ws_for_window_class_name(&class) { @@ -2047,10 +2053,10 @@ impl<'a> Manager<'a> { if candidate.should_kill(state.kill_after) { call_wrapper.send_kill(candidate.win)?; pgwm_core::util::vec_ops::remove(&mut state.dying_windows, 0); - pgwm_core::debug!("Sent kill for marked window {candidate:?}"); + pgwm_utils::debug!("Sent kill for marked window {candidate:?}"); } else if candidate.should_destroy() { call_wrapper.send_destroy(candidate.win)?; - pgwm_core::debug!("Sent destroy for marked window {candidate:?}"); + pgwm_utils::debug!("Sent destroy for marked window {candidate:?}"); candidate.sent_destroy = true; break; } else { @@ -2072,7 +2078,7 @@ impl<'a> Manager<'a> { state.dying_windows, WinMarkedForDeath::new(win, state.destroy_after) )?; - pgwm_core::debug!("Marked win {win} for death"); + pgwm_utils::debug!("Marked win {win} for death"); Ok(()) } @@ -2185,7 +2191,7 @@ fn toggle_tabbed(mon_ind: usize, ws_ind: usize, state: &mut State) -> Result, + attributes: FixedCookie, transient_cookie: SingleCardCookie, wm_state: WmStateCookie, prop_cookie: WindowPropertiesCookie, diff --git a/pgwm/src/util.rs b/pgwm-app/src/util.rs similarity index 100% rename from pgwm/src/util.rs rename to pgwm-app/src/util.rs diff --git a/pgwm/src/wm.rs b/pgwm-app/src/wm.rs similarity index 66% rename from pgwm/src/wm.rs rename to pgwm-app/src/wm.rs index 6e0caab..b97259f 100644 --- a/pgwm/src/wm.rs +++ b/pgwm-app/src/wm.rs @@ -1,3 +1,22 @@ +use alloc::vec::Vec; +use core::time::Duration; +use smallmap::Map; +use tiny_std::signal::{CatchSignal, SaSignalaction}; +use xcb_rust_protocol::connection::render::RenderConnection; +use xcb_rust_protocol::proto::render::{PictTypeEnum, Pictformat, Pictforminfo}; +use xcb_rust_protocol::proto::xproto::{ + ButtonPressEvent, ButtonReleaseEvent, ClientMessageEvent, ConfigureNotifyEvent, + ConfigureRequestEvent, DestroyNotifyEvent, EnterNotifyEvent, KeyPressEvent, MapRequestEvent, + MotionNotifyEvent, PropertyNotifyEvent, Screen, UnmapNotifyEvent, VisibilityNotifyEvent, + Visualid, +}; +use xcb_rust_protocol::util::FixedLengthFromBytes; +use xcb_rust_protocol::{XcbConnection, XcbEnv}; + +use pgwm_core::config::{BarCfg, Cfg, Options, Sizing}; +use pgwm_core::render::{RenderVisualInfo, VisualInfo}; +use pgwm_core::state::State; + use crate::error::{Error, Result}; use crate::manager; use crate::manager::bar::BarManager; @@ -6,21 +25,8 @@ use crate::manager::font::{load_alloc_fonts, FontDrawer, LoadedFonts}; use crate::manager::Manager; use crate::x11::call_wrapper::CallWrapper; use crate::x11::colors::alloc_colors; -use pgwm_core::config::{BarCfg, Cfg, Options, Sizing}; -use pgwm_core::render::{RenderVisualInfo, VisualInfo}; -use pgwm_core::state::State; -use std::collections::HashMap; -use std::time::Duration; -use x11rb::protocol::render::{PictType, Pictformat, Pictforminfo}; -use x11rb::protocol::xproto::{ - ButtonPressEvent, ButtonReleaseEvent, ClientMessageEvent, ConfigureNotifyEvent, - ConfigureRequestEvent, DestroyNotifyEvent, EnterNotifyEvent, KeyPressEvent, MapRequestEvent, - MotionNotifyEvent, PropertyNotifyEvent, Screen, UnmapNotifyEvent, VisibilityNotifyEvent, - Visualid, -}; -pub type XorgConnection = x11rb::SocketConnection; -use x11rb::x11_utils::TryParse; +pub type XorgConnection = xcb_rust_connection::connection::SocketConnection; #[allow(clippy::too_many_lines)] pub(crate) fn run_wm() -> Result<()> { @@ -36,7 +42,10 @@ pub(crate) fn run_wm() -> Result<()> { mouse_mappings, key_mappings, bar, - } = Cfg::new()?; + } = Cfg::new( + tiny_std::env::var("XDG_CONFIG_HOME").ok(), + tiny_std::env::var("HOME").ok(), + )?; #[cfg(not(feature = "status-bar"))] let Cfg { sizing, @@ -49,7 +58,10 @@ pub(crate) fn run_wm() -> Result<()> { mouse_mappings, key_mappings, bar, - } = Cfg::new()?; + } = Cfg::new( + tiny_std::env::var("XDG_CONFIG_HOME").ok(), + tiny_std::env::var("HOME").ok(), + )?; let Sizing { status_bar_height, tab_bar_height, @@ -73,25 +85,45 @@ pub(crate) fn run_wm() -> Result<()> { let dpy = Some(":4"); #[cfg(not(feature = "perf-test"))] let dpy = None; - let (connection, screen_num) = XorgConnection::connect(dpy)?; + // We just spawn user stuff, we don't care when they terminate, could signalfd -> poll if we did + // without the raw unsafety of setting up a signal handler + unsafe { + tiny_std::signal::add_signal_action(CatchSignal::SIGCHLD, SaSignalaction::Ign)?; + } + let xcb_env = env_to_xcb_env(); + let (mut connection, screen_num) = XorgConnection::connect(dpy, xcb_env)?; let setup = connection.setup().clone(); - + connection.flush()?; + pgwm_utils::debug!("Connected"); let screen = &setup.roots[screen_num]; let mut call_wrapper = CallWrapper::new(connection)?; - + pgwm_utils::debug!("Set up call wrapper"); + call_wrapper.inner_mut().sync()?; call_wrapper.try_become_wm(screen)?; - pgwm_core::debug!("Became wm"); - pgwm_core::debug!("Got resource database properties"); - let resource_db = x11rb::resource_manager::new_from_default(call_wrapper.inner_mut())?; - let cursor_handle = x11rb::cursor::Handle::new(call_wrapper.inner_mut(), 0, &resource_db)?; + pgwm_utils::debug!("Became wm"); + pgwm_utils::debug!("Got resource database properties"); + let resource_db = xcb_rust_protocol::helpers::resource_manager::new_from_default( + call_wrapper.inner_mut(), + tiny_std::env::var("HOME").ok(), + tiny_std::env::var("XENVIRONMENT").ok(), + )?; + let cursor_handle = xcb_rust_protocol::helpers::cursor::Handle::new( + call_wrapper.inner_mut(), + screen_num, + &resource_db, + xcb_env, + )?; let visual = find_render_visual_info(call_wrapper.inner_mut(), screen)?; let loaded = load_alloc_fonts(&mut call_wrapper, &visual, &fonts, &char_remap)?; + crate::debug!("Loaded {} fonts", loaded.len()); let lf = LoadedFonts::new(loaded, &char_remap)?; - let font_drawer = FontDrawer::new(&lf); + call_wrapper.inner_mut().flush()?; + crate::debug!("Font drawer initialized"); let colors = alloc_colors(call_wrapper.inner_mut(), screen.default_colormap, colors)?; + crate::debug!("Allocated colors"); - pgwm_core::debug!("Creating state"); + pgwm_utils::debug!("Creating state"); let mut state = crate::x11::state_lifecycle::create_state( &mut call_wrapper, &font_drawer, @@ -151,6 +183,7 @@ pub(crate) fn run_wm() -> Result<()> { match e { Error::StateInvalidated => { crate::x11::state_lifecycle::teardown_dynamic_state(&mut call_wrapper, &state)?; + call_wrapper.inner_mut().sync()?; state = crate::x11::state_lifecycle::reinit_state( &mut call_wrapper, &font_drawer, @@ -172,7 +205,9 @@ pub(crate) fn run_wm() -> Result<()> { &state, &lf, )?; - call_wrapper.reset_root_focus(&state)?; + call_wrapper.reset_root_window(&state)?; + call_wrapper.inner_mut().sync()?; + drop(call_wrapper); return Ok(()); } Error::FullRestart => { @@ -181,7 +216,9 @@ pub(crate) fn run_wm() -> Result<()> { &state, &lf, )?; - call_wrapper.reset_root_focus(&state)?; + call_wrapper.reset_root_window(&state)?; + call_wrapper.inner_mut().sync()?; + drop(call_wrapper); return Err(Error::FullRestart); } _ => { @@ -192,23 +229,35 @@ pub(crate) fn run_wm() -> Result<()> { } } +fn env_to_xcb_env() -> XcbEnv<'static> { + XcbEnv { + home_dir: tiny_std::env::var("HOME").ok(), + x_environment: tiny_std::env::var("XENVIRONMENT").ok(), + x_authority: tiny_std::env::var("XAUTHORITY").ok(), + display: tiny_std::env::var("DISPLAY").ok(), + x_cursor_size: tiny_std::env::var("XCURSOR_SIZE").ok(), + } +} + #[cfg(feature = "status-bar")] -fn loop_with_status<'a>( +fn loop_with_status( call_wrapper: &mut CallWrapper, - manager: &Manager<'a>, + manager: &Manager, checker: &mut pgwm_core::status::checker::Checker, state: &mut State, ) -> Result<()> { - let mut next_check = std::time::Instant::now(); + let mut next_check = tiny_std::time::Instant::now(); // Extremely hot place in the code, should bench the checker loop { // This looks dumb... Anyway, avoiding an unnecessary poll and going straight to status update // if no new events or duration is now. - while let Some(event) = call_wrapper - .inner_mut() - .read_next_event(next_check.duration_since(std::time::Instant::now()))? - { + while let Some(event) = call_wrapper.inner_mut().read_next_event( + next_check + .duration_since(tiny_std::time::Instant::now()) + .unwrap_or_default(), + )? { handle_event(event, call_wrapper, manager, state)?; + call_wrapper.inner_mut().flush()?; } // Status wants redraw and no new events. let dry_run = !state.any_monitors_showing_status(); @@ -219,6 +268,7 @@ fn loop_with_status<'a>( next_check = next.next_check; // Check destroyed, not that important so moved from event handling flow Manager::destroy_marked(call_wrapper, state)?; + call_wrapper.inner_mut().flush()?; #[cfg(feature = "debug")] call_wrapper.inner_mut().clear_cache()?; } @@ -234,8 +284,10 @@ fn loop_without_status<'a>( loop { while let Some(event) = call_wrapper.inner_mut().read_next_event(DEADLINE)? { handle_event(event, call_wrapper, manager, state)?; + call_wrapper.inner_mut().flush()?; } Manager::destroy_marked(call_wrapper, state)?; + call_wrapper.inner_mut().flush()?; #[cfg(feature = "debug")] call_wrapper.inner_mut().clear_cache()?; } @@ -259,96 +311,96 @@ fn handle_event<'a>( dbg_event(&raw, &call_wrapper.inner_mut().extensions); // Unmap and enter are caused by upstream actions, causing unwanted focusing behaviour etc. if state.should_ignore_sequence(seq) - && (response_type == x11rb::protocol::xproto::ENTER_NOTIFY_EVENT - || response_type == x11rb::protocol::xproto::UNMAP_NOTIFY_EVENT) + && (response_type == xcb_rust_protocol::proto::xproto::ENTER_NOTIFY_EVENT + || response_type == xcb_rust_protocol::proto::xproto::UNMAP_NOTIFY_EVENT) { - pgwm_core::debug!("[Ignored]"); + pgwm_utils::debug!("[Ignored]"); return Ok(()); } match response_type { - x11rb::protocol::xproto::KEY_PRESS_EVENT => { + xcb_rust_protocol::proto::xproto::KEY_PRESS_EVENT => { manager.handle_key_press( call_wrapper, - KeyPressEvent::try_parse(&raw).unwrap().0, + KeyPressEvent::from_bytes(&raw).unwrap(), state, )?; } - x11rb::protocol::xproto::MAP_REQUEST_EVENT => { + xcb_rust_protocol::proto::xproto::MAP_REQUEST_EVENT => { manager.handle_map_request( call_wrapper, - MapRequestEvent::try_parse(&raw).unwrap().0, + MapRequestEvent::from_bytes(&raw).unwrap(), state, )?; } - x11rb::protocol::xproto::UNMAP_NOTIFY_EVENT => { - let evt = UnmapNotifyEvent::try_parse(&raw).unwrap().0; + xcb_rust_protocol::proto::xproto::UNMAP_NOTIFY_EVENT => { + let evt = UnmapNotifyEvent::from_bytes(&raw).unwrap(); manager.handle_unmap_notify(call_wrapper, evt, state)?; } - x11rb::protocol::xproto::DESTROY_NOTIFY_EVENT => { + xcb_rust_protocol::proto::xproto::DESTROY_NOTIFY_EVENT => { manager.handle_destroy_notify( call_wrapper, - DestroyNotifyEvent::try_parse(&raw).unwrap().0, + DestroyNotifyEvent::from_bytes(&raw).unwrap(), state, )?; } - x11rb::protocol::xproto::CONFIGURE_NOTIFY_EVENT => { + xcb_rust_protocol::proto::xproto::CONFIGURE_NOTIFY_EVENT => { Manager::handle_configure_notify( call_wrapper, - ConfigureNotifyEvent::try_parse(&raw).unwrap().0, + ConfigureNotifyEvent::from_bytes(&raw).unwrap(), state, )?; } - x11rb::protocol::xproto::CONFIGURE_REQUEST_EVENT => { + xcb_rust_protocol::proto::xproto::CONFIGURE_REQUEST_EVENT => { Manager::handle_configure_request( call_wrapper, - ConfigureRequestEvent::try_parse(&raw).unwrap().0, + ConfigureRequestEvent::from_bytes(&raw).unwrap(), state, )?; } - x11rb::protocol::xproto::BUTTON_PRESS_EVENT => { + xcb_rust_protocol::proto::xproto::BUTTON_PRESS_EVENT => { manager.handle_button_press( call_wrapper, - ButtonPressEvent::try_parse(&raw).unwrap().0, + ButtonPressEvent::from_bytes(&raw).unwrap(), state, )?; } - x11rb::protocol::xproto::BUTTON_RELEASE_EVENT => { + xcb_rust_protocol::proto::xproto::BUTTON_RELEASE_EVENT => { manager.handle_button_release( call_wrapper, - ButtonReleaseEvent::try_parse(&raw).unwrap().0, + ButtonReleaseEvent::from_bytes(&raw).unwrap(), state, )?; } - x11rb::protocol::xproto::MOTION_NOTIFY_EVENT => { + xcb_rust_protocol::proto::xproto::MOTION_NOTIFY_EVENT => { manager.handle_motion_notify( call_wrapper, - MotionNotifyEvent::try_parse(&raw).unwrap().0, + MotionNotifyEvent::from_bytes(&raw).unwrap(), state, )?; } - x11rb::protocol::xproto::ENTER_NOTIFY_EVENT => { - let evt = EnterNotifyEvent::try_parse(&raw).unwrap().0; + xcb_rust_protocol::proto::xproto::ENTER_NOTIFY_EVENT => { + let evt = EnterNotifyEvent::from_bytes(&raw).unwrap(); manager.handle_enter(call_wrapper, evt, state)?; } - x11rb::protocol::xproto::CLIENT_MESSAGE_EVENT => { + xcb_rust_protocol::proto::xproto::CLIENT_MESSAGE_EVENT => { manager.handle_client_message( call_wrapper, - ClientMessageEvent::try_parse(&raw).unwrap().0, + ClientMessageEvent::from_bytes(&raw).unwrap(), state, )?; } - x11rb::protocol::xproto::PROPERTY_NOTIFY_EVENT => { + xcb_rust_protocol::proto::xproto::PROPERTY_NOTIFY_EVENT => { manager.handle_property_notify( call_wrapper, - PropertyNotifyEvent::try_parse(&raw).unwrap().0, + PropertyNotifyEvent::from_bytes(&raw).unwrap(), state, )?; } - x11rb::protocol::xproto::VISIBILITY_NOTIFY_EVENT => { + xcb_rust_protocol::proto::xproto::VISIBILITY_NOTIFY_EVENT => { manager.handle_visibility_change( call_wrapper, - VisibilityNotifyEvent::try_parse(&raw).unwrap().0, + VisibilityNotifyEvent::from_bytes(&raw).unwrap(), state, )?; } @@ -358,13 +410,16 @@ fn handle_event<'a>( } #[cfg(feature = "debug")] -fn dbg_event(raw: &[u8], ext_info_provider: &x11rb::x11_utils::ExtensionInfoProvider) { - match x11rb::xcb::Event::parse(raw, ext_info_provider) { +fn dbg_event( + raw: &[u8], + ext_info_provider: &xcb_rust_connection::helpers::basic_info_provider::BasicExtensionInfoProvider, +) { + match xcb_rust_protocol::proto::Event::from_bytes(raw, ext_info_provider) { Ok(evt) => { - eprintln!("{evt:?}"); + crate::debug!("Got event: {evt:?}"); } Err(e) => { - eprintln!("Failed to parse event {e}"); + crate::debug!("Failed to parse event {e}"); } } } @@ -384,15 +439,15 @@ fn find_appropriate_visual( depth: u8, match_visual_id: Option, ) -> Result { - let formats = x11rb::xcb::render::query_pict_formats(connection, false)?.reply(connection)?; + let formats = connection.query_pict_formats(false)?.reply(connection)?; let candidates = formats .formats .into_iter() // Need a 32 bit depth visual .filter_map(|pfi| { - (pfi.type_ == PictType::DIRECT && pfi.depth == depth).then(|| (pfi.id, pfi)) + (pfi.r#type == PictTypeEnum::DIRECT && pfi.depth == depth).then_some((pfi.id, pfi)) }) - .collect::>(); + .collect::>(); // Should only be one for screen in formats.screens { let candidate = screen.depths.into_iter().find_map(|pd| { diff --git a/pgwm/src/x11/call_wrapper.rs b/pgwm-app/src/x11/call_wrapper.rs similarity index 80% rename from pgwm/src/x11/call_wrapper.rs rename to pgwm-app/src/x11/call_wrapper.rs index 25962ea..d53acf0 100644 --- a/pgwm/src/x11/call_wrapper.rs +++ b/pgwm-app/src/x11/call_wrapper.rs @@ -1,20 +1,27 @@ -use crate::error::{Error, Result}; +use alloc::string::String; +use alloc::vec::Vec; + use smallmap::Map; -use x11rb::cookie::{Cookie, VoidCookie}; -use x11rb::protocol::render::{ - CreatePictureAux, Glyphinfo, Glyphset, PictOp, Picture, PolyEdge, PolyMode, Repeat, +use xcb_rust_protocol::connection::render::RenderConnection; +use xcb_rust_protocol::connection::xproto::XprotoConnection; +use xcb_rust_protocol::cookie::{Cookie, FixedCookie, VoidCookie}; +use xcb_rust_protocol::helpers::properties::{ + WmHints, WmHintsCookie, WmSizeHints, WmSizeHintsCookie, }; -use x11rb::protocol::xproto::{ - Atom, AtomEnum, ChangeWindowAttributesAux, ClientMessageEvent, ConfigureRequestEvent, - ConfigureWindowAux, EventMask, GetPropertyReply, GetWindowAttributesReply, GrabMode, - InputFocus, InternAtomReply, PropMode, QueryPointerReply, Screen, StackMode, Timestamp, Window, +use xcb_rust_protocol::helpers::{new_client_message32, CanIterFormats, Iter32, XcbEnv}; +use xcb_rust_protocol::proto::render::{ + CreatePictureValueList, Glyphinfo, Glyphset, PictOpEnum, Picture, PolyEdgeEnum, PolyModeEnum, + RepeatEnum, }; -use x11rb::protocol::ErrorKind; -use x11rb::xcb::xproto; -use x11rb::{CURRENT_TIME, NONE}; +use xcb_rust_protocol::proto::xproto::{ + Atom, AtomEnum, ChangeWindowAttributesValueList, ConfigWindow, ConfigureRequestEvent, + ConfigureWindowValueList, CursorEnum, EventMask, GetGeometryReply, GetPropertyReply, + GetPropertyTypeEnum, GetWindowAttributesReply, GrabModeEnum, InputFocusEnum, InternAtomReply, + PropModeEnum, QueryPointerReply, QueryTreeReply, Screen, StackModeEnum, Timestamp, Window, + WindowEnum, +}; +use xcb_rust_protocol::{XcbConnection, CURRENT_TIME, NONE}; -use crate::error::Error::GlyphMismatch; -use crate::wm::XorgConnection; use pgwm_core::config::{ APPLICATION_WINDOW_LIMIT, WINDOW_MANAGER_NAME, WINDOW_MANAGER_NAME_BUF_SIZE, WM_CLASS_NAME_LIMIT, WM_NAME_LIMIT, @@ -27,9 +34,10 @@ use pgwm_core::state::properties::{ }; use pgwm_core::state::workspace::FocusStyle; use pgwm_core::state::State; -use x11rb::errors::ReplyError; -use x11rb::properties::{WmHints, WmHintsCookie, WmSizeHints, WmSizeHintsCookie}; -use x11rb::xcb::xproto::{GetGeometryReply, QueryTreeReply}; + +use crate::error::Error::GlyphMismatch; +use crate::error::{Error, Result}; +use crate::wm::XorgConnection; const MAX_STORED_ATOMS: usize = 64; @@ -39,7 +47,7 @@ pub(crate) trait PropFirstU32 { impl PropFirstU32 for GetPropertyReply { fn first_u32(&self) -> Option { - self.value32().and_then(|mut it| it.next()) + Iter32::new(&self.value).next() } } @@ -59,14 +67,14 @@ macro_rules! impl_atoms { fn init_maps(connection: &mut XorgConnection) -> Result<(Map<&'static [u8], ResolvedAtom>, Map)> { let mut name_to_atom = Map::new(); let mut atom_to_resolved = Map::new(); - let mut cookies = heapless::Deque::, 64>::new(); + let mut cookies = heapless::Deque::, 64>::new(); $( - cookies.push_back(xproto::intern_atom(connection, false, $const_name, false)?) + cookies.push_back(XprotoConnection::intern_atom(connection, 0, $const_name, false)?) .expect("Not enough space for intern atoms"); )* $( - let atom = cookies.pop_front().unwrap().reply(connection)?.atom; + let atom = cookies.pop_front().unwrap().reply(connection)?.atom.0; name_to_atom.insert( $const_name, ResolvedAtom { @@ -291,19 +299,20 @@ pub(crate) struct CallWrapper { impl CallWrapper { pub(crate) fn try_become_wm(&mut self, screen: &Screen) -> Result<()> { - let change = ChangeWindowAttributesAux::default() + let change = ChangeWindowAttributesValueList::default() .event_mask(EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY); - - let res = - xproto::change_window_attributes(&mut self.connection, screen.root, &change, false)? - .check(&mut self.connection); - if let Err(ReplyError::X11Error(ref error)) = res { - if error.error_kind == ErrorKind::Access { - pgwm_core::debug!("Fatal error, Failed to start WM because another WM is running"); - Err(Error::BecomeWm) - } else { - Err(Error::BecomeWm) - } + pgwm_utils::debug!("Changing props"); + let res = XprotoConnection::change_window_attributes( + &mut self.connection, + screen.root, + change, + false, + )? + .check(&mut self.connection); + #[cfg_attr(not(feature = "debug"), allow(unused))] + if let Err(e) = res { + pgwm_utils::debug!("Fatal error, Failed to start WM, is another WM running? {e}"); + Err(Error::BecomeWm) } else { Ok(()) } @@ -312,10 +321,10 @@ impl CallWrapper { #[allow(clippy::too_many_lines)] pub(crate) fn set_default_manager_props(&mut self, state: &State) -> Result<()> { self.connection.change_property32( - PropMode::REPLACE, + PropModeEnum::REPLACE, state.screen.root, self.name_to_atom.get(&_NET_SUPPORTED).unwrap().value, - AtomEnum::ATOM, + AtomEnum::ATOM.0, self.name_to_atom .iter() .filter(|supported| supported.1.ewmh) @@ -325,60 +334,60 @@ impl CallWrapper { true, )?; self.connection.change_property32( - PropMode::REPLACE, + PropModeEnum::REPLACE, state.screen.root, self.name_to_atom.get(&_NET_CLIENT_LIST).unwrap().value, - AtomEnum::WINDOW, + AtomEnum::WINDOW.0, &[], true, )?; self.connection.change_property32( - PropMode::REPLACE, + PropModeEnum::REPLACE, state.screen.root, self.name_to_atom .get(&_NET_NUMBER_OF_DESKTOPS) .unwrap() .value, - AtomEnum::CARDINAL, + AtomEnum::CARDINAL.0, &[0], true, )?; let utf8 = WINDOW_MANAGER_NAME .chars() - .chain(std::iter::once('\u{0}')) + .chain(core::iter::once('\u{0}')) .map(|ch| ch as u32) .collect::>(); self.connection.change_property32( - PropMode::REPLACE, + PropModeEnum::REPLACE, state.screen.root, self.name_to_atom.get(&_NET_DESKTOP_NAMES).unwrap().value, - AtomEnum::STRING, + AtomEnum::STRING.0, utf8.as_slice(), true, )?; self.connection.change_property32( - PropMode::REPLACE, + PropModeEnum::REPLACE, state.screen.root, self.name_to_atom.get(&_NET_CURRENT_DESKTOP).unwrap().value, - AtomEnum::CARDINAL, + AtomEnum::CARDINAL.0, &[0], true, )?; self.connection.change_property32( - PropMode::REPLACE, + PropModeEnum::REPLACE, state.screen.root, self.name_to_atom.get(&_NET_DESKTOP_VIEWPORT).unwrap().value, - AtomEnum::CARDINAL, + AtomEnum::CARDINAL.0, &[0; 2], true, )?; self.connection.change_property32( - PropMode::REPLACE, + PropModeEnum::REPLACE, state.screen.root, self.name_to_atom.get(&_NET_DESKTOP_GEOMETRY).unwrap().value, - AtomEnum::CARDINAL, + AtomEnum::CARDINAL.0, &[ state.screen.width_in_pixels as u32, state.screen.height_in_pixels as u32, @@ -386,10 +395,10 @@ impl CallWrapper { true, )?; self.connection.change_property32( - PropMode::REPLACE, + PropModeEnum::REPLACE, state.screen.root, self.name_to_atom.get(&_NET_WORKAREA).unwrap().value, - AtomEnum::CARDINAL, + AtomEnum::CARDINAL.0, &[ 0, state.status_bar_height as u32, @@ -399,40 +408,40 @@ impl CallWrapper { true, )?; self.connection.change_property32( - PropMode::REPLACE, + PropModeEnum::REPLACE, state.screen.root, self.name_to_atom.get(&_NET_ACTIVE_WINDOW).unwrap().value, - AtomEnum::CARDINAL, + AtomEnum::CARDINAL.0, &[], true, )?; self.connection.change_property32( - PropMode::REPLACE, + PropModeEnum::REPLACE, state.screen.root, self.name_to_atom .get(&_NET_SUPPORTING_WM_CHECK) .unwrap() .value, - AtomEnum::WINDOW, + AtomEnum::WINDOW.0, &[state.wm_check_win], true, )?; self.connection.change_property32( - PropMode::REPLACE, + PropModeEnum::REPLACE, state.wm_check_win, self.name_to_atom .get(&_NET_SUPPORTING_WM_CHECK) .unwrap() .value, - AtomEnum::WINDOW, + AtomEnum::WINDOW.0, &[state.wm_check_win], true, )?; self.connection.change_property8( - PropMode::REPLACE, + PropModeEnum::REPLACE, state.wm_check_win, self.name_to_atom.get(&_NET_WM_NAME).unwrap().value, - AtomEnum::STRING, + AtomEnum::STRING.0, WINDOW_MANAGER_NAME.as_bytes(), true, )?; @@ -473,7 +482,7 @@ impl CallWrapper { } pub(crate) fn set_base_client_event_mask(&mut self, window: Window) -> Result<()> { - let cw = ChangeWindowAttributesAux::new().event_mask( + let cw = ChangeWindowAttributesValueList::default().event_mask( EventMask::ENTER_WINDOW | EventMask::FOCUS_CHANGE | EventMask::PROPERTY_CHANGE @@ -481,19 +490,19 @@ impl CallWrapper { | EventMask::EXPOSURE | EventMask::STRUCTURE_NOTIFY, ); - xproto::change_window_attributes(&mut self.connection, window, &cw, true)?; + XprotoConnection::change_window_attributes(&mut self.connection, window, cw, true)?; Ok(()) } pub(crate) fn set_base_client_properties(&mut self, window: Window) -> Result<()> { self.connection.change_property32( - PropMode::REPLACE, + PropModeEnum::REPLACE, window, self.name_to_atom .get(&_NET_WM_ALLOWED_ACTIONS) .unwrap() .value, - AtomEnum::ATOM, + AtomEnum::ATOM.0, &[ self.name_to_atom .get(&_NET_WM_ACTION_FULLSCREEN) @@ -508,10 +517,10 @@ impl CallWrapper { pub fn push_to_client_list(&mut self, root: Window, new_win: Window) -> Result<()> { self.connection.change_property32( - PropMode::APPEND, + PropModeEnum::APPEND, root, self.name_to_atom.get(&_NET_CLIENT_LIST).unwrap().value, - AtomEnum::WINDOW, + AtomEnum::WINDOW.0, &[new_win], true, )?; @@ -520,10 +529,10 @@ impl CallWrapper { pub fn update_client_list(&mut self, managed: &[Window], state: &State) -> Result<()> { self.connection.change_property32( - PropMode::REPLACE, + PropModeEnum::REPLACE, state.screen.root, self.name_to_atom.get(&_NET_CLIENT_LIST).unwrap().value, - AtomEnum::WINDOW, + AtomEnum::WINDOW.0, managed, true, )?; @@ -532,12 +541,15 @@ impl CallWrapper { pub(crate) fn query_subwindows(&mut self, window: Window) -> Result { Ok(QueryTreeCookie { - inner: xproto::query_tree(&mut self.connection, window, false)?, + inner: XprotoConnection::query_tree(&mut self.connection, window, false)?, }) } - pub(crate) fn query_pointer(&mut self, state: &State) -> Result> { - Ok(xproto::query_pointer( + pub(crate) fn query_pointer( + &mut self, + state: &State, + ) -> Result> { + Ok(XprotoConnection::query_pointer( &mut self.connection, state.screen.root, false, @@ -546,15 +558,15 @@ impl CallWrapper { pub(crate) fn get_dimensions(&mut self, window: Window) -> Result { Ok(DimensionsCookie { - inner: xproto::get_geometry(&mut self.connection, window, false)?, + inner: XprotoConnection::get_geometry(&mut self.connection, window, false)?, }) } pub(crate) fn get_window_attributes( &mut self, window: Window, - ) -> Result> { - Ok(xproto::get_window_attributes( + ) -> Result> { + Ok(XprotoConnection::get_window_attributes( &mut self.connection, window, false, @@ -562,12 +574,12 @@ impl CallWrapper { } pub(crate) fn get_class_names(&mut self, win: Window) -> Result { - let inner = xproto::get_property( + let inner = XprotoConnection::get_property( &mut self.connection, - false, + 0, win, - AtomEnum::WM_CLASS, - AtomEnum::STRING, + AtomEnum::WM_CLASS.0, + GetPropertyTypeEnum(AtomEnum::STRING.0), 0, 64, false, @@ -577,12 +589,12 @@ impl CallWrapper { pub(crate) fn get_wm_name(&mut self, win: Window) -> Result { Ok(NameCookie { - inner: xproto::get_property( + inner: XprotoConnection::get_property( &mut self.connection, - false, + 0, win, - AtomEnum::WM_NAME, - AtomEnum::ANY, + AtomEnum::WM_NAME.0, + GetPropertyTypeEnum(AtomEnum::ANY.0), 0, 1028, false, @@ -592,12 +604,12 @@ impl CallWrapper { pub(crate) fn get_net_wm_name(&mut self, win: Window) -> Result { Ok(NameCookie { - inner: xproto::get_property( + inner: XprotoConnection::get_property( &mut self.connection, - false, + 0, win, self.name_to_atom.get(&_NET_WM_NAME).unwrap().value, - AtomEnum::ANY, + GetPropertyTypeEnum(AtomEnum::ANY.0), 0, 1028, false, @@ -606,12 +618,12 @@ impl CallWrapper { } pub(crate) fn get_is_transient_for(&mut self, win: Window) -> Result { - let inner = xproto::get_property( + let inner = XprotoConnection::get_property( &mut self.connection, - false, + 0, win, - AtomEnum::WM_TRANSIENT_FOR, - AtomEnum::WINDOW, + AtomEnum::WM_TRANSIENT_FOR.0, + GetPropertyTypeEnum(AtomEnum::WINDOW.0), 0, 32, false, @@ -628,10 +640,10 @@ impl CallWrapper { pub(crate) fn set_extents(&mut self, win: Window, border_width: u32) -> Result<()> { self.connection.change_property32( - PropMode::REPLACE, + PropModeEnum::REPLACE, win, self.name_to_atom.get(&_NET_FRAME_EXTENTS).unwrap().value, - AtomEnum::CARDINAL, + AtomEnum::CARDINAL.0, &[border_width; 4], true, )?; @@ -640,7 +652,7 @@ impl CallWrapper { pub(crate) fn set_state(&mut self, win: Window, state: WmState) -> Result<()> { self.connection.change_property32( - PropMode::REPLACE, + PropModeEnum::REPLACE, win, self.name_to_atom.get(&WM_STATE).unwrap().value, self.name_to_atom.get(&WM_STATE).unwrap().value, @@ -652,10 +664,10 @@ impl CallWrapper { pub(crate) fn set_root_event_mask( &mut self, - cursor_handle: &x11rb::cursor::Handle, + cursor_handle: &xcb_rust_protocol::helpers::cursor::Handle, state: &State, ) -> Result { - let change_attrs_aux = ChangeWindowAttributesAux::new() + let change_attrs_aux = ChangeWindowAttributesValueList::default() .event_mask( EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY @@ -667,36 +679,38 @@ impl CallWrapper { | EventMask::PROPERTY_CHANGE | EventMask::KEY_PRESS, ) - .cursor(cursor_handle.load_cursor(&mut self.connection, state.cursor_name.as_str())?); + .cursor(CursorEnum(cursor_handle.load_cursor( + &mut self.connection, + state.cursor_name.as_str(), + XcbEnv::default(), + )?)); - Ok(xproto::change_window_attributes( + Ok(XprotoConnection::change_window_attributes( &mut self.connection, state.screen.root, - &change_attrs_aux, + change_attrs_aux, false, )?) } pub(crate) fn grab_pointer(&mut self, state: &State) -> Result<()> { - xproto::grab_pointer( + XprotoConnection::grab_pointer( &mut self.connection, - true, + 0, state.screen.root, - u32::from( - EventMask::BUTTON_PRESS | EventMask::BUTTON_RELEASE | EventMask::POINTER_MOTION, - ) as u16, - GrabMode::ASYNC, - GrabMode::ASYNC, - 0u16, - 0u16, - CURRENT_TIME, + EventMask::BUTTON_PRESS | EventMask::BUTTON_RELEASE | EventMask::POINTER_MOTION, + GrabModeEnum::ASYNC, + GrabModeEnum::ASYNC, + WindowEnum::NONE, + CursorEnum::NONE, + CURRENT_TIME.into(), true, )?; Ok(()) } pub(crate) fn ungrab_pointer(&mut self) -> Result<()> { - xproto::ungrab_pointer(&mut self.connection, CURRENT_TIME, true)?; + XprotoConnection::ungrab_pointer(&mut self.connection, CURRENT_TIME.into(), true)?; Ok(()) } @@ -710,47 +724,47 @@ impl CallWrapper { ) -> Result<()> { let target = if target == root { // No active window if root gets focused - xproto::set_input_focus( + XprotoConnection::set_input_focus( &mut self.connection, - InputFocus::PARENT, - target, - CURRENT_TIME, + InputFocusEnum::PARENT, + target.into(), + CURRENT_TIME.into(), true, )?; NONE } else { match focus_style { FocusStyle::NoInput => { - pgwm_core::debug!("NoInput win {target} take focus"); + pgwm_utils::debug!("NoInput win {target} take focus"); target } FocusStyle::Passive => { - pgwm_core::debug!("Passive win {target} take focus"); - xproto::set_input_focus( + pgwm_utils::debug!("Passive win {target} take focus"); + XprotoConnection::set_input_focus( &mut self.connection, - InputFocus::PARENT, - target, - CURRENT_TIME, + InputFocusEnum::PARENT, + target.into(), + CURRENT_TIME.into(), true, )?; target } FocusStyle::LocallyActive => { - pgwm_core::debug!("Locally active win {target} set input focus"); + pgwm_utils::debug!("Locally active win {target} set input focus"); // Setting input focus should only be required if the client's top-level-window // doesn't already have the focus, but whatever just always set it. - xproto::set_input_focus( + XprotoConnection::set_input_focus( &mut self.connection, - InputFocus::PARENT, - target, - CURRENT_TIME, + InputFocusEnum::PARENT, + target.into(), + CURRENT_TIME.into(), true, )?; self.send_take_focus(target, state.last_timestamp)?; target } FocusStyle::GloballyActive => { - pgwm_core::debug!("Globally active win {target} take focus"); + pgwm_utils::debug!("Globally active win {target} take focus"); self.send_take_focus(target, state.last_timestamp)?; target } @@ -758,36 +772,41 @@ impl CallWrapper { }; let data = [target, CURRENT_TIME]; self.connection.change_property32( - PropMode::REPLACE, + PropModeEnum::REPLACE, root, self.name_to_atom.get(&_NET_ACTIVE_WINDOW).unwrap().value, - AtomEnum::WINDOW, + AtomEnum::WINDOW.0, &data, true, )?; Ok(()) } - pub(crate) fn reset_root_focus(&mut self, state: &State) -> Result<()> { - xproto::delete_property( + pub(crate) fn reset_root_window(&mut self, state: &State) -> Result<()> { + XprotoConnection::delete_property( &mut self.connection, state.screen.root, self.name_to_atom.get(&_NET_ACTIVE_WINDOW).unwrap().value, true, )?; - xproto::set_input_focus( + XprotoConnection::set_input_focus( &mut self.connection, - InputFocus::POINTER_ROOT, - u32::from(InputFocus::POINTER_ROOT), - CURRENT_TIME, + InputFocusEnum::POINTER_ROOT, + InputFocusEnum::POINTER_ROOT, + CURRENT_TIME.into(), + true, + )?; + XprotoConnection::change_window_attributes( + &mut self.connection, + state.screen.root, + ChangeWindowAttributesValueList::default().event_mask(EventMask::NO_EVENT), true, )?; Ok(()) } fn send_take_focus(&mut self, win: Window, timestamp: Timestamp) -> Result<()> { - let event = ClientMessageEvent::new( - 32, + let event = new_client_message32( win, self.name_to_atom.get(&WM_PROTOCOLS).unwrap().value, [ @@ -798,11 +817,11 @@ impl CallWrapper { 0, ], ); - pgwm_core::debug!("Sending WM_TAKE_FOCUS focus for {}", win); - xproto::send_event( + pgwm_utils::debug!("Sending WM_TAKE_FOCUS focus for {}", win); + XprotoConnection::send_event( &mut self.connection, - false, - win, + 0, + win.into(), EventMask::NO_EVENT, &event, true, @@ -811,8 +830,7 @@ impl CallWrapper { } pub(crate) fn send_delete(&mut self, win: Window) -> Result<()> { - let event = ClientMessageEvent::new( - 32, + let event = new_client_message32( win, self.name_to_atom.get(&WM_PROTOCOLS).unwrap().value, [ @@ -823,11 +841,11 @@ impl CallWrapper { 0, ], ); - pgwm_core::debug!("Sending delete for {}", win); - xproto::send_event( + pgwm_utils::debug!("Sending delete for {}", win); + XprotoConnection::send_event( &mut self.connection, - false, - win, + 0, + win.into(), EventMask::NO_EVENT, &event, true, @@ -836,32 +854,32 @@ impl CallWrapper { } pub(crate) fn send_map(&mut self, window: Window, state: &mut State) -> Result<()> { - let cookie = xproto::map_window(&mut self.connection, window, true)?; + let cookie = XprotoConnection::map_window(&mut self.connection, window, true)?; // Triggers an enter-notify that needs to be ignored - state.push_sequence(cookie.sequence_number()); + state.push_sequence(cookie.seq); Ok(()) } pub(crate) fn send_unmap(&mut self, window: Window, state: &mut State) -> Result<()> { - let cookie = xproto::unmap_window(&mut self.connection, window, true)?; + let cookie = XprotoConnection::unmap_window(&mut self.connection, window, true)?; // Triggers an enter-notify that needs to be ignored, we also don't want to react to an UnmapNotify that we created - state.push_sequence(cookie.sequence_number()); + state.push_sequence(cookie.seq); Ok(()) } pub(crate) fn send_destroy(&mut self, window: Window) -> Result<()> { - xproto::destroy_window(&mut self.connection, window, true)?; + XprotoConnection::destroy_window(&mut self.connection, window, true)?; Ok(()) } pub(crate) fn send_kill(&mut self, window: Window) -> Result<()> { - xproto::kill_client(&mut self.connection, window, true)?; + XprotoConnection::kill_client(&mut self.connection, window.into(), true)?; Ok(()) } pub(crate) fn push_window_to_top(&mut self, window: Window, state: &mut State) -> Result<()> { - let cfg = ConfigureWindowAux::new().stack_mode(StackMode::ABOVE); - self.do_configure(window, &cfg, state) + let cfg = ConfigureWindowValueList::default().stack_mode(StackModeEnum::ABOVE); + self.do_configure(window, cfg, state) } pub(crate) fn configure_window( @@ -871,27 +889,52 @@ impl CallWrapper { border_width: u32, state: &mut State, ) -> Result<()> { - let cfg = ConfigureWindowAux::new() + let cfg = ConfigureWindowValueList::default() .x(dimension.x as i32) .y(dimension.y as i32) .width(dimension.width as u32) .height(dimension.height as u32) .border_width(border_width) - .stack_mode(StackMode::ABOVE); + .stack_mode(StackModeEnum::ABOVE); self.set_extents(window, border_width)?; - self.do_configure(window, &cfg, state) + self.do_configure(window, cfg, state) } // When windows themselves ask to configure resultant mapping should not be ignored, so no need to ignore child-sequences here pub(crate) fn configure_from_request(&mut self, event: &ConfigureRequestEvent) -> Result<()> { - let cfg = ConfigureWindowAux::from_configure_request(event); + let mut cfg = ConfigureWindowValueList::default(); + if event.value_mask.0 & ConfigWindow::HEIGHT.0 != 0 { + cfg.height = Some(event.height as u32); + } + if event.value_mask.0 & ConfigWindow::WIDTH.0 != 0 { + cfg.width = Some(event.width as u32); + } + if event.value_mask.0 & ConfigWindow::BORDER_WIDTH.0 != 0 { + cfg.border_width = Some(event.border_width as u32); + } + if event.value_mask.0 & ConfigWindow::SIBLING.0 != 0 { + cfg.sibling = Some(event.sibling); + } + if event.value_mask.0 & ConfigWindow::STACK_MODE.0 != 0 { + cfg.stack_mode = Some(event.stack_mode); + } + if event.value_mask.0 & ConfigWindow::X.0 != 0 { + cfg.x = Some(event.x as i32); + } + if event.value_mask.0 & ConfigWindow::Y.0 != 0 { + cfg.y = Some(event.y as i32); + } + + crate::debug!("Configuring {event:?}\n on {}", event.window); if let Some(border_width) = cfg.border_width { self.set_extents(event.window, border_width)?; } - xproto::configure_window(&mut self.connection, event.window, &cfg, true)?; + XprotoConnection::configure_window(&mut self.connection, event.window, cfg, true)?; + self.connection.flush()?; Ok(()) } + #[inline] pub(crate) fn move_window( &mut self, window: Window, @@ -899,11 +942,11 @@ impl CallWrapper { y: i32, state: &mut State, ) -> Result<()> { - let cfg = ConfigureWindowAux::new() + let cfg = ConfigureWindowValueList::default() .x(x) .y(y) - .stack_mode(StackMode::ABOVE); - self.do_configure(window, &cfg, state) + .stack_mode(StackModeEnum::ABOVE); + self.do_configure(window, cfg, state) } pub(crate) fn resize_window( @@ -913,25 +956,28 @@ impl CallWrapper { width: u32, state: &mut State, ) -> Result<()> { - let cfg = ConfigureWindowAux::new().height(height).width(width); - self.do_configure(window, &cfg, state) + let cfg = ConfigureWindowValueList::default() + .height(height) + .width(width); + self.do_configure(window, cfg, state) } fn do_configure( &mut self, window: Window, - cfg: &ConfigureWindowAux, + cfg: ConfigureWindowValueList, state: &mut State, ) -> Result<()> { - let cookie = xproto::configure_window(&mut self.connection, window, cfg, true)?; + let cookie = XprotoConnection::configure_window(&mut self.connection, window, cfg, true)?; // Triggers an enter-notify that needs to be ignored - state.push_sequence(cookie.sequence_number() as u16); + state.push_sequence(cookie.seq); + self.connection.flush()?; Ok(()) } pub(crate) fn change_border_color(&mut self, window: Window, pixel: u32) -> Result<()> { - let cw = ChangeWindowAttributesAux::new().border_pixel(pixel); - xproto::change_window_attributes(&mut self.connection, window, &cw, true)?; + let cw = ChangeWindowAttributesValueList::default().border_pixel(pixel); + XprotoConnection::change_window_attributes(&mut self.connection, window, cw, true)?; Ok(()) } @@ -941,14 +987,14 @@ impl CallWrapper { vis_info: &RenderVisualInfo, ) -> Result { let picture = self.connection.generate_id()?; - x11rb::xcb::render::create_picture( + RenderConnection::create_picture( &mut self.connection, picture, win, vis_info.root.pict_format, - &CreatePictureAux::new() - .polyedge(PolyEdge::SMOOTH) - .polymode(PolyMode::IMPRECISE), + CreatePictureValueList::default() + .polyedge(PolyEdgeEnum::SMOOTH) + .polymode(PolyModeEnum::IMPRECISE), true, )?; Ok(picture) @@ -960,12 +1006,12 @@ impl CallWrapper { vis_info: &RenderVisualInfo, ) -> Result { let picture = self.connection.generate_id()?; - x11rb::xcb::render::create_picture( + RenderConnection::create_picture( &mut self.connection, picture, win, vis_info.render.pict_format, - &CreatePictureAux::new().repeat(Repeat::NORMAL), + CreatePictureValueList::default().repeat(RepeatEnum::NORMAL), true, )?; Ok(picture) @@ -973,7 +1019,7 @@ impl CallWrapper { pub(crate) fn create_glyphset(&mut self, vis_info: &RenderVisualInfo) -> Result { let id = self.connection.generate_id()?; - x11rb::xcb::render::create_glyph_set( + RenderConnection::create_glyph_set( &mut self.connection, id, vis_info.render.pict_format, @@ -992,9 +1038,10 @@ impl CallWrapper { if !glyph_ids.len() == glyph_info.len() { return Err(GlyphMismatch); } - x11rb::xcb::render::add_glyphs( + RenderConnection::add_glyphs( &mut self.connection, glyph_set, + glyph_ids.len() as u32, glyph_ids, glyph_info, rendered_picture_glyphs, @@ -1006,13 +1053,13 @@ impl CallWrapper { pub(crate) fn fill_xrender_rectangle( &mut self, picture: Picture, - color: x11rb::protocol::render::Color, + color: xcb_rust_protocol::proto::render::Color, dimensions: Dimensions, ) -> Result<()> { //let (red, green, blue, alpha) = color.to_rgba16(); - x11rb::xcb::render::fill_rectangles( + RenderConnection::fill_rectangles( &mut self.connection, - PictOp::SRC, + PictOpEnum::SRC, picture, color, &[dimensions.to_rectangle()], @@ -1045,9 +1092,9 @@ impl CallWrapper { for glyph in render { buf.extend_from_slice(&glyph.to_ne_bytes()); // Dump to u8s } - x11rb::xcb::render::composite_glyphs16( + RenderConnection::composite_glyphs16( &mut self.connection, - PictOp::OVER, + PictOpEnum::OVER, dbw.pixmap.picture, dbw.window.picture, 0, @@ -1065,17 +1112,19 @@ impl CallWrapper { } #[cfg(feature = "debug")] + #[cfg_attr(test, allow(unused_imports))] pub(crate) fn debug_window(&mut self, win: Window) -> Result<()> { - use std::fmt::Write; - let props = xproto::list_properties(&mut self.connection, win, false)?; + use alloc::format; + use alloc::string::ToString; + use core::fmt::Write; + let props = XprotoConnection::list_properties(&mut self.connection, win, false)?; let geom = self.get_dimensions(win)?; let attrs = self.get_window_attributes(win)?; let name = self.get_wm_name(win)?; let class = self.get_class_names(win)?; let hints_cookie = WmHints::get(&mut self.connection, win)?; let mut base = format!( - "Debug Window {}, name: {}, classes: {:?}\n", - win, + "Debug Window {win}, name: {}, classes: {:?}\n", name.await_name(&mut self.connection) .unwrap_or_default() .unwrap_or_default(), @@ -1086,24 +1135,25 @@ impl CallWrapper { ); base.push_str("\tHints: \n"); if let Ok(hints) = hints_cookie.reply(&mut self.connection) { - let _ = base.write_fmt(format_args!("\t\t{:?}", hints)); + let _ = base.write_fmt(format_args!("\t\t{hints:?}")); } base.push('\n'); base.push_str("\tAttributes: \n"); if let Ok(attributes) = attrs.reply(&mut self.connection) { - let _ = base.write_fmt(format_args!("\t\t{:?}", attributes)); + let _ = base.write_fmt(format_args!("\t\t{attributes:?}")); } base.push('\n'); base.push_str("\tGeometry: \n"); if let Ok(dims) = geom.await_dimensions(&mut self.connection) { - let _ = base.write_fmt(format_args!("\t\t{:?}", dims)); + let _ = base.write_fmt(format_args!("\t\t{dims:?}")); } base.push('\n'); base.push_str("\tProperties: "); if let Ok(props) = props.reply(&mut self.connection) { for prop in props.atoms { - if let Ok(name) = xproto::get_atom_name(&mut self.connection, prop, false)? - .reply(&mut self.connection) + if let Ok(name) = + XprotoConnection::get_atom_name(&mut self.connection, prop, false)? + .reply(&mut self.connection) { if let Ok(utf8) = String::from_utf8(name.name) { let post = match utf8.as_str() { @@ -1120,9 +1170,9 @@ impl CallWrapper { .await_protocols(self) .ok() .unwrap_or_default(); - format!("{:?}", protocols) + format!("{protocols:?}") } - _ => "".to_owned(), + _ => String::new(), }; let _ = base.write_fmt(format_args!("\n\t\t{utf8}: {post}")); } @@ -1130,14 +1180,14 @@ impl CallWrapper { } } base.push('\n'); - eprintln!("{base}"); + crate::debug!("{base}"); Ok(()) } #[cfg(feature = "debug")] pub(crate) fn get_atom_name(&mut self, atom: Atom) -> Result { Ok(String::from_utf8( - xproto::get_atom_name(&mut self.connection, atom, false)? + XprotoConnection::get_atom_name(&mut self.connection, atom, false)? .reply(&mut self.connection)? .name, )?) @@ -1158,12 +1208,12 @@ impl CallWrapper { pub(crate) fn get_wm_state(&mut self, window: Window) -> Result { Ok(WmStateCookie { - inner: xproto::get_property( + inner: XprotoConnection::get_property( &mut self.connection, - false, + 0, window, self.name_to_atom.get(&WM_STATE).unwrap().value, - self.name_to_atom.get(&WM_STATE).unwrap().value, + self.name_to_atom.get(&WM_STATE).unwrap().value.into(), 0, 4, false, @@ -1173,12 +1223,12 @@ impl CallWrapper { pub(crate) fn get_net_wm_state(&mut self, window: Window) -> Result { Ok(NetWmStateCookie { - inner: xproto::get_property( + inner: XprotoConnection::get_property( &mut self.connection, - false, + 0, window, self.name_to_atom.get(&_NET_WM_STATE).unwrap().value, - AtomEnum::ATOM, + GetPropertyTypeEnum(AtomEnum::ATOM.0), 0, 4 * 32, false, @@ -1279,10 +1329,10 @@ impl CallWrapper { )?; } self.connection.change_property32( - PropMode::REPLACE, + PropModeEnum::REPLACE, window, self.name_to_atom.get(&_NET_WM_STATE).unwrap().value, - AtomEnum::ATOM, + AtomEnum::ATOM.0, state.as_slice(), true, )?; @@ -1291,12 +1341,12 @@ impl CallWrapper { pub(crate) fn get_window_types(&mut self, window: Window) -> Result { Ok(WindowTypesCookie { - inner: xproto::get_property( + inner: XprotoConnection::get_property( &mut self.connection, - false, + 0, window, self.name_to_atom.get(&_NET_WM_WINDOW_TYPE).unwrap().value, - AtomEnum::ATOM, + AtomEnum::ATOM.0.into(), 0, 4 * 32, false, @@ -1306,12 +1356,12 @@ impl CallWrapper { pub(crate) fn get_leader(&mut self, window: Window) -> Result { Ok(SingleCardCookie { - inner: xproto::get_property( + inner: XprotoConnection::get_property( &mut self.connection, - false, + 0, window, self.name_to_atom.get(&WM_CLIENT_LEADER).unwrap().value, - AtomEnum::WINDOW, + AtomEnum::WINDOW.0.into(), 0, 4, false, @@ -1321,12 +1371,12 @@ impl CallWrapper { pub(crate) fn get_pid(&mut self, window: Window) -> Result { Ok(SingleCardCookie { - inner: xproto::get_property( + inner: XprotoConnection::get_property( &mut self.connection, - false, + 0, window, self.name_to_atom.get(&_NET_WM_PID).unwrap().value, - AtomEnum::CARDINAL, + AtomEnum::CARDINAL.0.into(), 0, 4, false, @@ -1336,12 +1386,12 @@ impl CallWrapper { pub(crate) fn get_protocols(&mut self, window: Window) -> Result { Ok(ProtocolsCookie { - inner: xproto::get_property( + inner: XprotoConnection::get_property( &mut self.connection, - false, + 0, window, self.name_to_atom.get(&WM_PROTOCOLS).unwrap().value, - AtomEnum::ATOM, + AtomEnum::ATOM.0.into(), 0, 4 * 32, false, @@ -1439,7 +1489,7 @@ impl NameCookie { fn utf8_heapless(bytes: Vec) -> Result>> { let slice = &bytes[..N.min(bytes.len())]; - Ok(std::str::from_utf8(slice).map(|s| Some(heapless::String::from(s)))?) + Ok(core::str::from_utf8(slice).map(|s| Some(heapless::String::from(s)))?) } pub(crate) struct WmClassCookie { @@ -1469,7 +1519,7 @@ fn extract_wm_class( .collect::, 4>>(); Some(complete_names) } else { - pgwm_core::debug!("Failed to parse class response value as utf-8"); + pgwm_utils::debug!("Failed to parse class response value as utf-8"); None } } @@ -1768,7 +1818,7 @@ impl QueryTreeCookie { } pub(crate) struct DimensionsCookie { - pub(crate) inner: Cookie, + pub(crate) inner: FixedCookie, } impl DimensionsCookie { diff --git a/pgwm/src/x11/client_message.rs b/pgwm-app/src/x11/client_message.rs similarity index 100% rename from pgwm/src/x11/client_message.rs rename to pgwm-app/src/x11/client_message.rs diff --git a/pgwm/src/x11/colors.rs b/pgwm-app/src/x11/colors.rs similarity index 76% rename from pgwm/src/x11/colors.rs rename to pgwm-app/src/x11/colors.rs index 522dac2..75c05fd 100644 --- a/pgwm/src/x11/colors.rs +++ b/pgwm-app/src/x11/colors.rs @@ -1,12 +1,13 @@ -use crate::error::Result; +use xcb_rust_protocol::connection::xproto::XprotoConnection; +use xcb_rust_protocol::cookie::FixedCookie; +use xcb_rust_protocol::proto::xproto::{AllocColorReply, Colormap}; + use pgwm_core::colors::{Color, Colors, Rgba8}; use pgwm_core::config::USED_DIFFERENT_COLOR_SEGMENTS; use pgwm_core::push_heapless; -use x11rb::cookie::Cookie; +use crate::error::Result; use crate::wm::XorgConnection; -use x11rb::protocol::xproto::{AllocColorReply, Colormap}; -use x11rb::xcb::xproto; #[allow(clippy::type_complexity)] pub(crate) fn alloc_colors( @@ -15,7 +16,7 @@ pub(crate) fn alloc_colors( colors: pgwm_core::colors::ColorBuilder, ) -> Result { let mut alloc_rgba_cookies: heapless::Vec< - ((u8, u8, u8, u8), Cookie), + ((u8, u8, u8, u8), FixedCookie), USED_DIFFERENT_COLOR_SEGMENTS, > = heapless::Vec::new(); for color in colors.get_all().iter() { @@ -25,10 +26,11 @@ pub(crate) fn alloc_colors( alloc_rgba_cookies, ( color, - xproto::alloc_color(connection, color_map, r, g, b, false)? + XprotoConnection::alloc_color(connection, color_map, r, g, b, false)? ) )?; } + connection.flush()?; let mut allocated_colors: heapless::Vec = heapless::Vec::new(); for ((r, g, b, a), cookie) in alloc_rgba_cookies { diff --git a/pgwm/src/x11/mod.rs b/pgwm-app/src/x11/mod.rs similarity index 100% rename from pgwm/src/x11/mod.rs rename to pgwm-app/src/x11/mod.rs diff --git a/pgwm/src/x11/state_lifecycle.rs b/pgwm-app/src/x11/state_lifecycle.rs similarity index 83% rename from pgwm/src/x11/state_lifecycle.rs rename to pgwm-app/src/x11/state_lifecycle.rs index a94405e..b5dcde8 100644 --- a/pgwm/src/x11/state_lifecycle.rs +++ b/pgwm-app/src/x11/state_lifecycle.rs @@ -1,10 +1,23 @@ -use crate::error::Result; +use alloc::string::String; +use alloc::vec::Vec; + use heapless::binary_heap::Min; use heapless::FnvIndexSet; -use std::collections::HashMap; -use x11rb::cookie::VoidCookie; +use smallmap::Map; +use xcb_rust_protocol::connection::render::RenderConnection; +use xcb_rust_protocol::connection::xproto::XprotoConnection; +use xcb_rust_protocol::cookie::VoidCookie; +use xcb_rust_protocol::proto::xproto::{ + CapStyleEnum, CreateGCValueList, CreateWindowValueList, CursorEnum, EventMask, Gcontext, + GrabEnum, GrabModeEnum, JoinStyleEnum, LineStyleEnum, Pixmap, Screen, Window, WindowClassEnum, + WindowEnum, +}; +use xcb_rust_protocol::{XcbConnection, COPY_DEPTH_FROM_PARENT, CURRENT_TIME}; use pgwm_core::colors::Colors; +use pgwm_core::config::key_map::{KeyBoardMappingKey, KeyboardMapping}; +use pgwm_core::config::mouse_map::MouseActionKey; +use pgwm_core::config::workspaces::UserWorkspace; use pgwm_core::config::{ Action, FontCfg, Fonts, Shortcut, SimpleKeyMapping, SimpleMouseMapping, TilingModifiers, APPLICATION_WINDOW_LIMIT, BINARY_HEAP_LIMIT, DYING_WINDOW_CACHE, @@ -12,12 +25,6 @@ use pgwm_core::config::{ #[cfg(feature = "status-bar")] use pgwm_core::config::{STATUS_BAR_CHECK_SEP, STATUS_BAR_FIRST_SEP}; use pgwm_core::geometry::{Dimensions, Line}; -use pgwm_core::state::workspace::Workspaces; -use pgwm_core::state::{Monitor, State, WinMarkedForDeath}; - -use pgwm_core::config::key_map::{KeyBoardMappingKey, KeyboardMapping}; -use pgwm_core::config::mouse_map::MouseActionKey; -use pgwm_core::config::workspaces::UserWorkspace; use pgwm_core::push_heapless; use pgwm_core::render::{DoubleBufferedRenderPicture, RenderPicture, RenderVisualInfo}; #[cfg(feature = "status-bar")] @@ -25,17 +32,15 @@ use pgwm_core::state::bar_geometry::StatusSection; use pgwm_core::state::bar_geometry::{ BarGeometry, FixedDisplayComponent, ShortcutComponent, ShortcutSection, WorkspaceSection, }; +use pgwm_core::state::workspace::Workspaces; +use pgwm_core::state::{Monitor, State, WinMarkedForDeath}; #[cfg(feature = "status-bar")] use pgwm_core::status::checker::{Check, CheckType}; -use x11rb::protocol::xproto::{ - ButtonIndex, CapStyle, CreateGCAux, CreateWindowAux, EventMask, Gcontext, GrabMode, JoinStyle, - LineStyle, Pixmap, Screen, Window, WindowClass, -}; -use x11rb::xcb::xproto; -use x11rb::{COPY_DEPTH_FROM_PARENT, CURRENT_TIME}; +use crate::error::Result; use crate::manager::font::{FontDrawer, LoadedFonts}; use crate::x11::call_wrapper::CallWrapper; + const COOKIE_CONTAINER_CAPACITY: usize = 64; pub(crate) fn create_state<'a>( @@ -152,13 +157,9 @@ pub(crate) fn reinit_state<'a>( pub(crate) fn teardown_dynamic_state(call_wrapper: &mut CallWrapper, state: &State) -> Result<()> { for mon in &state.monitors { call_wrapper.send_destroy(mon.bar_win.window.drawable)?; - x11rb::xcb::render::free_picture( - call_wrapper.inner_mut(), - mon.bar_win.window.picture, - true, - )?; + RenderConnection::free_picture(call_wrapper.inner_mut(), mon.bar_win.window.picture, true)?; call_wrapper.send_destroy(mon.tab_bar_win.window.drawable)?; - x11rb::xcb::render::free_picture( + RenderConnection::free_picture( call_wrapper.inner_mut(), mon.tab_bar_win.window.picture, true, @@ -175,7 +176,16 @@ pub(crate) fn teardown_full_state( let _ = teardown_dynamic_state(call_wrapper, state); call_wrapper.send_destroy(state.wm_check_win)?; for font in loaded_fonts.fonts.values() { - x11rb::xcb::render::free_glyph_set(call_wrapper.inner_mut(), font.glyph_set, true)?; + RenderConnection::free_glyph_set(call_wrapper.inner_mut(), font.glyph_set, true)?; + } + ungrab_keys(call_wrapper, &state.key_mapping, state.screen.root)?; + for mon in &state.monitors { + ungrab_mouse( + call_wrapper, + mon.bar_win.window.drawable, + state.screen.root, + &state.mouse_mapping, + )?; } Ok(()) } @@ -219,9 +229,9 @@ fn do_create_state<'a>( if dimensions.width > max_bar_width { max_bar_width = dimensions.width; } - pgwm_core::debug!("Monitor {} size = {:?}", i, dimensions); + pgwm_utils::debug!("Monitor {} size = {:?}", i, dimensions); if i > init_workspaces.len() { - pgwm_core::debug!( + pgwm_utils::debug!( "More monitors than workspaces, not using more than {}", i - 1 ); @@ -264,7 +274,7 @@ fn do_create_state<'a>( )? )?; if show_bar_initially { - xproto::map_window(call_wrapper.inner_mut(), bar_win, true)?; + call_wrapper.inner_mut().map_window(bar_win, true)?; } let bar_win = init_xrender_double_buffered(call_wrapper, screen.root, bar_win, &vis_info)?; @@ -294,13 +304,13 @@ fn do_create_state<'a>( monitors.push(new_mon); } - pgwm_core::debug!("Initializing mouse"); + pgwm_utils::debug!("Initializing mouse"); let mouse_mapping = init_mouse(mouse_mappings); - pgwm_core::debug!("Initializing keys"); + pgwm_utils::debug!("Initializing keys"); let key_mapping = init_keys(call_wrapper, key_mappings)?; grab_keys(call_wrapper, &key_mapping, screen.root)?; for bar_win in monitors.iter().map(|mon| &mon.bar_win) { - pgwm_core::debug!("Grabbing mouse keys on bar_win"); + pgwm_utils::debug!("Grabbing mouse keys on bar_win"); grab_mouse( call_wrapper, bar_win.window.drawable, @@ -309,7 +319,7 @@ fn do_create_state<'a>( )?; } - pgwm_core::debug!("Creating status bar pixmap"); + pgwm_utils::debug!("Creating status bar pixmap"); #[cfg(feature = "status-bar")] let status_pixmap = call_wrapper.inner_mut().generate_id()?; @@ -328,7 +338,7 @@ fn do_create_state<'a>( for cookie in cookie_container { cookie.check(call_wrapper.inner_mut())?; } - pgwm_core::debug!("Created state"); + pgwm_utils::debug!("Created state"); Ok(State { intern_created_windows, drag_window: None, @@ -404,11 +414,10 @@ fn create_tab_bar_win( dimensions: Dimensions, tab_bar_height: i16, ) -> Result { - let create_win = CreateWindowAux::new() + let create_win = CreateWindowValueList::default() .event_mask(EventMask::BUTTON_PRESS) .background_pixel(0); - Ok(xproto::create_window( - call_wrapper.inner_mut(), + Ok(call_wrapper.inner_mut().create_window( COPY_DEPTH_FROM_PARENT, tab_bar_win, screen.root, @@ -417,9 +426,9 @@ fn create_tab_bar_win( dimensions.width as u16, tab_bar_height as u16, 0, - WindowClass::INPUT_OUTPUT, + WindowClassEnum::INPUT_OUTPUT, 0, - &create_win, + create_win, false, )?) } @@ -431,7 +440,7 @@ fn create_workspace_bar_win( dimensions: Dimensions, status_bar_height: u16, ) -> Result { - let cw = CreateWindowAux::new() + let cw = CreateWindowValueList::default() .background_pixel(screen.black_pixel) .event_mask( EventMask::ENTER_WINDOW @@ -440,8 +449,7 @@ fn create_workspace_bar_win( | EventMask::VISIBILITY_CHANGE | EventMask::LEAVE_WINDOW, ); - Ok(xproto::create_window( - call_wrapper.inner_mut(), + Ok(call_wrapper.inner_mut().create_window( COPY_DEPTH_FROM_PARENT, ws_bar_win, screen.root, @@ -450,9 +458,9 @@ fn create_workspace_bar_win( dimensions.width as u16, status_bar_height, 0, - WindowClass::INPUT_OUTPUT, + WindowClassEnum::INPUT_OUTPUT, 0, - &cw, + cw, false, )?) } @@ -464,8 +472,7 @@ fn create_workspace_bar_pixmap( dimensions: Dimensions, status_bar_height: u16, ) -> Result { - Ok(xproto::create_pixmap( - call_wrapper.inner_mut(), + Ok(call_wrapper.inner_mut().create_pixmap( screen.root_depth, bar_pixmap, screen.root, @@ -480,10 +487,10 @@ fn create_wm_check_win<'a>( screen: &'a Screen, check_win: Window, ) -> Result { - let cw = CreateWindowAux::new() + let cw = CreateWindowValueList::default() .event_mask(EventMask::NO_EVENT) .background_pixel(0); - Ok(xproto::create_window( + Ok(XprotoConnection::create_window( call_wrapper.inner_mut(), COPY_DEPTH_FROM_PARENT, check_win, @@ -493,9 +500,9 @@ fn create_wm_check_win<'a>( 1, 1, 0, - WindowClass::INPUT_OUTPUT, + WindowClassEnum::INPUT_OUTPUT, 0, - &cw, + cw, false, )?) } @@ -525,21 +532,22 @@ fn create_gcs<'a>( Ok(v) } + fn create_background_gc( call_wrapper: &mut CallWrapper, win: Window, pixel: u32, ) -> Result<(Gcontext, VoidCookie)> { let gc = call_wrapper.inner_mut().generate_id()?; - let gc_aux = CreateGCAux::new() + let gc_aux = CreateGCValueList::default() .graphics_exposures(0) - .line_style(LineStyle::SOLID) - .cap_style(CapStyle::BUTT) - .join_style(JoinStyle::MITER) + .line_style(LineStyleEnum::SOLID) + .cap_style(CapStyleEnum::BUTT) + .join_style(JoinStyleEnum::MITER) .foreground(pixel) .background(pixel); - let cookie = xproto::create_gc(call_wrapper.inner_mut(), gc, win, &gc_aux, false)?; + let cookie = XprotoConnection::create_g_c(call_wrapper.inner_mut(), gc, win, gc_aux, false)?; Ok((gc, cookie)) } @@ -549,7 +557,7 @@ fn get_screen_dimensions( _connection: &mut CallWrapper, screen: &Screen, ) -> Result> { - Ok(vec![Dimensions::new( + Ok(alloc::vec![Dimensions::new( screen.width_in_pixels as i16, screen.height_in_pixels as i16, 0, @@ -563,19 +571,22 @@ fn get_screen_dimensions( _screen: &Screen, ) -> Result> { Ok( - x11rb::xcb::xinerama::query_screens(call_wrapper.inner_mut(), false)? - .reply(call_wrapper.inner_mut())? - .screen_info - .iter() - .map(|screen_info| { - Dimensions::new( - screen_info.width as i16, - screen_info.height as i16, - screen_info.x_org, - screen_info.y_org, - ) - }) - .collect(), + xcb_rust_protocol::connection::xinerama::XineramaConnection::query_screens( + call_wrapper.inner_mut(), + false, + )? + .reply(call_wrapper.inner_mut())? + .screen_info + .iter() + .map(|screen_info| { + Dimensions::new( + screen_info.width as i16, + screen_info.height as i16, + screen_info.x_org, + screen_info.y_org, + ) + }) + .collect(), ) } @@ -585,7 +596,7 @@ fn create_tab_pixmap<'a>( pixmap: Pixmap, tab_bar_height: u16, ) -> Result { - Ok(xproto::create_pixmap( + Ok(XprotoConnection::create_pixmap( call_wrapper.inner_mut(), screen.root_depth, pixmap, @@ -595,6 +606,7 @@ fn create_tab_pixmap<'a>( false, )?) } + #[cfg(feature = "status-bar")] fn create_status_bar_pixmap( call_wrapper: &mut CallWrapper, @@ -603,7 +615,7 @@ fn create_status_bar_pixmap( max_bar_width: u16, status_bar_height: u16, ) -> Result { - Ok(xproto::create_pixmap( + Ok(XprotoConnection::create_pixmap( call_wrapper.inner_mut(), screen.root_depth, pixmap, @@ -689,9 +701,8 @@ fn create_status_section_geometry<'a>( .0 } CheckType::Date(fmt) => { - let tokens = time::format_description::parse(&fmt.pattern).unwrap_or_default(); font_manager - .text_geometry(&fmt.format_date(&tokens), &fonts.status_section) + .text_geometry(&fmt.format_date(), &fonts.status_section) .0 } }; @@ -800,17 +811,18 @@ fn create_fixed_components>( fn init_keys( call_wrapper: &mut CallWrapper, simple_key_mappings: &[SimpleKeyMapping], -) -> Result> { +) -> Result> { let setup = call_wrapper.inner_mut().setup(); let lo = setup.min_keycode; let hi = setup.max_keycode; let capacity = hi - lo + 1; - let mapping = xproto::get_keyboard_mapping(call_wrapper.inner_mut(), lo, capacity, false)? - .reply(call_wrapper.inner_mut())?; - pgwm_core::debug!("Got key mapping"); + let mapping = + XprotoConnection::get_keyboard_mapping(call_wrapper.inner_mut(), lo, capacity, false)? + .reply(call_wrapper.inner_mut())?; + pgwm_utils::debug!("Got key mapping"); let syms = mapping.keysyms; - let mut map = HashMap::new(); + let mut map = Map::new(); let mut converted: Vec = Vec::new(); for simple_key_mapping in simple_key_mappings { @@ -819,7 +831,7 @@ fn init_keys( for (keysym_ind, sym) in syms.iter().enumerate() { while let Some(keymap_ind) = converted.iter().position(|k| &k.keysym == sym) { let key_def = converted.swap_remove(keymap_ind); - let mods = u16::from(key_def.modmask); + let mods = key_def.modmask.0; let modded_ind = keysym_ind + mods as usize; let code = (modded_ind - mods as usize) / mapping.keysyms_per_keycode as usize + lo as usize; @@ -832,18 +844,18 @@ fn init_keys( fn grab_keys( call_wrapper: &mut CallWrapper, - key_map: &HashMap, + key_map: &Map, root_win: Window, ) -> Result<()> { for key in key_map.keys() { - xproto::grab_key( + XprotoConnection::grab_key( call_wrapper.inner_mut(), - true, + 0, root_win, - key.mods, - key.code, - GrabMode::ASYNC, - GrabMode::ASYNC, + key.mods.into(), + key.code.into(), + GrabModeEnum::ASYNC, + GrabModeEnum::ASYNC, false, )? .check(call_wrapper.inner_mut())?; @@ -851,16 +863,29 @@ fn grab_keys( Ok(()) } -fn init_mouse(simple_mouse_mappings: &[SimpleMouseMapping]) -> HashMap { - let mut action_map = HashMap::new(); +fn ungrab_keys( + call_wrapper: &mut CallWrapper, + key_map: &Map, + root_win: Window, +) -> Result<()> { + for key in key_map.keys() { + call_wrapper + .inner_mut() + .ungrab_key(GrabEnum(key.code), root_win, key.mods.into(), true)?; + } + Ok(()) +} + +fn init_mouse(simple_mouse_mappings: &[SimpleMouseMapping]) -> Map { + let mut action_map = Map::new(); for mapping in simple_mouse_mappings .iter() .map(|smm| smm.clone().to_mouse_mapping()) { action_map.insert( MouseActionKey { - detail: u8::from(mapping.button), - state: u16::from(mapping.mods), + detail: mapping.button.0, + state: mapping.mods.0, target: mapping.target, }, mapping.action, @@ -873,20 +898,45 @@ fn grab_mouse( call_wrapper: &mut CallWrapper, bar_win: Window, root_win: Window, - mouse_map: &HashMap, + mouse_map: &Map, ) -> Result<()> { for key in mouse_map.keys() { - xproto::grab_button( + XprotoConnection::grab_button( call_wrapper.inner_mut(), + 0, + if key.target.on_bar() { + bar_win + } else { + root_win + }, + EventMask::BUTTON_PRESS | EventMask::BUTTON_RELEASE, + GrabModeEnum::ASYNC, + GrabModeEnum::ASYNC, + WindowEnum::NONE, + CursorEnum::NONE, + key.detail.into(), + key.state.into(), true, - key.target.on_bar().then(|| bar_win).unwrap_or(root_win), - u32::from(EventMask::BUTTON_PRESS | EventMask::BUTTON_RELEASE) as u16, - GrabMode::ASYNC, - GrabMode::ASYNC, - 0u16, - 0u16, - ButtonIndex::from(key.detail), - key.state, + )?; + } + Ok(()) +} + +fn ungrab_mouse( + call_wrapper: &mut CallWrapper, + bar_win: Window, + root_win: Window, + mouse_map: &Map, +) -> Result<()> { + for key in mouse_map.keys() { + call_wrapper.inner_mut().ungrab_button( + key.detail.into(), + if key.target.on_bar() { + bar_win + } else { + root_win + }, + key.state.into(), true, )?; } @@ -901,7 +951,7 @@ fn init_xrender_double_buffered( ) -> Result { let direct = call_wrapper.window_mapped_picture(window, vis_info)?; let write_buf_pixmap = call_wrapper.inner_mut().generate_id()?; - xproto::create_pixmap( + XprotoConnection::create_pixmap( call_wrapper.inner_mut(), vis_info.render.depth, write_buf_pixmap, @@ -913,7 +963,7 @@ fn init_xrender_double_buffered( let write_buf_picture = call_wrapper.pixmap_mapped_picture(write_buf_pixmap, vis_info)?; call_wrapper.fill_xrender_rectangle( write_buf_picture, - x11rb::protocol::render::Color { + xcb_rust_protocol::proto::render::Color { red: 0xffff, green: 0xffff, blue: 0xffff, @@ -921,7 +971,7 @@ fn init_xrender_double_buffered( }, Dimensions::new(1, 1, 0, 0), )?; - xproto::free_pixmap(call_wrapper.inner_mut(), write_buf_pixmap, true)?; + XprotoConnection::free_pixmap(call_wrapper.inner_mut(), write_buf_pixmap, true)?; Ok(DoubleBufferedRenderPicture { window: RenderPicture { drawable: window, diff --git a/pgwm-core/Cargo.toml b/pgwm-core/Cargo.toml index 7af7ad4..a2dae89 100644 --- a/pgwm-core/Cargo.toml +++ b/pgwm-core/Cargo.toml @@ -8,25 +8,27 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] status-bar = ["time"] -debug = [] +debug = ["xcb-rust-connection/debug", "xcb-rust-protocol/debug", "pgwm-utils/debug"] config-file = ["serde", "heapless/serde", "toml"] [dependencies] -atoi = { version = "1.0.0", default-features = false } -bstr = { version = "0.2.17", default-features = false } -heapless = { version = "0.7.15", default-features = false } -smallmap = { version = "1.4.0", default-features = false } -toml = { version = "0.5.9", optional = true } -thiserror = "1.0.30" -x11-keysyms = { git = "https://github.com/MarcusGrass/x11-keysyms", rev = "4b0f0e3516100666d18df4b6e8eb520b318e1dc9", features = ["miscellany", "latin1"] } +atoi = { version = "2.0.0", default-features = false } +heapless = { version = "0.7.16", default-features = false } +pgwm-utils = { path = "../pgwm-utils" } +toml = { version = "0.5.9", default-features = false, optional = true } +x11-keysyms = { git = "https://github.com/MarcusGrass/x11-keysyms", rev = "da4634c071fdc547772cd4dc7892d857b29dd6e5", features = ["miscellany", "latin1"] } -x11rb = { version = "0.10.0", features = ["render", "cursor"]} +xcb-rust-connection = { version = "0.1" } +xcb-rust-protocol = { version = "0.1" } +tiny-std = { version = "0.1" , features = ["start", "alloc"] } -time = { version = "0.3.11", optional = true, default-features = false, features = ["formatting", "local-offset"] } -serde = { version = "1.0.137", default-features = false, features = ["derive"], optional = true } +time = { version = "0.3.17", optional = true, default-features = false } +serde = { version = "1.0.147", default-features = false, features = ["derive"], optional = true } +smallmap = { version = "1.4.0", default-features = false, features = ["serde"] } [dev-dependencies] -tiny-bench = "0.1.0" +tiny-bench = "0.2.0" +tiny-std = { version = "0.1", features = ["start", "alloc"] } [[bench]] name = "benchmark" diff --git a/pgwm-core/benches/benchmark.rs b/pgwm-core/benches/benchmark.rs index 4fcd7bc..aa70553 100644 --- a/pgwm-core/benches/benchmark.rs +++ b/pgwm-core/benches/benchmark.rs @@ -1,6 +1,8 @@ +use tiny_bench::*; + use pgwm_core::config::NUM_TILING_MODIFIERS; use pgwm_core::geometry::layout::Layout; -use tiny_bench::*; + const VERTICAL_TILING_MODIFIERS: [f32; NUM_TILING_MODIFIERS] = [1.0; NUM_TILING_MODIFIERS]; pub fn main() { diff --git a/pgwm-core/src/colors/mod.rs b/pgwm-core/src/colors/mod.rs index 1703c6e..bb041aa 100644 --- a/pgwm-core/src/colors/mod.rs +++ b/pgwm-core/src/colors/mod.rs @@ -1,4 +1,4 @@ -use std::fmt::Debug; +use core::fmt::Debug; use crate::config::USED_DIFFERENT_COLOR_SEGMENTS; @@ -10,8 +10,9 @@ pub struct Color { impl Color { #[must_use] - pub fn as_render_color(&self) -> x11rb::protocol::render::Color { - x11rb::protocol::render::Color { + #[inline] + pub fn as_render_color(&self) -> xcb_rust_protocol::proto::render::Color { + xcb_rust_protocol::proto::render::Color { red: convert_up(self.bgra8[2]), green: convert_up(self.bgra8[1]), blue: convert_up(self.bgra8[0]), @@ -47,12 +48,13 @@ impl Rgba8 for (u8, u8, u8, u8) { } pub type RGBA = (u8, u8, u8, u8); + /** Color configuration, Here colors are set for different segments that the WM draws. Naming is hopefully fairly self-explanatory for what each color does. A constant can be declared as above for reuse. **/ -#[derive(Copy, Clone, Debug)] +#[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "config-file", derive(serde::Deserialize))] #[cfg_attr(test, derive(PartialEq))] pub struct ColorBuilder { diff --git a/pgwm-core/src/config/key_map.rs b/pgwm-core/src/config/key_map.rs index 5460a7a..ec67225 100644 --- a/pgwm-core/src/config/key_map.rs +++ b/pgwm-core/src/config/key_map.rs @@ -1,5 +1,6 @@ +use xcb_rust_protocol::proto::xproto::ModMask; + use crate::config::Action; -use x11rb::protocol::xproto::ModMask; #[derive(Debug)] pub struct KeyboardMapping { diff --git a/pgwm-core/src/config/mod.rs b/pgwm-core/src/config/mod.rs index d1168f6..b553435 100644 --- a/pgwm-core/src/config/mod.rs +++ b/pgwm-core/src/config/mod.rs @@ -1,14 +1,19 @@ -use crate::colors::ColorBuilder; -use crate::config::key_map::KeyboardMapping; -use crate::config::mouse_map::{MouseMapping, MouseTarget}; -use crate::config::workspaces::UserWorkspace; -use std::collections::HashMap; -use std::path::PathBuf; +use alloc::borrow::ToOwned; +use alloc::string::String; +use alloc::vec; +use alloc::vec::Vec; + +use smallmap::Map; use x11_keysyms::{ XK_Print, XK_Return, XK_b, XK_c, XK_comma, XK_d, XK_f, XK_h, XK_j, XK_k, XK_l, XK_n, XK_period, XK_q, XK_r, XK_space, XK_t, XK_1, XK_2, XK_3, XK_4, XK_5, XK_6, XK_7, XK_8, XK_9, }; -use x11rb::protocol::xproto::{ButtonIndex, Keysym, ModMask}; +use xcb_rust_protocol::proto::xproto::{ButtonIndexEnum, Keysym, ModMask}; + +use crate::colors::ColorBuilder; +use crate::config::key_map::KeyboardMapping; +use crate::config::mouse_map::{MouseMapping, MouseTarget}; +use crate::config::workspaces::UserWorkspace; pub mod key_map; pub mod mouse_map; @@ -43,59 +48,59 @@ pub const WM_NAME_LIMIT: usize = 256; pub const WM_CLASS_NAME_LIMIT: usize = 128; /** - The name that the window manager will broadcast itself as. Will also affect where - configuration is placed/read from. -**/ +The name that the window manager will broadcast itself as. Will also affect where +configuration is placed/read from. + **/ pub const WINDOW_MANAGER_NAME: &str = "pgwm"; /** - Should not be changed, internally used. -**/ +Should not be changed, internally used. + **/ pub const WINDOW_MANAGER_NAME_BUF_SIZE: usize = WINDOW_MANAGER_NAME.len() * 2; /** - How many different color segments are used. eg. tab bar text and status bar text makes 2, - even if they use the same color, - this should not be touched for simple configuration. -**/ +How many different color segments are used. eg. tab bar text and status bar text makes 2, +even if they use the same color, +this should not be touched for simple configuration. + **/ pub const USED_DIFFERENT_COLOR_SEGMENTS: usize = 17; // Configuration that necessarily need to be comptime for working with heapless datastructures /** - How many windows can reside in a workspace, loosely used but if tiling into really small windows - is desired, this can be raised an arbitrary amount. - Not too harsh on stack space. -**/ +How many windows can reside in a workspace, loosely used but if tiling into really small windows +is desired, this can be raised an arbitrary amount. +Not too harsh on stack space. + **/ pub const WS_WINDOW_LIMIT: usize = 16; /** - How many windows that can be managed simultaneously, can be arbitrarily chosen with risk of - crashing if the number is exceeded. - Not too harsh on stack space. -**/ +How many windows that can be managed simultaneously, can be arbitrarily chosen with risk of +crashing if the number is exceeded. +Not too harsh on stack space. + **/ pub const APPLICATION_WINDOW_LIMIT: usize = 128; /** - Size of the binary which stores events to ignore. Since it's flushed on every incoming event above - the given ignored sequence its max required size could be statically determined, but that's a pain, - 64 should be enough. -**/ +Size of the binary which stores events to ignore. Since it's flushed on every incoming event above +the given ignored sequence its max required size could be statically determined, but that's a pain, +64 should be enough. + **/ pub const BINARY_HEAP_LIMIT: usize = 64; /** - Cache size of windows that have been closed but not destroyed yet. These will be destroyed - and later killed if no destroy-notify is received. Can be arbitrarily chosen but will cause - a crash if too low. - Only triggered in the event that for some reason a lot of windows that are misbehaving are manually - closed at the same time and refuse to die within timeout. -**/ +Cache size of windows that have been closed but not destroyed yet. These will be destroyed +and later killed if no destroy-notify is received. Can be arbitrarily chosen but will cause +a crash if too low. +Only triggered in the event that for some reason a lot of windows that are misbehaving are manually +closed at the same time and refuse to die within timeout. + **/ pub const DYING_WINDOW_CACHE: usize = 16; /** - Internally used for writing the render buffer to xft when drawing, 32 gives good performance. -**/ +Internally used for writing the render buffer to xft when drawing, 32 gives good performance. + **/ pub const FONT_WRITE_BUF_LIMIT: usize = 32; /** - Convenience constant -**/ +Convenience constant + **/ pub const NUM_TILING_MODIFIERS: usize = WS_WINDOW_LIMIT - 1; #[cfg_attr(feature = "config-file", derive(serde::Deserialize))] @@ -113,7 +118,7 @@ pub struct Cfg { feature = "config-file", serde(alias = "char-remap", default = "init_char_remap") )] - pub char_remap: HashMap, FontCfg>, + pub char_remap: Map, FontCfg>, #[cfg_attr( feature = "config-file", serde(alias = "workspace", default = "init_workspaces") @@ -135,20 +140,21 @@ pub struct Cfg { } impl Cfg { - pub fn new() -> crate::error::Result { + #[cfg_attr(not(feature = "config-file"), allow(unused_variables))] + pub fn new(config_home: Option<&str>, home: Option<&str>) -> crate::error::Result { #[cfg(feature = "config-file")] { - let mut cfg = match crate::util::load_cfg::load_cfg() { + let mut cfg = match crate::util::load_cfg::load_cfg(config_home, home) { Ok(cfg) => Ok(cfg), // Not having a config file is not an error, fallback to default hard-coded Err(e) => match e { crate::error::Error::ConfigDirFind | crate::error::Error::ConfigFileFind => { - crate::debug!("Failed to find config, loading default"); + pgwm_utils::debug!("Failed to find config, loading default"); Ok(Cfg::default()) } #[allow(unused_variables)] - crate::error::Error::Io(e) => { - crate::debug!("Got io error reading config {e}, loading default"); + crate::error::Error::Syscall(e) => { + pgwm_utils::debug!("Got syscall error reading config {e}, loading default"); Ok(Cfg::default()) } @@ -242,7 +248,7 @@ fn validate_config(cfg: &mut Cfg) -> crate::error::Result<()> { return Err(crate::error::Error::ConfigLogic("Key/mouse mapping(s) out of configured workspace bounds and will cause a crash on activation")); } } - _ => {}, + _ => {} } Ok(()) })?; @@ -334,9 +340,11 @@ pub struct Options { #[cfg_attr(feature = "config-file", serde(default = "default_pad_while_tabbed"))] pub pad_while_tabbed: bool, #[cfg_attr(feature = "config-file", serde(default = "default_destroy_after"))] - pub destroy_after: u64, // Millis before force-close + pub destroy_after: u64, + // Millis before force-close #[cfg_attr(feature = "config-file", serde(default = "default_kill_after"))] - pub kill_after: u64, // Millis before we kill the client + pub kill_after: u64, + // Millis before we kill the client #[cfg_attr(feature = "config-file", serde(default = "default_cursor_name"))] pub cursor_name: String, #[cfg_attr(feature = "config-file", serde(default = "default_show_bar_initially"))] @@ -383,6 +391,7 @@ impl Default for Options { } } } + #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "config-file", derive(serde::Deserialize))] pub struct TilingModifiers { @@ -456,7 +465,7 @@ impl Fonts { } #[must_use] - pub fn get_all_font_paths(&self) -> Vec { + pub fn get_all_font_paths(&self) -> Vec { let it = self .fallback .iter() @@ -466,7 +475,7 @@ impl Fonts { .chain(self.window_name_display_section.iter()); #[cfg(feature = "status-bar")] let it = it.chain(self.status_section.iter()); - it.map(|f_cfg| PathBuf::from(&f_cfg.path)).collect() + it.map(|f_cfg| f_cfg.path.clone()).collect() } } @@ -542,7 +551,7 @@ impl Default for Fonts { #[derive(Debug, Clone)] #[cfg_attr(feature = "config-file", derive(serde::Deserialize))] -#[cfg_attr(test, derive(Eq, PartialEq))] +#[cfg_attr(test, derive(PartialEq))] pub struct SimpleMouseMapping { target: MouseTarget, pub mods: ModMasks, @@ -564,7 +573,7 @@ impl SimpleMouseMapping { #[derive(Debug, Clone)] #[cfg_attr(feature = "config-file", derive(serde::Deserialize))] -#[cfg_attr(test, derive(Eq, PartialEq))] +#[cfg_attr(test, derive(PartialEq))] pub struct SimpleKeyMapping { mods: ModMasks, key: Keysym, @@ -590,7 +599,7 @@ impl SimpleKeyMapping { } #[derive(Copy, Clone, Debug)] -#[cfg_attr(test, derive(Eq, PartialEq))] +#[cfg_attr(test, derive(PartialEq))] pub struct ModMasks { pub inner: ModMask, } @@ -617,13 +626,13 @@ pub(crate) enum ModMaskEnum { } #[derive(Copy, Clone, Debug)] -#[cfg_attr(test, derive(Eq, PartialEq))] +#[cfg_attr(test, derive(PartialEq))] pub struct Button { - pub inner: ButtonIndex, + pub inner: ButtonIndexEnum, } -impl From for Button { - fn from(inner: ButtonIndex) -> Self { +impl From for Button { + fn from(inner: ButtonIndexEnum) -> Self { Button { inner } } } @@ -656,11 +665,11 @@ impl Default for DefaultDraw { } /** - Workspace configuration, names and which window classes should map to each workspace is put here. - If the name is longer than `WS_NAME_LIMIT` the wm will crash on startup. - Similarly if any class name is longer than `MAX_WM_CLASS_NAME` it will crash. - Increase those parameters as needed. -**/ +Workspace configuration, names and which window classes should map to each workspace is put here. +If the name is longer than `WS_NAME_LIMIT` the wm will crash on startup. +Similarly if any class name is longer than `MAX_WM_CLASS_NAME` it will crash. +Increase those parameters as needed. + **/ fn init_workspaces() -> Vec { vec![ UserWorkspace::new( @@ -699,115 +708,115 @@ fn init_workspaces() -> Vec { } /** Which mouse-keys will be grabbed and what actions will be executed if they are pressed. - Actions: - `MoveClient`: Will float a client if tiled and until the pressed button is released the window - will be moved along with the client - Resize: Will resize the client along its tiling axis if tiled, or both axes if floating, by the contained number. - eg. Resize(i16) means that when that button is pressed the window size will increase by 2, - while Resize(-2) means that the window size will decrease by 2 - The unit of 2 is undefined, it's some implementation specific modifier - Available modifiers can be found in `ButtonIndex` imported at the top of this file (although it's M1 through M5). - `MouseTarget` should likely always be `MouseTarget::ClientWindow` -**/ +Actions: +`MoveClient`: Will float a client if tiled and until the pressed button is released the window +will be moved along with the client +Resize: Will resize the client along its tiling axis if tiled, or both axes if floating, by the contained number. +eg. Resize(i16) means that when that button is pressed the window size will increase by 2, +while Resize(-2) means that the window size will decrease by 2 +The unit of 2 is undefined, it's some implementation specific modifier +Available modifiers can be found in `ButtonIndex` imported at the top of this file (although it's M1 through M5). +`MouseTarget` should likely always be `MouseTarget::ClientWindow` + **/ fn init_mouse_mappings() -> Vec { vec![ SimpleMouseMapping { target: MouseTarget::ClientWindow, mods: ModMasks::from(MOD_KEY), - button: Button::from(ButtonIndex::M1), + button: Button::from(ButtonIndexEnum::ONE), on_click: Action::MoveWindow, }, SimpleMouseMapping { target: MouseTarget::ClientWindow, mods: ModMasks::from(MOD_KEY), - button: Button::from(ButtonIndex::M4), + button: Button::from(ButtonIndexEnum::FOUR), on_click: Action::ResizeWindow(4), }, SimpleMouseMapping { target: MouseTarget::ClientWindow, mods: ModMasks::from(MOD_KEY), - button: Button::from(ButtonIndex::M5), + button: Button::from(ButtonIndexEnum::FIVE), on_click: Action::ResizeWindow(-4), }, SimpleMouseMapping { target: MouseTarget::WorkspaceBarComponent(0), mods: ModMasks::from(ModMask::from(0u16)), - button: Button::from(ButtonIndex::M1), + button: Button::from(ButtonIndexEnum::ONE), on_click: Action::ToggleWorkspace(0), }, SimpleMouseMapping { target: MouseTarget::WorkspaceBarComponent(1), mods: ModMasks::from(ModMask::from(0u16)), - button: Button::from(ButtonIndex::M1), + button: Button::from(ButtonIndexEnum::ONE), on_click: Action::ToggleWorkspace(1), }, SimpleMouseMapping { target: MouseTarget::WorkspaceBarComponent(2), mods: ModMasks::from(ModMask::from(0u16)), - button: Button::from(ButtonIndex::M1), + button: Button::from(ButtonIndexEnum::ONE), on_click: Action::ToggleWorkspace(2), }, SimpleMouseMapping { target: MouseTarget::WorkspaceBarComponent(3), mods: ModMasks::from(ModMask::from(0u16)), - button: Button::from(ButtonIndex::M1), + button: Button::from(ButtonIndexEnum::ONE), on_click: Action::ToggleWorkspace(3), }, SimpleMouseMapping { target: MouseTarget::WorkspaceBarComponent(4), mods: ModMasks::from(ModMask::from(0u16)), - button: Button::from(ButtonIndex::M1), + button: Button::from(ButtonIndexEnum::ONE), on_click: Action::ToggleWorkspace(4), }, SimpleMouseMapping { target: MouseTarget::WorkspaceBarComponent(5), mods: ModMasks::from(ModMask::from(0u16)), - button: Button::from(ButtonIndex::M1), + button: Button::from(ButtonIndexEnum::ONE), on_click: Action::ToggleWorkspace(5), }, SimpleMouseMapping { target: MouseTarget::WorkspaceBarComponent(6), mods: ModMasks::from(ModMask::from(0u16)), - button: Button::from(ButtonIndex::M1), + button: Button::from(ButtonIndexEnum::ONE), on_click: Action::ToggleWorkspace(6), }, SimpleMouseMapping { target: MouseTarget::WorkspaceBarComponent(7), mods: ModMasks::from(ModMask::from(0u16)), - button: Button::from(ButtonIndex::M1), + button: Button::from(ButtonIndexEnum::ONE), on_click: Action::ToggleWorkspace(7), }, SimpleMouseMapping { target: MouseTarget::WorkspaceBarComponent(8), mods: ModMasks::from(ModMask::from(0u16)), - button: Button::from(ButtonIndex::M1), + button: Button::from(ButtonIndexEnum::ONE), on_click: Action::ToggleWorkspace(8), }, SimpleMouseMapping { target: MouseTarget::StatusComponent(0), mods: ModMasks::from(ModMask::from(0u16)), - button: Button::from(ButtonIndex::M1), - on_click: Action::Spawn("alacritty".into(), vec!["-e".into(), "htop".into()]), + button: Button::from(ButtonIndexEnum::ONE), + on_click: Action::Spawn("/usr/bin/xterm".into(), vec!["-e".into(), "htop".into()]), }, SimpleMouseMapping { target: MouseTarget::StatusComponent(3), mods: ModMasks::from(ModMask::from(0u16)), - button: Button::from(ButtonIndex::M1), + button: Button::from(ButtonIndexEnum::ONE), on_click: Action::Spawn( - "firefox".into(), + "/usr/bin/firefox".into(), vec!["-new-tab".into(), "https://calendar.google.com".into()], ), }, SimpleMouseMapping { target: MouseTarget::ShortcutComponent(0), mods: ModMasks::from(ModMask::from(0u16)), - button: Button::from(ButtonIndex::M1), + button: Button::from(ButtonIndexEnum::ONE), on_click: Action::Spawn( - "alacritty".into(), + "/usr/bin/xterm".into(), vec![ "-e".into(), // Using bash to access '~' as home - "bash".into(), + "/usr/bin/bash".into(), "-c".into(), // Pop some configuration files in a new terminal "nvim ~/.bashrc ~/.xinitrc ~/.config/pgwm/pgwm.toml".into(), @@ -817,8 +826,8 @@ fn init_mouse_mappings() -> Vec { SimpleMouseMapping { target: MouseTarget::ShortcutComponent(1), mods: ModMasks::from(ModMask::from(0u16)), - button: Button::from(ButtonIndex::M1), - on_click: Action::Spawn("xscreensaver-command".into(), vec!["-lock".into()]), + button: Button::from(ButtonIndexEnum::ONE), + on_click: Action::Spawn("/usr/bin/xscreensaver-command".into(), vec!["-lock".into()]), }, ] } @@ -831,6 +840,7 @@ The checks all take an `icon` parameter which is an arbitrary string drawn next fn init_status_checks( ) -> heapless::Vec { use crate::status::checker::{Check, CheckType, CpuFormat, DateFormat, MemFormat, NetFormat}; + use crate::status::time::{ClockFormatter, Format}; let mut checks = heapless::Vec::new(); /* Commented out because I'm usually not using a computer with batteries and configure those with config files let mut battery_threshholds = heapless::Vec::new(); @@ -904,38 +914,37 @@ fn init_status_checks( ) .unwrap(); crate::push_heapless!( - checks, - // The Dateformat takes a format string, over-engineered explanation here - // https://time-rs.github.io/book/api/format-description.html - // Hopefully the below examples will help - Check { - check_type: CheckType::Date(DateFormat::new( + checks, + Check { + check_type: CheckType::Date(DateFormat::new( heapless::String::from("\u{f073}"), - heapless::String::from("[weekday repr:short] [month repr:short] [day] w[week_number] [hour]:[minute]:[second]"), - time::UtcOffset::from_hms(2, 0, 0).unwrap(), + ClockFormatter::new( + Format::new("{%d%} {%M%} {%D%} v{%W%} {%h%}:{%m%}:{%s%}").unwrap(), + time::UtcOffset::from_hms(1, 0, 0).unwrap() + ), )), - interval: 1000 - } - - ) - .unwrap(); + interval: 1000 + } + ) + .unwrap(); checks } /** - The mod key, maps to super on my machine/key_board, can be changed to any of the available - ModMasks, check the ModMask struct. -**/ -const MOD_KEY: ModMask = ModMask::M4; +The mod key, maps to super on my machine's keyboard, can be changed to any of the available +`ModMasks`, check the `ModMask` struct. + **/ +const MOD_KEY: ModMask = ModMask::FOUR; + /** - Keyboard mapping. - The first argument is a bitwise or of all applied masks or `ModMask::from(0u16)` denoting none. - The second argument is the x11 Keysyms, found here https://cgit.freedesktop.org/xorg/proto/x11proto/tree/keysymdef.h - if more are needed they can be qualified as `x11::keysym::XK_b` or imported at the top of the file with the - others and used more concisely as `XK_b`. - The third parameter is the action that should be taken when the mods and key gets pressed. - It's an enum of which all values are exemplified in the below default configuration. -**/ +Keyboard mapping. +The first argument is a bitwise or of all applied masks or `ModMask::from(0u16)` denoting none. +The second argument is the x11 Keysyms, [found here](https://cgit.freedesktop.org/xorg/proto/x11proto/tree/keysymdef.h) +if more are needed they can be qualified as `x11::keysym::XK_b` or imported at the top of the file with the +others and used more concisely as `XK_b`. +The third parameter is the action that should be taken when the mods and key gets pressed. +It's an enum of which all values are exemplified in the below default configuration. + **/ fn init_key_mappings() -> Vec { vec![ // Shows or hides the top bar @@ -1012,13 +1021,13 @@ fn init_key_mappings() -> Vec { SimpleKeyMapping::new( MOD_KEY | ModMask::SHIFT, XK_Return, - Action::Spawn("alacritty".to_owned(), vec![]), + Action::Spawn("/usr/bin/xterm".to_owned(), vec![]), ), SimpleKeyMapping::new( MOD_KEY, XK_d, Action::Spawn( - "dmenu_run".to_owned(), + "/usr/bin/dmenu_run".to_owned(), vec!["-i".into(), "-p".into(), "Run: ".into()], ), ), @@ -1026,11 +1035,11 @@ fn init_key_mappings() -> Vec { ModMask::from(0u16), XK_Print, Action::Spawn( - "bash".into(), + "/usr/bin/bash".into(), vec![ "-c".into(), // Piping through string pipes ('|') is not valid Rust, just send it to shell instead - "maim -s -u | xclip -selection clipboard -t image/png -i".into(), + "/usr/bin/maim -s -u | xclip -selection clipboard -t image/png -i".into(), ], ), ), @@ -1038,11 +1047,11 @@ fn init_key_mappings() -> Vec { } /** - Overrides specific character drawing. - If some character needs icons from a certain render, they should be mapped below. -**/ -fn init_char_remap() -> HashMap, FontCfg> { - let mut icon_map = HashMap::new(); +Overrides specific character drawing. +If some character needs icons from a certain render, they should be mapped below. + **/ +fn init_char_remap() -> Map, FontCfg> { + let mut icon_map = Map::new(); let icon_font = FontCfg::new( "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", "13.0", diff --git a/pgwm-core/src/config/mouse_map.rs b/pgwm-core/src/config/mouse_map.rs index 6205f16..9e97e4a 100644 --- a/pgwm-core/src/config/mouse_map.rs +++ b/pgwm-core/src/config/mouse_map.rs @@ -1,5 +1,6 @@ +use xcb_rust_protocol::proto::xproto::{ButtonIndexEnum, ModMask}; + use crate::config::Action; -use x11rb::protocol::xproto::{ButtonIndex, ModMask}; #[derive(Debug, Hash, Eq, PartialEq)] pub struct MouseActionKey { @@ -49,7 +50,7 @@ pub struct MouseMapping { pub target: MouseTarget, pub action: Action, pub mods: ModMask, - pub button: ButtonIndex, + pub button: ButtonIndexEnum, } impl MouseMapping { @@ -57,7 +58,7 @@ impl MouseMapping { pub const fn new( target: MouseTarget, mods: ModMask, - button: ButtonIndex, + button: ButtonIndexEnum, action: Action, ) -> Self { MouseMapping { diff --git a/pgwm-core/src/config/workspaces.rs b/pgwm-core/src/config/workspaces.rs index b72ad36..c3db497 100644 --- a/pgwm-core/src/config/workspaces.rs +++ b/pgwm-core/src/config/workspaces.rs @@ -1,3 +1,6 @@ +use alloc::string::String; +use alloc::vec::Vec; + use crate::config::DefaultDraw; #[cfg_attr(feature = "config-file", derive(serde::Deserialize))] diff --git a/pgwm-core/src/error.rs b/pgwm-core/src/error.rs index b87f86b..b12f2ed 100644 --- a/pgwm-core/src/error.rs +++ b/pgwm-core/src/error.rs @@ -1,53 +1,97 @@ -pub type Result = std::result::Result; -#[derive(thiserror::Error, Debug)] +use core::fmt::{Debug, Formatter}; + +use tiny_std::error::Error as StdError; + +use pgwm_utils::from_error; + +pub type Result = core::result::Result; + +#[derive(Debug)] pub enum Error { - #[error("Failed to save old draw mode when going back to from fullscreen (this is a programming error)")] InvalidDrawMode, - #[error("Failed to push to heapless datastructure, limit was {0}")] HeaplessPush(usize), - #[error("Failed to convert heapless vec into array")] HeaplessIntoArray, - #[error("Heapless instantiation impossible, too many items for heapless max_size")] HeaplessInstantiate, + Syscall(StdError), #[cfg(feature = "status-bar")] - #[error("Channel error an check")] Check, #[cfg(feature = "config-file")] - #[error("Failed to find appropriate user config directory after searching environment variables $XDG_CONFIG_HOME falling back to $HOME/.config")] ConfigDirFind, #[cfg(feature = "config-file")] - #[error("Managed to find user config directory but not a config file")] ConfigFileFind, - #[cfg(any(feature = "config-file", feature = "status-bar"))] - #[error("Failed to read config from disk")] - Io(#[from] std::io::Error), #[cfg(feature = "config-file")] - #[error("Failed to parse config")] - ConfigParse(#[from] toml::de::Error), + ConfigParse(toml::de::Error), #[cfg(feature = "config-file")] - #[error("Unsound configuration: Reason {0}")] ConfigLogic(&'static str), + #[cfg(feature = "config-file")] + ConfigP(alloc::string::String), #[cfg(feature = "status-bar")] - #[error(transparent)] - ParseFloat(#[from] std::num::ParseFloatError), + ParseFloat(core::num::ParseFloatError), #[cfg(feature = "status-bar")] - #[error(transparent)] - ParseInt(#[from] std::num::ParseIntError), + ParseInt(core::num::ParseIntError), #[cfg(feature = "status-bar")] - #[error("Failed to parse bat info")] BatParseError, #[cfg(feature = "status-bar")] - #[error("Failed to parse mem_info, reason = {0}")] MemParseError(&'static str), #[cfg(feature = "status-bar")] - #[error("Failed to convert bytes to utf8 string")] - Utf8Convert(#[from] std::string::FromUtf8Error), + Utf8Convert(alloc::string::FromUtf8Error), #[cfg(feature = "status-bar")] - #[error("Failed to parse netstat, could not find in/out")] NetStatParseError, #[cfg(feature = "status-bar")] - #[error("Failed to parse proc stat, could not find aggregate CPU")] ProcStatParseError, - #[error("Error loading render {0}")] - FontLoad(String), + #[cfg(feature = "status-bar")] + FontLoad(alloc::string::String), + #[cfg(feature = "status-bar")] + Time(alloc::string::String), +} +from_error!(StdError, Error, Syscall); +#[cfg(feature = "config-file")] +from_error!(toml::de::Error, Error, ConfigParse); +#[cfg(feature = "status-bar")] +from_error!(core::num::ParseFloatError, Error, ParseFloat); +#[cfg(feature = "status-bar")] +from_error!(core::num::ParseIntError, Error, ParseInt); +#[cfg(feature = "status-bar")] +from_error!(alloc::string::FromUtf8Error, Error, Utf8Convert); + +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + Error::InvalidDrawMode => f.write_str("Failed to save old draw mode when going back to from fullscreen (this is a programming error)"), + Error::HeaplessPush(u) => f.write_fmt(format_args!("Failed to push to heapless datastructure, limit was {u}")), + Error::HeaplessIntoArray => f.write_str("Failed to convert heapless vec into array"), + Error::HeaplessInstantiate => f.write_str("Heapless instantiation impossible, too many items for heapless max_size"), + #[cfg(feature = "status-bar")] + Error::Check => f.write_str("Channel error an check"), + #[cfg(feature = "config-file")] + Error::ConfigDirFind => f.write_str("Failed to find appropriate user config directory after searching environment variables $XDG_CONFIG_HOME falling back to $HOME/.config"), + #[cfg(feature = "config-file")] + Error::ConfigFileFind => f.write_str("Managed to find user config directory but not a config file"), + #[cfg(feature = "config-file")] + Error::ConfigParse(e) => f.write_fmt(format_args!("Failed to parse config {e}")), + #[cfg(feature = "config-file")] + Error::ConfigLogic(e) => f.write_fmt(format_args!("Invalid configuration {e}")), + #[cfg(feature = "config-file")] + Error::ConfigP(e) => f.write_str(e), + #[cfg(feature = "status-bar")] + Error::ParseFloat(e) => core::fmt::Display::fmt(e, f), + #[cfg(feature = "status-bar")] + Error::ParseInt(e) => core::fmt::Display::fmt(e, f), + #[cfg(feature = "status-bar")] + Error::BatParseError => f.write_str("Failed to parse bat info"), + #[cfg(feature = "status-bar")] + Error::MemParseError(r) => f.write_fmt(format_args!("Failed to parse mem_info, reason = {r}")), + #[cfg(feature = "status-bar")] + Error::Utf8Convert(e) => f.write_fmt(format_args!("Failed to convert bytes to utf8 string {e}")), + #[cfg(feature = "status-bar")] + Error::NetStatParseError => f.write_str("Failed to parse netstat, could not find in/out"), + #[cfg(feature = "status-bar")] + Error::ProcStatParseError => f.write_str("Failed to parse proc stat, could not find aggregate CPU"), + #[cfg(feature = "status-bar")] + Error::Time(t) => f.write_fmt(format_args!("Failed to format time {t}")), + #[cfg(feature = "status-bar")] + Error::FontLoad(s) => f.write_fmt(format_args!("Error loading render {s}")), + Error::Syscall(e) => f.write_fmt(format_args!("Syscall error {e}")), + } + } } diff --git a/pgwm-core/src/geometry/draw.rs b/pgwm-core/src/geometry/draw.rs index c1991fa..dd8c906 100644 --- a/pgwm-core/src/geometry/draw.rs +++ b/pgwm-core/src/geometry/draw.rs @@ -1,7 +1,8 @@ +use xcb_rust_protocol::proto::xproto::Window; + use crate::error::Error; use crate::error::Result; use crate::geometry::layout::Layout; -use x11rb::protocol::xproto::Window; #[derive(Debug, Eq, PartialEq, Copy, Clone)] #[cfg_attr(feature = "config-file", derive(serde::Deserialize))] diff --git a/pgwm-core/src/geometry/layout.rs b/pgwm-core/src/geometry/layout.rs index 8270af9..cdf87b0 100644 --- a/pgwm-core/src/geometry/layout.rs +++ b/pgwm-core/src/geometry/layout.rs @@ -331,6 +331,7 @@ mod tests { use crate::config::WS_WINDOW_LIMIT; use crate::geometry::layout::Layout; use crate::geometry::Dimensions; + const TEST_WIDTH: u32 = 1000; const TEST_HEIGHT: u32 = 1000; const TEST_PAD: i16 = 5; diff --git a/pgwm-core/src/geometry/mod.rs b/pgwm-core/src/geometry/mod.rs index a9b47a3..5c6374c 100644 --- a/pgwm-core/src/geometry/mod.rs +++ b/pgwm-core/src/geometry/mod.rs @@ -1,4 +1,4 @@ -use x11rb::protocol::xproto::Rectangle; +use xcb_rust_protocol::proto::xproto::Rectangle; pub mod draw; pub mod layout; diff --git a/pgwm-core/src/lib.rs b/pgwm-core/src/lib.rs index e96df47..6f5d522 100644 --- a/pgwm-core/src/lib.rs +++ b/pgwm-core/src/lib.rs @@ -5,7 +5,7 @@ #![allow(clippy::too_many_lines)] #![allow(clippy::missing_errors_doc)] #![allow(clippy::missing_panics_doc)] -#![allow(clippy::let_underscore_drop)] +#![allow(let_underscore_drop)] #![allow(clippy::needless_pass_by_value)] // X11 uses inconsistent integer types fairly interchangeably #![allow(clippy::cast_lossless)] @@ -14,6 +14,11 @@ #![allow(clippy::cast_possible_wrap)] #![allow(clippy::cast_precision_loss)] #![allow(clippy::module_name_repetitions)] +// Debug log complaints +#![allow(clippy::used_underscore_binding)] +#![cfg_attr(not(test), no_std)] +extern crate alloc; + pub mod colors; pub mod config; pub mod error; @@ -23,19 +28,3 @@ pub mod state; #[cfg(feature = "status-bar")] pub mod status; pub mod util; - -#[cfg(test)] -mod tests { - use crate::config::Cfg; - use crate::state::State; - - #[test] - fn pin_stack_sizes() { - let cfg_stack_size = std::mem::size_of::(); - let state_stack_size = std::mem::size_of::(); - assert!(cfg_stack_size < 20_000); - assert!(state_stack_size < 40_000); - println!("{cfg_stack_size}"); - println!("{state_stack_size}"); - } -} diff --git a/pgwm-core/src/render/mod.rs b/pgwm-core/src/render/mod.rs index 44eaf8d..7c8f43f 100644 --- a/pgwm-core/src/render/mod.rs +++ b/pgwm-core/src/render/mod.rs @@ -1,5 +1,5 @@ -use x11rb::protocol::render::{Directformat, Pictformat, Picture}; -use x11rb::protocol::xproto::{Drawable, Visualid}; +use xcb_rust_protocol::proto::render::{Directformat, Pictformat, Picture}; +use xcb_rust_protocol::proto::xproto::{Drawable, Visualid}; pub struct RenderPicture { pub drawable: Drawable, diff --git a/pgwm-core/src/state/bar_geometry.rs b/pgwm-core/src/state/bar_geometry.rs index fc1b0a4..5e24dde 100644 --- a/pgwm-core/src/state/bar_geometry.rs +++ b/pgwm-core/src/state/bar_geometry.rs @@ -1,3 +1,6 @@ +use alloc::string::String; +use alloc::vec::Vec; + use crate::config::mouse_map::MouseTarget; use crate::config::WM_NAME_LIMIT; #[cfg(feature = "status-bar")] @@ -28,7 +31,7 @@ impl BarGeometry { self.window_title_section .position .contains(x) - .then(|| MouseTarget::WindowTitle) + .then_some(MouseTarget::WindowTitle) }) } #[cfg(not(feature = "status-bar"))] @@ -37,7 +40,7 @@ impl BarGeometry { self.window_title_section .position .contains(x) - .then(|| MouseTarget::WindowTitle) + .then_some(MouseTarget::WindowTitle) }) } } @@ -103,7 +106,7 @@ impl ShortcutSection { .find_map(|(ind, component)| { (x >= component.position.start && x <= component.position.start + component.position.length) - .then(|| MouseTarget::ShortcutComponent(ind)) + .then_some(MouseTarget::ShortcutComponent(ind)) }) }) .flatten() @@ -205,7 +208,7 @@ impl StatusSection { .find_map(|(ind, component)| { (x >= component.position.start && x <= component.position.start + component.position.length) - .then(|| MouseTarget::StatusComponent(ind)) + .then_some(MouseTarget::StatusComponent(ind)) }) }) .flatten() @@ -223,6 +226,7 @@ pub struct WorkspaceSection { pub position: Line, pub components: Vec, } + impl WorkspaceSection { fn hit_component(&self, x: i16) -> Option { (x >= self.position.start && x <= self.position.start + self.position.length) @@ -233,12 +237,13 @@ impl WorkspaceSection { .find_map(|(ind, component)| { (x >= component.position.start && x <= component.position.start + component.position.length) - .then(|| MouseTarget::WorkspaceBarComponent(ind)) + .then_some(MouseTarget::WorkspaceBarComponent(ind)) }) }) .flatten() } } + pub struct FixedDisplayComponent { pub position: Line, pub write_offset: i16, diff --git a/pgwm-core/src/state/mod.rs b/pgwm-core/src/state/mod.rs index a25d095..0084c90 100644 --- a/pgwm-core/src/state/mod.rs +++ b/pgwm-core/src/state/mod.rs @@ -1,22 +1,18 @@ -pub mod bar_geometry; -pub mod properties; -pub mod workspace; +use alloc::string::String; +use alloc::vec::Vec; +use core::ops::{Add, Sub}; +use core::time::Duration; -use crate::error::Result; use heapless::binary_heap::Min; -use std::collections::HashMap; -use std::ops::Sub; -use std::{ - ops::Add, - time::{Duration, SystemTime}, -}; - -use x11rb::protocol::xproto::{Screen, Window}; -use x11rb::xcb::xproto::Timestamp; +use smallmap::Map; +use tiny_std::time::Instant; +use xcb_rust_protocol::proto::xproto::Timestamp; +use xcb_rust_protocol::proto::xproto::{Screen, Window}; use crate::config::key_map::KeyBoardMappingKey; use crate::config::mouse_map::{MouseActionKey, MouseTarget}; use crate::config::Action; +use crate::error::Result; use crate::geometry::draw::Mode; use crate::geometry::Dimensions; use crate::render::DoubleBufferedRenderPicture; @@ -27,6 +23,10 @@ use crate::{ state::workspace::Workspaces, }; +pub mod bar_geometry; +pub mod properties; +pub mod workspace; + #[allow(clippy::struct_excessive_bools)] pub struct State { pub wm_check_win: Window, @@ -51,8 +51,8 @@ pub struct State { pub destroy_after: u64, pub kill_after: u64, pub show_bar_initially: bool, - pub mouse_mapping: HashMap, - pub key_mapping: HashMap, + pub mouse_mapping: Map, + pub key_mapping: Map, pub last_timestamp: Timestamp, } @@ -217,7 +217,7 @@ impl State { if self.focused_mon == new_focus { None } else { - Some(std::mem::replace(&mut self.focused_mon, new_focus)) + Some(core::mem::replace(&mut self.focused_mon, new_focus)) } } } @@ -238,6 +238,7 @@ pub struct DrawArea { pub width: i16, pub window: Window, } + pub struct DragPosition { origin_x: i16, origin_y: i16, @@ -247,6 +248,7 @@ pub struct DragPosition { impl DragPosition { #[must_use] + #[inline] pub fn new(origin_x: i16, origin_y: i16, event_origin_x: i16, event_origin_y: i16) -> Self { DragPosition { origin_x, @@ -257,6 +259,7 @@ impl DragPosition { } #[must_use] + #[inline] pub fn current_position(&self, cursor_x: i16, cursor_y: i16) -> (i16, i16) { let x = self.origin_x + cursor_x - self.event_origin_x; let y = self.origin_y + cursor_y - self.event_origin_y; @@ -267,7 +270,7 @@ impl DragPosition { #[derive(Debug, Clone, Copy)] pub struct WinMarkedForDeath { pub win: Window, - die_at: SystemTime, + die_at: Instant, pub sent_destroy: bool, } @@ -276,22 +279,35 @@ impl WinMarkedForDeath { pub fn new(win: Window, destroy_after: u64) -> Self { Self { win, - die_at: SystemTime::now().add(Duration::from_millis(destroy_after)), + die_at: Instant::now() + .add(Duration::from_millis(destroy_after)) + .unwrap(), sent_destroy: false, } } #[must_use] pub fn should_kill(&self, kill_after: u64) -> bool { - self.sent_destroy && self.die_at <= SystemTime::now().sub(Duration::from_millis(kill_after)) + self.sent_destroy + && self.die_at + <= Instant::now() + .sub(Duration::from_millis(kill_after)) + .unwrap() } #[must_use] pub fn should_destroy(&self) -> bool { - self.die_at <= SystemTime::now() + self.die_at <= Instant::now() } } #[cfg(test)] mod tests { + use alloc::string::String; + use alloc::vec; + + use smallmap::Map; + use xcb_rust_protocol::proto::xproto::{BackingStoreEnum, EventMask, Screen}; + use xcb_rust_protocol::CURRENT_TIME; + use crate::colors::{Color, Colors}; use crate::config::{Cfg, USED_DIFFERENT_COLOR_SEGMENTS}; use crate::geometry::{Dimensions, Line}; @@ -302,8 +318,6 @@ mod tests { use crate::state::properties::{WindowProperties, WmName}; use crate::state::workspace::{ArrangeKind, FocusStyle, ManagedWindow, Workspaces}; use crate::state::{Monitor, State}; - use x11rb::protocol::xproto::{BackingStore, Screen}; - use x11rb::CURRENT_TIME; fn create_base_state() -> State { let cfg = Cfg::default(); @@ -432,7 +446,7 @@ mod tests { default_colormap: 0, white_pixel: 0, black_pixel: 0, - current_input_masks: 0, + current_input_masks: EventMask::NO_EVENT, width_in_pixels: 0, height_in_pixels: 0, width_in_millimeters: 0, @@ -440,8 +454,8 @@ mod tests { min_installed_maps: 0, max_installed_maps: 0, root_visual: 0, - backing_stores: BackingStore::NOT_USEFUL, - save_unders: false, + backing_stores: BackingStoreEnum::NOT_USEFUL, + save_unders: 0, root_depth: 0, allowed_depths: vec![], }, @@ -460,8 +474,8 @@ mod tests { destroy_after: 0, kill_after: 0, show_bar_initially: true, - mouse_mapping: std::collections::HashMap::default(), - key_mapping: std::collections::HashMap::default(), + mouse_mapping: Map::default(), + key_mapping: Map::default(), last_timestamp: CURRENT_TIME, } } diff --git a/pgwm-core/src/state/properties.rs b/pgwm-core/src/state/properties.rs index bbb4240..e905884 100644 --- a/pgwm-core/src/state/properties.rs +++ b/pgwm-core/src/state/properties.rs @@ -1,6 +1,7 @@ +use xcb_rust_protocol::helpers::properties::{WmHints, WmSizeHints}; +use xcb_rust_protocol::proto::xproto::Window; + use crate::config::{WM_CLASS_NAME_LIMIT, WM_NAME_LIMIT}; -use x11rb::properties::{WmHints, WmSizeHints}; -use x11rb::xcb::xproto::Window; #[derive(Debug, Clone)] pub struct WindowProperties { diff --git a/pgwm-core/src/state/workspace.rs b/pgwm-core/src/state/workspace.rs index fd3279d..62ae351 100644 --- a/pgwm-core/src/state/workspace.rs +++ b/pgwm-core/src/state/workspace.rs @@ -1,3 +1,9 @@ +use alloc::string::String; +use alloc::vec::Vec; + +use smallmap::Map; +use xcb_rust_protocol::proto::xproto::Window; + use crate::config::workspaces::UserWorkspace; use crate::config::{DefaultDraw, TilingModifiers, APPLICATION_WINDOW_LIMIT, WS_WINDOW_LIMIT}; use crate::error::Result; @@ -5,7 +11,6 @@ use crate::geometry::draw::{Mode, OldDrawMode}; use crate::geometry::layout::Layout; use crate::state::properties::WindowProperties; use crate::util::vec_ops::push_to_front; -use x11rb::protocol::xproto::Window; #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] @@ -13,9 +18,9 @@ pub struct Workspaces { // Hot read spaces: Vec, // Hot on read/write - win_to_ws: smallmap::Map, + win_to_ws: Map, // Hot read - name_to_ws: smallmap::Map, + name_to_ws: Map, base_tiling_modifiers: TilingModifiers, } @@ -25,7 +30,7 @@ impl Workspaces { tiling_modifiers: TilingModifiers, ) -> Result { let mut v = Vec::::new(); - let mut name_to_ws = smallmap::Map::new(); + let mut name_to_ws = Map::new(); for (i, ws) in init_workspaces.iter().enumerate() { v.push(Workspace { draw_mode: match ws.default_draw { @@ -43,7 +48,7 @@ impl Workspaces { } Ok(Workspaces { spaces: v, - win_to_ws: smallmap::Map::new(), + win_to_ws: Map::new(), name_to_ws, base_tiling_modifiers: tiling_modifiers, }) @@ -105,12 +110,18 @@ impl Workspaces { } = dm { ws.draw_mode = last_draw_mode.to_draw_mode(); + // If it's not managed we want to remove it from the ws mapping to avoid a memory leak + if ws.find_managed_window(window).is_none() { + self.win_to_ws.remove(&window); + } return Some(window); } None } pub fn set_fullscreened(&mut self, ws_ind: usize, window: Window) -> Result> { + // We want to be able to track if a ws owns a fullscreened window even if it's not managed + self.win_to_ws.insert(window, ws_ind); let ws = &mut self.spaces[ws_ind]; let dm = ws.draw_mode; let (new_mode, old_fullscreen) = match dm { @@ -213,7 +224,7 @@ impl Workspaces { if mw.arrange == floating { false } else { - crate::debug!("Floating {}", mw.window); + pgwm_utils::debug!("Floating {}", mw.window); mw.arrange = floating; true } @@ -288,7 +299,19 @@ impl Workspaces { self.win_to_ws.remove(&child.window); } } - self.spaces[ind].delete_child(window) + let dr = self.spaces[ind].delete_child(window); + // We need to remove fullscreen status If the window was fullscreened + // or else we'll have a bug + if let Mode::Fullscreen { + window: fs_window, + last_draw_mode, + } = self.spaces[ind].draw_mode + { + if fs_window == window { + self.spaces[ind].draw_mode = last_draw_mode.to_draw_mode(); + } + }; + dr }) } @@ -319,7 +342,7 @@ impl Workspaces { pub fn find_first_tiled(&self, num: usize) -> Option { self.spaces[num] .iter_all_windows() - .find_map(|ch| (ch.arrange == ArrangeKind::NoFloat).then(|| ch.window)) + .find_map(|ch| (ch.arrange == ArrangeKind::NoFloat).then_some(ch.window)) } #[must_use] @@ -409,7 +432,7 @@ impl Workspace { focus_style: FocusStyle, properties: WindowProperties, ) -> Result<()> { - crate::debug!("Adding child to ws: win = {} {:?}", window, arrange,); + pgwm_utils::debug!("Adding child to ws: win = {} {:?}", window, arrange,); for child in &mut self.children { if child.managed.window == window { child.managed.arrange = arrange; @@ -434,13 +457,13 @@ impl Workspace { fn iter_all_windows(&self) -> impl Iterator { self.children .iter() - .flat_map(|ch| std::iter::once(&ch.managed).chain(ch.attached.iter())) + .flat_map(|ch| core::iter::once(&ch.managed).chain(ch.attached.iter())) } fn iter_all_windows_mut(&mut self) -> impl Iterator { self.children .iter_mut() - .flat_map(|ch| std::iter::once(&mut ch.managed).chain(ch.attached.iter_mut())) + .flat_map(|ch| core::iter::once(&mut ch.managed).chain(ch.attached.iter_mut())) } fn resize_children(&mut self, window: Window, resize: f32) -> bool { @@ -575,7 +598,7 @@ impl Workspace { for child in &mut self.children { if let Some(ind) = child.attached.iter().position(|tr| tr.window == window) { let mw = crate::util::vec_ops::remove(&mut child.attached, ind); - crate::debug!("Removed attached from ws {:?}", child); + pgwm_utils::debug!("Removed attached from ws {:?}", child); return if mw.arrange == ArrangeKind::NoFloat { DeleteResult::AttachedTiled((child.managed.window, mw)) } else { @@ -743,6 +766,9 @@ impl ManagedWindow { #[cfg(test)] mod tests { + use alloc::vec; + use alloc::vec::Vec; + use crate::config::Cfg; use crate::geometry::draw::Mode; use crate::geometry::layout::Layout; @@ -824,7 +850,7 @@ mod tests { 2, ArrangeKind::FloatingInactive(0.0, 0.0), FocusStyle::Passive, - &default_properties() + &default_properties(), ) .unwrap()); assert!(workspaces.get_managed_win(0).is_some()); diff --git a/pgwm-core/src/status/checker.rs b/pgwm-core/src/status/checker.rs index 4d6e930..84f128d 100644 --- a/pgwm-core/src/status/checker.rs +++ b/pgwm-core/src/status/checker.rs @@ -1,19 +1,21 @@ +use alloc::string::ToString; +use core::cmp::Ordering; +use core::ops::Add; +use core::time::Duration; + +use heapless::binary_heap::Min; +use heapless::{BinaryHeap, String}; +use tiny_std::time::Instant; + use crate::config::{ - STATUS_BAR_BAT_SEGMENT_LIMIT, STATUS_BAR_CHECK_CONTENT_LIMIT, STATUS_BAR_DATE_PATTERN_LIMIT, - STATUS_BAR_UNIQUE_CHECK_LIMIT, + STATUS_BAR_BAT_SEGMENT_LIMIT, STATUS_BAR_CHECK_CONTENT_LIMIT, STATUS_BAR_UNIQUE_CHECK_LIMIT, }; use crate::format_heapless; use crate::status::cpu::LoadChecker; -use crate::status::sys::mem::Data; -use heapless::binary_heap::Min; -use heapless::{BinaryHeap, String}; -use std::cmp::Ordering; -use std::ops::Add; -use std::time::{Duration, Instant}; -use time::format_description::FormatItem; -use time::{OffsetDateTime, UtcOffset}; - use crate::status::net::{ThroughputChecker, ThroughputPerSec}; +use crate::status::sys::bat::BatChecker; +use crate::status::sys::mem::{Data, MemChecker}; +use crate::status::time::ClockFormatter; #[cfg_attr(feature = "config-file", derive(serde::Deserialize))] #[derive(Debug, Clone, Eq, PartialEq)] @@ -235,53 +237,37 @@ impl MemFormat { #[derive(Debug, Clone, Eq, PartialEq)] pub struct DateFormat { icon: String, - pub pattern: String, - #[cfg_attr(feature = "config-file", serde(deserialize_with = "from_hms_tuple"))] - utc_offset: UtcOffset, -} - -#[cfg(feature = "config-file")] -fn from_hms_tuple<'de, D: serde::de::Deserializer<'de>>( - deserializer: D, -) -> std::result::Result { - let (h, m, s): (i8, i8, i8) = serde::de::Deserialize::deserialize(deserializer)?; - UtcOffset::from_hms(h, m, s) - .map_err(|d| serde::de::Error::custom(format!("Failed to parse utc-offset {:?}", d))) + clock_formatter: ClockFormatter, } impl DateFormat { #[must_use] pub fn new( icon: String, - pattern: String, - utc_offset: UtcOffset, + clock_formatter: ClockFormatter, ) -> Self { Self { icon, - pattern, - utc_offset, + clock_formatter, } } #[must_use] - pub fn format_date<'a>( - &self, - items: &'a [FormatItem<'a>], - ) -> String { - let dt = OffsetDateTime::now_utc() - .to_offset(self.utc_offset) - .format(items) - .ok() - .unwrap_or_else(|| "Failed Dt parse".to_owned()); - format_heapless!("{} {}", self.icon, &dt) + pub fn format_date(&self) -> String { + let output = self + .clock_formatter + .format_now() + .unwrap_or_else(|_| "Failed to format get date".to_string()); + format_heapless!("{} {}", self.icon, output) } } pub struct Checker<'a> { cpu_checker: LoadChecker, net_checker: ThroughputChecker, + mem_checker: MemChecker, + bat_checker: BatChecker, check_heap: BinaryHeap, Min, STATUS_BAR_UNIQUE_CHECK_LIMIT>, - date_fmt: std::prelude::rust_2021::Vec>, } #[derive(PartialEq, Eq)] @@ -296,7 +282,8 @@ impl<'a> PackagedCheck<'a> { // Using this instead of SystemTime now avoids de-syncs between checks and unnecessary system calls self.next_time = self .next_time - .add(Duration::from_millis(self.check.interval)); + .add(Duration::from_millis(self.check.interval)) + .unwrap(); } } @@ -359,7 +346,9 @@ impl<'a> Checker<'a> { packaged: &mut PackagedCheck, ) -> Option> { match &packaged.check.check_type { - CheckType::Battery(limits) => crate::status::sys::bat::get_battery_percentage() + CheckType::Battery(limits) => self + .bat_checker + .get_battery_percentage() .ok() .and_then(|bat| limits.iter().find_map(|limit| limit.format_bat(bat))), CheckType::Cpu(fmt) => self @@ -372,10 +361,12 @@ impl<'a> Checker<'a> { .get_throughput() .ok() .map(|tp| fmt.format_net(tp)), - CheckType::Mem(fmt) => crate::status::sys::mem::read_mem_info() + CheckType::Mem(fmt) => self + .mem_checker + .read_mem_info() .ok() .map(|mem| fmt.format_mem(mem)), - CheckType::Date(fmt) => Some(fmt.format_date(self.date_fmt.as_slice())), + CheckType::Date(fmt) => Some(fmt.format_date()), } } @@ -396,28 +387,25 @@ impl<'a> Checker<'a> { position, }); } - let mut date_fmt = Vec::new(); - for check in checks.iter() { - if let CheckType::Date(df) = &check.check_type { - date_fmt = time::format_description::parse(&df.pattern).unwrap_or_default(); - break; // Only one date check atm - } - } Checker { cpu_checker: LoadChecker::default(), net_checker: ThroughputChecker::default(), + mem_checker: MemChecker::default(), + bat_checker: BatChecker::default(), check_heap, - date_fmt, } } } #[cfg(test)] mod checker_tests { - use crate::status::checker::{Check, CheckType, Checker, CpuFormat}; + use core::ops::{Add, Sub}; + use core::time::Duration; + use pgwm_utils::unix_eprintln; use std::collections::HashSet; - use std::ops::{Add, Sub}; - use std::time::{Duration, Instant}; + use tiny_std::time::Instant; + + use crate::status::checker::{Check, CheckType, Checker, CpuFormat}; #[test] #[should_panic] @@ -441,9 +429,9 @@ mod checker_tests { let mut checker = Checker::new(&mut checks); let result = checker.run_next(true); assert!(result.content.is_none()); - assert!(result.next_check >= now + interval); + assert!(result.next_check >= now.add(interval).unwrap()); // If this test takes more than 10 seconds there are other issues - assert!(result.next_check < now + 2 * interval); + assert!(result.next_check < now.add(2 * interval).unwrap()); } // Risk for flakiness @@ -479,7 +467,7 @@ mod checker_tests { let start = Instant::now(); // Need some end duration that's big enough to allow all checks at least one run but low enough // to need cause multiplication overlap - let end = start.add(Duration::from_millis(13)); + let end = start.add(Duration::from_millis(13)).unwrap(); let mut acquired_check_times = HashSet::new(); let mut checker = Checker::new(&mut checks); @@ -492,10 +480,11 @@ mod checker_tests { acquired_check_times.insert(next); } assert!(first_check_time.is_some()); - let years_ago = Instant::now().sub(Duration::from_secs(1_000_000_000)); + let long_ago = Instant::now().sub(Duration::from_secs(10)).unwrap(); let first_check = first_check_time .unwrap() - .duration_since(years_ago) + .duration_since(long_ago) + .unwrap() .as_nanos(); assert_eq!(9, acquired_check_times.len()); @@ -504,7 +493,8 @@ mod checker_tests { let mut div_by_seven = 0; for time in acquired_check_times { // Since next time should be a clean add in the checker we should be able to use nano-time without a problem - let nanos_of_check = time.duration_since(years_ago).as_nanos(); + let nanos_of_check = time.duration_since(long_ago).unwrap().as_nanos(); + unix_eprintln!("{nanos_of_check} - {first_check}"); let diff = nanos_of_check - first_check; assert_eq!(0, diff % 1_000_000); // All should be millis expanded to nano time, no clock drift let compacted = diff / 1_000_000; @@ -543,8 +533,8 @@ mod checker_tests { let mut checker = Checker::new(&mut checks); let result = checker.run_next(false); assert!(result.content.is_some()); - assert!(result.next_check >= now + interval); + assert!(result.next_check >= now.add(interval).unwrap()); // If this test takes more than 10 seconds there are other issues - assert!(result.next_check < now + 2 * interval); + assert!(result.next_check < now.add(2 * interval).unwrap()); } } diff --git a/pgwm-core/src/status/mod.rs b/pgwm-core/src/status/mod.rs index 732d027..dbb99b6 100644 --- a/pgwm-core/src/status/mod.rs +++ b/pgwm-core/src/status/mod.rs @@ -2,3 +2,4 @@ pub mod checker; pub mod cpu; pub mod net; pub mod sys; +pub mod time; diff --git a/pgwm-core/src/status/net.rs b/pgwm-core/src/status/net.rs index 2a0914e..3fd8e76 100644 --- a/pgwm-core/src/status/net.rs +++ b/pgwm-core/src/status/net.rs @@ -1,11 +1,12 @@ +use tiny_std::time::Instant; + use crate::error::Error; use crate::status::sys::net::Data; -use std::time::SystemTime; #[derive(Clone)] pub struct ThroughputChecker { prev_data: Data, - prev_check: SystemTime, + prev_check: Instant, } #[derive(Default, Clone, Copy, Debug)] @@ -17,11 +18,10 @@ pub struct ThroughputPerSec { impl ThroughputChecker { pub fn get_throughput(&mut self) -> Result { let net_stats = crate::status::sys::net::read_net_stats()?; - let now = SystemTime::now(); + let now = Instant::now(); let time_passed = now .duration_since(self.prev_check) - .map(|d| d.as_secs_f64()) - .unwrap_or(1f64); + .map_or(1f64, |d| d.as_secs_f64()); let in_diff = (net_stats.bytes_in - self.prev_data.bytes_in) as f64 / time_passed; let out_diff = (net_stats.bytes_out - self.prev_data.bytes_out) as f64 / time_passed; @@ -43,7 +43,7 @@ impl Default for ThroughputChecker { }; Self { prev_data: throughput, - prev_check: SystemTime::now(), + prev_check: Instant::now(), } } } diff --git a/pgwm-core/src/status/sys/bat/mod.rs b/pgwm-core/src/status/sys/bat/mod.rs index 8e17dd5..976e2be 100644 --- a/pgwm-core/src/status/sys/bat/mod.rs +++ b/pgwm-core/src/status/sys/bat/mod.rs @@ -1,8 +1,26 @@ +use tiny_std::fs::OpenOptions; +use tiny_std::io::Read; + use crate::error::Error; -const BAT_FILE: &str = "/sys/class/power_supply/BAT0/capacity"; +const BAT_FILE: &str = "/sys/class/power_supply/BAT0/capacity\0"; + +#[derive(Debug, Default)] +pub struct BatChecker { + read_buf: [u8; 8], +} + +impl BatChecker { + #[inline] + pub fn get_battery_percentage(&mut self) -> Result { + get_battery_percentage(&mut self.read_buf) + } +} -pub fn get_battery_percentage() -> Result { - let content = std::fs::read(BAT_FILE)?; - atoi::atoi(&content).ok_or(Error::BatParseError) +#[allow(unsafe_code)] +#[inline] +pub fn get_battery_percentage(buf: &mut [u8]) -> Result { + let mut file = OpenOptions::new().read(true).open(BAT_FILE)?; + let bytes = file.read(buf)?; + atoi::atoi(&buf[..bytes]).ok_or(Error::BatParseError) } diff --git a/pgwm-core/src/status/sys/cpu/mod.rs b/pgwm-core/src/status/sys/cpu/mod.rs index ff558b6..77fc33f 100644 --- a/pgwm-core/src/status/sys/cpu/mod.rs +++ b/pgwm-core/src/status/sys/cpu/mod.rs @@ -1,7 +1,7 @@ use crate::error::Error; -use bstr::ByteSlice; +use crate::status::sys::find_byte; -const LOAD_FILE: &str = "/proc/stat"; +const LOAD_FILE: &str = "/proc/stat\0"; #[derive(Debug, Default)] pub struct Load { @@ -9,9 +9,10 @@ pub struct Load { pub idle: f64, } +#[allow(unsafe_code)] pub fn read_cpu_load() -> Result { - let content = std::fs::read(LOAD_FILE)?; - parse_raw(&content) + let buf = tiny_std::fs::read(LOAD_FILE)?; + parse_raw(&buf) } pub fn parse_raw(content: &[u8]) -> Result { @@ -19,7 +20,7 @@ pub fn parse_raw(content: &[u8]) -> Result { let mut it = 0; let mut busy = 0; let mut idle = 0; - while let Some(space_ind) = content[prev_ind..].find_byte(b' ') { + while let Some(space_ind) = find_byte(b' ', &content[prev_ind..]) { if prev_ind == 0 { prev_ind = space_ind; continue; @@ -39,6 +40,7 @@ pub fn parse_raw(content: &[u8]) -> Result { space_ind }; } + Ok(Load { busy: busy as f64, idle: idle as f64, diff --git a/pgwm-core/src/status/sys/mem/mod.rs b/pgwm-core/src/status/sys/mem/mod.rs index f35f5b7..51f26b4 100644 --- a/pgwm-core/src/status/sys/mem/mod.rs +++ b/pgwm-core/src/status/sys/mem/mod.rs @@ -1,7 +1,7 @@ use crate::error::Error; -use bstr::ByteSlice; +use crate::status::sys::{find_byte, find_in_haystack}; -const MEM_LOAD: &str = "/proc/meminfo"; +const MEM_LOAD: &str = "/proc/meminfo\0"; #[derive(Debug, Copy, Clone, Default)] pub struct Data { @@ -10,9 +10,21 @@ pub struct Data { pub swapped: u64, } +#[derive(Debug, Clone, Default)] +pub struct MemChecker; + +impl MemChecker { + #[inline] + pub fn read_mem_info(&mut self) -> Result { + read_mem_info() + } +} + +#[allow(unsafe_code)] +#[inline] pub fn read_mem_info() -> Result { - let content = std::fs::read(MEM_LOAD)?; - parse_raw(&content) + let buf = tiny_std::fs::read(MEM_LOAD)?; + parse_raw(&buf) } pub fn parse_raw(mem_info: &[u8]) -> Result { @@ -28,7 +40,7 @@ pub fn parse_raw(mem_info: &[u8]) -> Result { } fn parse_line_containing(haystack: &[u8], needle: &[u8]) -> Result { - if let Some(start) = haystack.find(needle) { + if let Some(start) = find_in_haystack(haystack, needle) { // We're not even close to the end here let sub_target = &haystack[start..]; parse_kb_value(sub_target) @@ -40,12 +52,12 @@ fn parse_line_containing(haystack: &[u8], needle: &[u8]) -> Result { } fn parse_kb_value(target: &[u8]) -> Result { - if let Some(next_space_ind) = target.find_byte(b' ') { + if let Some(next_space_ind) = find_byte(b' ', target) { let mut next_non_space = next_space_ind; while target[next_non_space] == b' ' { next_non_space += 1; } - if let Some(next_space) = target[next_non_space..].find_byte(b' ') { + if let Some(next_space) = find_byte(b' ', &target[next_non_space..]) { return atoi::atoi(&target[next_non_space..next_non_space + next_space]) .ok_or(Error::MemParseError("Failed to parse kb value of mem info")); } diff --git a/pgwm-core/src/status/sys/mod.rs b/pgwm-core/src/status/sys/mod.rs index b0f24fe..4c73301 100644 --- a/pgwm-core/src/status/sys/mod.rs +++ b/pgwm-core/src/status/sys/mod.rs @@ -2,3 +2,20 @@ pub mod bat; pub mod cpu; pub mod mem; pub mod net; + +#[inline] +fn find_in_haystack(haystack: &[u8], needle: &[u8]) -> Option { + let hay_len = haystack.len(); + let needle_len = needle.len(); + (0..hay_len - needle_len).find(|&i| &haystack[i..i + needle_len] == needle) +} + +#[inline] +fn find_byte(tgt: u8, bytes: &[u8]) -> Option { + for (ind, byte) in bytes.iter().enumerate() { + if byte == &tgt { + return Some(ind); + } + } + None +} diff --git a/pgwm-core/src/status/sys/net/mod.rs b/pgwm-core/src/status/sys/net/mod.rs index 4a14d80..9198259 100644 --- a/pgwm-core/src/status/sys/net/mod.rs +++ b/pgwm-core/src/status/sys/net/mod.rs @@ -1,21 +1,24 @@ use crate::error::Error; -use bstr::ByteSlice; +use crate::status::sys::{find_byte, find_in_haystack}; -const NET_STAT: &str = "/proc/net/netstat"; +const NET_STAT: &str = "/proc/net/netstat\0"; +#[allow(unsafe_code)] pub fn read_net_stats() -> Result { - let content = std::fs::read(NET_STAT)?; - parse_raw(&content) + let buf = tiny_std::fs::read(NET_STAT)?; + parse_raw(&buf) } pub fn parse_raw(raw_data: &[u8]) -> Result { - if let Some(label_line_start) = raw_data.find(b"IpExt: ") { - if let Some(real_line_start) = raw_data[label_line_start + 1..].find(b"IpExt: ") { + if let Some(label_line_start) = find_in_haystack(raw_data, b"IpExt: ") { + if let Some(real_line_start) = + find_in_haystack(&raw_data[label_line_start + 1..], b"IpExt: ") + { let real_line_start = label_line_start + real_line_start; let mut prev_ind = real_line_start; let mut it = 0; let mut bytes_in = 0; - while let Some(space_ind) = raw_data[prev_ind..].find_byte(b' ') { + while let Some(space_ind) = find_byte(b' ', &raw_data[prev_ind..]) { prev_ind += if space_ind == 0 { 1 } else { diff --git a/pgwm-core/src/status/time.rs b/pgwm-core/src/status/time.rs new file mode 100644 index 0000000..239ae21 --- /dev/null +++ b/pgwm-core/src/status/time.rs @@ -0,0 +1,240 @@ +use alloc::format; +use alloc::string::String; +use alloc::vec::Vec; + +#[cfg(feature = "config-file")] +use serde::de::Error as DeError; +use time::{Month, OffsetDateTime, UtcOffset, Weekday}; +use tiny_std::time::SystemTime; + +use crate::error::Error; + +#[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "config-file", derive(serde::Deserialize))] +pub struct ClockFormatter { + #[cfg_attr(feature = "config-file", serde(deserialize_with = "from_pattern"))] + format: Format, + #[cfg_attr(feature = "config-file", serde(deserialize_with = "from_hms_tuple"))] + utc_offset: UtcOffset, +} + +impl ClockFormatter { + pub fn format_now(&self) -> crate::error::Result { + let nanos = SystemTime::now().duration_since_unix_time().as_nanos(); + let dt = OffsetDateTime::from_unix_timestamp_nanos( + nanos + .try_into() + .map_err(|e| Error::Time(format!("Convert nanos to i128 failed: {e}")))?, + ) + .map_err(|e| Error::Time(format!("Instantiate Offset datetime: {e}")))? + .to_offset(self.utc_offset); + self.format.format(dt) + } + #[must_use] + pub fn new(format: Format, offset: UtcOffset) -> Self { + Self { + format, + utc_offset: offset, + } + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Format { + chunks: Vec, +} + +#[cfg(feature = "config-file")] +fn from_pattern<'de, D: serde::de::Deserializer<'de>>( + deserializer: D, +) -> core::result::Result { + let s: &'de str = serde::de::Deserialize::deserialize(deserializer)?; + Format::new(s) + .map_err(|e| D::Error::custom(format!("Failed to deserialize valid date pattern {e}"))) +} + +#[cfg(feature = "config-file")] +fn from_hms_tuple<'de, D: serde::de::Deserializer<'de>>( + deserializer: D, +) -> core::result::Result { + let (h, m, s): (i8, i8, i8) = serde::de::Deserialize::deserialize(deserializer)?; + UtcOffset::from_hms(h, m, s) + .map_err(|d| serde::de::Error::custom(format!("Failed to parse utc-offset {d:?}"))) +} + +impl Format { + pub fn format(&self, dt: OffsetDateTime) -> crate::error::Result { + let mut out = String::new(); + for chunk in &self.chunks { + match chunk { + FormatChunk::Value(v) => { + out.push_str(v); + } + FormatChunk::Token(t) => { + t.write_into(&dt, &mut out) + .map_err(|e| Error::Time(format!("Writing token into string {e}")))?; + } + } + } + Ok(out) + } + + pub fn new(input: &str) -> crate::error::Result { + let mut chunks = Vec::new(); + let mut cur_raw_chunk = String::new(); + let mut state = State::Ready; + for ch in input.chars() { + match state { + State::Ready => { + if ch == '{' { + state = State::SeenStartBracket; + } else { + cur_raw_chunk.push(ch); + } + } + State::SeenStartBracket => { + if ch == '%' { + state = State::SeenStartPerc; + } else { + state = State::Ready; + } + } + State::SeenStartPerc => { + let t = match ch { + 'Y' => Token::Year, + 'M' => Token::Month, + 'W' => Token::Week, + 'D' => Token::Day, + 'd' => Token::WeekDay, + 'h' => Token::Hour, + 'm' => Token::Minute, + 's' => Token::Second, + _ => return Err(Error::Time(format!("Bad token {ch}"))), + }; + if !cur_raw_chunk.is_empty() { + chunks.push(FormatChunk::Value(core::mem::take(&mut cur_raw_chunk))); + } + chunks.push(FormatChunk::Token(t)); + state = State::SeenValue; + } + State::SeenValue => { + if ch != '%' { + return Err(Error::Time(format!("Expected end perc, got {ch}"))); + } + state = State::SeenEndPerc; + } + State::SeenEndPerc => { + if ch != '}' { + return Err(Error::Time(format!("Expected end bracket, got {ch}"))); + } + state = State::Ready; + } + } + } + Ok(Self { chunks }) + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +enum FormatChunk { + Value(String), + Token(Token), +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +enum Token { + Year, + Month, + Week, + WeekDay, + Day, + Hour, + Minute, + Second, +} + +#[derive(Copy, Clone)] +enum State { + Ready, + SeenStartBracket, + SeenStartPerc, + SeenValue, + SeenEndPerc, +} + +impl Token { + fn write_into(self, dt: &OffsetDateTime, sink: &mut W) -> Result<(), core::fmt::Error> + where + W: core::fmt::Write, + { + match self { + Token::Year => { + sink.write_fmt(format_args!("{}", dt.year()))?; + } + Token::Month => match dt.month() { + Month::January => sink.write_fmt(format_args!("Jan"))?, + Month::February => sink.write_fmt(format_args!("Feb"))?, + Month::March => sink.write_fmt(format_args!("Mar"))?, + Month::April => sink.write_fmt(format_args!("Apr"))?, + Month::May => sink.write_fmt(format_args!("May"))?, + Month::June => sink.write_fmt(format_args!("Jun"))?, + Month::July => sink.write_fmt(format_args!("Jul"))?, + Month::August => sink.write_fmt(format_args!("Aul"))?, + Month::September => sink.write_fmt(format_args!("Sep"))?, + Month::October => sink.write_fmt(format_args!("Oct"))?, + Month::November => sink.write_fmt(format_args!("Nov"))?, + Month::December => sink.write_fmt(format_args!("Dec"))?, + }, + Token::Week => sink.write_fmt(format_args!("{}", dt.iso_week()))?, + Token::WeekDay => match dt.weekday() { + Weekday::Monday => sink.write_fmt(format_args!("Mon"))?, + Weekday::Tuesday => sink.write_fmt(format_args!("Tue"))?, + Weekday::Wednesday => sink.write_fmt(format_args!("Wed"))?, + Weekday::Thursday => sink.write_fmt(format_args!("Thu"))?, + Weekday::Friday => sink.write_fmt(format_args!("Fri"))?, + Weekday::Saturday => sink.write_fmt(format_args!("Sat"))?, + Weekday::Sunday => sink.write_fmt(format_args!("Sun"))?, + }, + Token::Day => sink.write_fmt(format_args!("{}", dt.day()))?, + Token::Hour => { + let t = dt.hour(); + if t < 10 { + sink.write_fmt(format_args!("0{t}"))?; + } else { + sink.write_fmt(format_args!("{t}"))?; + } + } + Token::Minute => { + let t = dt.minute(); + if t < 10 { + sink.write_fmt(format_args!("0{t}"))?; + } else { + sink.write_fmt(format_args!("{t}"))?; + } + } + Token::Second => { + let t = dt.second(); + if t < 10 { + sink.write_fmt(format_args!("0{t}"))?; + } else { + sink.write_fmt(format_args!("{t}"))?; + } + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn formats_time() { + let in_str = "Year {%Y%}, Month {%M%}, Week {%W%}, Day {%D%}, hour {%h%}, minute {%m%}, second {%s%}"; + let fmt = Format::new(in_str).unwrap(); + let dt = OffsetDateTime::from_unix_timestamp_nanos(1_666_551_103_791_951_912i128).unwrap(); + let expect = "Year 2022, Month Oct, Week 42, Day 23, hour 18, minute 51, second 43"; + assert_eq!(expect, fmt.format(dt).unwrap()); + } +} diff --git a/pgwm-core/src/util/load_cfg.rs b/pgwm-core/src/util/load_cfg.rs index d6cc485..b301ca9 100644 --- a/pgwm-core/src/util/load_cfg.rs +++ b/pgwm-core/src/util/load_cfg.rs @@ -1,40 +1,31 @@ -use crate::config::{Button, ButtonMask, Cfg, ModMaskEnum, ModMasks, WINDOW_MANAGER_NAME}; -use crate::error::{Error, Result}; +use alloc::format; +use alloc::string::String; +use core::fmt::{Formatter, Write}; + use serde::de::{EnumAccess, SeqAccess, Visitor}; use serde::{Deserialize, Deserializer}; -use std::fmt::Formatter; -use std::path::PathBuf; -use x11rb::protocol::xproto::{ButtonIndex, ModMask}; - -pub(crate) fn load_cfg() -> Result { - if let Some(user_cfg_dir) = find_cfg_dir() { - let wm_cfg_dir = user_cfg_dir.join(WINDOW_MANAGER_NAME); - let file_path = wm_cfg_dir.join(format!("{WINDOW_MANAGER_NAME}.toml")); - crate::debug!("Attempting config read at {file_path:?}"); - match std::fs::read(&file_path) { - Ok(content) => Ok(toml::from_slice(content.as_slice())?), - Err(e) => { - if e.kind() == std::io::ErrorKind::NotFound { - Err(Error::ConfigFileFind) - } else { - Err(e.into()) - } - } - } +use xcb_rust_protocol::proto::xproto::{ButtonIndexEnum, ModMask}; + +use crate::config::{Button, ButtonMask, Cfg, ModMaskEnum, ModMasks, WINDOW_MANAGER_NAME}; +use crate::error::{Error, Result}; + +pub fn load_cfg(config_home: Option<&str>, home: Option<&str>) -> Result { + if let Some(mut user_cfg_dir) = find_cfg_dir(config_home, home) { + let _ = user_cfg_dir.write_fmt(format_args!( + "/{WINDOW_MANAGER_NAME}/{WINDOW_MANAGER_NAME}.toml" + )); + pgwm_utils::debug!("Attempting config read at {user_cfg_dir}"); + let buf = tiny_std::fs::read(&user_cfg_dir)?; + Ok(toml::from_slice(buf.as_slice())?) } else { Err(Error::ConfigDirFind) } } -fn find_cfg_dir() -> Option { - std::env::var("XDG_CONFIG_HOME") - .ok() - .map(PathBuf::from) - .or_else(|| { - std::env::var("HOME") - .map(|home| PathBuf::from(home).join(".config")) - .ok() - }) +fn find_cfg_dir(xdg_config_home: Option<&str>, home: Option<&str>) -> Option { + xdg_config_home + .map(alloc::string::ToString::to_string) + .or_else(|| home.map(|dir| format!("{dir}/.config"))) } impl ModMaskEnum { @@ -43,67 +34,69 @@ impl ModMaskEnum { ModMaskEnum::Shift => ModMask::SHIFT, ModMaskEnum::Lock => ModMask::LOCK, ModMaskEnum::Control => ModMask::CONTROL, - ModMaskEnum::M1 => ModMask::M1, - ModMaskEnum::M2 => ModMask::M2, - ModMaskEnum::M3 => ModMask::M3, - ModMaskEnum::M4 => ModMask::M4, - ModMaskEnum::M5 => ModMask::M5, + ModMaskEnum::M1 => ModMask::ONE, + ModMaskEnum::M2 => ModMask::TWO, + ModMaskEnum::M3 => ModMask::THREE, + ModMaskEnum::M4 => ModMask::FOUR, + ModMaskEnum::M5 => ModMask::FIVE, ModMaskEnum::Any => ModMask::ANY, } } } impl ButtonMask { - pub(crate) fn to_button_index(&self) -> ButtonIndex { + pub(crate) fn to_button_index(&self) -> ButtonIndexEnum { match self { - ButtonMask::Any => ButtonIndex::ANY, - ButtonMask::M1 => ButtonIndex::M1, - ButtonMask::M2 => ButtonIndex::M2, - ButtonMask::M3 => ButtonIndex::M3, - ButtonMask::M4 => ButtonIndex::M4, - ButtonMask::M5 => ButtonIndex::M5, + ButtonMask::Any => ButtonIndexEnum::ANY, + ButtonMask::M1 => ButtonIndexEnum::ONE, + ButtonMask::M2 => ButtonIndexEnum::TWO, + ButtonMask::M3 => ButtonIndexEnum::THREE, + ButtonMask::M4 => ButtonIndexEnum::FOUR, + ButtonMask::M5 => ButtonIndexEnum::FIVE, } } } + pub(crate) struct ModMaskVisitor; impl<'de> Visitor<'de> for ModMaskVisitor { type Value = ModMasks; - fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + fn expecting(&self, formatter: &mut Formatter) -> core::fmt::Result { formatter.write_str("Expected an array of ModMaskEnums") } - fn visit_seq(self, mut seq: A) -> std::result::Result + fn visit_seq(self, mut seq: A) -> core::result::Result where A: SeqAccess<'de>, { let mut base = ModMask::from(0u16); while let Some(e) = seq.next_element::()? { - base = base | e.to_mod_mask(); + base |= e.to_mod_mask(); } Ok(ModMasks { inner: base }) } } impl<'de> Deserialize<'de> for ModMasks { - fn deserialize(deserializer: D) -> std::result::Result + fn deserialize(deserializer: D) -> core::result::Result where D: Deserializer<'de>, { deserializer.deserialize_seq(ModMaskVisitor) } } + struct ButtonVisitor; impl<'de> Visitor<'de> for ButtonVisitor { type Value = Button; - fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + fn expecting(&self, formatter: &mut Formatter) -> core::fmt::Result { formatter.write_str("Expected one of the ButtonMask enum") } - fn visit_enum(self, data: A) -> std::result::Result + fn visit_enum(self, data: A) -> core::result::Result where A: EnumAccess<'de>, { @@ -115,7 +108,7 @@ impl<'de> Visitor<'de> for ButtonVisitor { } impl<'de> Deserialize<'de> for Button { - fn deserialize(deserializer: D) -> std::result::Result + fn deserialize(deserializer: D) -> core::result::Result where D: Deserializer<'de>, { @@ -129,24 +122,27 @@ impl<'de> Deserialize<'de> for Button { #[cfg(test)] mod tests { - use crate::config::{Cfg, WINDOW_MANAGER_NAME}; - use crate::util::load_cfg::find_cfg_dir; + use alloc::string::ToString; + use std::env; use std::path::PathBuf; + use crate::config::{Cfg, WINDOW_MANAGER_NAME}; + use crate::util::load_cfg::find_cfg_dir; + #[test] fn will_read_environment_variables_to_find_config_falling_back() { - env::remove_var("XDG_CONFIG_HOME"); - env::remove_var("HOME"); - assert!(find_cfg_dir().is_none()); - env::set_var("HOME", "here"); - assert_eq!(Some(PathBuf::from("here/.config")), find_cfg_dir()); - env::set_var("XDG_CONFIG_HOME", "there"); - assert_eq!(Some(PathBuf::from("there")), find_cfg_dir()); - env::remove_var("HOME"); - assert_eq!(Some(PathBuf::from("there")), find_cfg_dir()); - env::remove_var("XDG_CONFIG_HOME"); - assert!(find_cfg_dir().is_none()); + assert!(find_cfg_dir(None, None).is_none()); + assert_eq!( + Some("here/.config".to_string()), + find_cfg_dir(None, Some("here")) + ); + assert_eq!( + Some("there".to_string()), + find_cfg_dir(Some("there"), Some("here")) + ); + assert_eq!(Some("there".to_string()), find_cfg_dir(Some("there"), None)); + assert!(find_cfg_dir(None, None).is_none()); } #[test] @@ -166,8 +162,8 @@ mod tests { fn read_cfg_from_root() -> Cfg { let project_root = find_project_root(); - let cfg_path = project_root.join(format!("{WINDOW_MANAGER_NAME}.toml")); - let cfg = std::fs::read(&cfg_path).unwrap(); + let cfg_path = project_root.join(alloc::format!("{WINDOW_MANAGER_NAME}.toml")); + let cfg = std::fs::read(cfg_path).unwrap(); toml::from_slice(cfg.as_slice()).unwrap() } @@ -180,7 +176,7 @@ mod tests { let dir_entry = dir_entry.unwrap(); let meta = dir_entry.metadata().unwrap(); if meta.is_dir() && dir_entry.file_name() == WINDOW_MANAGER_NAME { - let children = std::fs::read_dir(&dir_entry.path()).unwrap(); + let children = std::fs::read_dir(dir_entry.path()).unwrap(); for child in children { let child = child.unwrap(); if child.file_name() == "Cargo.lock" { diff --git a/pgwm-core/src/util/macros.rs b/pgwm-core/src/util/macros.rs index 3b87bd8..a904cdf 100644 --- a/pgwm-core/src/util/macros.rs +++ b/pgwm-core/src/util/macros.rs @@ -47,18 +47,6 @@ macro_rules! hvec { } } } -#[macro_export] -#[cfg(feature = "debug")] -macro_rules! debug { - ($($arg:tt)*) => {{ - eprintln!("[{}:L#{}] {}", file!(), line!(), format_args!($($arg)*)); - }} -} -#[macro_export] -#[cfg(not(feature = "debug"))] -macro_rules! debug { - ($($arg:tt)*) => {{}}; -} #[macro_export] macro_rules! format_heapless { diff --git a/pgwm-core/src/util/mod.rs b/pgwm-core/src/util/mod.rs index 57c0061..a7635ac 100644 --- a/pgwm-core/src/util/mod.rs +++ b/pgwm-core/src/util/mod.rs @@ -1,4 +1,4 @@ #[cfg(feature = "config-file")] -pub(crate) mod load_cfg; +pub mod load_cfg; pub mod macros; pub mod vec_ops; diff --git a/pgwm-utils/Cargo.toml b/pgwm-utils/Cargo.toml new file mode 100644 index 0000000..199d34d --- /dev/null +++ b/pgwm-utils/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "pgwm-utils" +version = "0.1.0" +edition = "2021" +license = "GPL-3.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = [] +debug = [] + +[dependencies] +unix-print = "0.1.0" \ No newline at end of file diff --git a/pgwm-utils/src/lib.rs b/pgwm-utils/src/lib.rs new file mode 100644 index 0000000..9e3c35d --- /dev/null +++ b/pgwm-utils/src/lib.rs @@ -0,0 +1,5 @@ +#![cfg_attr(not(test), no_std)] + +pub use unix_print::unix_eprintln; + +pub mod macros; diff --git a/pgwm-utils/src/macros.rs b/pgwm-utils/src/macros.rs new file mode 100644 index 0000000..6a2bc15 --- /dev/null +++ b/pgwm-utils/src/macros.rs @@ -0,0 +1,24 @@ +#[macro_export] +#[cfg(feature = "debug")] +macro_rules! debug { + ($($arg:tt)*) => {{ + $crate::unix_eprintln!("[{}:L#{}] {}", file!(), line!(), format_args!($($arg)*)); + }} +} +#[macro_export] +#[cfg(not(feature = "debug"))] +macro_rules! debug { + ($($arg:tt)*) => {{}}; +} + +#[macro_export] +macro_rules! from_error { + ($from: ty, $to: ty, $tag: ident) => { + impl From<$from> for $to { + #[inline] + fn from(e: $from) -> $to { + Self::$tag(e) + } + } + }; +} diff --git a/pgwm.toml b/pgwm.toml index 7d1fff8..3e80e5e 100644 --- a/pgwm.toml +++ b/pgwm.toml @@ -1,4 +1,23 @@ # Example cfg, most cfg can be omitted with the exception of `[fonts] fallback` +# Mapping for specific characters to specific fonts. +# If a certain render contains a desired icon the mapping for that char-mapping is put below +char-remap."\uf121" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } +char-remap."\uf120" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } +char-remap."\uf086" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } +char-remap."\ue007" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Brands-Regular-400.otf", size = "13.0" } +char-remap."\uf1bc" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Brands-Regular-400.otf", size = "13.0" } +char-remap."\uf11b" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } +char-remap."\uf7d9" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } +char-remap."\uf02b" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } +char-remap."\uf02c" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } +char-remap."\uf2db" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } +char-remap."\uf538" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } +char-remap."\uf019" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } +char-remap."\uf093" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } +char-remap."\uf073" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } +char-remap."\uf502" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } +char-remap."\uf304" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } + [sizing] # Height in pixels of the status_bar # Cannot be 0 or larger than any monitor's height @@ -121,25 +140,7 @@ shortcut_section = [ { path = "/usr/share/fonts/TTF/JetBrains Mono Regular Nerd Font Complete Mono.ttf", size = "14.0" }, ] -# Mapping for specific characters to specific fonts. -# If a certain render contains a desired icon the mapping for that char-mapping is put below -[char-remap] -"\uf121" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } -"\uf120" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } -"\uf086" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } -"\ue007" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Brands-Regular-400.otf", size = "13.0" } -"\uf1bc" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Brands-Regular-400.otf", size = "13.0" } -"\uf11b" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } -"\uf7d9" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } -"\uf02b" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } -"\uf02c" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } -"\uf2db" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } -"\uf538" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } -"\uf019" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } -"\uf093" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } -"\uf073" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } -"\uf502" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } -"\uf304" = { path = "/usr/share/fonts/OTF/Font Awesome 6 Free-Solid-900.otf", size = "13.0" } + # Available workspaces and their names and respective class_name mappings # The mapped_class_names is an array of wm class names @@ -203,7 +204,7 @@ check_type = { kind = "Net", args = { icon_up = "\uf093", icon_down = "\uf019", interval = 1000 # The pattern here can be at most `STATUS_BAR_DATE_PATTERN_LIMIT` bytes of utf8 characters, for reference this one is 85 bytes # Your timezone's utc-offset is specified here, mostly because of performance, security, and future-proofing reasons. -check_type = { kind = "Date", args = { icon = "\uf073", pattern = "[weekday repr:short] [month repr:short] [day] w[week_number] [hour]:[minute]:[second]", utc_offset = [2, 0, 0]}} +check_type = { kind = "Date", args = { icon = "\uf073", clock_formatter = { format = "{%d%} {%M%} {%D%} v{%W%} {%h%}:{%m%}:{%s%}", utc_offset = [1, 0, 0] }}} # Mouse mappings # M4 is usually super/win @@ -300,26 +301,26 @@ button = "M1" # Press first status component target = { kind = "StatusComponent", args = 0 } # Pop htop using alacritty as term, close on exit -on_click = { action = "Spawn", args = ["alacritty", ["-e", "htop"]]} +on_click = { action = "Spawn", args = ["/usr/bin/xterm", ["-e", "htop"]]} [[mouse-mapping]] mods = [] button = "M1" # Press fourth target = { kind = "StatusComponent", args = 3 } # Open a new tab in firefox with a calendar -on_click = { action = "Spawn", args = ["firefox", ["-new-tab", "https://calendar.google.com"]]} +on_click = { action = "Spawn", args = ["/usr/bin/firefox", ["-new-tab", "https://calendar.google.com"]]} [[mouse-mapping]] mods = [] button = "M1" target = { kind = "ShortcutComponent", args = 0 } # Pop a term with some configuration open, running with bash to be able to use `~` for home -on_click = { action = "Spawn", args = ["alacritty", ["-e", "bash", "-c", "nvim ~/.bashrc ~/.xinitrc ~/.config/pgwm/pgwm.toml"]]} +on_click = { action = "Spawn", args = ["/usr/bin/xterm", ["-e", "/usr/bin/bash", "-c", "nvim ~/.bashrc ~/.xinitrc ~/.config/pgwm/pgwm.toml"]]} [[mouse-mapping]] mods = [] button = "M1" target = { kind = "ShortcutComponent", args = 1 } # Lock screen -on_click = { action = "Spawn", args = ["xscreensaver-command", ["-lock"]]} +on_click = { action = "Spawn", args = ["/usr/bin/xscreensaver-command", ["-lock"]]} # Key mappings # Available mods are same as for mouse mappings: @@ -508,12 +509,12 @@ on_click = { action = "SendToWorkspace", args = 8 } [[key-mapping]] mods = ["M4", "Shift"] key = 0xff0d # -on_click = { action = "Spawn", args = ["alacritty", []] } +on_click = { action = "Spawn", args = ["/usr/bin/xterm", []] } [[key-mapping]] mods = ["M4"] key = 0x0064 # d -on_click = { action = "Spawn", args = ["dmenu_run", ["-i", "-p", "Run: "]] } +on_click = { action = "Spawn", args = ["/usr/bin/dmenu_run", ["-i", "-p", "Run: "]] } [[key-mapping]] mods = [] key = 0xff61 # Print -on_click = { action = "Spawn", args = ["bash", ["-c", "maim -s -u | xclip -selection clipboard -t image/png -i"]] } +on_click = { action = "Spawn", args = ["/usr/bin/bash", ["-c", "/usr/bin/maim -s -u | xclip -selection clipboard -t image/png -i"]] } diff --git a/pgwm/Cargo.toml b/pgwm/Cargo.toml index 0c22139..49e346d 100644 --- a/pgwm/Cargo.toml +++ b/pgwm/Cargo.toml @@ -3,23 +3,19 @@ name = "pgwm" version = "0.1.0" edition = "2021" license = "GPL-3.0" -publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [features] -config-file = ["pgwm-core/config-file"] +config-file = ["pgwm-app/config-file"] default = ["xinerama", "status-bar", "config-file"] -debug = ["pgwm-core/debug", "x11rb/debug"] -xinerama = ["x11rb/xinerama"] -status-bar = ["pgwm-core/status-bar", "time"] -perf-test = [] +debug = ["pgwm-app/debug"] +xinerama = ["pgwm-app/xinerama"] +status-bar = ["pgwm-app/status-bar"] +perf-test = ["pgwm-app/perf-test"] [dependencies] -heapless = { version = "0.7.15", default-features = false } -fontdue = { version = "0.7.2", default-features = false, features = ["simd"] } -pgwm-core = { path = "../pgwm-core" } -smallmap = { version = "1.4.0", default-features = false } -thiserror = "1.0.31" -time = { version = "0.3.11", optional = true, default-features = false, features = ["formatting", "local-offset"] } -x11rb = { version = "0.10.0", features = ["render", "cursor"]} +compiler_builtins = { version = "0.1.83", default-features = false, features = ["mem"] } +dlmalloc = { git = "https://github.com/MarcusGrass/dlmalloc-rs.git", rev = "a8e9fc0d2c03a06810530a48abd37fecc71e8109", default-features = false } +pgwm-app = { path = "../pgwm-app" } +tiny-std = { version = "0.1", features = ["start", "alloc", "symbols", "aux", "vdso"] } +unix-print = "0.1.0" diff --git a/pgwm/src/error.rs b/pgwm/src/error.rs deleted file mode 100644 index 893b903..0000000 --- a/pgwm/src/error.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::io; -use std::{ffi::NulError, string::FromUtf8Error}; - -use x11rb::errors::{ConnectError, ConnectionError, ReplyError, ReplyOrIdError}; - -pub(crate) type Result = std::result::Result; -#[derive(thiserror::Error, Debug)] -pub(crate) enum Error { - #[error(transparent)] - Core(#[from] pgwm_core::error::Error), - #[error(transparent)] - X11Connect(#[from] ConnectError), - #[error(transparent)] - X11Connection(#[from] ConnectionError), - #[error(transparent)] - X11Reply(#[from] ReplyError), - #[error(transparent)] - X11IdCreation(#[from] ReplyOrIdError), - #[error("Failed to parse event")] - X11EventParse, - #[error("Number of glyph ids not corresponding to number of metrics")] - GlyphMismatch, - #[error("Could not become wm, access denied, there is likely another WM running")] - BecomeWm, - #[error("Failed to calculate correct tiling dimensions (this is a programming error)")] - Tiling, - #[error("Failed to find an appropriate 32 bit depth visual")] - NoAppropriateVisual, - #[error(transparent)] - ContentToCstr(#[from] NulError), - #[error(transparent)] - Io(#[from] io::Error), - #[error(transparent)] - ConvertToUtf8(#[from] FromUtf8Error), - #[error(transparent)] - ConvertCoreToUtf8(#[from] core::str::Utf8Error), - #[error("StateInvalidated")] - StateInvalidated, - #[error("Exit triggered")] - GracefulShutdown, - #[error("Restart triggered")] - FullRestart, - #[error("Size not parseable as f32")] - ParseFloat, - #[error("Failed to load font {0}")] - FontLoad(&'static str), - #[error("Invalid char to font mapping {0}")] - BadCharFontMapping(&'static str), -} diff --git a/pgwm/src/main.rs b/pgwm/src/main.rs index 1b99213..635ef64 100644 --- a/pgwm/src/main.rs +++ b/pgwm/src/main.rs @@ -1,87 +1,81 @@ -#![deny(unsafe_code)] -#![warn(clippy::all)] -#![warn(clippy::pedantic)] -#![allow(clippy::too_many_arguments)] -#![allow(clippy::needless_pass_by_value)] -#![allow(clippy::let_underscore_drop)] -#![allow(clippy::too_many_lines)] -// X11 uses inconsistent integer types fairly interchangeably -#![allow(clippy::cast_lossless)] -#![allow(clippy::cast_possible_truncation)] -#![allow(clippy::cast_sign_loss)] -#![allow(clippy::cast_possible_wrap)] -#![allow(clippy::cast_precision_loss)] -#![allow(clippy::module_name_repetitions)] - -pub(crate) mod error; -mod manager; -pub(crate) mod util; -mod wm; -mod x11; - -use crate::error::{Error, Result}; -use crate::wm::run_wm; -use pgwm_core::debug; -use std::path::PathBuf; - -fn main() -> Result<()> { - debug!("Starting pgwm"); - if check_cfg() { - return Ok(()); - } - loop { - return match run_wm() { - Ok(_) => { - debug!("Exiting WM"); - Ok(()) - } - Err(e) => { - if let Error::FullRestart = e { - debug!("Restarting WM"); - continue; - } - debug!("Fatal error {e}"); - Err(e) - } - }; - } +#![no_std] +#![no_main] +// Start function, looks promising for stabilization https://github.com/rust-lang/rust/pull/93587 +#![feature(naked_functions)] +// Seemingly moving towards stability https://github.com/rust-lang/rust/pull/102318 +// Would be a lot nicer to just accept a symbol instead of forcing #[alloc_error_handler] +// Could fork the compiler and remove this check https://github.com/rust-lang/rust/blob/56074b5231ceef266a1097ea355f62c951e1b468/compiler/rustc_metadata/src/creader.rs#L1063 but ugh +#![feature(default_alloc_error_handler)] + +extern crate alloc; + +use core::alloc::{GlobalAlloc, Layout}; +use core::cell::UnsafeCell; + +use dlmalloc::Dlmalloc; +use tiny_std::process::exit; +use unix_print::unix_eprintln; + +use pgwm_app::main_loop; + +#[global_allocator] +static ALLOCATOR: SingleThreadedAlloc = SingleThreadedAlloc::new(); + +struct SingleThreadedAlloc { + inner: UnsafeCell, } -fn check_cfg() -> bool { - let mut args = std::env::args(); - args.next(); // drain program argument - if let Some(k) = args.next() { - if k.as_str() == "--check-cfg" { - match pgwm_core::config::Cfg::new() { - Ok(cfg) => { - let collected_fonts = cfg - .fonts - .get_all_font_paths() - .into_iter() - .chain(cfg.char_remap.values().map(|v| PathBuf::from(&v.path))); - for font in collected_fonts { - match std::fs::metadata(&font) { - Ok(meta) => { - if !meta.is_file() { - eprintln!("Invalid configuration, specified font {font:?} points to something that isn't a file."); - } - } - Err(e) => { - eprintln!("Invalid configuration, could not read metadata for font {font:?}: {e}"); - } - } - } - println!("Configuration valid!"); - } - Err(e) => { - println!("Invalid configuration: {e}"); - } - } - } else { - println!("The only valid argument is `--check-cfg` to check if configuration is valid and can be found"); +impl SingleThreadedAlloc { + pub(crate) const fn new() -> Self { + SingleThreadedAlloc { + inner: UnsafeCell::new(Dlmalloc::new()), } - true - } else { - false } } + +unsafe impl GlobalAlloc for SingleThreadedAlloc { + #[inline] + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + (*self.inner.get()).malloc(layout.size(), layout.align()) + } + + #[inline] + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + (*self.inner.get()).free(ptr, layout.size(), layout.align()) + } + + #[inline] + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + (*self.inner.get()).calloc(layout.size(), layout.align()) + } + + #[inline] + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + (*self.inner.get()).realloc(ptr, layout.size(), layout.align(), new_size) + } +} + +/// Extremely unsafe, this program is not thread safe at all will immediately segfault on more threads +unsafe impl Sync for SingleThreadedAlloc {} + +unsafe impl Send for SingleThreadedAlloc {} + +#[panic_handler] +fn on_panic(info: &core::panic::PanicInfo) -> ! { + unix_eprintln!("{info}"); + exit(1) +} + +/// Compiler complains about this symbol being missing for some reason +/// we don't unwind anyway so it shouldn't be needed. +/// # Safety +/// Just another necessary symbol +#[no_mangle] +pub unsafe extern "C" fn _Unwind_Resume() -> ! { + exit(2); +} + +#[no_mangle] +fn main() -> i32 { + main_loop() +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..c89bbb2 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly" +components = ["clippy"] \ No newline at end of file