Files
feresaria/compositor/src/handlers/xdg_shell.rs
T

390 lines
12 KiB
Rust

use smithay::{
desktop::{
PopupKeyboardGrab, PopupKind, PopupManager, PopupPointerGrab, Space, Window,
WindowSurfaceType, find_popup_root_surface, get_popup_toplevel_coords,
},
input::{
Seat,
pointer::{Focus, GrabStartData},
},
reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode,
reexports::wayland_server::{
Resource,
protocol::{wl_seat, wl_surface::WlSurface},
},
utils::{Rectangle, Serial},
wayland::{
compositor::with_states,
shell::xdg::decoration::XdgDecorationHandler,
shell::xdg::{
PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
XdgToplevelSurfaceData,
},
},
};
use crate::{
Ferese,
grabs::{MoveSurfaceGrab, ResizeEdge, ResizeSurfaceGrab, handle_resize_commit},
};
impl XdgShellHandler for Ferese {
fn xdg_shell_state(&mut self) -> &mut XdgShellState {
&mut self.xdg_shell_state
}
fn new_toplevel(&mut self, surface: ToplevelSurface) {
let parent = surface.parent().and_then(|parent| {
self.window_ids.iter().find_map(|(window, id)| {
window
.toplevel()
.is_some_and(|toplevel| toplevel.wl_surface() == &parent)
.then_some(*id)
})
});
let window = Window::new_wayland_window(surface);
if let Some(parent) = parent {
self.add_transient_window(window, parent);
} else {
self.add_tiled_window(window);
}
}
fn parent_changed(&mut self, surface: ToplevelSurface) {
let Some(parent) = surface.parent() else {
return;
};
let parent = self.window_ids.iter().find_map(|(window, id)| {
window
.toplevel()
.is_some_and(|toplevel| toplevel.wl_surface() == &parent)
.then_some(*id)
});
let child = self
.space
.elements()
.find(|window| {
window
.toplevel()
.is_some_and(|toplevel| toplevel.wl_surface() == surface.wl_surface())
})
.cloned();
if let (Some(parent), Some(child)) = (parent, child) {
self.make_window_transient(&child, parent);
}
}
fn toplevel_destroyed(&mut self, surface: ToplevelSurface) {
let Some(window) = self
.space
.elements()
.find(|window| {
window
.toplevel()
.is_some_and(|toplevel| toplevel.wl_surface() == surface.wl_surface())
})
.cloned()
else {
return;
};
self.remove_tiled_window(&window);
}
fn new_popup(&mut self, surface: PopupSurface, _positioner: PositionerState) {
self.unconstrain_popup(&surface);
if let Err(error) = self.popups.track_popup(PopupKind::Xdg(surface)) {
tracing::warn!(?error, "failed to track xdg popup");
}
}
fn reposition_request(
&mut self,
surface: PopupSurface,
positioner: PositionerState,
token: u32,
) {
surface.with_pending_state(|state| {
state.geometry = positioner.get_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 Some(seat) = Seat::from_resource(&seat) else {
return;
};
let Some(start_data) = check_grab(&seat, surface.wl_surface(), serial) else {
return;
};
let Some(pointer) = seat.get_pointer() else {
return;
};
let Some(window) = self
.space
.elements()
.find(|window| {
window
.toplevel()
.is_some_and(|toplevel| toplevel.wl_surface() == surface.wl_surface())
})
.cloned()
else {
return;
};
let Some(initial_location) = self.space.element_location(&window) else {
return;
};
if !self.is_floating_window(&window) {
return;
}
pointer.set_grab(
self,
MoveSurfaceGrab {
start_data,
window,
initial_location,
finished: false,
},
serial,
Focus::Clear,
);
}
fn resize_request(
&mut self,
surface: ToplevelSurface,
seat: wl_seat::WlSeat,
serial: Serial,
edges: smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::ResizeEdge,
) {
let Some(seat) = Seat::from_resource(&seat) else {
return;
};
let Some(start_data) = check_grab(&seat, surface.wl_surface(), serial) else {
return;
};
let Some(pointer) = seat.get_pointer() else {
return;
};
let Some(window) = self
.space
.elements()
.find(|window| {
window
.toplevel()
.is_some_and(|toplevel| toplevel.wl_surface() == surface.wl_surface())
})
.cloned()
else {
return;
};
let Some(location) = self.space.element_location(&window) else {
return;
};
if !self.is_floating_window(&window) {
return;
}
let rect = Rectangle::new(location, window.geometry().size);
pointer.set_grab(
self,
ResizeSurfaceGrab::new(start_data, window, ResizeEdge::from(edges), rect),
serial,
Focus::Clear,
);
}
fn fullscreen_request(
&mut self,
surface: ToplevelSurface,
_output: Option<smithay::reexports::wayland_server::protocol::wl_output::WlOutput>,
) {
if let Some(window) = self.window_ids.iter().find_map(|(window, id)| {
window
.toplevel()
.is_some_and(|toplevel| toplevel.wl_surface() == surface.wl_surface())
.then_some(*id)
}) {
self.set_window_fullscreen(window, true);
}
}
fn unfullscreen_request(&mut self, surface: ToplevelSurface) {
if let Some(window) = self.window_ids.iter().find_map(|(window, id)| {
window
.toplevel()
.is_some_and(|toplevel| toplevel.wl_surface() == surface.wl_surface())
.then_some(*id)
}) {
self.set_window_fullscreen(window, false);
}
}
fn grab(&mut self, surface: PopupSurface, seat: wl_seat::WlSeat, serial: Serial) {
let Some(seat) = Seat::from_resource(&seat) else {
return;
};
let popup = PopupKind::Xdg(surface);
let Ok(root) = find_popup_root_surface(&popup) else {
return;
};
let popup_grab = match self.popups.grab_popup(root, popup, &seat, serial) {
Ok(grab) => grab,
Err(error) => {
tracing::debug!(?error, "rejected xdg popup grab");
return;
}
};
if let Some(pointer) = seat.get_pointer() {
pointer.set_grab(
self,
PopupPointerGrab::new(&popup_grab),
serial,
Focus::Keep,
);
}
if let Some(keyboard) = seat.get_keyboard() {
keyboard.set_grab(self, PopupKeyboardGrab::new(&popup_grab), serial);
}
}
}
fn check_grab(
seat: &Seat<Ferese>,
surface: &WlSurface,
serial: Serial,
) -> Option<GrabStartData<Ferese>> {
let pointer = seat.get_pointer()?;
if !pointer.has_grab(serial) {
return None;
}
let start_data = pointer.grab_start_data()?;
let (focus, _) = start_data.focus.as_ref()?;
focus
.id()
.same_client_as(&surface.id())
.then_some(start_data)
}
impl XdgDecorationHandler for Ferese {
fn new_decoration(&mut self, toplevel: ToplevelSurface) {
let mode = self.decoration_mode_for(&toplevel);
set_decoration_mode(&toplevel, mode);
}
fn request_mode(&mut self, toplevel: ToplevelSurface, _mode: Mode) {
let mode = self.decoration_mode_for(&toplevel);
set_decoration_mode(&toplevel, mode);
}
fn unset_mode(&mut self, toplevel: ToplevelSurface) {
let mode = self.decoration_mode_for(&toplevel);
set_decoration_mode(&toplevel, mode);
}
}
impl Ferese {
fn decoration_mode_for(&self, toplevel: &ToplevelSurface) -> Mode {
self.space
.elements()
.find(|window| {
window
.toplevel()
.is_some_and(|candidate| candidate.wl_surface() == toplevel.wl_surface())
})
.filter(|window| self.is_floating_window(window))
.map_or(Mode::ServerSide, |_| Mode::ClientSide)
}
}
fn set_decoration_mode(toplevel: &ToplevelSurface, mode: Mode) {
toplevel.with_pending_state(|state| state.decoration_mode = Some(mode));
toplevel.send_pending_configure();
}
pub fn handle_commit(popups: &mut PopupManager, space: &mut Space<Window>, surface: &WlSurface) {
handle_resize_commit(space, surface);
if let Some(window) = space
.elements()
.find(|window| {
window
.toplevel()
.is_some_and(|toplevel| toplevel.wl_surface() == surface)
})
.cloned()
{
let configured = with_states(surface, |states| {
states
.data_map
.get::<XdgToplevelSurfaceData>()
.expect("xdg toplevel state exists")
.lock()
.expect("xdg toplevel state is not poisoned")
.initial_configure_sent
});
if !configured {
window
.toplevel()
.expect("mapped window has a toplevel")
.send_configure();
}
}
popups.commit(surface);
if let Some(PopupKind::Xdg(popup)) = popups.find_popup(surface)
&& !popup.is_initial_configure_sent()
{
popup
.send_configure()
.expect("initial popup configure is valid");
}
}
impl Ferese {
pub(crate) fn unconstrain_popup(&self, popup: &PopupSurface) {
let Ok(root) = find_popup_root_surface(&PopupKind::Xdg(popup.clone())) else {
return;
};
let window = self.space.elements().find(|window| {
window
.toplevel()
.is_some_and(|toplevel| toplevel.wl_surface() == &root)
});
let geometry = (|| {
if let Some(window) = window {
let output = self.space.outputs().next()?;
let output_geometry = self.space.output_geometry(output)?;
let window_geometry = self.space.element_geometry(window)?;
Some((output_geometry, window_geometry.loc))
} else {
let layer = self
.space
.layer_for_surface(&root, WindowSurfaceType::TOPLEVEL)?;
self.space.outputs().find_map(|output| {
let output_geometry = self.space.output_geometry(output)?;
let layer_geometry =
smithay::desktop::layer_map_for_output(output).layer_geometry(&layer)?;
Some((output_geometry, output_geometry.loc + layer_geometry.loc))
})
}
})();
let Some((mut target, root_location)) = geometry else {
return;
};
target.loc -= get_popup_toplevel_coords(&PopupKind::Xdg(popup.clone()));
target.loc -= root_location;
popup.with_pending_state(|state| {
state.geometry = state.positioner.get_unconstrained_geometry(target);
});
}
}