mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-26 21:58:10 +01:00
Merge pull request #240 from pinnacle-comp/layout_transactions
Add layout transactions
This commit is contained in:
commit
c597ff6cce
29 changed files with 2149 additions and 540 deletions
|
@ -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",
|
||||
|
|
|
@ -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
12
TODO.md
|
@ -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
|
||||
|
|
|
@ -13,7 +13,7 @@ require("pinnacle").setup(function(Pinnacle)
|
|||
---@type Modifier
|
||||
local mod_key = "ctrl"
|
||||
|
||||
local terminal = "alacritty"
|
||||
local terminal = "foot"
|
||||
|
||||
--------------------
|
||||
-- Mousebinds --
|
||||
|
|
62
src/api.rs
62
src/api.rs
|
@ -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);
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
12
src/focus.rs
12
src/focus.rs
|
@ -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;
|
||||
}
|
||||
|
|
313
src/handlers.rs
313
src/handlers.rs
|
@ -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
69
src/handlers/window.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
213
src/layout/transaction.rs
Normal 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(),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
pub mod api;
|
||||
pub mod backend;
|
||||
pub mod cli;
|
||||
|
|
|
@ -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 {
|
||||
|
|
301
src/render.rs
301
src/render.rs
|
@ -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>,
|
||||
}
|
||||
|
||||
impl<R> AsRenderElements<R> for WindowElement
|
||||
where
|
||||
R: Renderer + ImportAll + ImportMem,
|
||||
<R as Renderer>::TextureId: Texture + Clone + 'static,
|
||||
{
|
||||
type RenderElement = WaylandSurfaceRenderElement<R>;
|
||||
|
||||
fn render_elements<C: From<Self::RenderElement>>(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
location: Point<i32, Physical>,
|
||||
scale: Scale<f64>,
|
||||
alpha: f32,
|
||||
) -> Vec<C> {
|
||||
self.deref()
|
||||
.render_elements(renderer, location, scale, alpha)
|
||||
Snapshot = SnapshotRenderElement<R>,
|
||||
}
|
||||
}
|
||||
|
||||
struct LayerRenderElements<R: Renderer> {
|
||||
/// Trait to reduce bound specifications.
|
||||
pub trait PRenderer
|
||||
where
|
||||
Self: Renderer<TextureId = Self::PTextureId, Error = Self::PError> + ImportAll + ImportMem,
|
||||
<Self as Renderer>::TextureId: Texture + Clone + 'static,
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
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, Logical>,
|
||||
scale: Scale<f64>,
|
||||
alpha: f32,
|
||||
) -> 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, 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: 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) => {
|
||||
|
|
|
@ -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>>(
|
||||
|
|
229
src/render/render_elements.rs
Normal file
229
src/render/render_elements.rs
Normal 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
123
src/render/texture.rs
Normal 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
152
src/render/util.rs
Normal 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
220
src/render/util/snapshot.rs
Normal 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
114
src/render/util/surface.rs
Normal 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
|
||||
}
|
33
src/state.rs
33
src/state.rs
|
@ -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)
|
||||
|
|
48
src/tag.rs
48
src/tag.rs
|
@ -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.
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue