From 9deaf7cb0b594df49831eb36df0d38c02fca5e7b Mon Sep 17 00:00:00 2001 From: Donovan Glover Date: Fri, 9 Aug 2024 13:42:16 -0400 Subject: [PATCH] meta: Vendor hyprland-rs for now Should prevent having to include the Cargo.lock in the nixpkgs update. Note that some doc comments were removed and #[allow(dead_code)] was added as needed to ./src/event_listener/shared.rs --- .gitattributes | 1 + .gitignore | 2 +- Cargo.lock | 2 - Cargo.toml | 2 +- hyprland/Cargo.lock | 1215 ++++++++++++++++++++++ hyprland/Cargo.toml | 94 ++ hyprland/hyprland-macros/Cargo.lock | 46 + hyprland/hyprland-macros/Cargo.toml | 19 + hyprland/hyprland-macros/src/lib.rs | 17 + hyprland/src/config.rs | 252 +++++ hyprland/src/ctl.rs | 405 ++++++++ hyprland/src/data/helpers.rs | 24 + hyprland/src/data/macros.rs | 201 ++++ hyprland/src/data/mod.rs | 64 ++ hyprland/src/data/regular.rs | 701 +++++++++++++ hyprland/src/dispatch.rs | 613 +++++++++++ hyprland/src/event_listener/async_im.rs | 120 +++ hyprland/src/event_listener/immutable.rs | 147 +++ hyprland/src/event_listener/macros.rs | 135 +++ hyprland/src/event_listener/mod.rs | 36 + hyprland/src/event_listener/shared.rs | 868 ++++++++++++++++ hyprland/src/keyword.rs | 179 ++++ hyprland/src/lib.rs | 73 ++ hyprland/src/shared.rs | 443 ++++++++ hyprland/src/unsafe_impl.rs | 34 + 25 files changed, 5689 insertions(+), 4 deletions(-) create mode 100644 .gitattributes create mode 100644 hyprland/Cargo.lock create mode 100644 hyprland/Cargo.toml create mode 100644 hyprland/hyprland-macros/Cargo.lock create mode 100644 hyprland/hyprland-macros/Cargo.toml create mode 100644 hyprland/hyprland-macros/src/lib.rs create mode 100644 hyprland/src/config.rs create mode 100644 hyprland/src/ctl.rs create mode 100644 hyprland/src/data/helpers.rs create mode 100644 hyprland/src/data/macros.rs create mode 100644 hyprland/src/data/mod.rs create mode 100644 hyprland/src/data/regular.rs create mode 100644 hyprland/src/dispatch.rs create mode 100644 hyprland/src/event_listener/async_im.rs create mode 100644 hyprland/src/event_listener/immutable.rs create mode 100644 hyprland/src/event_listener/macros.rs create mode 100644 hyprland/src/event_listener/mod.rs create mode 100644 hyprland/src/event_listener/shared.rs create mode 100644 hyprland/src/keyword.rs create mode 100644 hyprland/src/lib.rs create mode 100644 hyprland/src/shared.rs create mode 100644 hyprland/src/unsafe_impl.rs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..0519f6a --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +hyprland/** linguist-vendored diff --git a/.gitignore b/.gitignore index ea8c4bf..eb5a316 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -/target +target diff --git a/Cargo.lock b/Cargo.lock index 4cd3d42..bd4264e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -312,7 +312,6 @@ dependencies = [ [[package]] name = "hyprland" version = "0.4.0-alpha.2" -source = "git+https://github.com/MrYuto/hyprland-rs#7a309a24a94f88973f8d3fb1e7d687e3d730d351" dependencies = [ "ahash", "derive_more", @@ -330,7 +329,6 @@ dependencies = [ [[package]] name = "hyprland-macros" version = "0.4.0-alpha.2" -source = "git+https://github.com/MrYuto/hyprland-rs#7a309a24a94f88973f8d3fb1e7d687e3d730d351" dependencies = [ "quote", "syn 2.0.60", diff --git a/Cargo.toml b/Cargo.toml index b69482b..ef12056 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ version = "3.4.4" features = ["termination"] [dependencies.hyprland] -git = "https://github.com/MrYuto/hyprland-rs" +path = "hyprland" features = ["silent"] [build-dependencies] diff --git a/hyprland/Cargo.lock b/hyprland/Cargo.lock new file mode 100644 index 0000000..eeb87a8 --- /dev/null +++ b/hyprland/Cargo.lock @@ -0,0 +1,1215 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "serde", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d4d23bcc79e27423727b36823d86233aad06dfea531837b038394d11e9928" +dependencies = [ + "concurrent-queue", + "event-listener 5.3.0", + "event-listener-strategy 0.5.2", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b10202063978b3351199d68f8b22c4e47e4b1b822f8d43fd862d5ea8c006b29a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.1.0", + "futures-lite 2.3.0", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.2.1", + "async-executor", + "async-io 2.3.2", + "async-lock 3.3.0", + "blocking", + "futures-lite 2.3.0", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.27", + "slab", + "socket2 0.4.10", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" +dependencies = [ + "async-lock 3.3.0", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.3.0", + "parking", + "polling 3.7.0", + "rustix 0.38.34", + "slab", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +dependencies = [ + "event-listener 4.0.3", + "event-listener-strategy 0.4.0", + "pin-project-lite", +] + +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io 2.3.2", + "blocking", + "futures-lite 2.3.0", +] + +[[package]] +name = "async-std" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io 1.13.0", + "async-lock 2.8.0", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite 1.13.0", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "blocking" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "495f7104e962b7356f0aeb34247aca1fe7d2e783b346582db7f2904cb5717e88" +dependencies = [ + "async-channel 2.2.1", + "async-lock 3.3.0", + "async-task", + "futures-io", + "futures-lite 2.3.0", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + +[[package]] +name = "cc" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "derive_more" +version = "0.99.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener 4.0.3", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener 5.3.0", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand 2.1.0", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hyprland" +version = "0.4.0-alpha.2" +dependencies = [ + "ahash", + "async-net", + "async-std", + "derive_more", + "futures-lite 2.3.0", + "hyprland-macros", + "num-traits", + "once_cell", + "parking_lot", + "paste", + "regex", + "serde", + "serde_json", + "serde_repr", + "tokio", +] + +[[package]] +name = "hyprland-macros" +version = "0.4.0-alpha.2" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "libc" +version = "0.2.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +dependencies = [ + "value-bag", +] + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +dependencies = [ + "parking_lot_core", +] + +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.5", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand 2.1.0", + "futures-io", +] + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 0.38.34", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + +[[package]] +name = "regex" +version = "1.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.37.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys 0.4.13", + "windows-sys 0.52.0", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "syn" +version = "2.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tokio" +version = "1.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "socket2 0.5.7", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "value-bag" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "waker-fn" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "zerocopy" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "087eca3c1eaf8c47b94d02790dd086cd594b912d2043d4de4bfdd466b3befb7c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f4b6c273f496d8fd4eaf18853e6b448760225dc030ff2c485a786859aea6393" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/hyprland/Cargo.toml b/hyprland/Cargo.toml new file mode 100644 index 0000000..a35d09d --- /dev/null +++ b/hyprland/Cargo.toml @@ -0,0 +1,94 @@ +[package] +name = "hyprland" +edition = "2021" +readme = "README.md" +description = "A unoffical rust wrapper for hyprland's IPC" +homepage = "https://github.com/hyprland-community/hyprland-rs" +version.workspace = true +license.workspace = true +author.workspace = true +repository.workspace = true +keywords.workspace = true +categories.workspace = true +rust-version = "1.75.0" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[package.metadata.nix] +build = true + +[profile.release] +opt-level = 3 +strip = true +lto = true + +[workspace] +members = [ + "hyprland-macros", +] + +[workspace.package] +version = "0.4.0-alpha.2" +license = "GPL-3.0-or-later" +repository = "https://github.com/hyprland-community/hyprland-rs" +keywords = ["hyprland", "ipc", "hypr", "wayland", "linux"] +categories = ["api-bindings"] +authors = ["yavko "] + +[dependencies] +hyprland-macros = { path = "hyprland-macros", version = "0.4.0-alpha.1" } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde_repr = "0.1" +tokio = { version = "1", features = [ + "io-std", + "io-util", + "macros", + "net", + "rt" +], optional = true } +async-net = { version = "2.0", optional = true } +async-std = { version = "1.12", optional = true } +futures-lite = { version = "2.3", optional = true, default-features = false } +regex = { version = "1.10", default-features = false, features = [ + "std", + "perf", + "unicode", +] } +num-traits = "0.2.19" +paste = "1.0.14" +derive_more = { version = "0.99", default-features = false, features = [ + "display", + "constructor", +] } +once_cell = "1.19" +parking_lot = { version = "0.12", optional = true } +ahash = { version = "0.8", features = [ + "std", + "no-rng", + "serde", +], optional = true, default-features = false } + +[features] +default = [ + "listener", + "dispatch", + "data", + "keyword", + "config", + "ctl", + "tokio", + "ahash", +] +async-lite = ["dep:async-net", "dep:futures-lite"] +async-std = ["dep:async-std", "dep:futures-lite"] +tokio = ["dep:tokio"] +dispatch = [] +data = [] +ctl = [] +keyword = [] +config = ["dispatch", "keyword"] +listener = ["data", "dispatch"] +silent = [] +parking_lot = ["dep:parking_lot", "once_cell/parking_lot", "tokio?/parking_lot"] +ahash = ["dep:ahash"] +unsafe-impl = [] diff --git a/hyprland/hyprland-macros/Cargo.lock b/hyprland/hyprland-macros/Cargo.lock new file mode 100644 index 0000000..cf3a3a0 --- /dev/null +++ b/hyprland/hyprland-macros/Cargo.lock @@ -0,0 +1,46 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "hyprland-macros" +version = "0.1.0" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" diff --git a/hyprland/hyprland-macros/Cargo.toml b/hyprland/hyprland-macros/Cargo.toml new file mode 100644 index 0000000..11d7067 --- /dev/null +++ b/hyprland/hyprland-macros/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "hyprland-macros" +description = "Macros used in hyprland-rs" +version.workspace = true +license.workspace = true +readme = "README.md" +edition = "2021" +homepage = "https://github.com/hyprland-community/hyprland-rs/tree/master/hyprland-macros" +repository = "https://github.com/hyprland-community/hyprland-rs" + +[package.metadata.nix] +build = true + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2", features = ["full", "parsing"]} +quote = "1" diff --git a/hyprland/hyprland-macros/src/lib.rs b/hyprland/hyprland-macros/src/lib.rs new file mode 100644 index 0000000..a8510f5 --- /dev/null +++ b/hyprland/hyprland-macros/src/lib.rs @@ -0,0 +1,17 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, ExprClosure}; + +/// Creates a async closure +#[proc_macro] +pub fn async_closure(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as ExprClosure); + let body = input.body; + let inputs = input.inputs; + let mova = input.capture; + let expanded = quote! {{ + use std::future::IntoFuture; + #mova |#inputs| Box::pin(async move { #body }) + }}; + expanded.into() +} diff --git a/hyprland/src/config.rs b/hyprland/src/config.rs new file mode 100644 index 0000000..7f7b818 --- /dev/null +++ b/hyprland/src/config.rs @@ -0,0 +1,252 @@ +//! # Hyprland Configuration in Rust +//! +use crate::dispatch::{gen_dispatch_str, DispatchType}; +use crate::keyword::Keyword; + +/// Module providing stuff for adding an removing keybinds +pub mod binds { + use super::*; + + trait Join: IntoIterator { + fn join(&self) -> String; + } + + /// Type for a key held by a bind + #[derive(Debug, Clone, PartialEq, Eq)] + pub enum Key<'a> { + /// Variant for if the bind holds a modded key + Mod( + /// Mods + Vec, + /// Key + &'a str, + ), + /// Variant for a regular key + Key(&'a str), + } + + impl std::fmt::Display for Key<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Key::Mod(m, s) => format!("{}_{s}", m.join()), + Key::Key(s) => s.to_string(), + } + ) + } + } + + #[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Display)] + #[allow(missing_docs)] + /// Enum for mod keys used in bind combinations + pub enum Mod { + #[display(fmt = "SUPER")] + SUPER, + #[display(fmt = "SHIFT")] + SHIFT, + #[display(fmt = "ALT")] + ALT, + #[display(fmt = "CTRL")] + CTRL, + #[display(fmt = "")] + NONE, + } + + impl Join for Vec { + fn join(&self) -> String { + let mut buf = String::new(); + for i in self { + buf.push_str(&i.to_string()); + } + buf + } + } + + #[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Display)] + #[allow(non_camel_case_types)] + /// Enum for bind flags + pub enum Flag { + /// Works when screen is locked + #[display(fmt = "l")] + l, + /// Used for mouse binds + #[display(fmt = "m")] + m, + /// Repeats when held + #[display(fmt = "e")] + e, + /// Activates on release + #[display(fmt = "r")] + r, + } + + impl Join for Vec { + fn join(&self) -> String { + let mut buf = String::new(); + for i in self { + buf.push_str(&i.to_string()); + } + buf + } + } + + /// A struct providing a key bind + #[derive(Debug, Clone)] + pub struct Binding<'a> { + /// All the mods + pub mods: Vec, + /// The key + pub key: Key<'a>, + /// Bind flags + pub flags: Vec, + /// The dispatcher to be called once complete + pub dispatcher: DispatchType<'a>, + } + + /// Struct to hold methods for adding and removing binds + pub struct Binder; + + impl Binder { + pub(crate) fn gen_str(binding: Binding) -> crate::Result { + Ok(format!( + "{mods},{key},{dispatcher}", + mods = binding.mods.join(), + key = binding.key, + dispatcher = gen_dispatch_str(binding.dispatcher, false)?.data + )) + } + /// Binds a keybinding + pub fn bind(binding: Binding) -> crate::Result<()> { + Keyword::set( + format!("bind{}", binding.flags.join()), + Self::gen_str(binding)?, + )?; + Ok(()) + } + /// Binds a keybinding (async) + pub async fn bind_async(binding: Binding<'_>) -> crate::Result<()> { + Keyword::set_async( + format!("bind{}", binding.flags.join()), + Self::gen_str(binding)?, + ) + .await?; + Ok(()) + } + } + /// Very macro basic abstraction over [Binder] for internal use, **Dont use this instead use [crate::bind]** + #[macro_export] + #[doc(hidden)] + macro_rules! bind_raw { + (sync $mods:expr,$key:expr,$flags:expr,$dis:expr ) => {{ + use $crate::config::binds::*; + let binding = Binding { + mods: $mods, + key: $key, + flags: $flags, + dispatcher: $dis, + }; + Binder::bind(binding) + }}; + ($mods:expr,$key:expr,$flags:expr,$dis:expr ) => {{ + use $crate::config::binds::*; + let binding = Binding { + mods: $mods, + key: $key, + flags: $flags, + dispatcher: $dis, + }; + Binder::bind_async(binding) + }}; + } + + /// Macro abstraction over [Binder] + #[macro_export] + macro_rules! bind { + ($( $flag:ident ) *|$( $mod:ident ) *,$keyt:ident, $( $key:expr ), * => $dis:ident, $( $arg:expr ), *) => { + $crate::bind_raw!( + sync + vec![$(Mod::$mod), *], + Key::$keyt( $( $key ), * ), + vec![$(Flag::$flag), *], + DispatchType::$dis( $($arg),* ) + ) + }; + ($( $flag:ident ) *|$( $mod:ident ) *,$keyt:ident,$( $key:expr ), * => $dis:ident ) => { + $crate::bind_raw!( + sync + vec![$(Mod::$mod), *], + Key::$keyt( $( $key ), * ), + vec![$(Flag::$flag), *], + DispatchType::$dis + ) + }; + ($( $mod:ident ) *,$keyt:ident,$( $key:expr ), * => $dis:ident, $( $arg:expr ), *) => { + $crate::bind_raw!( + sync + vec![$(Mod::$mod), *], + Key::$keyt( $( $key ), * ), + vec![], + DispatchType::$dis( $($arg),* ) + ) + }; + ($( $mod:ident ) *,$keyt:ident,$( $key:expr ), * => $dis:ident ) => { + $crate::bind_raw!( + sync + vec![$(Mod::$mod), *], + Key::$keyt( $( $key ), * ), + vec![], + DispatchType::$dis + ) + }; + (async ; $( $flag:ident ) *|$( $mod:ident ) *,$keyt:ident, $( $key:expr ), * => $dis:ident, $( $arg:expr ), *) => { + $crate::bind_raw!( + vec![$(Mod::$mod), *], + Key::$keyt( $( $key ), * ), + vec![$(Flag::$flag), *], + DispatchType::$dis( $($arg),* ) + ) + }; + (async ; $( $flag:ident ) *|$( $mod:ident ) *,$keyt:ident,$( $key:expr ), * => $dis:ident ) => { + $crate::bind_raw!( + vec![$(Mod::$mod), *], + Key::$keyt( $( $key ), * ), + vec![$(Flag::$flag), *], + DispatchType::$dis + ) + }; + (async ; $( $mod:ident ) *,$keyt:ident,$( $key:expr ), * => $dis:ident, $( $arg:expr ), *) => { + $crate::bind_raw!( + vec![$(Mod::$mod), *], + Key::$keyt( $( $key ), * ), + vec![], + DispatchType::$dis( $($arg),* ) + ) + }; + (async ; $( $mod:ident ) *,$keyt:ident,$( $key:expr ), * => $dis:ident ) => { + $crate::bind_raw!( + vec![$(Mod::$mod), *], + Key::$keyt( $( $key ), * ), + vec![], + DispatchType::$dis + ) + }; + } +} + +#[test] +fn test_binds() { + use binds::*; + let binding = Binding { + mods: vec![Mod::SUPER], + key: Key::Key("v"), + flags: vec![], + dispatcher: DispatchType::ToggleFloating(None), + }; + let built_bind = match Binder::gen_str(binding) { + Ok(v) => v, + Err(e) => panic!("Error occured: {e}"), // Note to greppers: this is in a test! + }; + assert_eq!(built_bind, "SUPER,v,/togglefloating"); +} diff --git a/hyprland/src/ctl.rs b/hyprland/src/ctl.rs new file mode 100644 index 0000000..977aece --- /dev/null +++ b/hyprland/src/ctl.rs @@ -0,0 +1,405 @@ +use derive_more::{Constructor, Display as MDisplay}; +use std::fmt::Display as FDisplay; + +use crate::shared::*; + +/// Reload hyprland config +pub mod reload { + use super::*; + /// Reload hyprland config + pub fn call() -> crate::Result<()> { + write_to_socket_sync(SocketType::Command, command!(Empty, "reload"))?; + Ok(()) + } + /// Reload hyprland config (async) + pub async fn call_async() -> crate::Result<()> { + write_to_socket(SocketType::Command, command!(Empty, "reload")).await?; + Ok(()) + } +} +/// Enter kill mode (similar to xkill) +pub mod kill { + use super::*; + /// Enter kill mode (similar to xkill) + pub fn call() -> crate::Result<()> { + write_to_socket_sync(SocketType::Command, command!(Empty, "kill"))?; + Ok(()) + } + /// Enter kill mode (similar to xkill) (async) + pub async fn call_async() -> crate::Result<()> { + write_to_socket(SocketType::Command, command!(Empty, "kill")).await?; + Ok(()) + } +} + +/// Set the cursor theme +pub mod set_cursor { + use super::*; + /// Set the cursor theme + pub fn call(theme: Str, size: u16) -> crate::Result<()> { + write_to_socket_sync( + SocketType::Command, + command!(Empty, "setcursor {theme} {size}"), + )?; + Ok(()) + } + /// Set the cursor theme (async) + pub async fn call_async(theme: Str, size: u16) -> crate::Result<()> { + write_to_socket( + SocketType::Command, + command!(Empty, "setcursor {theme} {size}"), + ) + .await?; + Ok(()) + } +} + +/// Stuff related to managing virtual outputs/displays +pub mod output { + use super::*; + /// Output backend types + #[derive(Debug, MDisplay, Clone, Copy, PartialEq, Eq)] + pub enum OutputBackends { + /// The wayland output backend + #[display(fmt = "wayland")] + Wayland, + /// The x11 output backend + #[display(fmt = "x11")] + X11, + /// The headless output backend + #[display(fmt = "headless")] + Headless, + /// Let Hyprland decide the backend type + #[display(fmt = "auto")] + Auto, + } + + /// Create virtual displays + pub fn create(backend: OutputBackends) -> crate::Result<()> { + write_to_socket_sync( + SocketType::Command, + command!(Empty, "output create {backend}"), + )?; + Ok(()) + } + /// Remove virtual displays + pub fn remove(name: Str) -> crate::Result<()> { + write_to_socket_sync(SocketType::Command, command!(Empty, "output remove {name}"))?; + Ok(()) + } +} + +/// Switch the xkb layout index for a keyboard +pub mod switch_xkb_layout { + use super::*; + /// The types of Cmds used by [switch_xkb_layout] + #[derive(Debug, MDisplay, Clone, Copy, PartialEq, Eq)] + pub enum SwitchXKBLayoutCmdTypes { + /// Next input + #[display(fmt = "next")] + Next, + /// Previous inout + #[display(fmt = "prev")] + Previous, + /// Set to a specific input id + #[display(fmt = "{}", "_0")] + Id(u8), + } + + /// Switch the xkb layout index for a keyboard + pub fn call(device: Str, cmd: SwitchXKBLayoutCmdTypes) -> crate::Result<()> { + write_to_socket_sync( + SocketType::Command, + command!(Empty, "switchxkblayout {device} {cmd}"), + )?; + Ok(()) + } + /// Switch the xkb layout index for a keyboard + pub async fn call_async( + device: Str, + cmd: SwitchXKBLayoutCmdTypes, + ) -> crate::Result<()> { + write_to_socket( + SocketType::Command, + command!(Empty, "switchxkblayout {device} {cmd}"), + ) + .await?; + Ok(()) + } +} + +/// Creates a error that Hyprland will display +pub mod set_error { + use super::*; + /// Creates a error that Hyprland will display + pub fn call(color: Color, msg: String) -> crate::Result<()> { + write_to_socket_sync( + SocketType::Command, + command!(Empty, "seterror {color} {msg}"), + )?; + Ok(()) + } + /// Creates a error that Hyprland will display (async) + pub async fn call_async(color: Color, msg: String) -> crate::Result<()> { + write_to_socket( + SocketType::Command, + command!(Empty, "seterror {color} {msg}"), + ) + .await?; + Ok(()) + } +} + +/// Creates a notification with Hyprland +pub mod notify { + use super::*; + use std::time::Duration; + + #[allow(missing_docs)] + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + #[repr(i8)] + pub enum Icon { + NoIcon = -1, + Warning = 0, + Info = 1, + Hint = 2, + Error = 3, + Confused = 4, + Ok = 5, + } + /// Creates a notification with Hyprland + pub fn call(icon: Icon, time: Duration, color: Color, msg: String) -> crate::Result<()> { + write_to_socket_sync( + SocketType::Command, + command!( + Empty, + "notify {} {} {color} {msg}", + icon as i8, + time.as_millis() + ), + )?; + Ok(()) + } + /// Creates a error that Hyprland will display (async) + pub async fn call_async( + icon: Icon, + time: Duration, + color: Color, + msg: String, + ) -> crate::Result<()> { + write_to_socket( + SocketType::Command, + command!( + Empty, + "notify {} {} {color} {msg}", + icon as i8, + time.as_millis() + ), + ) + .await?; + Ok(()) + } +} + +/// A 8-bit color with a alpha channel +#[derive(Debug, Copy, Clone, MDisplay, Constructor, PartialEq, Eq)] +#[display(fmt = "rgba({:02x}{:02x}{:02x}{:02x})", "_0", "_1", "_2", "_3")] +pub struct Color(u8, u8, u8, u8); + +/// Provides things to setting props +pub mod set_prop { + use super::*; + + fn l(b: bool) -> &'static str { + if b { + "lock" + } else { + "" + } + } + + /// Type that represents a prop + #[derive(MDisplay, Clone, PartialEq)] + pub enum PropType { + /// The animation style + #[display(fmt = "animationstyle {}", "_0")] + AnimationStyle(String), + /// The roundness + #[display(fmt = "rounding {} {}", "_0", "l(*_1)")] + Rounding( + i64, + /// locked + bool, + ), + /// Force no blur + #[display(fmt = "forcenoblur {} {}", "*_0 as u8", "l(*_1)")] + ForceNoBlur( + bool, + /// locked + bool, + ), + /// Force opaque + #[display(fmt = "forceopaque {} {}", "*_0 as u8", "l(*_1)")] + ForceOpaque( + bool, + /// locked + bool, + ), + /// Force opaque overriden + #[display(fmt = "forceopaqueoverriden {} {}", "*_0 as u8", "l(*_1)")] + ForceOpaqueOverriden( + bool, + /// locked + bool, + ), + /// Force allow input + #[display(fmt = "forceallowsinput {} {}", "*_0 as u8", "l(*_1)")] + ForceAllowsInput( + bool, + /// locked + bool, + ), + /// Force no animations + #[display(fmt = "forcenoanims {} {}", "*_0 as u8", "l(*_1)")] + ForceNoAnims( + bool, + /// locked + bool, + ), + /// Force no border + #[display(fmt = "forcenoborder {} {}", "*_0 as u8", "l(*_1)")] + ForceNoBorder( + bool, + /// locked + bool, + ), + /// Force no shadow + #[display(fmt = "forcenoshadow {} {}", "*_0 as u8", "l(*_1)")] + ForceNoShadow( + bool, + /// locked + bool, + ), + /// Allow for windoe dancing? + #[display(fmt = "windowdancecompat {} {}", "*_0 as u8", "l(*_1)")] + WindowDanceCompat( + bool, + /// locked + bool, + ), + /// Allow for overstepping max size + #[display(fmt = "nomaxsize {} {}", "*_0 as u8", "l(*_1)")] + NoMaxSize( + bool, + /// locked + bool, + ), + /// Dim around? + #[display(fmt = "dimaround {} {}", "*_0 as u8", "l(*_1)")] + DimAround( + bool, + /// locked + bool, + ), + /// Makes the next setting be override instead of multiply + #[display(fmt = "alphaoverride {} {}", "*_0 as u8", "l(*_1)")] + AlphaOverride( + bool, + /// locked + bool, + ), + /// The alpha + #[display(fmt = "alpha {} {}", "_0", "l(*_1)")] + Alpha( + f32, + /// locked + bool, + ), + /// Makes the next setting be override instead of multiply + #[display(fmt = "alphainactiveoverride {} {}", "*_0 as u8", "l(*_1)")] + AlphaInactiveOverride( + bool, + /// locked + bool, + ), + /// The alpha for inactive + #[display(fmt = "alphainactive {} {}", "_0", "l(*_1)")] + AlphaInactive( + f32, + /// locked + bool, + ), + /// The active border color + #[display(fmt = "alphabordercolor {} {}", "_0", "l(*_1)")] + ActiveBorderColor( + Color, + /// locked + bool, + ), + /// The inactive border color + #[display(fmt = "inalphabordercolor {} {}", "_0", "l(*_1)")] + InactiveBorderColor( + Color, + /// locked + bool, + ), + } + + /// Sets a window prob + pub fn call(ident: String, prop: PropType, lock: bool) -> crate::Result<()> { + write_to_socket_sync( + SocketType::Command, + command!( + Empty, + "setprop {ident} {prop} {}", + if lock { "lock" } else { "" } + ), + )?; + Ok(()) + } + /// Sets a window prob (async) + pub async fn call_async(ident: String, prop: PropType, lock: bool) -> crate::Result<()> { + write_to_socket( + SocketType::Command, + command!( + Empty, + "setprop {ident} {prop} {}", + if lock { "lock" } else { "" } + ), + ) + .await?; + Ok(()) + } +} + +/// Provides functions for communication with plugin system +pub mod plugin { + use super::*; + use std::path::Path; + + /// Loads a plugin, by path + pub fn load(path: &Path) -> crate::Result<()> { + write_to_socket_sync( + SocketType::Command, + command!(Empty, "plugin load {}", path.display()), + )?; + Ok(()) + } + /// Loads a plugin, by path (async) + pub async fn load_async(path: &Path) -> crate::Result<()> { + write_to_socket( + SocketType::Command, + command!(Empty, "plugin load {}", path.display()), + ) + .await?; + Ok(()) + } + /// Returns a list of all plugins + pub fn list() -> crate::Result { + write_to_socket_sync(SocketType::Command, command!(Empty, "plugin list")) + } + /// Returns a list of all plugins (async) + pub async fn list_async() -> crate::Result { + write_to_socket(SocketType::Command, command!(Empty, "plugin list")).await + } +} diff --git a/hyprland/src/data/helpers.rs b/hyprland/src/data/helpers.rs new file mode 100644 index 0000000..26f8b49 --- /dev/null +++ b/hyprland/src/data/helpers.rs @@ -0,0 +1,24 @@ +use super::*; + +/// A helper struct that provides the current fullscreen state +#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Display)] +pub struct FullscreenState( + /// State + pub bool, +); + +impl HyprData for FullscreenState { + fn get() -> crate::Result { + Ok(Self(Workspace::get_active()?.fullscreen)) + } + async fn get_async() -> crate::Result { + Ok(Self(Workspace::get_active_async().await?.fullscreen)) + } +} + +impl FullscreenState { + /// This method returns a bool of the current fullscreen state + pub fn bool(self) -> bool { + self.0 + } +} diff --git a/hyprland/src/data/macros.rs b/hyprland/src/data/macros.rs new file mode 100644 index 0000000..af7462b --- /dev/null +++ b/hyprland/src/data/macros.rs @@ -0,0 +1,201 @@ +macro_rules! impl_on { + ($name:ident) => { + impl HyprData for $name { + fn get() -> $crate::Result { + let data = call_hyprctl_data_cmd(DataCommands::$name)?; + let deserialized: $name = serde_json::from_str(&data)?; + Ok(deserialized) + } + async fn get_async() -> $crate::Result { + let data = call_hyprctl_data_cmd_async(DataCommands::$name).await?; + let deserialized: $name = serde_json::from_str(&data)?; + Ok(deserialized) + } + } + }; +} + +macro_rules! implement_iterators { + ( + vector, + name: $name:ident, + iterated_field: $iterated_field:tt, + holding_type: $holding_type:ty, + ) => { + impl $name { + paste!( + #[doc = "Creates the iterator by references of `" $name "`."] + pub fn iter(&self) -> std::slice::Iter<$holding_type> { + self.0.iter() + } + + #[doc = "Creates the iterator by mutable references of " $name "`."] + pub fn iter_mut(&mut self) -> std::slice::IterMut<$holding_type> { + self.0.iter_mut() + } + ); + } + + impl IntoIterator for $name { + type Item = $holding_type; + + type IntoIter = std::vec::IntoIter<$holding_type>; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } + } + + impl<'a> IntoIterator for &'a $name { + type Item = &'a $holding_type; + type IntoIter = std::slice::Iter<'a, $holding_type>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } + } + + impl<'a> IntoIterator for &'a mut $name { + type Item = &'a mut $holding_type; + type IntoIter = std::slice::IterMut<'a, $holding_type>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter_mut() + } + } + }; + + ( + table, + name: $name:ident, + iterated_field: $iterated_field:tt, + key: $key:ty, + value: $value:ty, + ) => { + impl $name { + paste!( + #[doc = "Creates the iterator of map by references of " $name] + pub fn iter(&self) -> std::collections::hash_map::Iter<$key, $value> { + self.$iterated_field.iter() + } + + #[doc = "Creates the iterator of map by mutable references of `" $name "`."] + pub fn iter_mut(&mut self) -> std::collections::hash_map::IterMut<$key, $value> { + self.$iterated_field.iter_mut() + } + + #[doc = "Creates the consuming iterator by keys with type `" $key "` of `" $name "`."] + pub fn into_keys(self) -> std::collections::hash_map::IntoKeys<$key, $value> { + self.$iterated_field.into_keys() + } + + #[doc = "Creates the consuming iterator by values of `" $name "`."] + pub fn into_values(self) -> std::collections::hash_map::IntoValues<$key, $value> { + self.$iterated_field.into_values() + } + ); + } + + impl IntoIterator for $name { + type Item = ($key, $value); + type IntoIter = std::collections::hash_map::IntoIter<$key, $value>; + + fn into_iter(self) -> Self::IntoIter { + self.$iterated_field.into_iter() + } + } + + impl<'a> IntoIterator for &'a $name { + type Item = (&'a $key, &'a $value); + type IntoIter = std::collections::hash_map::Iter<'a, $key, $value>; + + fn into_iter(self) -> Self::IntoIter { + self.$iterated_field.iter() + } + } + + impl<'a> IntoIterator for &'a mut $name { + type Item = (&'a $key, &'a mut $value); + type IntoIter = std::collections::hash_map::IterMut<'a, $key, $value>; + + fn into_iter(self) -> Self::IntoIter { + self.$iterated_field.iter_mut() + } + } + } +} + +macro_rules! create_data_struct { + ( + vector, + name: $name:ident, + command: $cmd_kind:path, + holding_type: $holding_type:ty, + doc: $doc:literal + ) => { + #[doc = $doc] + #[derive(Debug, Clone)] + pub struct $name(Vec<$holding_type>); + + implement_iterators!( + vector, + name: $name, + iterated_field: 0, + holding_type: $holding_type, + ); + + impl HyprData for $name { + fn get() -> $crate::Result { + let data = call_hyprctl_data_cmd($cmd_kind)?; + let deserialized: Vec<$holding_type> = serde_json::from_str(&data)?; + Ok(Self(deserialized)) + } + async fn get_async() -> $crate::Result { + let data = call_hyprctl_data_cmd_async($cmd_kind).await?; + let deserialized: Vec<$holding_type> = serde_json::from_str(&data)?; + Ok(Self(deserialized)) + } + } + + impl HyprDataVec<$holding_type> for $name { + fn to_vec(self) -> Vec<$holding_type> { + self.0 + } + } + }; + + ( + table, + name: $name:ident, + command: $cmd_kind:path, + key: $key:ty, + value: $value:ty, + doc: $doc:literal + ) => { + #[doc = $doc] + #[derive(Debug)] + pub struct $name(HashMap<$key, $value>); + + implement_iterators!( + table, + name: $name, + iterated_field: 0, + key: $key, + value: $value, + ); + + impl HyprData for $name { + fn get() -> $crate::Result { + let data = call_hyprctl_data_cmd($cmd_kind)?; + let deserialized: HashMap<$key, $value> = serde_json::from_str(&data)?; + Ok(Self(deserialized)) + } + + async fn get_async() -> $crate::Result { + let data = call_hyprctl_data_cmd_async($cmd_kind).await?; + let deserialized: HashMap<$key, $value> = serde_json::from_str(&data)?; + Ok(Self(deserialized)) + } + } + }; +} diff --git a/hyprland/src/data/mod.rs b/hyprland/src/data/mod.rs new file mode 100644 index 0000000..d47663f --- /dev/null +++ b/hyprland/src/data/mod.rs @@ -0,0 +1,64 @@ +//! # Data module +//! +//! This module provides functions for getting information on the compositor +//! +//! ## Usage +//! +//! here is a example of every function in use! (blocking) +//! ```rust +//! use hyprland::data::*; +//! use hyprland::prelude::*; +//! use hyprland::shared::HResult; +//! +//! fn main() -> HResult<()> { +//! let monitors = Monitors::get()?.to_vec(); +//! println!("{monitors:#?}"); +//! +//! let workspaces = Workspaces::get()?.to_vec(); +//! println!("{workspaces:#?}"); +//! +//! let clients = Clients::get()?.to_vec(); +//! println!("{clients:#?}"); +//! +//! let active_window = Client::get_active()?; +//! println!("{active_window:#?}"); +//! +//! let layers = Layers::get()?; +//! println!("{layers:#?}"); +//! +//! let devices = Devices::get()?; +//! println!("{devices:#?}"); +//! +//! let version = Version::get()?; +//! println!("{version:#?}"); +//! +//! let cursor_pos = CursorPosition::get()?; +//! println!("{cursor_pos:#?}"); +//! Ok(()) +//! } +//! ``` + +#[macro_use] +mod macros; + +use crate::shared::*; + +#[cfg(feature = "ahash")] +use ahash::HashMap; +#[cfg(not(feature = "ahash"))] +use std::collections::HashMap; + +mod regular; + +/// Helpers data commands, these use other hyprctl commands to create new ones! +mod helpers; + +pub use crate::data::helpers::*; + +pub use crate::data::regular::*; + +//// This module provides async function calls +//pub mod asynchronous; + +//// This module provides blocking function calls +//pub mod blocking; diff --git a/hyprland/src/data/regular.rs b/hyprland/src/data/regular.rs new file mode 100644 index 0000000..c875549 --- /dev/null +++ b/hyprland/src/data/regular.rs @@ -0,0 +1,701 @@ +use super::*; +use derive_more::Display; +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; + +/// This private function is to call socket commands +async fn call_hyprctl_data_cmd_async(cmd: DataCommands) -> crate::Result { + let socket_path = SocketType::Command; + + let command = CommandContent { + flag: CommandFlag::JSON, + data: cmd.to_string(), + }; + + write_to_socket(socket_path, command).await +} + +fn call_hyprctl_data_cmd(cmd: DataCommands) -> crate::Result { + let socket_path = SocketType::Command; + + let command = CommandContent { + flag: CommandFlag::JSON, + data: cmd.to_string(), + }; + + write_to_socket_sync(socket_path, command) +} + +/// This pub(crate) enum holds every socket command that returns data +#[derive(Debug, Display, Clone, Copy, PartialEq, Eq)] +pub(crate) enum DataCommands { + #[display(fmt = "monitors")] + Monitors, + #[display(fmt = "workspaces")] + Workspaces, + #[display(fmt = "activeworkspace")] + ActiveWorkspace, + #[display(fmt = "clients")] + Clients, + #[display(fmt = "activewindow")] + ActiveWindow, + #[display(fmt = "layers")] + Layers, + #[display(fmt = "devices")] + Devices, + #[display(fmt = "version")] + Version, + #[display(fmt = "cursorpos")] + CursorPosition, + #[display(fmt = "binds")] + Binds, + #[display(fmt = "animations")] + Animations, + #[display(fmt = "workspacerules")] + WorkspaceRules, +} + +/// This struct holds a basic identifier for a workspace often used in other structs +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct WorkspaceBasic { + /// The workspace Id + pub id: WorkspaceId, + /// The workspace's name + pub name: String, +} + +/// This enum provides the different monitor transforms +#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, PartialEq, Eq, Copy)] +#[repr(u8)] +pub enum Transforms { + /// No transform + Normal = 0, + /// Rotated 90 degrees + Normal90 = 1, + /// Rotated 180 degrees + Normal180 = 2, + /// Rotated 270 degrees + Normal270 = 3, + /// Flipped + Flipped = 4, + /// Flipped and rotated 90 degrees + Flipped90 = 5, + /// Flipped and rotated 180 degrees + Flipped180 = 6, + /// Flipped and rotated 270 degrees + Flipped270 = 7, +} + +/// This struct holds information for a monitor +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct Monitor { + /// The monitor id + pub id: MonitorId, + /// The monitor's name + pub name: String, + /// The monitor's description + pub description: String, + /// The monitor width (in pixels) + pub width: u16, + /// The monitor height (in pixels) + pub height: u16, + /// The monitor's refresh rate (in hertz) + #[serde(rename = "refreshRate")] + pub refresh_rate: f32, + /// The monitor's position on the x axis (not irl ofc) + pub x: i32, + /// The monitor's position on the x axis (not irl ofc) + pub y: i32, + /// A basic identifier for the active workspace + #[serde(rename = "activeWorkspace")] + pub active_workspace: WorkspaceBasic, + /// Reserved is the amount of space (in pre-scale pixels) that a layer surface has claimed + pub reserved: (u16, u16, u16, u16), + /// The display's scale + pub scale: f32, + /// I think like the rotation? + pub transform: Transforms, + /// a string that identifies if the display is active + pub focused: bool, + /// The dpms status of a monitor + #[serde(rename = "dpmsStatus")] + pub dpms_status: bool, + /// VRR state + pub vrr: bool, +} + +impl HyprDataActive for Monitor { + fn get_active() -> crate::Result { + let all = Monitors::get()?; + if let Some(it) = all.into_iter().find(|item| item.focused) { + Ok(it) + } else { + hypr_err!("No active Hyprland monitor detected!") + } + } + async fn get_active_async() -> crate::Result { + let all = Monitors::get_async().await?; + if let Some(it) = all.into_iter().find(|item| item.focused) { + Ok(it) + } else { + hypr_err!("No active Hyprland monitor detected!") + } + } +} + +create_data_struct!( + vector, + name: Monitors, + command: DataCommands::Monitors, + holding_type: Monitor, + doc: "This struct holds a vector of monitors" +); + +/// This struct holds information for a workspace +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct Workspace { + /// The workspace Id + pub id: WorkspaceId, + /// The workspace's name + pub name: String, + /// The monitor the workspace is on + pub monitor: String, + /// The monitor id the workspace is on + #[serde(rename = "monitorID")] + pub monitor_id: MonitorId, + /// The amount of windows in the workspace + pub windows: u16, + /// A bool that shows if there is a fullscreen window in the workspace + #[serde(rename = "hasfullscreen")] + pub fullscreen: bool, + /// The last window's [Address] + #[serde(rename = "lastwindow")] + pub last_window: Address, + /// The last window's title + #[serde(rename = "lastwindowtitle")] + pub last_window_title: String, +} + +impl HyprDataActive for Workspace { + fn get_active() -> crate::Result { + let data = call_hyprctl_data_cmd(DataCommands::ActiveWorkspace)?; + let deserialized: Workspace = serde_json::from_str(&data)?; + Ok(deserialized) + } + async fn get_active_async() -> crate::Result { + let data = call_hyprctl_data_cmd_async(DataCommands::ActiveWorkspace).await?; + let deserialized: Workspace = serde_json::from_str(&data)?; + Ok(deserialized) + } +} + +create_data_struct!( + vector, + name: Workspaces, + command: DataCommands::Workspaces, + holding_type: Workspace, + doc: "This type provides a vector of workspaces" +); + +/// This struct holds information for a client/window fullscreen mode +#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, PartialEq, Eq, Copy)] +#[repr(u8)] +pub enum FullscreenMode { + /// Normal window + None = 0, + /// Maximized window + Maximized = 1, + /// Fullscreen window + Fullscreen = 2, + /// Maximized and fullscreen window + MaximizedFullscreen = 3, +} + +/// This struct holds information for a client/window +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct Client { + /// The client's [`Address`][crate::shared::Address] + pub address: Address, + /// The window location + pub at: (i16, i16), + /// The window size + pub size: (i16, i16), + /// The workspace its on + pub workspace: WorkspaceBasic, + /// Is this window floating? + pub floating: bool, + /// The internal fullscreen mode + pub fullscreen: FullscreenMode, + /// The client fullscreen mode + #[serde(rename = "fullscreenClient")] + pub fullscreen_client: FullscreenMode, + /// The monitor id the window is on + pub monitor: MonitorId, + /// The initial window class + #[serde(rename = "initialClass")] + pub initial_class: String, + /// The window class + pub class: String, + /// The initial window title + #[serde(rename = "initialTitle")] + pub initial_title: String, + /// The window title + pub title: String, + /// The process Id of the client + pub pid: i32, + /// Is this window running under XWayland? + pub xwayland: bool, + /// Is this window pinned? + pub pinned: bool, + /// Group members + pub grouped: Vec>, + /// Is this window print on screen + pub mapped: bool, + /// The swallowed window + pub swallowing: Option>, + /// When was this window last focused relatively to other windows? 0 for current, 1 previous, 2 previous before that, etc + #[serde(rename = "focusHistoryID")] + pub focus_history_id: i8, +} + +#[derive(Deserialize, Debug)] +#[serde(deny_unknown_fields)] +struct Empty {} + +impl HyprDataActiveOptional for Client { + fn get_active() -> crate::Result> { + let data = call_hyprctl_data_cmd(DataCommands::ActiveWindow)?; + let res = serde_json::from_str::(&data); + if res.is_err() { + let t = serde_json::from_str::(&data)?; + Ok(Some(t)) + } else { + Ok(None) + } + } + async fn get_active_async() -> crate::Result> { + let data = call_hyprctl_data_cmd_async(DataCommands::ActiveWindow).await?; + let res = serde_json::from_str::(&data); + if res.is_err() { + let t = serde_json::from_str::(&data)?; + Ok(Some(t)) + } else { + Ok(None) + } + } +} + +create_data_struct!( + vector, + name: Clients, + command: DataCommands::Clients, + holding_type: Client, + doc: "This struct holds a vector of clients" +); + +/// This struct holds information about a layer surface/client +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct LayerClient { + /// The layer's [`Address`][crate::shared::Address] + pub address: Address, + /// The layer's x position + pub x: i32, + /// The layer's y position + pub y: i32, + /// The layer's width + pub w: i16, + /// The layer's height + pub h: i16, + /// The layer's namespace + pub namespace: String, +} + +/// This struct holds all the layer surfaces for a display +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct LayerDisplay { + /// The different levels of layers + pub levels: HashMap>, +} + +implement_iterators!( + table, + name: LayerDisplay, + iterated_field: levels, + key: String, + value: Vec, +); + +create_data_struct!( + table, + name: Layers, + command: DataCommands::Layers, + key: String, + value: LayerDisplay, + doc: "This struct holds a hashmap of all current displays, and their layer surfaces" +); + +/// This struct holds information about a mouse device +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct Mouse { + /// The mouse's address + pub address: Address, + /// The mouse's name + pub name: String, +} + +/// This struct holds information about a keyboard device +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct Keyboard { + /// The keyboard's address + pub address: Address, + /// The keyboard's name + pub name: String, + /// The keyboard rules + pub rules: String, + /// The keyboard model + pub model: String, + /// The layout of the keyboard + pub layout: String, + /// The keyboard variant + pub variant: String, + /// The keyboard options + pub options: String, + /// The keyboard's active keymap + pub active_keymap: String, + /// The keyboard's primary status + pub main: bool, +} + +/// A enum that holds the types of tablets +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] +pub enum TabletType { + /// The TabletPad type of tablet + #[serde(rename = "tabletPad")] + TabletPad, + /// The TabletTool type of tablet + #[serde(rename = "tabletTool")] + TabletTool, +} + +/// A enum to match what the tablet belongs to +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(untagged)] +pub enum TabletBelongsTo { + /// The belongsTo data if the tablet is of type TabletPad + TabletPad { + /// The name of the parent + name: String, + /// The address of the parent + address: Address, + }, + /// The belongsTo data if the tablet is of type TabletTool + Address(Address), +} + +/// This struct holds information about a tablet device +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct Tablet { + /// The tablet's address + pub address: Address, + /// The tablet type + #[serde(rename = "type")] + pub tablet_type: Option, + /// What the tablet belongs to + #[serde(rename = "belongsTo")] + pub belongs_to: Option, + /// The name of the tablet + pub name: Option, +} + +/// This struct holds all current devices +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct Devices { + /// All the mice + pub mice: Vec, + /// All the keyboards + pub keyboards: Vec, + /// All the tablets + pub tablets: Vec, +} +impl_on!(Devices); + +/// This struct holds version information +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct Version { + /// The git branch Hyprland was built on + pub branch: String, + /// The git commit Hyprland was built on + pub commit: String, + /// This is true if there were unstaged changed when Hyprland was built + pub dirty: bool, + /// The git commit message + pub commit_message: String, + /// The flags that Hyprland was built with + pub flags: Vec, +} +impl_on!(Version); + +/// This struct holds information on the cursor position +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] +pub struct CursorPosition { + /// The x position of the cursor + pub x: i64, + /// The y position of the cursor + pub y: i64, +} +impl_on!(CursorPosition); + +/// A keybinding returned from the binds command +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct Bind { + /// Is it locked? + pub locked: bool, + /// Is it a mouse bind? + pub mouse: bool, + /// Does it execute on release? + pub release: bool, + /// Can it be held? + pub repeat: bool, + /// It's modmask + pub modmask: u16, + /// The submap its apart of + pub submap: String, + /// The key + pub key: String, + /// The keycode + pub keycode: i16, + /// The dispatcher to be executed + pub dispatcher: String, + /// The dispatcher arg + pub arg: String, +} + +create_data_struct!( + vector, + name: Binds, + command: DataCommands::Binds, + holding_type: Bind, + doc: "This struct holds a vector of binds" +); + +/// Animation styles +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub enum AnimationStyle { + /// Slide animation + Slide, + /// Vertical slide animation + SlideVert, + /// Fading slide animation + SlideFade, + /// Fading slide animation in a vertical direction + SlideFadeVert, + /// Popin animation (with percentage) + PopIn(u8), + /// Fade animation + Fade, + /// Once animation used for gradient animation + Once, + /// Loop animation used for gradient animation + Loop, + /// No animation style + None, + /// Unknown style + Unknown(String), +} + +impl From for AnimationStyle { + fn from(value: String) -> Self { + if value.starts_with("popin") { + let mut iter = value.split(' '); + iter.next(); + AnimationStyle::PopIn({ + let mut str = iter.next().unwrap_or("100%").to_string(); + str.remove(str.len() - 1); + + str.parse().unwrap_or(100_u8) + }) + } else { + match value.as_str() { + "slide" => AnimationStyle::Slide, + "slidevert" => AnimationStyle::SlideVert, + "fade" => AnimationStyle::Fade, + "slidefade" => AnimationStyle::SlideFade, + "slidefadevert" => AnimationStyle::SlideFadeVert, + "once" => AnimationStyle::Once, + "loop" => AnimationStyle::Loop, + "" => AnimationStyle::None, + _ => AnimationStyle::Unknown(value), + } + } + } +} +/// Bezier identifier +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub enum BezierIdent { + /// No bezier specified + #[serde(rename = "")] + None, + /// The default bezier + #[serde(rename = "default")] + Default, + /// A specified bezier + #[serde(rename = "name")] + Specified(String), +} + +impl From for BezierIdent { + fn from(value: String) -> Self { + match value.as_str() { + "" => BezierIdent::None, + "default" => BezierIdent::Default, + _ => BezierIdent::Specified(value), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +struct RawBezierIdent { + pub name: String, +} + +/// A bezier curve +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct Bezier { + ///. Name of the bezier + pub name: String, + /// X position of first point + pub x0: f32, + /// Y position of first point + pub y0: f32, + /// X position of second point + pub x1: f32, + /// Y position of second point + pub y1: f32, +} + +/// A struct representing a animation +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +struct AnimationRaw { + /// The name of the animation + pub name: String, + /// Is it overridden? + pub overridden: bool, + /// What bezier does it use? + pub bezier: String, + /// Is it enabled? + pub enabled: bool, + /// How fast is it? + pub speed: f32, + /// The style of animation + pub style: String, +} + +/// A struct representing a animation +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct Animation { + /// The name of the animation + pub name: String, + /// Is it overridden? + pub overridden: bool, + /// What bezier does it use? + pub bezier: BezierIdent, + /// Is it enabled? + pub enabled: bool, + /// How fast is it? + pub speed: f32, + /// The style of animation + pub style: AnimationStyle, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +struct AnimationsRaw(Vec, Vec); + +/// Struct that holds animations and beziers +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct Animations(pub Vec, pub Vec); + +impl HyprData for Animations { + fn get() -> crate::Result + where + Self: Sized, + { + let out = call_hyprctl_data_cmd(DataCommands::Animations)?; + let des: AnimationsRaw = serde_json::from_str(&out)?; + let AnimationsRaw(anims, beziers) = des; + let new_anims: Vec = anims + .into_iter() + .map(|item| Animation { + name: item.name, + overridden: item.overridden, + bezier: item.bezier.into(), + enabled: item.enabled, + speed: item.speed, + style: item.style.into(), + }) + .collect(); + let new_bezs: Vec = beziers.into_iter().map(|item| item.name.into()).collect(); + Ok(Animations(new_anims, new_bezs)) + } + async fn get_async() -> crate::Result + where + Self: Sized, + { + let out = call_hyprctl_data_cmd_async(DataCommands::Animations).await?; + let des: AnimationsRaw = serde_json::from_str(&out)?; + let AnimationsRaw(anims, beziers) = des; + let new_anims: Vec = anims + .into_iter() + .map(|item| Animation { + name: item.name, + overridden: item.overridden, + bezier: item.bezier.into(), + enabled: item.enabled, + speed: item.speed, + style: item.style.into(), + }) + .collect(); + let new_bezs: Vec = beziers.into_iter().map(|item| item.name.into()).collect(); + Ok(Animations(new_anims, new_bezs)) + } +} + +// HACK: shadow and decorate are actually missing from the hyprctl json output for some reason +// HACK: gaps_in and gaps_out are returned as arrays with 4 integers, even though Hyprland doesn't support per-side gaps +/// The rules of an individual workspace, as returned by hyprctl json. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct WorkspaceRuleset { + /// The name of the workspace + #[serde(rename = "workspaceString")] + pub workspace_string: String, + /// The monitor the workspace is on + pub monitor: Option, + /// Is it default? + pub default: Option, + /// The gaps between windows + #[serde(rename = "gapsIn")] + pub gaps_in: Option>, + /// The gaps between windows and monitor edges + #[serde(rename = "gapsOut")] + pub gaps_out: Option>, + /// The size of window borders + #[serde(rename = "borderSize")] + pub border_size: Option, + /// Are borders enabled? + pub border: Option, + /// Are shadows enabled? + pub shadow: Option, + /// Is rounding enabled? + pub rounding: Option, + /// Are window decorations enabled? + pub decorate: Option, + /// Is it persistent? + pub persistent: Option, +} + +create_data_struct!( + vector, + name: WorkspaceRules, + command: DataCommands::WorkspaceRules, + holding_type: WorkspaceRuleset, + doc: "This struct holds a vector of workspace rules per workspace" +); diff --git a/hyprland/src/dispatch.rs b/hyprland/src/dispatch.rs new file mode 100644 index 0000000..51a2ea9 --- /dev/null +++ b/hyprland/src/dispatch.rs @@ -0,0 +1,613 @@ +//! # Dispatch module +//! +//! This module is used for calling dispatchers and changing keywords +//! +//! ## Usage +//! +//! ```rust +//! use hyprland::shared::HResult; +//! use hyprland::dispatch::{Dispatch, DispatchType}; +//! fn main() -> HResult<()> { +//! Dispatch::call(DispatchType::Exec("kitty"))?; +//! +//! Ok(()) +//! } +//! ```` + +use crate::shared::*; +use derive_more::Display; +use std::string::ToString; + +/// This enum is for identifying a window +#[derive(Debug, Clone, Display)] +pub enum WindowIdentifier<'a> { + /// The address of a window + #[display(fmt = "address:{_0}")] + Address(Address), + /// A Regular Expression to match the window class (handled by Hyprland) + #[display(fmt = "{_0}")] + ClassRegularExpression(&'a str), + /// The window title + #[display(fmt = "title:{_0}")] + Title(&'a str), + /// The window's process Id + #[display(fmt = "pid:{_0}")] + ProcessId(u32), +} + +/// This enum holds the fullscreen types +#[derive(Debug, Clone, Display)] +pub enum FullscreenType { + /// Fills the whole screen + #[display(fmt = "0")] + Real, + /// Maximizes the window + #[display(fmt = "1")] + Maximize, + /// Passes no param + #[display(fmt = "")] + NoParam, +} + +/// This enum holds directions, typically used for moving +#[derive(Debug, Clone, Display)] +#[allow(missing_docs)] +pub enum Direction { + #[display(fmt = "u")] + Up, + #[display(fmt = "d")] + Down, + #[display(fmt = "r")] + Right, + #[display(fmt = "l")] + Left, +} + +/// This enum is used for resizing and moving windows precisely +#[derive(Debug, Clone)] +pub enum Position { + /// A delta + Delta(i16, i16), + /// The exact size + Exact(i16, i16), +} + +impl std::fmt::Display for Position { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let out = match self { + Position::Delta(x, y) => format!("{x} {y}"), + Position::Exact(w, h) => format!("exact {w} {h}"), + }; + write!(f, "{out}") + } +} + +/// This enum holds a direction for cycling +#[allow(missing_docs)] +#[derive(Debug, Clone, Display)] +pub enum CycleDirection { + #[display(fmt = "")] + Next, + #[display(fmt = "prev")] + Previous, +} + +/// This enum holds a direction for switch windows in a group +#[allow(missing_docs)] +#[derive(Debug, Clone, Display)] +pub enum WindowSwitchDirection { + #[display(fmt = "b")] + Back, + #[display(fmt = "f")] + Forward, +} + +/// This enum is used for identifying monitors +#[derive(Debug, Clone)] +pub enum MonitorIdentifier<'a> { + /// The monitor that is to the specified direction of the active one + Direction(Direction), + /// The monitor id + Id(MonitorId), + /// The monitor name + Name(&'a str), + /// The current monitor + Current, + /// The workspace relative to the current workspace + Relative(i32), +} + +impl std::fmt::Display for MonitorIdentifier<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let out = match self { + MonitorIdentifier::Direction(dir) => dir.to_string(), + MonitorIdentifier::Id(id) => id.to_string(), + MonitorIdentifier::Name(name) => name.to_string(), + MonitorIdentifier::Current => "current".to_string(), + MonitorIdentifier::Relative(int) => format_relative(*int, ""), + }; + write!(f, "{out}") + } +} + +/// This enum holds corners +#[allow(missing_docs)] +#[derive(Debug, Clone)] +pub enum Corner { + BottomLeft = 0, + BottomRight = 1, + TopRight = 2, + TopLeft = 3, +} + +/// This enum holds options that are applied to the current workspace +#[derive(Debug, Clone, Display)] +pub enum WorkspaceOptions { + /// Makes all windows pseudo tiled + #[display(fmt = "allfloat")] + AllPseudo, + /// Makes all windows float + #[display(fmt = "allpseudo")] + AllFloat, +} + +/// This enum is for identifying workspaces that also includes the special workspace +#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)] +pub enum WorkspaceIdentifierWithSpecial<'a> { + /// The workspace Id + Id(WorkspaceId), + /// The workspace relative to the current workspace + #[display(fmt = "{}", "format_relative(*_0, \"\")")] + Relative(i32), + /// The workspace on the monitor relative to the current workspace + #[display(fmt = "{}", "format_relative(*_0, \"m\")")] + RelativeMonitor(i32), + /// The workspace on the monitor relative to the current workspace, including empty workspaces + #[display(fmt = "{}", "format_relative(*_0, \"r\")")] + RelativeMonitorIncludingEmpty(i32), + /// The open workspace relative to the current workspace + #[display(fmt = "{}", "format_relative(*_0, \"e\")")] + RelativeOpen(i32), + /// The previous Workspace + #[display(fmt = "previous")] + Previous, + /// The first available empty workspace + #[display(fmt = "empty")] + Empty, + /// The name of the workspace + #[display(fmt = "name:{_0}")] + Name(&'a str), + /// The special workspace + #[display(fmt = "special{}", "format_special_workspace_ident(_0)")] + Special(Option<&'a str>), +} + +#[inline(always)] +fn format_special_workspace_ident<'a>(opt: &'a Option<&'a str>) -> String { + match opt { + Some(o) => ":".to_owned() + o, + None => String::new(), + } +} + +/// This enum is for identifying workspaces +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum WorkspaceIdentifier<'a> { + /// The workspace Id + Id(WorkspaceId), + /// The workspace relative to the current workspace + Relative(i32), + /// The workspace on the monitor relative to the current workspace + RelativeMonitor(i32), + /// The workspace on the monitor relative to the current workspace, including empty workspaces + RelativeMonitorIncludingEmpty(i32), + /// The open workspace relative to the current workspace + RelativeOpen(i32), + /// The previous Workspace + Previous, + /// The first available empty workspace + Empty, + /// The name of the workspace + Name(&'a str), +} + +impl std::fmt::Display for WorkspaceIdentifier<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use WorkspaceIdentifier::*; + let out = match self { + Id(id) => format!("{id}"), + Name(name) => format!("name:{name}"), + Relative(int) => format_relative(*int, ""), + RelativeMonitor(int) => format_relative(*int, "m"), + RelativeMonitorIncludingEmpty(int) => format_relative(*int, "r"), + RelativeOpen(int) => format_relative(*int, "e"), + Previous => "previous".to_string(), + Empty => "empty".to_string(), + }; + + write!(f, "{out}") + } +} + +/// This enum is the params to MoveWindow dispatcher +#[derive(Debug, Clone)] +pub enum WindowMove<'a> { + /// Moves the window to a specified monitor + Monitor(MonitorIdentifier<'a>), + /// Moves the window in a specified direction + Direction(Direction), +} + +/// This enum holds every dispatcher +#[derive(Debug, Clone)] +pub enum DispatchType<'a> { + /// This lets you use dispatchers not supported by hyprland-rs yet, please make issues before + /// using + Custom( + /// Name of event + &'a str, + /// Args + &'a str, + ), + /// This dispatcher changes the current cursor + SetCursor( + /// The cursor theme + &'a str, + /// The size + u16, + ), + /// This dispatcher executes a program + Exec(&'a str), + /// This dispatcher passes a keybind to a window when called in a + /// keybind, its used for global keybinds. And should **ONLY** be used with keybinds + Pass(WindowIdentifier<'a>), + /// Executes a Global Shortcut using the GlobalShortcuts portal. + Global(&'a str), + /// This dispatcher kills the active window/client + KillActiveWindow, + /// This dispatcher closes the specified window + CloseWindow(WindowIdentifier<'a>), + /// This dispatcher changes the current workspace + Workspace(WorkspaceIdentifierWithSpecial<'a>), + /// This dispatcher moves a window (focused if not specified) to a workspace + MoveToWorkspace( + WorkspaceIdentifierWithSpecial<'a>, + Option>, + ), + /// This dispatcher moves a window (focused if not specified) to a workspace, without switching to that + /// workspace + MoveToWorkspaceSilent( + WorkspaceIdentifierWithSpecial<'a>, + Option>, + ), + /// This dispatcher floats a window (current if not specified) + ToggleFloating(Option>), + /// This dispatcher toggles the current window fullscreen state + ToggleFullscreen(FullscreenType), + /// This dispatcher toggles the focused window’s internal + /// fullscreen state without altering the geometry + ToggleFakeFullscreen, + /// This dispatcher sets the DPMS status for all monitors + ToggleDPMS(bool, Option<&'a str>), + /// This dispatcher toggles pseudo tiling for the current window + TogglePseudo, + /// This dispatcher pins the active window to all workspaces + TogglePin, + /// This dispatcher moves the window focus in a specified direction + MoveFocus(Direction), + /// This dispatcher moves the current window to a monitor or in a specified direction + MoveWindow(WindowMove<'a>), + /// This dispatcher centers the active window + CenterWindow, + /// This dispatcher resizes the active window using a [`Position`][Position] enum + ResizeActive(Position), + /// This dispatcher moves the active window using a [`Position`][Position] enum + MoveActive(Position), + /// This dispatcher resizes the specified window using a [`Position`][Position] enum + ResizeWindowPixel(Position, WindowIdentifier<'a>), + /// This dispatcher moves the specified window using a [`Position`][Position] enum + MoveWindowPixel(Position, WindowIdentifier<'a>), + /// This dispatcher cycles windows using a specified direction + CycleWindow(CycleDirection), + /// This dispatcher swaps the focused window with the window on a workspace using a specified direction + SwapNext(CycleDirection), + /// This dispatcher swaps windows using a specified direction + SwapWindow(Direction), + /// This dispatcher focuses a specified window + FocusWindow(WindowIdentifier<'a>), + /// This dispatcher focuses a specified monitor + FocusMonitor(MonitorIdentifier<'a>), + /// This dispatcher changed the split ratio + ChangeSplitRatio(f32), + /// This dispatcher toggle opacity for the current window/client + ToggleOpaque, + /// This dispatcher moves the cursor to a specified corner of a window + MoveCursorToCorner(Corner), + /// This dispatcher moves the cursor to a specified position + /// (x, y) where x starts from left to right, and y starts from top to bottom + MoveCursor(i64, i64), + /// This dispatcher applied a option to all windows in a workspace + WorkspaceOption(WorkspaceOptions), + /// This dispatcher renames a workspace + RenameWorkspace(WorkspaceId, Option<&'a str>), + /// This exits Hyprland **(DANGEROUS)** + Exit, + /// This dispatcher forces the renderer to reload + ForceRendererReload, + /// This dispatcher moves the current workspace to a specified monitor + MoveCurrentWorkspaceToMonitor(MonitorIdentifier<'a>), + /// This dispatcher moves a specified workspace to a specified monitor + MoveWorkspaceToMonitor(WorkspaceIdentifier<'a>, MonitorIdentifier<'a>), + /// This dispatcher swaps the active workspaces of two monitors + SwapActiveWorkspaces(MonitorIdentifier<'a>, MonitorIdentifier<'a>), + /// This dispatcher brings the active window to the top of the stack + BringActiveToTop, + /// This toggles the special workspace (AKA scratchpad) + ToggleSpecialWorkspace(Option), + /// This dispatcher jump to urgent or the last window + FocusUrgentOrLast, + /// Switch focus from current to previously focused window + FocusCurrentOrLast, + + // LAYOUT DISPATCHERS + // DWINDLE + /// Toggles the split (top/side) of the current window. `preserve_split` must be enabled for toggling to work. + ToggleSplit, + + // MASTER + /// Swaps the current window with master. + /// If the current window is the master, + /// swaps it with the first child. + SwapWithMaster(SwapWithMasterParam), + /// Focuses the master window. + FocusMaster(FocusMasterParam), + /// Adds a master to the master side. That will be the active window, + /// if it’s not a master, or the first non-master window. + AddMaster, + /// Removes a master from the master side. That will be the + /// active window, if it’s a master, or the last master window. + RemoveMaster, + /// Sets the orientation for the current workspace to left + /// (master area left, slave windows to the right, vertically stacked) + OrientationLeft, + /// Sets the orientation for the current workspace to right + /// (master area right, slave windows to the left, vertically stacked) + OrientationRight, + /// Sets the orientation for the current workspace to top + /// (master area top, slave windows to the bottom, horizontally stacked) + OrientationTop, + /// Sets the orientation for the current workspace to bottom + /// (master area bottom, slave windows to the top, horizontally stacked) + OrientationBottom, + /// Sets the orientation for the current workspace to center + /// (master area center, slave windows alternate to the left and right, vertically stacked) + OrientationCenter, + /// Cycle to the next orientation for the current workspace (clockwise) + OrientationNext, + /// Cycle to the previous orientation for the current workspace (counter-clockwise) + OrientationPrev, + + // Group Dispatchers + /// Toggles the current active window into a group + ToggleGroup, + /// Switches to the next window in a group. + ChangeGroupActive(WindowSwitchDirection), + /// Locks the groups + LockGroups(LockType), + /// Moves the active window into a group in a specified direction + MoveIntoGroup(Direction), + /// Moves the active window out of a group. + MoveOutOfGroup, +} + +/// Enum used with [DispatchType::LockGroups], to determine how to lock/unlock +#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, PartialOrd, Ord)] +pub enum LockType { + /// Lock Group + #[display(fmt = "lock")] + Lock, + /// Unlock Group + #[display(fmt = "unlock")] + Unlock, + /// Toggle lock state of Group + #[display(fmt = "toggle")] + ToggleLock, +} + +/// Param for [SwapWithMaster] dispatcher +#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)] +pub enum SwapWithMasterParam { + /// New focus is the new master window + #[display(fmt = "master")] + Master, + /// New focus is the new child + #[display(fmt = "child")] + Child, + /// Keep the focus of the previously focused window + #[display(fmt = "auto")] + Auto, +} + +/// Param for [FocusMaster] dispatcher +#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)] +pub enum FocusMasterParam { + /// Focus stays at master, (even if it was selected before) + #[display(fmt = "master")] + Master, + /// If the current window is the master, focuses the first child + #[display(fmt = "auto")] + Auto, +} + +#[inline(always)] +fn format_relative( + int: T, + extra: &'_ str, +) -> String { + if int.is_positive() { + format!("{extra}+{int}") + } else if int.is_negative() { + format!("{extra}-{}", int.abs()) + } else { + "+0".to_owned() + } +} + +pub(crate) fn gen_dispatch_str(cmd: DispatchType, dispatch: bool) -> crate::Result { + use DispatchType::*; + let sep = if dispatch { " " } else { "," }; + let string_to_pass = match &cmd { + Custom(name, args) => format!("{name}{sep}{args}"), + Exec(sh) => format!("exec{sep}{sh}"), + Pass(win) => format!("pass{sep}{win}"), + Global(name) => format!("global{sep}{name}"), + KillActiveWindow => "killactive".to_string(), + CloseWindow(win) => format!("closewindow{sep}{win}"), + Workspace(work) => format!("workspace{sep}{work}"), + MoveToWorkspace(work, Some(win)) => format!("movetoworkspace{sep}{work},{win}"), + MoveToWorkspace(work, None) => format!("movetoworkspace{sep}{work}"), + MoveToWorkspaceSilent(work, Some(win)) => format!("movetoworkspacesilent{sep}{work},{win}"), + MoveToWorkspaceSilent(work, None) => format!("movetoworkspacesilent{sep}{work}"), + ToggleFloating(Some(v)) => format!("togglefloating{sep}{v}"), + ToggleFloating(None) => "togglefloating".to_string(), + ToggleFullscreen(ftype) => format!("fullscreen{sep}{ftype}"), + ToggleFakeFullscreen => "fakefullscreen".to_string(), + ToggleDPMS(stat, mon) => { + format!( + "dpms{sep}{} {}", + if *stat { "on" } else { "off" }, + mon.unwrap_or_default() + ) + } + TogglePseudo => "pseudo".to_string(), + TogglePin => "pin".to_string(), + MoveFocus(dir) => format!("movefocus{sep}{dir}",), + MoveWindow(ident) => format!( + "movewindow{sep}{}", + match ident { + WindowMove::Direction(dir) => dir.to_string(), + WindowMove::Monitor(mon) => format!("mon:{mon}"), + } + ), + CenterWindow => "centerwindow".to_string(), + ResizeActive(pos) => format!("resizeactive{sep}{pos}"), + MoveActive(pos) => format!("moveactive {pos}"), + ResizeWindowPixel(pos, win) => format!("resizewindowpixel{sep}{pos},{win}"), + MoveWindowPixel(pos, win) => format!("movewindowpixel{sep}{pos},{win}"), + CycleWindow(dir) => format!("cyclenext{sep}{dir}"), + SwapNext(dir) => format!("swapnext{sep}{dir}"), + SwapWindow(dir) => format!("swapwindow{sep}{dir}"), + FocusWindow(win) => format!("focuswindow{sep}{win}"), + FocusMonitor(mon) => format!("focusmonitor{sep}{mon}"), + ChangeSplitRatio(ratio) => format!("splitratio {ratio}"), + ToggleOpaque => "toggleopaque".to_string(), + MoveCursorToCorner(corner) => format!("movecursortocorner{sep}{}", corner.clone() as u8), + MoveCursor(x, y) => format!("movecursor{sep}{x} {y}"), + WorkspaceOption(opt) => format!("workspaceopt{sep}{opt}"), + Exit => "exit".to_string(), + ForceRendererReload => "forcerendererreload".to_string(), + MoveCurrentWorkspaceToMonitor(mon) => format!("movecurrentworkspacetomonitor{sep}{mon}"), + MoveWorkspaceToMonitor(work, mon) => format!("moveworkspacetomonitor{sep}{work} {mon}"), + ToggleSpecialWorkspace(Some(name)) => format!("togglespecialworkspace {name}"), + ToggleSpecialWorkspace(None) => "togglespecialworkspace".to_string(), + RenameWorkspace(id, name) => { + format!( + "renameworkspace{sep}{id} {}", + name.unwrap_or(&id.to_string()) + ) + } + SwapActiveWorkspaces(mon, mon2) => format!("swapactiveworkspaces{sep}{mon} {mon2}",), + BringActiveToTop => "bringactivetotop".to_string(), + SetCursor(theme, size) => format!("{theme} {}", *size), + FocusUrgentOrLast => "focusurgentorlast".to_string(), + FocusCurrentOrLast => "focuscurrentorlast".to_string(), + ToggleSplit => "togglesplit".to_string(), + SwapWithMaster(param) => format!("swapwithmaster{sep}{param}"), + FocusMaster(param) => format!("focusmaster{sep}{param}"), + AddMaster => "addmaster".to_string(), + RemoveMaster => "removemaster".to_string(), + OrientationLeft => "orientationleft".to_string(), + OrientationRight => "orientationright".to_string(), + OrientationTop => "orientationtop".to_string(), + OrientationBottom => "orientationbottom".to_string(), + OrientationCenter => "orientationcenter".to_string(), + OrientationNext => "orientationnext".to_string(), + OrientationPrev => "orientationprev".to_string(), + ToggleGroup => "togglegroup".to_string(), + ChangeGroupActive(dir) => format!("changegroupactive{sep}{dir}"), + LockGroups(how) => format!("lockgroups{sep}{how}"), + MoveIntoGroup(dir) => format!("moveintogroup{sep}{dir}"), + MoveOutOfGroup => "moveoutofgroup".to_string(), + }; + + if let SetCursor(_, _) = cmd { + Ok(command!(JSON, "setcursor {string_to_pass}")) + } else if dispatch { + Ok(command!(JSON, "dispatch {string_to_pass}")) + } else { + Ok(command!(Empty, "{string_to_pass}")) + } +} + +/// The struct that provides all dispatching methods +pub struct Dispatch; + +impl Dispatch { + /// This function calls a specified dispatcher (blocking) + /// + /// ```rust + /// # use hyprland::shared::HResult; + /// # fn main() -> HResult<()> { + /// use hyprland::dispatch::{DispatchType,Dispatch}; + /// // This is an example of just one dispatcher, there are many more! + /// Dispatch::call(DispatchType::Exec("kitty")) + /// # } + /// ``` + pub fn call(dispatch_type: DispatchType) -> crate::Result<()> { + let output = + write_to_socket_sync(SocketType::Command, gen_dispatch_str(dispatch_type, true)?); + + match output { + Ok(msg) => match msg.as_str() { + "ok" => Ok(()), + msg => Err(HyprError::NotOkDispatch(msg.to_string())), + }, + Err(error) => Err(error), + } + } + + /// This function calls a specified dispatcher (async) + /// + /// ```rust + /// # use hyprland::shared::HResult; + /// # async fn function() -> HResult<()> { + /// use hyprland::dispatch::{DispatchType,Dispatch}; + /// // This is an example of just one dispatcher, there are many more! + /// Dispatch::call_async(DispatchType::Exec("kitty")).await?; + /// # Ok(()) + /// # } + /// ``` + pub async fn call_async(dispatch_type: DispatchType<'_>) -> crate::Result<()> { + let output = + write_to_socket(SocketType::Command, gen_dispatch_str(dispatch_type, true)?).await; + + match output { + Ok(msg) => match msg.as_str() { + "ok" => Ok(()), + msg => Err(HyprError::NotOkDispatch(msg.to_string())), + }, + Err(error) => Err(error), + } + } +} + +/// Macro abstraction over [Dispatch::call] +#[macro_export] +macro_rules! dispatch { + ($dis:ident, $( $arg:expr ), *) => { + Dispatch::call(DispatchType::$dis($($arg), *)) + }; + (async; $dis:ident, $( $arg:expr ), *) => { + Dispatch::call_async(DispatchType::$dis($($arg), *)) + }; +} diff --git a/hyprland/src/event_listener/async_im.rs b/hyprland/src/event_listener/async_im.rs new file mode 100644 index 0000000..0be2577 --- /dev/null +++ b/hyprland/src/event_listener/async_im.rs @@ -0,0 +1,120 @@ +use crate::shared::*; + +use crate::event_listener::shared::*; + +/// This struct is used for adding event handlers and executing them on events +/// # The Event Listener +/// +/// This struct holds what you need to create a event listener +/// +/// ## Usage +/// +/// ```rust, no_run +/// use hyprland::event_listener::EventListener; +/// let mut listener = EventListener::new(); // creates a new listener +/// // add a event handler which will be ran when this event happens +/// listener.add_workspace_change_handler(|data| println!("{:#?}", data)); +/// listener.start_listener(); // or `.start_listener_async().await` if async +/// ``` +pub struct AsyncEventListener { + pub(crate) events: AsyncEvents, +} + +// Mark the EventListener as thread-safe +impl Default for AsyncEventListener { + fn default() -> Self { + Self::new() + } +} + +impl HasAsyncExecutor for AsyncEventListener { + async fn event_executor_async(&mut self, event: Event) -> crate::Result<()> { + match event { + Event::WorkspaceChanged(id) => arm_async!(id, workspace_changed_events, self), + Event::WorkspaceAdded(id) => arm_async!(id, workspace_added_events, self), + Event::WorkspaceDeleted(data) => { + arm_async!(data, workspace_destroyed_events, self) + } + Event::WorkspaceMoved(evend) => arm_async!(evend, workspace_moved_events, self), + Event::WorkspaceRename(even) => arm_async!(even, workspace_rename_events, self), + Event::ActiveMonitorChanged(evend) => { + arm_async!(evend, active_monitor_changed_events, self) + } + Event::ActiveWindowChangedMerged(event) => { + arm_async!(event, active_window_changed_events, self) + } + Event::ActiveWindowChangedV1(_) => (), + Event::ActiveWindowChangedV2(_) => (), + Event::FullscreenStateChanged(bool) => { + arm_async!(bool, fullscreen_state_changed_events, self) + } + Event::MonitorAdded(monitor) => arm_async!(monitor, monitor_added_events, self), + Event::MonitorRemoved(monitor) => arm_async!(monitor, monitor_removed_events, self), + Event::WindowClosed(addr) => arm_async!(addr, window_close_events, self), + Event::WindowMoved(even) => arm_async!(even, window_moved_events, self), + Event::WindowOpened(even) => arm_async!(even, window_open_events, self), + Event::LayoutChanged(even) => arm_async!(even, keyboard_layout_change_events, self), + Event::SubMapChanged(map) => arm_async!(map, sub_map_changed_events, self), + Event::LayerOpened(namespace) => arm_async!(namespace, layer_open_events, self), + Event::LayerClosed(namespace) => arm_async!(namespace, layer_closed_events, self), + Event::FloatStateChanged(even) => arm_async!(even, float_state_events, self), + Event::UrgentStateChanged(even) => arm_async!(even, urgent_state_events, self), + Event::Minimize(data) => arm_async!(data, minimize_events, self), + Event::WindowTitleChanged(addr) => arm_async!(addr, window_title_changed_events, self), + Event::Screencast(data) => arm_async!(data, screencast_events, self), + } + Ok(()) + } +} + +impl AsyncEventListener { + /// This method creates a new EventListener instance + /// + /// ```rust + /// use hyprland::event_listener::EventListener; + /// let mut listener = EventListener::new(); + /// ``` + pub fn new() -> Self { + Self { + events: init_events!(AsyncEvents), + } + } + + /// This method starts the event listener (async) + /// + /// This should be ran after all of your handlers are defined + /// ```rust, no_run + /// # async fn function() -> std::io::Result<()> { + /// use hyprland::event_listener::EventListener; + /// let mut listener = EventListener::new(); + /// listener.add_workspace_change_handler(|id| println!("changed workspace to {id:?}")); + /// listener.start_listener_async().await; + /// # Ok(()) + /// # } + /// ``` + pub async fn start_listener_async(&mut self) -> crate::Result<()> { + use crate::unix_async::*; + + let socket_path = get_socket_path(SocketType::Listener)?; + let mut stream = UnixStream::connect(socket_path).await?; + + let mut active_windows = vec![]; + loop { + let mut buf = [0; 4096]; + + let num_read = stream.read(&mut buf).await?; + if num_read == 0 { + break; + } + let buf = &buf[..num_read]; + let string = String::from_utf8(buf.to_vec())?; + let parsed: Vec = event_parser(string)?; + + for event in parsed { + self.event_primer_async(event, &mut active_windows).await?; + } + } + + Ok(()) + } +} diff --git a/hyprland/src/event_listener/immutable.rs b/hyprland/src/event_listener/immutable.rs new file mode 100644 index 0000000..59311c5 --- /dev/null +++ b/hyprland/src/event_listener/immutable.rs @@ -0,0 +1,147 @@ +use super::*; +use std::io; + +/// This struct is used for adding event handlers and executing them on events +/// # The Event Listener +/// +/// This struct holds what you need to create a event listener +/// +/// ## Usage +/// +/// ```rust, no_run +/// use hyprland::event_listener::EventListener; +/// let mut listener = EventListener::new(); // creates a new listener +/// // add a event handler which will be ran when this event happens +/// listener.add_workspace_change_handler(|data| println!("{:#?}", data)); +/// listener.start_listener(); // or `.start_listener_async().await` if async +/// ``` +pub struct EventListener { + pub(crate) events: Events, +} + +impl Default for EventListener { + fn default() -> Self { + Self::new() + } +} + +impl HasExecutor for EventListener { + fn event_executor(&mut self, event: Event) -> crate::Result<()> { + use Event::*; + match event { + WorkspaceChanged(id) => arm!(id, workspace_changed_events, self), + WorkspaceAdded(id) => arm!(id, workspace_added_events, self), + WorkspaceDeleted(data) => arm!(data, workspace_destroyed_events, self), + WorkspaceMoved(evend) => arm!(evend, workspace_moved_events, self), + WorkspaceRename(even) => arm!(even, workspace_rename_events, self), + ActiveMonitorChanged(evend) => arm!(evend, active_monitor_changed_events, self), + ActiveWindowChangedMerged(opt) => arm!(opt, active_window_changed_events, self), + ActiveWindowChangedV1(_) => (), + ActiveWindowChangedV2(_) => (), + FullscreenStateChanged(bool) => arm!(bool, fullscreen_state_changed_events, self), + MonitorAdded(monitor) => arm!(monitor, monitor_added_events, self), + MonitorRemoved(monitor) => arm!(monitor, monitor_removed_events, self), + WindowClosed(addr) => arm!(addr, window_close_events, self), + WindowMoved(even) => arm!(even, window_moved_events, self), + WindowOpened(even) => arm!(even, window_open_events, self), + LayoutChanged(even) => arm!(even, keyboard_layout_change_events, self), + SubMapChanged(map) => arm!(map, sub_map_changed_events, self), + LayerOpened(namespace) => arm!(namespace, layer_open_events, self), + LayerClosed(namespace) => arm!(namespace, layer_closed_events, self), + FloatStateChanged(even) => arm!(even, float_state_events, self), + UrgentStateChanged(even) => arm!(even, urgent_state_events, self), + Minimize(data) => arm!(data, minimize_events, self), + WindowTitleChanged(addr) => arm!(addr, window_title_changed_events, self), + Screencast(data) => arm!(data, screencast_events, self), + } + Ok(()) + } +} + +impl EventListener { + /// This method creates a new EventListener instance + /// + /// ```rust + /// use hyprland::event_listener::EventListener; + /// let mut listener = EventListener::new(); + /// ``` + pub fn new() -> EventListener { + EventListener { + events: init_events!(Events), + } + } + + /// This method starts the event listener (async) + /// + /// This should be ran after all of your handlers are defined + /// ```rust, no_run + /// # async fn function() -> std::io::Result<()> { + /// use hyprland::event_listener::EventListener; + /// let mut listener = EventListener::new(); + /// listener.add_workspace_change_handler(|id| println!("changed workspace to {id:?}")); + /// listener.start_listener_async().await; + /// # Ok(()) + /// # } + /// ``` + pub async fn start_listener_async(&mut self) -> crate::Result<()> { + use crate::unix_async::*; + + let socket_path = get_socket_path(SocketType::Listener)?; + let mut stream = UnixStream::connect(socket_path).await?; + + let mut active_windows = vec![]; + loop { + let mut buf = [0; 4096]; + + let num_read = stream.read(&mut buf).await?; + if num_read == 0 { + break; + } + let buf = &buf[..num_read]; + let string = String::from_utf8(buf.to_vec())?; + let parsed: Vec = event_parser(string)?; + + for event in parsed { + self.event_primer(event, &mut active_windows)?; + } + } + + Ok(()) + } + + /// This method starts the event listener (blocking) + /// + /// This should be ran after all of your handlers are defined + /// ```rust, no_run + /// use hyprland::event_listener::EventListener; + /// let mut listener = EventListener::new(); + /// listener.add_workspace_change_handler(&|id| println!("changed workspace to {id:?}")); + /// listener.start_listener(); + /// ``` + pub fn start_listener(&mut self) -> crate::Result<()> { + use io::prelude::*; + use std::os::unix::net::UnixStream; + + let socket_path = get_socket_path(SocketType::Listener)?; + let mut stream = UnixStream::connect(socket_path)?; + + let mut active_windows = vec![]; + loop { + let mut buf = [0; 4096]; + + let num_read = stream.read(&mut buf)?; + if num_read == 0 { + break; + } + let buf = &buf[..num_read]; + let string = String::from_utf8(buf.to_vec())?; + let parsed: Vec = event_parser(string)?; + + for event in parsed { + self.event_primer(event, &mut active_windows)?; + } + } + + Ok(()) + } +} diff --git a/hyprland/src/event_listener/macros.rs b/hyprland/src/event_listener/macros.rs new file mode 100644 index 0000000..3601351 --- /dev/null +++ b/hyprland/src/event_listener/macros.rs @@ -0,0 +1,135 @@ +macro_rules! add_listener { + ($name:ident $end:ident,$f:ty,$c:literal,$c2:literal => $id:ident) => { + add_listener_reg!($name $end,$f,$c,$c2 => $id); + add_async_listener!($name $end,$f,$c,$c2 => $id); + }; + ($name:ident,$f:ty,$c:literal,$c2:literal => $id:ident) => { + add_listener_reg!($name,$f,$c,$c2 => $id); + add_async_listener!($name,$f,$c,$c2 => $id); + }; +} + +macro_rules! add_listener_reg { + ($name:ident $end:ident,$f:ty,$c:literal,$c2:expr => $id:ident) => { + paste! { + impl EventListener { + #[doc = concat!("This methods adds a event which ", stringify!($c), r#" +```rust, no_run +use hyprland::event_listener::EventListener; +let mut listener = EventListener::new(); +listener.add_"#, stringify!($name), r#"_handler(|"#, stringify!($id), r#"| println!(""#, $c2, ": {", stringify!($id), r#":#?}")); +listener.start_listener();"#)] + pub fn [](&mut self, f: impl Fn($f) + 'static) { + self.events.[<$name $end _events>].push(Box::new(f)); + } + } + } + }; + ($name:ident,$f:ty,$c:literal,$c2:expr => $id:ident) => { + paste! { + impl EventListener { + #[doc = concat!("This methods adds a event which executes when ", $c, r#" +```rust, no_run +use hyprland::event_listener::EventListener; +let mut listener = EventListener::new(); +listener.add_"#, stringify!($name), r#"_handler(|"#, stringify!($id), r#"| println!(""#, $c2, ": {", stringify!($id), r#":#?}")); +listener.start_listener();"#)] + pub fn [](&mut self, f: impl Fn($f) + 'static) { + self.events.[<$name _events>].push(Box::new(f)); + } + } + } + }; +} + +macro_rules! add_async_listener { + ($name:ident $end:ident,$f:ty,$c:literal,$c2:expr => $id:ident) => { + paste! { + impl AsyncEventListener { + #[doc = concat!("This methods adds a event which ", $c, r#" +```rust, no_run +use hyprland::event_listener::EventListener; +let mut listener = EventListener::new(); +listener.add_"#, stringify!($name), r#"_handler(|"#, stringify!($id), r#"| println!(""#, $c2, ": {", stringify!($id), r#":#?}")); +listener.start_listener();"#)] + pub fn [](&mut self, f: impl Fn($f) -> VoidFuture + Send + Sync + 'static) { + self.events.[<$name $end _events>].push(Box::pin(f)); + } + } + } + }; + ($name:ident,$f:ty,$c:literal,$c2:expr => $id:ident) => { + paste! { + impl AsyncEventListener { + #[doc = concat!("This methods adds a event which executes when ", $c, r#" +```rust, no_run +use hyprland::event_listener::EventListener; +let mut listener = EventListener::new(); +listener.add_"#, stringify!($name), r#"_handler(|"#, stringify!($id), r#"| println!(""#, $c2, ": {", stringify!($id), r#":#?}")); +listener.start_listener();"#)] + pub fn [](&mut self, f: impl Fn($f) -> VoidFuture + Send + Sync + 'static) { + self.events.[<$name _events>].push(Box::pin(f)); + } + } + } + }; +} + +#[allow(unused_macros)] +macro_rules! arm_alpha { + ($sync:ident; $val:expr,$nam:ident,$se:ident) => {{ + paste! { + let events = &$se.events.$nam; + for item in events.iter() { + [](item, $val); + } + } + }}; +} + +macro_rules! arm { + ($val:expr,$nam:ident,$se:ident) => {{ + let events = &$se.events.$nam; + for item in events.iter() { + execute_closure(item, $val.clone()); + } + }}; +} + +macro_rules! arm_async { + ($val:expr,$nam:ident,$se:ident) => {{ + let events = &$se.events.$nam; + for item in events.iter() { + execute_closure_async(item, $val.clone()).await; + } + }}; +} + +macro_rules! init_events { + ($name:ident) => { + $name { + workspace_changed_events: vec![], + workspace_added_events: vec![], + workspace_destroyed_events: vec![], + workspace_moved_events: vec![], + workspace_rename_events: vec![], + active_monitor_changed_events: vec![], + active_window_changed_events: vec![], + fullscreen_state_changed_events: vec![], + monitor_removed_events: vec![], + monitor_added_events: vec![], + window_open_events: vec![], + window_close_events: vec![], + window_moved_events: vec![], + keyboard_layout_change_events: vec![], + sub_map_changed_events: vec![], + layer_open_events: vec![], + layer_closed_events: vec![], + float_state_events: vec![], + urgent_state_events: vec![], + minimize_events: vec![], + window_title_changed_events: vec![], + screencast_events: vec![], + } + }; +} diff --git a/hyprland/src/event_listener/mod.rs b/hyprland/src/event_listener/mod.rs new file mode 100644 index 0000000..57545d8 --- /dev/null +++ b/hyprland/src/event_listener/mod.rs @@ -0,0 +1,36 @@ +#[macro_use] +mod macros; + +use crate::shared::*; + +mod shared; +pub use crate::event_listener::shared::*; + +mod immutable; +pub use crate::event_listener::immutable::EventListener; + +mod async_im; +pub use crate::event_listener::async_im::AsyncEventListener; + +add_listener!(workspace_change d, WorkspaceType, "on workspace change", "changed workspace to" => id); +add_listener!(workspace_added, WorkspaceType, "a workspace is created", "workspace was added" => id); +add_listener!(workspace_destroy ed, WorkspaceDestroyedEventData, "a workspace is destroyed", "a workspace was destroyed" => data); +add_listener!(workspace_moved, MonitorEventData, "a workspace is moved", "workspace was moved" => id); +add_listener!(workspace_rename, WorkspaceRenameEventData, "a workspace is renamed", "workspace was renamed" => id); +add_listener!(active_monitor_change d, MonitorEventData, "the active monitor is changed", "Active monitor changed to" => data); +add_listener!(active_window_change d, Option, "the active window is changed", "Active window changed" => data); +add_listener!(fullscreen_state_change d, bool, "the active monitor is changed", "Fullscreen is on" => state); +add_listener!(monitor_added, String, "a new monitor is added", "Monitor added" => data); +add_listener!(monitor_removed, String, "a monitor is removed", "Monitor removed" => data); +add_listener!(window_open, WindowOpenEvent, "a window is opened", "Window opened" => data); +add_listener!(window_close, Address, "a window is closed", "Window closed" => data); +add_listener!(window_moved, WindowMoveEvent, "a window is moved", "Window moved" => data); +add_listener!(keyboard_layout_change, LayoutEvent, "the keyboard layout is changed", "Layout changed" => data); +add_listener!(sub_map_change d, String, "the sub map is changed", "Submap changed" => data); +add_listener!(layer_open, String, "a new layer is opened", "Layer opened" => data); +add_listener!(layer_closed, String, "a layer is closed", "Layer closed" => data); +add_listener!(float_state, WindowFloatEventData, "the float state of a window is changed", "Float state changed" => data); +add_listener!(urgent_state, Address, "the urgent state of a window is changed", "urgent state changed" => data); +add_listener!(minimize, MinimizeEventData, "the minimize state of a window is changed", "minimize state changed" => data); +add_listener!(window_title_change d, Address, "a window title is changed", "A window title changed" => data); +add_listener!(screencast, ScreencastEventData, "the screencast state of a window is changed", "screencast state changed" => data); diff --git a/hyprland/src/event_listener/shared.rs b/hyprland/src/event_listener/shared.rs new file mode 100644 index 0000000..5855260 --- /dev/null +++ b/hyprland/src/event_listener/shared.rs @@ -0,0 +1,868 @@ +use crate::shared::*; +use once_cell::sync::Lazy; +use regex::{Error as RegexError, Regex}; +use std::{fmt::Debug, pin::Pin}; + +/// This trait provides shared behaviour for listener types +#[allow(dead_code)] +pub(crate) trait Listener: HasExecutor { + /// This method starts the event listener + fn start_listener() -> crate::Result<()>; +} + +/// This trait provides shared behaviour for listener types +#[allow(dead_code)] +pub(crate) trait AsyncListener: HasAsyncExecutor { + /// This method starts the event listener (async) + async fn start_listener_async() -> crate::Result<()>; +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) enum ActiveWindowValue { + Queued(T), // aka Some(T) + None, // No current window + Empty, // Empty queue +} + +impl ActiveWindowValue { + pub fn reset(&mut self) { + *self = Self::Empty; + } + pub fn is_empty(&self) -> bool { + matches!(self, Self::Empty) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct ActiveWindowState { + pub class: ActiveWindowValue, + pub title: ActiveWindowValue, + pub addr: ActiveWindowValue
, +} + +pub(crate) trait HasExecutor { + fn event_executor(&mut self, event: Event) -> crate::Result<()>; + + fn event_primer(&mut self, event: Event, abuf: &mut Vec) -> crate::Result<()> + where + Self: std::marker::Sized, + { + if abuf.is_empty() { + abuf.push(ActiveWindowState::new()); + } + if let Event::ActiveWindowChangedV1(data) = event { + let mut to_remove = vec![]; + let data = into(data); + for (index, awin) in abuf.iter_mut().enumerate() { + if awin.title.is_empty() && awin.class.is_empty() { + (awin.class, awin.title) = data.clone(); + } + if awin.ready() { + awin.execute(self)?; + to_remove.push(index); + break; + } + } + for index in to_remove.into_iter().rev() { + abuf.swap_remove(index); + } + } else if let Event::ActiveWindowChangedV2(data) = event { + let mut to_remove = vec![]; + for (index, awin) in abuf.iter_mut().enumerate() { + if awin.addr.is_empty() { + awin.addr = data.clone().into(); + } + if awin.ready() { + awin.execute(self)?; + to_remove.push(index); + break; + } + } + for index in to_remove.into_iter().rev() { + abuf.swap_remove(index); + } + } else { + self.event_executor(event)?; + } + Ok(()) + } +} + +pub(crate) trait HasAsyncExecutor { + async fn event_executor_async(&mut self, event: Event) -> crate::Result<()>; + + async fn event_primer_async( + &mut self, + event: Event, + abuf: &mut Vec, + ) -> crate::Result<()> + where + Self: std::marker::Sized, + { + if abuf.is_empty() { + abuf.push(ActiveWindowState::new()); + } + if let Event::ActiveWindowChangedV1(data) = event { + let mut to_remove = vec![]; + let data = into(data); + for (index, awin) in abuf.iter_mut().enumerate() { + if awin.title.is_empty() && awin.class.is_empty() { + (awin.class, awin.title) = data.clone(); + } + if awin.ready() { + awin.execute_async(self).await?; + to_remove.push(index); + break; + } + } + for index in to_remove.into_iter().rev() { + abuf.swap_remove(index); + } + } else if let Event::ActiveWindowChangedV2(data) = event { + let mut to_remove = vec![]; + for (index, awin) in abuf.iter_mut().enumerate() { + if awin.addr.is_empty() { + awin.addr = data.clone().into(); + } + if awin.ready() { + awin.execute_async(self).await?; + to_remove.push(index); + break; + } + } + for index in to_remove.into_iter().rev() { + abuf.swap_remove(index); + } + } else { + self.event_executor_async(event).await?; + } + Ok(()) + } +} + +impl ActiveWindowState { + pub fn execute(&mut self, listener: &mut T) -> crate::Result<()> { + use ActiveWindowValue::{None, Queued}; + let data = (&self.title, &self.class, &self.addr); + if let (Queued(ref title), Queued(ref class), Queued(ref addr)) = data { + listener.event_executor(Event::ActiveWindowChangedMerged(Some(WindowEventData { + window_class: class.to_string(), + window_title: title.to_string(), + window_address: addr.clone(), + })))?; + self.reset(); + } else if let (None, None, None) = data { + listener.event_executor(Event::ActiveWindowChangedMerged(Option::None))?; + } + Ok(()) + } + pub async fn execute_async( + &mut self, + listener: &mut T, + ) -> crate::Result<()> { + use ActiveWindowValue::{None, Queued}; + let data = (&self.title, &self.class, &self.addr); + if let (Queued(ref title), Queued(ref class), Queued(ref addr)) = data { + listener + .event_executor_async(Event::ActiveWindowChangedMerged(Some(WindowEventData { + window_class: class.to_string(), + window_title: title.to_string(), + window_address: addr.clone(), + }))) + .await?; + self.reset(); + } else if let (None, None, None) = data { + listener + .event_executor_async(Event::ActiveWindowChangedMerged(Option::None)) + .await?; + } + Ok(()) + } + + pub fn ready(&self) -> bool { + !self.class.is_empty() && !self.title.is_empty() && !self.addr.is_empty() + } + pub fn reset(&mut self) { + self.class.reset(); + self.title.reset(); + self.addr.reset(); + } + pub fn new() -> Self { + Self { + class: ActiveWindowValue::Empty, + title: ActiveWindowValue::Empty, + addr: ActiveWindowValue::Empty, + } + } +} + +impl From> for ActiveWindowValue { + fn from(value: Option) -> Self { + match value { + Some(v) => ActiveWindowValue::Queued(v), + None => ActiveWindowValue::None, + } + } +} + +pub(crate) fn into(from: Option<(T, T)>) -> (ActiveWindowValue, ActiveWindowValue) { + if let Some((first, second)) = from { + ( + ActiveWindowValue::Queued(first), + ActiveWindowValue::Queued(second), + ) + } else { + (ActiveWindowValue::None, ActiveWindowValue::None) + } +} + +pub(crate) type EventType = Box; +pub(crate) type AsyncEventType = Pin>; + +pub(crate) type VoidFuture = std::pin::Pin + Send>>; + +pub(crate) type Closure = EventType; +pub(crate) type AsyncClosure = AsyncEventType VoidFuture>; +pub(crate) type Closures = Vec>; +pub(crate) type AsyncClosures = Vec>; + +pub(crate) struct Events { + pub(crate) workspace_changed_events: Closures, + pub(crate) workspace_added_events: Closures, + pub(crate) workspace_destroyed_events: Closures, + pub(crate) workspace_moved_events: Closures, + pub(crate) workspace_rename_events: Closures, + pub(crate) active_monitor_changed_events: Closures, + pub(crate) active_window_changed_events: Closures>, + pub(crate) fullscreen_state_changed_events: Closures, + pub(crate) monitor_removed_events: Closures, + pub(crate) monitor_added_events: Closures, + pub(crate) keyboard_layout_change_events: Closures, + pub(crate) sub_map_changed_events: Closures, + pub(crate) window_open_events: Closures, + pub(crate) window_close_events: Closures
, + pub(crate) window_moved_events: Closures, + pub(crate) layer_open_events: Closures, + pub(crate) layer_closed_events: Closures, + pub(crate) float_state_events: Closures, + pub(crate) urgent_state_events: Closures
, + pub(crate) minimize_events: Closures, + pub(crate) window_title_changed_events: Closures
, + pub(crate) screencast_events: Closures, +} + +#[allow(clippy::type_complexity)] +pub(crate) struct AsyncEvents { + pub(crate) workspace_changed_events: AsyncClosures, + pub(crate) workspace_added_events: AsyncClosures, + pub(crate) workspace_destroyed_events: AsyncClosures, + pub(crate) workspace_moved_events: AsyncClosures, + pub(crate) workspace_rename_events: AsyncClosures, + pub(crate) active_monitor_changed_events: AsyncClosures, + pub(crate) active_window_changed_events: AsyncClosures>, + pub(crate) fullscreen_state_changed_events: AsyncClosures, + pub(crate) monitor_removed_events: AsyncClosures, + pub(crate) monitor_added_events: AsyncClosures, + pub(crate) keyboard_layout_change_events: AsyncClosures, + pub(crate) sub_map_changed_events: AsyncClosures, + pub(crate) window_open_events: AsyncClosures, + pub(crate) window_close_events: AsyncClosures
, + pub(crate) window_moved_events: AsyncClosures, + pub(crate) layer_open_events: AsyncClosures, + pub(crate) layer_closed_events: AsyncClosures, + pub(crate) float_state_events: AsyncClosures, + pub(crate) urgent_state_events: AsyncClosures
, + pub(crate) minimize_events: AsyncClosures, + pub(crate) window_title_changed_events: AsyncClosures
, + pub(crate) screencast_events: AsyncClosures, +} + +/// Event data for destroyworkspacev2 event +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WorkspaceDestroyedEventData { + /// Workspace Id + pub workspace_id: WorkspaceId, + /// Workspace name + pub workspace_name: String, +} + +/// Event data for renameworkspace event +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WorkspaceRenameEventData { + /// Workspace id + pub workspace_id: WorkspaceId, + /// Workspace name content + pub workspace_name: String, +} + +/// Event data for a minimize event +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MinimizeEventData { + /// Window address + pub window_address: Address, + /// whether it's minimized or not + pub is_minimized: bool, +} + +/// Event data for screencast event +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ScreencastEventData { + /// State/Is it turning on? + pub is_turning_on: bool, + /// Owner type, is it a monitor? + pub is_monitor: bool, +} + +/// The data for the event executed when moving a window to a new workspace +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct WindowMoveEvent { + /// Window address + pub window_address: Address, + /// The workspace name + pub workspace_name: String, +} + +/// The data for the event executed when opening a new window +#[derive(Clone, Debug)] +pub struct WindowOpenEvent { + /// Window address + pub window_address: Address, + /// The workspace name + pub workspace_name: String, + /// Window class + pub window_class: String, + /// Window title + pub window_title: String, +} + +/// The data for the event executed when changing keyboard layouts +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct LayoutEvent { + /// Keyboard name + pub keyboard_name: String, + /// Layout name + pub layout_name: String, +} + +/// The mutable state available to Closures +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct State { + /// The active workspace + pub active_workspace: WorkspaceType, + /// The active monitor + pub active_monitor: String, + /// The fullscreen state + pub fullscreen_state: bool, +} + +impl State { + /// Execute changes in state + pub async fn execute_state(self, old: State) -> crate::Result { + let state = self.clone(); + if self != old { + use crate::dispatch::{Dispatch, DispatchType}; + if old.fullscreen_state != state.fullscreen_state { + use crate::dispatch::FullscreenType; + Dispatch::call_async(DispatchType::ToggleFullscreen(FullscreenType::NoParam)) + .await?; + } + if old.active_workspace != state.active_workspace { + use crate::dispatch::WorkspaceIdentifierWithSpecial; + Dispatch::call_async(DispatchType::Workspace(match &state.active_workspace { + WorkspaceType::Regular(name) => WorkspaceIdentifierWithSpecial::Name(name), + WorkspaceType::Special(opt) => { + WorkspaceIdentifierWithSpecial::Special(match opt { + Some(name) => Some(name), + None => None, + }) + } + })) + .await?; + } + if old.active_monitor != state.active_monitor { + use crate::dispatch::MonitorIdentifier; + Dispatch::call_async(DispatchType::FocusMonitor(MonitorIdentifier::Name( + &state.active_monitor, + ))) + .await?; + }; + } + Ok(state) + } + /// Execute changes in state + pub fn execute_state_sync(self, old: State) -> crate::Result { + let state = self.clone(); + if self != old { + use crate::dispatch::{Dispatch, DispatchType}; + if old.fullscreen_state != state.fullscreen_state { + use crate::dispatch::FullscreenType; + Dispatch::call(DispatchType::ToggleFullscreen(FullscreenType::NoParam))?; + } + if old.active_workspace != state.active_workspace { + use crate::dispatch::WorkspaceIdentifierWithSpecial; + Dispatch::call(DispatchType::Workspace(match &state.active_workspace { + WorkspaceType::Regular(name) => WorkspaceIdentifierWithSpecial::Name(name), + WorkspaceType::Special(opt) => { + WorkspaceIdentifierWithSpecial::Special(match opt { + Some(name) => Some(name), + None => None, + }) + } + }))?; + } + if old.active_monitor != state.active_monitor { + use crate::dispatch::MonitorIdentifier; + Dispatch::call(DispatchType::FocusMonitor(MonitorIdentifier::Name( + &state.active_monitor, + )))?; + }; + } + Ok(state) + } +} + +pub(crate) fn execute_closure(f: &Closure, val: T) { + f(val); +} + +pub(crate) async fn execute_closure_async(f: &AsyncClosure, val: T) { + f(val).await; +} + +/// This tuple struct holds window event data +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WindowEventData { + /// The window class + pub window_class: String, + /// The window title + pub window_title: String, + /// The window address + pub window_address: Address, +} + +/// This tuple struct holds monitor event data +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MonitorEventData { + /// The monitor name + pub monitor_name: String, + /// The workspace + pub workspace: WorkspaceType, +} + +/// This tuple struct holds monitor event data +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WindowFloatEventData { + /// The window address + pub window_address: Address, + /// The float state + pub is_floating: bool, +} + +/// This enum holds every event type +#[derive(Debug, Clone)] +pub(crate) enum Event { + WorkspaceChanged(WorkspaceType), + WorkspaceDeleted(WorkspaceDestroyedEventData), + WorkspaceAdded(WorkspaceType), + WorkspaceMoved(MonitorEventData), + WorkspaceRename(WorkspaceRenameEventData), + ActiveWindowChangedV1(Option<(String, String)>), + ActiveWindowChangedV2(Option
), + ActiveWindowChangedMerged(Option), + ActiveMonitorChanged(MonitorEventData), + FullscreenStateChanged(bool), + MonitorAdded(String), + MonitorRemoved(String), + WindowOpened(WindowOpenEvent), + WindowClosed(Address), + WindowMoved(WindowMoveEvent), + LayoutChanged(LayoutEvent), + SubMapChanged(String), + LayerOpened(String), + LayerClosed(String), + FloatStateChanged(WindowFloatEventData), + UrgentStateChanged(Address), + Minimize(MinimizeEventData), + WindowTitleChanged(Address), + Screencast(ScreencastEventData), +} + +fn parse_string_as_work(str: String) -> WorkspaceType { + if str == "special" { + WorkspaceType::Special(None) + } else if str.starts_with("special:") { + { + let mut iter = str.split(':'); + iter.next(); + match iter.next() { + Some(name) => WorkspaceType::Special(Some(name.to_string())), + None => WorkspaceType::Special(None), + } + } + } else { + WorkspaceType::Regular(str) + } +} + +macro_rules! report_unknown { + ($event:expr) => { + #[cfg(not(feature = "silent"))] + eprintln!( + "An unknown event was passed into Hyprland-rs + PLEASE MAKE AN ISSUE!! + The event was: {event}", + event = $event + ); + }; +} + +#[cfg(feature = "ahash")] +use ahash::{HashSet, HashSetExt}; +#[cfg(not(feature = "ahash"))] +use std::collections::HashSet; + +#[cfg(feature = "parking_lot")] +use parking_lot::Mutex; +#[cfg(not(feature = "parking_lot"))] +use std::sync::Mutex; + +#[allow(dead_code)] +static CHECK_TABLE: Lazy>> = Lazy::new(|| Mutex::new(HashSet::new())); + +#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)] +enum ParsedEventType { + WorkspaceChanged, + WorkspaceDeletedV2, + WorkspaceAdded, + WorkspaceMoved, + WorkspaceRename, + ActiveWindowChangedV1, + ActiveWindowChangedV2, + ActiveMonitorChanged, + FullscreenStateChanged, + MonitorAdded, + MonitorRemoved, + WindowOpened, + WindowClosed, + WindowMoved, + LayoutChanged, + SubMapChanged, + LayerOpened, + LayerClosed, + FloatStateChanged, + UrgentStateChanged, + Minimize, + WindowTitleChanged, + Screencast, + Unknown, +} + +/// All the recognized events +static EVENT_SET: Lazy> = Lazy::new(|| { + [ + ( + ParsedEventType::WorkspaceChanged, + r"\bworkspace>>(?P.*)", + ), + ( + ParsedEventType::WorkspaceDeletedV2, + r"destroyworkspacev2>>(?P.*),(?P.*)", + ), + ( + ParsedEventType::WorkspaceAdded, + r"createworkspace>>(?P.*)", + ), + ( + ParsedEventType::WorkspaceMoved, + r"moveworkspace>>(?P.*),(?P.*)", + ), + ( + ParsedEventType::WorkspaceRename, + r"renameworkspace>>(?P.*),(?P.*)", + ), + ( + ParsedEventType::ActiveMonitorChanged, + r"focusedmon>>(?P.*),(?P.*)", + ), + ( + ParsedEventType::ActiveWindowChangedV1, + r"activewindow>>(?P.*?),(?P.*)", + ), + ( + ParsedEventType::ActiveWindowChangedV2, + r"activewindowv2>>(?P<address>.*)", + ), + ( + ParsedEventType::FullscreenStateChanged, + r"fullscreen>>(?P<state>0|1)", + ), + ( + ParsedEventType::MonitorRemoved, + r"monitorremoved>>(?P<monitor>.*)", + ), + ( + ParsedEventType::MonitorAdded, + r"monitoradded>>(?P<monitor>.*)", + ), + ( + ParsedEventType::WindowOpened, + r"openwindow>>(?P<address>.*),(?P<workspace>.*),(?P<class>.*),(?P<title>.*)", + ), + ( + ParsedEventType::WindowClosed, + r"closewindow>>(?P<address>.*)", + ), + ( + ParsedEventType::WindowMoved, + r"movewindow>>(?P<address>.*),(?P<workspace>.*)", + ), + ( + ParsedEventType::LayoutChanged, + r"activelayout>>(?P<keyboard>.*)(?P<layout>.*)", + ), + (ParsedEventType::SubMapChanged, r"submap>>(?P<submap>.*)"), + ( + ParsedEventType::LayerOpened, + r"openlayer>>(?P<namespace>.*)", + ), + ( + ParsedEventType::LayerClosed, + r"closelayer>>(?P<namespace>.*)", + ), + ( + ParsedEventType::FloatStateChanged, + r"changefloatingmode>>(?P<address>.*),(?P<floatstate>[0-1])", + ), + ( + ParsedEventType::Minimize, + r"minimize>>(?P<address>.*),(?P<state>[0-1])", + ), + ( + ParsedEventType::Screencast, + r"screencast>>(?P<state>[0-1]),(?P<owner>[0-1])", + ), + ( + ParsedEventType::UrgentStateChanged, + r"urgent>>(?P<address>.*)", + ), + ( + ParsedEventType::WindowTitleChanged, + r"windowtitle>>(?P<address>.*)", + ), + (ParsedEventType::Unknown, r"(?P<Event>^[^>]*)"), + ].into_iter() + .map(|(e, r)| ( + e, + match Regex::new(r) { + Ok(value) => value, + Err(e) => { + // I believe that panics here are fine because the chances of the library user finding them are extremely high + // This check does occur at runtime though... + eprintln!("An internal error occured in hyprland-rs while parsing regex! Please open an issue!"); + match e { + RegexError::Syntax(str) => panic!("Regex syntax error: {str}"), + RegexError::CompiledTooBig(size) => { + panic!("The compiled regex size is too big! ({size})") + } + _ => panic!("Error compiling regex: {e}"), + } + } + }) + ).collect() +}); + +/// This internal function parses event strings +pub(crate) fn event_parser(event: String) -> crate::Result<Vec<Event>> { + // TODO: Optimize nested looped regex capturing. Maybe pull in rayon if possible. + let event_iter = event + .trim() + .lines() + .map(|event_line| { + let type_matches = EVENT_SET + .iter() + .filter_map(|(event_type, regex)| Some((event_type, regex.captures(event_line)?))) + .collect::<Vec<_>>(); + + (event_line, type_matches) + }) + .filter(|(_, b)| !b.is_empty()); + + let mut temp_event_holder = Vec::new(); + + for (event_str, matches) in event_iter { + match matches.len() { + 0 => hypr_err!( + "A Hyprland event that has no regex matches was passed! Please file a bug report!" + ), + 1 => { + report_unknown!((event_str.split('>').next().unwrap_or("unknown"))); + continue; + } + 2 => { + let (event_type, captures) = match matches + .into_iter() + .find(|(e, _)| **e != ParsedEventType::Unknown) { + Some(t) => t, + None => hypr_err!("The only events captured were unknown Hyprland events! Please file a bug report!"), + }; + + temp_event_holder.push((event_str, event_type, captures)); + } + _ => { + hypr_err!("Event matched more than one regex (not an unknown event issue!)"); + } + } + } + + let parsed_events = temp_event_holder + .into_iter() + .map(|(event_str, event_type, captures)| match event_type { + ParsedEventType::WorkspaceChanged => { + let captured = &captures["workspace"]; + let workspace = if !captured.is_empty() { + parse_string_as_work(captured.to_string()) + } else { + WorkspaceType::Regular("1".to_string()) + }; + Ok(Event::WorkspaceChanged(workspace)) + } + ParsedEventType::WorkspaceDeletedV2 => Ok(Event::WorkspaceDeleted(WorkspaceDestroyedEventData { workspace_id: captures["id"].parse::<WorkspaceId>().map_err(|e| HyprError::Internal(format!("Workspace delete v2: invalid integer error: {e}")))?, workspace_name: captures["name"].to_string() })), + ParsedEventType::WorkspaceAdded => Ok(Event::WorkspaceAdded(parse_string_as_work( + captures["workspace"].to_string(), + ))), + ParsedEventType::WorkspaceMoved => Ok(Event::WorkspaceMoved(MonitorEventData { + monitor_name: captures["monitor"].to_string(), + workspace: parse_string_as_work(captures["workspace"].to_string()), + })), + ParsedEventType::WorkspaceRename => { + Ok(Event::WorkspaceRename(WorkspaceRenameEventData { + workspace_id: captures["id"] + .parse::<WorkspaceId>() + .map_err(|e| HyprError::Internal(format!("Workspace rename: invalid integer error: {e}")))?, + workspace_name: captures["name"].to_string(), + })) + } + ParsedEventType::ActiveMonitorChanged => { + Ok(Event::ActiveMonitorChanged(MonitorEventData { + monitor_name: captures["monitor"].to_string(), + workspace: WorkspaceType::Regular(captures["workspace"].to_string()), + })) + } + ParsedEventType::ActiveWindowChangedV1 => { + let class = &captures["class"]; + let title = &captures["title"]; + let event = if !class.is_empty() && !title.is_empty() { + Event::ActiveWindowChangedV1(Some((class.to_string(), title.to_string()))) + } else { + Event::ActiveWindowChangedV1(None) + }; + + Ok(event) + } + ParsedEventType::ActiveWindowChangedV2 => { + let addr = &captures["address"]; + let event = if addr != "," { + Event::ActiveWindowChangedV2(Some(Address::fmt_new(addr))) + } else { + Event::ActiveWindowChangedV2(None) + }; + Ok(event) + } + ParsedEventType::FullscreenStateChanged => { + let state = &captures["state"] != "0"; + Ok(Event::FullscreenStateChanged(state)) + } + ParsedEventType::MonitorRemoved => { + Ok(Event::MonitorRemoved(captures["monitor"].to_string())) + } + ParsedEventType::MonitorAdded => { + Ok(Event::MonitorAdded(captures["monitor"].to_string())) + } + ParsedEventType::WindowOpened => Ok(Event::WindowOpened(WindowOpenEvent { + window_address: Address::fmt_new(&captures["address"]), + workspace_name: captures["workspace"].to_string(), + window_class: captures["class"].to_string(), + window_title: captures["title"].to_string(), + })), + ParsedEventType::WindowClosed => Ok(Event::WindowClosed(Address::fmt_new(&captures["address"]))), + ParsedEventType::WindowMoved => Ok(Event::WindowMoved(WindowMoveEvent { + window_address: Address::fmt_new(&captures["address"]), + workspace_name: captures["workspace"].to_string(), + })), + ParsedEventType::LayoutChanged => Ok(Event::LayoutChanged(LayoutEvent { + keyboard_name: captures["keyboard"].to_string(), + layout_name: captures["layout"].to_string(), + })), + ParsedEventType::SubMapChanged => { + Ok(Event::SubMapChanged(captures["submap"].to_string())) + } + ParsedEventType::LayerOpened => { + Ok(Event::LayerOpened(captures["namespace"].to_string())) + } + ParsedEventType::LayerClosed => { + Ok(Event::LayerClosed(captures["namespace"].to_string())) + } + ParsedEventType::FloatStateChanged => { + let state = &captures["floatstate"] == "0"; // FIXME: does 0 mean it's floating? + Ok(Event::FloatStateChanged(WindowFloatEventData { + window_address: Address::fmt_new(&captures["address"]), + is_floating: state, + })) + } + ParsedEventType::Minimize => { + let state = &captures["state"] == "1"; + Ok(Event::Minimize(MinimizeEventData { + window_address: Address::fmt_new(&captures["address"]), + is_minimized: state, + })) + } + ParsedEventType::Screencast => { + let state = &captures["state"] == "1"; + let owner = &captures["owner"] == "1"; + Ok(Event::Screencast(ScreencastEventData { + is_turning_on: state, + is_monitor: owner, + })) + } + ParsedEventType::UrgentStateChanged => Ok(Event::UrgentStateChanged(Address::fmt_new(&captures["address"]))), + ParsedEventType::WindowTitleChanged => Ok(Event::WindowTitleChanged(Address::fmt_new(&captures["address"]))), + ParsedEventType::Unknown => { + #[cfg(not(feature = "silent"))] + { + let table = CHECK_TABLE.lock(); + // The std mutex returns a Result, the parking_lot mutex does not. This is a hack that allows us to + // keep the table code how it is, without duplicating or `return`ing. + #[cfg(feature = "parking_lot")] + let table = Ok::<_, std::convert::Infallible>(table); + + if let Ok(mut tbl) = table { + let (event_string, print_str) = + match captures.name("event").map(|s| s.as_str()) { + Some(s) => (s.to_string(), s), + None => ("Unknown".to_owned(), event_str), + }; + + let should_run = tbl.insert(event_string); + if should_run { + eprintln!( + "An unknown event was passed into Hyprland-rs\nPLEASE MAKE AN ISSUE!!\nThe event was: {print_str}" + ); + } + } + } + hypr_err!("Unknown event: {event_str}"); + } + }); + + let mut events: Vec<Event> = Vec::new(); + + for event in parsed_events { + events.push(event?); + } + + // if events.is_empty() { + // hypr_err!("No events!"); + // } + + Ok(events) +} diff --git a/hyprland/src/keyword.rs b/hyprland/src/keyword.rs new file mode 100644 index 0000000..51f7ced --- /dev/null +++ b/hyprland/src/keyword.rs @@ -0,0 +1,179 @@ +//! # Keyword module +//! +//! This module is used for setting, and getting keywords +//! +//! ## Usage +//! +//! ```rust, no_run +//! use hyprland::shared::HResult; +//! use hyprland::keyword::Keyword; +//! fn main() -> HResult<()> { +//! Keyword::get("some_keyword")?; +//! Keyword::set("another_keyword", "the value to set it to")?; +//! +//! Ok(()) +//! } +//! ``` + +use crate::shared::*; +use derive_more::Display; +use num_traits::AsPrimitive; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub(crate) struct OptionRaw { + pub option: String, + pub int: Option<i64>, + pub float: Option<f64>, + pub str: Option<String>, + pub set: bool, +} + +/// This enum holds the possible values of a keyword/option +#[derive(Serialize, Deserialize, Debug, Clone, Display)] +pub enum OptionValue { + /// A integer (64-bit) + #[display(fmt = "{}", "_0")] + Int(i64), + /// A floating point (64-point) + #[display(fmt = "{}", "_0")] + Float(f64), + /// A string + #[display(fmt = "{}", "_0")] + String(String), +} + +impl From<OptionValue> for String { + fn from(opt: OptionValue) -> Self { + opt.to_string() + } +} + +trait IsString {} +impl IsString for String {} +impl IsString for &str {} + +impl<Str: ToString + IsString> From<Str> for OptionValue { + fn from(str: Str) -> Self { + OptionValue::String(str.to_string()) + } +} + +macro_rules! ints_to_opt { + ($($ty:ty), *) => { + $( + impl From<$ty> for OptionValue { + fn from(num: $ty) -> Self { + OptionValue::Int(num.as_()) + } + } + )* + }; +} + +ints_to_opt!(u8, i8, u16, i16, u32, i32, u64, i64); + +macro_rules! floats_to_opt { + ($($ty:ty),*) => { + $( + impl From<$ty> for OptionValue { + fn from(num: $ty) -> Self { + OptionValue::Float(num.as_()) + } + } + )* + }; +} + +floats_to_opt!(f32, f64); + +/// This struct holds a keyword +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Keyword { + /// The identifier (or name) of the keyword + pub option: String, + /// The value of the keyword/option + pub value: OptionValue, + /// Is value overriden or not + pub set: bool, +} + +macro_rules! keyword { + ($k:tt,$v:tt) => { + CommandContent { + flag: CommandFlag::Empty, + data: format!("keyword {} {}", $k, $v), + } + }; + (g $l:tt) => { + CommandContent { + flag: CommandFlag::JSON, + data: format!("getoption {}", $l), + } + }; +} + +impl Keyword { + fn parse_opts( + OptionRaw { + option, + int, + float, + str, + set, + }: OptionRaw, + ) -> crate::Result<Keyword> { + let int_exists = int.is_some() as u8; + let float_exists = float.is_some() as u8; + let str_exists = str.is_some() as u8; + + // EXPLANATION: if at least two types of value is exists then we stop execution. + if int_exists + float_exists + str_exists > 1 { + hypr_err!("Expected single value type, but received more than one! Please open an issue with hyprland-rs with the information: Option {{ option: {option}, int: {int:?}, float: {float:?}, str: {str:?}, set: {set} }}!"); + } + + let value = match (int, float, str) { + (Some(int), _, _) => OptionValue::Int(int), + (_, Some(float), _) => OptionValue::Float(float), + (_, _, Some(str)) => OptionValue::String(str), + (int, float, str) => hypr_err!("Expected either an 'int', a 'float' or a 'str', but none of them is not received! Please open an issue with hyprland-rs with the information: Option {{ option: {option}, int: {int:?}, float: {float:?}, str: {str:?}, set: {set} }}!"), + }; + + Ok(Keyword { option, value, set }) + } + + /// This function sets a keyword's value + pub fn set<Str: ToString, Opt: Into<OptionValue>>(key: Str, value: Opt) -> crate::Result<()> { + let _ = write_to_socket_sync( + SocketType::Command, + keyword!((key.to_string()), (value.into().to_string())), + )?; + Ok(()) + } + /// This function sets a keyword's value (async) + pub async fn set_async<Str: ToString, Opt: Into<OptionValue>>( + key: Str, + value: Opt, + ) -> crate::Result<()> { + let _ = write_to_socket( + SocketType::Command, + keyword!((key.to_string()), (value.into().to_string())), + ) + .await?; + Ok(()) + } + /// This function returns the value of a keyword + pub fn get<Str: ToString>(key: Str) -> crate::Result<Self> { + let data = write_to_socket_sync(SocketType::Command, keyword!(g(key.to_string())))?; + let deserialized: OptionRaw = serde_json::from_str(&data)?; + let keyword = Keyword::parse_opts(deserialized)?; + Ok(keyword) + } + /// This function returns the value of a keyword (async) + pub async fn get_async<Str: ToString>(key: Str) -> crate::Result<Self> { + let data = write_to_socket(SocketType::Command, keyword!(g(key.to_string()))).await?; + let deserialized: OptionRaw = serde_json::from_str(&data)?; + let keyword = Keyword::parse_opts(deserialized)?; + Ok(keyword) + } +} diff --git a/hyprland/src/lib.rs b/hyprland/src/lib.rs new file mode 100644 index 0000000..cdcad2c --- /dev/null +++ b/hyprland/src/lib.rs @@ -0,0 +1,73 @@ +#![deny(clippy::unwrap_used)] +#![deny(clippy::expect_used)] +#![allow(async_fn_in_trait)] +#![cfg_attr(feature = "unsafe-impl", allow(unsafe_code))] +#![cfg_attr(not(feature = "unsafe-impl"), forbid(unsafe_code))] + +#[macro_use] +extern crate paste; + +pub use hyprland_macros::*; + +/// This module provides several impls that are unsafe, for FFI purposes. Only use if you know what you are doing. +#[cfg(feature = "unsafe-impl")] +pub mod unsafe_impl; + +/// This module provides shared things throughout the crate +pub mod shared; + +/// This module provides functions for getting information on the compositor +#[cfg(feature = "data")] +pub mod data; + +/// This module provides the EventListener struct for listening and acting upon for events +#[cfg(feature = "listener")] +pub mod event_listener; + +/// This module is for calling dispatchers and changing keywords +#[cfg(feature = "dispatch")] +pub mod dispatch; + +/// This module is for calling hyprctl **commands**, for getting data use [data] +#[cfg(feature = "ctl")] +pub mod ctl; + +/// This module provides the stuff needed to mutate, and read Hyprland config values +#[cfg(feature = "keyword")] +pub mod keyword; + +/// This module provides helpers to easily config Hyprland +#[cfg(feature = "config")] +pub mod config; + +/// The prelude module, this is to import all traits +pub mod prelude { + pub use crate::shared::{HyprData, HyprDataActive, HyprDataActiveOptional, HyprDataVec}; + pub use hyprland_macros::async_closure; +} + +pub(crate) mod unix_async { + #[cfg(all(feature = "async-lite", not(feature = "tokio")))] + pub use async_net::unix::UnixStream; + #[cfg(all(feature = "async-lite", not(feature = "tokio")))] + pub use futures_lite::io::{AsyncReadExt, AsyncWriteExt}; + + #[cfg(all( + feature = "async-std", + not(feature = "tokio"), + not(feature = "async-lite") + ))] + pub use async_std::{ + io::{ReadExt, WriteExt}, + os::unix::net::UnixStream, + }; + + #[cfg(feature = "tokio")] + pub use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::UnixStream, + }; +} + +/// This type provides the result type used everywhere in Hyprland-rs +pub type Result<T> = std::result::Result<T, shared::HyprError>; diff --git a/hyprland/src/shared.rs b/hyprland/src/shared.rs new file mode 100644 index 0000000..f9ec13e --- /dev/null +++ b/hyprland/src/shared.rs @@ -0,0 +1,443 @@ +//! # The Shared Module +//! +//! This module provides shared private and public functions, structs, enum, and types +use derive_more::Display; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; +use std::env::{var, VarError}; +use std::hash::{Hash, Hasher}; +use std::path::PathBuf; +use std::{error, fmt, io}; + +#[derive(Debug, derive_more::Display)] +/// Error that unifies different error types used by Hyprland-rs +pub enum HyprError { + /// Error coming from serde + #[display(format = "{_0}")] + SerdeError(serde_json::Error), + /// Error coming from std::io + #[display(format = "{_0}")] + IoError(io::Error), + /// Error that occurs when parsing UTF-8 string + #[display(format = "{_0}")] + FromUtf8Error(std::string::FromUtf8Error), + /// Dispatcher returned non `ok` value + #[display(format = "A dispatcher returned a non-`ok`, value which is probably an error: {_0}")] + NotOkDispatch(String), + /// Internal Hyprland error + Internal(String), + /// Error that occurs for other reasons. Avoid using this. + #[display(format = "{_0}")] + Other(String), +} +impl HyprError { + /// Try to get an owned version of the internal error. + /// + /// Some dependencies of hyprland do not impl Clone in their error types. This is a partial workaround. + /// + /// If it succeeds, it returns the owned version of HyprError in Ok(). Otherwise, it returns a reference to the error type. + pub fn try_as_cloned(&self) -> Result<Self, &Self> { + match self { + Self::SerdeError(_) => Err(self), + Self::IoError(_) => Err(self), + Self::FromUtf8Error(e) => Ok(Self::FromUtf8Error(e.clone())), + Self::NotOkDispatch(s) => Ok(Self::NotOkDispatch(s.clone())), + Self::Internal(s) => Ok(Self::Internal(s.clone())), + Self::Other(s) => Ok(Self::Other(s.clone())), + } + } + /// Create a Hyprland error with dynamic data. + #[inline(always)] + pub fn other<S: Into<String>>(other: S) -> Self { + Self::Other(other.into()) + } +} + +impl From<io::Error> for HyprError { + fn from(error: io::Error) -> Self { + HyprError::IoError(error) + } +} + +impl From<serde_json::Error> for HyprError { + fn from(error: serde_json::Error) -> Self { + HyprError::SerdeError(error) + } +} + +impl From<std::string::FromUtf8Error> for HyprError { + fn from(error: std::string::FromUtf8Error) -> Self { + HyprError::FromUtf8Error(error) + } +} + +impl error::Error for HyprError {} + +/// Internal macro to return a Hyprland error +macro_rules! hypr_err { + ($fmt:literal) => { + return Err($crate::shared::HyprError::Internal(format!($fmt))) + }; + (other $fmt:literal) => { + return Err($crate::shared::HyprError::Other(format!($fmt))) + }; + ($fmt:literal $(, $value:expr)+) => { + return Err($crate::shared::HyprError::Internal(format!($fmt $(, $value)+))) + }; + (other $fmt:literal $(, $value:expr)+) => { + return Err($crate::shared::HyprError::Other(format!($fmt $(, $value)+))) + }; +} + +pub(crate) use hypr_err; + +/// This type provides the result type used everywhere in Hyprland-rs +#[deprecated(since = "0.3.1", note = "New location: hyprland::Result")] +pub type HResult<T> = Result<T, HyprError>; + +/// The address struct holds a address as a tuple with a single value +/// and has methods to reveal the address in different data formats +#[derive( + Debug, Deserialize, Serialize, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, derive_more::Display, +)] +pub struct Address(String); +impl Address { + #[inline(always)] + pub(crate) fn fmt_new(address: &str) -> Self { + // this way is faster than std::fmt + Self("0x".to_owned() + address) + } + /// This creates a new address from a value that implements [std::string::ToString] + pub fn new<T: ToString>(string: T) -> Self { + Self(string.to_string()) + } +} + +/// This trait provides a standardized way to get data +pub trait HyprData { + /// This method gets the data + fn get() -> crate::Result<Self> + where + Self: Sized; + /// This method gets the data (async) + async fn get_async() -> crate::Result<Self> + where + Self: Sized; +} + +/// Trait for helper functions to get the active of the implementor +pub trait HyprDataActive { + /// This method gets the active data + fn get_active() -> crate::Result<Self> + where + Self: Sized; + /// This method gets the active data (async) + async fn get_active_async() -> crate::Result<Self> + where + Self: Sized; +} + +/// Trait for helper functions to get the active of the implementor, but for optional ones +pub trait HyprDataActiveOptional { + /// This method gets the active data + fn get_active() -> crate::Result<Option<Self>> + where + Self: Sized; + /// This method gets the active data (async) + async fn get_active_async() -> crate::Result<Option<Self>> + where + Self: Sized; +} + +/// This trait provides a standardized way to get data in a from of a vector +pub trait HyprDataVec<T>: HyprData { + /// This method returns a vector of data + fn to_vec(self) -> Vec<T>; +} + +/// This type provides the id used to identify workspaces +/// > its a type because it might change at some point +pub type WorkspaceId = i32; + +/// This type provides the id used to identify monitors +/// > its a type because it might change at some point +pub type MonitorId = i128; + +#[inline] +fn ser_spec_opt(opt: &Option<String>) -> String { + match opt { + Some(name) => "special:".to_owned() + name, + None => "special".to_owned(), + } +} + +/// This enum holds workspace data +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Display, PartialOrd, Ord)] +#[serde(untagged)] +pub enum WorkspaceType { + /// A named workspace + Regular( + /// The name + String, + ), + /// The special workspace + #[display(fmt = "{}", "ser_spec_opt(_0)")] + Special( + /// The name, if exists + Option<String>, + ), +} + +impl From<&WorkspaceType> for String { + fn from(value: &WorkspaceType) -> Self { + value.to_string() + } +} +macro_rules! from { + ($($ty:ty),+$(,)?) => { + $( + impl TryFrom<$ty> for WorkspaceType { + type Error = HyprError; + fn try_from(int: $ty) -> Result<Self, Self::Error> { + match int { + 1.. => Ok(WorkspaceType::Regular(int.to_string())), + _ => hypr_err!("Conversion error: Unrecognised id"), + } + } + } + )+ + }; +} +from![u8, u16, u32, u64, usize, i8, i16, i32, i64, isize]; + +impl Hash for WorkspaceType { + fn hash<H: Hasher>(&self, state: &mut H) { + match self { + WorkspaceType::Regular(name) => name.hash(state), + WorkspaceType::Special(value) => match value { + Some(name) => name.hash(state), + None => "".hash(state), + }, + } + } +} + +/// This pub(crate) function is used to write a value to a socket and to get the response +pub(crate) async fn write_to_socket( + ty: SocketType, + content: CommandContent, +) -> crate::Result<String> { + use crate::unix_async::*; + + let path = get_socket_path(ty)?; + let mut stream = UnixStream::connect(path).await?; + + stream.write_all(&content.as_bytes()).await?; + + let mut response = vec![]; + + const BUF_SIZE: usize = 8192; + let mut buf = [0; BUF_SIZE]; + loop { + let num_read = stream.read(&mut buf).await?; + let buf = &buf[..num_read]; + response.append(&mut buf.to_vec()); + if num_read == 0 || num_read != BUF_SIZE { + break; + } + } + + Ok(String::from_utf8(response)?) +} + +/// This pub(crate) function is used to write a value to a socket and to get the response +pub(crate) fn write_to_socket_sync( + ty: SocketType, + content: CommandContent, +) -> crate::Result<String> { + use io::prelude::*; + use std::os::unix::net::UnixStream; + + let path = get_socket_path(ty)?; + let mut stream = UnixStream::connect(path)?; + + stream.write_all(&content.as_bytes())?; + + let mut response = Vec::new(); + + const BUF_SIZE: usize = 8192; + let mut buf = [0; BUF_SIZE]; + loop { + let num_read = stream.read(&mut buf)?; + let buf = &buf[..num_read]; + response.append(&mut buf.to_vec()); + if num_read == 0 || num_read != BUF_SIZE { + break; + } + } + + Ok(String::from_utf8(response)?) +} + +/// This pub(crate) enum holds the different sockets that Hyprland has +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum SocketType { + /// The socket used to send commands to Hyprland (AKA `.socket.sock`) + Command, + /// The socket used to listen for events (AKA `.socket2.sock`) + Listener, +} +impl SocketType { + pub(crate) const fn socket_name(&self) -> &'static str { + match self { + Self::Command => ".socket.sock", + Self::Listener => ".socket2.sock", + } + } +} + +pub(crate) static COMMAND_SOCK: Lazy<crate::Result<PathBuf>> = + Lazy::new(|| init_socket_path(SocketType::Command)); +pub(crate) static LISTENER_SOCK: Lazy<crate::Result<PathBuf>> = + Lazy::new(|| init_socket_path(SocketType::Listener)); + +/// Get the socket path. According to benchmarks, this is faster than an atomic OnceCell. +pub(crate) fn get_socket_path(socket_type: SocketType) -> crate::Result<PathBuf> { + macro_rules! me { + ($var:expr) => { + match $var { + Ok(p) => Ok(p.clone()), + Err(e) => Err(match e.try_as_cloned() { + Ok(c) => c, + Err(e) => HyprError::Other(e.to_string()), + }), + } + }; + } + match socket_type { + SocketType::Command => me!(COMMAND_SOCK.as_ref()), + SocketType::Listener => me!(LISTENER_SOCK.as_ref()), + } +} + +fn init_socket_path(socket_type: SocketType) -> crate::Result<PathBuf> { + let instance = match var("HYPRLAND_INSTANCE_SIGNATURE") { + Ok(var) => var, + Err(VarError::NotPresent) => { + hypr_err!("Could not get socket path! (Is Hyprland running??)") + } + Err(VarError::NotUnicode(_)) => { + hypr_err!("Corrupted Hyprland socket variable: Invalid unicode!") + } + }; + + let mut p: PathBuf; + fn var_path(instance: String) -> Option<PathBuf> { + if let Ok(runtime_path) = var("XDG_RUNTIME_DIR") { + let mut buf = PathBuf::from(runtime_path); + buf.push("hypr"); + buf.push(instance); + if buf.exists() { + return Some(buf); + } + } + None + } + fn uid_path(instance: String) -> Option<PathBuf> { + if let Ok(uid) = var("UID") { + let mut buf = PathBuf::from("/run/user/".to_owned() + &uid); + buf.push("hypr"); + buf.push(instance); + if buf.exists() { + return Some(buf); + } + } + None + } + let old_buf = PathBuf::from("/tmp/hypr/".to_owned() + &instance); + if let Some(path) = var_path(instance.clone()) { + p = path; + } else if let Some(path) = uid_path(instance) { + p = path; + } else if old_buf.exists() { + p = old_buf; + } else { + hypr_err!("No xdg runtime path found!") + } + + p.push(socket_type.socket_name()); + Ok(p) +} + +/// Creates a `CommandContent` instance with the given flag and formatted data. +/// +/// # Arguments +/// +/// * `$flag` - A `CommandFlag` variant (`JSON` or `Empty`) that represents the flag for the command. +/// * `$($k:tt)*` - A format string and its arguments to be used as the data in the `CommandContent` instance. +#[macro_export] +macro_rules! command { + ($flag:ident, $($k:tt)*) => {{ + CommandContent { + flag: CommandFlag::$flag, + data: format!($($k)*), + } + }}; +} +pub use command; + +/// This enum defines the possible command flags that can be used. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum CommandFlag { + /// The JSON flag. + #[default] + JSON, + /// An empty flag. + Empty, +} + +/// This struct defines the content of a command, which consists of a flag and a data string. +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct CommandContent { + /// The flag for the command. + pub flag: CommandFlag, + /// The data string for the command. + pub data: String, +} + +impl CommandContent { + /// Converts the command content to a byte vector. + /// + /// # Examples + /// + /// ``` + /// use hyprland::shared::*; + /// + /// let content = CommandContent { flag: CommandFlag::JSON, data: "foo".to_string() }; + /// let bytes = content.as_bytes(); + /// assert_eq!(bytes, b"j/foo"); + /// ``` + pub fn as_bytes(&self) -> Vec<u8> { + self.to_string().into_bytes() + } +} + +impl fmt::Display for CommandContent { + /// Formats the command content as a string for display. + /// + /// # Examples + /// + /// ``` + /// use hyprland::shared::*; + /// + /// let content = CommandContent { flag: CommandFlag::JSON, data: "foo".to_string() }; + /// let s = format!("{}", content); + /// assert_eq!(s, "j/foo"); + /// ``` + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.flag { + CommandFlag::JSON => write!(f, "j/{}", &self.data), + CommandFlag::Empty => write!(f, "/{}", &self.data), + } + } +} diff --git a/hyprland/src/unsafe_impl.rs b/hyprland/src/unsafe_impl.rs new file mode 100644 index 0000000..4c24e2a --- /dev/null +++ b/hyprland/src/unsafe_impl.rs @@ -0,0 +1,34 @@ +//! This module provides unsafe impls for several types, mainly for FFI purposes. Do not use unless you know what you are doing. + +/// unsafe implementations for event listener structs +#[cfg(feature = "listener")] +pub mod listeners { + use crate::event_listener::*; + + unsafe impl Send for AsyncEventListener {} + unsafe impl Sync for AsyncEventListener {} + + unsafe impl Send for EventListener {} + unsafe impl Sync for EventListener {} + + unsafe impl Send for WindowMoveEvent {} + unsafe impl Sync for WindowMoveEvent {} + + unsafe impl Send for WindowOpenEvent {} + unsafe impl Sync for WindowOpenEvent {} + + unsafe impl Send for LayoutEvent {} + unsafe impl Sync for LayoutEvent {} + + unsafe impl Send for State {} + unsafe impl Sync for State {} + + unsafe impl Send for WindowEventData {} + unsafe impl Sync for WindowEventData {} + + unsafe impl Send for MonitorEventData {} + unsafe impl Sync for MonitorEventData {} + + unsafe impl Send for WindowFloatEventData {} + unsafe impl Sync for WindowFloatEventData {} +}