diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/bar.rs | 9 | ||||
| -rw-r--r-- | src/event_loop.rs | 4 | ||||
| -rw-r--r-- | src/main.rs | 5 | ||||
| -rw-r--r-- | src/output.rs | 24 | ||||
| -rw-r--r-- | src/shared_state.rs | 4 | ||||
| -rw-r--r-- | src/state.rs | 42 | ||||
| -rw-r--r-- | src/status_cmd.rs | 33 | ||||
| -rw-r--r-- | src/utils.rs | 30 | ||||
| -rw-r--r-- | src/wm_info_provider.rs | 19 | ||||
| -rw-r--r-- | src/wm_info_provider/hyprland.rs | 174 | ||||
| -rw-r--r-- | src/wm_info_provider/river.rs | 23 |
11 files changed, 292 insertions, 75 deletions
@@ -32,7 +32,7 @@ pub struct Bar { tags: Vec<Tag>, layout_name: Option<String>, mode_name: Option<String>, - tags_btns: ButtonManager<String>, + tags_btns: ButtonManager<u32>, tags_computed: Vec<(ColorPair, ComputedText)>, layout_name_computed: Option<ComputedText>, mode_computed: Option<ComputedText>, @@ -119,9 +119,9 @@ impl Bar { x: f64, _y: f64, ) -> anyhow::Result<()> { - if let Some(tag) = self.tags_btns.click(x) { + if let Some(tag_id) = self.tags_btns.click(x) { if let Some(wm) = &mut ss.wm_info_provider { - wm.click_on_tag(conn, self.output.wl, seat, tag, button); + wm.click_on_tag(conn, self.output.wl, seat, *tag_id, button); } } else if let Some((name, instance)) = self.blocks_btns.click(x) { if let Some(cmd) = &mut ss.status_cmd { @@ -212,8 +212,7 @@ impl Bar { continue; }; let comp = compute_tag_label(&tag.name, &ss.config); - self.tags_btns - .push(offset_left, comp.width, tag.name.clone()); + self.tags_btns.push(offset_left, comp.width, tag.id); offset_left += comp.width; self.tags_computed.push((ColorPair { bg, fg }, comp)); } diff --git a/src/event_loop.rs b/src/event_loop.rs index 2300513..d869d80 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -4,9 +4,11 @@ use std::os::fd::RawFd; use anyhow::Result; +type Callback<D> = Box<dyn FnMut(&mut D) -> Result<Action>>; + pub struct EventLoop<D> { pollfds: Vec<libc::pollfd>, - cbs: HashMap<RawFd, Box<dyn FnMut(&mut D) -> Result<Action>>>, + cbs: HashMap<RawFd, Callback<D>>, } pub enum Action { diff --git a/src/main.rs b/src/main.rs index 54c1449..20bc49c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,10 +45,9 @@ fn main() -> anyhow::Result<()> { signal_hook::low_level::pipe::register(SIGUSR1, sig_write)?; let (mut conn, globals) = Connection::connect_and_collect_globals()?; - let state = State::new(&mut conn, &globals, args.config.as_deref()); - conn.flush(IoMode::Blocking)?; - let mut el = EventLoop::<(Connection<State>, State)>::new(); + let state = State::new(&mut conn, &globals, &mut el, args.config.as_deref()); + conn.flush(IoMode::Blocking)?; el.register_with_fd(sig_read, move |(conn, state)| { nix::unistd::read(sig_read, &mut [0; 1])?; diff --git a/src/output.rs b/src/output.rs index 7c492ed..d243d52 100644 --- a/src/output.rs +++ b/src/output.rs @@ -9,9 +9,16 @@ pub struct Output { pub wl: WlOutput, pub reg_name: u32, pub scale: u32, + pub name: String, } -impl Output { +pub struct PendingOutput { + pub wl: WlOutput, + pub reg_name: u32, + pub scale: u32, +} + +impl PendingOutput { pub fn bind(conn: &mut Connection<State>, global: &Global) -> Self { Self { wl: global @@ -21,7 +28,9 @@ impl Output { scale: 1, } } +} +impl Output { pub fn destroy(self, conn: &mut Connection<State>) { self.wl.release(conn); } @@ -37,11 +46,14 @@ fn wl_output_cb(ctx: EventCtx<State, WlOutput>) { .position(|o| o.wl == ctx.proxy) .unwrap(); let output = ctx.state.pending_outputs.swap_remove(i); - ctx.state.register_output( - ctx.conn, - output, - name.to_str().expect("invalid output name"), - ); + let name = String::from_utf8(name.into_bytes()).expect("invalid output name"); + let output = Output { + wl: output.wl, + reg_name: output.reg_name, + scale: output.scale, + name, + }; + ctx.state.register_output(ctx.conn, output); } wl_output::Event::Scale(scale) => { if let Some(bar) = ctx diff --git a/src/shared_state.rs b/src/shared_state.rs index a4ff48f..50ab77a 100644 --- a/src/shared_state.rs +++ b/src/shared_state.rs @@ -19,4 +19,8 @@ impl SharedState { pub fn get_river(&mut self) -> Option<&mut wm_info_provider::RiverInfoProvider> { self.wm_info_provider.as_mut()?.as_any().downcast_mut() } + + pub fn get_hyprland(&mut self) -> Option<&mut wm_info_provider::Hyprland> { + self.wm_info_provider.as_mut()?.as_any().downcast_mut() + } } diff --git a/src/state.rs b/src/state.rs index 4b3a0ca..c4fd0ce 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,5 +1,6 @@ use crate::blocks_cache::BlocksCache; -use crate::output::Output; +use crate::event_loop::EventLoop; +use crate::output::{Output, PendingOutput}; use crate::protocol::*; use crate::wm_info_provider; @@ -9,8 +10,7 @@ use std::path::Path; use wayrs_client::global::{GlobalExt, Globals, GlobalsExt}; use wayrs_client::proxy::Proxy; -use wayrs_client::Connection; -use wayrs_client::EventCtx; +use wayrs_client::{Connection, EventCtx}; use wayrs_utils::cursor::{CursorImage, CursorShape, CursorTheme, ThemedPointer}; use wayrs_utils::seats::{SeatHandler, Seats}; use wayrs_utils::shm_alloc::ShmAlloc; @@ -30,7 +30,7 @@ pub struct State { pointers: Vec<Pointer>, // Outputs that haven't yet advertised their names - pub pending_outputs: Vec<Output>, + pub pending_outputs: Vec<PendingOutput>, pub hidden: bool, pub bars: Vec<Bar>, @@ -54,7 +54,12 @@ struct Pointer { } impl State { - pub fn new(conn: &mut Connection<Self>, globals: &Globals, config_path: Option<&Path>) -> Self { + pub fn new( + conn: &mut Connection<Self>, + globals: &Globals, + event_loop: &mut EventLoop<(Connection<Self>, Self)>, + config_path: Option<&Path>, + ) -> Self { let mut error = Ok(()); let config = Config::new(config_path) @@ -75,7 +80,7 @@ impl State { .map_err(|e| error = Err(e.into())) .ok(); - let wm_info_provider = wm_info_provider::bind(conn, globals, &config.wm); + let wm_info_provider = wm_info_provider::bind(conn, globals, event_loop, &config.wm); let mut this = Self { wl_compositor, @@ -89,7 +94,7 @@ impl State { pending_outputs: globals .iter() .filter(|g| g.is::<WlOutput>()) - .map(|g| Output::bind(conn, g)) + .map(|g| PendingOutput::bind(conn, g)) .collect(), hidden: false, @@ -144,8 +149,8 @@ impl State { .map(|cmd| cmd.output.as_raw_fd()) } - pub fn register_output(&mut self, conn: &mut Connection<Self>, output: Output, name: &str) { - if !self.shared_state.config.output_enabled(name) { + pub fn register_output(&mut self, conn: &mut Connection<Self>, output: Output) { + if !self.shared_state.config.output_enabled(&output.name) { return; } @@ -155,6 +160,10 @@ impl State { let mut bar = Bar::new(conn, self, output); + if let Some(wm) = &self.shared_state.wm_info_provider { + bar.set_tags(wm.get_tags(&bar.output)); + } + if !self.hidden { bar.show(conn, &self.shared_state); } @@ -203,12 +212,7 @@ impl State { pub fn tags_updated(&mut self, conn: &mut Connection<Self>, output: Option<WlOutput>) { self.for_each_bar(output, |bar, ss| { - bar.set_tags( - ss.wm_info_provider - .as_mut() - .unwrap() - .get_tags(bar.output.wl), - ); + bar.set_tags(ss.wm_info_provider.as_mut().unwrap().get_tags(&bar.output)); bar.request_frame(conn); }); } @@ -219,7 +223,7 @@ impl State { ss.wm_info_provider .as_mut() .unwrap() - .get_layout_name(bar.output.wl), + .get_layout_name(&bar.output), ); bar.request_frame(conn); }); @@ -231,7 +235,7 @@ impl State { ss.wm_info_provider .as_mut() .unwrap() - .get_mode_name(bar.output.wl), + .get_mode_name(&bar.output), ); bar.request_frame(conn); }); @@ -270,7 +274,9 @@ impl SeatHandler for State { fn wl_registry_cb(conn: &mut Connection<State>, state: &mut State, event: &wl_registry::Event) { match event { wl_registry::Event::Global(global) if global.is::<WlOutput>() => { - state.pending_outputs.push(Output::bind(conn, global)); + state + .pending_outputs + .push(PendingOutput::bind(conn, global)); } wl_registry::Event::GlobalRemove(name) => { if let Some(bar_index) = state diff --git a/src/status_cmd.rs b/src/status_cmd.rs index 217ddab..0d6f049 100644 --- a/src/status_cmd.rs +++ b/src/status_cmd.rs @@ -1,10 +1,11 @@ -use std::io::{self, BufWriter, ErrorKind, Write}; -use std::os::unix::io::{AsRawFd, RawFd}; +use std::io::{BufWriter, ErrorKind, Write}; +use std::os::unix::io::AsRawFd; use std::process::{Child, ChildStdin, ChildStdout, Command, Stdio}; use anyhow::Result; use crate::i3bar_protocol::{Block, Event, Protocol}; +use crate::utils::read_to_vec; #[derive(Debug)] pub struct StatusCmd { @@ -38,7 +39,7 @@ impl StatusCmd { } pub fn receive_blocks(&mut self) -> Result<Option<Vec<Block>>> { - match read_to_vec(self.output.as_raw_fd(), &mut self.buf) { + match read_to_vec(&mut self.output, &mut self.buf) { Ok(0) => bail!("status command exited"), Ok(_n) => (), Err(e) if e.kind() == ErrorKind::WouldBlock => return Ok(None), @@ -61,29 +62,3 @@ impl StatusCmd { Ok(()) } } - -/// Read from a raw file descriptor to the vector. -/// -/// Appends data at the end of the buffer. Resizes vector as needed. -pub fn read_to_vec(fd: RawFd, buf: &mut Vec<u8>) -> io::Result<usize> { - if buf.capacity() - buf.len() < 1024 { - buf.reserve(buf.capacity().max(1024)); - } - - let res = unsafe { - libc::read( - fd, - buf.as_mut_ptr().add(buf.len()) as *mut libc::c_void, - (buf.capacity() - buf.len()) as libc::size_t, - ) - }; - - if res == -1 { - return Err(io::Error::last_os_error()); - } - - let read = res as usize; - unsafe { buf.set_len(buf.len() + read) }; - - Ok(read) -} diff --git a/src/utils.rs b/src/utils.rs index e45785f..211c6bb 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,8 +1,38 @@ //! Some usefull functions +use std::io; +use std::os::fd::{AsFd, AsRawFd}; + use serde::Deserialize; use serde_json::{Deserializer, Error as JsonError}; +/// Read from a raw file descriptor to the vector. +/// +/// Appends data at the end of the buffer. Resizes vector as needed. +#[allow(clippy::needless_pass_by_ref_mut)] +pub fn read_to_vec(fd: &mut impl AsFd, buf: &mut Vec<u8>) -> io::Result<usize> { + if buf.capacity() - buf.len() < 1024 { + buf.reserve(buf.capacity().max(1024)); + } + + let res = unsafe { + libc::read( + fd.as_fd().as_raw_fd(), + buf.as_mut_ptr().add(buf.len()) as *mut libc::c_void, + (buf.capacity() - buf.len()) as libc::size_t, + ) + }; + + if res == -1 { + return Err(io::Error::last_os_error()); + } + + let read = res as usize; + unsafe { buf.set_len(buf.len() + read) }; + + Ok(read) +} + /// Retuns (`last_line`, `remaining`). See tests for examples. pub fn last_line(s: &[u8]) -> Option<(&[u8], &[u8])> { let mut it = memchr::memrchr_iter(b'\n', s); diff --git a/src/wm_info_provider.rs b/src/wm_info_provider.rs index e9af320..e1004fa 100644 --- a/src/wm_info_provider.rs +++ b/src/wm_info_provider.rs @@ -4,6 +4,8 @@ use wayrs_client::global::*; use wayrs_client::Connection; use crate::config::WmConfig; +use crate::event_loop::EventLoop; +use crate::output::Output; use crate::pointer_btn::PointerBtn; use crate::protocol::*; use crate::state::State; @@ -11,20 +13,23 @@ use crate::state::State; mod river; pub use river::*; +mod hyprland; +pub use hyprland::*; + pub trait WmInfoProvider { fn new_ouput(&mut self, conn: &mut Connection<State>, output: WlOutput); fn output_removed(&mut self, conn: &mut Connection<State>, output: WlOutput); - fn get_tags(&self, output: WlOutput) -> Vec<Tag>; - fn get_layout_name(&self, output: WlOutput) -> Option<String>; - fn get_mode_name(&self, output: WlOutput) -> Option<String>; + fn get_tags(&self, output: &Output) -> Vec<Tag>; + fn get_layout_name(&self, output: &Output) -> Option<String>; + fn get_mode_name(&self, output: &Output) -> Option<String>; fn click_on_tag( &mut self, conn: &mut Connection<State>, output: WlOutput, seat: WlSeat, - tag: &str, + tag_id: u32, btn: PointerBtn, ); @@ -34,17 +39,23 @@ pub trait WmInfoProvider { pub fn bind( conn: &mut Connection<State>, globals: &Globals, + event_loop: &mut EventLoop<(Connection<State>, State)>, config: &WmConfig, ) -> Option<Box<dyn WmInfoProvider>> { if let Some(river) = RiverInfoProvider::bind(conn, globals, config) { return Some(Box::new(river)); } + if let Some(hyprland) = Hyprland::new(event_loop) { + return Some(Box::new(hyprland)); + } + None } #[derive(Debug)] pub struct Tag { + pub id: u32, pub name: String, pub is_focused: bool, pub is_active: bool, 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); } |
