i3bar-river 0.1.1

A port of i3bar for river
use std::any::Any;
use std::ffi::CString;

use wayrs_client::connection::Connection;
use wayrs_client::global::*;

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

pub(super) struct RiverInfoProvider {
    status_manager: ZriverStatusManagerV1,
    control: ZriverControlV1,
    output_statuses: Vec<OutputStatus>,
    callback: WmInfoCallback,
}

struct OutputStatus {
    output: WlOutput,
    status: ZriverOutputStatusV1,
    focused_tags: u32,
    urgent_tags: u32,
    active_tags: u32,
    layout_name: Option<String>,
}

impl RiverInfoProvider {
    pub(super) fn bind(
        conn: &mut Connection<State>,
        globals: &Globals,
        callback: WmInfoCallback,
    ) -> Option<Self> {
        Some(Self {
            status_manager: globals.bind(conn, 1..=4).ok()?,
            control: globals.bind(conn, 1..=1).ok()?,
            output_statuses: Vec::new(),
            callback,
        })
    }
}

impl WmInfoProvider for RiverInfoProvider {
    fn as_any(&mut self) -> &mut dyn Any {
        self
    }

    fn new_outut(&mut self, conn: &mut Connection<State>, output: WlOutput) {
        let status =
            self.status_manager
                .get_river_output_status_with_cb(conn, output, output_status_cb);
        self.output_statuses.push(OutputStatus {
            output,
            status,
            focused_tags: 0,
            urgent_tags: 0,
            active_tags: 0,
            layout_name: None,
        });
    }

    fn output_removed(&mut self, conn: &mut Connection<State>, output: WlOutput) {
        let index = self
            .output_statuses
            .iter()
            .position(|s| s.output == output)
            .unwrap();
        let output_status = self.output_statuses.swap_remove(index);
        output_status.status.destroy(conn);
    }

    fn left_click_on_tag(
        &mut self,
        conn: &mut Connection<State>,
        _: WlOutput,
        seat: WlSeat,
        tag: &str,
    ) {
        let tag: u32 = tag.parse().unwrap();
        self.control
            .add_argument(conn, CString::new("set-focused-tags").unwrap());
        self.control
            .add_argument(conn, CString::new((1u32 << (tag - 1)).to_string()).unwrap());
        self.control
            .run_command_with_cb(conn, seat, river_command_cb);
    }

    fn right_click_on_tag(
        &mut self,
        conn: &mut Connection<State>,
        _: WlOutput,
        seat: WlSeat,
        tag: &str,
    ) {
        let tag: u32 = tag.parse().unwrap();
        self.control
            .add_argument(conn, CString::new("toggle-focused-tags").unwrap());
        self.control
            .add_argument(conn, CString::new((1u32 << (tag - 1)).to_string()).unwrap());
        self.control
            .run_command_with_cb(conn, seat, river_command_cb);
    }
}

fn output_status_cb(
    conn: &mut Connection<State>,
    state: &mut State,
    output_status: ZriverOutputStatusV1,
    event: zriver_output_status_v1::Event,
) {
    let river: &mut RiverInfoProvider = state
        .shared_state
        .wm_info_provider
        .as_mut()
        .unwrap()
        .as_any()
        .downcast_mut()
        .unwrap();

    let status = river
        .output_statuses
        .iter_mut()
        .find(|s| s.status == output_status)
        .unwrap();

    use zriver_output_status_v1::Event;
    match event {
        Event::FocusedTags(tags) => status.focused_tags = tags,
        Event::ViewTags(tags) => {
            status.active_tags = tags
                .chunks_exact(4)
                .map(|bytes| u32::from_ne_bytes(bytes.try_into().unwrap()))
                .fold(0, |a, b| a | b);
        }
        Event::UrgentTags(tags) => status.urgent_tags = tags,
        Event::LayoutName(ln) => status.layout_name = Some(ln.to_string_lossy().into()),
        Event::LayoutNameClear => status.layout_name = None,
    }

    let info = WmInfo {
        layout_name: status.layout_name.clone(),
        tags: (1..10)
            .map(|tag| Tag {
                name: tag.to_string(),
                is_focused: status.focused_tags & (1 << (tag - 1)) != 0,
                is_active: status.active_tags & (1 << (tag - 1)) != 0,
                is_urgent: status.urgent_tags & (1 << (tag - 1)) != 0,
            })
            .collect(),
    };

    let output = status.output;
    (river.callback)(conn, state, output, info);
}

fn river_command_cb(
    conn: &mut Connection<State>,
    state: &mut State,
    _: ZriverCommandCallbackV1,
    event: zriver_command_callback_v1::Event,
) {
    if let zriver_command_callback_v1::Event::Failure(msg) = event {
        state.set_error(conn, msg.to_string_lossy())
    }
}