tkbar 0.2.0

A minimalist, hardened status bar for Wayland.
use {
    super::Ws,
    crate::ui::CONFIG,
    hyprland::{
        data::{Workspace, Workspaces},
        dispatch::{Dispatch, DispatchType, WorkspaceIdentifierWithSpecial},
        event_listener::EventListener,
        prelude::*,
    },
};

pub(super) fn event_loop(tx: &async_channel::Sender<Vec<Ws>>) -> Result<(), String> {
    ipc_loop(tx).map_err(|e| format!("{e:?}"))
}

fn ipc_loop(tx: &async_channel::Sender<Vec<Ws>>) -> hyprland::Result<()> {
    let mut listener = EventListener::new();

    let cli_tx = tx.clone();
    listener.add_workspace_changed_handler(move |_| snapshot(&cli_tx));

    let add_tx = tx.clone();
    listener.add_workspace_added_handler(move |_| snapshot(&add_tx));

    let destroy_tx = tx.clone();
    listener.add_workspace_deleted_handler(move |_| snapshot(&destroy_tx));

    let monitor_tx = tx.clone();
    listener.add_active_monitor_changed_handler(move |_| snapshot(&monitor_tx));

    let open_tx = tx.clone();
    listener.add_window_opened_handler(move |_| snapshot(&open_tx));

    let close_tx = tx.clone();
    listener.add_window_closed_handler(move |_| snapshot(&close_tx));

    snapshot(tx);

    listener.start_listener()
}

fn snapshot(tx: &async_channel::Sender<Vec<Ws>>) {
    let list = match read_workspaces() {
        Ok(list) => list,
        Err(e) => {
            crate::log::warn("workspaces", &format!("hyprland IPC error: {e:?}"));
            return;
        },
    };
    let _ = tx.send_blocking(list);
}

fn read_workspaces() -> hyprland::Result<Vec<Ws>> {
    let focused_id = Workspace::get_active()?.id;

    let mut list: Vec<Ws> = Workspaces::get()?
        .to_vec()
        .into_iter()
        .filter_map(|wks| {
            if wks.id <= 0 {
                return None;
            }
            Some(Ws {
                id: u64::try_from(wks.id).ok()?,
                idx: u8::try_from(wks.id).ok()?,
                label: if CONFIG.security.should_allow_workspace_label {
                    wks.name
                } else {
                    wks.id.to_string()
                },
                is_active: wks.windows > 0,
                is_focused: wks.id == focused_id,
            })
        })
        .collect();

    for idx in 1..=CONFIG.behaviour.workspace_count {
        if list.iter().all(|wks| wks.id != u64::from(idx)) {
            list.push(Ws {
                id: u64::from(idx),
                idx,
                label: idx.to_string(),
                is_active: false,
                is_focused: idx == u8::try_from(focused_id).unwrap_or_default(),
            });
        }
    }


    list.sort_by_key(|wks| wks.id);
    Ok(list)
}

pub(super) fn focus_workspace(id: u64) {
    let Ok(id) = i32::try_from(id) else {
        return;
    };
    if let Err(e) = Dispatch::call(DispatchType::Workspace(WorkspaceIdentifierWithSpecial::Id(
        id,
    ))) {
        crate::log::warn("workspaces", &format!("failed to focus workspace: {e:?}"));
    }
}