summaryrefslogtreecommitdiff
path: root/src/wm_info_provider/mango.rs
blob: 82b511c4f1b48e6f27e88cbd570158bb853448fc (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#![allow(clippy::collapsible_else_if)]

use std::io;
use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd};
use std::os::unix::net::UnixStream;
use std::process::{Command, Stdio};

use super::*;
use crate::event_loop;
use crate::utils::read_to_vec;

pub struct MangoInfoProvider {
    mmsg_sock: UnixStream,
    mmsg_buf: Vec<u8>,
    all_tags: Vec<MangoTagsContainer>,
}

#[derive(serde::Deserialize, Debug, Clone)]
struct MangoTag {
    index: u32,
    is_active: bool,
    is_urgent: bool,
    client_count: u32,
}

#[derive(serde::Deserialize, Debug, Clone)]
struct MangoTagsContainer {
    monitor: String,
    tags: Vec<MangoTag>,
}

#[derive(serde::Deserialize, Debug)]
struct MangoIpc {
    all_tags: Vec<MangoTagsContainer>,
}

impl MangoInfoProvider {
    pub fn new() -> Option<Self> {
        let child = Command::new("/usr/bin/mmsg")
            .arg("watch")
            .arg("all-tags")
            .stdout(Stdio::piped())
            .spawn().ok()?;
        let stdout = child.stdout.expect("couldn't get stdout");
        let sock = unsafe { UnixStream::from_raw_fd(stdout.into_raw_fd()) };
        sock.set_nonblocking(true).ok()?;
        Some(Self {
            mmsg_sock: sock,
            mmsg_buf: Vec::new(),
            all_tags: Vec::new(),
        })
    }
    fn next_event(&mut self) -> io::Result<MangoIpc> {
        loop {
            if let Some(i) = memchr::memchr(b'\n', &self.mmsg_buf) {
                let event = String::from_utf8_lossy(&self.mmsg_buf[..i]).into_owned();
                self.mmsg_buf.drain(..=i);
                return Ok(serde_json::from_str(&event)?);
            }
            if read_to_vec(&self.mmsg_sock, &mut self.mmsg_buf)? == 0 {
                return Err(io::Error::new(
                    io::ErrorKind::BrokenPipe,
                    "mmsg child process disconnected",
                ));
            }
        }
    }
}

impl WmInfoProvider for MangoInfoProvider {
    fn register(&self, ev: &mut EventLoop) {
        ev.register_with_fd(self.mmsg_sock.as_raw_fd(), |ctx| {
            loop {
                match ctx.state.shared_state.get_mango().unwrap().next_event() {
                    Ok(event) => {
                        ctx.state.shared_state.get_mango().unwrap().all_tags = event.all_tags;
                        ctx.state.tags_updated(ctx.conn, None);
                    }
                    Err(e) if e.kind() == io::ErrorKind::WouldBlock => break,
                    Err(e) => {
                        ctx.state.set_error(ctx.conn, "mango", e);
                        return Ok(event_loop::Action::Unregister);
                    }
                }
            }
            Ok(event_loop::Action::Keep)
        });
    }
    fn get_tags(&self, output: &Output) -> Vec<Tag> {
        self.all_tags
            .iter()
            .find(|c| c.monitor == output.name)
            .map_or_else(
                || {
                    eprintln!("Couldn't find tags for monitor {}", output.name);
                    Vec::new()
                },
                |container| {
                    container
                        .tags
                        .iter()
                        .enumerate()
                        .map(|(i, mango_tag)| Tag {
                            id: mango_tag.index,
                            name: (i + 1).to_string(),
                            is_focused: mango_tag.is_active,
                            is_active: mango_tag.client_count > 0,
                            is_urgent: mango_tag.is_urgent,
                        })
                        .collect()
                },
            )
    }
    fn get_mode_name(&self, _: &Output) -> Option<String> {
        None
    }

    fn click_on_tag(
        &mut self,
        _conn: &mut Connection<State>,
        _output: &Output,
        _seat: WlSeat,
        _tag_id: Option<u32>,
        _btn: PointerBtn,
    ) {
    }
}