feat: more robust logic and handle discord reconnects

This commit is contained in:
Virt 2025-07-17 16:56:33 +02:00
commit 30034b9f60
2 changed files with 78 additions and 38 deletions

View file

@ -1,34 +1,32 @@
/* 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 anyhow::{Context, Result};
use discord_rich_presence::{ use discord_rich_presence::{
DiscordIpc, DiscordIpcClient, DiscordIpc, DiscordIpcClient,
activity::{Activity, Assets, Button, Timestamps}, activity::{Activity, Assets, Button, Timestamps},
}; };
use log::{debug, error, info}; use log::{debug, info};
#[derive(Debug)] #[derive(Debug)]
pub struct Discord { pub struct Discord {
client: DiscordIpcClient, client: DiscordIpcClient,
connected: bool,
} }
impl Discord { impl Discord {
pub fn new(id: &str) -> Result<Self> { pub fn new(id: &str) -> Result<Self> {
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<()> { pub fn connect(&mut self) -> Result<()> {
debug!("connecting to discord"); debug!("connecting to discord");
self.client.connect().context("failed to connect to discord ipc")?; self.client.connect().context("failed to connect to discord ipc")?;
self.connected = true;
info!("successfully connected to discord"); info!("successfully connected to discord");
Ok(()) Ok(())
@ -37,7 +35,10 @@ impl Discord {
pub fn disconnect(&mut self) -> Result<()> { pub fn disconnect(&mut self) -> Result<()> {
debug!("disconnecting from discord"); 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<()> { pub fn clear(&mut self) -> Result<()> {

View file

@ -1,6 +1,5 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
thread::sleep,
time::{Duration, Instant, SystemTime}, time::{Duration, Instant, SystemTime},
}; };
@ -8,7 +7,7 @@ use anyhow::Result;
use discord::Discord; use discord::Discord;
use hyprland::{Address, Hyprland}; use hyprland::{Address, Hyprland};
use language::get_language; use language::get_language;
use log::{debug, info}; use log::{debug, error, info, warn};
pub mod discord; pub mod discord;
pub mod hyprland; pub mod hyprland;
@ -19,7 +18,7 @@ const ICONS_URL: &str =
"https://raw.githubusercontent.com/xhyrom/zed-discord-presence/main/assets/icons"; "https://raw.githubusercontent.com/xhyrom/zed-discord-presence/main/assets/icons";
const ZED_CLASS: &str = "dev.zed.Zed"; const ZED_CLASS: &str = "dev.zed.Zed";
#[derive(Debug)] #[derive(Debug, Clone)]
struct ZedInstance { struct ZedInstance {
workspace: String, workspace: String,
file: Option<String>, file: Option<String>,
@ -29,6 +28,14 @@ struct ZedInstance {
focused: SystemTime, 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 { impl ZedInstance {
pub fn new(title: &str) -> Self { pub fn new(title: &str) -> Self {
let mut s = Self { let mut s = Self {
@ -79,19 +86,18 @@ fn main() -> Result<()> {
); );
} }
info!("connecting to discord ipc");
discord.connect()?;
info!("connecting to hyprland ipc"); info!("connecting to hyprland ipc");
hyprland.connect(Duration::from_secs(60))?; hyprland.connect(Duration::from_secs(60))?;
let mut first = true; let mut updated = Instant::now() - idle;
let mut shown: Option<ZedInstance> = None;
loop { loop {
let events = hyprland.read_events()?; let events = hyprland.read_events()?;
debug!("received new hyprland events"); debug!("received new hyprland events");
// only check on timeout if not active // update anyways if last update has been before idle timeout
let mut changed = first || events.is_none() && !instances.contains_key(&active); let mut changed = Instant::now().duration_since(updated) < idle;
first = false;
if let Some(events) = events { if let Some(events) = events {
for event in events { for event in events {
@ -131,27 +137,55 @@ fn main() -> Result<()> {
} }
if changed { 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| { instances.values().max_by(|a, b| a.focused.cmp(&b.focused)).filter(|instance| {
SystemTime::now().duration_since(instance.focused).expect("compare times wtf") SystemTime::now().duration_since(instance.focused).expect("compare times wtf")
< idle < idle
}) })
}) { });
info!(
"setting discord activity to {} in workspace {}",
instance.file.as_ref().map(|s| s.as_str()).unwrap_or("<none>"),
instance.workspace
);
set_activity( if instance != shown.as_ref() {
&mut discord, debug!("updating discord status as change was detected");
instance.file.as_ref().map(|s| s.as_str()).unwrap_or("nothing"), shown = instance.cloned();
&instance.workspace, updated = Instant::now();
instance.started,
)? if !discord.is_connected() {
} else { info!("(re-)connecting to discord ipc");
info!("removing discord activity");
discord.clear()?; 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("<none>"),
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, started: SystemTime,
) -> Result<()> { ) -> Result<()> {
let language = get_language(file); 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( discord.set(
Some(format!("Editing {file}")), Some(format!("Editing {file}")),