tkbar 0.2.0

A minimalist, hardened status bar for Wayland.
use {
    super::Ws,
    crate::conf::CONFIG,
    niri_ipc::{Action, Event, Request, Response, WorkspaceReferenceArg, socket::Socket},
};

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

fn ipc_loop(tx: &async_channel::Sender<Vec<Ws>>) -> std::io::Result<()> {
    let mut socket = Socket::connect()?;
    let reply = socket.send(Request::EventStream)?;
    if !matches!(reply, Ok(Response::Handled)) {
        crate::log::warn(
            "workspaces",
            &format!("unexpected reply to EventStream: {reply:?}"),
        );
    }
    let mut read_event = socket.read_events();

    let mut state: Vec<Ws> = Vec::new();

    loop {
        match read_event()? {
            Event::WorkspacesChanged { workspaces } => {
                state = workspaces
                    .into_iter()
                    .map(|w| {
                        Ws {
                            id: w.id,
                            idx: w.idx,
                            label: if CONFIG.security.should_allow_workspace_label &&
                                let Some(name) = w.name
                            {
                                name
                            } else {
                                w.idx.to_string()
                            },
                            is_active: w.active_window_id.is_some(),
                            is_focused: w.is_focused,
                        }
                    })
                    .collect();
            },
            Event::WorkspaceActivated { id, focused } => {
                for w in state.iter_mut() {
                    if focused {
                        w.is_focused = w.id == id;
                    }
                }
            },
            Event::WorkspaceActiveWindowChanged {
                workspace_id,
                active_window_id,
            } => {
                if let Some(ws) = state.iter_mut().find(|ws| ws.id == workspace_id) {
                    ws.is_active = active_window_id.is_some();
                }
            },
            _ => continue,
        }

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

        state.sort_unstable_by_key(|wks| wks.idx);

        let _ = tx.send_blocking(state.clone());
    }
}


pub(super) fn focus_workspace(id: u64) {
    let mut socket = match Socket::connect() {
        Ok(socket) => socket,
        Err(e) => {
            crate::log::warn("workspaces", &format!("could not connect to niri IPC: {e}"));
            return;
        },
    };
    if let Err(e) = socket.send(Request::Action(Action::FocusWorkspace {
        reference: WorkspaceReferenceArg::Id(id),
    })) {
        crate::log::warn("workspaces", &format!("failed to focus workspace: {e}"));
    }
}