hyprscratch 0.6.4

Improved scratchpad functionality for Hyprland
use crate::config::Config;
use crate::daemon::{DaemonOptions, DaemonState};
use crate::logs::*;
use crate::utils::*;
use hyprland::data::{Client, Clients, Workspace};
use hyprland::dispatch::*;
use hyprland::event_listener::EventListener;
use hyprland::prelude::*;
use hyprland::Result;
use notify::event::ModifyKind;
use notify::{Event, EventKind, RecursiveMode, Watcher};
use std::path::{Path, PathBuf};
use std::sync::{mpsc, Arc, Mutex};
use std::thread::*;
use std::time::Duration;

type ConfigMutex = Arc<Mutex<Config>>;

fn add_vanish(ev: &mut EventListener, config: ConfigMutex) {
    ev.add_window_moved_handler(move |data| {
        let (f, l) = (file!(), line!());
        let ephemeral_titles = &config.lock().unwrap_log(f, l).cache.ephemeral_titles;

        if ephemeral_titles.is_empty() {
            return;
        }

        if let (Ok(clients), Ok(active)) = (Clients::get(), Workspace::get_active()) {
            clients
                .iter()
                .filter(|cl| cl.address == data.window_address && active.id != data.workspace_id)
                .filter(|cl| is_known(ephemeral_titles, cl))
                .for_each(|cl| {
                    hyprland::dispatch!(CloseWindow, WindowIdentifier::Address(cl.address.clone()))
                        .log_err(f, l);
                });
        }
    });
}

fn add_clean(ev: &mut EventListener, config: ConfigMutex) {
    ev.add_workspace_changed_handler(move |_| {
        let (f, l) = (file!(), line!());
        let slick_map = &config.lock().unwrap_log(f, l).cache.clean_map;
        move_floating(slick_map).log_err(f, l);

        if let Ok(Some(ac)) = Client::get_active() {
            if is_known_map(slick_map, &ac) {
                hide_special(&ac);
            }
        }
    });
}

fn add_spotless(ev: &mut EventListener, config: ConfigMutex) {
    ev.add_active_window_changed_handler(move |_| {
        if let Ok(Some(cl)) = Client::get_active() {
            let (f, l) = (file!(), line!());
            let conf = &config.lock().unwrap_log(f, l);

            if !is_known(&conf.cache.normal_titles, &cl) {
                move_floating(&conf.cache.spotless_map).log_err(f, l);
            }
        }
    });
}

fn add_builtin_reload(ev: &mut EventListener, config: ConfigMutex) {
    ev.add_config_reloaded_handler(move || {
        let (f, l) = (file!(), line!());
        config.lock().unwrap_log(f, l).reload(None).log_err(f, l);
    });
}

fn start_events(options: Arc<DaemonOptions>, config: ConfigMutex) -> Result<()> {
    let mut ev = EventListener::new();

    if options.auto_reload {
        add_builtin_reload(&mut ev, config.clone());
    }

    if options.clean {
        add_clean(&mut ev, config.clone());
    }

    if options.spotless {
        add_spotless(&mut ev, config.clone());
    }

    add_vanish(&mut ev, config.clone());
    ev.start_listener()
}

fn keep_alive(mut handle: JoinHandle<()>, options: Arc<DaemonOptions>, config: ConfigMutex) {
    let max_restarts = 50;
    let mut restarts = 0;

    loop {
        let _ = handle.join();
        let config = config.clone();
        let options = options.clone();

        restarts += 1;
        if restarts >= max_restarts {
            let _ = log(
                "Event listener repeated panic, terminating thread.".to_string(),
                Warn,
            );
            break;
        }

        let _ = log("Event listener panic, restarting thread".to_string(), Warn);
        handle = spawn(|| start_events(options, config).log_err(file!(), line!()));
    }
}

fn reload_on_modify(res: notify::Result<Event>, config: ConfigMutex) {
    let (f, l) = (file!(), line!());
    let mut config_guard = config.lock().unwrap_log(f, l);
    let config_path = PathBuf::from(&config_guard.config_file);

    match res {
        Ok(e) if e.paths.contains(&config_path) => {
            if let EventKind::Modify(ModifyKind::Data(_)) = e.kind {
                sleep(Duration::from_millis(100));
                config_guard.reload(None).log_err(f, l);
            }
        }
        Err(err) => {
            let _ = log(format!("Watcher returned error: {err}"), Warn);
        }
        _ => (),
    }
}

fn start_auto_reload(config: ConfigMutex) -> notify::Result<()> {
    let (tx, rx) = mpsc::channel::<notify::Result<Event>>();
    let mut watcher = notify::recommended_watcher(tx)?;

    let (f, l) = (file!(), line!());
    watcher.watch(
        Path::new(&config.lock().unwrap_log(f, l).config_file)
            .parent()
            .unwrap_log(f, l),
        RecursiveMode::NonRecursive,
    )?;

    for res in rx {
        reload_on_modify(res, config.clone());
    }
    Ok(())
}

pub fn start_event_listeners(config: &ConfigMutex, state: &mut DaemonState) {
    let (f, l) = (file!(), line!());
    if state.options.auto_reload {
        let config_c = config.clone();
        spawn(move || start_auto_reload(config_c).log_err(f, l));
    }

    let config_c = config.clone();
    let options = state.options.clone();
    let handle = spawn(move || start_events(options, config_c).log_err(f, l));

    let config_c = config.clone();
    let options = state.options.clone();
    spawn(move || keep_alive(handle, options, config_c));
}