i3bar-river 0.1.2

A port of i3bar for river
use wayrs_client::connection::Connection;
use wayrs_client::global::*;
use wayrs_client::object::ObjectId;
use wayrs_client::proxy::Proxy;

use super::*;
use crate::state::State;

pub struct ExtWorkspaceUnstable {
    manager: ZextWorkspaceManagerV1,
    groups: Vec<Group>,
    known_outputs: Vec<WlOutput>,
    callback: WmInfoCallback,
}

#[derive(Debug)]
struct Group {
    group_handle: ZextWorkspaceGroupHandleV1,
    workspaces: Vec<Workspace>,
    outputs: Vec<ObjectId>,
}

#[derive(Debug)]
struct Workspace {
    workspace_handle: ZextWorkspaceHandleV1,
    name: Option<String>,
    is_focused: bool,
    is_urgent: bool,
}

impl ExtWorkspaceUnstable {
    pub fn bind(
        conn: &mut Connection<State>,
        globals: &Globals,
        callback: WmInfoCallback,
    ) -> Option<Self> {
        Some(Self {
            manager: globals.bind_with_cb(conn, 1..=1, manager_cb).ok()?,
            groups: Vec::new(),
            known_outputs: Vec::new(),
            callback,
        })
    }
}

impl ExtWorkspaceUnstable {
    pub fn new_ouput(&mut self, _: &mut Connection<State>, output: WlOutput) {
        self.known_outputs.push(output);
    }

    pub fn output_removed(&mut self, _: &mut Connection<State>, output: WlOutput) {
        self.known_outputs.retain(|&o| o != output);
        for group in &mut self.groups {
            group.outputs.retain(|&id| id != output.id());
        }
    }

    pub fn click_on_tag(
        &mut self,
        conn: &mut Connection<State>,
        _: WlOutput,
        _: WlSeat,
        tag: &str,
        btn: PointerBtn,
    ) {
        if btn != PointerBtn::Left {
            return;
        }

        let Some(ws) = self
            .groups
            .iter()
            .find_map(|g| g.workspaces.iter().find(|w| w.name.as_deref() == Some(tag)))
        else { return };

        ws.workspace_handle.activate(conn);
        self.manager.commit(conn);
    }
}

fn manager_cb(
    conn: &mut Connection<State>,
    s: &mut State,
    _: ZextWorkspaceManagerV1,
    event: zext_workspace_manager_v1::Event,
) {
    let WmInfoProvider::Ewu(state) = &mut s.shared_state.wm_info_provider else { unreachable!() };

    match event {
        zext_workspace_manager_v1::Event::WorkspaceGroup(group_handle) => {
            conn.set_callback_for(group_handle, group_cb);
            state.groups.push(Group {
                group_handle,
                workspaces: Vec::new(),
                outputs: Vec::new(),
            });
        }
        zext_workspace_manager_v1::Event::Done => {
            let mut events = Vec::new();
            for &output in &state.known_outputs {
                if let Some(group) = state
                    .groups
                    .iter()
                    .find(|g| g.outputs.contains(&output.id()))
                {
                    let mut tags: Vec<_> = group
                        .workspaces
                        .iter()
                        .flat_map(|w| {
                            let name = w.name.clone()?;
                            Some(Tag {
                                name,
                                is_focused: w.is_focused,
                                is_active: true,
                                is_urgent: w.is_urgent,
                            })
                        })
                        .collect();
                    tags.sort_unstable_by(|a, b| a.name.cmp(&b.name));
                    events.push((output, tags));
                }
            }

            let cb = state.callback;
            for (output, tags) in events {
                let info = WmInfo {
                    layout_name: None,
                    tags,
                };
                (cb)(conn, s, output, info);
            }
        }
        zext_workspace_manager_v1::Event::Finished => unreachable!(),
    }
}

fn group_cb(
    conn: &mut Connection<State>,
    state: &mut State,
    group_handle: ZextWorkspaceGroupHandleV1,
    event: zext_workspace_group_handle_v1::Event,
) {
    let WmInfoProvider::Ewu(state) = &mut state.shared_state.wm_info_provider else { unreachable!() };
    let group = state
        .groups
        .iter_mut()
        .find(|g| g.group_handle == group_handle)
        .unwrap();

    match event {
        zext_workspace_group_handle_v1::Event::OutputEnter(output) => {
            group.outputs.push(output);
        }
        zext_workspace_group_handle_v1::Event::OutputLeave(_) => todo!(),
        zext_workspace_group_handle_v1::Event::Workspace(workspace_handle) => {
            conn.set_callback_for(workspace_handle, workspace_cb);
            group.workspaces.push(Workspace {
                workspace_handle,
                name: None,
                is_focused: false,
                is_urgent: false,
            });
        }
        zext_workspace_group_handle_v1::Event::Remove => {
            todo!();
        }
    }
}

fn workspace_cb(
    conn: &mut Connection<State>,
    state: &mut State,
    workspace_handle: ZextWorkspaceHandleV1,
    event: zext_workspace_handle_v1::Event,
) {
    let WmInfoProvider::Ewu(state) = &mut state.shared_state.wm_info_provider else { unreachable!() };
    let group = state
        .groups
        .iter_mut()
        .find(|g| {
            g.workspaces
                .iter()
                .any(|w| w.workspace_handle == workspace_handle)
        })
        .unwrap();
    let workspace = group
        .workspaces
        .iter_mut()
        .find(|w| w.workspace_handle == workspace_handle)
        .unwrap();

    match event {
        zext_workspace_handle_v1::Event::Name(name) => {
            workspace.name = Some(name.to_string_lossy().into_owned());
        }
        zext_workspace_handle_v1::Event::Coordinates(_) => (),
        zext_workspace_handle_v1::Event::State(state) => {
            workspace.is_focused = false;
            workspace.is_urgent = false;
            for state in state
                .chunks_exact(4)
                .map(|b| u32::from_ne_bytes(b.try_into().unwrap()))
            {
                workspace.is_focused |= state == zext_workspace_handle_v1::State::Active as u32;
                workspace.is_urgent |= state == zext_workspace_handle_v1::State::Urgent as u32;
            }
        }
        zext_workspace_handle_v1::Event::Remove => {
            group
                .workspaces
                .retain(|w| w.workspace_handle != workspace_handle);
            workspace_handle.destroy(conn);
        }
    }
}