summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bar.rs9
-rw-r--r--src/event_loop.rs4
-rw-r--r--src/main.rs5
-rw-r--r--src/output.rs24
-rw-r--r--src/shared_state.rs4
-rw-r--r--src/state.rs42
-rw-r--r--src/status_cmd.rs33
-rw-r--r--src/utils.rs30
-rw-r--r--src/wm_info_provider.rs19
-rw-r--r--src/wm_info_provider/hyprland.rs174
-rw-r--r--src/wm_info_provider/river.rs23
11 files changed, 292 insertions, 75 deletions
diff --git a/src/bar.rs b/src/bar.rs
index 2ca9b6c..d616008 100644
--- a/src/bar.rs
+++ b/src/bar.rs
@@ -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);
}