summaryrefslogtreecommitdiff
path: root/src/wm_info_provider/mango.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/wm_info_provider/mango.rs')
-rw-r--r--src/wm_info_provider/mango.rs81
1 files changed, 75 insertions, 6 deletions
diff --git a/src/wm_info_provider/mango.rs b/src/wm_info_provider/mango.rs
index fe5759e..5476f5e 100644
--- a/src/wm_info_provider/mango.rs
+++ b/src/wm_info_provider/mango.rs
@@ -29,6 +29,18 @@ struct MangoMonitor {
active: bool,
tags: Vec<MangoTag>,
layout_symbol: String,
+ #[serde(default)]
+ active_client: Option<MangoActiveClient>,
+}
+
+// When no client is focused, the compositor sends
+// {"id":null,"title":null,"appid":null} instead of omitting the field.
+#[derive(serde::Deserialize, Debug, Clone)]
+struct MangoActiveClient {
+ #[allow(dead_code)]
+ id: Option<u32>,
+ title: Option<String>,
+ appid: Option<String>,
}
@@ -40,7 +52,7 @@ struct MangoIpc {
impl MangoInfoProvider {
pub fn new() -> Option<Self> {
- let child = Command::new("/usr/bin/mmsg")
+ let child = Command::new("mmsg")
.arg("watch")
.arg("all-monitors")
.stdout(Stdio::piped())
@@ -80,6 +92,7 @@ impl WmInfoProvider for MangoInfoProvider {
loop {
let mango = ctx.state.shared_state.get_mango().unwrap();
let mut layout_updated = false;
+ let focused_window_updated;
match mango.next_event() {
Ok(event) => {
if let Some(i) = event.monitors.iter().position(|m| m.active) {
@@ -87,6 +100,15 @@ impl WmInfoProvider for MangoInfoProvider {
layout_updated = true;
}
}
+ focused_window_updated = event.monitors.iter().any(|new_monitor| {
+ let old_client = mango
+ .all_monitors
+ .iter()
+ .find(|m| m.name == new_monitor.name)
+ .and_then(|m| m.active_client.as_ref());
+ old_client.map(|c| (&c.title, &c.appid))
+ != new_monitor.active_client.as_ref().map(|c| (&c.title, &c.appid))
+ });
mango.all_monitors = event.monitors;
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => break,
@@ -99,6 +121,9 @@ impl WmInfoProvider for MangoInfoProvider {
if layout_updated {
ctx.state.layout_name_updated(ctx.conn, None);
}
+ if focused_window_updated {
+ ctx.state.focused_window_updated(ctx.conn, None);
+ }
}
Ok(event_loop::Action::Keep)
});
@@ -107,7 +132,8 @@ impl WmInfoProvider for MangoInfoProvider {
self.all_monitors
.iter()
.find(|c| c.name == output.name)
- .map_or_default(
+ .map_or_else(
+ Vec::new,
|monitor| {
monitor
.tags
@@ -124,9 +150,7 @@ impl WmInfoProvider for MangoInfoProvider {
},
)
}
- // TODO
fn get_layout_name(&self, output: &Output) -> Option<String> {
- // self.layout_symbol.clone()
self.all_monitors
.iter()
.find(|c| c.name == output.name)
@@ -142,17 +166,29 @@ impl WmInfoProvider for MangoInfoProvider {
fn get_mode_name(&self, _: &Output) -> Option<String> {
None
}
+ fn get_focused_window(&self, output: &Output) -> Option<FocusedWindow> {
+ self.all_monitors
+ .iter()
+ .find(|c| c.name == output.name)
+ .and_then(|monitor| monitor.active_client.as_ref())
+ .and_then(|client| {
+ Some(FocusedWindow {
+ app_id: client.appid.clone()?,
+ title: client.title.clone().unwrap_or_default(),
+ })
+ })
+ }
fn click_on_tag(
&mut self,
- conn: &mut Connection<State>,
+ _conn: &mut Connection<State>,
_output: &Output,
_seat: WlSeat,
tag_id: Option<u32>,
_btn: PointerBtn,
) {
if let Some(id) = tag_id {
- let _ = Command::new("/usr/bin/mmsg")
+ let _ = Command::new("mmsg")
.arg("dispatch")
.arg(format!("view,{}", id))
.stdout(Stdio::piped())
@@ -160,3 +196,36 @@ impl WmInfoProvider for MangoInfoProvider {
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ // Real output shape of `mmsg watch all-monitors` (mangowm 0.15.4)
+ #[test]
+ fn parse_all_monitors_event() {
+ let event = r#"{"monitors":[{"name":"DP-1","active":true,"is_hdr":false,"is_vrr":false,"x":1920,"y":0,"width":1920,"height":1080,"scale":1,"layout_index":0,"layout_symbol":"T","last_open_surface":"i3bar-river","tags":[{"index":1,"is_active":true,"is_urgent":false,"layout":"T","client_count":1}],"active_tags":[1],"active_client":{"id":3,"title":"some title","appid":"emacs"},"keymode":"default","keyboardlayout":"English (US)"}]}"#;
+ let ipc: MangoIpc = serde_json::from_str(event).unwrap();
+ let client = ipc.monitors[0].active_client.as_ref().unwrap();
+ assert_eq!(client.appid.as_deref(), Some("emacs"));
+ assert_eq!(client.title.as_deref(), Some("some title"));
+ }
+
+ // With no focused client the compositor sends nulls instead of omitting the field
+ #[test]
+ fn parse_all_monitors_event_no_focused_client() {
+ let event = r#"{"monitors":[{"name":"DP-1","active":true,"layout_symbol":"T","tags":[{"index":1,"is_active":false,"is_urgent":false,"client_count":0}],"active_client":{"id":null,"title":null,"appid":null}}]}"#;
+ let ipc: MangoIpc = serde_json::from_str(event).unwrap();
+ let client = ipc.monitors[0].active_client.as_ref().unwrap();
+ assert_eq!(client.appid, None);
+ assert_eq!(client.title, None);
+ }
+
+ // Older mangowc versions (<0.14.x) have no active_client field at all
+ #[test]
+ fn parse_all_monitors_event_without_active_client() {
+ let event = r#"{"monitors":[{"name":"DP-1","active":true,"layout_symbol":"T","tags":[{"index":1,"is_active":false,"is_urgent":false,"client_count":0}]}]}"#;
+ let ipc: MangoIpc = serde_json::from_str(event).unwrap();
+ assert!(ipc.monitors[0].active_client.is_none());
+ }
+}