diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/clients/client.rs | 65 | ||||
-rw-r--r-- | src/clients/mod.rs | 19 | ||||
-rw-r--r-- | src/main.rs | 49 |
3 files changed, 105 insertions, 28 deletions
diff --git a/src/clients/client.rs b/src/clients/client.rs index c9fa645..43d4c54 100644 --- a/src/clients/client.rs +++ b/src/clients/client.rs @@ -1,7 +1,8 @@ use xcb::{ x::{ - ChangeProperty, ChangeWindowAttributes, ConfigWindow, ConfigureNotifyEvent, - ConfigureWindow, Cw, EventMask, MapWindow, SendEvent, SendEventDest, StackMode, Window, + self, ChangeProperty, ChangeWindowAttributes, ConfigWindow, ConfigureNotifyEvent, + ConfigureWindow, Cw, EventMask, MapWindow, PropMode, SendEvent, SendEventDest, StackMode, + Window, }, VoidCookieChecked, Xid, }; @@ -125,25 +126,40 @@ impl Client { matches!(self.layout_mode, LayoutMode::Tiled) } + /// Whether the client is fullscreen + pub const fn fullscreen(&self) -> bool { + matches!(self.layout_mode, LayoutMode::Fullscreen) + } + /// Set the window as tiled - pub fn set_tiled(&mut self) { + pub fn set_tiled(&mut self, conn: &Connection<'_>) { + let dirty = !matches!(self.layout_mode, LayoutMode::Tiled); self.layout_mode = LayoutMode::Tiled; + if dirty { + self.apply_net_wm_state(conn) + } } /// Set the window as floating pub fn set_floating(&mut self, conn: &Connection<'_>) { - if !matches!(self.layout_mode, LayoutMode::Floating) { + let dirty = !matches!(self.layout_mode, LayoutMode::Floating); + self.layout_mode = LayoutMode::Floating; + + if dirty { conn.send_request(&ConfigureWindow { window: self.window, value_list: &[ConfigWindow::StackMode(StackMode::Above)], }); + self.apply_net_wm_state(conn); } - self.layout_mode = LayoutMode::Floating; } /// Set the window as fullscreen pub fn set_fullscreen(&mut self, conn: &Connection<'_>, mon_geom: &MonitorGeometry) { - if !matches!(self.layout_mode, LayoutMode::Fullscreen) { + let dirty = !matches!(self.layout_mode, LayoutMode::Fullscreen); + self.layout_mode = LayoutMode::Fullscreen; + + if dirty { conn.send_request(&ConfigureWindow { window: self.window, value_list: &[ConfigWindow::StackMode(StackMode::Above)], @@ -157,8 +173,22 @@ impl Client { mon_geom.height, self.border_width, ); + self.apply_net_wm_state(conn); } - self.layout_mode = LayoutMode::Fullscreen; + } + + fn apply_net_wm_state(&self, conn: &Connection<'_>) { + conn.send_request(&ChangeProperty { + mode: PropMode::Replace, + window: self.window, + property: conn.atoms.net_wm_state, + r#type: x::ATOM_ATOM, + data: &[match self.layout_mode { + LayoutMode::Tiled => 0_u32, + LayoutMode::Floating => 0_u32, + LayoutMode::Fullscreen => conn.atoms.net_wm_fullscreen.resource_id(), + }], + }); } /// Ensure this client is currently mapped / visible @@ -188,8 +218,8 @@ impl Client { }) } - /// Sync the non-geometry related properties with EWMH hints - pub fn sync_properties(&mut self, conn: &Connection<'_>, focused: bool) { + /// Sync the non-geometry related hints with EWMH hints + pub fn sync_hints(&mut self, conn: &Connection<'_>, focused: bool) { let Some(mut hints) = hints::Ewm::get(conn, self.window) else { return; }; @@ -204,6 +234,15 @@ impl Client { self.never_focus = hints.input().is_some_and(|i| !i); } + /// Apply any geometry-related EWM hints on the window + pub fn apply_geometry_hints(&mut self, conn: &Connection<'_>, mon_geom: &MonitorGeometry) { + if hints::is_fullscreen(conn, self.window) { + self.set_fullscreen(conn, mon_geom); + } else if hints::is_dialog(conn, self.window) { + self.set_floating(conn); + } + } + /// Set the given window as withdrawn / not withdrawn. pub fn set_withdrawn(&self, conn: &Connection<'_>, withdrawn: bool) -> VoidCookieChecked { conn.send_request_checked(&ChangeProperty { @@ -215,14 +254,6 @@ impl Client { }) } - pub fn update_window_type(&mut self, conn: &Connection<'_>, mon_geom: &MonitorGeometry) { - if hints::is_fullscreen(conn, self.window) { - self.set_fullscreen(conn, mon_geom); - } else if hints::is_dialog(conn, self.window) { - self.set_floating(conn); - } - } - pub fn set_urgent(&mut self, urgent: bool) { self.urgent = urgent; } diff --git a/src/clients/mod.rs b/src/clients/mod.rs index 053b4de..597a336 100644 --- a/src/clients/mod.rs +++ b/src/clients/mod.rs @@ -210,13 +210,15 @@ impl ClientState { BORDER_WIDTH, ); c.set_border(conn, conn.colours.border_normal()); + if floating { c.set_floating(conn); } - c.update_window_type(conn, &mon_geom); - c.ensure_mapped(conn); - c.sync_properties(conn, true); + c.sync_hints(conn, true); + c.apply_geometry_hints(conn, &mon_geom); + + c.ensure_mapped(conn); c.set_event_mask( conn, @@ -266,7 +268,7 @@ impl ClientState { self.unfocus(conn); if let Some(new) = self.set_focused(mon, i) { new.set_border(conn, conn.colours.border_focused()); - new.sync_properties(conn, true); + new.sync_hints(conn, true); if !new.never_focus() { conn.send_request(&SetInputFocus { revert_to: InputFocus::PointerRoot, @@ -332,7 +334,7 @@ impl ClientState { if c.tiled() { c.set_floating(conn); } else { - c.set_tiled(); + c.set_tiled(conn); } self.rearrange_monitor(conn, mon); @@ -345,7 +347,7 @@ impl ClientState { if c.tiled() { c.set_fullscreen(conn, &mon_info.screen_info); } else { - c.set_tiled(); + c.set_tiled(conn); } self.rearrange_monitor(conn, mon); @@ -482,6 +484,11 @@ impl ClientState { const fn focused_mon(&self) -> usize { self.focused.0 } + + /// Get the geometry of the monitor with the given index + pub fn mon_geometry(&self, mon: usize) -> MonitorGeometry { + self.mons[mon].screen_info + } } impl Default for ClientState { diff --git a/src/main.rs b/src/main.rs index 7c6c7bd..122f735 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,8 +29,8 @@ use nix::{ unistd::Pid, }; use xcb::{ - x::{self, PropertyNotifyEvent}, - Connection as RawConnection, Event, Extension, + x::{self, ClientMessageEvent, PropertyNotifyEvent}, + Connection as RawConnection, Event, Extension, Xid, }; pub mod clients; @@ -101,12 +101,13 @@ impl<'a> WM<'a> { Event::X(x::Event::MapRequest(e)) => self.handle_map_request(&e)?, Event::X(x::Event::UnmapNotify(e)) => self.handle_unmap_notify(&e), - // // See focus.rs + // See focus.rs Event::X(x::Event::EnterNotify(e)) => self.handle_enter_notify(&e), Event::X(x::Event::FocusIn(e)) => self.handle_focus_in(&e), - // // See below + // See below Event::X(x::Event::PropertyNotify(e)) => self.handle_property_notify(&e), + Event::X(x::Event::ClientMessage(e)) => self.handle_client_message(&e), _ => {} } } @@ -127,10 +128,48 @@ impl<'a> WM<'a> { if x::ATOM_WM_HINTS == e.atom() { let focused = self.clients.is_focused(e.window()); if let Some(c) = self.clients.find_client_mut(e.window()) { - c.sync_properties(&self.conn, focused); + c.sync_hints(&self.conn, focused); } } } + + /// Handle some common client requests set out by the EWMH spec + fn handle_client_message(&mut self, e: &ClientMessageEvent) { + let Some((mon, pos)) = self.clients.find_client_pos(e.window()) else { + return; + }; + + if e.format() != 32 { + return; + } + + if dbg!(e.r#type()) == dbg!(self.conn.atoms.net_wm_state) { + let x::ClientMessageData::Data32(data) = e.data() else { + unreachable!(); + }; + + if !(data[1] == self.conn.atoms.net_wm_fullscreen.resource_id() + || data[2] == self.conn.atoms.net_wm_fullscreen.resource_id()) + { + return; + } + + let mon_geom = self.clients.mon_geometry(mon); + let c = self.clients.client_mut(mon, pos).unwrap(); + let fullscreen = match data[0] { + 1 => true, + 2 => !c.fullscreen(), + _ => false, + }; + + if fullscreen { + c.set_fullscreen(&self.conn, &mon_geom); + } else { + c.set_tiled(&self.conn); + } + self.clients.rearrange(&self.conn); + } + } } /// Cleanup this process' children and set some flags. |