From 07c9950cb8104c2f011561971d2414176360f4c9 Mon Sep 17 00:00:00 2001 From: Daniel Flanagan Date: Wed, 22 Jan 2025 11:56:29 -0600 Subject: [PATCH] Goofin' --- .../eww/scripts/hypr-workspaces.bash | 50 ++--- .../eww/scripts/workspaces/.gitignore | 2 + .../eww/scripts/workspaces/default.nix | 20 ++ .../eww/scripts/workspaces/src/.gitignore | 1 + .../eww/scripts/workspaces/src/Cargo.lock | 96 +++++++++ .../eww/scripts/workspaces/src/Cargo.toml | 19 ++ .../eww/scripts/workspaces/src/main.rs | 186 ++++++++++++++++++ modules/nixos/default.nix | 2 +- 8 files changed, 353 insertions(+), 23 deletions(-) create mode 100644 modules/home-manager/eww/scripts/workspaces/.gitignore create mode 100644 modules/home-manager/eww/scripts/workspaces/default.nix create mode 100644 modules/home-manager/eww/scripts/workspaces/src/.gitignore create mode 100644 modules/home-manager/eww/scripts/workspaces/src/Cargo.lock create mode 100644 modules/home-manager/eww/scripts/workspaces/src/Cargo.toml create mode 100644 modules/home-manager/eww/scripts/workspaces/src/main.rs diff --git a/modules/home-manager/eww/scripts/hypr-workspaces.bash b/modules/home-manager/eww/scripts/hypr-workspaces.bash index 66523dc..6cbd72e 100755 --- a/modules/home-manager/eww/scripts/hypr-workspaces.bash +++ b/modules/home-manager/eww/scripts/hypr-workspaces.bash @@ -1,34 +1,38 @@ #!/usr/bin/env bash # TODO: we're mixing bash arrays and not-arrays - get it together +declare -A OCCUPIED +declare -A ACTIVE +declare -A FOCUSED #define icons for workspaces 1-9 -ic=(1 2 3 4 5 6 7 8 9) +spaces=(1 2 3 4 5 6 7 8 9) +icons=(1 2 3 4 5 6 7 8 9) -occ() { export o"$1"="occupied"; } -unocc() { unset -v o"$1"; } +occupy() { export OCCUPIED["$1"]=occupied; } +unoccupy() { unset "OCCUPIED[$1]"; } -active() { export a"$1"="active"; } -unactive() { unset -v a"$1"; } +activate() { export ACTIVE["$1"]=active; } +deactivate() { unset "ACTIVE[$1]"; } -focus() { export f"$1"="focused"; } -unfocus() { unset -v f"$1"; } +focus() { export FOCUSED["$1"]=focused; } +unfocus() { unset "FOCUSED[$1]"; } workspaces() { - for num in 1 2 3 4 5 6 7 8 9; do - unfocus $num - unactive $num - unocc $num + for s in "${spaces[@]}"; do + unfocus "$s" + deactivate "$s" + unoccupy "$s" done # TODO: avoid recomputing these each time and actually listen to the events? mons_json=$(hyprctl monitors -j) for num in $(hyprctl workspaces -j | jq -r '.[] | select(.windows > 0) | .id'); do - occ "$num" + occupy "$num" done for num in $(echo "$mons_json" | jq -r '.[].activeWorkspace.id'); do - active "$num" + activate "$num" done for num in $(echo "$mons_json" | jq -r '.[] | select(.focused) | .activeWorkspace.id'); do @@ -38,21 +42,23 @@ workspaces() { # TODO: would be nice to have monitors' workspaces show up in left-to-right # order as laid out in physical/pixel space # this would make glancing at the workspace indicator more intuitive + # # TODO: might be nice to exclude certain windows as counting towards "occupation" such as xwaylandvideobridge or w/e + # # NOTE: maybe I can group workspaces by their monitor with some mechanism for "unassigned" workspace to show up by a "primary" monitor # render eww widget echo "(eventbox :onscroll \"echo {} | sed -e 's/up/-1/g' -e 's/down/+1/g' | xargs hyprctl dispatch workspace\" \ (box :class \"workspaces\" :orientation \"h\" :spacing 0 :space-evenly \"true\" \ - (button :onclick \"hyprctl dispatch workspace 1\" :onrightclick \"hyprctl dispatch workspace 1\" :class \"workspace $a1 $o1 $f1\" \"${ic[0]}\") \ - (button :onclick \"hyprctl dispatch workspace 2\" :onrightclick \"hyprctl dispatch workspace 2\" :class \"workspace $a2 $o2 $f2\" \"${ic[1]}\") \ - (button :onclick \"hyprctl dispatch workspace 3\" :onrightclick \"hyprctl dispatch workspace 3\" :class \"workspace $a3 $o3 $f3\" \"${ic[2]}\") \ - (button :onclick \"hyprctl dispatch workspace 4\" :onrightclick \"hyprctl dispatch workspace 4\" :class \"workspace $a4 $o4 $f4\" \"${ic[3]}\") \ - (button :onclick \"hyprctl dispatch workspace 5\" :onrightclick \"hyprctl dispatch workspace 5\" :class \"workspace $a5 $o5 $f5\" \"${ic[4]}\") \ - (button :onclick \"hyprctl dispatch workspace 6\" :onrightclick \"hyprctl dispatch workspace 6\" :class \"workspace $a6 $o6 $f6\" \"${ic[5]}\") \ - (button :onclick \"hyprctl dispatch workspace 7\" :onrightclick \"hyprctl dispatch workspace 7\" :class \"workspace $a7 $o7 $f7\" \"${ic[6]}\") \ - (button :onclick \"hyprctl dispatch workspace 8\" :onrightclick \"hyprctl dispatch workspace 8\" :class \"workspace $a8 $o8 $f8\" \"${ic[7]}\") \ - (button :onclick \"hyprctl dispatch workspace 9\" :onrightclick \"hyprctl dispatch workspace 9\" :class \"workspace $a9 $o9 $f9\" \"${ic[8]}\") \ + (button :onclick \"hyprctl dispatch workspace 1\" :onrightclick \"hyprctl dispatch workspace 1\" :class \"workspace ${ACTIVE[1]} ${OCCUPIED[1]} ${FOCUSED[1]}\" \"${icons[0]}\") \ + (button :onclick \"hyprctl dispatch workspace 2\" :onrightclick \"hyprctl dispatch workspace 2\" :class \"workspace ${ACTIVE[2]} ${OCCUPIED[2]} ${FOCUSED[2]}\" \"${icons[1]}\") \ + (button :onclick \"hyprctl dispatch workspace 3\" :onrightclick \"hyprctl dispatch workspace 3\" :class \"workspace ${ACTIVE[3]} ${OCCUPIED[3]} ${FOCUSED[3]}\" \"${icons[2]}\") \ + (button :onclick \"hyprctl dispatch workspace 4\" :onrightclick \"hyprctl dispatch workspace 4\" :class \"workspace ${ACTIVE[4]} ${OCCUPIED[4]} ${FOCUSED[4]}\" \"${icons[3]}\") \ + (button :onclick \"hyprctl dispatch workspace 5\" :onrightclick \"hyprctl dispatch workspace 5\" :class \"workspace ${ACTIVE[5]} ${OCCUPIED[5]} ${FOCUSED[5]}\" \"${icons[4]}\") \ + (button :onclick \"hyprctl dispatch workspace 6\" :onrightclick \"hyprctl dispatch workspace 6\" :class \"workspace ${ACTIVE[6]} ${OCCUPIED[6]} ${FOCUSED[6]}\" \"${icons[5]}\") \ + (button :onclick \"hyprctl dispatch workspace 7\" :onrightclick \"hyprctl dispatch workspace 7\" :class \"workspace ${ACTIVE[7]} ${OCCUPIED[7]} ${FOCUSED[7]}\" \"${icons[6]}\") \ + (button :onclick \"hyprctl dispatch workspace 8\" :onrightclick \"hyprctl dispatch workspace 8\" :class \"workspace ${ACTIVE[8]} ${OCCUPIED[8]} ${FOCUSED[8]}\" \"${icons[7]}\") \ + (button :onclick \"hyprctl dispatch workspace 9\" :onrightclick \"hyprctl dispatch workspace 9\" :class \"workspace ${ACTIVE[9]} ${OCCUPIED[9]} ${FOCUSED[9]}\" \"${icons[8]}\") \ ) \ )" } diff --git a/modules/home-manager/eww/scripts/workspaces/.gitignore b/modules/home-manager/eww/scripts/workspaces/.gitignore new file mode 100644 index 0000000..0bc0bbc --- /dev/null +++ b/modules/home-manager/eww/scripts/workspaces/.gitignore @@ -0,0 +1,2 @@ +./target +./result diff --git a/modules/home-manager/eww/scripts/workspaces/default.nix b/modules/home-manager/eww/scripts/workspaces/default.nix new file mode 100644 index 0000000..f485d1e --- /dev/null +++ b/modules/home-manager/eww/scripts/workspaces/default.nix @@ -0,0 +1,20 @@ +{pkgs ? import {}}: let + # lock = builtins.fromJSON (builtins.readFile ../../../../../flake.lock); + # nixpkgsRev = lock.nodes.nixpkgs.locked.rev; + # pkgs = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/${nixpkgsRev}.tar.gz") {}; + pname = "hyprland-workspaces-eww"; + version = "1.0.0"; + src = ./src; +in + pkgs.rustPlatform.buildRustPackage { + inherit pname version src; + cargoHash = "sha256-6Wl3cOIxlPJjzEuzNhCBZJXayL8runQfAxPruvzh2Vc="; + # cargoHash = pkgs.lib.fakeHash; + checkType = "release"; + postBuild = '' + # pushd target/*/release + # ls -la + # ${pkgs.upx}/bin/upx --best --lzma hyprland-workspaces-eww + # popd + ''; + } diff --git a/modules/home-manager/eww/scripts/workspaces/src/.gitignore b/modules/home-manager/eww/scripts/workspaces/src/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/modules/home-manager/eww/scripts/workspaces/src/.gitignore @@ -0,0 +1 @@ +target diff --git a/modules/home-manager/eww/scripts/workspaces/src/Cargo.lock b/modules/home-manager/eww/scripts/workspaces/src/Cargo.lock new file mode 100644 index 0000000..10e543c --- /dev/null +++ b/modules/home-manager/eww/scripts/workspaces/src/Cargo.lock @@ -0,0 +1,96 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "hyprland-workspaces-eww" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[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.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "2.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" diff --git a/modules/home-manager/eww/scripts/workspaces/src/Cargo.toml b/modules/home-manager/eww/scripts/workspaces/src/Cargo.toml new file mode 100644 index 0000000..2fd947d --- /dev/null +++ b/modules/home-manager/eww/scripts/workspaces/src/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "hyprland-workspaces-eww" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "hyprland-workspaces-eww" +path = "./main.rs" + +[dependencies] +serde = "1.0.217" +serde_json = "1.0.137" + +[profile.release] +strip = true +opt-level = "z" +lto = true +codegen-units = 1 +panic = "abort" diff --git a/modules/home-manager/eww/scripts/workspaces/src/main.rs b/modules/home-manager/eww/scripts/workspaces/src/main.rs new file mode 100644 index 0000000..a77ecc2 --- /dev/null +++ b/modules/home-manager/eww/scripts/workspaces/src/main.rs @@ -0,0 +1,186 @@ +mod workspace { + pub struct Workspace { + id: usize, + icon: char, + pub is_active: bool, + pub is_occupied: bool, + pub is_focused: bool, + } + + impl Workspace { + pub fn new(id: usize) -> Self { + Self { + id, + icon: id.to_string().chars().next().unwrap_or('?'), + is_active: false, + is_occupied: false, + is_focused: false, + } + } + + pub fn id(&self) -> usize { + return self.id; + } + + pub fn icon(&self) -> char { + return self.icon; + } + + pub fn clear_states(&mut self) { + self.is_active = false; + self.is_occupied = false; + self.is_focused = false; + } + } +} + +mod eww {} + +mod hypr { + pub mod hyprland { + + pub mod workspace { + pub type Id = usize; + pub type Name = String; + } + + pub mod socket2 { + use super::workspace; + use std::{error::Error, fmt::Display, num::ParseIntError, str::FromStr}; + + #[derive(Debug)] + pub enum Event { + Workspace(workspace::Id), + Exit(), + WorkspaceV2(String, String), + } + + #[derive(Debug)] + pub enum EventParseError { + UnknownEventType(String), + MissingParameters(String), + InvalidParameters(String, String), + ParseIntError(ParseIntError), + } + + impl From for EventParseError { + fn from(value: ParseIntError) -> Self { + Self::ParseIntError(value) + } + } + + impl Error for EventParseError {} + + impl Display for EventParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + EventParseError::UnknownEventType(event_type) => { + write!(f, "unknown event type: {event_type}") + } + + EventParseError::MissingParameters(event_type) => { + write!(f, "missing parameters for event type: {event_type}") + } + + EventParseError::ParseIntError(err) => { + write!(f, "error parsing integer: {err}") + } + EventParseError::InvalidParameters(event_type, params) => { + write!( + f, + "invalid parameters for event type {event_type}: {params}" + ) + } + } + } + } + + impl FromStr for Event { + type Err = EventParseError; + + fn from_str(s: &str) -> Result { + let (event_type, rest): (&str, Option<&str>) = s + .find(">>") + .map(|n| { + let (a, b) = s.split_at(n); + (a, Option::Some(&b[2..])) + }) + .unwrap_or((s, Option::None)); + match (event_type, rest) { + ("workspace", None) => { + Err(EventParseError::MissingParameters(event_type.to_string())) + } + ("workspace", Some(workspace)) => Ok(Event::Workspace(workspace.parse()?)), + ("workspacev2", Some(args)) => { + let args: (String, String) = args + .split_once(',') + .map(|(a, b)| (a.to_string(), b.to_string())) + .ok_or(EventParseError::InvalidParameters( + event_type.to_string(), + args.to_string(), + ))?; + Ok(Event::WorkspaceV2(args.0, args.1)) + } + ("exit", _) => Ok(Event::Exit()), + _ => Err(EventParseError::UnknownEventType(event_type.to_string())), + } + } + } + } + } +} + +use hypr::hyprland::socket2::Event; +use std::{ + collections::HashMap, + env, + error::Error, + io::{BufRead, BufReader}, + os::unix::net::UnixStream, +}; +use workspace::Workspace; + +fn main() -> Result<(), Box> { + let mut workspaces: HashMap = + (1..=9).map(|n| (n, Workspace::new(n))).collect(); + let path = format!( + "{}/hypr/{}/.socket2.sock", + env::var("XDG_RUNTIME_DIR")?, + env::var("HYPRLAND_INSTANCE_SIGNATURE")? + ); + + eprintln!("opening {}", path); + let stream = UnixStream::connect(&path)?; + let event_lines = BufReader::new(stream).lines(); + for l in event_lines.into_iter() { + match l?.parse::() { + Ok(e) => match e { + Event::Workspace(i) => match workspaces.get_mut(&i) { + Some(related_workspace) => { + eprintln!( + "setting workspace {} (id: {}) as active", + related_workspace.icon(), + related_workspace.id() + ); + related_workspace.is_active = true + } + None => { + eprintln!("event for untracked workspace {}", i); + } + }, + Event::Exit() => break, + other => { + eprintln!("unhandled event: {:?}", other); + } + }, + Err(e) => eprintln!("error parsing event: {}", e), + } + render(&workspaces)?; + } + + Ok(()) +} + +fn render(workspaces: &HashMap) -> Result<(), Box> { + Ok(()) +} diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index 01a22bd..b732e6f 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -27,7 +27,7 @@ programs.hyprland = { enable = true; }; - environment.systemPackages = with pkgs; [hyprpaper xwaylandvideobridge socat]; + environment.systemPackages = with pkgs; [hyprpaper xwaylandvideobridge netcat-openbsd]; programs.hyprland = { package = flakeInputs.hyprland.packages.${pkgs.system}.hyprland;