Support client initiated window resizing

Closes #185
This commit is contained in:
Shawn Wallace 2025-09-06 13:28:54 -04:00
parent 0b94ae1eb8
commit 970728d0d9
4 changed files with 155 additions and 52 deletions

View file

@ -6,7 +6,7 @@ pub(crate) mod selection;
mod tests;
use self::event::*;
use crate::xstate::{Decorations, WindowDims, WmHints, WmName, WmNormalHints};
use crate::xstate::{Decorations, MoveResizeDirection, WindowDims, WmHints, WmName, WmNormalHints};
use crate::{X11Selection, XConnection};
use clientside::MyWorld;
use hecs::{Entity, World};
@ -49,7 +49,7 @@ use wayland_protocols::{
xdg_popup::XdgPopup,
xdg_positioner::{Anchor, Gravity, XdgPositioner},
xdg_surface::XdgSurface,
xdg_toplevel::XdgToplevel,
xdg_toplevel::{self, XdgToplevel},
xdg_wm_base::XdgWmBase,
},
xdg_output::zv1::server::zxdg_output_manager_v1::ZxdgOutputManagerV1,
@ -1116,6 +1116,47 @@ impl<S: X11Selection + 'static> InnerServerState<S> {
data.toplevel._move(&last_click_data.0, last_click_data.1);
}
pub fn resize_window(&mut self, window: x::Window, direction: MoveResizeDirection) {
let Some(data) = self
.windows
.get(&window)
.copied()
.and_then(|e| self.world.entity(e).ok())
else {
warn!("Requested resize of unknown window {window:?}");
return;
};
let Some(last_click_data) = data.get::<&LastClickSerial>() else {
warn!("Requested resize of window {window:?} but we don't have a click serial for it");
return;
};
let role = data.get::<&SurfaceRole>();
let Some(SurfaceRole::Toplevel(Some(data))) = role.as_deref() else {
warn!("Requested resize of non toplevel {window:?} ({role:?})");
return;
};
let edge = match direction {
MoveResizeDirection::SizeTopLeft => xdg_toplevel::ResizeEdge::TopLeft,
MoveResizeDirection::SizeTop => xdg_toplevel::ResizeEdge::Top,
MoveResizeDirection::SizeTopRight => xdg_toplevel::ResizeEdge::TopRight,
MoveResizeDirection::SizeRight => xdg_toplevel::ResizeEdge::Right,
MoveResizeDirection::SizeBottomRight => xdg_toplevel::ResizeEdge::BottomRight,
MoveResizeDirection::SizeBottom => xdg_toplevel::ResizeEdge::Bottom,
MoveResizeDirection::SizeBottomLeft => xdg_toplevel::ResizeEdge::BottomLeft,
MoveResizeDirection::SizeLeft => xdg_toplevel::ResizeEdge::Left,
MoveResizeDirection::MoveKeyboard
| MoveResizeDirection::SizeKeyboard
| MoveResizeDirection::Move
| MoveResizeDirection::Cancel => unreachable!(),
};
data.toplevel
.resize(&last_click_data.0, last_click_data.1, edge);
}
pub fn destroy_window(&mut self, window: x::Window) {
if let Some(id) = self.windows.remove(&window) {
self.world.remove::<(x::Window, WindowData)>(id).unwrap();

View file

@ -471,68 +471,87 @@ impl XState {
}
));
}
xcb::Event::X(x::Event::ClientMessage(e)) => match e.r#type() {
x if x == self.atoms.wl_surface_id => {
panic!(concat!(
xcb::Event::X(x::Event::ClientMessage(e)) => {
match e.r#type() {
x if x == self.atoms.wl_surface_id => {
panic!(concat!(
"Xserver should be using WL_SURFACE_SERIAL, not WL_SURFACE_ID\n",
"Your Xwayland is likely too old, it should be version 23.1 or greater."
));
}
x if x == self.atoms.wl_surface_serial => {
let x::ClientMessageData::Data32(data) = e.data() else {
unreachable!();
};
server_state.set_window_serial(e.window(), [data[0], data[1]]);
}
x if x == self.atoms.net_wm_state => {
let x::ClientMessageData::Data32(data) = e.data() else {
unreachable!();
};
let Ok(action) = SetState::try_from(data[0]) else {
warn!("unknown action for _NET_WM_STATE: {}", data[0]);
continue;
};
let prop1 = unsafe { x::Atom::new(data[1]) };
let prop2 = unsafe { x::Atom::new(data[2]) };
}
x if x == self.atoms.wl_surface_serial => {
let x::ClientMessageData::Data32(data) = e.data() else {
unreachable!();
};
server_state.set_window_serial(e.window(), [data[0], data[1]]);
}
x if x == self.atoms.net_wm_state => {
let x::ClientMessageData::Data32(data) = e.data() else {
unreachable!();
};
let Ok(action) = SetState::try_from(data[0]) else {
warn!("unknown action for _NET_WM_STATE: {}", data[0]);
continue;
};
let prop1 = unsafe { x::Atom::new(data[1]) };
let prop2 = unsafe { x::Atom::new(data[2]) };
trace!("_NET_WM_STATE ({action:?}) props: {prop1:?} {prop2:?}");
trace!("_NET_WM_STATE ({action:?}) props: {prop1:?} {prop2:?}");
for prop in [prop1, prop2] {
match prop {
x if x == self.atoms.wm_fullscreen => {
server_state.set_fullscreen(e.window(), action);
for prop in [prop1, prop2] {
match prop {
x if x == self.atoms.wm_fullscreen => {
server_state.set_fullscreen(e.window(), action);
}
_ => {}
}
_ => {}
}
}
}
x if x == self.atoms.active_win => {
server_state.activate_window(e.window());
}
x if x == self.atoms.moveresize => {
let x::ClientMessageData::Data32(data) = e.data() else {
unreachable!();
};
let (_x_root, _y_root) = (data[0], data[1]);
let Ok(direction) = MoveResizeDirection::try_from(data[2]) else {
warn!("unknown direction for _NET_WM_MOVERESIZE: {}", data[2]);
continue;
};
let button = data[3];
// XXX: This can technically be driven by keyboard events and other mouse buttons as well,
// but I haven't found an application that does this yet. We'll cross that bridge when we get to it.
if button != 1 {
warn!("Attempted move/resize of {:?} with non left click button ({button})", e.window());
continue;
x if x == self.atoms.active_win => {
server_state.activate_window(e.window());
}
x if x == self.atoms.moveresize => {
let x::ClientMessageData::Data32(data) = e.data() else {
unreachable!();
};
if matches!(direction, MoveResizeDirection::Move) {
server_state.move_window(e.window());
let (_x_root, _y_root) = (data[0], data[1]);
let Ok(direction) = MoveResizeDirection::try_from(data[2]) else {
warn!("unknown direction for _NET_WM_MOVERESIZE: {}", data[2]);
continue;
};
let button = data[3];
// XXX: This can technically be driven by keyboard events and other mouse buttons as well,
// but I haven't found an application that does this yet. We'll cross that bridge when we get to it.
if button != 1 {
warn!("Attempted move/resize of {:?} with non left click button ({button})", e.window());
continue;
}
match direction {
MoveResizeDirection::Move => {
server_state.move_window(e.window());
}
MoveResizeDirection::SizeTopLeft
| MoveResizeDirection::SizeTop
| MoveResizeDirection::SizeTopRight
| MoveResizeDirection::SizeRight
| MoveResizeDirection::SizeBottomRight
| MoveResizeDirection::SizeBottom
| MoveResizeDirection::SizeBottomLeft
| MoveResizeDirection::SizeLeft => {
server_state.resize_window(e.window(), direction);
}
MoveResizeDirection::SizeKeyboard
| MoveResizeDirection::MoveKeyboard
| MoveResizeDirection::Cancel => {
warn!("Unimplemented window move/resize action: {direction:?} ({:?})", e.window());
}
}
}
t => warn!("unrecognized message: {t:?}"),
}
t => warn!("unrecognized message: {t:?}"),
},
}
xcb::Event::X(x::Event::MappingNotify(_)) => {}
xcb::Event::RandR(xcb::randr::Event::Notify(e))
if matches!(e.u(), xcb::randr::NotifyData::Rc(_)) =>