Merge pull request #240 from pinnacle-comp/layout_transactions

Add layout transactions
This commit is contained in:
Ottatop 2024-05-27 18:49:53 -05:00 committed by GitHub
commit c597ff6cce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 2149 additions and 540 deletions

View file

@ -36,6 +36,7 @@ tempfile = "3.10.1"
[workspace.dependencies.smithay]
git = "https://github.com/Smithay/smithay"
rev = "900b938"
# path = "../../git/smithay"
default-features = false
features = [
"desktop",

View file

@ -251,3 +251,6 @@ See [`CONTRIBUTING.md`](CONTRIBUTING.md).
# Changelog
See [`CHANGELOG.md`](CHANGELOG.md).
# With Special Thanks To
- [Niri](https://github.com/YaLTeR/niri): For all that rendering and protocol stuff I, ahem, *took inspiration* from

12
TODO.md
View file

@ -1 +1,11 @@
- Log git commit on startup and add to --help/-V
- Re-add raising file descriptor limit
- Like an idiot I managed to remove that sometime and not add it back
- Provide scale and transform on new window/layer
Problems:
- Pointer input to xwayland windows saturates at x=0, y=0, so windows on outputs at negative coords
get screwed up pointer events
- Xwayland popups are screwed when the output is not at (0, 0)
- Dragging an xwayland window to another output and closing a nested right click menu closes the whole
right click menu because the keyboard focus is getting updated on the original output.
- Transactions don't render floating windows

View file

@ -13,7 +13,7 @@ require("pinnacle").setup(function(Pinnacle)
---@type Modifier
local mod_key = "ctrl"
local terminal = "alacritty"
local terminal = "foot"
--------------------
-- Mousebinds --

View file

@ -56,6 +56,7 @@ use crate::{
config::ConnectorSavedState,
input::ModifierMask,
output::OutputName,
render::util::snapshot::capture_snapshots_on_output,
state::{State, WithState},
tag::{Tag, TagId},
};
@ -737,19 +738,33 @@ impl tag_service_server::TagService for TagService {
return;
};
match set_or_toggle {
SetOrToggle::Set => tag.set_active(true, state),
SetOrToggle::Unset => tag.set_active(false, state),
SetOrToggle::Toggle => tag.set_active(!tag.active(), state),
SetOrToggle::Unspecified => unreachable!(),
}
let Some(output) = tag.output(&state.pinnacle) else {
return;
};
let snapshots = state.backend.with_renderer(|renderer| {
capture_snapshots_on_output(&mut state.pinnacle, renderer, &output, [])
});
match set_or_toggle {
SetOrToggle::Set => tag.set_active(true, &mut state.pinnacle),
SetOrToggle::Unset => tag.set_active(false, &mut state.pinnacle),
SetOrToggle::Toggle => tag.set_active(!tag.active(), &mut state.pinnacle),
SetOrToggle::Unspecified => unreachable!(),
}
state.pinnacle.fixup_xwayland_window_layering();
if let Some((fs_and_up_snapshots, under_fs_snapshots)) = snapshots {
output.with_state_mut(|op_state| {
op_state.new_wait_layout_transaction(
state.pinnacle.loop_handle.clone(),
fs_and_up_snapshots,
under_fs_snapshots,
)
});
}
state.pinnacle.request_layout(&output);
state.update_keyboard_focus(&output);
state.schedule_render(&output);
@ -772,15 +787,29 @@ impl tag_service_server::TagService for TagService {
return;
};
let snapshots = state.backend.with_renderer(|renderer| {
capture_snapshots_on_output(&mut state.pinnacle, renderer, &output, [])
});
output.with_state(|op_state| {
for op_tag in op_state.tags.iter() {
op_tag.set_active(false, state);
op_tag.set_active(false, &mut state.pinnacle);
}
tag.set_active(true, state);
tag.set_active(true, &mut state.pinnacle);
});
state.pinnacle.fixup_xwayland_window_layering();
if let Some((fs_and_up_snapshots, under_fs_snapshots)) = snapshots {
output.with_state_mut(|op_state| {
op_state.new_wait_layout_transaction(
state.pinnacle.loop_handle.clone(),
fs_and_up_snapshots,
under_fs_snapshots,
)
});
}
state.pinnacle.request_layout(&output);
state.update_keyboard_focus(&output);
state.schedule_render(&output);
@ -1068,6 +1097,10 @@ impl output_service_server::OutputService for OutputService {
current_scale = f64::max(current_scale, 0.25);
let snapshots = state.backend.with_renderer(|renderer| {
capture_snapshots_on_output(&mut state.pinnacle, renderer, &output, [])
});
state.pinnacle.change_output_state(
&output,
None,
@ -1075,6 +1108,17 @@ impl output_service_server::OutputService for OutputService {
Some(Scale::Fractional(current_scale)),
None,
);
if let Some((fs_and_up_snapshots, under_fs_snapshots)) = snapshots {
output.with_state_mut(|op_state| {
op_state.new_wait_layout_transaction(
state.pinnacle.loop_handle.clone(),
fs_and_up_snapshots,
under_fs_snapshots,
)
});
}
state.pinnacle.request_layout(&output);
state.schedule_render(&output);
})

View file

@ -21,7 +21,10 @@ use smithay::{
use tonic::{Request, Response, Status};
use tracing::warn;
use crate::{output::OutputName, state::WithState, tag::TagId, window::window_state::WindowId};
use crate::{
output::OutputName, render::util::snapshot::capture_snapshots_on_output, state::WithState,
tag::TagId, window::window_state::WindowId,
};
use super::{run_unary, run_unary_no_response, StateFnSender};
@ -130,33 +133,26 @@ impl window_service_server::WindowService for WindowService {
return Err(Status::invalid_argument("unspecified set or toggle"));
}
run_unary_no_response(&self.sender, move |state| {
let pinnacle = &mut state.pinnacle;
let Some(window) = window_id.window(pinnacle) else {
return;
};
match set_or_toggle {
SetOrToggle::Set => {
if !window.with_state(|state| state.fullscreen_or_maximized.is_fullscreen()) {
window.toggle_fullscreen();
}
}
SetOrToggle::Unset => {
if window.with_state(|state| state.fullscreen_or_maximized.is_fullscreen()) {
window.toggle_fullscreen();
}
}
SetOrToggle::Toggle => window.toggle_fullscreen(),
let fullscreen = match set_or_toggle {
SetOrToggle::Set => Some(true),
SetOrToggle::Unset => Some(false),
SetOrToggle::Toggle => None,
SetOrToggle::Unspecified => unreachable!(),
}
};
let Some(output) = window.output(pinnacle) else {
run_unary_no_response(&self.sender, move |state| {
let Some(window) = window_id.window(&state.pinnacle) else {
return;
};
pinnacle.request_layout(&output);
state.schedule_render(&output);
match fullscreen {
Some(fullscreen) => state.set_window_fullscreen(&window, fullscreen),
None => {
let is_fullscreen = window
.with_state(|win_state| win_state.fullscreen_or_maximized.is_fullscreen());
state.set_window_fullscreen(&window, !is_fullscreen);
}
}
})
.await
}
@ -179,33 +175,26 @@ impl window_service_server::WindowService for WindowService {
return Err(Status::invalid_argument("unspecified set or toggle"));
}
run_unary_no_response(&self.sender, move |state| {
let pinnacle = &mut state.pinnacle;
let Some(window) = window_id.window(pinnacle) else {
return;
};
match set_or_toggle {
SetOrToggle::Set => {
if !window.with_state(|state| state.fullscreen_or_maximized.is_maximized()) {
window.toggle_maximized();
}
}
SetOrToggle::Unset => {
if window.with_state(|state| state.fullscreen_or_maximized.is_maximized()) {
window.toggle_maximized();
}
}
SetOrToggle::Toggle => window.toggle_maximized(),
let maximized = match set_or_toggle {
SetOrToggle::Set => Some(true),
SetOrToggle::Unset => Some(false),
SetOrToggle::Toggle => None,
SetOrToggle::Unspecified => unreachable!(),
}
};
let Some(output) = window.output(pinnacle) else {
run_unary_no_response(&self.sender, move |state| {
let Some(window) = window_id.window(&state.pinnacle) else {
return;
};
pinnacle.request_layout(&output);
state.schedule_render(&output);
match maximized {
Some(maximized) => state.set_window_maximized(&window, maximized),
None => {
let is_maximized = window
.with_state(|win_state| win_state.fullscreen_or_maximized.is_maximized());
state.set_window_maximized(&window, !is_maximized);
}
}
})
.await
}
@ -234,6 +223,12 @@ impl window_service_server::WindowService for WindowService {
return;
};
let snapshots = window.output(pinnacle).map(|output| {
state.backend.with_renderer(|renderer| {
capture_snapshots_on_output(pinnacle, renderer, &output, [window.clone()])
})
});
match set_or_toggle {
SetOrToggle::Set => {
if !window.with_state(|state| state.floating_or_tiled.is_floating()) {
@ -253,6 +248,16 @@ impl window_service_server::WindowService for WindowService {
return;
};
if let Some((fs_and_up_snapshots, under_fs_snapshots)) = snapshots.flatten() {
output.with_state_mut(|op_state| {
op_state.new_wait_layout_transaction(
pinnacle.loop_handle.clone(),
fs_and_up_snapshots,
under_fs_snapshots,
)
});
}
pinnacle.request_layout(&output);
state.schedule_render(&output);
})
@ -363,12 +368,28 @@ impl window_service_server::WindowService for WindowService {
let Some(tag) = tag_id.tag(pinnacle) else { return };
let snapshots = window.output(pinnacle).map(|output| {
state.backend.with_renderer(|renderer| {
capture_snapshots_on_output(pinnacle, renderer, &output, [window.clone()])
})
});
window.with_state_mut(|state| {
state.tags = vec![tag.clone()];
});
let Some(output) = tag.output(pinnacle) else { return };
if let Some((fs_and_up_snapshots, under_fs_snapshots)) = snapshots.flatten() {
output.with_state_mut(|op_state| {
op_state.new_wait_layout_transaction(
pinnacle.loop_handle.clone(),
fs_and_up_snapshots,
under_fs_snapshots,
)
});
}
pinnacle.request_layout(&output);
state.schedule_render(&output);
@ -405,6 +426,12 @@ impl window_service_server::WindowService for WindowService {
};
let Some(tag) = tag_id.tag(pinnacle) else { return };
let snapshots = window.output(pinnacle).map(|output| {
state.backend.with_renderer(|renderer| {
capture_snapshots_on_output(pinnacle, renderer, &output, [window.clone()])
})
});
// TODO: turn state.tags into a hashset
match set_or_toggle {
SetOrToggle::Set => window.with_state_mut(|state| {
@ -425,6 +452,17 @@ impl window_service_server::WindowService for WindowService {
}
let Some(output) = tag.output(pinnacle) else { return };
if let Some((fs_and_up_snapshots, under_fs_snapshots)) = snapshots.flatten() {
output.with_state_mut(|op_state| {
op_state.new_wait_layout_transaction(
pinnacle.loop_handle.clone(),
fs_and_up_snapshots,
under_fs_snapshots,
)
});
}
pinnacle.request_layout(&output);
state.schedule_render(&output);
@ -635,7 +673,7 @@ impl window_service_server::WindowService for WindowService {
pinnacle
.focused_output()
.and_then(|output| pinnacle.focused_window(output))
.map(|foc_win| win == &foc_win)
.map(|foc_win| win == foc_win)
});
let floating = window

View file

@ -10,6 +10,7 @@ use smithay::{
default_primary_scanout_output_compare, utils::select_dmabuf_feedback,
RenderElementStates,
},
gles::GlesRenderer,
ImportDma, Renderer, TextureFilter,
},
},
@ -106,6 +107,18 @@ impl Backend {
}
}
pub fn with_renderer<T>(
&mut self,
with_renderer: impl FnOnce(&mut GlesRenderer) -> T,
) -> Option<T> {
match self {
Backend::Winit(winit) => Some(with_renderer(winit.backend.renderer())),
Backend::Udev(udev) => Some(with_renderer(udev.renderer().ok()?.as_mut())),
#[cfg(feature = "testing")]
Backend::Dummy(_) => None,
}
}
/// Returns `true` if the backend is [`Winit`].
///
/// [`Winit`]: Backend::Winit

View file

@ -108,7 +108,7 @@ const SUPPORTED_FORMATS: &[Fourcc] = &[
const SUPPORTED_FORMATS_8BIT_ONLY: &[Fourcc] = &[Fourcc::Abgr8888, Fourcc::Argb8888];
/// A [`MultiRenderer`] that uses the [`GbmGlesBackend`].
type UdevRenderer<'a> = MultiRenderer<
pub type UdevRenderer<'a> = MultiRenderer<
'a,
'a,
GbmGlesBackend<GlesRenderer, DrmDeviceFd>,
@ -866,6 +866,10 @@ fn render_frame<'a>(
}
impl Udev {
pub fn renderer(&mut self) -> anyhow::Result<UdevRenderer<'_>> {
Ok(self.gpu_manager.single_renderer(&self.primary_gpu)?)
}
/// A GPU was plugged in.
fn device_added(
&mut self,
@ -1505,6 +1509,22 @@ impl Udev {
));
}
// HACK: Taking the transaction before creating render elements
// leads to a possibility where the original buffer still gets displayed.
// Need to figure that out.
// In the meantime we take the transaction afterwards and schedule another render.
let mut render_after_transaction_finish = false;
output.with_state_mut(|state| {
if state
.layout_transaction
.as_ref()
.is_some_and(|ts| ts.ready())
{
state.layout_transaction.take();
render_after_transaction_finish = true;
}
});
let clear_color = if pinnacle.lock_state.is_unlocked() {
CLEAR_COLOR
} else {
@ -1572,6 +1592,10 @@ impl Udev {
Ok(true) => surface.render_state = RenderState::WaitingForVblank { dirty: false },
Ok(false) | Err(_) => surface.render_state = RenderState::Idle,
}
if render_after_transaction_finish {
self.schedule_render(&pinnacle.loop_handle, output);
}
}
}

View file

@ -335,6 +335,24 @@ impl Winit {
));
}
let mut clear_snapshots = false;
self.output.with_state_mut(|state| {
if state
.layout_transaction
.as_ref()
.is_some_and(|ts| ts.ready())
{
state.layout_transaction.take();
clear_snapshots = true;
}
});
if clear_snapshots {
for win in pinnacle.windows.iter() {
win.with_state_mut(|state| state.snapshot.take());
}
}
let render_res = self.backend.bind().and_then(|_| {
let age = if *full_redraw > 0 {
0

View file

@ -1,7 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
use smithay::{desktop::space::SpaceElement, output::Output, utils::SERIAL_COUNTER};
use tracing::warn;
use crate::{
state::{Pinnacle, State, WithState},
@ -77,17 +76,10 @@ impl Pinnacle {
}
/// Raise a window to the top of the z-index stack.
///
/// This does nothing if the window is unmapped.
pub fn raise_window(&mut self, window: WindowElement, activate: bool) {
if self.space.elements().all(|win| win != &window) {
warn!("Tried to raise an unmapped window");
return;
}
self.space.raise_element(&window, activate);
self.z_index_stack.retain(|win| win != &window);
self.z_index_stack.retain(|win| win != window);
self.z_index_stack.push(window);
self.fixup_xwayland_window_layering();
@ -128,7 +120,7 @@ impl WindowKeyboardFocusStack {
/// If it's already in the stack, it will be removed then pushed.
/// If it isn't, it will just be pushed.
pub fn set_focus(&mut self, window: WindowElement) {
self.stack.retain(|win| win != &window);
self.stack.retain(|win| win != window);
self.stack.push(window);
self.focused = true;
}

View file

@ -1,10 +1,11 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pub mod session_lock;
pub mod window;
mod xdg_shell;
mod xwayland;
use std::{mem, os::fd::OwnedFd, sync::Arc, time::Duration};
use std::{mem, os::fd::OwnedFd, sync::Arc};
use smithay::{
backend::renderer::utils::{self, with_renderer_surface_state},
@ -13,8 +14,8 @@ use smithay::{
delegate_primary_selection, delegate_relative_pointer, delegate_seat,
delegate_security_context, delegate_shm, delegate_viewporter, delegate_xwayland_shell,
desktop::{
self, find_popup_root_surface, get_popup_toplevel_coords, layer_map_for_output,
utils::surface_primary_scanout_output, PopupKind, WindowSurfaceType,
self, find_popup_root_surface, get_popup_toplevel_coords, layer_map_for_output, PopupKind,
PopupManager, WindowSurfaceType,
},
input::{
pointer::{CursorImageStatus, PointerHandle},
@ -32,12 +33,12 @@ use smithay::{
Client, Resource,
},
},
utils::{Logical, Point, Rectangle, SERIAL_COUNTER},
utils::{Logical, Point, Rectangle},
wayland::{
buffer::BufferHandler,
compositor::{
self, BufferAssignment, CompositorClientState, CompositorHandler, CompositorState,
SurfaceAttributes,
self, add_pre_commit_hook, BufferAssignment, CompositorClientState, CompositorHandler,
CompositorState, SurfaceAttributes,
},
dmabuf,
fractional_scale::{self, FractionalScaleHandler},
@ -73,11 +74,13 @@ use crate::{
backend::Backend,
delegate_foreign_toplevel, delegate_gamma_control, delegate_screencopy,
focus::{keyboard::KeyboardFocusTarget, pointer::PointerFocusTarget},
handlers::xdg_shell::snapshot_pre_commit_hook,
protocol::{
foreign_toplevel::{self, ForeignToplevelHandler, ForeignToplevelManagerState},
gamma_control::{GammaControlHandler, GammaControlManagerState},
screencopy::{Screencopy, ScreencopyHandler},
},
render::util::snapshot::capture_snapshots_on_output,
state::{ClientState, Pinnacle, State, WithState},
};
@ -137,108 +140,175 @@ impl CompositorHandler for State {
self.backend.early_import(surface);
if compositor::is_sync_subsurface(surface) {
return;
}
let mut root = surface.clone();
while let Some(parent) = compositor::get_parent(&root) {
root = parent;
}
if !compositor::is_sync_subsurface(surface) {
self.pinnacle
.root_surface_cache
.insert(surface.clone(), root.clone());
if let Some(window) = self.pinnacle.window_for_surface(&root) {
window.mark_serial_as_committed();
window.on_commit();
if let Some(loc) = window.with_state_mut(|state| state.target_loc.take()) {
self.pinnacle.space.map_element(window.clone(), loc, false);
}
}
};
self.pinnacle.popup_manager.commit(surface);
// TODO: maps here, is that good?
self.pinnacle.move_surface_if_resized(surface);
if let Some(new_window) = self
.pinnacle
.new_windows
.iter()
.find(|win| win.wl_surface().is_some_and(|surf| &*surf == surface))
.cloned()
{
// Root surface commit
if surface == &root {
// Unmapped window commit
if let Some(unmapped_window) = self.pinnacle.unmapped_window_for_surface(surface) {
let Some(is_mapped) =
with_renderer_surface_state(surface, |state| state.buffer().is_some())
else {
unreachable!("on_commit_buffer_handler was called previously");
};
// Unmapped window has become mapped
if is_mapped {
self.pinnacle.new_windows.retain(|win| win != &new_window);
self.pinnacle.windows.push(new_window.clone());
unmapped_window.on_commit();
if let Some(output) = self.pinnacle.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()));
if let Some(toplevel) = unmapped_window.toplevel() {
let hook_id =
add_pre_commit_hook(toplevel.wl_surface(), snapshot_pre_commit_hook);
unmapped_window
.with_state_mut(|state| state.snapshot_hook_id = Some(hook_id));
}
// 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.
let snapshots = if let Some(output) = self.pinnacle.focused_output().cloned() {
tracing::debug!("Placing toplevel");
unmapped_window.place_on_output(&output);
output.with_state_mut(|state| {
state.focus_stack.set_focus(unmapped_window.clone())
});
Some(self.backend.with_renderer(|renderer| {
capture_snapshots_on_output(&mut self.pinnacle, renderer, &output, [])
}))
} else {
None
};
self.pinnacle
.space
.map_element(new_window.clone(), (1000000, 0), true);
.unmapped_windows
.retain(|win| win != unmapped_window);
self.pinnacle.windows.push(unmapped_window.clone());
self.pinnacle.raise_window(new_window.clone(), true);
self.pinnacle.raise_window(unmapped_window.clone(), true);
self.pinnacle.apply_window_rules(&new_window);
self.pinnacle.apply_window_rules(&unmapped_window);
if let Some(focused_output) = self.pinnacle.focused_output().cloned() {
self.pinnacle.request_layout(&focused_output);
new_window.send_frame(
&focused_output,
self.pinnacle.clock.now(),
Some(Duration::ZERO),
surface_primary_scanout_output,
);
if unmapped_window.is_on_active_tag() {
self.update_keyboard_focus(&focused_output);
// FIXME: an actual way to map new windows
// This is not self.update_keyboard_focus because
// the extra configure in that causes windows to map then resize
self.pinnacle.loop_handle.insert_idle(move |state| {
if let Some(keyboard) = state.pinnacle.seat.get_keyboard() {
if new_window.is_on_active_tag() {
keyboard.set_focus(
state,
Some(KeyboardFocusTarget::Window(new_window)),
SERIAL_COUNTER.next_serial(),
);
}
}
if let Some((fs_and_up_snapshots, under_fs_snapshots)) =
snapshots.flatten()
{
focused_output.with_state_mut(|state| {
state.new_wait_layout_transaction(
self.pinnacle.loop_handle.clone(),
fs_and_up_snapshots,
under_fs_snapshots,
)
});
}
} else if new_window.toplevel().is_some() {
new_window.on_commit();
self.pinnacle.request_layout(&focused_output);
}
}
} else {
// Still unmapped
unmapped_window.on_commit();
self.pinnacle.ensure_initial_configure(surface);
}
return;
}
// Window surface commit
if let Some(window) = self.pinnacle.window_for_surface(surface) {
if window.is_wayland() {
let Some(is_mapped) =
with_renderer_surface_state(surface, |state| state.buffer().is_some())
else {
unreachable!("on_commit_buffer_handler was called previously");
};
window.on_commit();
// Toplevel has become unmapped,
// see https://wayland.app/protocols/xdg-shell#xdg_toplevel
if !is_mapped {
if let Some(hook_id) =
window.with_state_mut(|state| state.snapshot_hook_id.take())
{
compositor::remove_pre_commit_hook(surface, hook_id);
}
if let Some(output) = window.output(&self.pinnacle) {
let snapshots = self.backend.with_renderer(|renderer| {
capture_snapshots_on_output(
&mut self.pinnacle,
renderer,
&output,
[],
)
});
if let Some((fs_and_up_snapshots, under_fs_snapshots)) = snapshots {
output.with_state_mut(|op_state| {
op_state.new_wait_layout_transaction(
self.pinnacle.loop_handle.clone(),
fs_and_up_snapshots,
under_fs_snapshots,
)
});
}
}
self.pinnacle.remove_window(&window, true);
if let Some(output) = window.output(&self.pinnacle) {
self.update_keyboard_focus(&output);
self.pinnacle.request_layout(&output);
}
}
// Update reactive popups
for (popup, _) in PopupManager::popups_for_surface(surface) {
if let PopupKind::Xdg(popup) = popup {
if popup.with_pending_state(|state| state.positioner.reactive) {
self.pinnacle.position_popup(&popup);
if let Err(err) = popup.send_pending_configure() {
warn!("Failed to configure reactive popup: {err}");
}
}
}
}
}
}
}
// TODO: split this up and don't call every commit
self.pinnacle.ensure_initial_configure(surface);
self.pinnacle.move_surface_if_resized(surface);
self.pinnacle.popup_manager.commit(surface);
let outputs = if let Some(window) = self.pinnacle.window_for_surface(surface) {
let mut outputs = self.pinnacle.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.pinnacle) {
outputs.push(output);
}
outputs // surface is a window
self.pinnacle.space.outputs_for_element(&window) // surface is a window
} else if let Some(window) = self.pinnacle.window_for_surface(&root) {
let mut outputs = self.pinnacle.space.outputs_for_element(&window);
if let Some(output) = window.output(&self.pinnacle) {
outputs.push(output);
}
outputs // surface is a root window
self.pinnacle.space.outputs_for_element(&window) // surface's root is a window
} else if let Some(PopupKind::Xdg(surf)) = self.pinnacle.popup_manager.find_popup(surface) {
// INFO: is this relative to the global space or no
let geo = surf.with_pending_state(|state| state.geometry);
let outputs = self
.pinnacle
@ -250,7 +320,7 @@ impl CompositorHandler for State {
})
.cloned()
.collect::<Vec<_>>();
outputs
outputs // surface is a popup
} else if let Some(output) = self
.pinnacle
.space
@ -278,7 +348,7 @@ impl CompositorHandler for State {
})
.cloned()
{
vec![output]
vec![output] // surface is a lock surface
} else {
return;
};
@ -288,6 +358,36 @@ impl CompositorHandler for State {
}
}
fn destroyed(&mut self, surface: &WlSurface) {
let Some(root_surface) = self.pinnacle.root_surface_cache.get(surface) else {
return;
};
let Some(window) = self.pinnacle.window_for_surface(root_surface) else {
return;
};
let Some(output) = window.output(&self.pinnacle) else {
return;
};
let Some(loc) = self.pinnacle.space.element_location(&window) else {
return;
};
let loc = loc - output.current_location();
self.backend.with_renderer(|renderer| {
window.capture_snapshot_and_store(
renderer,
loc,
output.current_scale().fractional_scale().into(),
1.0,
);
});
self.pinnacle
.root_surface_cache
.retain(|surf, root| surf != surface && root != surface);
}
fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
if let Some(state) = client.get_data::<XWaylandClientData>() {
return &state.compositor_state;
@ -302,23 +402,20 @@ delegate_compositor!(State);
impl Pinnacle {
fn ensure_initial_configure(&mut self, surface: &WlSurface) {
if let (Some(window), _) | (None, Some(window)) = (
self.window_for_surface(surface),
self.new_window_for_surface(surface),
) {
if let Some(window) = self.unmapped_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")
.unwrap()
.lock()
.expect("Failed to lock Mutex<XdgToplevelSurfaceData>")
.unwrap()
.initial_configure_sent
});
if !initial_configure_sent {
tracing::debug!("Initial configure");
tracing::debug!("Initial configure on wl_surface {:?}", surface.id());
toplevel.send_configure();
}
}
@ -331,15 +428,15 @@ impl Pinnacle {
states
.data_map
.get::<XdgPopupSurfaceData>()
.expect("XdgPopupSurfaceData wasn't in popup's data map")
.unwrap()
.lock()
.expect("Failed to lock Mutex<XdgPopupSurfaceData>")
.unwrap()
.initial_configure_sent
});
if !initial_configure_sent {
popup
.send_configure()
.expect("popup initial configure failed");
popup.send_configure().expect(
"sent configure for popup that doesn't allow multiple or is nonreactive",
);
}
return;
}
@ -355,9 +452,9 @@ impl Pinnacle {
states
.data_map
.get::<LayerSurfaceData>()
.expect("no LayerSurfaceData")
.unwrap()
.lock()
.expect("failed to lock data")
.unwrap()
.initial_configure_sent
});
@ -725,9 +822,9 @@ impl ForeignToplevelHandler for State {
output.with_state(|state| {
if state.tags.contains(&tag) {
for op_tag in state.tags.iter() {
op_tag.set_active(false, self);
op_tag.set_active(false, &mut self.pinnacle);
}
tag.set_active(true, self);
tag.set_active(true, &mut self.pinnacle);
}
});
}
@ -754,16 +851,7 @@ impl ForeignToplevelHandler for State {
return;
};
if !window.with_state(|state| state.fullscreen_or_maximized.is_fullscreen()) {
window.toggle_fullscreen();
}
let Some(output) = window.output(&self.pinnacle) else {
return;
};
self.pinnacle.request_layout(&output);
self.schedule_render(&output);
self.set_window_fullscreen(&window, true);
}
fn unset_fullscreen(&mut self, wl_surface: WlSurface) {
@ -771,16 +859,7 @@ impl ForeignToplevelHandler for State {
return;
};
if window.with_state(|state| state.fullscreen_or_maximized.is_fullscreen()) {
window.toggle_fullscreen();
}
let Some(output) = window.output(&self.pinnacle) else {
return;
};
self.pinnacle.request_layout(&output);
self.schedule_render(&output);
self.set_window_fullscreen(&window, false);
}
fn set_maximized(&mut self, wl_surface: WlSurface) {
@ -788,16 +867,7 @@ impl ForeignToplevelHandler for State {
return;
};
if !window.with_state(|state| state.fullscreen_or_maximized.is_maximized()) {
window.toggle_maximized();
}
let Some(output) = window.output(&self.pinnacle) else {
return;
};
self.pinnacle.request_layout(&output);
self.schedule_render(&output);
self.set_window_maximized(&window, true);
}
fn unset_maximized(&mut self, wl_surface: WlSurface) {
@ -805,16 +875,7 @@ impl ForeignToplevelHandler for State {
return;
};
if window.with_state(|state| state.fullscreen_or_maximized.is_maximized()) {
window.toggle_maximized();
}
let Some(output) = window.output(&self.pinnacle) else {
return;
};
self.pinnacle.request_layout(&output);
self.schedule_render(&output);
self.set_window_maximized(&window, false);
}
fn set_minimized(&mut self, wl_surface: WlSurface) {

69
src/handlers/window.rs Normal file
View file

@ -0,0 +1,69 @@
use crate::{
render::util::snapshot::capture_snapshots_on_output,
state::{State, WithState},
window::WindowElement,
};
impl State {
pub fn set_window_maximized(&mut self, window: &WindowElement, maximized: bool) {
let snapshots = window.output(&self.pinnacle).map(|output| {
self.backend.with_renderer(|renderer| {
capture_snapshots_on_output(&mut self.pinnacle, renderer, &output, [window.clone()])
})
});
if maximized {
if !window.with_state(|state| state.fullscreen_or_maximized.is_maximized()) {
window.toggle_maximized();
}
} else if window.with_state(|state| state.fullscreen_or_maximized.is_maximized()) {
window.toggle_maximized();
}
if let Some(output) = window.output(&self.pinnacle) {
if let Some((fs_and_up_snapshots, under_fs_snapshots)) = snapshots.flatten() {
output.with_state_mut(|op_state| {
op_state.new_wait_layout_transaction(
self.pinnacle.loop_handle.clone(),
fs_and_up_snapshots,
under_fs_snapshots,
)
});
}
self.pinnacle.request_layout(&output);
self.schedule_render(&output);
}
}
pub fn set_window_fullscreen(&mut self, window: &WindowElement, fullscreen: bool) {
let snapshots = window.output(&self.pinnacle).map(|output| {
self.backend.with_renderer(|renderer| {
capture_snapshots_on_output(&mut self.pinnacle, renderer, &output, [window.clone()])
})
});
if fullscreen {
if !window.with_state(|state| state.fullscreen_or_maximized.is_fullscreen()) {
window.toggle_fullscreen();
}
} else if window.with_state(|state| state.fullscreen_or_maximized.is_fullscreen()) {
window.toggle_fullscreen();
}
if let Some(output) = window.output(&self.pinnacle) {
if let Some((fs_and_up_snapshots, under_fs_snapshots)) = snapshots.flatten() {
output.with_state_mut(|op_state| {
op_state.new_wait_layout_transaction(
self.pinnacle.loop_handle.clone(),
fs_and_up_snapshots,
under_fs_snapshots,
)
});
}
self.pinnacle.request_layout(&output);
self.schedule_render(&output);
}
}
}

View file

@ -9,19 +9,23 @@ use smithay::{
reexports::{
wayland_protocols::xdg::shell::server::xdg_toplevel::{self, ResizeEdge},
wayland_server::{
protocol::{wl_output::WlOutput, wl_seat::WlSeat},
Resource,
protocol::{wl_output::WlOutput, wl_seat::WlSeat, wl_surface::WlSurface},
DisplayHandle, Resource,
},
},
utils::Serial,
wayland::shell::xdg::{
wayland::{
compositor::{self, BufferAssignment, SurfaceAttributes},
shell::xdg::{
PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
},
},
};
use tracing::trace;
use crate::{
focus::keyboard::KeyboardFocusTarget,
render::util::snapshot::capture_snapshots_on_output,
state::{State, WithState},
window::WindowElement,
};
@ -33,6 +37,7 @@ impl XdgShellHandler for State {
fn new_toplevel(&mut self, surface: ToplevelSurface) {
surface.with_pending_state(|state| {
// state.size = Some((600, 400).into()); // gets wleird-slow-ack working
state.states.set(xdg_toplevel::State::TiledTop);
state.states.set(xdg_toplevel::State::TiledBottom);
state.states.set(xdg_toplevel::State::TiledLeft);
@ -40,7 +45,7 @@ impl XdgShellHandler for State {
});
let window = WindowElement::new(Window::new_wayland_window(surface.clone()));
self.pinnacle.new_windows.push(window);
self.pinnacle.unmapped_windows.push(window);
}
fn toplevel_destroyed(&mut self, surface: ToplevelSurface) {
@ -50,28 +55,32 @@ impl XdgShellHandler for State {
return;
};
self.pinnacle.windows.retain(|win| win != &window);
let snapshots = if let Some(output) = window.output(&self.pinnacle) {
self.backend.with_renderer(|renderer| {
Some(capture_snapshots_on_output(
&mut self.pinnacle,
renderer,
&output,
[],
))
})
} else {
None
};
self.pinnacle.z_index_stack.retain(|win| win != &window);
for output in self.pinnacle.space.outputs() {
output.with_state_mut(|state| state.focus_stack.stack.retain(|win| win != &window));
}
self.pinnacle.remove_window(&window, false);
if let Some(output) = window.output(&self.pinnacle) {
self.pinnacle.request_layout(&output);
let focus = self
.pinnacle
.focused_window(&output)
.map(KeyboardFocusTarget::Window);
if let Some(KeyboardFocusTarget::Window(window)) = &focus {
tracing::debug!("Focusing on prev win");
self.pinnacle.raise_window(window.clone(), true);
if let Some(toplevel) = window.toplevel() {
toplevel.send_configure();
}
if let Some((fs_and_up_snapshots, under_fs_snapshots)) = snapshots.flatten() {
output.with_state_mut(|state| {
state.new_wait_layout_transaction(
self.pinnacle.loop_handle.clone(),
fs_and_up_snapshots,
under_fs_snapshots,
);
});
}
self.update_keyboard_focus(&output);
@ -232,53 +241,30 @@ impl XdgShellHandler for State {
}
surface.with_pending_state(|state| {
state.states.set(xdg_toplevel::State::Fullscreen);
state.size = Some(geometry.size);
state.fullscreen_output = wl_output;
});
let Some(window) = self.pinnacle.window_for_surface(wl_surface) else {
tracing::error!("wl_surface had no window");
return;
};
if !window.with_state(|state| state.fullscreen_or_maximized.is_fullscreen()) {
window.toggle_fullscreen();
self.pinnacle.request_layout(&output);
}
self.set_window_fullscreen(&window, true);
}
surface.send_configure();
}
fn unfullscreen_request(&mut self, surface: ToplevelSurface) {
if !surface
.current_state()
.states
.contains(xdg_toplevel::State::Fullscreen)
{
return;
}
surface.with_pending_state(|state| {
state.states.unset(xdg_toplevel::State::Fullscreen);
state.size = None;
state.fullscreen_output.take();
});
surface.send_pending_configure();
let Some(window) = self.pinnacle.window_for_surface(surface.wl_surface()) else {
tracing::error!("wl_surface had no window");
return;
};
if window.with_state(|state| state.fullscreen_or_maximized.is_fullscreen()) {
window.toggle_fullscreen();
if let Some(output) = window.output(&self.pinnacle) {
self.pinnacle.request_layout(&output);
}
}
self.set_window_fullscreen(&window, false);
}
fn maximize_request(&mut self, surface: ToplevelSurface) {
@ -286,14 +272,7 @@ impl XdgShellHandler for State {
return;
};
if !window.with_state(|state| state.fullscreen_or_maximized.is_maximized()) {
window.toggle_maximized();
}
let Some(output) = window.output(&self.pinnacle) else {
return;
};
self.pinnacle.request_layout(&output);
self.set_window_maximized(&window, true);
}
fn unmaximize_request(&mut self, surface: ToplevelSurface) {
@ -301,14 +280,7 @@ impl XdgShellHandler for State {
return;
};
if window.with_state(|state| state.fullscreen_or_maximized.is_maximized()) {
window.toggle_maximized();
}
let Some(output) = window.output(&self.pinnacle) else {
return;
};
self.pinnacle.request_layout(&output);
self.set_window_maximized(&window, false);
}
fn minimize_request(&mut self, _surface: ToplevelSurface) {
@ -321,3 +293,40 @@ impl XdgShellHandler for State {
// TODO: impl the rest of the fns in XdgShellHandler
}
delegate_xdg_shell!(State);
pub fn snapshot_pre_commit_hook(
state: &mut State,
_display_handle: &DisplayHandle,
surface: &WlSurface,
) {
let Some(window) = state.pinnacle.window_for_surface(surface) else {
return;
};
let got_unmapped = compositor::with_states(surface, |states| {
let buffer = &states.cached_state.pending::<SurfaceAttributes>().buffer;
matches!(buffer, Some(BufferAssignment::Removed))
});
if got_unmapped {
let Some(output) = window.output(&state.pinnacle) else {
return;
};
let Some(loc) = state.pinnacle.space.element_location(&window) else {
return;
};
let loc = loc - output.current_location();
state.backend.with_renderer(|renderer| {
window.capture_snapshot_and_store(
renderer,
loc,
output.current_scale().fractional_scale().into(),
1.0,
);
});
} else {
window.with_state_mut(|state| state.snapshot.take());
}
}

View file

@ -26,6 +26,7 @@ use tracing::{debug, error, trace, warn};
use crate::{
cursor::Cursor,
focus::keyboard::KeyboardFocusTarget,
render::util::snapshot::capture_snapshots_on_output,
state::{Pinnacle, State, WithState},
window::{window_state::FloatingOrTiled, WindowElement},
};
@ -48,14 +49,7 @@ impl XwmHandler for State {
}
let window = WindowElement::new(Window::new_x11_window(surface));
self.pinnacle
.space
.map_element(window.clone(), (0, 0), true);
let bbox = self
.pinnacle
.space
.element_bbox(&window)
.expect("called element_bbox on an unmapped window");
let bbox = window.bbox();
let output_size = self
.pinnacle
@ -83,16 +77,13 @@ impl XwmHandler for State {
unreachable!()
};
self.pinnacle.space.map_element(window.clone(), loc, true);
surface.set_mapped(true).expect("failed to map x11 window");
let bbox = Rectangle::from_loc_and_size(loc, bbox.size);
debug!("map_window_request, configuring with bbox {bbox:?}");
surface
.configure(bbox)
.expect("failed to configure x11 window");
// TODO: ssd
if let Some(output) = self.pinnacle.focused_output() {
window.place_on_output(output);
@ -102,22 +93,42 @@ impl XwmHandler for State {
window.with_state_mut(|state| {
state.floating_or_tiled = FloatingOrTiled::Floating(bbox);
});
self.pinnacle.space.map_element(window.clone(), loc, true);
}
// TODO: will an unmap -> map duplicate the window
// TODO: do snapshot and transaction here BUT ONLY IF TILED AND ON ACTIVE TAG
let snapshots = if let Some(output) = window.output(&self.pinnacle) {
Some(self.backend.with_renderer(|renderer| {
capture_snapshots_on_output(&mut self.pinnacle, renderer, &output, [])
}))
} else {
None
};
self.pinnacle.windows.push(window.clone());
self.pinnacle.raise_window(window.clone(), true);
self.pinnacle.apply_window_rules(&window);
if window.is_on_active_tag() {
if let Some(output) = window.output(&self.pinnacle) {
output.with_state_mut(|state| state.focus_stack.set_focus(window.clone()));
self.pinnacle.request_layout(&output);
self.update_keyboard_focus(&output);
self.pinnacle.loop_handle.insert_idle(move |state| {
state.update_keyboard_focus(&output);
if let Some((fs_and_up_snapshots, under_fs_snapshots)) = snapshots.flatten() {
output.with_state_mut(|state| {
state.new_wait_layout_transaction(
self.pinnacle.loop_handle.clone(),
fs_and_up_snapshots,
under_fs_snapshots,
)
});
}
self.pinnacle.request_layout(&output);
}
}
}
fn mapped_override_redirect_window(&mut self, _xwm: XwmId, surface: X11Surface) {
@ -238,10 +249,6 @@ impl XwmHandler for State {
}
fn maximize_request(&mut self, _xwm: XwmId, window: X11Surface) {
window
.set_maximized(true)
.expect("failed to set x11 win to maximized");
let Some(window) = window
.wl_surface()
.and_then(|surf| self.pinnacle.window_for_surface(&surf))
@ -249,16 +256,10 @@ impl XwmHandler for State {
return;
};
if !window.with_state(|state| state.fullscreen_or_maximized.is_maximized()) {
window.toggle_maximized();
}
self.set_window_maximized(&window, true);
}
fn unmaximize_request(&mut self, _xwm: XwmId, window: X11Surface) {
window
.set_maximized(false)
.expect("failed to set x11 win to maximized");
let Some(window) = window
.wl_surface()
.and_then(|surf| self.pinnacle.window_for_surface(&surf))
@ -266,16 +267,10 @@ impl XwmHandler for State {
return;
};
if window.with_state(|state| state.fullscreen_or_maximized.is_maximized()) {
window.toggle_maximized();
}
self.set_window_maximized(&window, false);
}
fn fullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
window
.set_fullscreen(true)
.expect("failed to set x11 win to fullscreen");
let Some(window) = window
.wl_surface()
.and_then(|surf| self.pinnacle.window_for_surface(&surf))
@ -283,19 +278,10 @@ impl XwmHandler for State {
return;
};
if !window.with_state(|state| state.fullscreen_or_maximized.is_fullscreen()) {
window.toggle_fullscreen();
if let Some(output) = window.output(&self.pinnacle) {
self.pinnacle.request_layout(&output);
}
}
self.set_window_fullscreen(&window, true);
}
fn unfullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
window
.set_fullscreen(false)
.expect("failed to set x11 win to unfullscreen");
let Some(window) = window
.wl_surface()
.and_then(|surf| self.pinnacle.window_for_surface(&surf))
@ -303,12 +289,7 @@ impl XwmHandler for State {
return;
};
if window.with_state(|state| state.fullscreen_or_maximized.is_fullscreen()) {
window.toggle_fullscreen();
if let Some(output) = window.output(&self.pinnacle) {
self.pinnacle.request_layout(&output);
}
}
self.set_window_fullscreen(&window, true);
}
fn resize_request(
@ -426,42 +407,37 @@ impl XwmHandler for State {
impl State {
fn remove_xwayland_window(&mut self, surface: X11Surface) {
tracing::debug!("remove_xwayland_window");
let win = self
.pinnacle
.windows
.iter()
.find(|elem| elem.x11_surface() == Some(&surface))
.cloned();
if let Some(win) = win {
debug!("removing x11 window from windows");
for output in self.pinnacle.space.outputs() {
let snapshots = win.output(&self.pinnacle).map(|output| {
self.backend.with_renderer(|renderer| {
capture_snapshots_on_output(&mut self.pinnacle, renderer, &output, [])
})
});
self.pinnacle.remove_window(&win, false);
if let Some(output) = win.output(&self.pinnacle) {
if let Some((fs_and_up_snapshots, under_fs_snapshots)) = snapshots.flatten() {
output.with_state_mut(|state| {
state.focus_stack.stack.retain(|w| w != &win);
state.new_wait_layout_transaction(
self.pinnacle.loop_handle.clone(),
fs_and_up_snapshots,
under_fs_snapshots,
)
});
}
self.pinnacle.windows.retain(|w| w != &win);
self.pinnacle.z_index_stack.retain(|w| w != &win);
if let Some(output) = win.output(&self.pinnacle) {
self.pinnacle.request_layout(&output);
let focus = self
.pinnacle
.focused_window(&output)
.map(KeyboardFocusTarget::Window);
if let Some(KeyboardFocusTarget::Window(win)) = &focus {
self.pinnacle.raise_window(win.clone(), true);
if let Some(toplevel) = win.toplevel() {
toplevel.send_configure();
}
}
self.update_keyboard_focus(&output);
self.schedule_render(&output);
}
}

View file

@ -1,13 +1,14 @@
// SPDX-License-Identifier: GPL-3.0-or-later
use std::{collections::HashMap, time::Duration};
pub mod transaction;
use std::collections::HashMap;
use pinnacle_api_defs::pinnacle::layout::v0alpha1::{layout_request::Geometries, LayoutResponse};
use smithay::{
desktop::{layer_map_for_output, WindowSurface},
output::Output,
utils::{Logical, Point, Rectangle, Serial},
wayland::{compositor, shell::xdg::XdgToplevelSurfaceData},
utils::{Logical, Rectangle, Serial},
};
use tokio::sync::mpsc::UnboundedSender;
use tonic::Status;
@ -15,6 +16,7 @@ use tracing::warn;
use crate::{
output::OutputName,
render::util::snapshot::capture_snapshots_on_output,
state::{Pinnacle, State, WithState},
window::{
window_state::{FloatingOrTiled, FullscreenOrMaximized},
@ -22,12 +24,14 @@ use crate::{
},
};
use self::transaction::LayoutTransaction;
impl Pinnacle {
fn update_windows_with_geometries(
&mut self,
output: &Output,
geometries: Vec<Rectangle<i32, Logical>>,
) {
) -> Vec<(WindowElement, Serial)> {
let windows_on_foc_tags = output.with_state(|state| {
let focused_tags = state.focused_tags().collect::<Vec<_>>();
self.windows
@ -94,56 +98,24 @@ impl Pinnacle {
}
let mut pending_wins = Vec::<(WindowElement, Serial)>::new();
let mut non_pending_wins = Vec::<(Point<i32, Logical>, WindowElement)>::new();
for win in windows_on_foc_tags.iter() {
if win.with_state(|state| state.target_loc.is_some()) {
match win.underlying_surface() {
WindowSurface::Wayland(toplevel) => {
let pending = compositor::with_states(toplevel.wl_surface(), |states| {
states
.data_map
.get::<XdgToplevelSurfaceData>()
.expect("XdgToplevelSurfaceData wasn't in surface's data map")
.lock()
.expect("Failed to lock Mutex<XdgToplevelSurfaceData>")
.has_pending_changes()
});
if let WindowSurface::Wayland(toplevel) = win.underlying_surface() {
if let Some(serial) = toplevel.send_pending_configure() {
pending_wins.push((win.clone(), serial));
}
}
if pending {
pending_wins.push((win.clone(), toplevel.send_configure()))
} else {
let loc = win.with_state_mut(|state| state.target_loc.take());
if let Some(loc) = loc {
non_pending_wins.push((loc, win.clone()));
}
}
}
WindowSurface::X11(_) => {
// TODO: get rid of target_loc
let loc = win.with_state_mut(|state| state.target_loc.take());
if let Some(loc) = loc {
self.space.map_element(win.clone(), loc, false);
}
}
}
}
}
for (loc, window) in non_pending_wins {
self.space.map_element(window, loc, false);
}
// FIXME:
// We are sending frames here to get offscreen windows to commit and map.
// Obviously this is a bad way to do this but its a bandaid solution
// until decent transactional layout applications are implemented.
for (win, _serial) in pending_wins {
win.send_frame(output, self.clock.now(), Some(Duration::ZERO), |_, _| {
Some(output.clone())
});
}
self.fixup_z_layering();
pending_wins
}
/// Swaps two windows in the main window vec and updates all windows.
@ -295,9 +267,27 @@ impl State {
.fulfilled_requests
.insert(output.clone(), current_pending);
self.pinnacle
let snapshots = self.backend.with_renderer(|renderer| {
capture_snapshots_on_output(&mut self.pinnacle, renderer, &output, [])
});
let pending_windows = self
.pinnacle
.update_windows_with_geometries(&output, geometries);
output.with_state_mut(|state| {
if let Some(ts) = state.layout_transaction.as_mut() {
ts.update_pending(pending_windows);
} else if let Some((fs_and_up_snapshots, under_fs_snapshots)) = snapshots {
state.layout_transaction = Some(LayoutTransaction::new(
self.pinnacle.loop_handle.clone(),
fs_and_up_snapshots,
under_fs_snapshots,
pending_windows,
));
}
});
self.schedule_render(&output);
self.pinnacle.layout_state.pending_swap = false;

213
src/layout/transaction.rs Normal file
View file

@ -0,0 +1,213 @@
//! Layout transactions.
use std::{
collections::HashMap,
time::{Duration, Instant},
};
use smithay::{
backend::renderer::element::{
self,
surface::WaylandSurfaceRenderElement,
texture::{TextureBuffer, TextureRenderElement},
utils::RescaleRenderElement,
},
desktop::Space,
reexports::calloop::{
timer::{TimeoutAction, Timer},
LoopHandle,
},
utils::{Logical, Point, Scale, Serial, Transform},
};
use crate::{
pinnacle_render_elements,
render::{
texture::CommonTextureRenderElement, util::snapshot::RenderSnapshot, AsGlesRenderer,
PRenderer,
},
state::State,
window::WindowElement,
};
/// The timeout before transactions stop applying.
const TIMEOUT: Duration = Duration::from_millis(150);
/// Type for window snapshots.
pub type LayoutSnapshot = RenderSnapshot<CommonTextureRenderElement>;
pinnacle_render_elements! {
/// Render elements for an output snapshot
#[derive(Debug)]
pub enum SnapshotRenderElement<R> {
/// Draw the window itself.
Window = WaylandSurfaceRenderElement<R>,
/// Draw a snapshot of the window.
Snapshot = RescaleRenderElement<CommonTextureRenderElement>,
}
}
/// Specifier for snapshots
#[derive(Debug)]
pub enum SnapshotTarget {
/// Render a window.
Window(WindowElement),
/// Render a snapshot.
Snapshot(LayoutSnapshot),
}
/// A layout transaction.
///
/// While one is active on an output, its snapshots will be drawn instead of windows.
#[derive(Debug)]
pub struct LayoutTransaction {
/// The loop handle to schedule event loop wakeups.
loop_handle: LoopHandle<'static, State>,
/// The instant this transaction started.
///
/// Used for transaction timeout.
start_time: Instant,
/// The snapshots to render while the transaction is processing.
pub fullscreen_and_up_snapshots: Vec<SnapshotTarget>,
/// The snapshots to render while the transaction is processing.
pub under_fullscreen_snapshots: Vec<SnapshotTarget>,
/// The windows that the transaction is waiting on.
pending_windows: HashMap<WindowElement, Serial>,
/// Wait for an update to the windows this transaction is waiting on
/// in anticipation of a new layout.
wait: bool,
}
impl LayoutTransaction {
/// Schedule an event after the timeout to check for readiness.
fn register_wakeup(loop_handle: &LoopHandle<'static, State>) {
let _ = loop_handle.insert_source(
Timer::from_duration(TIMEOUT + Duration::from_millis(10)),
|_, _, _| TimeoutAction::Drop,
);
}
/// Creates a new layout transaction that will become immediately active.
pub fn new(
loop_handle: LoopHandle<'static, State>,
fullscreen_and_up_snapshots: impl IntoIterator<Item = SnapshotTarget>,
under_fullscreen_snapshots: impl IntoIterator<Item = SnapshotTarget>,
pending_windows: impl IntoIterator<Item = (WindowElement, Serial)>,
) -> Self {
Self::register_wakeup(&loop_handle);
Self {
loop_handle,
start_time: Instant::now(),
fullscreen_and_up_snapshots: fullscreen_and_up_snapshots.into_iter().collect(),
under_fullscreen_snapshots: under_fullscreen_snapshots.into_iter().collect(),
pending_windows: pending_windows.into_iter().collect(),
wait: false,
}
}
/// Wait for the next pending window update.
pub fn wait(&mut self) {
self.wait = true;
self.start_time = Instant::now();
Self::register_wakeup(&self.loop_handle);
}
/// Creates a new layout transaction that waits for the next update to pending windows.
pub fn new_and_wait(
loop_handle: LoopHandle<'static, State>,
fullscreen_and_up_snapshots: impl IntoIterator<Item = SnapshotTarget>,
under_fullscreen_snapshots: impl IntoIterator<Item = SnapshotTarget>,
) -> Self {
Self::register_wakeup(&loop_handle);
Self {
loop_handle,
start_time: Instant::now(),
fullscreen_and_up_snapshots: fullscreen_and_up_snapshots.into_iter().collect(),
under_fullscreen_snapshots: under_fullscreen_snapshots.into_iter().collect(),
pending_windows: HashMap::new(),
wait: true,
}
}
/// Updates the pending windows for this transaction, for example
/// when a new layout comes in while a transaction is already processing.
pub fn update_pending(
&mut self,
pending_windows: impl IntoIterator<Item = (WindowElement, Serial)>,
) {
self.pending_windows = pending_windows.into_iter().collect();
self.wait = false;
self.start_time = Instant::now();
Self::register_wakeup(&self.loop_handle);
}
/// Returns whether all pending windows have committed their serials or the timeout has been
/// reached.
pub fn ready(&self) -> bool {
Instant::now().duration_since(self.start_time) >= TIMEOUT
|| (!self.wait
&& self
.pending_windows
.iter()
.all(|(win, serial)| win.is_serial_committed(*serial)))
}
/// Render elements for this transaction, split into ones for windows fullscreen and up
/// and the rest.
///
/// Window targets will be rendered normally and snapshot targets will
/// render their texture.
pub fn render_elements<R: PRenderer + AsGlesRenderer>(
&self,
renderer: &mut R,
space: &Space<WindowElement>,
output_loc: Point<i32, Logical>,
scale: Scale<f64>,
alpha: f32,
) -> (Vec<SnapshotRenderElement<R>>, Vec<SnapshotRenderElement<R>>) {
let mut flat_map = |snapshot: &SnapshotTarget| match snapshot {
SnapshotTarget::Window(window) => {
let loc = space.element_location(window).unwrap_or_default() - output_loc;
window
.render_elements(renderer, loc, scale, alpha)
.into_iter()
.map(SnapshotRenderElement::Window)
.collect()
}
SnapshotTarget::Snapshot(snapshot) => {
let Some((texture, loc)) = snapshot.texture(renderer.as_gles_renderer()) else {
return Vec::new();
};
let buffer =
TextureBuffer::from_texture(renderer, texture, 1, Transform::Normal, None);
let elem = TextureRenderElement::from_texture_buffer(
loc.to_f64(),
&buffer,
Some(alpha),
None,
None,
element::Kind::Unspecified,
);
let common = CommonTextureRenderElement::new(elem);
let scale = Scale::from((1.0 / scale.x, 1.0 / scale.y));
vec![SnapshotRenderElement::Snapshot(
RescaleRenderElement::from_element(common, loc, scale),
)]
}
};
(
self.fullscreen_and_up_snapshots
.iter()
.flat_map(&mut flat_map)
.collect(),
self.under_fullscreen_snapshots
.iter()
.flat_map(&mut flat_map)
.collect(),
)
}
}

View file

@ -1,5 +1,3 @@
#![warn(clippy::unwrap_used)]
pub mod api;
pub mod backend;
pub mod cli;

View file

@ -6,15 +6,18 @@ use pinnacle_api_defs::pinnacle::signal::v0alpha1::{OutputMoveResponse, OutputRe
use smithay::{
desktop::layer_map_for_output,
output::{Mode, Output, Scale},
reexports::calloop::LoopHandle,
utils::{Logical, Point, Transform},
wayland::session_lock::LockSurface,
};
use crate::{
focus::WindowKeyboardFocusStack,
layout::transaction::{LayoutTransaction, SnapshotTarget},
protocol::screencopy::Screencopy,
state::{Pinnacle, WithState},
state::{Pinnacle, State, WithState},
tag::Tag,
window::window_state::FloatingOrTiled,
};
/// A unique identifier for an output.
@ -57,6 +60,8 @@ pub struct OutputState {
pub modes: Vec<Mode>,
pub lock_surface: Option<LockSurface>,
pub blanking_state: BlankingState,
/// A pending layout transaction.
pub layout_transaction: Option<LayoutTransaction>,
}
impl WithState for Output {
@ -89,6 +94,23 @@ impl OutputState {
pub fn focused_tags(&self) -> impl Iterator<Item = &Tag> {
self.tags.iter().filter(|tag| tag.active())
}
pub fn new_wait_layout_transaction(
&mut self,
loop_handle: LoopHandle<'static, State>,
fullscreen_and_up_snapshots: impl IntoIterator<Item = SnapshotTarget>,
under_fullscreen_snapshots: impl IntoIterator<Item = SnapshotTarget>,
) {
if let Some(ts) = self.layout_transaction.as_mut() {
ts.wait();
} else {
self.layout_transaction = Some(LayoutTransaction::new_and_wait(
loop_handle,
fullscreen_and_up_snapshots,
under_fullscreen_snapshots,
));
}
}
}
impl Pinnacle {
@ -102,6 +124,8 @@ impl Pinnacle {
scale: Option<Scale>,
location: Option<Point<i32, Logical>>,
) {
let old_scale = output.current_scale().fractional_scale();
output.change_current_state(mode, transform, scale, location);
if let Some(location) = location {
self.space.map_output(output, location);
@ -129,6 +153,41 @@ impl Pinnacle {
output.with_state_mut(|state| state.modes.push(mode));
}
if let Some(scale) = scale {
let pos_multiplier = old_scale / scale.fractional_scale();
for win in self
.windows
.iter()
.filter(|win| win.output(self).as_ref() == Some(output))
.filter(|win| win.with_state(|state| state.floating_or_tiled.is_floating()))
.cloned()
.collect::<Vec<_>>()
{
let Some(output) = win.output(self) else { unreachable!() };
let output_loc = output.current_location();
// FIXME: get everything out of this with_state
win.with_state_mut(|state| {
let FloatingOrTiled::Floating(rect) = &mut state.floating_or_tiled else {
unreachable!()
};
let loc = rect.loc;
let mut loc_relative_to_output = loc - output_loc;
loc_relative_to_output = loc_relative_to_output
.to_f64()
.upscale(pos_multiplier)
.to_i32_round();
rect.loc = loc_relative_to_output + output_loc;
self.space.map_element(win.clone(), rect.loc, false);
});
}
}
if let Some(lock_surface) = output.with_state(|state| state.lock_surface.clone()) {
lock_surface.with_pending_state(|state| {
let Some(new_geo) = self.space.output_geometry(output) else {

View file

@ -1,10 +1,16 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pub mod pointer;
pub mod render_elements;
pub mod texture;
pub mod util;
use std::{ops::Deref, sync::Mutex};
use smithay::{
backend::renderer::{
element::{surface::WaylandSurfaceRenderElement, AsRenderElements, RenderElementStates},
gles::GlesRenderer,
ImportAll, ImportMem, Renderer, Texture,
},
desktop::{
@ -14,70 +20,180 @@ use smithay::{
surface_presentation_feedback_flags_from_states, surface_primary_scanout_output,
OutputPresentationFeedback,
},
Space,
PopupManager, Space, WindowSurface,
},
input::pointer::{CursorImageAttributes, CursorImageStatus},
output::Output,
reexports::wayland_server::protocol::wl_surface::WlSurface,
render_elements,
utils::{Logical, Physical, Point, Scale},
utils::{Logical, Point, Scale},
wayland::{compositor, shell::wlr_layer},
};
use crate::{
backend::Backend,
backend::{udev::UdevRenderer, Backend},
layout::transaction::{LayoutTransaction, SnapshotRenderElement, SnapshotTarget},
pinnacle_render_elements,
state::{State, WithState},
window::WindowElement,
};
use self::pointer::{PointerElement, PointerRenderElement};
pub mod pointer;
use self::{
pointer::{PointerElement, PointerRenderElement},
texture::CommonTextureRenderElement,
util::surface::texture_render_elements_from_surface_tree,
};
pub const CLEAR_COLOR: [f32; 4] = [0.6, 0.6, 0.6, 1.0];
pub const CLEAR_COLOR_LOCKED: [f32; 4] = [0.2, 0.0, 0.3, 1.0];
render_elements! {
pub OutputRenderElement<R> where R: ImportAll + ImportMem;
pinnacle_render_elements! {
#[derive(Debug)]
pub enum OutputRenderElement<R> {
Surface = WaylandSurfaceRenderElement<R>,
Pointer = PointerRenderElement<R>,
Snapshot = SnapshotRenderElement<R>,
}
}
impl<R> AsRenderElements<R> for WindowElement
/// Trait to reduce bound specifications.
pub trait PRenderer
where
R: Renderer + ImportAll + ImportMem,
<R as Renderer>::TextureId: Texture + Clone + 'static,
Self: Renderer<TextureId = Self::PTextureId, Error = Self::PError> + ImportAll + ImportMem,
<Self as Renderer>::TextureId: Texture + Clone + 'static,
{
type RenderElement = WaylandSurfaceRenderElement<R>;
// Self::TextureId: Texture + Clone + 'static doesn't work in the where clause,
// which is why these associated types exist.
//
// From https://github.com/YaLTeR/niri/blob/ae7fb4c4f405aa0ff49930040d414581a812d938/src/render_helpers/renderer.rs#L10
type PTextureId: Texture + Clone + 'static;
type PError: std::error::Error + Send + Sync + 'static;
}
fn render_elements<C: From<Self::RenderElement>>(
impl<R> PRenderer for R
where
R: ImportAll + ImportMem,
R::TextureId: Texture + Clone + 'static,
R::Error: std::error::Error + Send + Sync + 'static,
{
type PTextureId = R::TextureId;
type PError = R::Error;
}
/// Trait for renderers that provide [`GlesRenderer`]s.
pub trait AsGlesRenderer {
/// Gets a [`GlesRenderer`] from this renderer.
fn as_gles_renderer(&mut self) -> &mut GlesRenderer;
}
impl AsGlesRenderer for GlesRenderer {
fn as_gles_renderer(&mut self) -> &mut GlesRenderer {
self
}
}
impl<'a> AsGlesRenderer for UdevRenderer<'a> {
fn as_gles_renderer(&mut self) -> &mut GlesRenderer {
self.as_mut()
}
}
impl WindowElement {
/// Render elements for this window at the given *logical* location in the space,
/// output-relative.
pub fn render_elements<R: PRenderer>(
&self,
renderer: &mut R,
location: Point<i32, Physical>,
location: Point<i32, Logical>,
scale: Scale<f64>,
alpha: f32,
) -> Vec<C> {
) -> Vec<WaylandSurfaceRenderElement<R>> {
let location = location - self.geometry().loc;
let phys_loc = location.to_f64().to_physical_precise_round(scale);
self.deref()
.render_elements(renderer, location, scale, alpha)
.render_elements(renderer, phys_loc, scale, alpha)
}
/// Render elements for this window as textures.
pub fn texture_render_elements<R: PRenderer + AsGlesRenderer>(
&self,
renderer: &mut R,
location: Point<i32, Logical>,
scale: Scale<f64>,
alpha: f32,
) -> Vec<CommonTextureRenderElement> {
let location = location - self.geometry().loc;
let location = location.to_f64().to_physical_precise_round(scale);
match self.underlying_surface() {
WindowSurface::Wayland(s) => {
let mut render_elements = Vec::new();
let surface = s.wl_surface();
let popup_render_elements =
PopupManager::popups_for_surface(surface).flat_map(|(popup, popup_offset)| {
let offset = (self.geometry().loc + popup_offset - popup.geometry().loc)
.to_physical_precise_round(scale);
texture_render_elements_from_surface_tree(
renderer.as_gles_renderer(),
popup.wl_surface(),
location + offset,
scale,
alpha,
)
});
render_elements.extend(
popup_render_elements
.into_iter()
.map(CommonTextureRenderElement::new),
);
render_elements.extend(
texture_render_elements_from_surface_tree(
renderer.as_gles_renderer(),
surface,
location,
scale,
alpha,
)
.into_iter()
.map(CommonTextureRenderElement::new),
);
render_elements
}
WindowSurface::X11(s) => {
if let Some(surface) = s.wl_surface() {
texture_render_elements_from_surface_tree(
renderer.as_gles_renderer(),
&surface,
location,
scale,
alpha,
)
.into_iter()
.map(CommonTextureRenderElement::new)
.collect()
} else {
Vec::new()
}
}
}
}
}
struct LayerRenderElements<R: Renderer> {
struct LayerRenderElements<R: PRenderer> {
background: Vec<WaylandSurfaceRenderElement<R>>,
bottom: Vec<WaylandSurfaceRenderElement<R>>,
top: Vec<WaylandSurfaceRenderElement<R>>,
overlay: Vec<WaylandSurfaceRenderElement<R>>,
}
fn layer_render_elements<R>(
fn layer_render_elements<R: PRenderer>(
output: &Output,
renderer: &mut R,
scale: Scale<f64>,
) -> LayerRenderElements<R>
where
R: Renderer + ImportAll,
<R as Renderer>::TextureId: Clone + 'static,
{
) -> LayerRenderElements<R> {
let layer_map = layer_map_for_output(output);
let mut overlay = vec![];
let mut top = vec![];
@ -119,38 +235,27 @@ where
///
/// ret.1 contains render elements for the windows at and above the first fullscreen window.
/// ret.2 contains the rest.
fn window_render_elements<R>(
fn window_render_elements<R: PRenderer>(
output: &Output,
windows: &[WindowElement],
space: &Space<WindowElement>,
renderer: &mut R,
scale: Scale<f64>,
) -> (Vec<OutputRenderElement<R>>, Vec<OutputRenderElement<R>>)
where
R: Renderer + ImportAll + ImportMem,
<R as Renderer>::TextureId: Clone + 'static,
{
) -> (Vec<OutputRenderElement<R>>, Vec<OutputRenderElement<R>>) {
let mut last_fullscreen_split_at = 0;
let mut fullscreen_and_up = windows
.iter()
.rev() // rev because I treat the focus stack backwards vs how the renderer orders it
.filter(|win| win.is_on_active_tag())
.enumerate()
.map(|(i, win)| {
if win.with_state(|state| state.fullscreen_or_maximized.is_fullscreen()) {
last_fullscreen_split_at = i + 1;
}
// subtract win.geometry().loc to align decorations correctly
let loc = (
space.element_location(win) .unwrap_or((0, 0).into())
- win.geometry().loc
- output.current_location()
)
.to_physical_precise_round(scale);
let loc = space.element_location(win).unwrap_or_default() - output.current_location();
win.render_elements::<WaylandSurfaceRenderElement<R>>(renderer, loc, scale, 1.0)
win.render_elements(renderer, loc, scale, 1.0)
.into_iter()
.map(OutputRenderElement::from)
}).collect::<Vec<_>>();
@ -163,7 +268,7 @@ where
)
}
pub fn pointer_render_elements<R>(
pub fn pointer_render_elements<R: PRenderer>(
output: &Output,
renderer: &mut R,
space: &Space<WindowElement>,
@ -171,11 +276,7 @@ pub fn pointer_render_elements<R>(
cursor_status: &mut CursorImageStatus,
dnd_icon: Option<&WlSurface>,
pointer_element: &PointerElement<<R as Renderer>::TextureId>,
) -> Vec<OutputRenderElement<R>>
where
R: Renderer + ImportAll,
<R as Renderer>::TextureId: Clone + 'static,
{
) -> Vec<OutputRenderElement<R>> {
let mut output_render_elements = Vec::new();
let Some(output_geometry) = space.output_geometry(output) else {
@ -222,21 +323,54 @@ where
output_render_elements
}
/// Render elements for any pending layout transaction.
///
/// Returns fullscreen_and_up elements then under_fullscreen elements.
fn layout_transaction_render_elements<R: PRenderer + AsGlesRenderer>(
transaction: &LayoutTransaction,
space: &Space<WindowElement>,
renderer: &mut R,
scale: Scale<f64>,
output_loc: Point<i32, Logical>,
) -> (Vec<SnapshotRenderElement<R>>, Vec<SnapshotRenderElement<R>>) {
let mut flat_map = |target: &SnapshotTarget| match target {
SnapshotTarget::Window(win) => {
let loc = space.element_location(win).unwrap_or_default() - output_loc;
win.render_elements(renderer, loc, scale, 1.0)
.into_iter()
.map(SnapshotRenderElement::from)
.collect::<Vec<_>>()
}
SnapshotTarget::Snapshot(snapshot) => snapshot
.render_elements(renderer, scale, 1.0)
.into_iter()
.collect(),
};
(
transaction
.fullscreen_and_up_snapshots
.iter()
.flat_map(&mut flat_map)
.collect::<Vec<_>>(),
transaction
.under_fullscreen_snapshots
.iter()
.flat_map(&mut flat_map)
.collect::<Vec<_>>(),
)
}
/// Generate render elements for the given output.
///
/// Render elements will be pulled from the provided windows,
/// with the first window being at the top and subsequent ones beneath.
pub fn output_render_elements<R, T>(
pub fn output_render_elements<R: PRenderer + AsGlesRenderer>(
output: &Output,
renderer: &mut R,
space: &Space<WindowElement>,
windows: &[WindowElement],
) -> Vec<OutputRenderElement<R>>
where
R: Renderer<TextureId = T> + ImportAll + ImportMem,
<R as Renderer>::TextureId: 'static,
T: Texture + Clone,
{
) -> Vec<OutputRenderElement<R>> {
let scale = Scale::from(output.current_scale().fractional_scale());
let mut output_render_elements: Vec<OutputRenderElement<_>> = Vec::new();
@ -246,6 +380,11 @@ where
.cloned()
.partition::<Vec<_>, _>(|win| !win.is_x11_override_redirect());
let windows = windows
.into_iter()
.filter(|win| win.is_on_active_tag())
.collect::<Vec<_>>();
// // draw input method surface if any
// let rectangle = input_method.coordinates();
// let position = Point::from((
@ -262,13 +401,15 @@ where
// ));
// });
let o_r_elements = override_redirect_windows.iter().flat_map(|surf| {
surf.render_elements::<WaylandSurfaceRenderElement<R>>(
let output_loc = output.current_location();
let o_r_elements = override_redirect_windows
.iter()
.filter(|win| win.is_on_active_tag_on_output(output))
.flat_map(|surf| {
surf.render_elements(
renderer,
space
.element_location(surf)
.unwrap_or((0, 0).into())
.to_physical_precise_round(scale),
space.element_location(surf).unwrap_or_default() - output_loc,
scale,
1.0,
)
@ -285,8 +426,28 @@ where
overlay,
} = layer_render_elements(output, renderer, scale);
let (fullscreen_and_up_elements, rest_of_window_elements) =
let fullscreen_and_up_elements;
let rest_of_window_elements;
// If there is a snapshot, render its elements instead
if let Some((fs_and_up_elements, under_fs_elements)) = output.with_state(|state| {
state
.layout_transaction
.as_ref()
.map(|ts| layout_transaction_render_elements(ts, space, renderer, scale, output_loc))
}) {
fullscreen_and_up_elements = fs_and_up_elements
.into_iter()
.map(OutputRenderElement::from)
.collect();
rest_of_window_elements = under_fs_elements
.into_iter()
.map(OutputRenderElement::from)
.collect();
} else {
(fullscreen_and_up_elements, rest_of_window_elements) =
window_render_elements::<R>(output, &windows, space, renderer, scale);
}
// Elements render from top to bottom
@ -335,7 +496,7 @@ pub fn take_presentation_feedback(
}
impl State {
/// Schedule a new render. This does nothing on the winit backend.
/// Schedule a new render.
pub fn schedule_render(&mut self, output: &Output) {
match &mut self.backend {
Backend::Udev(udev) => {

View file

@ -15,6 +15,8 @@ use smithay::{
utils::{Physical, Point, Scale},
};
use super::PRenderer;
pub struct PointerElement<T: Texture> {
texture: Option<TextureBuffer<T>>,
status: CursorImageStatus,
@ -50,16 +52,13 @@ impl<T: Texture> PointerElement<T> {
}
render_elements! {
#[derive(Debug)]
pub PointerRenderElement<R> where R: ImportAll;
Surface=WaylandSurfaceRenderElement<R>,
Texture=TextureRenderElement<<R as Renderer>::TextureId>,
}
impl<T, R> AsRenderElements<R> for PointerElement<T>
where
T: Texture + Clone + 'static,
R: Renderer<TextureId = T> + ImportAll,
{
impl<R: PRenderer> AsRenderElements<R> for PointerElement<R::TextureId> {
type RenderElement = PointerRenderElement<R>;
fn render_elements<C: From<Self::RenderElement>>(

View file

@ -0,0 +1,229 @@
/// A custom implementation of [`smithay::render_elements`] that is not generic but rather
/// implements over the three used renderers.
///
/// This is needed to allow GlesTextures to be easily rendered on winit and udev.
///
/// Also idea from Niri. Ya know this whole compositor is slowly inching towards
/// being a Niri clone lol
#[macro_export]
macro_rules! pinnacle_render_elements {
(
$(#[$attr:meta])*
$vis:vis enum $name:ident {
$( $(#[$variant_attr:meta])* $variant:ident = $type:ty),+ $(,)?
}
) => {
$(#[$attr])*
$vis enum $name {
$( $(#[$variant_attr])* $variant($type)),+
}
$(impl From<$type> for $name {
fn from(x: $type) -> Self {
Self::$variant(x)
}
})+
$crate::pinnacle_render_elements! {
@impl $name ($name) () => { $($variant = $type),+ }
}
};
(
$(#[$attr:meta])*
$vis:vis enum $name:ident<$generic_name:ident> {
$( $(#[$variant_attr:meta])* $variant:ident = $type:ty),+ $(,)?
}
) => {
$(#[$attr])*
$vis enum $name<$generic_name>
where
$generic_name: ::smithay::backend::renderer::Renderer + ::smithay::backend::renderer::ImportAll + ::smithay::backend::renderer::ImportMem,
$generic_name::TextureId: 'static,
{
$( $(#[$variant_attr])* $variant($type)),+
}
$(impl<$generic_name> From<$type> for $name<$generic_name>
where
$generic_name: ::smithay::backend::renderer::Renderer + ::smithay::backend::renderer::ImportAll + ::smithay::backend::renderer::ImportMem,
$generic_name::TextureId: 'static,
{
fn from(x: $type) -> Self {
Self::$variant(x)
}
})+
$crate::pinnacle_render_elements! {
@impl $name () ($name<$generic_name>) => { $($variant = $type),+ }
}
};
(@impl $name:ident ($($name_no_generic:ident)?) ($($name_generic:ident<$generic:ident>)?) => {
$($variant:ident = $type:ty),+
}) => {
impl$(<$generic>)? ::smithay::backend::renderer::element::Element for $name$(<$generic>)?
$(where
$generic: ::smithay::backend::renderer::Renderer + ::smithay::backend::renderer::ImportAll + ::smithay::backend::renderer::ImportMem,
$generic::TextureId: 'static,)?
{
fn id(&self) -> &::smithay::backend::renderer::element::Id {
match self {
$($name::$variant(elem) => elem.id()),+
}
}
fn current_commit(&self) -> ::smithay::backend::renderer::utils::CommitCounter {
match self {
$($name::$variant(elem) => elem.current_commit()),+
}
}
fn geometry(
&self,
scale: ::smithay::utils::Scale<f64>
) -> ::smithay::utils::Rectangle<i32, smithay::utils::Physical> {
match self {
$($name::$variant(elem) => elem.geometry(scale)),+
}
}
fn transform(&self) -> ::smithay::utils::Transform {
match self {
$($name::$variant(elem) => elem.transform()),+
}
}
fn src(&self) -> ::smithay::utils::Rectangle<f64, ::smithay::utils::Buffer> {
match self {
$($name::$variant(elem) => elem.src()),+
}
}
fn damage_since(
&self,
scale: ::smithay::utils::Scale<f64>,
commit: ::std::option::Option<::smithay::backend::renderer::utils::CommitCounter>,
) -> ::smithay::backend::renderer::utils::DamageSet<i32, ::smithay::utils::Physical> {
match self {
$($name::$variant(elem) => elem.damage_since(scale, commit)),+
}
}
fn opaque_regions(
&self,
scale: ::smithay::utils::Scale<f64>,
) -> ::smithay::backend::renderer::utils::OpaqueRegions<i32, ::smithay::utils::Physical> {
match self {
$($name::$variant(elem) => elem.opaque_regions(scale)),+
}
}
fn alpha(&self) -> f32 {
match self {
$($name::$variant(elem) => elem.alpha()),+
}
}
fn kind(&self) -> ::smithay::backend::renderer::element::Kind {
match self {
$($name::$variant(elem) => elem.kind()),+
}
}
}
impl ::smithay::backend::renderer::element::RenderElement<::smithay::backend::renderer::gles::GlesRenderer>
for $($name_generic<::smithay::backend::renderer::gles::GlesRenderer>)? $($name_no_generic)?
{
fn draw(
&self,
frame: &mut ::smithay::backend::renderer::gles::GlesFrame<'_>,
src: ::smithay::utils::Rectangle<f64, ::smithay::utils::Buffer>,
dst: ::smithay::utils::Rectangle<i32, ::smithay::utils::Physical>,
damage: &[::smithay::utils::Rectangle<i32, ::smithay::utils::Physical>],
) -> ::std::result::Result<(), ::smithay::backend::renderer::gles::GlesError> {
match self {
$($name::$variant(elem) => {
::smithay::backend::renderer::element::RenderElement::<
::smithay::backend::renderer::gles::GlesRenderer
>::draw(elem, frame, src, dst, damage)
})+
}
}
fn underlying_storage(
&self,
renderer: &mut ::smithay::backend::renderer::gles::GlesRenderer
) -> ::std::option::Option<::smithay::backend::renderer::element::UnderlyingStorage> {
match self {
$($name::$variant(elem) => elem.underlying_storage(renderer)),+
}
}
}
impl<'a> ::smithay::backend::renderer::element::RenderElement<$crate::backend::udev::UdevRenderer<'a>>
for $($name_generic<$crate::backend::udev::UdevRenderer<'a>>)? $($name_no_generic)?
{
fn draw(
&self,
frame: &mut <$crate::backend::udev::UdevRenderer<'a> as ::smithay::backend::renderer::Renderer>::Frame<'_>,
src: ::smithay::utils::Rectangle<f64, ::smithay::utils::Buffer>,
dst: ::smithay::utils::Rectangle<i32, ::smithay::utils::Physical>,
damage: &[::smithay::utils::Rectangle<i32, ::smithay::utils::Physical>],
) -> ::std::result::Result<
(),
<$crate::backend::udev::UdevRenderer as ::smithay::backend::renderer::Renderer>::Error,
> {
match self {
$($name::$variant(elem) => {
::smithay::backend::renderer::element::RenderElement::<
$crate::backend::udev::UdevRenderer
>::draw(elem, frame, src, dst, damage)
})+
}
}
fn underlying_storage(
&self,
renderer: &mut $crate::backend::udev::UdevRenderer<'a>,
) -> ::std::option::Option<::smithay::backend::renderer::element::UnderlyingStorage> {
match self {
$($name::$variant(elem) => elem.underlying_storage(renderer)),+
}
}
}
#[cfg(feature = "testing")]
impl ::smithay::backend::renderer::element::RenderElement<::smithay::backend::renderer::test::DummyRenderer>
for $($name_generic<::smithay::backend::renderer::test::DummyRenderer>)? $($name_no_generic)?
{
fn draw(
&self,
frame: &mut <::smithay::backend::renderer::test::DummyRenderer as ::smithay::backend::renderer::Renderer>::Frame<'_>,
src: ::smithay::utils::Rectangle<f64, ::smithay::utils::Buffer>,
dst: ::smithay::utils::Rectangle<i32, ::smithay::utils::Physical>,
damage: &[::smithay::utils::Rectangle<i32, ::smithay::utils::Physical>],
) -> ::std::result::Result<
(),
<::smithay::backend::renderer::test::DummyRenderer as ::smithay::backend::renderer::Renderer>::Error,
> {
match self {
$($name::$variant(elem) => {
::smithay::backend::renderer::element::RenderElement::<
::smithay::backend::renderer::test::DummyRenderer
>::draw(elem, frame, src, dst, damage)
})+
}
}
fn underlying_storage(
&self,
renderer: &mut ::smithay::backend::renderer::test::DummyRenderer,
) -> ::std::option::Option<::smithay::backend::renderer::element::UnderlyingStorage> {
match self {
$($name::$variant(elem) => elem.underlying_storage(renderer)),+
}
}
}
}
}

123
src/render/texture.rs Normal file
View file

@ -0,0 +1,123 @@
#[cfg(feature = "testing")]
use smithay::backend::renderer::test::DummyRenderer;
use smithay::{
backend::renderer::{
element::{self, texture::TextureRenderElement, Element, RenderElement},
gles::{GlesRenderer, GlesTexture},
utils::{CommitCounter, DamageSet, OpaqueRegions},
Renderer,
},
utils::{Buffer, Physical, Rectangle, Scale},
};
use crate::backend::udev::UdevRenderer;
/// TODO: docs
#[derive(Debug)]
pub struct CommonTextureRenderElement(TextureRenderElement<GlesTexture>);
impl CommonTextureRenderElement {
pub fn new(element: TextureRenderElement<GlesTexture>) -> Self {
Self(element)
}
}
impl Element for CommonTextureRenderElement {
fn id(&self) -> &element::Id {
self.0.id()
}
fn current_commit(&self) -> CommitCounter {
self.0.current_commit()
}
fn src(&self) -> Rectangle<f64, Buffer> {
self.0.src()
}
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
self.0.geometry(scale)
}
fn location(&self, scale: Scale<f64>) -> smithay::utils::Point<i32, Physical> {
self.0.location(scale)
}
fn transform(&self) -> smithay::utils::Transform {
self.0.transform()
}
fn damage_since(
&self,
scale: Scale<f64>,
commit: Option<CommitCounter>,
) -> DamageSet<i32, Physical> {
self.0.damage_since(scale, commit)
}
fn opaque_regions(&self, scale: Scale<f64>) -> OpaqueRegions<i32, Physical> {
self.0.opaque_regions(scale)
}
fn alpha(&self) -> f32 {
self.0.alpha()
}
fn kind(&self) -> element::Kind {
self.0.kind()
}
}
impl RenderElement<GlesRenderer> for CommonTextureRenderElement {
fn draw(
&self,
frame: &mut <GlesRenderer as Renderer>::Frame<'_>,
src: Rectangle<f64, Buffer>,
dst: Rectangle<i32, Physical>,
damage: &[Rectangle<i32, Physical>],
) -> Result<(), <GlesRenderer as Renderer>::Error> {
RenderElement::<GlesRenderer>::draw(&self.0, frame, src, dst, damage)
}
fn underlying_storage(
&self,
renderer: &mut GlesRenderer,
) -> Option<element::UnderlyingStorage<'_>> {
let _ = renderer;
None
}
}
impl<'a> RenderElement<UdevRenderer<'a>> for CommonTextureRenderElement {
fn draw(
&self,
frame: &mut <UdevRenderer<'a> as Renderer>::Frame<'_>,
src: Rectangle<f64, Buffer>,
dst: Rectangle<i32, Physical>,
damage: &[Rectangle<i32, Physical>],
) -> Result<(), <UdevRenderer<'a> as Renderer>::Error> {
RenderElement::<GlesRenderer>::draw(&self.0, frame.as_mut(), src, dst, damage)?;
Ok(())
}
fn underlying_storage(
&self,
renderer: &mut UdevRenderer<'a>,
) -> Option<element::UnderlyingStorage<'_>> {
let _ = renderer;
None
}
}
#[cfg(feature = "testing")]
impl RenderElement<DummyRenderer> for CommonTextureRenderElement {
fn draw(
&self,
_frame: &mut <DummyRenderer as Renderer>::Frame<'_>,
_src: Rectangle<f64, Buffer>,
_dst: Rectangle<i32, Physical>,
_damage: &[Rectangle<i32, Physical>],
) -> Result<(), <DummyRenderer as Renderer>::Error> {
Ok(())
}
}

152
src/render/util.rs Normal file
View file

@ -0,0 +1,152 @@
//! Render utilities.
pub mod snapshot;
pub mod surface;
use anyhow::{bail, Context};
use smithay::backend::allocator::Fourcc;
use smithay::backend::renderer::element::utils::{Relocate, RelocateRenderElement};
use smithay::backend::renderer::{Bind, Frame, Offscreen, Renderer};
use smithay::utils::{Point, Rectangle};
use smithay::{
backend::renderer::{
element::RenderElement,
gles::{GlesRenderer, GlesTexture},
sync::SyncPoint,
},
utils::{Physical, Scale, Size, Transform},
};
/// A texture from [`render_to_encompassing_texture`].
///
/// Additionally contains the sync point and location that the elements would originally
/// be drawn at.
#[derive(Debug, Clone)]
pub struct EncompassingTexture {
/// The rendered texture.
pub texture: GlesTexture,
/// The sync point.
pub sync_point: SyncPoint,
/// The location the elements would have been originally drawn at.
pub loc: Point<i32, Physical>,
}
/// Renders the given elements to a [`GlesTexture`] that encompasses them all.
///
/// See [`render_to_texture`].
///
/// From https://github.com/YaLTeR/niri/blob/efb39e466b5248eb894745e899de33661493511d/src/render_helpers/mod.rs#L158
pub fn render_to_encompassing_texture<E: RenderElement<GlesRenderer>>(
renderer: &mut GlesRenderer,
elements: impl IntoIterator<Item = E>,
scale: Scale<f64>,
transform: Transform,
fourcc: Fourcc,
) -> anyhow::Result<EncompassingTexture> {
let elements = elements.into_iter().collect::<Vec<_>>();
let encompassing_geo = elements
.iter()
.map(|elem| elem.geometry(scale))
.reduce(|first, second| first.merge(second))
.context("no elements to render")?;
// Make elements relative to (0, 0) for rendering
let elements = elements.iter().rev().map(|elem| {
RelocateRenderElement::from_element(
elem,
(-encompassing_geo.loc.x, -encompassing_geo.loc.y),
Relocate::Relative,
)
});
let (texture, sync_point) = render_to_texture(
renderer,
elements,
encompassing_geo.size,
scale,
transform,
fourcc,
)?;
Ok(EncompassingTexture {
texture,
sync_point,
loc: encompassing_geo.loc,
})
}
/// Renders the given elements to a [`GlesTexture`].
///
/// `elements` should have their locations relative to (0, 0), as they will be rendered
/// to a texture with a rectangle of loc (0, 0) and size `size`. This can be achieved
/// by wrapping them in a
/// [`RelocateRenderElement`][smithay::backend::renderer::element::utils::RelocateRenderElement].
///
/// Elements outside of the rectangle will be clipped.
///
/// From https://github.com/YaLTeR/niri/blob/efb39e466b5248eb894745e899de33661493511d/src/render_helpers/mod.rs#L180
pub fn render_to_texture(
renderer: &mut GlesRenderer,
elements: impl IntoIterator<Item = impl RenderElement<GlesRenderer>>,
size: Size<i32, Physical>,
scale: Scale<f64>,
transform: Transform,
fourcc: Fourcc,
) -> anyhow::Result<(GlesTexture, SyncPoint)> {
if size.is_empty() {
// Causes GL_INVALID_VALUE when binding
bail!("size was empty");
}
let buffer_size = size.to_logical(1).to_buffer(1, Transform::Normal);
let texture: GlesTexture = renderer
.create_buffer(fourcc, buffer_size)
.context("failed to create texture")?;
renderer
.bind(texture.clone())
.context("failed to bind texture")?;
let sync_point =
render_elements_to_bound_framebuffer(renderer, elements, size, scale, transform)?;
Ok((texture, sync_point))
}
/// Renders the given elements into the currently bound framebuffer.
///
/// `elements` should have their locations relative to (0, 0), as they will be rendered
/// to a texture with a rectangle of loc (0, 0) and size `size`.
///
/// From https://github.com/YaLTeR/niri/blob/efb39e466b5248eb894745e899de33661493511d/src/render_helpers/mod.rs#L295
fn render_elements_to_bound_framebuffer(
renderer: &mut GlesRenderer,
elements: impl IntoIterator<Item = impl RenderElement<GlesRenderer>>,
size: Size<i32, Physical>,
scale: Scale<f64>,
transform: Transform,
) -> anyhow::Result<SyncPoint> {
// TODO: see what transform.invert() does here
let dst_rect = Rectangle::from_loc_and_size((0, 0), transform.transform_size(size));
let mut frame = renderer
.render(size, transform)
.context("failed to start render")?;
frame
.clear([0.0, 0.0, 0.0, 0.0], &[dst_rect])
.context("failed to clear frame")?;
for elem in elements {
let src = elem.src();
let dst = elem.geometry(scale);
if let Some(mut damage) = dst_rect.intersection(dst) {
damage.loc -= dst.loc;
elem.draw(&mut frame, src, dst, &[damage])
.context("failed to draw element")?;
}
}
frame.finish().context("failed to finish frame")
}

220
src/render/util/snapshot.rs Normal file
View file

@ -0,0 +1,220 @@
//! Utilities for capturing snapshots of windows and other elements.
use std::cell::OnceCell;
use std::collections::HashSet;
use std::rc::Rc;
use smithay::backend::allocator::Fourcc;
use smithay::backend::renderer::element;
use smithay::backend::renderer::element::texture::{TextureBuffer, TextureRenderElement};
use smithay::backend::renderer::element::utils::RescaleRenderElement;
use smithay::output::Output;
use smithay::utils::Logical;
use smithay::{
backend::renderer::{
element::RenderElement,
gles::{GlesRenderer, GlesTexture},
},
utils::{Physical, Point, Scale, Transform},
};
use tracing::error;
use crate::layout::transaction::{LayoutSnapshot, SnapshotRenderElement, SnapshotTarget};
use crate::render::texture::CommonTextureRenderElement;
use crate::render::{AsGlesRenderer, PRenderer};
use crate::state::{Pinnacle, WithState};
use crate::window::WindowElement;
use super::{render_to_encompassing_texture, EncompassingTexture};
/// A snapshot of given elements that can be rendered at some point in the future.
#[derive(Debug)]
pub struct RenderSnapshot<E> {
/// Rendered elements.
///
/// These are not used directly in rendering due to floating-point rounding
/// inaccuracies that cause pixel imperfections when being displayed.
elements: Rc<Vec<E>>,
/// The original scale used to create this snapshot.
scale: Scale<f64>,
/// The texture that elements will be rendered into.
///
/// Happens lazily for performance.
texture: OnceCell<(GlesTexture, Point<i32, Physical>)>,
}
impl<E> Clone for RenderSnapshot<E> {
fn clone(&self) -> Self {
Self {
elements: self.elements.clone(),
scale: self.scale,
texture: self.texture.clone(),
}
}
}
impl<E: RenderElement<GlesRenderer>> RenderSnapshot<E> {
/// Creates a new snapshot from elements.
pub fn new(elements: impl IntoIterator<Item = E>, scale: Scale<f64>) -> Self {
Self {
elements: Rc::new(elements.into_iter().collect()),
scale,
texture: OnceCell::new(),
}
}
/// Get the texture, rendering it to a new one if it doesn't exist.
pub fn texture(
&self,
renderer: &mut GlesRenderer,
) -> Option<(GlesTexture, Point<i32, Physical>)> {
// Not `get_or_init` because that would require the cell be an option/result
// and I didn't want that
if self.texture.get().is_none() {
let EncompassingTexture {
texture,
sync_point: _,
loc,
} = match render_to_encompassing_texture(
renderer,
self.elements.as_ref(),
self.scale,
Transform::Normal, // TODO: transform
Fourcc::Argb8888,
) {
Ok(tex) => tex,
Err(err) => {
error!("Failed to render to encompassing texture: {err}");
return None;
}
};
let Ok(()) = self.texture.set((texture, loc)) else {
unreachable!()
};
}
self.texture.get().cloned()
}
/// Render elements for this snapshot.
pub fn render_elements<R: PRenderer + AsGlesRenderer>(
&self,
renderer: &mut R,
scale: Scale<f64>,
alpha: f32,
) -> Option<SnapshotRenderElement<R>> {
let (texture, loc) = self.texture(renderer.as_gles_renderer())?;
let buffer = TextureBuffer::from_texture(renderer, texture, 1, Transform::Normal, None);
let elem = TextureRenderElement::from_texture_buffer(
loc.to_f64(),
&buffer,
Some(alpha),
None,
None,
element::Kind::Unspecified,
);
let common = CommonTextureRenderElement::new(elem);
// Scale in the opposite direction from the original scale to have it be the same size
let scale = Scale::from((1.0 / scale.x, 1.0 / scale.y));
Some(SnapshotRenderElement::Snapshot(
RescaleRenderElement::from_element(common, loc, scale),
))
}
}
impl WindowElement {
/// Capture a snapshot for this window and store it in its user data.
pub fn capture_snapshot_and_store(
&self,
renderer: &mut GlesRenderer,
location: Point<i32, Logical>,
scale: Scale<f64>,
alpha: f32,
) -> LayoutSnapshot {
self.with_state_mut(|state| {
if state.snapshot.is_none() || self.is_x11() {
let elements = self.texture_render_elements(renderer, location, scale, alpha);
state.snapshot = Some(RenderSnapshot::new(elements, scale));
}
let Some(ret) = state.snapshot.clone() else {
unreachable!()
};
ret
})
}
}
/// Capture snapshots for all tiled windows on this output.
///
/// Any windows in `also_include` are also included in the capture.
///
/// ret.1 = fullscreen and up,
/// ret.2 = under fullscreen
pub fn capture_snapshots_on_output(
pinnacle: &mut Pinnacle,
renderer: &mut GlesRenderer,
output: &Output,
also_include: impl IntoIterator<Item = WindowElement>,
) -> (Vec<SnapshotTarget>, Vec<SnapshotTarget>) {
let split_index = pinnacle
.space
.elements()
.filter(|win| {
win.is_on_active_tag_on_output(output)
|| (win.is_on_active_tag()
&& win.with_state(|state| state.floating_or_tiled.is_floating()))
})
.position(|win| win.with_state(|state| state.fullscreen_or_maximized.is_fullscreen()));
let mut under_fullscreen = pinnacle
.space
.elements()
.filter(|win| {
win.is_on_active_tag_on_output(output)
|| (win.is_on_active_tag()
&& win.with_state(|state| state.floating_or_tiled.is_floating()))
})
.cloned()
.collect::<Vec<_>>();
let fullscreen_and_up =
under_fullscreen.split_off(split_index.unwrap_or(under_fullscreen.len()));
let also_include = also_include.into_iter().collect::<HashSet<_>>();
let mut flat_map = |win: WindowElement| {
if win.with_state(|state| state.floating_or_tiled.is_tiled()) || also_include.contains(&win)
{
let loc = pinnacle.space.element_location(&win)? - output.current_location();
let snapshot = win.capture_snapshot_and_store(
renderer,
loc,
output.current_scale().fractional_scale().into(),
1.0,
);
Some(SnapshotTarget::Snapshot(snapshot))
} else {
Some(SnapshotTarget::Window(win))
}
};
let under_fullscreen_snapshots = under_fullscreen
.into_iter()
.rev()
.flat_map(&mut flat_map)
.collect();
let fullscreen_and_up_snapshots = fullscreen_and_up
.into_iter()
.rev()
.flat_map(&mut flat_map)
.collect();
(fullscreen_and_up_snapshots, under_fullscreen_snapshots)
}

114
src/render/util/surface.rs Normal file
View file

@ -0,0 +1,114 @@
//! Utils for rendering surface trees to textures.
use smithay::{
backend::renderer::{
element::{
self,
surface::WaylandSurfaceRenderElement,
texture::{TextureBuffer, TextureRenderElement},
},
gles::{GlesRenderer, GlesTexture},
utils::RendererSurfaceStateUserData,
},
reexports::wayland_server::protocol::wl_surface::WlSurface,
utils::{Physical, Point, Scale},
wayland::compositor::{self, TraversalAction},
};
use tracing::warn;
/// Render a surface tree as [TextureRenderElement]s instead of wayland ones.
///
/// Needed to allow WaylandSurfaceRenderElements to be dropped to free shm buffers.
pub fn texture_render_elements_from_surface_tree(
renderer: &mut GlesRenderer,
surface: &WlSurface,
location: impl Into<Point<i32, Physical>>,
scale: impl Into<Scale<f64>>,
alpha: f32,
) -> Vec<TextureRenderElement<GlesTexture>> {
let location = location.into().to_f64();
let scale = scale.into();
let mut surfaces: Vec<TextureRenderElement<GlesTexture>> = Vec::new();
compositor::with_surface_tree_downward(
surface,
location,
|_, states, location| {
let mut location = *location;
let data = states.data_map.get::<RendererSurfaceStateUserData>();
if let Some(data) = data {
let data = &*data.borrow();
if let Some(view) = data.view() {
location += view.offset.to_f64().to_physical(scale);
TraversalAction::DoChildren(location)
} else {
TraversalAction::SkipChildren
}
} else {
TraversalAction::SkipChildren
}
},
|surface, states, location| {
let mut location = *location;
let data = states.data_map.get::<RendererSurfaceStateUserData>();
if let Some(data) = data {
let has_view = if let Some(view) = data.borrow().view() {
location += view.offset.to_f64().to_physical(scale);
true
} else {
false
};
if has_view {
match WaylandSurfaceRenderElement::from_surface(
renderer,
surface,
states,
location,
alpha,
element::Kind::Unspecified,
) {
Ok(Some(surface)) => {
// Reconstruct the element as a TextureRenderElement
let data = data.borrow();
let view = data.view().unwrap();
// TODO: figure out what is making the WaylandSurfaceRenderElement
// not drop and release the shm buffer for wleird
let texture_buffer = TextureBuffer::from_texture(
renderer,
surface.texture().clone(),
data.buffer_scale(),
data.buffer_transform(),
None,
);
let texture_elem = TextureRenderElement::from_texture_buffer(
location,
&texture_buffer,
Some(alpha),
Some(view.src),
Some(view.dst),
element::Kind::Unspecified,
);
surfaces.push(texture_elem);
}
Ok(None) => {} // surface is not mapped
Err(err) => {
warn!("Failed to import surface: {}", err);
}
};
}
}
},
|_, _, _| true,
);
surfaces
}

View file

@ -51,7 +51,7 @@ use smithay::{
},
xwayland::{X11Wm, XWaylandClientData},
};
use std::{cell::RefCell, path::PathBuf, sync::Arc};
use std::{cell::RefCell, collections::HashMap, path::PathBuf, sync::Arc};
use sysinfo::{ProcessRefreshKind, RefreshKind};
use tracing::{info, warn};
use xdg::BaseDirectories;
@ -115,7 +115,8 @@ pub struct Pinnacle {
/// The main window vec
pub windows: Vec<WindowElement>,
pub new_windows: Vec<WindowElement>,
/// Windows with no buffer.
pub unmapped_windows: Vec<WindowElement>,
pub config: Config,
@ -133,6 +134,9 @@ pub struct Pinnacle {
pub signal_state: SignalState,
pub layout_state: LayoutState,
/// A cache of surfaces to their root surface.
pub root_surface_cache: HashMap<WlSurface, WlSurface>,
}
impl State {
@ -147,18 +151,23 @@ impl State {
winit.render_if_scheduled(&mut self.pinnacle);
}
// FIXME: Don't poll this every cycle
for output in self.pinnacle.space.outputs().cloned().collect::<Vec<_>>() {
output.with_state_mut(|state| {
if state
.layout_transaction
.as_ref()
.is_some_and(|ts| ts.ready())
{
self.schedule_render(&output);
}
});
}
self.pinnacle
.display_handle
.flush_clients()
.expect("failed to flush client buffers");
// TODO: couple these or something, this is really error-prone
assert_eq!(
self.pinnacle.windows.len(),
self.pinnacle.z_index_stack.len(),
"Length of `windows` and `z_index_stack` are different. \
If you see this, report it to the developer."
);
}
}
@ -295,7 +304,7 @@ impl Pinnacle {
popup_manager: PopupManager::default(),
windows: Vec::new(),
new_windows: Vec::new(),
unmapped_windows: Vec::new(),
xwm: None,
xdisplay: None,
@ -312,6 +321,8 @@ impl Pinnacle {
signal_state: SignalState::default(),
layout_state: LayoutState::default(),
root_surface_cache: HashMap::new(),
};
Ok(pinnacle)

View file

@ -9,7 +9,7 @@ use std::{
use smithay::output::Output;
use crate::state::{Pinnacle, State, WithState};
use crate::state::{Pinnacle, WithState};
static TAG_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
@ -43,47 +43,55 @@ impl TagId {
#[derive(Debug)]
struct TagInner {
/// The internal id of this tag.
id: TagId,
/// The name of this tag.
name: String,
/// Whether this tag is active or not.
active: bool,
}
impl PartialEq for TagInner {
/// A marker for windows.
///
/// A window may have 0 or more tags, and you can display 0 or more tags
/// on each output at a time.
#[derive(Debug, Clone)]
pub struct Tag {
/// The internal id of this tag.
id: TagId,
inner: Rc<RefCell<TagInner>>,
}
impl PartialEq for Tag {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for TagInner {}
impl Eq for Tag {}
/// A marker for windows.
///
/// A window may have 0 or more tags, and you can display 0 or more tags
/// on each output at a time.
#[derive(Debug, Clone, PartialEq)]
pub struct Tag(Rc<RefCell<TagInner>>);
impl Hash for Tag {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
// RefCell Safety: These methods should never panic because they are all self-contained or Copy.
impl Tag {
pub fn id(&self) -> TagId {
self.0.borrow().id
self.id
}
pub fn name(&self) -> String {
self.0.borrow().name.clone()
self.inner.borrow().name.clone()
}
pub fn active(&self) -> bool {
self.0.borrow().active
self.inner.borrow().active
}
pub fn set_active(&self, active: bool, state: &mut State) {
self.0.borrow_mut().active = active;
pub fn set_active(&self, active: bool, pinnacle: &mut Pinnacle) {
self.inner.borrow_mut().active = active;
state.pinnacle.signal_state.tag_active.signal(|buf| {
pinnacle.signal_state.tag_active.signal(|buf| {
buf.push_back(
pinnacle_api_defs::pinnacle::signal::v0alpha1::TagActiveResponse {
tag_id: Some(self.id().0),
@ -96,11 +104,13 @@ impl Tag {
impl Tag {
pub fn new(name: String) -> Self {
Self(Rc::new(RefCell::new(TagInner {
Self {
id: TagId::next(),
inner: Rc::new(RefCell::new(TagInner {
name,
active: false,
})))
})),
}
}
/// Get the output this tag is on.

View file

@ -2,13 +2,13 @@
pub mod rules;
use std::{cell::RefCell, ops::Deref};
use std::{cell::RefCell, collections::HashSet, ops::Deref};
use smithay::{
desktop::{space::SpaceElement, Window, WindowSurface},
output::Output,
reexports::wayland_server::protocol::wl_surface::WlSurface,
utils::{IsAlive, Logical, Point, Rectangle},
utils::{IsAlive, Logical, Point, Rectangle, Serial},
wayland::{compositor, seat::WaylandFocus, shell::xdg::XdgToplevelSurfaceData},
};
use tracing::{error, warn};
@ -30,6 +30,18 @@ impl Deref for WindowElement {
}
}
impl PartialEq<&WindowElement> for WindowElement {
fn eq(&self, other: &&WindowElement) -> bool {
self == *other
}
}
impl PartialEq<WindowElement> for &WindowElement {
fn eq(&self, other: &WindowElement) -> bool {
*self == other
}
}
impl WindowElement {
pub fn new(window: Window) -> Self {
Self(window)
@ -131,6 +143,23 @@ impl WindowElement {
self.with_state(|state| state.tags.iter().any(|tag| tag.active()))
}
pub fn is_on_active_tag_on_output(&self, output: &Output) -> bool {
// PERF: dear god benchmark this
let win_tags = self
.with_state(|state| state.tags.clone())
.into_iter()
.collect::<HashSet<_>>();
output.with_state(|state| {
state
.focused_tags()
.cloned()
.collect::<HashSet<_>>()
.intersection(&win_tags)
.next()
.is_some()
})
}
/// Place this window on the given output, giving it the output's focused tags.
///
/// RefCell Safety: Uses `with_state_mut` on the window and `with_state` on the output
@ -158,6 +187,30 @@ impl WindowElement {
pub fn is_x11_override_redirect(&self) -> bool {
matches!(self.x11_surface(), Some(surface) if surface.is_override_redirect())
}
/// Marks the currently acked configure as committed.
pub fn mark_serial_as_committed(&self) {
let Some(toplevel) = self.toplevel() else { return };
let serial = compositor::with_states(toplevel.wl_surface(), |states| {
states
.data_map
.get::<XdgToplevelSurfaceData>()
.unwrap()
.lock()
.unwrap()
.configure_serial
});
self.with_state_mut(|state| state.committed_serial = serial);
}
/// Returns whether the currently committed serial is >= the given serial.
pub fn is_serial_committed(&self, serial: Serial) -> bool {
match self.with_state(|state| state.committed_serial) {
Some(committed_serial) => committed_serial >= serial,
None => false,
}
}
}
impl SpaceElement for WindowElement {
@ -229,24 +282,34 @@ impl WithState for WindowElement {
impl Pinnacle {
/// Returns the [Window] associated with a given [WlSurface].
pub fn window_for_surface(&self, surface: &WlSurface) -> Option<WindowElement> {
self.space
.elements()
.find(|window| window.wl_surface().map(|s| &*s == surface).unwrap_or(false))
.or_else(|| {
self.windows
.iter()
.find(|&win| win.wl_surface().is_some_and(|surf| &*surf == surface))
})
.cloned()
}
/// `window_for_surface` but for windows that haven't commited a buffer yet.
///
/// Currently only used in `ensure_initial_configure` in [`handlers`][crate::handlers].
pub fn new_window_for_surface(&self, surface: &WlSurface) -> Option<WindowElement> {
self.new_windows
/// [`Self::window_for_surface`] but for windows that don't have a buffer.
pub fn unmapped_window_for_surface(&self, surface: &WlSurface) -> Option<WindowElement> {
self.unmapped_windows
.iter()
.find(|&win| win.wl_surface().is_some_and(|surf| &*surf == surface))
.cloned()
}
/// Removes a window from the main window vec, z_index stack, and focus stacks.
///
/// If `unmap` is true the window has become unmapped and will be pushed to `unmapped_windows`.
pub fn remove_window(&mut self, window: &WindowElement, unmap: bool) {
self.windows.retain(|win| win != window);
self.unmapped_windows.retain(|win| win != window);
if unmap {
self.unmapped_windows.push(window.clone());
}
self.z_index_stack.retain(|win| win != window);
for output in self.space.outputs() {
output.with_state_mut(|state| state.focus_stack.stack.retain(|win| win != window));
}
}
}

View file

@ -5,10 +5,12 @@ use std::sync::atomic::{AtomicU32, Ordering};
use smithay::{
desktop::{space::SpaceElement, WindowSurface},
reexports::wayland_protocols::xdg::shell::server::xdg_toplevel,
utils::{Logical, Point, Rectangle},
utils::{Logical, Point, Rectangle, Serial},
wayland::compositor::HookId,
};
use crate::{
layout::transaction::LayoutSnapshot,
state::{Pinnacle, WithState},
tag::Tag,
};
@ -54,6 +56,10 @@ pub struct WindowElementState {
pub fullscreen_or_maximized: FullscreenOrMaximized,
pub target_loc: Option<Point<i32, Logical>>,
pub minimized: bool,
/// The most recent serial that has been committed.
pub committed_serial: Option<Serial>,
pub snapshot: Option<LayoutSnapshot>,
pub snapshot_hook_id: Option<HookId>,
}
impl WindowElement {
@ -303,6 +309,9 @@ impl WindowElementState {
fullscreen_or_maximized: FullscreenOrMaximized::Neither,
target_loc: None,
minimized: false,
committed_serial: None,
snapshot: None,
snapshot_hook_id: None,
}
}
}