390 lines
12 KiB
Rust
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);
|
|
});
|
|
}
|
|
}
|