Files
niri/src/handlers/compositor.rs
T

374 lines
15 KiB
Rust
Raw Normal View History

use std::collections::hash_map::Entry;
use smithay::backend::renderer::utils::{on_commit_buffer_handler, with_renderer_surface_state};
2023-09-06 11:56:50 +04:00
use smithay::input::pointer::CursorImageStatus;
2023-09-03 14:22:04 +04:00
use smithay::reexports::calloop::Interest;
2023-08-07 19:45:55 +04:00
use smithay::reexports::wayland_server::protocol::wl_buffer;
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
2023-09-03 14:22:04 +04:00
use smithay::reexports::wayland_server::{Client, Resource};
2023-08-07 19:45:55 +04:00
use smithay::wayland::buffer::BufferHandler;
use smithay::wayland::compositor::{
2024-05-29 13:32:11 +03:00
add_blocker, add_pre_commit_hook, get_parent, is_sync_subsurface, with_states,
BufferAssignment, CompositorClientState, CompositorHandler, CompositorState, SurfaceAttributes,
2023-08-07 19:44:40 +04:00
};
2023-09-03 14:22:04 +04:00
use smithay::wayland::dmabuf::get_dmabuf;
2024-05-10 16:58:53 +04:00
use smithay::wayland::shell::xdg::XdgToplevelSurfaceData;
2023-08-07 19:45:55 +04:00
use smithay::wayland::shm::{ShmHandler, ShmState};
use smithay::{delegate_compositor, delegate_shm};
2023-08-07 19:44:40 +04:00
2024-04-13 09:12:32 +04:00
use super::xdg_shell::add_mapped_toplevel_pre_commit_hook;
2023-09-03 14:10:02 +04:00
use crate::niri::{ClientState, State};
2024-05-29 13:32:11 +03:00
use crate::utils::send_scale_transform;
2024-03-19 14:41:17 +04:00
use crate::window::{InitialConfigureState, Mapped, ResolvedWindowRules, Unmapped};
2023-08-07 19:44:40 +04:00
2023-09-03 14:10:02 +04:00
impl CompositorHandler for State {
2023-08-07 19:44:40 +04:00
fn compositor_state(&mut self) -> &mut CompositorState {
2023-09-03 14:10:02 +04:00
&mut self.niri.compositor_state
2023-08-07 19:44:40 +04:00
}
fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
&client.get_data::<ClientState>().unwrap().compositor_state
}
2023-10-26 00:15:46 +04:00
fn new_subsurface(&mut self, surface: &WlSurface, parent: &WlSurface) {
let mut root = parent.clone();
while let Some(parent) = get_parent(&root) {
root = parent;
}
if let Some(output) = self.niri.output_for_root(&root) {
2024-05-29 13:32:11 +03:00
let scale = output.current_scale();
2023-10-26 00:15:46 +04:00
let transform = output.current_transform();
with_states(surface, |data| {
2024-05-29 13:32:11 +03:00
send_scale_transform(surface, data, scale, transform);
2023-10-26 00:15:46 +04:00
});
}
}
2023-09-03 14:22:04 +04:00
fn new_surface(&mut self, surface: &WlSurface) {
add_pre_commit_hook::<Self, _>(surface, move |state, _dh, surface| {
let maybe_dmabuf = with_states(surface, |surface_data| {
surface_data
.cached_state
2024-07-05 21:49:08 +02:00
.get::<SurfaceAttributes>()
.pending()
2023-09-03 14:22:04 +04:00
.buffer
.as_ref()
.and_then(|assignment| match assignment {
2024-05-16 18:00:28 +04:00
BufferAssignment::NewBuffer(buffer) => get_dmabuf(buffer).cloned().ok(),
2023-09-03 14:22:04 +04:00
_ => None,
})
});
if let Some(dmabuf) = maybe_dmabuf {
if let Ok((blocker, source)) = dmabuf.generate_blocker(Interest::READ) {
2024-05-05 11:14:46 +04:00
if let Some(client) = surface.client() {
let res =
2023-09-24 17:30:06 +04:00
state
2024-05-05 11:14:46 +04:00
.niri
.event_loop
.insert_source(source, move |_, _, state| {
let display_handle = state.niri.display_handle.clone();
state
.client_compositor_state(&client)
.blocker_cleared(state, &display_handle);
Ok(())
});
if res.is_ok() {
add_blocker(surface, blocker);
}
2023-09-03 14:22:04 +04:00
}
}
}
2024-02-28 13:22:11 +04:00
});
2023-09-03 14:22:04 +04:00
}
2023-08-07 19:44:40 +04:00
fn commit(&mut self, surface: &WlSurface) {
2023-08-15 18:17:12 +04:00
let _span = tracy_client::span!("CompositorHandler::commit");
2023-08-10 14:12:20 +04:00
2024-04-13 09:12:32 +04:00
on_commit_buffer_handler::<Self>(surface);
self.backend.early_import(surface);
if is_sync_subsurface(surface) {
return;
}
let mut root_surface = surface.clone();
while let Some(parent) = get_parent(&root_surface) {
root_surface = parent;
}
// Update the cached root surface.
self.niri
.root_surface
.insert(surface.clone(), root_surface.clone());
if surface == &root_surface {
// This is a root surface commit. It might have mapped a previously-unmapped toplevel.
2023-09-03 14:10:02 +04:00
if let Entry::Occupied(entry) = self.niri.unmapped_windows.entry(surface.clone()) {
let is_mapped =
2024-02-08 13:51:54 +04:00
with_renderer_surface_state(surface, |state| state.buffer().is_some())
.unwrap_or_else(|| {
error!("no renderer surface state even though we use commit handler");
false
});
if is_mapped {
// The toplevel got mapped.
let Unmapped { window, state } = entry.remove();
window.on_commit();
2024-04-13 09:12:32 +04:00
let toplevel = window.toplevel().expect("no X11 support");
2024-05-11 22:40:30 +02:00
let (rules, width, is_full_width, output, workspace_name) =
2024-02-23 14:24:39 +04:00
if let InitialConfigureState::Configured {
2024-03-19 14:41:17 +04:00
rules,
2024-02-23 14:24:39 +04:00
width,
is_full_width,
output,
2024-05-11 22:40:30 +02:00
workspace_name,
2024-02-23 14:24:39 +04:00
} = state
{
// Check that the output is still connected.
let output =
output.filter(|o| self.niri.layout.monitor_for_output(o).is_some());
2024-06-09 10:50:22 +00:00
// Check that the workspace still exists.
2024-05-11 22:40:30 +02:00
let workspace_name = workspace_name
.filter(|n| self.niri.layout.find_workspace_by_name(n).is_some());
(rules, width, is_full_width, output, workspace_name)
} else {
error!("window map must happen after initial configure");
2024-05-11 22:40:30 +02:00
(ResolvedWindowRules::empty(), None, false, None, None)
};
2024-04-13 09:12:32 +04:00
let parent = toplevel
.parent()
.and_then(|parent| self.niri.layout.find_window_and_output(&parent))
// Only consider the parent if we configured the window for the same
// output.
//
// Normally when we're following the parent, the configured output will be
// None. If the configured output is set, that means it was set explicitly
// by a window rule or a fullscreen request.
.filter(|(_, parent_output)| {
output.is_none() || output.as_ref() == Some(*parent_output)
})
2024-03-19 14:41:17 +04:00
.map(|(mapped, _)| mapped.window.clone());
2024-02-13 17:46:37 +04:00
2024-04-13 09:12:32 +04:00
let hook = add_mapped_toplevel_pre_commit_hook(toplevel);
let mapped = Mapped::new(window, rules, hook);
2024-03-19 14:41:17 +04:00
let window = mapped.window.clone();
let output = if let Some(p) = parent {
// Open dialogs immediately to the right of their parent window.
2024-02-23 14:24:39 +04:00
self.niri
.layout
2024-03-19 14:41:17 +04:00
.add_window_right_of(&p, mapped, width, is_full_width)
2024-05-11 22:40:30 +02:00
} else if let Some(workspace_name) = &workspace_name {
self.niri.layout.add_window_to_named_workspace(
workspace_name,
mapped,
width,
is_full_width,
)
2024-02-13 17:46:37 +04:00
} else if let Some(output) = &output {
self.niri
.layout
2024-03-19 14:41:17 +04:00
.add_window_on_output(output, mapped, width, is_full_width);
2024-02-13 17:46:37 +04:00
Some(output)
} else {
2024-03-19 14:41:17 +04:00
self.niri.layout.add_window(mapped, width, is_full_width)
};
if let Some(output) = output.cloned() {
2024-02-07 11:32:02 +04:00
self.niri.layout.start_open_animation_for_window(&window);
2024-03-17 07:48:34 +04:00
2024-03-19 14:41:17 +04:00
let new_active_window =
self.niri.layout.active_window().map(|(m, _)| &m.window);
2024-03-17 07:48:34 +04:00
if new_active_window == Some(&window) {
self.maybe_warp_cursor_to_focus();
}
2024-03-23 14:16:36 +04:00
self.niri.queue_redraw(&output);
}
return;
}
// The toplevel remains unmapped.
let unmapped = entry.get();
if unmapped.needs_initial_configure() {
2024-02-24 18:31:54 +01:00
let toplevel = unmapped.window.toplevel().expect("no x11 support").clone();
self.queue_initial_configure(toplevel);
2024-02-13 17:46:37 +04:00
}
return;
2023-08-07 19:44:40 +04:00
}
// This is a commit of a previously-mapped root or a non-toplevel root.
2024-03-19 14:41:17 +04:00
if let Some((mapped, output)) = self.niri.layout.find_window_and_output(surface) {
let window = mapped.window.clone();
let output = output.clone();
2023-12-24 17:38:13 +04:00
2024-06-27 11:36:24 +04:00
#[cfg(feature = "xdp-gnome-screencast")]
let id = mapped.id();
// This is a commit of a previously-mapped toplevel.
let is_mapped =
2024-02-08 13:51:54 +04:00
with_renderer_surface_state(surface, |state| state.buffer().is_some())
.unwrap_or_else(|| {
error!("no renderer surface state even though we use commit handler");
false
});
2024-04-13 09:12:32 +04:00
// Must start the close animation before window.on_commit().
if !is_mapped {
2024-04-09 22:37:10 +04:00
self.backend.with_primary_renderer(|renderer| {
self.niri
.layout
.start_close_animation_for_window(renderer, &window);
});
}
window.on_commit();
if !is_mapped {
// The toplevel got unmapped.
2024-04-09 21:59:30 +04:00
//
// Test client: wleird-unmap.
let active_window = self.niri.layout.active_window().map(|(m, _)| &m.window);
let was_active = active_window == Some(&window);
2024-06-27 11:36:24 +04:00
#[cfg(feature = "xdp-gnome-screencast")]
self.niri
.stop_casts_for_target(crate::pw_utils::CastTarget::Window {
id: u64::from(id.get()),
});
self.niri.layout.remove_window(&window);
2024-04-09 21:59:30 +04:00
if was_active {
self.maybe_warp_cursor_to_focus();
}
// Newly-unmapped toplevels must perform the initial commit-configure sequence
// afresh.
let unmapped = Unmapped::new(window);
self.niri.unmapped_windows.insert(surface.clone(), unmapped);
2024-03-23 14:16:36 +04:00
self.niri.queue_redraw(&output);
return;
}
2024-05-10 16:58:53 +04:00
let serial = with_states(surface, |states| {
let role = states
.data_map
.get::<XdgToplevelSurfaceData>()
.unwrap()
.lock()
.unwrap();
role.configure_serial
});
if serial.is_none() {
error!("commit on a mapped surface without a configured serial");
}
// The toplevel remains mapped.
2024-05-10 16:58:53 +04:00
self.niri.layout.update_window(&window, serial);
2023-12-19 20:56:00 +04:00
// Popup placement depends on window size which might have changed.
self.update_reactive_popups(&window, &output);
2024-03-23 14:16:36 +04:00
self.niri.queue_redraw(&output);
return;
2023-08-07 19:44:40 +04:00
}
// This is a commit of a non-toplevel root.
}
2023-08-10 09:58:26 +04:00
// This is a commit of a non-root or a non-toplevel root.
let root_window_output = self.niri.layout.find_window_and_output(&root_surface);
2024-03-19 14:41:17 +04:00
if let Some((mapped, output)) = root_window_output {
let window = mapped.window.clone();
let output = output.clone();
window.on_commit();
2024-05-10 16:58:53 +04:00
self.niri.layout.update_window(&window, None);
2024-03-23 14:16:36 +04:00
self.niri.queue_redraw(&output);
return;
}
// This might be a popup.
self.popups_handle_commit(surface);
2023-09-03 14:10:02 +04:00
if let Some(popup) = self.niri.popups.find_popup(surface) {
if let Some(output) = self.output_for_popup(&popup) {
2024-03-23 14:16:36 +04:00
self.niri.queue_redraw(&output.clone());
}
}
2023-08-15 12:49:26 +04:00
// This might be a layer-shell surface.
self.layer_shell_handle_commit(surface);
2023-09-06 11:56:50 +04:00
// This might be a cursor surface.
2023-10-29 14:04:38 +04:00
if matches!(&self.niri.cursor_manager.cursor_image(), CursorImageStatus::Surface(s) if s == surface)
{
2023-09-06 11:56:50 +04:00
// FIXME: granular redraws for cursors.
self.niri.queue_redraw_all();
}
// This might be a DnD icon surface.
if self.niri.dnd_icon.as_ref() == Some(surface) {
// FIXME: granular redraws for cursors.
self.niri.queue_redraw_all();
}
2023-10-13 13:30:11 +04:00
// This might be a lock surface.
if self.niri.is_locked() {
for (output, state) in &self.niri.output_state {
if let Some(lock_surface) = &state.lock_surface {
2024-05-17 15:59:49 +04:00
if lock_surface.wl_surface() == &root_surface {
2024-03-23 14:16:36 +04:00
self.niri.queue_redraw(&output.clone());
2023-10-13 13:30:11 +04:00
break;
}
}
}
}
2023-08-07 19:44:40 +04:00
}
fn destroyed(&mut self, surface: &WlSurface) {
// Clients may destroy their subsurfaces before the main surface. Ensure we have a snapshot
// when that happens, so that the closing animation includes all these subsurfaces.
//
2024-05-15 20:30:02 +04:00
// Test client: alacritty with CSD <= 0.13 (it was fixed in winit afterwards:
// https://github.com/rust-windowing/winit/pull/3625).
//
// This is still not perfect, as this function is called already after the (first)
// subsurface is destroyed; in the case of alacritty, this is the top CSD shadow. But, it
// gets most of the job done.
if let Some(root) = self.niri.root_surface.get(surface) {
if let Some((mapped, _)) = self.niri.layout.find_window_and_output(root) {
2024-05-01 19:00:11 +04:00
let window = mapped.window.clone();
self.backend.with_primary_renderer(|renderer| {
2024-05-01 19:00:11 +04:00
self.niri.layout.store_unmap_snapshot(renderer, &window);
});
}
}
self.niri
.root_surface
.retain(|k, v| k != surface && v != surface);
}
2023-08-07 19:44:40 +04:00
}
2023-09-03 14:10:02 +04:00
impl BufferHandler for State {
2023-08-07 19:44:40 +04:00
fn buffer_destroyed(&mut self, _buffer: &wl_buffer::WlBuffer) {}
}
2023-09-03 14:10:02 +04:00
impl ShmHandler for State {
2023-08-07 19:44:40 +04:00
fn shm_state(&self) -> &ShmState {
2023-09-03 14:10:02 +04:00
&self.niri.shm_state
2023-08-07 19:44:40 +04:00
}
}
2023-09-03 14:10:02 +04:00
delegate_compositor!(State);
delegate_shm!(State);