diff --git a/flake.nix b/flake.nix index 26c53bd..905ebf5 100644 --- a/flake.nix +++ b/flake.nix @@ -68,12 +68,21 @@ packages = with pkgs; [ nixos-anywhere + # Formatting tools alejandra + rustfmt treefmt mdl + # Secret management ssh-to-age sops + + # Rust toolchain + cargo + rustc + clippy + rust-analyzer ]; }; diff --git a/modules/home-manager/desktop_environment/components/sway.nix b/modules/home-manager/desktop_environment/components/sway.nix index 046968a..535ef1e 100644 --- a/modules/home-manager/desktop_environment/components/sway.nix +++ b/modules/home-manager/desktop_environment/components/sway.nix @@ -44,30 +44,14 @@ "${modifier}+comma" = "fullscreen"; "${modifier}+period" = "floating toggle"; - "${modifier}+a" = "workspace number 0"; - "${modifier}+s" = "workspace number 1"; - "${modifier}+d" = "workspace number 2"; - "${modifier}+f" = "workspace number 3"; - "${modifier}+x" = "workspace number 4"; - "${modifier}+c" = "workspace number 5"; - "${modifier}+v" = "workspace number 6"; - - "${modifier}+shift+a" = "move workspace number 0"; - "${modifier}+shift+s" = "move workspace number 1"; - "${modifier}+shift+d" = "move workspace number 2"; - "${modifier}+shift+f" = "move workspace number 3"; - "${modifier}+shift+x" = "move workspace number 4"; - "${modifier}+shift+c" = "move workspace number 5"; - "${modifier}+shift+v" = "move workspace number 6"; - "${modifier}+e" = "mode \"sink volume\""; "${modifier}+r" = "mode resize"; "${modifier}+space" = "exec alacritty"; - "${modifier}+semicolon" = "exec ${tofi-run} | ${xargs} swaymsg exec --"; - "${modifier}+p" = "exec ${de-screenshot}"; + "${modifier}+semicolon" = "exec alacritty --class sway-helper -e sway-helper combi"; + "XF86AudioRaiseVolume" = "exec --no-startup-id ${pactl} set-sink-volume @DEFAULT_SINK@ +5%"; "XF86AudioLowerVolume" = "exec --no-startup-id ${pactl} set-sink-volume @DEFAULT_SINK@ -5%"; "XF86AudioMute" = "exec --no-startup-id ${pactl} set-sink-mute @DEFAULT_SINK@ toggle"; @@ -201,7 +185,7 @@ floating.criteria = [ { - app_id = "FloatingAlacritty"; + app_id = "sway-helper"; } { instance = "qjackctl"; diff --git a/modules/home-manager/desktop_environment/default.nix b/modules/home-manager/desktop_environment/default.nix index 2beb99d..8aaf1b5 100644 --- a/modules/home-manager/desktop_environment/default.nix +++ b/modules/home-manager/desktop_environment/default.nix @@ -29,6 +29,8 @@ wdisplays wl-clipboard xdg-utils + + sway-helper ]; xdg = { diff --git a/overlays/pkgs/default.nix b/overlays/pkgs/default.nix index 1d01974..b5b91f1 100644 --- a/overlays/pkgs/default.nix +++ b/overlays/pkgs/default.nix @@ -3,4 +3,5 @@ de-screenshot = pkgs.callPackage ./de-screenshot {}; firefox-private = pkgs.callPackage ./firefox-private {}; media-control = pkgs.callPackage ./media-control {}; + sway-helper = pkgs.callPackage ./sway-helper {}; } diff --git a/overlays/pkgs/sway-helper/.gitignore b/overlays/pkgs/sway-helper/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/overlays/pkgs/sway-helper/.gitignore @@ -0,0 +1 @@ +target diff --git a/overlays/pkgs/sway-helper/Cargo.lock b/overlays/pkgs/sway-helper/Cargo.lock new file mode 100644 index 0000000..e63c5e2 --- /dev/null +++ b/overlays/pkgs/sway-helper/Cargo.lock @@ -0,0 +1,677 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim 0.11.1", +] + +[[package]] +name = "clap_derive" +version = "4.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.74", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_macro" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +dependencies = [ + "derive_builder_core", + "syn 1.0.109", +] + +[[package]] +name = "edit" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f364860e764787163c8c8f58231003839be31276e821e2ad2092ddf496b1aa09" +dependencies = [ + "tempfile", + "which", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fzf-wrapped" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c61a44d13f57f2bb4c181a380dbb2e0367d1af53ca6721b5c9fc6b9c7e345d" +dependencies = [ + "derive_builder", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.157" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374af5f94e54fa97cf75e945cce8a6b201e88a1a07e688b47dfd2a59c66dbd86" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +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 = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "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 = "serde" +version = "1.0.208" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.208" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.74", +] + +[[package]] +name = "serde_json" +version = "1.0.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "sway-helper" +version = "0.1.0" +dependencies = [ + "clap", + "colored", + "edit", + "fzf-wrapped", + "regex", + "swayipc", + "thiserror", +] + +[[package]] +name = "swayipc" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daa5d19f881f372e225095e297072e2e3ee1c4e9e3a46cafe5f5cf70f1313f29" +dependencies = [ + "serde", + "serde_json", + "swayipc-types", +] + +[[package]] +name = "swayipc-types" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e487a656336f74341c70a73a289f68d9ba3cab579ba776352ea0c6cdf603fcda" +dependencies = [ + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.74", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[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.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/overlays/pkgs/sway-helper/Cargo.toml b/overlays/pkgs/sway-helper/Cargo.toml new file mode 100644 index 0000000..ae7f2f0 --- /dev/null +++ b/overlays/pkgs/sway-helper/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "sway-helper" +version = "0.1.0" +edition = "2021" + +[dependencies] +clap = { version = "4.5.16", features = ["derive"] } +colored = "2.1.0" +edit = "0.1.5" +fzf-wrapped = "0.1.4" +regex = "1.10.6" +swayipc = "3.0.2" +thiserror = "1.0.63" diff --git a/overlays/pkgs/sway-helper/default.nix b/overlays/pkgs/sway-helper/default.nix new file mode 100644 index 0000000..0c6d5ee --- /dev/null +++ b/overlays/pkgs/sway-helper/default.nix @@ -0,0 +1,7 @@ +{rustPlatform}: +rustPlatform.buildRustPackage { + name = "sway-helper"; + + src = ./.; + cargoLock.lockFile = ./Cargo.lock; +} diff --git a/overlays/pkgs/sway-helper/src/commands/combi.rs b/overlays/pkgs/sway-helper/src/commands/combi.rs new file mode 100644 index 0000000..c3123e3 --- /dev/null +++ b/overlays/pkgs/sway-helper/src/commands/combi.rs @@ -0,0 +1,74 @@ +use super::CliRun; +use crate::helpers::{ + programs::list_programs_from_path, + sway::{ + focus_container, get_container_id_from_fzf_string, get_containers_fzf_strings, run_program, + }, +}; +use clap::Parser; +use colored::Colorize; +use regex::Regex; + +pub(crate) struct Combi {} + +const WIN: &str = "win"; +const CMD: &str = "cmd"; +const RUN: &str = "run"; + +impl CliRun for Combi { + fn run(&self, sway: &mut swayipc::Connection) -> crate::Result<()> { + let mut options = Vec::default(); + + // Get a list of windows/containers. + let tree = sway.get_tree()?; + let mut containers = Vec::new(); + get_containers_fzf_strings(&tree, None, &mut containers); + + options.extend(containers.iter().map(|i| format!("[{}] {i}", WIN.green()))); + + // Get a list of subcommands (not already covered by other included items). + options.extend( + [ + "new-workspace", + "new-named-workspace", + "rename-workspace", + "switch-to-workspace", + ] + .iter() + .map(|i| format!("[{}] {i}", CMD.yellow())), + ); + + // Get a list of all programs in PATH. + options.extend( + &mut list_programs_from_path() + .iter() + .map(|i| format!("[{}] {i}", RUN.blue())), + ); + + // Fuzzy select something. + let selected = crate::helpers::run_fzf("combi", options)?; + + // Extract info from the selected item. + let re = Regex::new(r"\[(\w+)\] (.+)$").expect("regex expression should be valid"); + + let invalid_input_error = || crate::Error::CombiInvalidInput(selected.clone()); + + let captures = re.captures(&selected).ok_or_else(invalid_input_error)?; + let (cmd, arg) = (&captures[1], &captures[2]); + + // Do the appropriate thing. + match cmd { + WIN => { + let container_id = get_container_id_from_fzf_string(arg)?; + focus_container(sway, &container_id) + } + CMD => { + let cli = + crate::Cli::try_parse_from(["", arg]).map_err(|_| invalid_input_error())?; + cli.command.run(sway) + } + RUN => run_program(sway, arg), + _ => Err(invalid_input_error()), + } + } +} diff --git a/overlays/pkgs/sway-helper/src/commands/focus_window.rs b/overlays/pkgs/sway-helper/src/commands/focus_window.rs new file mode 100644 index 0000000..e1e653c --- /dev/null +++ b/overlays/pkgs/sway-helper/src/commands/focus_window.rs @@ -0,0 +1,21 @@ +use super::CliRun; +use crate::helpers::sway::{ + focus_container, get_container_id_from_fzf_string, get_containers_fzf_strings, +}; + +pub(crate) struct FocusWindow {} + +impl CliRun for FocusWindow { + fn run(&self, sway: &mut swayipc::Connection) -> crate::Result<()> { + let tree = sway.get_tree()?; + + let mut items = Vec::new(); + get_containers_fzf_strings(&tree, None, &mut items); + + let selection = crate::helpers::run_fzf("window to focus", items)?; + + let container_id = get_container_id_from_fzf_string(&selection)?; + + focus_container(sway, &container_id) + } +} diff --git a/overlays/pkgs/sway-helper/src/commands/mod.rs b/overlays/pkgs/sway-helper/src/commands/mod.rs new file mode 100644 index 0000000..0df8ca9 --- /dev/null +++ b/overlays/pkgs/sway-helper/src/commands/mod.rs @@ -0,0 +1,45 @@ +mod combi; +mod focus_window; +mod new_workspace; +mod rename_workspace; +mod run; +mod switch_to_workspace; + +use self::{ + combi::Combi, + focus_window::FocusWindow, + new_workspace::{NewNamedWorkspace, NewWorkspace}, + rename_workspace::RenameWorkspace, + run::Run, + switch_to_workspace::SwitchToWorkspace, +}; +use clap::Subcommand; + +pub(crate) trait CliRun { + fn run(&self, sway: &mut swayipc::Connection) -> crate::Result<()>; +} + +#[derive(Debug, Subcommand)] +pub(crate) enum Command { + Combi, + Run, + NewWorkspace, + NewNamedWorkspace, + RenameWorkspace, + SwitchToWorkspace, + FocusWindow, +} + +impl CliRun for Command { + fn run(&self, sway: &mut swayipc::Connection) -> crate::Result<()> { + match self { + Self::Combi => Combi {}.run(sway), + Self::Run => Run {}.run(sway), + Self::NewWorkspace => NewWorkspace {}.run(sway), + Self::NewNamedWorkspace => NewNamedWorkspace {}.run(sway), + Self::RenameWorkspace => RenameWorkspace {}.run(sway), + Self::SwitchToWorkspace => SwitchToWorkspace {}.run(sway), + Self::FocusWindow => FocusWindow {}.run(sway), + } + } +} diff --git a/overlays/pkgs/sway-helper/src/commands/new_workspace.rs b/overlays/pkgs/sway-helper/src/commands/new_workspace.rs new file mode 100644 index 0000000..a285f93 --- /dev/null +++ b/overlays/pkgs/sway-helper/src/commands/new_workspace.rs @@ -0,0 +1,68 @@ +use super::CliRun; +use crate::helpers::edit_single_line; +use swayipc::{Node, NodeType}; + +pub(crate) struct NewWorkspace {} + +impl CliRun for NewWorkspace { + fn run(&self, sway: &mut swayipc::Connection) -> crate::Result<()> { + // Generate a default name for the workspace. + let name = generate_new_workspace_name(sway)?; + + // Create and focus the new workspace. + create_new_workspace(sway, &name) + } +} + +pub(crate) struct NewNamedWorkspace {} + +impl CliRun for NewNamedWorkspace { + fn run(&self, sway: &mut swayipc::Connection) -> crate::Result<()> { + // Generate a default name for the workspace. + let name = generate_new_workspace_name(sway)?; + + // Allow the user to edit it. + let name = edit_single_line(&name)?; + + // Create and focus the new workspace. + create_new_workspace(sway, &name) + } +} + +fn generate_new_workspace_name(sway: &mut swayipc::Connection) -> crate::Result { + let tree = sway.get_tree()?; + let mut workspaces = Vec::new(); + list_workspaces(&tree, &mut workspaces); + + // Determine the highest workspace number currently in use. + let highest_ws_number = workspaces.into_iter().flat_map(|ws| ws.number).max(); + + // Create a name for the new workspace that is the highest workspace number plus one, or + // zero if there are no numbered workspaces in use. + Ok(match highest_ws_number { + Some(num) => num + 1, + None => 0, + } + .to_string()) +} + +/// Create and focus the new workspace. +fn create_new_workspace(sway: &mut swayipc::Connection, name: &str) -> crate::Result<()> { + sway.run_command(format!("workspace \"{name}\""))?; + Ok(()) +} + +#[derive(Debug)] +struct WorkspaceDetails { + number: Option, +} + +fn list_workspaces(node: &Node, workspaces: &mut Vec) { + if node.node_type == NodeType::Workspace { + workspaces.push(WorkspaceDetails { number: node.num }); + } + + for child in node.nodes.iter() { + list_workspaces(child, workspaces); + } +} diff --git a/overlays/pkgs/sway-helper/src/commands/rename_workspace.rs b/overlays/pkgs/sway-helper/src/commands/rename_workspace.rs new file mode 100644 index 0000000..b65669f --- /dev/null +++ b/overlays/pkgs/sway-helper/src/commands/rename_workspace.rs @@ -0,0 +1,26 @@ +use super::CliRun; +use crate::helpers::{ + edit_single_line, + sway::{get_workspace_names, FocusedWorkspacePosition}, +}; + +pub(crate) struct RenameWorkspace {} + +impl CliRun for RenameWorkspace { + fn run(&self, sway: &mut swayipc::Connection) -> crate::Result<()> { + // Get a list of all the workspace names, with the focused workspace at the front (allowing + // fastest access to editing). + let workspace_names = get_workspace_names(sway, FocusedWorkspacePosition::Front)?; + + // Fuzzy select a workspace. + let selected = crate::helpers::run_fzf("workspace to rename", workspace_names)?; + + // Get a new name for the workspace. + let new_name = edit_single_line(&selected)?; + + // Rename the workspace. + sway.run_command(format!("rename workspace \"{selected}\" to \"{new_name}\""))?; + + Ok(()) + } +} diff --git a/overlays/pkgs/sway-helper/src/commands/run.rs b/overlays/pkgs/sway-helper/src/commands/run.rs new file mode 100644 index 0000000..c028e70 --- /dev/null +++ b/overlays/pkgs/sway-helper/src/commands/run.rs @@ -0,0 +1,17 @@ +use super::CliRun; +use crate::helpers::{programs::list_programs_from_path, sway::run_program}; + +pub(crate) struct Run {} + +impl CliRun for Run { + fn run(&self, sway: &mut swayipc::Connection) -> crate::Result<()> { + // Get a list of all programs in PATH. + let selection = list_programs_from_path(); + + // Fuzzy select a program. + let program = crate::helpers::run_fzf("run", selection)?; + + // Run the selected program. + run_program(sway, &program) + } +} diff --git a/overlays/pkgs/sway-helper/src/commands/switch_to_workspace.rs b/overlays/pkgs/sway-helper/src/commands/switch_to_workspace.rs new file mode 100644 index 0000000..df9a035 --- /dev/null +++ b/overlays/pkgs/sway-helper/src/commands/switch_to_workspace.rs @@ -0,0 +1,26 @@ +use super::CliRun; +use crate::helpers::sway::{get_workspace_names, FocusedWorkspacePosition}; + +pub(crate) struct SwitchToWorkspace {} + +impl CliRun for SwitchToWorkspace { + fn run(&self, sway: &mut swayipc::Connection) -> crate::Result<()> { + // Add "back_and_forth" at the start of the list to allow very fast switching between the + // last two focused workspaces. + let mut workspace_names = vec!["back_and_forth".to_string()]; + + // Add the names of all the workspaces, with the name of the current workspace at the back. + workspace_names.append(&mut get_workspace_names( + sway, + FocusedWorkspacePosition::Back, + )?); + + // Fuzzy select a workspace. + let selection = crate::helpers::run_fzf("workspace", workspace_names)?; + + // Switch to the selected workspace. + sway.run_command(format!("workspace {selection}"))?; + + Ok(()) + } +} diff --git a/overlays/pkgs/sway-helper/src/error.rs b/overlays/pkgs/sway-helper/src/error.rs new file mode 100644 index 0000000..fde839d --- /dev/null +++ b/overlays/pkgs/sway-helper/src/error.rs @@ -0,0 +1,25 @@ +#[derive(Debug, thiserror::Error)] +pub(crate) enum Error { + #[error("Sway IPC error: {0}")] + SwayIpc(#[from] swayipc::Error), + + #[error("Failed to run editor: {0}")] + Editor(std::io::Error), + + #[error("Failed to build Fzf wrapper: {0}")] + FzfBuilder(#[from] fzf_wrapped::FzfBuilderError), + + #[error("Failed to run Fzf: {0}")] + Fzf(std::io::Error), + + #[error("No selectoin has been made")] + NoSelectionMade, + + #[error("Invalid input provided in window focus mode: {0}")] + WindowInvalidInput(String), + + #[error("Invalid input provided in combi mode: {0}")] + CombiInvalidInput(String), +} + +pub(crate) type Result = std::result::Result; diff --git a/overlays/pkgs/sway-helper/src/helpers/mod.rs b/overlays/pkgs/sway-helper/src/helpers/mod.rs new file mode 100644 index 0000000..62ca6b4 --- /dev/null +++ b/overlays/pkgs/sway-helper/src/helpers/mod.rs @@ -0,0 +1,37 @@ +pub(crate) mod programs; +pub(crate) mod sway; + +use fzf_wrapped::FzfBuilder; + +pub(crate) fn run_fzf>>( + prompt: &str, + items: T, +) -> crate::Result { + let mut fzf = FzfBuilder::default() + .layout(fzf_wrapped::Layout::Reverse) + .prompt(format!("{prompt}> ")) + .cycle(true) + .custom_args(vec!["--bind=tab:down,shift-tab:up", "--ansi"]) + .build()?; + + fzf.run().map_err(crate::Error::Fzf)?; + + fzf.add_items(items).map_err(crate::Error::Fzf)?; + + match fzf.output() { + Some(selection) => { + if selection.trim().is_empty() { + Err(crate::Error::NoSelectionMade) + } else { + Ok(selection) + } + } + None => Err(crate::Error::NoSelectionMade), + } +} + +pub(crate) fn edit_single_line(s: &str) -> crate::Result { + let name = edit::edit(s).map_err(crate::Error::Editor)?; + let name = name.trim(); + Ok(name.to_string()) +} diff --git a/overlays/pkgs/sway-helper/src/helpers/programs.rs b/overlays/pkgs/sway-helper/src/helpers/programs.rs new file mode 100644 index 0000000..391bc6a --- /dev/null +++ b/overlays/pkgs/sway-helper/src/helpers/programs.rs @@ -0,0 +1,29 @@ +use std::{os::linux::fs::MetadataExt, path::Path}; + +pub(crate) fn list_programs_from_path() -> Vec { + std::env::var("PATH") + .expect("PATH environment variable should be set") + .split(':') + .flat_map(|path| match std::fs::read_dir(path) { + Ok(entries) => Some(entries.into_iter().flat_map(|e| e.ok()).collect::>()), + Err(_) => None, + }) + .flatten() + .flat_map(|entry| { + let path = entry.path(); + if path.is_file() && is_executable(&path) { + path.file_name() + .and_then(|v| v.to_str().map(|v| v.to_owned())) + } else { + None + } + }) + .collect() +} + +fn is_executable(path: &Path) -> bool { + match std::fs::metadata(path) { + Ok(meta) => (meta.st_mode() & 0o111) != 0, + Err(_) => false, + } +} diff --git a/overlays/pkgs/sway-helper/src/helpers/sway.rs b/overlays/pkgs/sway-helper/src/helpers/sway.rs new file mode 100644 index 0000000..323923d --- /dev/null +++ b/overlays/pkgs/sway-helper/src/helpers/sway.rs @@ -0,0 +1,108 @@ +use regex::Regex; +use std::cmp::Ordering; +use swayipc::{Node, NodeType}; + +pub(crate) fn run_program(sway: &mut swayipc::Connection, program: &str) -> crate::Result<()> { + sway.run_command(format!("exec \"{program}\""))?; + Ok(()) +} + +pub(crate) enum FocusedWorkspacePosition { + Front, + // Sorted, + Back, +} + +pub(crate) fn get_workspace_names( + sway: &mut swayipc::Connection, + focused: FocusedWorkspacePosition, +) -> crate::Result> { + let mut workspaces = sway.get_workspaces()?; + + workspaces.sort_by(|a, b| match focused { + FocusedWorkspacePosition::Front => { + if a.focused == b.focused { + Ordering::Equal + } else if a.focused { + Ordering::Less + } else { + Ordering::Greater + } + } + // FocusedWorkspacePosition::Sorted => Ordering::Equal, + FocusedWorkspacePosition::Back => { + if a.focused == b.focused { + Ordering::Equal + } else if a.focused { + Ordering::Greater + } else { + Ordering::Less + } + } + }); + + Ok(workspaces.into_iter().map(|i| i.name).collect()) +} + +pub(crate) fn get_containers_fzf_strings<'a>( + node: &'a Node, + mut parent_workspace: Option<&'a Node>, + strings: &mut Vec, +) { + if node.node_type == NodeType::Workspace { + parent_workspace = Some(node); + } + + if (node.node_type == NodeType::Con || node.node_type == NodeType::FloatingCon) + && node.nodes.is_empty() + { + let mut s = String::new(); + + if let Some(ws) = &parent_workspace { + if let Some(name) = &ws.name { + s.push_str(&format!("[{name}] ")); + } + } + + if let Some(app_id) = &node.app_id { + s.push_str(&format!("[{app_id}] ")); + } + + s.push_str(&format!( + "{} ", + match &node.name { + Some(name) => name, + None => "", + } + )); + + s.push_str(&format!("(id={})", node.id)); + + strings.push(s); + } + + for child in node.nodes.iter() { + get_containers_fzf_strings(child, parent_workspace, strings); + } + for child in node.floating_nodes.iter() { + get_containers_fzf_strings(child, parent_workspace, strings); + } +} + +pub(crate) fn get_container_id_from_fzf_string(s: &str) -> crate::Result { + let re = Regex::new(r".+ \(id=(\w+)\)$").expect("regex expression should be valid"); + + let captures = re + .captures(s) + .ok_or_else(|| crate::Error::WindowInvalidInput(s.to_string()))?; + + Ok(captures[1].to_string()) +} + +pub(crate) fn focus_container( + sway: &mut swayipc::Connection, + container_id: &str, +) -> crate::Result<()> { + sway.run_command(format!("[con_id={container_id}] focus"))?; + Ok(()) +} diff --git a/overlays/pkgs/sway-helper/src/main.rs b/overlays/pkgs/sway-helper/src/main.rs new file mode 100644 index 0000000..874e5f3 --- /dev/null +++ b/overlays/pkgs/sway-helper/src/main.rs @@ -0,0 +1,22 @@ +#[deny(clippy::unwrap_used)] +mod commands; +mod error; +mod helpers; + +use self::{ + commands::CliRun, + error::{Error, Result}, +}; +use clap::Parser; + +#[derive(Debug, Parser)] +struct Cli { + #[clap(subcommand)] + command: self::commands::Command, +} + +fn main() -> crate::Result<()> { + let cli = Cli::parse(); + let mut sway = swayipc::Connection::new()?; + cli.command.run(&mut sway) +}