From 46e2c7448c3add1059c360845d7a0c26569c00d0 Mon Sep 17 00:00:00 2001 From: user Date: Sat, 1 May 2021 14:39:48 +0200 Subject: [PATCH] added support for transient windows transient windows float but stay on the `VirtualScreen` their parent window is on --- src/clients.rs | 96 ++++++++++++++++++++++++++++++++++++++++++-------- src/state.rs | 30 +++++++++++----- src/xlib.rs | 27 ++++++++++++-- xinitrc | 6 ++++ 4 files changed, 134 insertions(+), 25 deletions(-) create mode 100644 xinitrc diff --git a/src/clients.rs b/src/clients.rs index 5aa63c4..79f97ce 100644 --- a/src/clients.rs +++ b/src/clients.rs @@ -17,6 +17,7 @@ mod client { pub(crate) window: Window, pub(crate) size: (i32, i32), pub(crate) position: (i32, i32), + pub(crate) transient_for: Option, } impl Default for Client { @@ -25,6 +26,7 @@ mod client { window: 0, size: (100, 100), position: (0, 0), + transient_for: None, } } } @@ -35,6 +37,16 @@ mod client { window, size, position, + transient_for: None, + } + } + + pub fn new_transient(window: Window, size: (i32, i32), transient: Window) -> Self { + Self { + window, + size, + transient_for: Some(transient), + ..Default::default() } } @@ -44,6 +56,10 @@ mod client { ..Default::default() } } + + pub fn is_transient(&self) -> bool { + self.transient_for.is_some() + } } impl Hash for Client { @@ -278,18 +294,32 @@ impl ClientState { } } - pub fn insert(&mut self, client: Client) -> Option<&Client> { + pub fn insert(&mut self, mut client: Client) -> Option<&Client> { let key = client.key(); - self.clients.insert(key, client); + if client.is_transient() && self.contains(&client.transient_for.unwrap()) { + let transient = self.get(&client.transient_for.unwrap()).unwrap(); - if let Some(vs) = self.virtual_screens.front_mut() { - vs.insert(&key); + client.position = { + ( + transient.position.0 + (transient.size.0 - client.size.0) / 2, + transient.position.1 + (transient.size.1 - client.size.1) / 2, + ) + }; + + self.floating_clients.insert(key, client); + } else { + self.clients.insert(key, client); + + if let Some(vs) = self.virtual_screens.front_mut() { + vs.insert(&key); + } } self.focus_client(&key); - self.clients.get(&key) + // TODO: eventually make this function return a `ClientEntry` instead of an `Option`. + self.get(&key).into_option() } pub fn remove(&mut self, key: &K) @@ -315,20 +345,47 @@ impl ClientState { self.floating_clients.iter() } + fn iter_all_clients(&self) -> impl Iterator { + self.floating_clients.iter().chain(self.clients.iter()) + } + pub fn iter_hidden(&self) -> impl Iterator { - self.clients - .iter() - .filter(move |&(k, _)| !self.virtual_screens.front().unwrap().contains(k)) + self.iter_all_clients() + .filter(move |&(k, _)| !self.is_client_visible(k)) } pub fn iter_visible(&self) -> impl Iterator { - self.iter_floating().chain(self.iter_current_screen()) + self.iter_all_clients() + .filter(move |&(k, _)| self.is_client_visible(k)) } pub fn iter_current_screen(&self) -> impl Iterator { self.clients .iter() - .filter(move |&(k, _)| self.virtual_screens.front().unwrap().contains(k)) + .filter(move |&(k, _)| self.current_vs().contains(k)) + } + + /// Returns reference to the current `VirtualScreen`. + fn current_vs(&self) -> &VirtualScreen { + // there is always at least one (1) virtual screen. + self.virtual_screens.front().unwrap() + } + + fn is_client_visible(&self, key: &K) -> bool + where + K: ClientKey, + { + match self.get(key) { + ClientEntry::Floating(c) => { + if let Some(transient_for) = c.transient_for { + self.is_client_visible(&transient_for) + } else { + true + } + } + ClientEntry::Tiled(_) => self.current_vs().contains(key), + _ => false, + } } pub fn get(&self, key: &K) -> ClientEntry<&Client> @@ -407,10 +464,18 @@ impl ClientState { self.floating_clients.insert(key, client); self.remove_from_virtual_screens(&key); } - (None, Some(client)) => { - self.clients.insert(key, client); - if let Some(vs) = self.virtual_screens.front_mut() { - vs.insert(&key); + (None, Some(floating_client)) => { + // transient clients cannot be tiled + match floating_client.is_transient() { + true => { + self.floating_clients.insert(key, floating_client); + } + false => { + self.clients.insert(key, floating_client); + if let Some(vs) = self.virtual_screens.front_mut() { + vs.insert(&key); + } + } } } _ => { @@ -539,12 +604,14 @@ impl ClientState { let width = width / (1 + i32::from(!vs.aux.is_empty())); // make sure we dont devide by 0 + // height is max height / number of clients in the stack let master_height = height / match NonZeroI32::new(vs.master.len() as i32) { Some(i) => i.get(), None => 1, }; + // height is max height / number of clients in the stack let aux_height = height / match NonZeroI32::new(vs.aux.len() as i32) { Some(i) => i.get(), @@ -559,6 +626,7 @@ impl ClientState { // add repeating height for each window and x pos for each window .zip(repeat(master_height).zip(repeat(0i32))) .chain( + // same things for aux stack vs.aux .iter() .enumerate() diff --git a/src/state.rs b/src/state.rs index 3b99c1d..fb2a631 100644 --- a/src/state.rs +++ b/src/state.rs @@ -87,12 +87,12 @@ impl WindowManager { self.add_keybind(KeyBinding::new( self.xlib.make_key("T", Mod1Mask), - |wm, _| wm.spawn("xterm", &[]), + |wm, _| wm.spawn("alacritty", &[]), )); self.add_keybind(KeyBinding::new( self.xlib.make_key("Return", Mod1Mask | ShiftMask), - |wm, _| wm.spawn("xterm", &[]), + |wm, _| wm.spawn("alacritty", &[]), )); self.add_keybind(KeyBinding::new( @@ -321,10 +321,6 @@ impl WindowManager { Direction::Right => self.clients.rotate_right(), } - self.clients - .iter_hidden() - .for_each(|(_, c)| self.xlib.hide_client(c)); - self.arrange_clients(); // focus first client in all visible clients @@ -335,12 +331,20 @@ impl WindowManager { } } + fn hide_hidden_clients(&self) { + self.clients + .iter_hidden() + .for_each(|(_, c)| self.xlib.hide_client(c)); + } + fn arrange_clients(&mut self) { let (width, height) = self.xlib.dimensions(); self.clients.arrange_virtual_screen(width, height, Some(2)); + self.hide_hidden_clients(); + self.clients - .iter_current_screen() + .iter_visible() .for_each(|(_, c)| self.xlib.move_resize_client(c)); self.clients @@ -371,7 +375,17 @@ impl WindowManager { } fn new_client(&mut self, window: Window) { - self.clients.insert(Client::new_default(window)).unwrap(); + let client = if let Some(transient_window) = self.xlib.get_transient_for_window(window) { + Client::new_transient( + window, + self.xlib.get_window_size(window).unwrap_or((100, 100)), + transient_window, + ) + } else { + Client::new_default(window) + }; + + self.clients.insert(client).unwrap(); self.xlib.map_window(window); self.focus_client(&window); diff --git a/src/xlib.rs b/src/xlib.rs index 45ec091..154d904 100644 --- a/src/xlib.rs +++ b/src/xlib.rs @@ -4,10 +4,10 @@ use std::{ffi::CString, rc::Rc}; use x11::xlib::{ self, Atom, ButtonPressMask, CWEventMask, ControlMask, EnterWindowMask, FocusChangeMask, LockMask, Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask, PointerMotionMask, - PropertyChangeMask, ShiftMask, StructureNotifyMask, SubstructureNotifyMask, + PropertyChangeMask, ShiftMask, Status, StructureNotifyMask, SubstructureNotifyMask, SubstructureRedirectMask, Window, XCloseDisplay, XConfigureRequestEvent, XDefaultScreen, - XEvent, XInternAtom, XKillClient, XMapWindow, XOpenDisplay, XRaiseWindow, XRootWindow, XSync, - XWarpPointer, + XEvent, XGetTransientForHint, XInternAtom, XKillClient, XMapWindow, XOpenDisplay, XRaiseWindow, + XRootWindow, XSync, XWarpPointer, }; use xlib::GrabModeAsync; @@ -321,6 +321,27 @@ impl XLib { } } + pub fn get_window_size(&self, window: Window) -> Option<(i32, i32)> { + let mut wa = + unsafe { std::mem::MaybeUninit::::zeroed().assume_init() }; + + if unsafe { xlib::XGetWindowAttributes(self.dpy(), window, &mut wa) != 0 } { + Some((wa.width, wa.height)) + } else { + None + } + } + + pub fn get_transient_for_window(&self, window: Window) -> Option { + let mut transient_for: Window = 0; + + if unsafe { XGetTransientForHint(self.dpy(), window, &mut transient_for) != 0 } { + Some(transient_for) + } else { + None + } + } + pub fn configure_window(&self, event: &XConfigureRequestEvent) { let mut wc = xlib::XWindowChanges { x: event.x, diff --git a/xinitrc b/xinitrc new file mode 100644 index 0000000..1eca442 --- /dev/null +++ b/xinitrc @@ -0,0 +1,6 @@ +#!/bin/sh + +/usr/bin/xset b off +/usr/bin/xsetroot -solid darkslategrey +/usr/bin/feh --bg-fill "/mnt/storage/rust/wm/starship.jpg" +exec /mnt/storage/rust/wm/target/release/wm