added support for transient windows

transient windows float but stay on the `VirtualScreen` their parent window is on
This commit is contained in:
user 2021-05-01 14:39:48 +02:00
parent cb0d7fbd18
commit 46e2c7448c
4 changed files with 134 additions and 25 deletions

View file

@ -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<Window>,
}
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<K>(&mut self, key: &K)
@ -315,20 +345,47 @@ impl ClientState {
self.floating_clients.iter()
}
fn iter_all_clients(&self) -> impl Iterator<Item = (&u64, &Client)> {
self.floating_clients.iter().chain(self.clients.iter())
}
pub fn iter_hidden(&self) -> impl Iterator<Item = (&u64, &Client)> {
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<Item = (&u64, &Client)> {
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<Item = (&u64, &Client)> {
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<K>(&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<K>(&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()

View file

@ -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);

View file

@ -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::<xlib::XWindowAttributes>::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<Window> {
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,

6
xinitrc Normal file
View file

@ -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