mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-27 21:58:18 +01:00
2dbc13d55d
Using a hack to set the cursor to the primary plane. Also the code is still doodoo
557 lines
19 KiB
Rust
557 lines
19 KiB
Rust
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
mod xdg_shell;
|
|
mod xwayland;
|
|
|
|
use std::{os::fd::OwnedFd, time::Duration};
|
|
|
|
use smithay::{
|
|
backend::renderer::utils::{self, with_renderer_surface_state},
|
|
delegate_compositor, delegate_data_control, delegate_data_device, delegate_fractional_scale,
|
|
delegate_layer_shell, delegate_output, delegate_presentation, delegate_primary_selection,
|
|
delegate_relative_pointer, delegate_seat, delegate_shm, delegate_viewporter,
|
|
desktop::{
|
|
self, layer_map_for_output, utils::surface_primary_scanout_output, PopupKind,
|
|
WindowSurfaceType,
|
|
},
|
|
input::{pointer::CursorImageStatus, Seat, SeatHandler, SeatState},
|
|
output::Output,
|
|
reexports::{
|
|
calloop::Interest,
|
|
wayland_server::{
|
|
protocol::{
|
|
wl_buffer::WlBuffer, wl_data_source::WlDataSource, wl_output::WlOutput,
|
|
wl_surface::WlSurface,
|
|
},
|
|
Client, Resource,
|
|
},
|
|
},
|
|
utils::SERIAL_COUNTER,
|
|
wayland::{
|
|
buffer::BufferHandler,
|
|
compositor::{
|
|
self, BufferAssignment, CompositorClientState, CompositorHandler, CompositorState,
|
|
SurfaceAttributes,
|
|
},
|
|
dmabuf,
|
|
fractional_scale::{self, FractionalScaleHandler},
|
|
output::OutputHandler,
|
|
seat::WaylandFocus,
|
|
selection::{
|
|
data_device::{
|
|
set_data_device_focus, ClientDndGrabHandler, DataDeviceHandler, DataDeviceState,
|
|
ServerDndGrabHandler,
|
|
},
|
|
primary_selection::{
|
|
set_primary_focus, PrimarySelectionHandler, PrimarySelectionState,
|
|
},
|
|
wlr_data_control::{DataControlHandler, DataControlState},
|
|
SelectionHandler, SelectionSource, SelectionTarget,
|
|
},
|
|
shell::{
|
|
wlr_layer::{self, Layer, LayerSurfaceData, WlrLayerShellHandler, WlrLayerShellState},
|
|
xdg::{XdgPopupSurfaceData, XdgToplevelSurfaceData},
|
|
},
|
|
shm::{ShmHandler, ShmState},
|
|
},
|
|
xwayland::{X11Wm, XWaylandClientData},
|
|
};
|
|
use tracing::error;
|
|
|
|
use crate::{
|
|
delegate_screencopy,
|
|
focus::{keyboard::KeyboardFocusTarget, pointer::PointerFocusTarget},
|
|
protocol::screencopy::{Screencopy, ScreencopyHandler},
|
|
state::{ClientState, State, WithState},
|
|
};
|
|
|
|
impl BufferHandler for State {
|
|
fn buffer_destroyed(&mut self, _buffer: &WlBuffer) {}
|
|
}
|
|
|
|
impl CompositorHandler for State {
|
|
fn compositor_state(&mut self) -> &mut CompositorState {
|
|
&mut self.compositor_state
|
|
}
|
|
|
|
fn new_surface(&mut self, surface: &WlSurface) {
|
|
compositor::add_pre_commit_hook::<Self, _>(surface, |state, _display_handle, surface| {
|
|
let maybe_dmabuf = compositor::with_states(surface, |surface_data| {
|
|
surface_data
|
|
.cached_state
|
|
.pending::<SurfaceAttributes>()
|
|
.buffer
|
|
.as_ref()
|
|
.and_then(|assignment| match assignment {
|
|
BufferAssignment::NewBuffer(buffer) => dmabuf::get_dmabuf(buffer).ok(),
|
|
_ => None,
|
|
})
|
|
});
|
|
if let Some(dmabuf) = maybe_dmabuf {
|
|
if let Ok((blocker, source)) = dmabuf.generate_blocker(Interest::READ) {
|
|
let client = surface
|
|
.client()
|
|
.expect("Surface has no client/is no longer alive");
|
|
let res = state.loop_handle.insert_source(source, move |_, _, state| {
|
|
state
|
|
.client_compositor_state(&client)
|
|
.blocker_cleared(state, &state.display_handle.clone());
|
|
Ok(())
|
|
});
|
|
if res.is_ok() {
|
|
compositor::add_blocker(surface, blocker);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
fn commit(&mut self, surface: &WlSurface) {
|
|
tracing::trace!("commit on surface {surface:?}");
|
|
|
|
utils::on_commit_buffer_handler::<State>(surface);
|
|
|
|
X11Wm::commit_hook::<State>(surface);
|
|
|
|
self.backend.early_import(surface);
|
|
|
|
let mut root = surface.clone();
|
|
while let Some(parent) = compositor::get_parent(&root) {
|
|
root = parent;
|
|
}
|
|
|
|
if !compositor::is_sync_subsurface(surface) {
|
|
if let Some(window) = self.window_for_surface(&root) {
|
|
window.on_commit();
|
|
if let Some(loc) = window.with_state_mut(|state| state.target_loc.take()) {
|
|
self.space.map_element(window.clone(), loc, false);
|
|
}
|
|
}
|
|
};
|
|
|
|
self.popup_manager.commit(surface);
|
|
|
|
if let Some(new_window) = self
|
|
.new_windows
|
|
.iter()
|
|
.find(|win| win.wl_surface().as_ref() == Some(surface))
|
|
.cloned()
|
|
{
|
|
let Some(is_mapped) =
|
|
with_renderer_surface_state(surface, |state| state.buffer().is_some())
|
|
else {
|
|
unreachable!("on_commit_buffer_handler was called previously");
|
|
};
|
|
|
|
if is_mapped {
|
|
self.new_windows.retain(|win| win != &new_window);
|
|
self.windows.push(new_window.clone());
|
|
|
|
if let Some(output) = self.focused_output() {
|
|
tracing::debug!("Placing toplevel");
|
|
new_window.place_on_output(output);
|
|
output.with_state_mut(|state| state.focus_stack.set_focus(new_window.clone()));
|
|
}
|
|
|
|
// FIXME: I'm mapping way offscreen here then sending a frame to prevent a window from
|
|
// | mapping with its default geometry then immediately resizing
|
|
// | because I don't set a target geometry before the initial configure.
|
|
self.space
|
|
.map_element(new_window.clone(), (1000000, 0), true);
|
|
|
|
self.raise_window(new_window.clone(), true);
|
|
|
|
self.apply_window_rules(&new_window);
|
|
|
|
if let Some(focused_output) = self.focused_output().cloned() {
|
|
self.request_layout(&focused_output);
|
|
new_window.send_frame(
|
|
&focused_output,
|
|
self.clock.now(),
|
|
Some(Duration::ZERO),
|
|
surface_primary_scanout_output,
|
|
);
|
|
}
|
|
|
|
self.loop_handle.insert_idle(move |state| {
|
|
state
|
|
.seat
|
|
.get_keyboard()
|
|
.expect("Seat had no keyboard") // FIXME: actually handle error
|
|
.set_focus(
|
|
state,
|
|
Some(KeyboardFocusTarget::Window(new_window)),
|
|
SERIAL_COUNTER.next_serial(),
|
|
);
|
|
});
|
|
} else if new_window.toplevel().is_some() {
|
|
new_window.on_commit();
|
|
ensure_initial_configure(surface, self);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
ensure_initial_configure(surface, self);
|
|
|
|
crate::grab::resize_grab::handle_commit(self, surface);
|
|
|
|
let outputs = if let Some(window) = self.window_for_surface(surface) {
|
|
let mut outputs = self.space.outputs_for_element(&window);
|
|
|
|
// When the window hasn't been mapped `outputs` is empty,
|
|
// so also trigger a render using the window's tags' output
|
|
if let Some(output) = window.output(self) {
|
|
outputs.push(output);
|
|
}
|
|
outputs // surface is a window
|
|
} else if let Some(window) = self.window_for_surface(&root) {
|
|
let mut outputs = self.space.outputs_for_element(&window);
|
|
if let Some(output) = window.output(self) {
|
|
outputs.push(output);
|
|
}
|
|
outputs // surface is a root window
|
|
} else if let Some(PopupKind::Xdg(surf)) = self.popup_manager.find_popup(surface) {
|
|
let geo = surf.with_pending_state(|state| state.geometry);
|
|
let outputs = self
|
|
.space
|
|
.outputs()
|
|
.filter_map(|output| {
|
|
let op_geo = self.space.output_geometry(output);
|
|
op_geo.and_then(|op_geo| op_geo.overlaps_or_touches(geo).then_some(output))
|
|
})
|
|
.cloned()
|
|
.collect::<Vec<_>>();
|
|
outputs
|
|
} else if let Some(output) = self
|
|
.space
|
|
.outputs()
|
|
.find(|op| {
|
|
let layer_map = layer_map_for_output(op);
|
|
layer_map
|
|
.layer_for_surface(surface, WindowSurfaceType::ALL)
|
|
.is_some()
|
|
})
|
|
.cloned()
|
|
{
|
|
vec![output] // surface is a layer surface
|
|
} else {
|
|
return;
|
|
};
|
|
|
|
for output in outputs {
|
|
self.schedule_render(&output);
|
|
}
|
|
}
|
|
|
|
fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
|
|
if let Some(state) = client.get_data::<XWaylandClientData>() {
|
|
return &state.compositor_state;
|
|
}
|
|
if let Some(state) = client.get_data::<ClientState>() {
|
|
return &state.compositor_state;
|
|
}
|
|
panic!("Unknown client data type");
|
|
}
|
|
}
|
|
delegate_compositor!(State);
|
|
|
|
fn ensure_initial_configure(surface: &WlSurface, state: &mut State) {
|
|
if let (Some(window), _) | (None, Some(window)) = (
|
|
state.window_for_surface(surface),
|
|
state.new_window_for_surface(surface),
|
|
) {
|
|
if let Some(toplevel) = window.toplevel() {
|
|
let initial_configure_sent = compositor::with_states(surface, |states| {
|
|
states
|
|
.data_map
|
|
.get::<XdgToplevelSurfaceData>()
|
|
.expect("XdgToplevelSurfaceData wasn't in surface's data map")
|
|
.lock()
|
|
.expect("Failed to lock Mutex<XdgToplevelSurfaceData>")
|
|
.initial_configure_sent
|
|
});
|
|
|
|
if !initial_configure_sent {
|
|
tracing::debug!("Initial configure");
|
|
toplevel.send_configure();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if let Some(popup) = state.popup_manager.find_popup(surface) {
|
|
let PopupKind::Xdg(popup) = &popup else { return };
|
|
let initial_configure_sent = compositor::with_states(surface, |states| {
|
|
states
|
|
.data_map
|
|
.get::<XdgPopupSurfaceData>()
|
|
.expect("XdgPopupSurfaceData wasn't in popup's data map")
|
|
.lock()
|
|
.expect("Failed to lock Mutex<XdgPopupSurfaceData>")
|
|
.initial_configure_sent
|
|
});
|
|
if !initial_configure_sent {
|
|
popup
|
|
.send_configure()
|
|
.expect("popup initial configure failed");
|
|
}
|
|
return;
|
|
}
|
|
|
|
if let Some(output) = state.space.outputs().find(|op| {
|
|
let map = layer_map_for_output(op);
|
|
map.layer_for_surface(surface, WindowSurfaceType::TOPLEVEL)
|
|
.is_some()
|
|
}) {
|
|
let initial_configure_sent = compositor::with_states(surface, |states| {
|
|
states
|
|
.data_map
|
|
.get::<LayerSurfaceData>()
|
|
.expect("no LayerSurfaceData")
|
|
.lock()
|
|
.expect("failed to lock data")
|
|
.initial_configure_sent
|
|
});
|
|
|
|
let mut map = layer_map_for_output(output);
|
|
|
|
map.arrange();
|
|
|
|
if !initial_configure_sent {
|
|
map.layer_for_surface(surface, WindowSurfaceType::TOPLEVEL)
|
|
.expect("no layer for surface")
|
|
.layer_surface()
|
|
.send_configure();
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ClientDndGrabHandler for State {
|
|
fn started(
|
|
&mut self,
|
|
_source: Option<WlDataSource>,
|
|
icon: Option<WlSurface>,
|
|
_seat: Seat<Self>,
|
|
) {
|
|
self.dnd_icon = icon;
|
|
}
|
|
|
|
fn dropped(&mut self, _seat: Seat<Self>) {
|
|
self.dnd_icon = None;
|
|
}
|
|
}
|
|
|
|
impl ServerDndGrabHandler for State {}
|
|
|
|
impl SelectionHandler for State {
|
|
type SelectionUserData = ();
|
|
|
|
fn new_selection(
|
|
&mut self,
|
|
ty: SelectionTarget,
|
|
source: Option<SelectionSource>,
|
|
_seat: Seat<Self>,
|
|
) {
|
|
if let Some(xwm) = self.xwm.as_mut() {
|
|
if let Err(err) = xwm.new_selection(ty, source.map(|source| source.mime_types())) {
|
|
tracing::warn!(?err, ?ty, "Failed to set Xwayland selection");
|
|
}
|
|
}
|
|
}
|
|
|
|
fn send_selection(
|
|
&mut self,
|
|
ty: SelectionTarget,
|
|
mime_type: String,
|
|
fd: OwnedFd,
|
|
_seat: Seat<Self>,
|
|
_user_data: &(),
|
|
) {
|
|
if let Some(xwm) = self.xwm.as_mut() {
|
|
if let Err(err) = xwm.send_selection(ty, mime_type, fd, self.loop_handle.clone()) {
|
|
tracing::warn!(?err, "Failed to send primary (X11 -> Wayland)");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl DataDeviceHandler for State {
|
|
fn data_device_state(&self) -> &DataDeviceState {
|
|
&self.data_device_state
|
|
}
|
|
}
|
|
delegate_data_device!(State);
|
|
|
|
impl PrimarySelectionHandler for State {
|
|
fn primary_selection_state(&self) -> &PrimarySelectionState {
|
|
&self.primary_selection_state
|
|
}
|
|
}
|
|
delegate_primary_selection!(State);
|
|
|
|
impl DataControlHandler for State {
|
|
fn data_control_state(&self) -> &DataControlState {
|
|
&self.data_control_state
|
|
}
|
|
}
|
|
delegate_data_control!(State);
|
|
|
|
impl SeatHandler for State {
|
|
type KeyboardFocus = KeyboardFocusTarget;
|
|
type PointerFocus = PointerFocusTarget;
|
|
type TouchFocus = PointerFocusTarget;
|
|
|
|
fn seat_state(&mut self) -> &mut SeatState<Self> {
|
|
&mut self.seat_state
|
|
}
|
|
|
|
fn cursor_image(&mut self, _seat: &Seat<Self>, image: CursorImageStatus) {
|
|
self.cursor_status = image;
|
|
}
|
|
|
|
fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&Self::KeyboardFocus>) {
|
|
let focus_client = focused.and_then(|foc_target| {
|
|
self.display_handle
|
|
.get_client(foc_target.wl_surface()?.id())
|
|
.ok()
|
|
});
|
|
set_data_device_focus(&self.display_handle, seat, focus_client.clone());
|
|
set_primary_focus(&self.display_handle, seat, focus_client);
|
|
}
|
|
}
|
|
delegate_seat!(State);
|
|
|
|
impl ShmHandler for State {
|
|
fn shm_state(&self) -> &ShmState {
|
|
&self.shm_state
|
|
}
|
|
}
|
|
delegate_shm!(State);
|
|
|
|
impl OutputHandler for State {}
|
|
delegate_output!(State);
|
|
|
|
delegate_viewporter!(State);
|
|
|
|
impl FractionalScaleHandler for State {
|
|
fn new_fractional_scale(&mut self, surface: WlSurface) {
|
|
// comment yanked from anvil
|
|
// Here we can set the initial fractional scale
|
|
//
|
|
// First we look if the surface already has a primary scan-out output, if not
|
|
// we test if the surface is a subsurface and try to use the primary scan-out output
|
|
// of the root surface. If the root also has no primary scan-out output we just try
|
|
// to use the first output of the toplevel.
|
|
// If the surface is the root we also try to use the first output of the toplevel.
|
|
//
|
|
// If all the above tests do not lead to a output we just use the first output
|
|
// of the space (which in case of anvil will also be the output a toplevel will
|
|
// initially be placed on)
|
|
let mut root = surface.clone();
|
|
while let Some(parent) = compositor::get_parent(&root) {
|
|
root = parent;
|
|
}
|
|
|
|
compositor::with_states(&surface, |states| {
|
|
let primary_scanout_output =
|
|
desktop::utils::surface_primary_scanout_output(&surface, states)
|
|
.or_else(|| {
|
|
if root != surface {
|
|
compositor::with_states(&root, |states| {
|
|
desktop::utils::surface_primary_scanout_output(&root, states)
|
|
.or_else(|| {
|
|
self.window_for_surface(&root).and_then(|window| {
|
|
self.space.outputs_for_element(&window).first().cloned()
|
|
})
|
|
})
|
|
})
|
|
} else {
|
|
self.window_for_surface(&root).and_then(|window| {
|
|
self.space.outputs_for_element(&window).first().cloned()
|
|
})
|
|
}
|
|
})
|
|
.or_else(|| self.space.outputs().next().cloned());
|
|
if let Some(output) = primary_scanout_output {
|
|
fractional_scale::with_fractional_scale(states, |fractional_scale| {
|
|
fractional_scale.set_preferred_scale(output.current_scale().fractional_scale());
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
delegate_fractional_scale!(State);
|
|
|
|
delegate_relative_pointer!(State);
|
|
|
|
delegate_presentation!(State);
|
|
|
|
impl WlrLayerShellHandler for State {
|
|
fn shell_state(&mut self) -> &mut WlrLayerShellState {
|
|
&mut self.layer_shell_state
|
|
}
|
|
|
|
fn new_layer_surface(
|
|
&mut self,
|
|
surface: wlr_layer::LayerSurface,
|
|
output: Option<WlOutput>,
|
|
_layer: Layer,
|
|
namespace: String,
|
|
) {
|
|
tracing::debug!("New layer surface");
|
|
let output = output
|
|
.as_ref()
|
|
.and_then(Output::from_resource)
|
|
.or_else(|| self.space.outputs().next().cloned());
|
|
|
|
let Some(output) = output else {
|
|
error!("New layer surface, but there was no output to map it on");
|
|
return;
|
|
};
|
|
|
|
if let Err(err) =
|
|
layer_map_for_output(&output).map_layer(&desktop::LayerSurface::new(surface, namespace))
|
|
{
|
|
error!("Failed to map layer surface: {err}");
|
|
}
|
|
|
|
self.loop_handle.insert_idle(move |state| {
|
|
state.request_layout(&output);
|
|
});
|
|
}
|
|
|
|
fn layer_destroyed(&mut self, surface: wlr_layer::LayerSurface) {
|
|
let mut output: Option<Output> = None;
|
|
if let Some((mut map, layer, op)) = self.space.outputs().find_map(|o| {
|
|
let map = layer_map_for_output(o);
|
|
let layer = map
|
|
.layers()
|
|
.find(|&layer| layer.layer_surface() == &surface)
|
|
.cloned();
|
|
layer.map(|layer| (map, layer, o))
|
|
}) {
|
|
map.unmap_layer(&layer);
|
|
output = Some(op.clone());
|
|
}
|
|
|
|
if let Some(output) = output {
|
|
self.loop_handle.insert_idle(move |state| {
|
|
state.request_layout(&output);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
delegate_layer_shell!(State);
|
|
|
|
impl ScreencopyHandler for State {
|
|
fn frame(&mut self, frame: Screencopy) {
|
|
let output = frame.output().clone();
|
|
if !frame.with_damage() {
|
|
self.schedule_render(&output);
|
|
}
|
|
output.with_state_mut(|state| state.screencopy.replace(frame));
|
|
}
|
|
}
|
|
delegate_screencopy!(State);
|