🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
use std::{cell::RefCell, os::unix::io::OwnedFd};
use smithay::{
desktop::{space::SpaceElement, Window},
input::pointer::Focus,
utils::{Logical, Rectangle, SERIAL_COUNTER},
wayland::{
compositor::with_states,
selection::{
data_device::{
clear_data_device_selection, current_data_device_selection_userdata,
request_data_device_client_selection, set_data_device_selection,
},
primary_selection::{
clear_primary_selection, current_primary_selection_userdata,
request_primary_client_selection, set_primary_selection,
},
SelectionTarget,
},
xwayland_shell::{XWaylandShellHandler, XWaylandShellState},
},
xwayland::{
xwm::{Reorder, ResizeEdge as X11ResizeEdge, XwmId},
X11Surface, X11Wm, XwmHandler,
},
};
use tracing::{error, trace};
use crate::{focus::KeyboardFocusTarget, state::Backend, AnvilState};
use super::{
place_new_window, FullscreenSurface, PointerMoveSurfaceGrab, PointerResizeSurfaceGrab, ResizeData,
ResizeState, SurfaceData, TouchMoveSurfaceGrab, WindowElement,
};
#[derive(Debug, Default)]
struct OldGeometry(RefCell<Option<Rectangle<i32, Logical>>>);
impl OldGeometry {
pub fn save(&self, geo: Rectangle<i32, Logical>) {
*self.0.borrow_mut() = Some(geo);
}
pub fn restore(&self) -> Option<Rectangle<i32, Logical>> {
self.0.borrow_mut().take()
}
}
impl<BackendData: Backend> XWaylandShellHandler for AnvilState<BackendData> {
fn xwayland_shell_state(&mut self) -> &mut XWaylandShellState {
&mut self.xwayland_shell_state
}
}
impl<BackendData: Backend> XwmHandler for AnvilState<BackendData> {
fn xwm_state(&mut self, _xwm: XwmId) -> &mut X11Wm {
self.xwm.as_mut().unwrap()
}
fn new_window(&mut self, _xwm: XwmId, _window: X11Surface) {}
fn new_override_redirect_window(&mut self, _xwm: XwmId, _window: X11Surface) {}
fn map_window_request(&mut self, _xwm: XwmId, window: X11Surface) {
window.set_mapped(true).unwrap();
let window = WindowElement(Window::new_x11_window(window));
place_new_window(&mut self.space, self.pointer.current_location(), &window, true);
let bbox = self.space.element_bbox(&window).unwrap();
let Some(xsurface) = window.0.x11_surface() else {
unreachable!()
};
xsurface.configure(Some(bbox)).unwrap();
window.set_ssd(!xsurface.is_decorated());
}
fn mapped_override_redirect_window(&mut self, _xwm: XwmId, window: X11Surface) {
let location = window.geometry().loc;
let window = WindowElement(Window::new_x11_window(window));
self.space.map_element(window, location, true);
}
fn unmapped_window(&mut self, _xwm: XwmId, window: X11Surface) {
let maybe = self
.space
.elements()
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == &window))
.cloned();
if let Some(elem) = maybe {
self.space.unmap_elem(&elem)
}
if !window.is_override_redirect() {
window.set_mapped(false).unwrap();
}
}
fn destroyed_window(&mut self, _xwm: XwmId, _window: X11Surface) {}
fn configure_request(
&mut self,
_xwm: XwmId,
window: X11Surface,
_x: Option<i32>,
_y: Option<i32>,
w: Option<u32>,
h: Option<u32>,
_reorder: Option<Reorder>,
) {
// we just set the new size, but don't let windows move themselves around freely
let mut geo = window.geometry();
if let Some(w) = w {
geo.size.w = w as i32;
}
if let Some(h) = h {
geo.size.h = h as i32;
}
let _ = window.configure(geo);
}
fn configure_notify(
&mut self,
_xwm: XwmId,
window: X11Surface,
geometry: Rectangle<i32, Logical>,
_above: Option<u32>,
) {
let Some(elem) = self
.space
.elements()
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == &window))
.cloned()
else {
return;
};
self.space.map_element(elem, geometry.loc, false);
// TODO: We don't properly handle the order of override-redirect windows here,
// they are always mapped top and then never reordered.
}
fn maximize_request(&mut self, _xwm: XwmId, window: X11Surface) {
self.maximize_request_x11(&window);
}
fn unmaximize_request(&mut self, _xwm: XwmId, window: X11Surface) {
let Some(elem) = self
.space
.elements()
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == &window))
.cloned()
else {
return;
};
window.set_maximized(false).unwrap();
if let Some(old_geo) = window
.user_data()
.get::<OldGeometry>()
.and_then(|data| data.restore())
{
window.configure(old_geo).unwrap();
self.space.map_element(elem, old_geo.loc, false);
}
}
fn fullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
if let Some(elem) = self
.space
.elements()
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == &window))
{
let outputs_for_window = self.space.outputs_for_element(elem);
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();
window.set_fullscreen(true).unwrap();
elem.set_ssd(false);
window.configure(geometry).unwrap();
output.user_data().insert_if_missing(FullscreenSurface::default);
output
.user_data()
.get::<FullscreenSurface>()
.unwrap()
.set(elem.clone());
trace!("Fullscreening: {:?}", elem);
}
}
fn unfullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
if let Some(elem) = self
.space
.elements()
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == &window))
{
window.set_fullscreen(false).unwrap();
elem.set_ssd(!window.is_decorated());
if let Some(output) = self.space.outputs().find(|o| {
o.user_data()
.get::<FullscreenSurface>()
.and_then(|f| f.get())
.map(|w| &w == elem)
.unwrap_or(false)
}) {
trace!("Unfullscreening: {:?}", elem);
output.user_data().get::<FullscreenSurface>().unwrap().clear();
window.configure(self.space.element_bbox(elem)).unwrap();
self.backend_data.reset_buffers(output);
}
}
}
fn resize_request(&mut self, _xwm: XwmId, window: X11Surface, _button: u32, edges: X11ResizeEdge) {
// luckily anvil only supports one seat anyway...
let start_data = self.pointer.grab_start_data().unwrap();
let Some(element) = self
.space
.elements()
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == &window))
else {
return;
};
let geometry = element.geometry();
let loc = self.space.element_location(element).unwrap();
let (initial_window_location, initial_window_size) = (loc, geometry.size);
with_states(&element.wl_surface().unwrap(), 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: element.clone(),
edges: edges.into(),
initial_window_location,
initial_window_size,
last_window_size: initial_window_size,
};
let pointer = self.pointer.clone();
pointer.set_grab(self, grab, SERIAL_COUNTER.next_serial(), Focus::Clear);
}
fn move_request(&mut self, _xwm: XwmId, window: X11Surface, _button: u32) {
self.move_request_x11(&window)
}
fn allow_selection_access(&mut self, xwm: XwmId, _selection: SelectionTarget) -> bool {
if let Some(keyboard) = self.seat.get_keyboard() {
// check that an X11 window is focused
if let Some(KeyboardFocusTarget::Window(w)) = keyboard.current_focus() {
if let Some(surface) = w.x11_surface() {
if surface.xwm_id().unwrap() == xwm {
return true;
}
}
}
}
false
}
fn send_selection(&mut self, _xwm: XwmId, selection: SelectionTarget, mime_type: String, fd: OwnedFd) {
match selection {
SelectionTarget::Clipboard => {
if let Err(err) = request_data_device_client_selection(&self.seat, mime_type, fd) {
error!(?err, "Failed to request current wayland clipboard for Xwayland",);
}
}
SelectionTarget::Primary => {
if let Err(err) = request_primary_client_selection(&self.seat, mime_type, fd) {
error!(
?err,
"Failed to request current wayland primary selection for Xwayland",
);
}
}
}
}
fn new_selection(&mut self, _xwm: XwmId, selection: SelectionTarget, mime_types: Vec<String>) {
trace!(?selection, ?mime_types, "Got Selection from X11",);
// TODO check, that focused windows is X11 window before doing this
match selection {
SelectionTarget::Clipboard => {
set_data_device_selection(&self.display_handle, &self.seat, mime_types, ())
}
SelectionTarget::Primary => {
set_primary_selection(&self.display_handle, &self.seat, mime_types, ())
}
}
}
fn cleared_selection(&mut self, _xwm: XwmId, selection: SelectionTarget) {
match selection {
SelectionTarget::Clipboard => {
if current_data_device_selection_userdata(&self.seat).is_some() {
clear_data_device_selection(&self.display_handle, &self.seat)
}
}
SelectionTarget::Primary => {
if current_primary_selection_userdata(&self.seat).is_some() {
clear_primary_selection(&self.display_handle, &self.seat)
}
}
}
}
}
impl<BackendData: Backend> AnvilState<BackendData> {
pub fn maximize_request_x11(&mut self, window: &X11Surface) {
let Some(elem) = self
.space
.elements()
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == window))
.cloned()
else {
return;
};
let old_geo = self.space.element_bbox(&elem).unwrap();
let outputs_for_window = self.space.outputs_for_element(&elem);
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();
window.set_maximized(true).unwrap();
window.configure(geometry).unwrap();
window.user_data().insert_if_missing(OldGeometry::default);
window.user_data().get::<OldGeometry>().unwrap().save(old_geo);
self.space.map_element(elem, geometry.loc, false);
}
pub fn move_request_x11(&mut self, window: &X11Surface) {
if let Some(touch) = self.seat.get_touch() {
if let Some(start_data) = touch.grab_start_data() {
let element = self
.space
.elements()
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == window));
if let Some(element) = element {
let mut initial_window_location = self.space.element_location(element).unwrap();
// If surface is maximized then unmaximize it
if window.is_maximized() {
window.set_maximized(false).unwrap();
let pos = start_data.location;
initial_window_location = (pos.x as i32, pos.y as i32).into();
if let Some(old_geo) = window
.user_data()
.get::<OldGeometry>()
.and_then(|data| data.restore())
{
window
.configure(Rectangle::new(initial_window_location, old_geo.size))
.unwrap();
}
}
let grab = TouchMoveSurfaceGrab {
start_data,
window: element.clone(),
initial_window_location,
};
touch.set_grab(self, grab, SERIAL_COUNTER.next_serial());
return;
}
}
}
// luckily anvil only supports one seat anyway...
let Some(start_data) = self.pointer.grab_start_data() else {
return;
};
let Some(element) = self
.space
.elements()
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == window))
else {
return;
};
let mut initial_window_location = self.space.element_location(element).unwrap();
// If surface is maximized then unmaximize it
if window.is_maximized() {
window.set_maximized(false).unwrap();
let pos = self.pointer.current_location();
initial_window_location = (pos.x as i32, pos.y as i32).into();
if let Some(old_geo) = window
.user_data()
.get::<OldGeometry>()
.and_then(|data| data.restore())
{
window
.configure(Rectangle::new(initial_window_location, old_geo.size))
.unwrap();
}
}
let grab = PointerMoveSurfaceGrab {
start_data,
window: element.clone(),
initial_window_location,
};
let pointer = self.pointer.clone();
pointer.set_grab(self, grab, SERIAL_COUNTER.next_serial(), Focus::Clear);
}
}