diff --git a/src/discord.rs b/src/discord.rs index fa1a7d9..91883cb 100644 --- a/src/discord.rs +++ b/src/discord.rs @@ -1,34 +1,32 @@ -/* - * 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 std::time::{SystemTime, UNIX_EPOCH}; use anyhow::{Context, Result}; use discord_rich_presence::{ DiscordIpc, DiscordIpcClient, activity::{Activity, Assets, Button, Timestamps}, }; -use log::{debug, error, info}; +use log::{debug, info}; #[derive(Debug)] pub struct Discord { client: DiscordIpcClient, + connected: bool, } impl Discord { pub fn new(id: &str) -> Result { - Ok(Self { client: DiscordIpcClient::new(id) }) + Ok(Self { client: DiscordIpcClient::new(id), connected: false }) + } + + pub fn is_connected(&self) -> bool { + self.connected } 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(()) @@ -37,7 +35,10 @@ impl Discord { pub fn disconnect(&mut self) -> Result<()> { debug!("disconnecting from discord"); - self.client.close().context("failed to disconnect from discord") + self.client.close().context("failed to disconnect from discord")?; + self.connected = false; + + Ok(()) } pub fn clear(&mut self) -> Result<()> { diff --git a/src/main.rs b/src/main.rs index d53ef0e..6845842 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ use std::{ collections::HashMap, - thread::sleep, time::{Duration, Instant, SystemTime}, }; @@ -8,7 +7,7 @@ use anyhow::Result; use discord::Discord; use hyprland::{Address, Hyprland}; use language::get_language; -use log::{debug, info}; +use log::{debug, error, info, warn}; pub mod discord; pub mod hyprland; @@ -19,7 +18,7 @@ const ICONS_URL: &str = "https://raw.githubusercontent.com/xhyrom/zed-discord-presence/main/assets/icons"; const ZED_CLASS: &str = "dev.zed.Zed"; -#[derive(Debug)] +#[derive(Debug, Clone)] struct ZedInstance { workspace: String, file: Option, @@ -29,6 +28,14 @@ struct ZedInstance { 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 { @@ -79,19 +86,18 @@ fn main() -> Result<()> { ); } - info!("connecting to discord ipc"); - discord.connect()?; info!("connecting to hyprland ipc"); hyprland.connect(Duration::from_secs(60))?; - let mut first = true; + let mut updated = Instant::now() - idle; + let mut shown: Option = None; + loop { let events = hyprland.read_events()?; debug!("received new hyprland events"); - // only check on timeout if not active - let mut changed = first || events.is_none() && !instances.contains_key(&active); - first = false; + // 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 { @@ -131,27 +137,55 @@ fn main() -> Result<()> { } if changed { - if let Some(instance) = instances.get(&active).or_else(|| { + 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 }) - }) { - 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 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; + } } } } @@ -164,7 +198,12 @@ pub fn set_activity( started: SystemTime, ) -> Result<()> { let language = get_language(file); - let language_upper: String = language.chars().take(1).chain(language.chars().skip(1)).collect(); + let language_upper: String = language + .chars() + .take(1) + .map(|a| a.to_ascii_uppercase()) + .chain(language.chars().skip(1)) + .collect(); discord.set( Some(format!("Editing {file}")),