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 @@
/*
* 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<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<()> {
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<()> {

View file

@ -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<String>,
@ -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<ZedInstance> = 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
})
}) {
});
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("<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()?;
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}")),