diff --git a/Cargo.lock b/Cargo.lock index 93ba5c6..f57de8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,34 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "serde", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -47,7 +75,7 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -58,7 +86,7 @@ checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -67,6 +95,61 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + [[package]] name = "cfg-if" version = "1.0.1" @@ -79,6 +162,27 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + [[package]] name = "discord-rich-presence" version = "0.2.5" @@ -93,6 +197,12 @@ dependencies = [ "uuid", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "env_filter" version = "0.1.3" @@ -116,6 +226,22 @@ dependencies = [ "log", ] +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -127,6 +253,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + [[package]] name = "hl-zed-dc-rpc" version = "0.1.0" @@ -134,10 +266,52 @@ dependencies = [ "anyhow", "discord-rich-presence", "env_logger", + "hyprland", "log", "regex", + "serde_json", +] + +[[package]] +name = "hyprland" +version = "0.4.0-beta.2" +source = "git+https://github.com/hyprland-community/hyprland-rs#fce63060619422f52563514b474398e380e5fc51" +dependencies = [ + "ahash", + "async-stream", + "derive_more", + "either", + "futures-lite", + "hyprland-macros", + "num-traits", + "once_cell", + "paste", + "phf", "serde", "serde_json", + "serde_repr", + "tokio", +] + +[[package]] +name = "hyprland-macros" +version = "0.4.0-beta.2" +source = "git+https://github.com/hyprland-community/hyprland-rs#fce63060619422f52563514b474398e380e5fc51" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags", + "cfg-if", + "libc", ] [[package]] @@ -194,12 +368,110 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.59.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.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + [[package]] name = "once_cell_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + [[package]] name = "portable-atomic" version = "1.11.1" @@ -233,6 +505,21 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + [[package]] name = "regex" version = "1.11.1" @@ -262,6 +549,12 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rustc-demangle" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" + [[package]] name = "ryu" version = "1.0.20" @@ -311,6 +604,28 @@ dependencies = [ "syn", ] +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "syn" version = "2.0.104" @@ -342,12 +657,47 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio" +version = "1.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "pin-project-lite", + "slab", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "utf8parse" version = "0.2.2" @@ -363,12 +713,27 @@ dependencies = [ "getrandom", ] +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -441,3 +806,23 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 9957494..26da3f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,12 +5,9 @@ edition = "2024" [dependencies] anyhow = "1.0.98" -env_logger = "0.11.8" -log = "0.4.27" - -regex = "1.11.1" - -serde = { version = "1.0.219", features = ["derive"] } -serde_json = "1.0.140" - discord-rich-presence = { git = "https://github.com/vionya/discord-rich-presence" } # git cause errors +env_logger = "0.11.8" +hyprland = { git = "https://github.com/hyprland-community/hyprland-rs" } +log = "0.4.27" +regex = "1.11.1" +serde_json = "1.0.140" diff --git a/src/discord.rs b/src/discord.rs index 91883cb..174dc0a 100644 --- a/src/discord.rs +++ b/src/discord.rs @@ -1,32 +1,36 @@ -use std::time::{SystemTime, UNIX_EPOCH}; +/* + * This file WAS ONCE is part of discord-presence. Extension for Zed that adds support for Discord Rich Presence using LSP. It is heavily modified to be used here. + * + * Copyright (c) 2025 Steinhübl, Virt + */ + +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use anyhow::{Context, Result}; use discord_rich_presence::{ DiscordIpc, DiscordIpcClient, activity::{Activity, Assets, Button, Timestamps}, }; -use log::{debug, info}; +use log::{debug, error, info}; #[derive(Debug)] pub struct Discord { client: DiscordIpcClient, - connected: bool, + start_timestamp: Duration, } impl Discord { pub fn new(id: &str) -> Result { - Ok(Self { client: DiscordIpcClient::new(id), connected: false }) - } + let since_epoch = + SystemTime::now().duration_since(UNIX_EPOCH).expect("we are after 1970 right??"); - pub fn is_connected(&self) -> bool { - self.connected + Ok(Self { client: DiscordIpcClient::new(id), start_timestamp: since_epoch }) } pub fn connect(&mut self) -> Result<()> { debug!("connecting to discord"); self.client.connect().context("failed to connect to discord ipc")?; - self.connected = true; info!("successfully connected to discord"); Ok(()) @@ -35,10 +39,7 @@ impl Discord { pub fn disconnect(&mut self) -> Result<()> { debug!("disconnecting from discord"); - self.client.close().context("failed to disconnect from discord")?; - self.connected = false; - - Ok(()) + self.client.close().context("failed to disconnect from discord") } pub fn clear(&mut self) -> Result<()> { @@ -57,21 +58,15 @@ impl Discord { small_image: Option, small_text: Option, git_remote_url: Option, - timestamp: SystemTime, ) -> Result<()> { - let activity = - Activity::new() - .timestamps( - Timestamps::new() - .start(timestamp.duration_since(UNIX_EPOCH).expect("1970!!").as_millis() - as i64), - ) - .buttons( - git_remote_url - .as_ref() - .map(|url| vec![Button::new("View Repository", url)]) - .unwrap_or_default(), - ); + let activity = Activity::new() + .timestamps(Timestamps::new().start(self.start_timestamp.as_millis() as i64)) + .buttons( + git_remote_url + .as_ref() + .map(|url| vec![Button::new("View Repository", url)]) + .unwrap_or_default(), + ); let activity = set_optional_field(activity, state.as_deref(), Activity::state); let activity = set_optional_field(activity, details.as_deref(), Activity::details); diff --git a/src/hyprland.rs b/src/hyprland.rs deleted file mode 100644 index b8519b7..0000000 --- a/src/hyprland.rs +++ /dev/null @@ -1,130 +0,0 @@ -use std::{ - io::{self, Read, Write}, - os::unix::net::UnixStream, - path::PathBuf, - time::Duration, -}; - -use anyhow::{Context, Result, anyhow}; -use log::debug; -use serde::Deserialize; - -pub type Address = String; - -#[derive(Deserialize)] -pub struct HyprlandClient { - pub address: Address, - pub class: String, - pub title: String, -} - -#[derive(Debug)] -pub enum HyprlandEvent { - Open { address: Address, workspace: String, title: String, class: String }, - Active { address: Address }, - Close { address: Address }, - Title { address: Address, title: String }, -} - -pub struct Hyprland { - path: PathBuf, - stream: Option, -} - -impl Hyprland { - /// creates a new instance based on the environment variables - pub fn env() -> Result { - let instance = std::env::var("HYPRLAND_INSTANCE_SIGNATURE") - .context("unable to read HYPRLAND_INSTANCE_SIGNATURE env")?; - - let runtime_dir = - std::env::var("XDG_RUNTIME_DIR").context("unable to read XDG_RUNTIME_DIR env")?; - - Ok(Self { path: PathBuf::from(format!("{runtime_dir}/hypr/{instance}")), stream: None }) - } - - pub fn connect(&mut self, timeout: Duration) -> Result<()> { - debug!("connecting to hyprland socket 2"); - - let stream = UnixStream::connect(self.path.join(".socket2.sock")) - .context("failed to connect to hyprland ipc")?; - stream.set_read_timeout(Some(timeout)).context("failed to set socket timeout")?; - - self.stream = Some(stream); - Ok(()) - } - - pub fn read_events(&mut self) -> Result>> { - let Some(stream) = self.stream.as_mut() else { - return Err(anyhow!("not yet connected to the ipc")); - }; - - let mut buf = [0; 4096]; - let len = match stream.read(&mut buf) { - Ok(len) => len, - Err(e) if e.kind() == io::ErrorKind::WouldBlock => return Ok(None), - Err(e) => return Err(e).context("failed to read from hyprland socket"), - }; - - let string = - String::from_utf8(buf[..len].to_vec()).context("read data from stream is not utf8")?; - - Ok(Some( - string - .split('\n') - .filter_map(|split| { - let things = split.split(">>").collect::>(); - if things.len() != 2 { - return None; - }; - - let event = things[0]; - let args = things[1].split(',').collect::>(); - - match event { - "activewindowv2" if args.len() == 1 => { - Some(HyprlandEvent::Active { address: args[0].to_string() }) - } - "openwindow" if args.len() == 4 => Some(HyprlandEvent::Open { - address: args[0].to_string(), - workspace: args[1].to_string(), - class: args[2].to_string(), - title: args[3].to_string(), - }), - "closewindow" if args.len() == 1 => { - Some(HyprlandEvent::Close { address: args[0].to_string() }) - } - "windowtitlev2" if args.len() == 2 => Some(HyprlandEvent::Title { - address: args[0].to_string(), - title: args[1].to_string(), - }), - _ => None, - } - }) - .collect(), - )) - } - - /// dispatches a command over hyprland's socket 1 and reads the result - fn dispatch_command(&self, command: &str) -> Result { - let mut stream = UnixStream::connect(self.path.join(".socket.sock")) - .context("failed to connect to hl's socket 1")?; - - stream - .write_all(format!("j/{command}").as_bytes()) - .context("failed to write to hl's socket 1")?; - - let mut buf = String::new(); - stream.read_to_string(&mut buf).context("failed to read from hl's socket 1")?; - - Ok(buf) - } - - /// gets the workspace state from socket 1 - pub fn get_all_clients(&self) -> Result> { - serde_json::from_str( - &self.dispatch_command("clients").context("failed to run `clients` hyprctl command")?, - ) - .context("failed to deserialize output of `clients` hyprctl command") - } -} diff --git a/src/main.rs b/src/main.rs index 6845842..2ef9155 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,209 +1,54 @@ -use std::{ - collections::HashMap, - time::{Duration, Instant, SystemTime}, -}; +use std::{thread::sleep, time::Duration}; use anyhow::Result; use discord::Discord; -use hyprland::{Address, Hyprland}; +use hyprland::{ + data::{Client, Clients}, + event_listener::EventListener, + shared::HyprData, +}; use language::get_language; -use log::{debug, error, info, warn}; pub mod discord; -pub mod hyprland; pub mod language; const APP_ID: &str = "1263505205522337886"; const ICONS_URL: &str = "https://raw.githubusercontent.com/xhyrom/zed-discord-presence/main/assets/icons"; -const ZED_CLASS: &str = "dev.zed.Zed"; - -#[derive(Debug, Clone)] -struct ZedInstance { - workspace: String, - file: Option, - started: SystemTime, - - // last time it was focused - focused: SystemTime, -} - -impl PartialEq for ZedInstance { - fn eq(&self, other: &Self) -> bool { - self.workspace.eq(&other.workspace) - && self.file.eq(&other.file) - && self.started.eq(&other.started) - } -} - -impl ZedInstance { - pub fn new(title: &str) -> Self { - let mut s = Self { - workspace: String::new(), - file: None, - started: SystemTime::now(), - focused: SystemTime::now(), - }; - - s.set_title(title); - - s - } - - fn set_title(&mut self, title: &str) { - let split = title.split('—').collect::>(); - - if split.len() == 1 { - self.file = None; - self.workspace = split[0].trim().to_string(); - } else if split.len() == 2 { - self.workspace = split[0].trim().to_string(); - self.file = Some(split[1].trim().to_string()); - } - } -} fn main() -> Result<()> { - env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); - - let idle = Duration::from_secs(10 * 60); - let mut discord = Discord::new(APP_ID)?; - let mut hyprland = Hyprland::env()?; + discord.connect()?; - let mut instances = HashMap::new(); - let mut active = Address::new(); + update_discord(&mut discord)?; + //set_activity(&mut discord, "main.rs", "a secret new rust project")?; - for client in hyprland.get_all_clients()? { - if client.class != "dev.zed.Zed" { - continue; - } + sleep(Duration::from_secs(10)); - instances.insert( - // we strip the 0x as this only appears in this request - client.address.strip_prefix("0x").unwrap_or("unknown").to_string(), - ZedInstance::new(&client.title), - ); - } - - info!("connecting to hyprland ipc"); - hyprland.connect(Duration::from_secs(60))?; - - let mut updated = Instant::now() - idle; - let mut shown: Option = None; - - loop { - let events = hyprland.read_events()?; - debug!("received new hyprland events"); - - // update anyways if last update has been before idle timeout - let mut changed = Instant::now().duration_since(updated) < idle; - - if let Some(events) = events { - for event in events { - changed |= match event { - hyprland::HyprlandEvent::Open { address, title, class, .. } - if class == ZED_CLASS => - { - instances.insert(address, ZedInstance::new(&title)); - true - } - hyprland::HyprlandEvent::Active { address } => { - let changed = if let Some(instance) = instances.get_mut(&active) { - instance.focused = SystemTime::now(); - true - } else { - false - }; - - active = address; - - changed || instances.contains_key(&active) - } - hyprland::HyprlandEvent::Close { address } => { - instances.remove(&address).is_some() - } - hyprland::HyprlandEvent::Title { address, title } => { - if let Some(instance) = instances.get_mut(&address) { - instance.set_title(&title); - true - } else { - false - } - } - _ => false, - }; - } - } - - if changed { - debug!("checking for instance change"); - - let instance = instances.get(&active).or_else(|| { - instances.values().max_by(|a, b| a.focused.cmp(&b.focused)).filter(|instance| { - SystemTime::now().duration_since(instance.focused).expect("compare times wtf") - < idle - }) - }); - - if instance != shown.as_ref() { - debug!("updating discord status as change was detected"); - shown = instance.cloned(); - updated = Instant::now(); - - if !discord.is_connected() { - info!("(re-)connecting to discord ipc"); - - if let Err(e) = discord.connect() { - warn!("failed to connect to discord, waiting for next update: {e:#}"); - shown = None; - continue; - } - } - - let result = if let Some(ref instance) = shown { - info!( - "setting discord activity to {} in workspace {}", - instance.file.as_ref().map(|s| s.as_str()).unwrap_or(""), - instance.workspace - ); - set_activity( - &mut discord, - instance.file.as_ref().map(|s| s.as_str()).unwrap_or("nothing"), - &instance.workspace, - instance.started, - ) - } else { - info!("removing discord activity"); - discord.clear() - }; - - if let Err(e) = result { - warn!("failed to set discord activity: {e:#}"); - - debug!("disconnecting from discord"); - discord.disconnect().map_err(|e| error!("failed to disconnect: {e:#}")).ok(); - - shown = None; - } - } - } - } + Ok(()) } -pub fn set_activity( - discord: &mut Discord, - file: &str, - workspace: &str, - started: SystemTime, -) -> Result<()> { +pub fn update_discord(discord: &mut Discord) -> Result<()> { + let mut clients = + Clients::get()?.into_iter().filter(|c| c.class == "dev.zed.Zed").collect::>(); + clients.sort_by(|c, d| c.focus_history_id.cmp(&d.focus_history_id)); + + if let Some(client) = clients.first() { + let split = client.title.split('—').collect::>(); + + if split.len() == 1 { + set_activity(discord, "not-doing-anything-atm", split[0].trim())?; + } else if split.len() == 2 { + set_activity(discord, split[1].trim(), split[0].trim())?; + } + } + + Ok(()) +} + +pub fn set_activity(discord: &mut Discord, file: &str, workspace: &str) -> Result<()> { let language = get_language(file); - let language_upper: String = language - .chars() - .take(1) - .map(|a| a.to_ascii_uppercase()) - .chain(language.chars().skip(1)) - .collect(); + let language_upper: String = language.chars().take(1).chain(language.chars().skip(1)).collect(); discord.set( Some(format!("Editing {file}")), @@ -213,6 +58,5 @@ pub fn set_activity( Some(format!("{ICONS_URL}/zed.png")), Some("Zed".to_string()), None, - started, ) }