parent
0b94ae1eb8
commit
970728d0d9
4 changed files with 155 additions and 52 deletions
|
|
@ -6,7 +6,7 @@ pub(crate) mod selection;
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
use self::event::*;
|
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 crate::{X11Selection, XConnection};
|
||||||
use clientside::MyWorld;
|
use clientside::MyWorld;
|
||||||
use hecs::{Entity, World};
|
use hecs::{Entity, World};
|
||||||
|
|
@ -49,7 +49,7 @@ use wayland_protocols::{
|
||||||
xdg_popup::XdgPopup,
|
xdg_popup::XdgPopup,
|
||||||
xdg_positioner::{Anchor, Gravity, XdgPositioner},
|
xdg_positioner::{Anchor, Gravity, XdgPositioner},
|
||||||
xdg_surface::XdgSurface,
|
xdg_surface::XdgSurface,
|
||||||
xdg_toplevel::XdgToplevel,
|
xdg_toplevel::{self, XdgToplevel},
|
||||||
xdg_wm_base::XdgWmBase,
|
xdg_wm_base::XdgWmBase,
|
||||||
},
|
},
|
||||||
xdg_output::zv1::server::zxdg_output_manager_v1::ZxdgOutputManagerV1,
|
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);
|
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) {
|
pub fn destroy_window(&mut self, window: x::Window) {
|
||||||
if let Some(id) = self.windows.remove(&window) {
|
if let Some(id) = self.windows.remove(&window) {
|
||||||
self.world.remove::<(x::Window, WindowData)>(id).unwrap();
|
self.world.remove::<(x::Window, WindowData)>(id).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -471,68 +471,87 @@ impl XState {
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
xcb::Event::X(x::Event::ClientMessage(e)) => match e.r#type() {
|
xcb::Event::X(x::Event::ClientMessage(e)) => {
|
||||||
x if x == self.atoms.wl_surface_id => {
|
match e.r#type() {
|
||||||
panic!(concat!(
|
x if x == self.atoms.wl_surface_id => {
|
||||||
|
panic!(concat!(
|
||||||
"Xserver should be using WL_SURFACE_SERIAL, not WL_SURFACE_ID\n",
|
"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."
|
"Your Xwayland is likely too old, it should be version 23.1 or greater."
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
x if x == self.atoms.wl_surface_serial => {
|
x if x == self.atoms.wl_surface_serial => {
|
||||||
let x::ClientMessageData::Data32(data) = e.data() else {
|
let x::ClientMessageData::Data32(data) = e.data() else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
server_state.set_window_serial(e.window(), [data[0], data[1]]);
|
server_state.set_window_serial(e.window(), [data[0], data[1]]);
|
||||||
}
|
}
|
||||||
x if x == self.atoms.net_wm_state => {
|
x if x == self.atoms.net_wm_state => {
|
||||||
let x::ClientMessageData::Data32(data) = e.data() else {
|
let x::ClientMessageData::Data32(data) = e.data() else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
let Ok(action) = SetState::try_from(data[0]) else {
|
let Ok(action) = SetState::try_from(data[0]) else {
|
||||||
warn!("unknown action for _NET_WM_STATE: {}", data[0]);
|
warn!("unknown action for _NET_WM_STATE: {}", data[0]);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let prop1 = unsafe { x::Atom::new(data[1]) };
|
let prop1 = unsafe { x::Atom::new(data[1]) };
|
||||||
let prop2 = unsafe { x::Atom::new(data[2]) };
|
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] {
|
for prop in [prop1, prop2] {
|
||||||
match prop {
|
match prop {
|
||||||
x if x == self.atoms.wm_fullscreen => {
|
x if x == self.atoms.wm_fullscreen => {
|
||||||
server_state.set_fullscreen(e.window(), action);
|
server_state.set_fullscreen(e.window(), action);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
x if x == self.atoms.active_win => {
|
||||||
x if x == self.atoms.active_win => {
|
server_state.activate_window(e.window());
|
||||||
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.moveresize => {
|
||||||
|
let x::ClientMessageData::Data32(data) = e.data() else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
|
||||||
if matches!(direction, MoveResizeDirection::Move) {
|
let (_x_root, _y_root) = (data[0], data[1]);
|
||||||
server_state.move_window(e.window());
|
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::X(x::Event::MappingNotify(_)) => {}
|
||||||
xcb::Event::RandR(xcb::randr::Event::Notify(e))
|
xcb::Event::RandR(xcb::randr::Event::Notify(e))
|
||||||
if matches!(e.u(), xcb::randr::NotifyData::Rc(_)) =>
|
if matches!(e.u(), xcb::randr::NotifyData::Rc(_)) =>
|
||||||
|
|
|
||||||
|
|
@ -2053,3 +2053,33 @@ fn client_init_move() {
|
||||||
let data = f.testwl.get_surface_data(surface).unwrap();
|
let data = f.testwl.get_surface_data(surface).unwrap();
|
||||||
assert!(data.moving);
|
assert!(data.moving);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn client_init_resize() {
|
||||||
|
let mut f = Fixture::new();
|
||||||
|
let mut connection = Connection::new(&f.display);
|
||||||
|
|
||||||
|
let win_toplevel = connection.new_window(connection.root, 0, 0, 20, 20, false);
|
||||||
|
let surface = f.map_as_toplevel(&mut connection, win_toplevel);
|
||||||
|
f.testwl.move_pointer_to(surface, 10., 10.);
|
||||||
|
let ptr = f.testwl.pointer();
|
||||||
|
ptr.motion(10, 10.0, 10.0);
|
||||||
|
ptr.frame();
|
||||||
|
ptr.button(10, 20, BTN_LEFT, wl_pointer::ButtonState::Pressed);
|
||||||
|
ptr.frame();
|
||||||
|
f.testwl.dispatch();
|
||||||
|
|
||||||
|
connection.send_client_message(&x::ClientMessageEvent::new(
|
||||||
|
win_toplevel,
|
||||||
|
connection.atoms.moveresize,
|
||||||
|
x::ClientMessageData::Data32([0, 0, MoveResizeDirection::SizeBottomRight.into(), 1, 0]),
|
||||||
|
));
|
||||||
|
|
||||||
|
f.wait_and_dispatch();
|
||||||
|
let data = f.testwl.get_surface_data(surface).unwrap();
|
||||||
|
assert!(
|
||||||
|
matches!(data.resizing, Some(xdg_toplevel::ResizeEdge::BottomRight)),
|
||||||
|
"Got wrong resizing edge: {:?}",
|
||||||
|
data.resizing
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,7 @@ pub struct SurfaceData {
|
||||||
pub fractional: Option<WpFractionalScaleV1>,
|
pub fractional: Option<WpFractionalScaleV1>,
|
||||||
pub viewport: Option<Viewport>,
|
pub viewport: Option<Viewport>,
|
||||||
pub moving: bool,
|
pub moving: bool,
|
||||||
|
pub resizing: Option<xdg_toplevel::ResizeEdge>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SurfaceData {
|
impl SurfaceData {
|
||||||
|
|
@ -1546,6 +1547,17 @@ impl Dispatch<XdgToplevel, SurfaceId> for State {
|
||||||
let data = state.surfaces.get_mut(surface_id).unwrap();
|
let data = state.surfaces.get_mut(surface_id).unwrap();
|
||||||
data.moving = true;
|
data.moving = true;
|
||||||
}
|
}
|
||||||
|
xdg_toplevel::Request::Resize {
|
||||||
|
seat: _,
|
||||||
|
serial: _,
|
||||||
|
edges,
|
||||||
|
} => {
|
||||||
|
let data = state.surfaces.get_mut(surface_id).unwrap();
|
||||||
|
let WEnum::Value(edge) = edges else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
data.resizing = Some(edge);
|
||||||
|
}
|
||||||
other => todo!("unhandled request {other:?}"),
|
other => todo!("unhandled request {other:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1868,6 +1880,7 @@ impl Dispatch<WlCompositor, ()> for State {
|
||||||
fractional: None,
|
fractional: None,
|
||||||
viewport: None,
|
viewport: None,
|
||||||
moving: false,
|
moving: false,
|
||||||
|
resizing: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
state.last_surface_id = Some(SurfaceId(id));
|
state.last_surface_id = Some(SurfaceId(id));
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue