🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
use std::cell::RefCell;
use smithay::{
desktop::{
find_popup_root_surface, get_popup_toplevel_coords, layer_map_for_output, space::SpaceElement,
PopupKeyboardGrab, PopupKind, PopupPointerGrab, PopupUngrabStrategy, Space, Window,
WindowSurfaceType,
},
input::{pointer::Focus, Seat},
output::Output,
reexports::{
wayland_protocols::xdg::{decoration as xdg_decoration, shell::server::xdg_toplevel},
wayland_server::{
protocol::{wl_output, wl_seat, wl_surface::WlSurface},
Resource,
},
},
utils::{Logical, Point, Serial},
wayland::{
compositor::{self, with_states},
seat::WaylandFocus,
shell::xdg::{
Configure, PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
XdgToplevelSurfaceData,
},
},
};
use tracing::{trace, warn};
use crate::{
focus::KeyboardFocusTarget,
shell::{TouchMoveSurfaceGrab, TouchResizeSurfaceGrab},
state::{AnvilState, Backend},
};
use super::{
fullscreen_output_geometry, place_new_window, FullscreenSurface, PointerMoveSurfaceGrab,
PointerResizeSurfaceGrab, ResizeData, ResizeEdge, ResizeState, SurfaceData, WindowElement,
};
impl<BackendData: Backend> XdgShellHandler for AnvilState<BackendData> {
fn xdg_shell_state(&mut self) -> &mut XdgShellState {
&mut self.xdg_shell_state
}
fn new_toplevel(&mut self, surface: ToplevelSurface) {
// Do not send a configure here, the initial configure
// of a xdg_surface has to be sent during the commit if
// the surface is not already configured
let window = WindowElement(Window::new_wayland_window(surface.clone()));
place_new_window(&mut self.space, self.pointer.current_location(), &window, true);
compositor::add_post_commit_hook(surface.wl_surface(), |state: &mut Self, _, surface| {
handle_toplevel_commit(&mut state.space, surface);
});
}
fn new_popup(&mut self, surface: PopupSurface, _positioner: PositionerState) {
// Do not send a configure here, the initial configure
// of a xdg_surface has to be sent during the commit if
// the surface is not already configured
self.unconstrain_popup(&surface);
if let Err(err) = self.popups.track_popup(PopupKind::from(surface)) {
warn!("Failed to track popup: {}", err);
}
}
fn reposition_request(&mut self, surface: PopupSurface, positioner: PositionerState, token: u32) {
surface.with_pending_state(|state| {
let geometry = positioner.get_geometry();
state.geometry = geometry;
state.positioner = positioner;
});
self.unconstrain_popup(&surface);
surface.send_repositioned(token);
}
fn move_request(&mut self, surface: ToplevelSurface, seat: wl_seat::WlSeat, serial: Serial) {
let seat: Seat<AnvilState<BackendData>> = Seat::from_resource(&seat).unwrap();
self.move_request_xdg(&surface, &seat, serial)
}
fn resize_request(
&mut self,
surface: ToplevelSurface,
seat: wl_seat::WlSeat,
serial: Serial,
edges: xdg_toplevel::ResizeEdge,
) {
let seat: Seat<AnvilState<BackendData>> = Seat::from_resource(&seat).unwrap();
if let Some(touch) = seat.get_touch() {
if touch.has_grab(serial) {
let start_data = touch.grab_start_data().unwrap();
tracing::info!(?start_data);
// If the client disconnects after requesting a move
// we can just ignore the request
let Some(window) = self.window_for_surface(surface.wl_surface()) else {
tracing::info!("no window");
return;
};
// If the focus was for a different surface, ignore the request.
if start_data.focus.is_none()
|| !start_data
.focus
.as_ref()
.unwrap()
.0
.same_client_as(&surface.wl_surface().id())
{
tracing::info!("different surface");
return;
}
let geometry = window.geometry();
let loc = self.space.element_location(&window).unwrap();
let (initial_window_location, initial_window_size) = (loc, geometry.size);
with_states(surface.wl_surface(), move |states| {
states
.data_map
.get::<RefCell<SurfaceData>>()
.unwrap()
.borrow_mut()
.resize_state = ResizeState::Resizing(ResizeData {
edges: edges.into(),
initial_window_location,
initial_window_size,
});
});
let grab = TouchResizeSurfaceGrab {
start_data,
window,
edges: edges.into(),
initial_window_location,
initial_window_size,
last_window_size: initial_window_size,
};
touch.set_grab(self, grab, serial);
return;
}
}
let pointer = seat.get_pointer().unwrap();
// Check that this surface has a click grab.
if !pointer.has_grab(serial) {
return;
}
let start_data = pointer.grab_start_data().unwrap();
let window = self.window_for_surface(surface.wl_surface()).unwrap();
// If the focus was for a different surface, ignore the request.
if start_data.focus.is_none()
|| !start_data
.focus
.as_ref()
.unwrap()
.0
.same_client_as(&surface.wl_surface().id())
{
return;
}
let geometry = window.geometry();
let loc = self.space.element_location(&window).unwrap();
let (initial_window_location, initial_window_size) = (loc, geometry.size);
with_states(surface.wl_surface(), move |states| {
states
.data_map
.get::<RefCell<SurfaceData>>()
.unwrap()
.borrow_mut()
.resize_state = ResizeState::Resizing(ResizeData {
edges: edges.into(),
initial_window_location,
initial_window_size,
});
});
let grab = PointerResizeSurfaceGrab {
start_data,
window,
edges: edges.into(),
initial_window_location,
initial_window_size,
last_window_size: initial_window_size,
};
pointer.set_grab(self, grab, serial, Focus::Clear);
}
fn ack_configure(&mut self, surface: WlSurface, configure: Configure) {
if let Configure::Toplevel(configure) = configure {
if let Some(serial) = with_states(&surface, |states| {
if let Some(data) = states.data_map.get::<RefCell<SurfaceData>>() {
if let ResizeState::WaitingForFinalAck(_, serial) = data.borrow().resize_state {
return Some(serial);
}
}
None
}) {
// When the resize grab is released the surface
// resize state will be set to WaitingForFinalAck
// and the client will receive a configure request
// without the resize state to inform the client
// resizing has finished. Here we will wait for
// the client to acknowledge the end of the
// resizing. To check if the surface was resizing
// before sending the configure we need to use
// the current state as the received acknowledge
// will no longer have the resize state set
let is_resizing = with_states(&surface, |states| {
states
.data_map
.get::<XdgToplevelSurfaceData>()
.unwrap()
.lock()
.unwrap()
.current
.states
.contains(xdg_toplevel::State::Resizing)
});
if configure.serial >= serial && is_resizing {
with_states(&surface, |states| {
let mut data = states
.data_map
.get::<RefCell<SurfaceData>>()
.unwrap()
.borrow_mut();
if let ResizeState::WaitingForFinalAck(resize_data, _) = data.resize_state {
data.resize_state = ResizeState::WaitingForCommit(resize_data);
} else {
unreachable!()
}
});
}
}
let window = self
.space
.elements()
.find(|element| element.wl_surface().as_deref() == Some(&surface));
if let Some(window) = window {
use xdg_decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode;
let is_ssd = configure
.state
.decoration_mode
.map(|mode| mode == Mode::ServerSide)
.unwrap_or(false);
window.set_ssd(is_ssd);
}
}
}
fn fullscreen_request(&mut self, surface: ToplevelSurface, mut wl_output: Option<wl_output::WlOutput>) {
if surface
.current_state()
.capabilities
.contains(xdg_toplevel::WmCapabilities::Fullscreen)
{
// NOTE: This is only one part of the solution. We can set the
// location and configure size here, but the surface should be rendered fullscreen
// independently from its buffer size
let wl_surface = surface.wl_surface();
let output_geometry = fullscreen_output_geometry(wl_surface, wl_output.as_ref(), &mut self.space);
if let Some(geometry) = output_geometry {
let output = wl_output
.as_ref()
.and_then(Output::from_resource)
.unwrap_or_else(|| self.space.outputs().next().unwrap().clone());
let client = match self.display_handle.get_client(wl_surface.id()) {
Ok(client) => client,
Err(_) => return,
};
for output in output.client_outputs(&client) {
wl_output = Some(output);
}
let window = self
.space
.elements()
.find(|window| window.wl_surface().map(|s| &*s == wl_surface).unwrap_or(false))
.unwrap();
surface.with_pending_state(|state| {
state.states.set(xdg_toplevel::State::Fullscreen);
state.size = Some(geometry.size);
state.fullscreen_output = wl_output;
});
output.user_data().insert_if_missing(FullscreenSurface::default);
output
.user_data()
.get::<FullscreenSurface>()
.unwrap()
.set(window.clone());
trace!("Fullscreening: {:?}", window);
}
}
// The protocol demands us to always reply with a configure,
// regardless of we fulfilled the request or not
if surface.is_initial_configure_sent() {
surface.send_configure();
} else {
// Will be sent during initial configure
}
}
fn unfullscreen_request(&mut self, surface: ToplevelSurface) {
if !surface
.current_state()
.states
.contains(xdg_toplevel::State::Fullscreen)
{
return;
}
let ret = surface.with_pending_state(|state| {
state.states.unset(xdg_toplevel::State::Fullscreen);
state.size = None;
state.fullscreen_output.take()
});
if let Some(output) = ret {
let output = Output::from_resource(&output).unwrap();
if let Some(fullscreen) = output.user_data().get::<FullscreenSurface>() {
trace!("Unfullscreening: {:?}", fullscreen.get());
fullscreen.clear();
self.backend_data.reset_buffers(&output);
}
}
surface.send_pending_configure();
}
fn maximize_request(&mut self, surface: ToplevelSurface) {
// NOTE: This should use layer-shell when it is implemented to
// get the correct maximum size
if surface
.current_state()
.capabilities
.contains(xdg_toplevel::WmCapabilities::Maximize)
{
let window = self.window_for_surface(surface.wl_surface()).unwrap();
let outputs_for_window = self.space.outputs_for_element(&window);
let output = outputs_for_window
.first()
// The window hasn't been mapped yet, use the primary output instead
.or_else(|| self.space.outputs().next())
// Assumes that at least one output exists
.expect("No outputs found");
let geometry = self.space.output_geometry(output).unwrap();
surface.with_pending_state(|state| {
state.states.set(xdg_toplevel::State::Maximized);
state.size = Some(geometry.size);
});
self.space.map_element(window, geometry.loc, true);
}
// The protocol demands us to always reply with a configure,
// regardless of we fulfilled the request or not
if surface.is_initial_configure_sent() {
surface.send_configure();
} else {
// Will be sent during initial configure
}
}
fn unmaximize_request(&mut self, surface: ToplevelSurface) {
if !surface
.current_state()
.states
.contains(xdg_toplevel::State::Maximized)
{
return;
}
surface.with_pending_state(|state| {
state.states.unset(xdg_toplevel::State::Maximized);
state.size = None;
});
surface.send_pending_configure();
}
fn grab(&mut self, surface: PopupSurface, seat: wl_seat::WlSeat, serial: Serial) {
let seat: Seat<AnvilState<BackendData>> = Seat::from_resource(&seat).unwrap();
let kind = PopupKind::Xdg(surface);
if let Some(root) = find_popup_root_surface(&kind).ok().and_then(|root| {
self.space
.elements()
.find(|w| w.wl_surface().map(|s| *s == root).unwrap_or(false))
.cloned()
.map(KeyboardFocusTarget::from)
.or_else(|| {
self.space
.outputs()
.find_map(|o| {
let map = layer_map_for_output(o);
map.layer_for_surface(&root, WindowSurfaceType::TOPLEVEL).cloned()
})
.map(KeyboardFocusTarget::LayerSurface)
})
}) {
let ret = self.popups.grab_popup(root, kind, &seat, serial);
if let Ok(mut grab) = ret {
if let Some(keyboard) = seat.get_keyboard() {
if keyboard.is_grabbed()
&& !(keyboard.has_grab(serial)
|| keyboard.has_grab(grab.previous_serial().unwrap_or(serial)))
{
grab.ungrab(PopupUngrabStrategy::All);
return;
}
keyboard.set_focus(self, grab.current_grab(), serial);
keyboard.set_grab(self, PopupKeyboardGrab::new(&grab), serial);
}
if let Some(pointer) = seat.get_pointer() {
if pointer.is_grabbed()
&& !(pointer.has_grab(serial)
|| pointer.has_grab(grab.previous_serial().unwrap_or_else(|| grab.serial())))
{
grab.ungrab(PopupUngrabStrategy::All);
return;
}
pointer.set_grab(self, PopupPointerGrab::new(&grab), serial, Focus::Keep);
}
}
}
}
}
impl<BackendData: Backend> AnvilState<BackendData> {
pub fn move_request_xdg(&mut self, surface: &ToplevelSurface, seat: &Seat<Self>, serial: Serial) {
if let Some(touch) = seat.get_touch() {
if touch.has_grab(serial) {
let start_data = touch.grab_start_data().unwrap();
// If the client disconnects after requesting a move
// we can just ignore the request
let Some(window) = self.window_for_surface(surface.wl_surface()) else {
return;
};
// If the focus was for a different surface, ignore the request.
if start_data.focus.is_none()
|| !start_data
.focus
.as_ref()
.unwrap()
.0
.same_client_as(&surface.wl_surface().id())
{
return;
}
let mut initial_window_location = self.space.element_location(&window).unwrap();
// If surface is maximized then unmaximize it
let current_state = surface.current_state();
if current_state.states.contains(xdg_toplevel::State::Maximized) {
surface.with_pending_state(|state| {
state.states.unset(xdg_toplevel::State::Maximized);
state.size = None;
});
surface.send_configure();
// NOTE: In real compositor mouse location should be mapped to a new window size
// For example, you could:
// 1) transform mouse pointer position from compositor space to window space (location relative)
// 2) divide the x coordinate by width of the window to get the percentage
// - 0.0 would be on the far left of the window
// - 0.5 would be in middle of the window
// - 1.0 would be on the far right of the window
// 3) multiply the percentage by new window width
// 4) by doing that, drag will look a lot more natural
//
// but for anvil needs setting location to pointer location is fine
initial_window_location = start_data.location.to_i32_round();
}
let grab = TouchMoveSurfaceGrab {
start_data,
window,
initial_window_location,
};
touch.set_grab(self, grab, serial);
return;
}
}
let pointer = seat.get_pointer().unwrap();
// Check that this surface has a click grab.
if !pointer.has_grab(serial) {
return;
}
let start_data = pointer.grab_start_data().unwrap();
// If the client disconnects after requesting a move
// we can just ignore the request
let Some(window) = self.window_for_surface(surface.wl_surface()) else {
return;
};
// If the focus was for a different surface, ignore the request.
if start_data.focus.is_none()
|| !start_data
.focus
.as_ref()
.unwrap()
.0
.same_client_as(&surface.wl_surface().id())
{
return;
}
let mut initial_window_location = self.space.element_location(&window).unwrap();
// If surface is maximized then unmaximize it
let current_state = surface.current_state();
if current_state.states.contains(xdg_toplevel::State::Maximized) {
surface.with_pending_state(|state| {
state.states.unset(xdg_toplevel::State::Maximized);
state.size = None;
});
surface.send_configure();
// NOTE: In real compositor mouse location should be mapped to a new window size
// For example, you could:
// 1) transform mouse pointer position from compositor space to window space (location relative)
// 2) divide the x coordinate by width of the window to get the percentage
// - 0.0 would be on the far left of the window
// - 0.5 would be in middle of the window
// - 1.0 would be on the far right of the window
// 3) multiply the percentage by new window width
// 4) by doing that, drag will look a lot more natural
//
// but for anvil needs setting location to pointer location is fine
let pos = pointer.current_location();
initial_window_location = (pos.x as i32, pos.y as i32).into();
}
let grab = PointerMoveSurfaceGrab {
start_data,
window,
initial_window_location,
};
pointer.set_grab(self, grab, serial, Focus::Clear);
}
fn unconstrain_popup(&self, popup: &PopupSurface) {
let Ok(root) = find_popup_root_surface(&PopupKind::Xdg(popup.clone())) else {
return;
};
let Some(window) = self.window_for_surface(&root) else {
return;
};
let mut outputs_for_window = self.space.outputs_for_element(&window);
if outputs_for_window.is_empty() {
return;
}
// Get a union of all outputs' geometries.
let mut outputs_geo = self
.space
.output_geometry(&outputs_for_window.pop().unwrap())
.unwrap();
for output in outputs_for_window {
outputs_geo = outputs_geo.merge(self.space.output_geometry(&output).unwrap());
}
let window_geo = self.space.element_geometry(&window).unwrap();
// The target geometry for the positioner should be relative to its parent's geometry, so
// we will compute that here.
let mut target = outputs_geo;
target.loc -= get_popup_toplevel_coords(&PopupKind::Xdg(popup.clone()));
target.loc -= window_geo.loc;
popup.with_pending_state(|state| {
state.geometry = state.positioner.get_unconstrained_geometry(target);
});
}
}
/// Should be called on `WlSurface::commit` of xdg toplevel
fn handle_toplevel_commit(space: &mut Space<WindowElement>, surface: &WlSurface) -> Option<()> {
let window = space
.elements()
.find(|w| w.wl_surface().as_deref() == Some(surface))
.cloned()?;
let mut window_loc = space.element_location(&window)?;
let geometry = window.geometry();
let new_loc: Point<Option<i32>, Logical> = with_states(window.wl_surface().as_deref()?, |states| {
let data = states.data_map.get::<RefCell<SurfaceData>>()?.borrow_mut();
if let ResizeState::Resizing(resize_data) = data.resize_state {
let edges = resize_data.edges;
let loc = resize_data.initial_window_location;
let size = resize_data.initial_window_size;
// If the window is being resized by top or left, its location must be adjusted
// accordingly.
edges.intersects(ResizeEdge::TOP_LEFT).then(|| {
let new_x = edges
.intersects(ResizeEdge::LEFT)
.then_some(loc.x + (size.w - geometry.size.w));
let new_y = edges
.intersects(ResizeEdge::TOP)
.then_some(loc.y + (size.h - geometry.size.h));
(new_x, new_y).into()
})
} else {
None
}
})?;
if let Some(new_x) = new_loc.x {
window_loc.x = new_x;
}
if let Some(new_y) = new_loc.y {
window_loc.y = new_y;
}
if new_loc.x.is_some() || new_loc.y.is_some() {
// If TOP or LEFT side of the window got resized, we have to move it
space.map_element(window, window_loc, false);
}
Some(())
}