diff options
| author | MaxVerevkin <maxxverrr@gmail.com> | 2023-09-07 00:47:28 +0300 |
|---|---|---|
| committer | MaxVerevkin <maxxverrr@gmail.com> | 2023-09-07 00:47:28 +0300 |
| commit | 74cc46a2383db5538d7e0392e4b79b7287775e9b (patch) | |
| tree | 6f5c2c026c0822dd71a645836eb085677a090d4c /src/wm_info_provider | |
| parent | eefff9a9cc2ee6f17eab3431cc808d5a5e7d31fc (diff) | |
| download | i3bar-mango-74cc46a2383db5538d7e0392e4b79b7287775e9b.tar.gz | |
hyprland again
Diffstat (limited to 'src/wm_info_provider')
| -rw-r--r-- | src/wm_info_provider/hyprland.rs | 174 | ||||
| -rw-r--r-- | src/wm_info_provider/river.rs | 23 |
2 files changed, 188 insertions, 9 deletions
diff --git a/src/wm_info_provider/hyprland.rs b/src/wm_info_provider/hyprland.rs new file mode 100644 index 0000000..52d8325 --- /dev/null +++ b/src/wm_info_provider/hyprland.rs @@ -0,0 +1,174 @@ +use std::io::{self, Write}; +use std::os::fd::AsRawFd; +use std::os::unix::net::UnixStream; + +use serde::de::DeserializeOwned; +use wayrs_client::{Connection, IoMode}; + +use super::*; +use crate::event_loop::{self, EventLoop}; +use crate::state::State; +use crate::utils::read_to_vec; + +pub struct Hyprland { + ipc: Ipc, + workspaces: Vec<IpcWorkspace>, + active_id: u32, +} + +impl Hyprland { + pub fn new(event_loop: &mut EventLoop<(Connection<State>, State)>) -> Option<Self> { + let his = std::env::var("HYPRLAND_INSTANCE_SIGNATURE").ok()?; + let ipc = Ipc::new(&his).ok()?; + let active_id = ipc.query_json::<IpcWorkspace>("j/activeworkspace").ok()?.id; + let mut this = Self { + ipc, + workspaces: Vec::new(), + active_id, + }; + this.fetch_workspaces().ok()?; + event_loop.register_with_fd(this.ipc.sock2.as_raw_fd(), |state| { + match hyprland_cb(&mut state.0, &mut state.1) { + Ok(()) => Ok(event_loop::Action::Keep), + Err(e) => { + state.1.set_error(&mut state.0, e); + state.0.flush(IoMode::Blocking)?; + Ok(event_loop::Action::Unregister) + } + } + }); + Some(this) + } + + fn fetch_workspaces(&mut self) -> io::Result<()> { + self.workspaces = self.ipc.query_json::<Vec<IpcWorkspace>>("j/workspaces")?; + self.workspaces.sort_unstable_by_key(|x| x.id); + Ok(()) + } +} + +impl WmInfoProvider for Hyprland { + fn new_ouput(&mut self, _: &mut Connection<State>, _: WlOutput) {} + + fn output_removed(&mut self, _: &mut Connection<State>, _: WlOutput) {} + + fn get_tags(&self, output: &Output) -> Vec<Tag> { + self.workspaces + .iter() + .filter(|ws| ws.monitor == output.name) + .map(|ws| Tag { + id: ws.id, + name: ws.name.clone(), + is_focused: ws.id == self.active_id, + is_active: true, + is_urgent: false, + }) + .collect() + } + + fn get_layout_name(&self, _: &Output) -> Option<String> { + None + } + + fn get_mode_name(&self, _: &Output) -> Option<String> { + None + } + + fn click_on_tag( + &mut self, + _: &mut Connection<State>, + _: WlOutput, + _: WlSeat, + tag_id: u32, + btn: PointerBtn, + ) { + if btn == PointerBtn::Left { + let _ = self.ipc.exec(&format!("/dispatch workspace {tag_id}")); + } + } + + fn as_any(&mut self) -> &mut dyn Any { + self + } +} + +fn hyprland_cb(conn: &mut Connection<State>, state: &mut State) -> io::Result<()> { + let hyprland = state.shared_state.get_hyprland().unwrap(); + let mut updated = false; + loop { + match hyprland.ipc.next_event() { + Ok(event) => { + if let Some(active_ws) = event.strip_prefix("workspace>>") { + hyprland.active_id = active_ws.parse().unwrap(); + updated = true; + } else if event.contains("workspace>>") { + hyprland.fetch_workspaces()?; + updated = true; + } + } + Err(e) if e.kind() == io::ErrorKind::WouldBlock => break, + Err(e) => return Err(e), + } + } + if updated { + state.tags_updated(conn, None); + } + Ok(()) +} + +struct Ipc { + sock1_path: String, + sock2: UnixStream, + sock2_buf: Vec<u8>, +} + +impl Ipc { + fn new(his: &str) -> io::Result<Self> { + let sock1_path = format!("/tmp/hypr/{his}/.socket.sock"); + let sock2_path = format!("/tmp/hypr/{his}/.socket2.sock"); + let sock2 = UnixStream::connect(sock2_path)?; + sock2.set_nonblocking(true)?; + Ok(Self { + sock1_path, + sock2, + sock2_buf: Vec::new(), + }) + } + + fn exec(&self, cmd: &str) -> io::Result<()> { + let mut sock = UnixStream::connect(&self.sock1_path)?; + sock.write_all(cmd.as_bytes())?; + sock.flush()?; + Ok(()) + } + + fn query_json<T: DeserializeOwned>(&self, cmd: &str) -> io::Result<T> { + let mut sock = UnixStream::connect(&self.sock1_path)?; + sock.write_all(cmd.as_bytes())?; + sock.flush()?; + serde_json::from_reader(&mut sock).map_err(Into::into) + } + + fn next_event(&mut self) -> io::Result<String> { + loop { + if let Some(i) = memchr::memchr(b'\n', &self.sock2_buf) { + let event = String::from_utf8_lossy(&self.sock2_buf[..i]).into_owned(); + self.sock2_buf.drain(..=i); + return Ok(event); + } + if read_to_vec(&mut self.sock2, &mut self.sock2_buf)? == 0 { + return Err(io::Error::new( + io::ErrorKind::BrokenPipe, + "hyprland socked disconnected", + )); + } + } + } +} + +#[derive(Debug, serde::Deserialize)] +struct IpcWorkspace { + id: u32, + name: String, + monitor: String, +} diff --git a/src/wm_info_provider/river.rs b/src/wm_info_provider/river.rs index 90c6cd1..d8e9c3b 100644 --- a/src/wm_info_provider/river.rs +++ b/src/wm_info_provider/river.rs @@ -78,12 +78,13 @@ impl WmInfoProvider for RiverInfoProvider { output_status.status.destroy(conn); } - fn get_tags(&self, output: WlOutput) -> Vec<Tag> { - let Some(status) = self.output_statuses.iter().find(|s| s.output == output) else { + fn get_tags(&self, output: &Output) -> Vec<Tag> { + let Some(status) = self.output_statuses.iter().find(|s| s.output == output.wl) else { return Vec::new(); }; (1..=self.max_tag) .map(|tag| Tag { + id: tag as u32, name: tag.to_string(), is_focused: status.focused_tags & (1 << (tag - 1)) != 0, is_active: status.active_tags & (1 << (tag - 1)) != 0, @@ -92,12 +93,15 @@ impl WmInfoProvider for RiverInfoProvider { .collect() } - fn get_layout_name(&self, output: WlOutput) -> Option<String> { - let status = self.output_statuses.iter().find(|s| s.output == output)?; + fn get_layout_name(&self, output: &Output) -> Option<String> { + let status = self + .output_statuses + .iter() + .find(|s| s.output == output.wl)?; status.layout_name.clone() } - fn get_mode_name(&self, _output: WlOutput) -> Option<String> { + fn get_mode_name(&self, _output: &Output) -> Option<String> { self.seat_status.mode.clone() } @@ -106,18 +110,19 @@ impl WmInfoProvider for RiverInfoProvider { conn: &mut Connection<State>, _output: WlOutput, seat: WlSeat, - tag: &str, + tag_id: u32, btn: PointerBtn, ) { - let tag: u32 = tag.parse().unwrap(); let cmd = match btn { PointerBtn::Left => cstr!("set-focused-tags"), PointerBtn::Right => cstr!("toggle-focused-tags"), _ => return, }; self.control.add_argument(conn, cmd.to_owned()); - self.control - .add_argument(conn, CString::new((1u32 << (tag - 1)).to_string()).unwrap()); + self.control.add_argument( + conn, + CString::new((1u32 << (tag_id - 1)).to_string()).unwrap(), + ); self.control .run_command_with_cb(conn, seat, river_command_cb); } |
