summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/clients.rs206
1 files changed, 181 insertions, 25 deletions
diff --git a/src/clients.rs b/src/clients.rs
index 341f73c..23780b5 100644
--- a/src/clients.rs
+++ b/src/clients.rs
@@ -1,17 +1,20 @@
use xcb::{
x::{
- ConfigureNotifyEvent, ConfigureRequestEvent, DestroyNotifyEvent, MapRequestEvent,
- UnmapNotifyEvent,
+ ConfigWindow, ConfigureNotifyEvent, ConfigureRequestEvent, ConfigureWindow,
+ DestroyNotifyEvent, EventMask, GetWindowAttributes, GetWindowAttributesReply,
+ MapRequestEvent, MapWindow, SendEvent, SendEventDest, UnmapNotifyEvent, Window,
},
xinerama::{self, ScreenInfo},
- Extension,
+ BaseEvent, Connection, Extension, Xid,
};
use crate::{error::*, WM};
impl WM<'_> {
/// Update the client state's recorded monitors and monitor sizes
- pub(crate) fn update_geometry(&mut self) -> Result<()> {
+ /// Returns true if any values changed, meaning windows should be re-tiled.
+ pub(crate) fn update_geometry(&mut self) -> Result<bool> {
+ let mut dirty = false;
if self
.conn
.active_extensions()
@@ -23,16 +26,18 @@ impl WM<'_> {
// Monitor removed, move its clients away
if reply.screen_info().len() > self.clients.monitor_count() {
+ dirty = true;
self.clients.truncate_screens(reply.screen_info().len());
}
// Update screen info & add new client lists if needed
for (i, monitor) in reply.screen_info().iter().enumerate() {
- self.clients.set_screen_info(i, *monitor);
+ dirty |= self.clients.set_monitor_geometry(i, (*monitor).into());
}
} else {
// Only one screen
if self.clients.monitor_count() > 1 {
+ dirty = true;
self.clients.truncate_screens(1);
}
@@ -43,9 +48,9 @@ impl WM<'_> {
.nth(self.screen_num as usize)
.ok_or(Error::NoSuchScreen)?;
- self.clients.set_screen_info(
+ dirty |= self.clients.set_monitor_geometry(
0,
- ScreenInfo {
+ MonitorGeometry {
x_org: 0,
y_org: 0,
width: screen.width_in_pixels(),
@@ -54,23 +59,92 @@ impl WM<'_> {
);
}
- Ok(())
+ Ok(dirty)
}
- pub(crate) fn handle_configure_request(&mut self, _e: ConfigureRequestEvent) -> Result<()> {
- todo!()
+ /// Update our monitor geometry if the root window is reconfigured
+ pub(crate) fn handle_configure_notify(&mut self, e: ConfigureNotifyEvent) -> Result<()> {
+ if e.window() == self.root && self.update_geometry()? {
+ self.clients.rearrange(self.conn)?;
+ }
+
+ Ok(())
}
- pub(crate) fn handle_configure_notify(&mut self, _e: ConfigureNotifyEvent) -> Result<()> {
- todo!()
+
+ /// Handle a configure request, by checking it's valid and performing it if so
+ pub(crate) fn handle_configure_request(&mut self, e: ConfigureRequestEvent) -> Result<()> {
+ if let Some(c) = self.clients.find_client_mut(e.window()) {
+ // TODO: Allow changing some properties:
+ // - Border width
+ // - Size and position if floating
+ c.configure_notify(self.conn)?;
+ } else {
+ // Configure it as requested, and sort the rest when we actually map the window
+ self.conn
+ .check_request(self.conn.send_request_checked(&ConfigureWindow {
+ window: e.window(),
+ value_list: &[
+ ConfigWindow::X(e.x().into()),
+ ConfigWindow::Y(e.y().into()),
+ ConfigWindow::Height(e.height().into()),
+ ConfigWindow::Width(e.height().into()),
+ ConfigWindow::BorderWidth(e.border_width().into()),
+ ConfigWindow::Sibling(e.parent()),
+ ConfigWindow::StackMode(e.stack_mode()),
+ ],
+ }))?
+ }
+
+ Ok(())
}
- pub(crate) fn handle_destroy_notify(&mut self, _e: DestroyNotifyEvent) -> Result<()> {
- todo!()
+
+ /// Handle a destroyed window, removing it from the client list and rearranging.
+ pub(crate) fn handle_destroy_notify(&mut self, e: DestroyNotifyEvent) -> Result<()> {
+ if self.clients.remove_client(e.window()).is_some() {
+ self.clients.rearrange(self.conn)?;
+ }
+
+ Ok(())
}
- pub(crate) fn handle_map_request(&mut self, _e: MapRequestEvent) -> Result<()> {
- todo!()
+
+ /// Map a window on request, starting to manage it if needed.
+ pub(crate) fn handle_map_request(&mut self, e: MapRequestEvent) -> Result<()> {
+ // Ignore already managed windows
+ if self.clients.find_client_mut(e.window()).is_some() {
+ return Ok(());
+ }
+
+ let attrs = self.conn.wait_for_reply(
+ self.conn
+ .send_request(&GetWindowAttributes { window: e.window() }),
+ )?;
+ if attrs.override_redirect() {
+ return Ok(());
+ }
+
+ // Start managing, and map window
+ self.clients.manage(e.window(), &attrs)?;
+ self.conn.send_request(&MapWindow { window: e.window() });
+
+ // TODO: clear focus
+
+ self.conn.flush()?;
+
+ Ok(())
}
- pub(crate) fn handle_unmap_notify(&mut self, _e: UnmapNotifyEvent) -> Result<()> {
- todo!()
+
+ /// Handle a window being unmapped by updating its client state, or stop managing it.
+ pub(crate) fn handle_unmap_notify(&mut self, e: UnmapNotifyEvent) -> Result<()> {
+ if self.clients.find_client_mut(e.window()).is_some() {
+ if e.is_from_send_event() {
+ // TODO: set client state to withdrawn
+ } else {
+ self.clients.remove_client(e.window());
+ // TODO: 'disown' the window - unmange(c, 0)
+ }
+ }
+
+ Ok(())
}
}
@@ -86,13 +160,26 @@ pub struct MonitorInfo {
clients: Vec<Client>,
/// The monitor's geometry
- screen_info: ScreenInfo,
+ screen_info: MonitorGeometry,
}
-/// Information about a single client / window
-#[derive(Debug)]
-pub struct Client {
- // TODO
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct MonitorGeometry {
+ pub x_org: i16,
+ pub y_org: i16,
+ pub width: u16,
+ pub height: u16,
+}
+
+impl From<ScreenInfo> for MonitorGeometry {
+ fn from(value: ScreenInfo) -> Self {
+ Self {
+ x_org: value.x_org,
+ y_org: value.y_org,
+ width: value.width,
+ height: value.height,
+ }
+ }
}
impl ClientState {
@@ -107,24 +194,93 @@ impl ClientState {
}
/// Set the info for the given screen, resizing the monitor list if necessary.
- pub fn set_screen_info(&mut self, i: usize, info: ScreenInfo) {
+ /// Returns true if the new info is different from the old one.
+ pub fn set_monitor_geometry(&mut self, i: usize, info: MonitorGeometry) -> bool {
while i >= self.0.len() {
self.0.push(MonitorInfo::default())
}
+ let dirty = self.0[i].screen_info != info;
self.0[i].screen_info = info;
+
+ dirty
}
/// Get the amount of monitors this state is currently aware of
pub fn monitor_count(&self) -> usize {
self.0.len()
}
+
+ /// Find the [`Client`] corresponding to the given window
+ pub fn find_client_mut(&mut self, window: Window) -> Option<&mut Client> {
+ self.0
+ .iter_mut()
+ .flat_map(|mi| mi.clients.iter_mut())
+ .find(|c| c.window == window)
+ }
+
+ /// Remove the client associated with the given window.
+ /// This doesn't perform any of the associated X11 stuff
+ pub fn remove_client(&mut self, window: Window) -> Option<Client> {
+ for mon in self.0.iter_mut() {
+ if let Some(pos) = mon.clients.iter().position(|c| c.window == window) {
+ return Some(mon.clients.remove(pos));
+ }
+ }
+ None
+ }
+
+ /// Rearrange all clients, reconfiguring them as needed.
+ pub fn rearrange(&mut self, _conn: &Connection) -> Result<()> {
+ todo!()
+ }
+
+ /// Start managing the given window, adding it to the client list and ensuring its configuration is valid.
+ fn manage(&self, _window: Window, _attrs: &GetWindowAttributesReply) -> Result<()> {
+ todo!()
+ }
+}
+
+/// Information about a single client / window
+#[derive(Debug)]
+pub struct Client {
+ /// The corresponding X11 window
+ window: Window,
+
+ x: i16,
+ y: i16,
+ width: u16,
+ height: u16,
+ border_width: u16,
+}
+
+impl Client {
+ fn configure_notify(&self, conn: &Connection) -> Result<()> {
+ conn.check_request(conn.send_request_checked(&SendEvent {
+ destination: SendEventDest::Window(self.window),
+ event_mask: EventMask::STRUCTURE_NOTIFY,
+ event: &ConfigureNotifyEvent::new(
+ self.window,
+ self.window,
+ Window::none(),
+ self.x,
+ self.y,
+ self.width,
+ self.height,
+ self.border_width,
+ false,
+ ),
+ propagate: false,
+ }))?;
+
+ Ok(())
+ }
}
impl Default for MonitorInfo {
fn default() -> Self {
Self {
clients: vec![],
- screen_info: ScreenInfo {
+ screen_info: MonitorGeometry {
x_org: 0,
y_org: 0,
width: 0,