Merge branch 'Smithay:master' into master
This commit is contained in:
commit
2fb1f93160
|
@ -105,7 +105,7 @@ jobs:
|
|||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: update
|
||||
args: -p libdbus-sys -p calloop:0.6.1
|
||||
args: -p libdbus-sys
|
||||
- name: Check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
|
@ -131,6 +131,7 @@ jobs:
|
|||
- backend_session_logind
|
||||
- backend_session_libseat
|
||||
- backend_x11
|
||||
- desktop
|
||||
- renderer_gl
|
||||
- wayland_frontend
|
||||
- xwayland
|
||||
|
|
|
@ -4,6 +4,7 @@ on:
|
|||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
doc:
|
||||
|
@ -33,12 +34,13 @@ jobs:
|
|||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: doc
|
||||
args: --no-deps --features "test_all_features" -p smithay -p calloop:0.9.1 -p dbus -p drm -p gbm -p input -p nix:0.22.0 -p udev -p slog -p wayland-server -p wayland-commons:0.29.1 -p wayland-protocols:0.29.1 -p winit -p x11rb
|
||||
args: --no-deps --features "test_all_features" -p smithay -p calloop -p dbus -p drm -p gbm -p input -p nix -p udev -p slog -p wayland-server -p wayland-commons -p wayland-protocols -p winit -p x11rb
|
||||
|
||||
- name: Setup index
|
||||
run: cp ./doc_index.html ./target/doc/index.html
|
||||
|
||||
- name: Deploy
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
@ -31,6 +31,9 @@
|
|||
- `X11Surface::buffer` now additionally returns the age of the buffer
|
||||
- `X11Surface` now has an explicit `submit` function
|
||||
- `X11Surface` is now multi-window capable.
|
||||
- `Renderer::clear` now expects a second argument to optionally only clear parts of the buffer/surface
|
||||
- `Transform::transform_size` now takes a `Size` instead of two `u32`
|
||||
- `Gles2Renderer` now automatically flips the `render` result to account for OpenGLs coordinate system
|
||||
|
||||
### Additions
|
||||
|
||||
|
@ -45,6 +48,7 @@
|
|||
- Add support for the zxdg-foreign-v2 protocol.
|
||||
- Support for `xdg_wm_base` protocol version 3
|
||||
- Added the option to initialize the dmabuf global with a client filter
|
||||
- `wayland::output::Output` now has user data attached to it and more functions to query its properties
|
||||
|
||||
#### Backends
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ drm = { version = "0.5.0", optional = true }
|
|||
drm-ffi = { version = "0.2.0", optional = true }
|
||||
gbm = { version = "0.7.0", optional = true, default-features = false, features = ["drm-support"] }
|
||||
input = { version = "0.7", default-features = false, features=["libinput_1_14"], optional = true }
|
||||
indexmap = { version = "1.7", optional = true }
|
||||
lazy_static = "1"
|
||||
libc = "0.2.103"
|
||||
libseat= { version = "0.1.1", optional = true }
|
||||
|
@ -39,14 +40,14 @@ rand = "0.8.4"
|
|||
slog = "2"
|
||||
slog-stdlog = { version = "4", optional = true }
|
||||
tempfile = { version = "3.0", optional = true }
|
||||
thiserror = "1.0.7"
|
||||
thiserror = "1.0.25"
|
||||
udev = { version = "0.6", optional = true }
|
||||
wayland-commons = { version = "0.29.0", optional = true }
|
||||
wayland-egl = { version = "0.29.0", optional = true }
|
||||
wayland-protocols = { version = "0.29.0", features = ["unstable_protocols", "staging_protocols", "server"], optional = true }
|
||||
wayland-server = { version = "0.29.0", optional = true }
|
||||
wayland-sys = { version = "0.29.0", optional = true }
|
||||
winit = { version = "0.25.0", optional = true }
|
||||
winit = { version = "0.26", optional = true }
|
||||
x11rb = { version = "0.9.0", optional = true }
|
||||
xkbcommon = "0.4.0"
|
||||
scan_fmt = { version = "0.2.3", default-features = false }
|
||||
|
@ -59,7 +60,7 @@ gl_generator = { version = "0.14", optional = true }
|
|||
pkg-config = { version = "0.3.17", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["backend_drm", "backend_gbm", "backend_libinput", "backend_udev", "backend_session_logind", "backend_winit", "renderer_gl", "xwayland", "wayland_frontend", "slog-stdlog", "backend_x11"]
|
||||
default = ["backend_drm", "backend_gbm", "backend_libinput", "backend_udev", "backend_session_logind", "backend_winit", "desktop", "renderer_gl", "xwayland", "wayland_frontend", "slog-stdlog", "backend_x11"]
|
||||
backend_winit = ["winit", "wayland-server/dlopen", "backend_egl", "wayland-egl", "renderer_gl"]
|
||||
backend_x11 = ["x11rb", "x11rb/dri3", "x11rb/xfixes", "x11rb/present", "x11rb_event_source", "backend_gbm", "backend_drm", "backend_egl"]
|
||||
backend_drm = ["drm", "drm-ffi"]
|
||||
|
@ -71,6 +72,7 @@ backend_udev = ["udev", "input/udev"]
|
|||
backend_session_logind = ["dbus", "backend_session", "pkg-config"]
|
||||
backend_session_elogind = ["backend_session_logind"]
|
||||
backend_session_libseat = ["backend_session", "libseat"]
|
||||
desktop = ["indexmap", "wayland_frontend"]
|
||||
renderer_gl = ["gl_generator", "backend_egl"]
|
||||
use_system_lib = ["wayland_frontend", "wayland-sys", "wayland-server/use_system_lib"]
|
||||
wayland_frontend = ["wayland-server", "wayland-commons", "wayland-protocols", "tempfile"]
|
||||
|
|
|
@ -178,6 +178,7 @@ where
|
|||
buffer_scale,
|
||||
output_scale as f64,
|
||||
Transform::Normal, /* TODO */
|
||||
&[Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX))],
|
||||
1.0,
|
||||
) {
|
||||
result = Err(err.into());
|
||||
|
@ -355,6 +356,7 @@ where
|
|||
_ => unreachable!(),
|
||||
},
|
||||
Rectangle::from_loc_and_size((offset_x, 0.0), (22.0 * output_scale, 35.0 * output_scale)),
|
||||
&[Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX))],
|
||||
Transform::Normal,
|
||||
1.0,
|
||||
)
|
||||
|
|
|
@ -24,7 +24,10 @@ pub fn render_layers_and_windows(
|
|||
output_scale: f32,
|
||||
logger: &Logger,
|
||||
) -> Result<(), SwapBuffersError> {
|
||||
frame.clear([0.8, 0.8, 0.9, 1.0])?;
|
||||
frame.clear(
|
||||
[0.8, 0.8, 0.9, 1.0],
|
||||
&[Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX))],
|
||||
)?;
|
||||
|
||||
for layer in [Layer::Background, Layer::Bottom] {
|
||||
draw_layers(
|
||||
|
|
|
@ -50,7 +50,7 @@ use smithay::{
|
|||
},
|
||||
utils::{
|
||||
signaling::{Linkable, SignalToken, Signaler},
|
||||
Logical, Point,
|
||||
Logical, Point, Rectangle,
|
||||
},
|
||||
wayland::{
|
||||
output::{Mode, PhysicalProperties},
|
||||
|
@ -743,18 +743,8 @@ fn render_surface(
|
|||
|
||||
// and draw to our buffer
|
||||
match renderer
|
||||
.render(
|
||||
mode.size,
|
||||
Transform::Flipped180, // Scanout is rotated
|
||||
|renderer, frame| {
|
||||
render_layers_and_windows(
|
||||
renderer,
|
||||
frame,
|
||||
window_map,
|
||||
output_geometry,
|
||||
output_scale,
|
||||
logger,
|
||||
)?;
|
||||
.render(mode.size, Transform::Normal, |renderer, frame| {
|
||||
render_layers_and_windows(renderer, frame, window_map, output_geometry, output_scale, logger)?;
|
||||
|
||||
// set cursor
|
||||
if output_geometry.to_f64().contains(pointer_location) {
|
||||
|
@ -807,6 +797,7 @@ fn render_surface(
|
|||
1,
|
||||
output_scale as f64,
|
||||
Transform::Normal,
|
||||
&[Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX))],
|
||||
1.0,
|
||||
)?;
|
||||
}
|
||||
|
@ -827,8 +818,7 @@ fn render_surface(
|
|||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
})
|
||||
.map_err(Into::<SwapBuffersError>::into)
|
||||
.and_then(|x| x)
|
||||
.map_err(Into::<SwapBuffersError>::into)
|
||||
|
@ -873,7 +863,10 @@ fn initial_render(surface: &mut RenderSurface, renderer: &mut Gles2Renderer) ->
|
|||
renderer
|
||||
.render((1, 1).into(), Transform::Normal, |_, frame| {
|
||||
frame
|
||||
.clear([0.8, 0.8, 0.9, 1.0])
|
||||
.clear(
|
||||
[0.8, 0.8, 0.9, 1.0],
|
||||
&[Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX))],
|
||||
)
|
||||
.map_err(Into::<SwapBuffersError>::into)
|
||||
})
|
||||
.map_err(Into::<SwapBuffersError>::into)
|
||||
|
|
|
@ -9,6 +9,7 @@ use smithay::{
|
|||
};
|
||||
use smithay::{
|
||||
backend::{
|
||||
renderer::{Renderer, Transform},
|
||||
winit::{self, WinitEvent},
|
||||
SwapBuffersError,
|
||||
},
|
||||
|
@ -46,39 +47,39 @@ pub fn run_winit(log: Logger) {
|
|||
let mut event_loop = EventLoop::try_new().unwrap();
|
||||
let display = Rc::new(RefCell::new(Display::new()));
|
||||
|
||||
let (renderer, mut winit) = match winit::init(log.clone()) {
|
||||
let (backend, mut winit) = match winit::init(log.clone()) {
|
||||
Ok(ret) => ret,
|
||||
Err(err) => {
|
||||
slog::crit!(log, "Failed to initialize Winit backend: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let renderer = Rc::new(RefCell::new(renderer));
|
||||
let backend = Rc::new(RefCell::new(backend));
|
||||
|
||||
#[cfg(feature = "egl")]
|
||||
if renderer
|
||||
if backend
|
||||
.borrow_mut()
|
||||
.renderer()
|
||||
.bind_wl_display(&display.borrow())
|
||||
.is_ok()
|
||||
{
|
||||
info!(log, "EGL hardware-acceleration enabled");
|
||||
let dmabuf_formats = renderer
|
||||
let dmabuf_formats = backend
|
||||
.borrow_mut()
|
||||
.renderer()
|
||||
.dmabuf_formats()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let renderer = renderer.clone();
|
||||
let backend = backend.clone();
|
||||
init_dmabuf_global(
|
||||
&mut *display.borrow_mut(),
|
||||
dmabuf_formats,
|
||||
move |buffer, _| renderer.borrow_mut().renderer().import_dmabuf(buffer).is_ok(),
|
||||
move |buffer, _| backend.borrow_mut().renderer().import_dmabuf(buffer).is_ok(),
|
||||
log.clone(),
|
||||
);
|
||||
};
|
||||
|
||||
let size = renderer.borrow().window_size().physical_size;
|
||||
let size = backend.borrow().window_size().physical_size;
|
||||
|
||||
/*
|
||||
* Initialize the globals
|
||||
|
@ -87,7 +88,7 @@ pub fn run_winit(log: Logger) {
|
|||
let data = WinitData {
|
||||
#[cfg(feature = "debug")]
|
||||
fps_texture: import_bitmap(
|
||||
renderer.borrow_mut().renderer(),
|
||||
backend.borrow_mut().renderer(),
|
||||
&image::io::Reader::with_format(std::io::Cursor::new(FPS_NUMBERS_PNG), image::ImageFormat::Png)
|
||||
.decode()
|
||||
.unwrap()
|
||||
|
@ -153,7 +154,7 @@ pub fn run_winit(log: Logger) {
|
|||
|
||||
// drawing logic
|
||||
{
|
||||
let mut renderer = renderer.borrow_mut();
|
||||
let mut backend = backend.borrow_mut();
|
||||
// This is safe to do as with winit we are guaranteed to have exactly one output
|
||||
let (output_geometry, output_scale) = state
|
||||
.output_map
|
||||
|
@ -162,8 +163,19 @@ pub fn run_winit(log: Logger) {
|
|||
.map(|output| (output.geometry(), output.scale()))
|
||||
.unwrap();
|
||||
|
||||
let result = renderer
|
||||
.render(|renderer, frame| {
|
||||
let result = backend
|
||||
.bind()
|
||||
.and_then(|_| {
|
||||
backend
|
||||
.renderer()
|
||||
.render(
|
||||
output_geometry
|
||||
.size
|
||||
.to_f64()
|
||||
.to_physical(output_scale as f64)
|
||||
.to_i32_round(),
|
||||
Transform::Flipped180,
|
||||
|renderer, frame| {
|
||||
render_layers_and_windows(
|
||||
renderer,
|
||||
frame,
|
||||
|
@ -233,11 +245,14 @@ pub fn run_winit(log: Logger) {
|
|||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
},
|
||||
)
|
||||
.map_err(Into::<SwapBuffersError>::into)
|
||||
.and_then(|x| x);
|
||||
.and_then(|x| x)
|
||||
})
|
||||
.and_then(|_| backend.submit(None, 1.0));
|
||||
|
||||
renderer.window().set_cursor_visible(cursor_visible);
|
||||
backend.window().set_cursor_visible(cursor_visible);
|
||||
|
||||
if let Err(SwapBuffersError::ContextLost(err)) = result {
|
||||
error!(log, "Critical Rendering Error: {}", err);
|
||||
|
|
|
@ -239,12 +239,8 @@ pub fn run_x11(log: Logger) {
|
|||
|
||||
// drawing logic
|
||||
match renderer
|
||||
// X11 scanout for a Dmabuf is upside down
|
||||
// TODO: Address this issue in renderer.
|
||||
.render(
|
||||
backend_data.mode.size,
|
||||
Transform::Flipped180,
|
||||
|renderer, frame| {
|
||||
.render(backend_data.mode.size, Transform::Normal, |renderer, frame| {
|
||||
render_layers_and_windows(
|
||||
renderer,
|
||||
frame,
|
||||
|
@ -309,8 +305,7 @@ pub fn run_x11(log: Logger) {
|
|||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
})
|
||||
.map_err(Into::<SwapBuffersError>::into)
|
||||
.and_then(|x| x)
|
||||
.map_err(Into::<SwapBuffersError>::into)
|
||||
|
|
2
build.rs
2
build.rs
|
@ -35,6 +35,8 @@ fn gl_generate() {
|
|||
"EGL_KHR_image_base",
|
||||
"EGL_EXT_image_dma_buf_import",
|
||||
"EGL_EXT_image_dma_buf_import_modifiers",
|
||||
"EGL_EXT_buffer_age",
|
||||
"EGL_EXT_swap_buffers_with_damage",
|
||||
],
|
||||
)
|
||||
.write_bindings(gl_generator::GlobalGenerator, &mut file)
|
||||
|
|
|
@ -196,6 +196,15 @@ where
|
|||
|
||||
/// Remove all internally cached buffers to e.g. reset age values
|
||||
pub fn reset_buffers(&mut self) {
|
||||
self.slots = Default::default();
|
||||
for slot in &mut self.slots {
|
||||
if let Some(internal_slot) = Arc::get_mut(slot) {
|
||||
*internal_slot = InternalSlot {
|
||||
buffer: internal_slot.buffer.take(),
|
||||
..Default::default()
|
||||
};
|
||||
} else {
|
||||
*slot = Default::default();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -238,6 +238,11 @@ where
|
|||
flip.map_err(Error::DrmError)
|
||||
}
|
||||
|
||||
/// Reset the underlying buffers
|
||||
pub fn reset_buffers(&mut self) {
|
||||
self.swapchain.reset_buffers()
|
||||
}
|
||||
|
||||
/// Returns the underlying [`crtc`](drm::control::crtc) of this surface
|
||||
pub fn crtc(&self) -> crtc::Handle {
|
||||
self.drm.crtc()
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
//! EGL context related structs
|
||||
use std::collections::HashSet;
|
||||
use std::os::raw::c_int;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
os::raw::c_int,
|
||||
sync::{atomic::Ordering, Arc},
|
||||
};
|
||||
|
||||
use super::{ffi, wrap_egl_call, Error, MakeCurrentError};
|
||||
use crate::backend::allocator::Format as DrmFormat;
|
||||
use crate::backend::egl::display::{EGLDisplay, PixelFormat};
|
||||
use crate::backend::egl::EGLSurface;
|
||||
use crate::{
|
||||
backend::{
|
||||
allocator::Format as DrmFormat,
|
||||
egl::{
|
||||
display::{EGLDisplay, PixelFormat},
|
||||
EGLSurface,
|
||||
},
|
||||
},
|
||||
utils::user_data::UserDataMap,
|
||||
};
|
||||
|
||||
use slog::{info, o, trace};
|
||||
|
||||
|
@ -17,6 +26,7 @@ pub struct EGLContext {
|
|||
pub(crate) display: EGLDisplay,
|
||||
config_id: ffi::egl::types::EGLConfig,
|
||||
pixel_format: Option<PixelFormat>,
|
||||
user_data: Arc<UserDataMap>,
|
||||
}
|
||||
// EGLContexts can be moved between threads safely
|
||||
unsafe impl Send for EGLContext {}
|
||||
|
@ -162,6 +172,11 @@ impl EGLContext {
|
|||
display: display.clone(),
|
||||
config_id,
|
||||
pixel_format,
|
||||
user_data: if let Some(shared) = shared {
|
||||
shared.user_data.clone()
|
||||
} else {
|
||||
Arc::new(UserDataMap::default())
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -241,6 +256,21 @@ impl EGLContext {
|
|||
pub fn dmabuf_texture_formats(&self) -> &HashSet<DrmFormat> {
|
||||
&self.display.dmabuf_import_formats
|
||||
}
|
||||
|
||||
/// Retrieve user_data associated with this context
|
||||
///
|
||||
/// *Note:* UserData is shared between shared context, if constructed with
|
||||
/// [`new_shared`](EGLContext::new_shared) or [`new_shared_with_config`](EGLContext::new_shared_with_config).
|
||||
pub fn user_data(&self) -> &UserDataMap {
|
||||
&*self.user_data
|
||||
}
|
||||
|
||||
/// Get a raw handle to the underlying context.
|
||||
///
|
||||
/// The pointer will become invalid, when this struct is destroyed.
|
||||
pub fn get_context_handle(&self) -> ffi::egl::types::EGLContext {
|
||||
self.context
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EGLContext {
|
||||
|
|
|
@ -173,7 +173,9 @@ impl EGLDevice {
|
|||
}
|
||||
|
||||
/// Returns the pointer to the raw [`EGLDevice`].
|
||||
pub fn inner(&self) -> *const c_void {
|
||||
///
|
||||
/// The pointer will become invalid, when this struct is destroyed.
|
||||
pub fn get_device_handle(&self) -> *const c_void {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ extern "system" fn egl_debug_log(
|
|||
}
|
||||
egl::DEBUG_MSG_WARN_KHR => slog::warn!(logger, "[EGL] {}: {}", command_utf8, message_utf8),
|
||||
egl::DEBUG_MSG_INFO_KHR => slog::info!(logger, "[EGL] {}: {}", command_utf8, message_utf8),
|
||||
_ => slog::debug!(logger, "[EGL] {}: {}", command_utf8, message_utf8),
|
||||
_ => {}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//! Type safe native types for safe context/surface creation
|
||||
|
||||
use super::{display::EGLDisplayHandle, ffi, wrap_egl_call, EGLDevice, SwapBuffersError};
|
||||
use crate::utils::{Physical, Rectangle};
|
||||
#[cfg(feature = "backend_winit")]
|
||||
use std::os::raw::c_int;
|
||||
use std::os::raw::c_void;
|
||||
|
@ -246,9 +247,19 @@ pub unsafe trait EGLNativeSurface: Send + Sync {
|
|||
&self,
|
||||
display: &Arc<EGLDisplayHandle>,
|
||||
surface: ffi::egl::types::EGLSurface,
|
||||
damage: Option<&mut [Rectangle<i32, Physical>]>,
|
||||
) -> Result<(), SwapBuffersError> {
|
||||
wrap_egl_call(|| unsafe {
|
||||
if let Some(damage) = damage {
|
||||
ffi::egl::SwapBuffersWithDamageEXT(
|
||||
***display,
|
||||
surface as *const _,
|
||||
damage.as_mut_ptr() as *mut _,
|
||||
damage.len() as i32,
|
||||
);
|
||||
} else {
|
||||
ffi::egl::SwapBuffers(***display, surface as *const _);
|
||||
}
|
||||
})
|
||||
.map_err(SwapBuffersError::EGLSwapBuffers)
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ use crate::backend::egl::{
|
|||
native::EGLNativeSurface,
|
||||
EGLError, SwapBuffersError,
|
||||
};
|
||||
use crate::utils::{Physical, Rectangle};
|
||||
|
||||
use slog::{debug, o};
|
||||
|
||||
|
@ -79,12 +80,30 @@ impl EGLSurface {
|
|||
})
|
||||
}
|
||||
|
||||
/// Returns the buffer age of the underlying back buffer
|
||||
pub fn buffer_age(&self) -> i32 {
|
||||
let surface = self.surface.load(Ordering::SeqCst);
|
||||
let mut age = 0;
|
||||
unsafe {
|
||||
ffi::egl::QuerySurface(
|
||||
**self.display,
|
||||
surface as *const _,
|
||||
ffi::egl::BUFFER_AGE_EXT as i32,
|
||||
&mut age as *mut _,
|
||||
);
|
||||
}
|
||||
age
|
||||
}
|
||||
|
||||
/// Swaps buffers at the end of a frame.
|
||||
pub fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> {
|
||||
pub fn swap_buffers(
|
||||
&self,
|
||||
damage: Option<&mut [Rectangle<i32, Physical>]>,
|
||||
) -> ::std::result::Result<(), SwapBuffersError> {
|
||||
let surface = self.surface.load(Ordering::SeqCst);
|
||||
|
||||
let result = if !surface.is_null() {
|
||||
self.native.swap_buffers(&self.display, surface)
|
||||
self.native.swap_buffers(&self.display, surface, damage)
|
||||
} else {
|
||||
Err(SwapBuffersError::EGLSwapBuffers(EGLError::BadSurface))
|
||||
};
|
||||
|
@ -153,6 +172,16 @@ impl EGLSurface {
|
|||
pub fn resize(&self, width: i32, height: i32, dx: i32, dy: i32) -> bool {
|
||||
self.native.resize(width, height, dx, dy)
|
||||
}
|
||||
|
||||
/// Get a raw handle to the underlying surface
|
||||
///
|
||||
/// *Note*: The surface might get dynamically recreated during swap-buffers
|
||||
/// causing the pointer to become invalid.
|
||||
///
|
||||
/// The pointer will become invalid, when this struct is destroyed.
|
||||
pub fn get_surface_handle(&self) -> ffi::egl::types::EGLSurface {
|
||||
self.surface.load(Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EGLSurface {
|
||||
|
|
|
@ -5,10 +5,7 @@ use std::ffi::CStr;
|
|||
use std::fmt;
|
||||
use std::ptr;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
mpsc::{channel, Receiver, Sender},
|
||||
};
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::{collections::HashSet, os::raw::c_char};
|
||||
|
||||
use cgmath::{prelude::*, Matrix3, Vector2, Vector3};
|
||||
|
@ -44,23 +41,27 @@ pub mod ffi {
|
|||
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
|
||||
}
|
||||
|
||||
// This static is used to assign every created Renderer a unique ID (until is overflows...).
|
||||
//
|
||||
// This id is used to differenciate between user_data of different renderers, because one
|
||||
// cannot assume, that resources between two renderers are (and even can be) shared.
|
||||
static RENDERER_COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Gles2Program {
|
||||
struct Gles2TexProgram {
|
||||
program: ffi::types::GLuint,
|
||||
uniform_tex: ffi::types::GLint,
|
||||
uniform_matrix: ffi::types::GLint,
|
||||
uniform_invert_y: ffi::types::GLint,
|
||||
uniform_alpha: ffi::types::GLint,
|
||||
attrib_vert: ffi::types::GLint,
|
||||
attrib_position: ffi::types::GLint,
|
||||
attrib_tex_coords: ffi::types::GLint,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Gles2SolidProgram {
|
||||
program: ffi::types::GLuint,
|
||||
uniform_matrix: ffi::types::GLint,
|
||||
uniform_color: ffi::types::GLint,
|
||||
attrib_vert: ffi::types::GLint,
|
||||
attrib_position: ffi::types::GLint,
|
||||
}
|
||||
|
||||
/// A handle to a GLES2 texture
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Gles2Texture(Rc<Gles2TextureInternal>);
|
||||
|
@ -158,45 +159,27 @@ struct Gles2Buffer {
|
|||
_dmabuf: Dmabuf,
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
struct BufferEntry {
|
||||
id: u32,
|
||||
buffer: wl_buffer::WlBuffer,
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
impl std::hash::Hash for BufferEntry {
|
||||
fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
|
||||
self.id.hash(hasher);
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
impl PartialEq for BufferEntry {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.buffer == other.buffer
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
impl Eq for BufferEntry {}
|
||||
|
||||
/// A renderer utilizing OpenGL ES 2
|
||||
pub struct Gles2Renderer {
|
||||
id: usize,
|
||||
buffers: Vec<WeakGles2Buffer>,
|
||||
target_buffer: Option<Gles2Buffer>,
|
||||
target_surface: Option<Rc<EGLSurface>>,
|
||||
extensions: Vec<String>,
|
||||
programs: [Gles2Program; shaders::FRAGMENT_COUNT],
|
||||
tex_programs: [Gles2TexProgram; shaders::FRAGMENT_COUNT],
|
||||
solid_program: Gles2SolidProgram,
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
dmabuf_cache: std::collections::HashMap<WeakDmabuf, Gles2Texture>,
|
||||
egl: EGLContext,
|
||||
#[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
|
||||
egl_reader: Option<EGLBufferReader>,
|
||||
vbos: [ffi::types::GLuint; 2],
|
||||
gl: ffi::Gles2,
|
||||
destruction_callback: Receiver<CleanupResource>,
|
||||
// This field is only accessed if the image or wayland_frontend features are active
|
||||
#[allow(dead_code)]
|
||||
destruction_callback_sender: Sender<CleanupResource>,
|
||||
min_filter: TextureFilter,
|
||||
max_filter: TextureFilter,
|
||||
logger_ptr: Option<*mut ::slog::Logger>,
|
||||
logger: ::slog::Logger,
|
||||
_not_send: *mut (),
|
||||
|
@ -205,15 +188,25 @@ pub struct Gles2Renderer {
|
|||
/// Handle to the currently rendered frame during [`Gles2Renderer::render`](Renderer::render)
|
||||
pub struct Gles2Frame {
|
||||
current_projection: Matrix3<f32>,
|
||||
transform: Transform,
|
||||
gl: ffi::Gles2,
|
||||
programs: [Gles2Program; shaders::FRAGMENT_COUNT],
|
||||
tex_programs: [Gles2TexProgram; shaders::FRAGMENT_COUNT],
|
||||
solid_program: Gles2SolidProgram,
|
||||
vbos: [ffi::types::GLuint; 2],
|
||||
size: Size<i32, Physical>,
|
||||
min_filter: TextureFilter,
|
||||
max_filter: TextureFilter,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Gles2Frame {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Gles2Frame")
|
||||
.field("current_projection", &self.current_projection)
|
||||
.field("programs", &self.programs)
|
||||
.field("tex_programs", &self.tex_programs)
|
||||
.field("solid_program", &self.solid_program)
|
||||
.field("size", &self.size)
|
||||
.field("min_filter", &self.min_filter)
|
||||
.field("max_filter", &self.max_filter)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
@ -221,14 +214,16 @@ impl fmt::Debug for Gles2Frame {
|
|||
impl fmt::Debug for Gles2Renderer {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Gles2Renderer")
|
||||
.field("id", &self.id)
|
||||
.field("buffers", &self.buffers)
|
||||
.field("target_buffer", &self.target_buffer)
|
||||
.field("target_surface", &self.target_surface)
|
||||
.field("extensions", &self.extensions)
|
||||
.field("programs", &self.programs)
|
||||
.field("tex_programs", &self.tex_programs)
|
||||
.field("solid_program", &self.solid_program)
|
||||
// ffi::Gles2 does not implement Debug
|
||||
.field("egl", &self.egl)
|
||||
.field("min_filter", &self.min_filter)
|
||||
.field("max_filter", &self.max_filter)
|
||||
.field("logger", &self.logger)
|
||||
.finish()
|
||||
}
|
||||
|
@ -382,9 +377,10 @@ unsafe fn link_program(
|
|||
Ok(program)
|
||||
}
|
||||
|
||||
unsafe fn texture_program(gl: &ffi::Gles2, frag: &'static str) -> Result<Gles2Program, Gles2Error> {
|
||||
unsafe fn texture_program(gl: &ffi::Gles2, frag: &'static str) -> Result<Gles2TexProgram, Gles2Error> {
|
||||
let program = link_program(gl, shaders::VERTEX_SHADER, frag)?;
|
||||
|
||||
let vert = CStr::from_bytes_with_nul(b"vert\0").expect("NULL terminated");
|
||||
let position = CStr::from_bytes_with_nul(b"position\0").expect("NULL terminated");
|
||||
let tex_coords = CStr::from_bytes_with_nul(b"tex_coords\0").expect("NULL terminated");
|
||||
let tex = CStr::from_bytes_with_nul(b"tex\0").expect("NULL terminated");
|
||||
|
@ -392,17 +388,35 @@ unsafe fn texture_program(gl: &ffi::Gles2, frag: &'static str) -> Result<Gles2Pr
|
|||
let invert_y = CStr::from_bytes_with_nul(b"invert_y\0").expect("NULL terminated");
|
||||
let alpha = CStr::from_bytes_with_nul(b"alpha\0").expect("NULL terminated");
|
||||
|
||||
Ok(Gles2Program {
|
||||
Ok(Gles2TexProgram {
|
||||
program,
|
||||
uniform_tex: gl.GetUniformLocation(program, tex.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_matrix: gl.GetUniformLocation(program, matrix.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_invert_y: gl.GetUniformLocation(program, invert_y.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_alpha: gl.GetUniformLocation(program, alpha.as_ptr() as *const ffi::types::GLchar),
|
||||
attrib_vert: gl.GetAttribLocation(program, vert.as_ptr() as *const ffi::types::GLchar),
|
||||
attrib_position: gl.GetAttribLocation(program, position.as_ptr() as *const ffi::types::GLchar),
|
||||
attrib_tex_coords: gl.GetAttribLocation(program, tex_coords.as_ptr() as *const ffi::types::GLchar),
|
||||
})
|
||||
}
|
||||
|
||||
unsafe fn solid_program(gl: &ffi::Gles2) -> Result<Gles2SolidProgram, Gles2Error> {
|
||||
let program = link_program(gl, shaders::VERTEX_SHADER_SOLID, shaders::FRAGMENT_SHADER_SOLID)?;
|
||||
|
||||
let matrix = CStr::from_bytes_with_nul(b"matrix\0").expect("NULL terminated");
|
||||
let color = CStr::from_bytes_with_nul(b"color\0").expect("NULL terminated");
|
||||
let vert = CStr::from_bytes_with_nul(b"vert\0").expect("NULL terminated");
|
||||
let position = CStr::from_bytes_with_nul(b"position\0").expect("NULL terminated");
|
||||
|
||||
Ok(Gles2SolidProgram {
|
||||
program,
|
||||
uniform_matrix: gl.GetUniformLocation(program, matrix.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_color: gl.GetUniformLocation(program, color.as_ptr() as *const ffi::types::GLchar),
|
||||
attrib_vert: gl.GetAttribLocation(program, vert.as_ptr() as *const ffi::types::GLchar),
|
||||
attrib_position: gl.GetAttribLocation(program, position.as_ptr() as *const ffi::types::GLchar),
|
||||
})
|
||||
}
|
||||
|
||||
impl Gles2Renderer {
|
||||
/// Creates a new OpenGL ES 2 renderer from a given [`EGLContext`](crate::backend::egl::EGLBuffer).
|
||||
///
|
||||
|
@ -473,6 +487,16 @@ impl Gles2Renderer {
|
|||
if gl_version < version::GLES_3_0 && !exts.iter().any(|ext| ext == "GL_EXT_unpack_subimage") {
|
||||
return Err(Gles2Error::GLExtensionNotSupported(&["GL_EXT_unpack_subimage"]));
|
||||
}
|
||||
// required for instanced damage rendering
|
||||
if gl_version < version::GLES_3_0
|
||||
&& !(exts.iter().any(|ext| ext == "GL_EXT_instanced_arrays")
|
||||
&& exts.iter().any(|ext| ext == "GL_EXT_draw_instanced"))
|
||||
{
|
||||
return Err(Gles2Error::GLExtensionNotSupported(&[
|
||||
"GL_EXT_instanced_arrays",
|
||||
"GL_EXT_draw_instanced",
|
||||
]));
|
||||
}
|
||||
|
||||
let logger = if exts.iter().any(|ext| ext == "GL_KHR_debug") {
|
||||
let logger = Box::into_raw(Box::new(log.clone()));
|
||||
|
@ -487,21 +511,33 @@ impl Gles2Renderer {
|
|||
(gl, exts, logger)
|
||||
};
|
||||
|
||||
let programs = [
|
||||
let tex_programs = [
|
||||
texture_program(&gl, shaders::FRAGMENT_SHADER_ABGR)?,
|
||||
texture_program(&gl, shaders::FRAGMENT_SHADER_XBGR)?,
|
||||
texture_program(&gl, shaders::FRAGMENT_SHADER_EXTERNAL)?,
|
||||
];
|
||||
let solid_program = solid_program(&gl)?;
|
||||
|
||||
let mut vbos = [0; 2];
|
||||
gl.GenBuffers(2, vbos.as_mut_ptr());
|
||||
gl.BindBuffer(ffi::ARRAY_BUFFER, vbos[0]);
|
||||
gl.BufferData(
|
||||
ffi::ARRAY_BUFFER,
|
||||
(std::mem::size_of::<ffi::types::GLfloat>() * VERTS.len()) as isize,
|
||||
VERTS.as_ptr() as *const _,
|
||||
ffi::STATIC_DRAW,
|
||||
);
|
||||
gl.BindBuffer(ffi::ARRAY_BUFFER, 0);
|
||||
|
||||
let (tx, rx) = channel();
|
||||
let mut renderer = Gles2Renderer {
|
||||
id: RENDERER_COUNTER.fetch_add(1, Ordering::SeqCst),
|
||||
let renderer = Gles2Renderer {
|
||||
gl,
|
||||
egl: context,
|
||||
#[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
|
||||
egl_reader: None,
|
||||
extensions: exts,
|
||||
programs,
|
||||
tex_programs,
|
||||
solid_program,
|
||||
target_buffer: None,
|
||||
target_surface: None,
|
||||
buffers: Vec::new(),
|
||||
|
@ -509,12 +545,13 @@ impl Gles2Renderer {
|
|||
dmabuf_cache: std::collections::HashMap::new(),
|
||||
destruction_callback: rx,
|
||||
destruction_callback_sender: tx,
|
||||
vbos,
|
||||
min_filter: TextureFilter::Nearest,
|
||||
max_filter: TextureFilter::Linear,
|
||||
logger_ptr,
|
||||
logger: log,
|
||||
_not_send: std::ptr::null_mut(),
|
||||
};
|
||||
renderer.downscale_filter(TextureFilter::Nearest)?;
|
||||
renderer.upscale_filter(TextureFilter::Linear)?;
|
||||
renderer.egl.unbind()?;
|
||||
Ok(renderer)
|
||||
}
|
||||
|
@ -607,7 +644,6 @@ impl ImportShm for Gles2Renderer {
|
|||
|
||||
unsafe {
|
||||
self.gl.BindTexture(ffi::TEXTURE_2D, texture.0.texture);
|
||||
|
||||
self.gl
|
||||
.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::CLAMP_TO_EDGE as i32);
|
||||
self.gl
|
||||
|
@ -966,9 +1002,11 @@ impl Drop for Gles2Renderer {
|
|||
unsafe {
|
||||
if self.egl.make_current().is_ok() {
|
||||
self.gl.BindFramebuffer(ffi::FRAMEBUFFER, 0);
|
||||
for program in &self.programs {
|
||||
for program in &self.tex_programs {
|
||||
self.gl.DeleteProgram(program.program);
|
||||
}
|
||||
self.gl.DeleteProgram(self.solid_program.program);
|
||||
self.gl.DeleteBuffers(2, self.vbos.as_ptr());
|
||||
|
||||
if self.extensions.iter().any(|ext| ext == "GL_KHR_debug") {
|
||||
self.gl.Disable(ffi::DEBUG_OUTPUT);
|
||||
|
@ -987,12 +1025,25 @@ impl Drop for Gles2Renderer {
|
|||
}
|
||||
|
||||
impl Gles2Renderer {
|
||||
/// Get access to the underlying [`EGLContext`].
|
||||
///
|
||||
/// *Note*: Modifying the context state, might result in rendering issues.
|
||||
/// The context state is considerd an implementation detail
|
||||
/// and no guarantee is made about what can or cannot be changed.
|
||||
/// To make sure a certain modification does not interfere with
|
||||
/// the renderer's behaviour, check the source.
|
||||
pub fn egl_context(&self) -> &EGLContext {
|
||||
&self.egl
|
||||
}
|
||||
|
||||
/// Run custom code in the GL context owned by this renderer.
|
||||
///
|
||||
/// *Note*: Any changes to the GL state should be restored at the end of this function.
|
||||
/// Otherwise this can lead to rendering errors while using functions of this renderer.
|
||||
/// Relying on any state set by the renderer may break on any smithay update as the
|
||||
/// details about how this renderer works are considered an implementation detail.
|
||||
/// The OpenGL state of the renderer is considered an implementation detail
|
||||
/// and no guarantee is made about what can or cannot be changed,
|
||||
/// as such you should reset everything you change back to its previous value
|
||||
/// or check the source code of the version of Smithay you are using to ensure
|
||||
/// your changes don't interfere with the renderer's behavior.
|
||||
/// Doing otherwise can lead to rendering errors while using other functions of this renderer.
|
||||
pub fn with_context<F, R>(&mut self, func: F) -> Result<R, Gles2Error>
|
||||
where
|
||||
F: FnOnce(&mut Self, &ffi::Gles2) -> R,
|
||||
|
@ -1009,31 +1060,11 @@ impl Renderer for Gles2Renderer {
|
|||
type Frame = Gles2Frame;
|
||||
|
||||
fn downscale_filter(&mut self, filter: TextureFilter) -> Result<(), Self::Error> {
|
||||
self.make_current()?;
|
||||
unsafe {
|
||||
self.gl.TexParameteri(
|
||||
ffi::TEXTURE_2D,
|
||||
ffi::TEXTURE_MIN_FILTER,
|
||||
match filter {
|
||||
TextureFilter::Nearest => ffi::NEAREST as i32,
|
||||
TextureFilter::Linear => ffi::LINEAR as i32,
|
||||
},
|
||||
);
|
||||
}
|
||||
self.min_filter = filter;
|
||||
Ok(())
|
||||
}
|
||||
fn upscale_filter(&mut self, filter: TextureFilter) -> Result<(), Self::Error> {
|
||||
self.make_current()?;
|
||||
unsafe {
|
||||
self.gl.TexParameteri(
|
||||
ffi::TEXTURE_2D,
|
||||
ffi::TEXTURE_MAG_FILTER,
|
||||
match filter {
|
||||
TextureFilter::Nearest => ffi::NEAREST as i32,
|
||||
TextureFilter::Linear => ffi::LINEAR as i32,
|
||||
},
|
||||
);
|
||||
}
|
||||
self.max_filter = filter;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1077,11 +1108,20 @@ impl Renderer for Gles2Renderer {
|
|||
renderer[2][0] = -(1.0f32.copysign(renderer[0][0] + renderer[1][0]));
|
||||
renderer[2][1] = -(1.0f32.copysign(renderer[0][1] + renderer[1][1]));
|
||||
|
||||
// We account for OpenGLs coordinate system here
|
||||
let flip180 = Matrix3::new(1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0);
|
||||
|
||||
let mut frame = Gles2Frame {
|
||||
gl: self.gl.clone(),
|
||||
programs: self.programs.clone(),
|
||||
tex_programs: self.tex_programs.clone(),
|
||||
solid_program: self.solid_program.clone(),
|
||||
// output transformation passed in by the user
|
||||
current_projection: transform.matrix() * renderer,
|
||||
current_projection: flip180 * transform.matrix() * renderer,
|
||||
transform,
|
||||
vbos: self.vbos,
|
||||
size,
|
||||
min_filter: self.min_filter,
|
||||
max_filter: self.max_filter,
|
||||
};
|
||||
|
||||
let result = rendering(self, &mut frame);
|
||||
|
@ -1121,10 +1161,85 @@ impl Frame for Gles2Frame {
|
|||
type Error = Gles2Error;
|
||||
type TextureId = Gles2Texture;
|
||||
|
||||
fn clear(&mut self, color: [f32; 4]) -> Result<(), Self::Error> {
|
||||
fn clear(&mut self, color: [f32; 4], at: &[Rectangle<i32, Physical>]) -> Result<(), Self::Error> {
|
||||
if at.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut mat = Matrix3::<f32>::identity();
|
||||
mat = mat * Matrix3::from_translation(Vector2::new(0.0, 0.0));
|
||||
mat = mat * Matrix3::from_nonuniform_scale(self.size.w as f32, self.size.h as f32);
|
||||
mat = self.current_projection * mat;
|
||||
|
||||
let damage = at
|
||||
.iter()
|
||||
.map(|rect| {
|
||||
[
|
||||
rect.loc.x as f32 / self.size.w as f32,
|
||||
rect.loc.y as f32 / self.size.h as f32,
|
||||
rect.size.w as f32 / self.size.w as f32,
|
||||
rect.size.h as f32 / self.size.h as f32,
|
||||
]
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<ffi::types::GLfloat>>();
|
||||
|
||||
unsafe {
|
||||
self.gl.ClearColor(color[0], color[1], color[2], color[3]);
|
||||
self.gl.Clear(ffi::COLOR_BUFFER_BIT);
|
||||
self.gl.UseProgram(self.solid_program.program);
|
||||
self.gl.Uniform4f(
|
||||
self.solid_program.uniform_color,
|
||||
color[0],
|
||||
color[1],
|
||||
color[2],
|
||||
color[3],
|
||||
);
|
||||
self.gl
|
||||
.UniformMatrix3fv(self.solid_program.uniform_matrix, 1, ffi::FALSE, mat.as_ptr());
|
||||
|
||||
self.gl
|
||||
.EnableVertexAttribArray(self.solid_program.attrib_vert as u32);
|
||||
self.gl.BindBuffer(ffi::ARRAY_BUFFER, self.vbos[0]);
|
||||
self.gl.VertexAttribPointer(
|
||||
self.solid_program.attrib_vert as u32,
|
||||
2,
|
||||
ffi::FLOAT,
|
||||
ffi::FALSE,
|
||||
0,
|
||||
std::ptr::null(),
|
||||
);
|
||||
|
||||
self.gl
|
||||
.EnableVertexAttribArray(self.solid_program.attrib_position as u32);
|
||||
self.gl.BindBuffer(ffi::ARRAY_BUFFER, self.vbos[1]);
|
||||
self.gl.BufferData(
|
||||
ffi::ARRAY_BUFFER,
|
||||
(std::mem::size_of::<ffi::types::GLfloat>() * damage.len()) as isize,
|
||||
damage.as_ptr() as *const _,
|
||||
ffi::STREAM_DRAW,
|
||||
);
|
||||
|
||||
self.gl.VertexAttribPointer(
|
||||
self.solid_program.attrib_position as u32,
|
||||
4,
|
||||
ffi::FLOAT,
|
||||
ffi::FALSE,
|
||||
0,
|
||||
std::ptr::null(),
|
||||
);
|
||||
self.gl
|
||||
.VertexAttribDivisor(self.solid_program.attrib_vert as u32, 0);
|
||||
|
||||
self.gl
|
||||
.VertexAttribDivisor(self.solid_program.attrib_position as u32, 1);
|
||||
|
||||
self.gl
|
||||
.DrawArraysInstanced(ffi::TRIANGLE_STRIP, 0, 4, at.len() as i32);
|
||||
|
||||
self.gl.BindBuffer(ffi::ARRAY_BUFFER, 0);
|
||||
self.gl
|
||||
.DisableVertexAttribArray(self.solid_program.attrib_vert as u32);
|
||||
self.gl
|
||||
.DisableVertexAttribArray(self.solid_program.attrib_position as u32);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -1135,6 +1250,7 @@ impl Frame for Gles2Frame {
|
|||
texture: &Self::TextureId,
|
||||
src: Rectangle<i32, Buffer>,
|
||||
dest: Rectangle<f64, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
transform: Transform,
|
||||
alpha: f32,
|
||||
) -> Result<(), Self::Error> {
|
||||
|
@ -1158,7 +1274,7 @@ impl Frame for Gles2Frame {
|
|||
let texture_mat = Matrix3::from_nonuniform_scale(tex_size.w as f32, tex_size.h as f32)
|
||||
.invert()
|
||||
.unwrap();
|
||||
let verts = [
|
||||
let tex_verts = [
|
||||
(texture_mat * Vector3::new((src.loc.x + src.size.w) as f32, src.loc.y as f32, 0.0)).truncate(), // top-right
|
||||
(texture_mat * Vector3::new(src.loc.x as f32, src.loc.y as f32, 0.0)).truncate(), // top-left
|
||||
(texture_mat
|
||||
|
@ -1170,7 +1286,32 @@ impl Frame for Gles2Frame {
|
|||
.truncate(), // bottom-right
|
||||
(texture_mat * Vector3::new(src.loc.x as f32, (src.loc.y + src.size.h) as f32, 0.0)).truncate(), // bottom-left
|
||||
];
|
||||
self.render_texture(texture, mat, verts, alpha)
|
||||
|
||||
let damage = damage
|
||||
.iter()
|
||||
.map(|rect| {
|
||||
let rect = rect.to_f64();
|
||||
|
||||
let rect_constrained_loc = rect
|
||||
.loc
|
||||
.constrain(Rectangle::from_extemities((0f64, 0f64), dest.size.to_point()));
|
||||
let rect_clamped_size = rect.size.clamp((0f64, 0f64), dest.size);
|
||||
|
||||
[
|
||||
(rect_constrained_loc.x / dest.size.w) as f32,
|
||||
(rect_constrained_loc.y / dest.size.h) as f32,
|
||||
(rect_clamped_size.w / dest.size.w) as f32,
|
||||
(rect_clamped_size.h / dest.size.h) as f32,
|
||||
]
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
self.render_texture(texture, mat, Some(&damage), tex_verts, alpha)
|
||||
}
|
||||
|
||||
fn transformation(&self) -> Transform {
|
||||
self.transform
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1181,6 +1322,7 @@ impl Gles2Frame {
|
|||
&mut self,
|
||||
tex: &Gles2Texture,
|
||||
mut matrix: Matrix3<f32>,
|
||||
instances: Option<&[ffi::types::GLfloat]>,
|
||||
tex_coords: [Vector2<f32>; 4],
|
||||
alpha: f32,
|
||||
) -> Result<(), Gles2Error> {
|
||||
|
@ -1197,35 +1339,43 @@ impl Gles2Frame {
|
|||
unsafe {
|
||||
self.gl.ActiveTexture(ffi::TEXTURE0);
|
||||
self.gl.BindTexture(target, tex.0.texture);
|
||||
self.gl
|
||||
.TexParameteri(target, ffi::TEXTURE_MIN_FILTER, ffi::LINEAR as i32);
|
||||
self.gl.UseProgram(self.programs[tex.0.texture_kind].program);
|
||||
self.gl.TexParameteri(
|
||||
ffi::TEXTURE_2D,
|
||||
ffi::TEXTURE_MIN_FILTER,
|
||||
match self.min_filter {
|
||||
TextureFilter::Nearest => ffi::NEAREST as i32,
|
||||
TextureFilter::Linear => ffi::LINEAR as i32,
|
||||
},
|
||||
);
|
||||
self.gl.TexParameteri(
|
||||
ffi::TEXTURE_2D,
|
||||
ffi::TEXTURE_MAG_FILTER,
|
||||
match self.max_filter {
|
||||
TextureFilter::Nearest => ffi::NEAREST as i32,
|
||||
TextureFilter::Linear => ffi::LINEAR as i32,
|
||||
},
|
||||
);
|
||||
self.gl.UseProgram(self.tex_programs[tex.0.texture_kind].program);
|
||||
|
||||
self.gl
|
||||
.Uniform1i(self.programs[tex.0.texture_kind].uniform_tex, 0);
|
||||
.Uniform1i(self.tex_programs[tex.0.texture_kind].uniform_tex, 0);
|
||||
self.gl.UniformMatrix3fv(
|
||||
self.programs[tex.0.texture_kind].uniform_matrix,
|
||||
self.tex_programs[tex.0.texture_kind].uniform_matrix,
|
||||
1,
|
||||
ffi::FALSE,
|
||||
matrix.as_ptr(),
|
||||
);
|
||||
self.gl.Uniform1i(
|
||||
self.programs[tex.0.texture_kind].uniform_invert_y,
|
||||
self.tex_programs[tex.0.texture_kind].uniform_invert_y,
|
||||
if tex.0.y_inverted { 1 } else { 0 },
|
||||
);
|
||||
self.gl
|
||||
.Uniform1f(self.programs[tex.0.texture_kind].uniform_alpha, alpha);
|
||||
.Uniform1f(self.tex_programs[tex.0.texture_kind].uniform_alpha, alpha);
|
||||
|
||||
self.gl
|
||||
.EnableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_tex_coords as u32);
|
||||
self.gl.VertexAttribPointer(
|
||||
self.programs[tex.0.texture_kind].attrib_position as u32,
|
||||
2,
|
||||
ffi::FLOAT,
|
||||
ffi::FALSE,
|
||||
0,
|
||||
VERTS.as_ptr() as *const _,
|
||||
);
|
||||
self.gl.VertexAttribPointer(
|
||||
self.programs[tex.0.texture_kind].attrib_tex_coords as u32,
|
||||
self.tex_programs[tex.0.texture_kind].attrib_tex_coords as u32,
|
||||
2,
|
||||
ffi::FLOAT,
|
||||
ffi::FALSE,
|
||||
|
@ -1234,20 +1384,62 @@ impl Gles2Frame {
|
|||
);
|
||||
|
||||
self.gl
|
||||
.EnableVertexAttribArray(self.programs[tex.0.texture_kind].attrib_position as u32);
|
||||
self.gl
|
||||
.EnableVertexAttribArray(self.programs[tex.0.texture_kind].attrib_tex_coords as u32);
|
||||
.EnableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_vert as u32);
|
||||
self.gl.BindBuffer(ffi::ARRAY_BUFFER, self.vbos[0]);
|
||||
self.gl.VertexAttribPointer(
|
||||
self.solid_program.attrib_vert as u32,
|
||||
2,
|
||||
ffi::FLOAT,
|
||||
ffi::FALSE,
|
||||
0,
|
||||
std::ptr::null(),
|
||||
);
|
||||
|
||||
self.gl.DrawArrays(ffi::TRIANGLE_STRIP, 0, 4);
|
||||
let damage = instances.unwrap_or(&[0.0, 0.0, 1.0, 1.0]);
|
||||
self.gl
|
||||
.EnableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_position as u32);
|
||||
self.gl.BindBuffer(ffi::ARRAY_BUFFER, self.vbos[1]);
|
||||
self.gl.BufferData(
|
||||
ffi::ARRAY_BUFFER,
|
||||
(std::mem::size_of::<ffi::types::GLfloat>() * damage.len()) as isize,
|
||||
damage.as_ptr() as *const _,
|
||||
ffi::STREAM_DRAW,
|
||||
);
|
||||
|
||||
let count = (damage.len() / 4) as i32;
|
||||
self.gl.VertexAttribPointer(
|
||||
self.tex_programs[tex.0.texture_kind].attrib_position as u32,
|
||||
4,
|
||||
ffi::FLOAT,
|
||||
ffi::FALSE,
|
||||
0,
|
||||
std::ptr::null(),
|
||||
);
|
||||
|
||||
self.gl
|
||||
.DisableVertexAttribArray(self.programs[tex.0.texture_kind].attrib_position as u32);
|
||||
.VertexAttribDivisor(self.tex_programs[tex.0.texture_kind].attrib_vert as u32, 0);
|
||||
self.gl
|
||||
.DisableVertexAttribArray(self.programs[tex.0.texture_kind].attrib_tex_coords as u32);
|
||||
.VertexAttribDivisor(self.tex_programs[tex.0.texture_kind].attrib_tex_coords as u32, 0);
|
||||
self.gl
|
||||
.VertexAttribDivisor(self.tex_programs[tex.0.texture_kind].attrib_position as u32, 1);
|
||||
|
||||
self.gl.DrawArraysInstanced(ffi::TRIANGLE_STRIP, 0, 4, count);
|
||||
|
||||
self.gl.BindBuffer(ffi::ARRAY_BUFFER, 0);
|
||||
self.gl.BindTexture(target, 0);
|
||||
self.gl
|
||||
.DisableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_tex_coords as u32);
|
||||
self.gl
|
||||
.DisableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_vert as u32);
|
||||
self.gl
|
||||
.DisableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_position as u32);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Projection matrix for this frame
|
||||
pub fn projection(&self) -> &[f32; 9] {
|
||||
self.current_projection.as_ref()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,29 +2,48 @@
|
|||
* OpenGL Shaders
|
||||
*/
|
||||
pub const VERTEX_SHADER: &str = r#"
|
||||
|
||||
#version 100
|
||||
uniform mat3 matrix;
|
||||
uniform bool invert_y;
|
||||
attribute vec2 position;
|
||||
|
||||
attribute vec2 vert;
|
||||
attribute vec2 tex_coords;
|
||||
attribute vec4 position;
|
||||
|
||||
varying vec2 v_tex_coords;
|
||||
|
||||
mat2 scale(vec2 scale_vec){
|
||||
return mat2(
|
||||
scale_vec.x, 0.0,
|
||||
0.0, scale_vec.y
|
||||
);
|
||||
}
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(matrix * vec3(position, 1.0), 1.0);
|
||||
if (invert_y) {
|
||||
v_tex_coords = vec2(tex_coords.x, 1.0 - tex_coords.y);
|
||||
} else {
|
||||
v_tex_coords = tex_coords;
|
||||
}
|
||||
}"#;
|
||||
|
||||
vec2 transform_translation = position.xy;
|
||||
vec2 transform_scale = position.zw;
|
||||
v_tex_coords = (vec3((tex_coords * scale(transform_scale)) + transform_translation, 1.0)).xy;
|
||||
gl_Position = vec4(matrix * vec3((vert * scale(transform_scale)) + transform_translation, 1.0), 1.0);
|
||||
}
|
||||
"#;
|
||||
|
||||
pub const FRAGMENT_COUNT: usize = 3;
|
||||
|
||||
pub const FRAGMENT_SHADER_ABGR: &str = r#"
|
||||
#version 100
|
||||
|
||||
precision mediump float;
|
||||
uniform sampler2D tex;
|
||||
uniform float alpha;
|
||||
varying vec2 v_tex_coords;
|
||||
|
||||
void main() {
|
||||
gl_FragColor = texture2D(tex, v_tex_coords) * alpha;
|
||||
}
|
||||
|
@ -32,10 +51,12 @@ void main() {
|
|||
|
||||
pub const FRAGMENT_SHADER_XBGR: &str = r#"
|
||||
#version 100
|
||||
|
||||
precision mediump float;
|
||||
uniform sampler2D tex;
|
||||
uniform float alpha;
|
||||
varying vec2 v_tex_coords;
|
||||
|
||||
void main() {
|
||||
gl_FragColor = vec4(texture2D(tex, v_tex_coords).rgb, 1.0) * alpha;
|
||||
}
|
||||
|
@ -44,11 +65,45 @@ void main() {
|
|||
pub const FRAGMENT_SHADER_EXTERNAL: &str = r#"
|
||||
#version 100
|
||||
#extension GL_OES_EGL_image_external : require
|
||||
|
||||
precision mediump float;
|
||||
uniform samplerExternalOES tex;
|
||||
uniform float alpha;
|
||||
varying vec2 v_tex_coords;
|
||||
|
||||
void main() {
|
||||
gl_FragColor = texture2D(tex, v_tex_coords) * alpha;
|
||||
}
|
||||
"#;
|
||||
|
||||
pub const VERTEX_SHADER_SOLID: &str = r#"
|
||||
#version 100
|
||||
|
||||
uniform mat3 matrix;
|
||||
attribute vec2 vert;
|
||||
attribute vec4 position;
|
||||
|
||||
mat2 scale(vec2 scale_vec){
|
||||
return mat2(
|
||||
scale_vec.x, 0.0,
|
||||
0.0, scale_vec.y
|
||||
);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 transform_translation = position.xy;
|
||||
vec2 transform_scale = position.zw;
|
||||
gl_Position = vec4(matrix * vec3((vert * scale(transform_scale)) + transform_translation, 1.0), 1.0);
|
||||
}
|
||||
"#;
|
||||
|
||||
pub const FRAGMENT_SHADER_SOLID: &str = r#"
|
||||
#version 100
|
||||
|
||||
precision mediump float;
|
||||
uniform vec4 color;
|
||||
|
||||
void main() {
|
||||
gl_FragColor = color;
|
||||
}
|
||||
"#;
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
use std::collections::HashSet;
|
||||
use std::error::Error;
|
||||
|
||||
use crate::utils::{Buffer, Physical, Point, Rectangle, Size};
|
||||
use crate::utils::{Buffer, Coordinate, Physical, Point, Rectangle, Size};
|
||||
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
use crate::wayland::compositor::SurfaceData;
|
||||
|
@ -32,6 +32,9 @@ use crate::backend::egl::{
|
|||
Error as EglError,
|
||||
};
|
||||
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
pub mod utils;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
/// Possible transformations to two-dimensional planes
|
||||
pub enum Transform {
|
||||
|
@ -94,17 +97,47 @@ impl Transform {
|
|||
}
|
||||
|
||||
/// Transformed size after applying this transformation.
|
||||
pub fn transform_size(&self, width: u32, height: u32) -> (u32, u32) {
|
||||
pub fn transform_size<N: Coordinate, Kind>(&self, size: Size<N, Kind>) -> Size<N, Kind> {
|
||||
if *self == Transform::_90
|
||||
|| *self == Transform::_270
|
||||
|| *self == Transform::Flipped90
|
||||
|| *self == Transform::Flipped270
|
||||
{
|
||||
(height, width)
|
||||
(size.h, size.w).into()
|
||||
} else {
|
||||
(width, height)
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
/// Transforms a rectangle inside an area of a given size by applying this transformation
|
||||
pub fn transform_rect_in<N: Coordinate, Kind>(
|
||||
&self,
|
||||
rect: Rectangle<N, Kind>,
|
||||
area: &Size<N, Kind>,
|
||||
) -> Rectangle<N, Kind> {
|
||||
let size = self.transform_size(rect.size);
|
||||
|
||||
let loc = match *self {
|
||||
Transform::Normal => rect.loc,
|
||||
Transform::_90 => (area.h - rect.loc.y - rect.size.h, rect.loc.x).into(),
|
||||
Transform::_180 => (
|
||||
area.w - rect.loc.x - rect.size.w,
|
||||
area.h - rect.loc.y - rect.size.h,
|
||||
)
|
||||
.into(),
|
||||
Transform::_270 => (rect.loc.y, area.w - rect.loc.x - rect.size.w).into(),
|
||||
Transform::Flipped => (area.w - rect.loc.x - rect.size.w, rect.loc.y).into(),
|
||||
Transform::Flipped90 => (rect.loc.y, rect.loc.x).into(),
|
||||
Transform::Flipped180 => (rect.loc.x, area.h - rect.loc.y - rect.size.h).into(),
|
||||
Transform::Flipped270 => (
|
||||
area.h - rect.loc.y - rect.size.h,
|
||||
area.w - rect.loc.x - rect.size.w,
|
||||
)
|
||||
.into(),
|
||||
};
|
||||
|
||||
Rectangle::from_loc_and_size(loc, size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
|
@ -171,11 +204,12 @@ pub trait Frame {
|
|||
///
|
||||
/// This operation is only valid in between a `begin` and `finish`-call.
|
||||
/// If called outside this operation may error-out, do nothing or modify future rendering results in any way.
|
||||
fn clear(&mut self, color: [f32; 4]) -> Result<(), Self::Error>;
|
||||
fn clear(&mut self, color: [f32; 4], at: &[Rectangle<i32, Physical>]) -> Result<(), Self::Error>;
|
||||
|
||||
/// Render a texture to the current target as a flat 2d-plane at a given
|
||||
/// position and applying the given transformation with the given alpha value.
|
||||
/// (Meaning `src_transform` should match the orientation of surface being rendered).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_texture_at(
|
||||
&mut self,
|
||||
texture: &Self::TextureId,
|
||||
|
@ -183,6 +217,7 @@ pub trait Frame {
|
|||
texture_scale: i32,
|
||||
output_scale: f64,
|
||||
src_transform: Transform,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
alpha: f32,
|
||||
) -> Result<(), Self::Error> {
|
||||
self.render_texture_from_to(
|
||||
|
@ -196,6 +231,7 @@ pub trait Frame {
|
|||
.to_f64()
|
||||
.to_physical(output_scale),
|
||||
),
|
||||
damage,
|
||||
src_transform,
|
||||
alpha,
|
||||
)
|
||||
|
@ -209,9 +245,13 @@ pub trait Frame {
|
|||
texture: &Self::TextureId,
|
||||
src: Rectangle<i32, Buffer>,
|
||||
dst: Rectangle<f64, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
src_transform: Transform,
|
||||
alpha: f32,
|
||||
) -> Result<(), Self::Error>;
|
||||
|
||||
/// Output transformation that is applied to this frame
|
||||
fn transformation(&self) -> Transform;
|
||||
}
|
||||
|
||||
/// Abstraction of commonly used rendering operations for compositors.
|
||||
|
@ -250,7 +290,7 @@ pub trait Renderer {
|
|||
pub trait ImportShm: Renderer {
|
||||
/// Import a given shm-based buffer into the renderer (see [`buffer_type`]).
|
||||
///
|
||||
/// Returns a texture_id, which can be used with [`Frame::render_texture`] (or [`Frame::render_texture_at`])
|
||||
/// Returns a texture_id, which can be used with [`Frame::render_texture_from_to`] (or [`Frame::render_texture_at`])
|
||||
/// or implementation-specific functions.
|
||||
///
|
||||
/// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it.
|
||||
|
@ -317,7 +357,7 @@ pub trait ImportEgl: Renderer {
|
|||
|
||||
/// Import a given wl_drm-based buffer into the renderer (see [`buffer_type`]).
|
||||
///
|
||||
/// Returns a texture_id, which can be used with [`Frame::render_texture`] (or [`Frame::render_texture_at`])
|
||||
/// Returns a texture_id, which can be used with [`Frame::render_texture_from_to`] (or [`Frame::render_texture_at`])
|
||||
/// or implementation-specific functions.
|
||||
///
|
||||
/// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it.
|
||||
|
@ -365,7 +405,7 @@ pub trait ImportDma: Renderer {
|
|||
|
||||
/// Import a given raw dmabuf into the renderer.
|
||||
///
|
||||
/// Returns a texture_id, which can be used with [`Frame::render_texture`] (or [`Frame::render_texture_at`])
|
||||
/// Returns a texture_id, which can be used with [`Frame::render_texture_from_to`] (or [`Frame::render_texture_at`])
|
||||
/// or implementation-specific functions.
|
||||
///
|
||||
/// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it.
|
||||
|
@ -388,7 +428,7 @@ pub trait ImportDma: Renderer {
|
|||
pub trait ImportAll: Renderer {
|
||||
/// Import a given buffer into the renderer.
|
||||
///
|
||||
/// Returns a texture_id, which can be used with [`Frame::render_texture`] (or [`Frame::render_texture_at`])
|
||||
/// Returns a texture_id, which can be used with [`Frame::render_texture_from_to`] (or [`Frame::render_texture_at`])
|
||||
/// or implementation-specific functions.
|
||||
///
|
||||
/// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it.
|
||||
|
@ -520,3 +560,102 @@ pub fn buffer_dimensions(buffer: &wl_buffer::WlBuffer) -> Option<Size<i32, Physi
|
|||
|
||||
crate::wayland::shm::with_buffer_contents(buffer, |_, data| (data.width, data.height).into()).ok()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Transform;
|
||||
use crate::utils::{Logical, Rectangle, Size};
|
||||
|
||||
#[test]
|
||||
fn transform_rect_ident() {
|
||||
let rect = Rectangle::<i32, Logical>::from_loc_and_size((10, 20), (30, 40));
|
||||
let size = Size::from((70, 90));
|
||||
let transform = Transform::Normal;
|
||||
|
||||
assert_eq!(rect, transform.transform_rect_in(rect, &size))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transform_rect_90() {
|
||||
let rect = Rectangle::<i32, Logical>::from_loc_and_size((10, 20), (30, 40));
|
||||
let size = Size::from((70, 90));
|
||||
let transform = Transform::_90;
|
||||
|
||||
assert_eq!(
|
||||
Rectangle::from_loc_and_size((30, 10), (40, 30)),
|
||||
transform.transform_rect_in(rect, &size)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transform_rect_180() {
|
||||
let rect = Rectangle::<i32, Logical>::from_loc_and_size((10, 20), (30, 40));
|
||||
let size = Size::from((70, 90));
|
||||
let transform = Transform::_180;
|
||||
|
||||
assert_eq!(
|
||||
Rectangle::from_loc_and_size((30, 30), (30, 40)),
|
||||
transform.transform_rect_in(rect, &size)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transform_rect_270() {
|
||||
let rect = Rectangle::<i32, Logical>::from_loc_and_size((10, 20), (30, 40));
|
||||
let size = Size::from((70, 90));
|
||||
let transform = Transform::_270;
|
||||
|
||||
assert_eq!(
|
||||
Rectangle::from_loc_and_size((20, 30), (40, 30)),
|
||||
transform.transform_rect_in(rect, &size)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transform_rect_f() {
|
||||
let rect = Rectangle::<i32, Logical>::from_loc_and_size((10, 20), (30, 40));
|
||||
let size = Size::from((70, 90));
|
||||
let transform = Transform::Flipped;
|
||||
|
||||
assert_eq!(
|
||||
Rectangle::from_loc_and_size((30, 20), (30, 40)),
|
||||
transform.transform_rect_in(rect, &size)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transform_rect_f90() {
|
||||
let rect = Rectangle::<i32, Logical>::from_loc_and_size((10, 20), (30, 40));
|
||||
let size = Size::from((70, 80));
|
||||
let transform = Transform::Flipped90;
|
||||
|
||||
assert_eq!(
|
||||
Rectangle::from_loc_and_size((20, 10), (40, 30)),
|
||||
transform.transform_rect_in(rect, &size)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transform_rect_f180() {
|
||||
let rect = Rectangle::<i32, Logical>::from_loc_and_size((10, 20), (30, 40));
|
||||
let size = Size::from((70, 90));
|
||||
let transform = Transform::Flipped180;
|
||||
|
||||
assert_eq!(
|
||||
Rectangle::from_loc_and_size((10, 30), (30, 40)),
|
||||
transform.transform_rect_in(rect, &size)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transform_rect_f270() {
|
||||
let rect = Rectangle::<i32, Logical>::from_loc_and_size((10, 20), (30, 40));
|
||||
let size = Size::from((70, 90));
|
||||
let transform = Transform::Flipped270;
|
||||
|
||||
assert_eq!(
|
||||
Rectangle::from_loc_and_size((30, 30), (40, 30)),
|
||||
transform.transform_rect_in(rect, &size)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,223 @@
|
|||
//! Utility module for helpers around drawing [`WlSurface`]s with [`Renderer`]s.
|
||||
|
||||
use crate::{
|
||||
backend::renderer::{buffer_dimensions, Frame, ImportAll, Renderer, Texture},
|
||||
utils::{Logical, Physical, Point, Rectangle, Size},
|
||||
wayland::compositor::{
|
||||
is_sync_subsurface, with_surface_tree_upward, BufferAssignment, Damage, SubsurfaceCachedState,
|
||||
SurfaceAttributes, TraversalAction,
|
||||
},
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
#[cfg(feature = "desktop")]
|
||||
use std::collections::HashSet;
|
||||
use wayland_server::protocol::{wl_buffer::WlBuffer, wl_surface::WlSurface};
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct SurfaceState {
|
||||
pub(crate) buffer_dimensions: Option<Size<i32, Physical>>,
|
||||
pub(crate) buffer_scale: i32,
|
||||
pub(crate) buffer: Option<WlBuffer>,
|
||||
pub(crate) texture: Option<Box<dyn std::any::Any + 'static>>,
|
||||
#[cfg(feature = "desktop")]
|
||||
pub(crate) damage_seen: HashSet<crate::desktop::space::SpaceOutputHash>,
|
||||
}
|
||||
|
||||
impl SurfaceState {
|
||||
pub fn update_buffer(&mut self, attrs: &mut SurfaceAttributes) {
|
||||
match attrs.buffer.take() {
|
||||
Some(BufferAssignment::NewBuffer { buffer, .. }) => {
|
||||
// new contents
|
||||
self.buffer_dimensions = buffer_dimensions(&buffer);
|
||||
self.buffer_scale = attrs.buffer_scale;
|
||||
if let Some(old_buffer) = std::mem::replace(&mut self.buffer, Some(buffer)) {
|
||||
if &old_buffer != self.buffer.as_ref().unwrap() {
|
||||
old_buffer.release();
|
||||
}
|
||||
}
|
||||
self.texture = None;
|
||||
#[cfg(feature = "desktop")]
|
||||
self.damage_seen.clear();
|
||||
}
|
||||
Some(BufferAssignment::Removed) => {
|
||||
// remove the contents
|
||||
self.buffer_dimensions = None;
|
||||
if let Some(buffer) = self.buffer.take() {
|
||||
buffer.release();
|
||||
};
|
||||
self.texture = None;
|
||||
#[cfg(feature = "desktop")]
|
||||
self.damage_seen.clear();
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handler to let smithay take over buffer management.
|
||||
///
|
||||
/// Needs to be called first on the commit-callback of
|
||||
/// [`crate::wayland::compositor::compositor_init`].
|
||||
///
|
||||
/// Consumes the buffer of [`SurfaceAttributes`], the buffer will
|
||||
/// not be accessible anymore, but [`draw_surface_tree`] and other
|
||||
/// `draw_*` helpers of the [desktop module](`crate::desktop`) will
|
||||
/// become usable for surfaces handled this way.
|
||||
pub fn on_commit_buffer_handler(surface: &WlSurface) {
|
||||
if !is_sync_subsurface(surface) {
|
||||
with_surface_tree_upward(
|
||||
surface,
|
||||
(),
|
||||
|_, _, _| TraversalAction::DoChildren(()),
|
||||
|_surf, states, _| {
|
||||
states
|
||||
.data_map
|
||||
.insert_if_missing(|| RefCell::new(SurfaceState::default()));
|
||||
let mut data = states
|
||||
.data_map
|
||||
.get::<RefCell<SurfaceState>>()
|
||||
.unwrap()
|
||||
.borrow_mut();
|
||||
data.update_buffer(&mut *states.cached_state.current::<SurfaceAttributes>());
|
||||
},
|
||||
|_, _, _| true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws a surface and its subsurfaces using a given [`Renderer`] and [`Frame`].
|
||||
///
|
||||
/// - `scale` needs to be equivalent to the fractional scale the rendered result should have.
|
||||
/// - `location` is the position the surface should be drawn at.
|
||||
/// - `damage` is the set of regions of the surface that should be drawn.
|
||||
///
|
||||
/// Note: This element will render nothing, if you are not using
|
||||
/// [`crate::backend::renderer::utils::on_commit_buffer_handler`]
|
||||
/// to let smithay handle buffer management.
|
||||
pub fn draw_surface_tree<R, E, F, T>(
|
||||
renderer: &mut R,
|
||||
frame: &mut F,
|
||||
surface: &WlSurface,
|
||||
scale: f64,
|
||||
location: Point<i32, Logical>,
|
||||
damage: &[Rectangle<i32, Logical>],
|
||||
log: &slog::Logger,
|
||||
) -> Result<(), R::Error>
|
||||
where
|
||||
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportAll,
|
||||
F: Frame<Error = E, TextureId = T>,
|
||||
E: std::error::Error,
|
||||
T: Texture + 'static,
|
||||
{
|
||||
let mut result = Ok(());
|
||||
let damage = damage
|
||||
.iter()
|
||||
.map(|geo| geo.to_f64().to_physical(scale).to_i32_up())
|
||||
.collect::<Vec<_>>();
|
||||
with_surface_tree_upward(
|
||||
surface,
|
||||
location,
|
||||
|_surface, states, location| {
|
||||
let mut location = *location;
|
||||
if let Some(data) = states.data_map.get::<RefCell<SurfaceState>>() {
|
||||
let mut data = data.borrow_mut();
|
||||
let attributes = states.cached_state.current::<SurfaceAttributes>();
|
||||
// Import a new buffer if necessary
|
||||
if data.texture.is_none() {
|
||||
if let Some(buffer) = data.buffer.as_ref() {
|
||||
let damage = attributes
|
||||
.damage
|
||||
.iter()
|
||||
.map(|dmg| match dmg {
|
||||
Damage::Buffer(rect) => *rect,
|
||||
// TODO also apply transformations
|
||||
Damage::Surface(rect) => rect.to_buffer(attributes.buffer_scale),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
match renderer.import_buffer(buffer, Some(states), &damage) {
|
||||
Some(Ok(m)) => {
|
||||
data.texture = Some(Box::new(m));
|
||||
}
|
||||
Some(Err(err)) => {
|
||||
slog::warn!(log, "Error loading buffer: {}", err);
|
||||
}
|
||||
None => {
|
||||
slog::error!(log, "Unknown buffer format for: {:?}", buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now, should we be drawn ?
|
||||
if data.texture.is_some() {
|
||||
// if yes, also process the children
|
||||
if states.role == Some("subsurface") {
|
||||
let current = states.cached_state.current::<SubsurfaceCachedState>();
|
||||
location += current.location;
|
||||
}
|
||||
TraversalAction::DoChildren(location)
|
||||
} else {
|
||||
// we are not displayed, so our children are neither
|
||||
TraversalAction::SkipChildren
|
||||
}
|
||||
} else {
|
||||
// we are not displayed, so our children are neither
|
||||
TraversalAction::SkipChildren
|
||||
}
|
||||
},
|
||||
|_surface, states, location| {
|
||||
let mut location = *location;
|
||||
if let Some(data) = states.data_map.get::<RefCell<SurfaceState>>() {
|
||||
let mut data = data.borrow_mut();
|
||||
let buffer_scale = data.buffer_scale;
|
||||
let buffer_dimensions = data.buffer_dimensions;
|
||||
let attributes = states.cached_state.current::<SurfaceAttributes>();
|
||||
if let Some(texture) = data.texture.as_mut().and_then(|x| x.downcast_mut::<T>()) {
|
||||
// we need to re-extract the subsurface offset, as the previous closure
|
||||
// only passes it to our children
|
||||
let mut surface_offset = (0, 0).into();
|
||||
if states.role == Some("subsurface") {
|
||||
let current = states.cached_state.current::<SubsurfaceCachedState>();
|
||||
surface_offset = current.location;
|
||||
location += current.location;
|
||||
}
|
||||
|
||||
let rect = Rectangle::<i32, Physical>::from_loc_and_size(
|
||||
surface_offset.to_f64().to_physical(scale).to_i32_round(),
|
||||
buffer_dimensions
|
||||
.unwrap_or_default()
|
||||
.to_logical(buffer_scale)
|
||||
.to_f64()
|
||||
.to_physical(scale)
|
||||
.to_i32_round(),
|
||||
);
|
||||
let new_damage = damage
|
||||
.iter()
|
||||
.cloned()
|
||||
.flat_map(|geo| geo.intersection(rect))
|
||||
.map(|mut geo| {
|
||||
geo.loc -= rect.loc;
|
||||
geo
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// TODO: Take wp_viewporter into account
|
||||
if let Err(err) = frame.render_texture_at(
|
||||
texture,
|
||||
location.to_f64().to_physical(scale).to_i32_round(),
|
||||
buffer_scale,
|
||||
scale,
|
||||
attributes.buffer_transform.into(),
|
||||
&new_damage,
|
||||
1.0,
|
||||
) {
|
||||
result = Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|_, _, _| true,
|
||||
);
|
||||
|
||||
result
|
||||
}
|
|
@ -10,7 +10,8 @@
|
|||
//! you want on the initialization of the backend. These functions will provide you
|
||||
//! with two objects:
|
||||
//!
|
||||
//! - a [`WinitGraphicsBackend`], which can give you an implementation of a [`Renderer`]
|
||||
//! - a [`WinitGraphicsBackend`], which can give you an implementation of a
|
||||
//! [`Renderer`](crate::backend::renderer::Renderer)
|
||||
//! (or even [`Gles2Renderer`]) through its `renderer` method in addition to further
|
||||
//! functionality to access and manage the created winit-window.
|
||||
//! - a [`WinitEventLoop`], which dispatches some [`WinitEvent`] from the host graphics server.
|
||||
|
@ -27,11 +28,11 @@ use crate::{
|
|||
},
|
||||
input::InputEvent,
|
||||
renderer::{
|
||||
gles2::{Gles2Error, Gles2Frame, Gles2Renderer},
|
||||
Bind, Renderer, Transform, Unbind,
|
||||
gles2::{Gles2Error, Gles2Renderer},
|
||||
Bind,
|
||||
},
|
||||
},
|
||||
utils::{Logical, Physical, Size},
|
||||
utils::{Logical, Physical, Rectangle, Size},
|
||||
};
|
||||
use std::{cell::RefCell, rc::Rc, time::Instant};
|
||||
use wayland_egl as wegl;
|
||||
|
@ -81,7 +82,7 @@ impl WindowSize {
|
|||
}
|
||||
}
|
||||
|
||||
/// Window with an active EGL Context created by `winit`. Implements the [`Renderer`] trait
|
||||
/// Window with an active EGL Context created by `winit`.
|
||||
#[derive(Debug)]
|
||||
pub struct WinitGraphicsBackend {
|
||||
renderer: Gles2Renderer,
|
||||
|
@ -90,6 +91,7 @@ pub struct WinitGraphicsBackend {
|
|||
egl: Rc<EGLSurface>,
|
||||
window: Rc<WinitWindow>,
|
||||
size: Rc<RefCell<WindowSize>>,
|
||||
damage_tracking: bool,
|
||||
resize_notification: Rc<Cell<Option<Size<i32, Physical>>>>,
|
||||
}
|
||||
|
||||
|
@ -111,7 +113,8 @@ pub struct WinitEventLoop {
|
|||
is_x11: bool,
|
||||
}
|
||||
|
||||
/// Create a new [`WinitGraphicsBackend`], which implements the [`Renderer`] trait and a corresponding
|
||||
/// Create a new [`WinitGraphicsBackend`], which implements the
|
||||
/// [`Renderer`](crate::backend::renderer::Renderer) trait and a corresponding
|
||||
/// [`WinitEventLoop`].
|
||||
pub fn init<L>(logger: L) -> Result<(WinitGraphicsBackend, WinitEventLoop), Error>
|
||||
where
|
||||
|
@ -126,7 +129,8 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
/// Create a new [`WinitGraphicsBackend`], which implements the [`Renderer`] trait, from a given [`WindowBuilder`]
|
||||
/// Create a new [`WinitGraphicsBackend`], which implements the
|
||||
/// [`Renderer`](crate::backend::renderer::Renderer) trait, from a given [`WindowBuilder`]
|
||||
/// struct and a corresponding [`WinitEventLoop`].
|
||||
pub fn init_from_builder<L>(
|
||||
builder: WindowBuilder,
|
||||
|
@ -147,7 +151,8 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
/// Create a new [`WinitGraphicsBackend`], which implements the [`Renderer`] trait, from a given [`WindowBuilder`]
|
||||
/// Create a new [`WinitGraphicsBackend`], which implements the
|
||||
/// [`Renderer`](crate::backend::renderer::Renderer) trait, from a given [`WindowBuilder`]
|
||||
/// struct, as well as given [`GlAttributes`] for further customization of the rendering pipeline and a
|
||||
/// corresponding [`WinitEventLoop`].
|
||||
pub fn init_from_builder_with_gl_attr<L>(
|
||||
|
@ -220,6 +225,10 @@ where
|
|||
let egl = Rc::new(surface);
|
||||
let renderer = unsafe { Gles2Renderer::new(context, log.clone())? };
|
||||
let resize_notification = Rc::new(Cell::new(None));
|
||||
let damage_tracking = display.extensions.iter().any(|ext| ext == "EGL_EXT_buffer_age")
|
||||
&& display.extensions.iter().any(|ext| {
|
||||
ext == "EGL_KHR_swap_buffers_with_damage" || ext == "EGL_EXT_swap_buffers_with_damage"
|
||||
});
|
||||
|
||||
Ok((
|
||||
WinitGraphicsBackend {
|
||||
|
@ -227,6 +236,7 @@ where
|
|||
_display: display,
|
||||
egl,
|
||||
renderer,
|
||||
damage_tracking,
|
||||
size: size.clone(),
|
||||
resize_notification: resize_notification.clone(),
|
||||
},
|
||||
|
@ -281,27 +291,59 @@ impl WinitGraphicsBackend {
|
|||
&mut self.renderer
|
||||
}
|
||||
|
||||
/// Shortcut to `Renderer::render` with the current window dimensions
|
||||
/// and this window set as the rendering target.
|
||||
pub fn render<F, R>(&mut self, rendering: F) -> Result<R, crate::backend::SwapBuffersError>
|
||||
where
|
||||
F: FnOnce(&mut Gles2Renderer, &mut Gles2Frame) -> R,
|
||||
{
|
||||
/// Bind the underlying window to the underlying renderer
|
||||
pub fn bind(&mut self) -> Result<(), crate::backend::SwapBuffersError> {
|
||||
// Were we told to resize?
|
||||
if let Some(size) = self.resize_notification.take() {
|
||||
self.egl.resize(size.w, size.h, 0, 0);
|
||||
}
|
||||
|
||||
let size = {
|
||||
let size = self.size.borrow();
|
||||
size.physical_size
|
||||
};
|
||||
|
||||
self.renderer.bind(self.egl.clone())?;
|
||||
let result = self.renderer.render(size, Transform::Normal, rendering)?;
|
||||
self.egl.swap_buffers()?;
|
||||
self.renderer.unbind()?;
|
||||
Ok(result)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieve the buffer age of the current backbuffer of the window
|
||||
pub fn buffer_age(&self) -> usize {
|
||||
if self.damage_tracking {
|
||||
self.egl.buffer_age() as usize
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// Submits the back buffer to the window by swapping, requires the window to be previously bound (see [`WinitGraphicsBackend::bind`]).
|
||||
pub fn submit(
|
||||
&mut self,
|
||||
damage: Option<&[Rectangle<i32, Logical>]>,
|
||||
scale: f64,
|
||||
) -> Result<(), crate::backend::SwapBuffersError> {
|
||||
let mut damage = match damage {
|
||||
Some(damage) if self.damage_tracking && !damage.is_empty() => {
|
||||
let size = self
|
||||
.size
|
||||
.borrow()
|
||||
.physical_size
|
||||
.to_f64()
|
||||
.to_logical(scale)
|
||||
.to_i32_round::<i32>();
|
||||
let damage = damage
|
||||
.iter()
|
||||
.map(|rect| {
|
||||
Rectangle::from_loc_and_size(
|
||||
(rect.loc.x, size.h - rect.loc.y - rect.size.h),
|
||||
rect.size,
|
||||
)
|
||||
.to_f64()
|
||||
.to_physical(scale)
|
||||
.to_i32_round::<i32>()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Some(damage)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
self.egl.swap_buffers(damage.as_deref_mut())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ impl X11Surface {
|
|||
///
|
||||
/// You may bind this buffer to a renderer to render.
|
||||
/// This function will return the same buffer until [`submit`](Self::submit) is called
|
||||
/// or [`reset_buffers`](Self::reset_buffer) is used to reset the buffers.
|
||||
/// or [`reset_buffers`](Self::reset_buffers) is used to reset the buffers.
|
||||
pub fn buffer(&mut self) -> Result<(Dmabuf, u8), AllocateBuffersError> {
|
||||
if let Some(new_size) = self.resize.try_iter().last() {
|
||||
self.resize(new_size);
|
||||
|
|
|
@ -0,0 +1,553 @@
|
|||
use crate::{
|
||||
backend::renderer::{utils::draw_surface_tree, Frame, ImportAll, Renderer, Texture},
|
||||
desktop::{utils::*, PopupManager, Space},
|
||||
utils::{user_data::UserDataMap, Logical, Point, Rectangle},
|
||||
wayland::{
|
||||
compositor::with_states,
|
||||
output::{Inner as OutputInner, Output},
|
||||
shell::wlr_layer::{
|
||||
Anchor, ExclusiveZone, KeyboardInteractivity, Layer as WlrLayer, LayerSurface as WlrLayerSurface,
|
||||
LayerSurfaceCachedState,
|
||||
},
|
||||
},
|
||||
};
|
||||
use indexmap::IndexSet;
|
||||
use wayland_server::protocol::wl_surface::WlSurface;
|
||||
|
||||
use std::{
|
||||
cell::{RefCell, RefMut},
|
||||
hash::{Hash, Hasher},
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex, Weak},
|
||||
};
|
||||
|
||||
crate::utils::ids::id_gen!(next_layer_id, LAYER_ID, LAYER_IDS);
|
||||
|
||||
/// Map of [`LayerSurface`]s on an [`Output`]
|
||||
#[derive(Debug)]
|
||||
pub struct LayerMap {
|
||||
layers: IndexSet<LayerSurface>,
|
||||
output: Weak<(Mutex<OutputInner>, wayland_server::UserDataMap)>,
|
||||
zone: Rectangle<i32, Logical>,
|
||||
}
|
||||
|
||||
/// Retrieve a [`LayerMap`] for a given [`Output`].
|
||||
///
|
||||
/// If none existed before a new empty [`LayerMap`] is attached
|
||||
/// to the output and returned on subsequent calls.
|
||||
///
|
||||
/// Note: This function internally uses a [`RefCell`] per
|
||||
/// [`Output`] as exposed by its return type. Therefor
|
||||
/// trying to hold on to multiple references of a [`LayerMap`]
|
||||
/// of the same output using this function *will* result in a panic.
|
||||
pub fn layer_map_for_output(o: &Output) -> RefMut<'_, LayerMap> {
|
||||
let userdata = o.user_data();
|
||||
let weak_output = Arc::downgrade(&o.inner);
|
||||
userdata.insert_if_missing(|| {
|
||||
RefCell::new(LayerMap {
|
||||
layers: IndexSet::new(),
|
||||
output: weak_output,
|
||||
zone: Rectangle::from_loc_and_size(
|
||||
(0, 0),
|
||||
o.current_mode()
|
||||
.map(|mode| mode.size.to_logical(o.current_scale()))
|
||||
.unwrap_or_else(|| (0, 0).into()),
|
||||
),
|
||||
})
|
||||
});
|
||||
userdata.get::<RefCell<LayerMap>>().unwrap().borrow_mut()
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum LayerError {
|
||||
#[error("Layer is already mapped to a different map")]
|
||||
AlreadyMapped,
|
||||
}
|
||||
|
||||
impl LayerMap {
|
||||
/// Map a [`LayerSurface`] to this [`LayerMap`].
|
||||
pub fn map_layer(&mut self, layer: &LayerSurface) -> Result<(), LayerError> {
|
||||
if !self.layers.contains(layer) {
|
||||
if layer
|
||||
.0
|
||||
.userdata
|
||||
.get::<LayerUserdata>()
|
||||
.map(|s| s.borrow().is_some())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Err(LayerError::AlreadyMapped);
|
||||
}
|
||||
|
||||
self.layers.insert(layer.clone());
|
||||
self.arrange();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove a [`LayerSurface`] from this [`LayerMap`].
|
||||
pub fn unmap_layer(&mut self, layer: &LayerSurface) {
|
||||
if self.layers.shift_remove(layer) {
|
||||
let _ = layer.user_data().get::<LayerUserdata>().take();
|
||||
self.arrange();
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the area of this output, that is not exclusive to any [`LayerSurface`]s.
|
||||
pub fn non_exclusive_zone(&self) -> Rectangle<i32, Logical> {
|
||||
self.zone
|
||||
}
|
||||
|
||||
/// Returns the geometry of a given mapped [`LayerSurface`].
|
||||
///
|
||||
/// If the surface was not previously mapped onto this layer map,
|
||||
/// this function return `None`.
|
||||
pub fn layer_geometry(&self, layer: &LayerSurface) -> Option<Rectangle<i32, Logical>> {
|
||||
if !self.layers.contains(layer) {
|
||||
return None;
|
||||
}
|
||||
let mut bbox = layer.bbox_with_popups();
|
||||
let state = layer_state(layer);
|
||||
bbox.loc += state.location;
|
||||
Some(bbox)
|
||||
}
|
||||
|
||||
/// Returns a [`LayerSurface`] under a given point and on a given layer, if any.
|
||||
pub fn layer_under<P: Into<Point<f64, Logical>>>(
|
||||
&self,
|
||||
layer: WlrLayer,
|
||||
point: P,
|
||||
) -> Option<&LayerSurface> {
|
||||
let point = point.into();
|
||||
self.layers_on(layer).rev().find(|l| {
|
||||
let bbox = self.layer_geometry(l).unwrap();
|
||||
bbox.to_f64().contains(point)
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterator over all [`LayerSurface`]s currently mapped.
|
||||
pub fn layers(&self) -> impl DoubleEndedIterator<Item = &LayerSurface> {
|
||||
self.layers.iter()
|
||||
}
|
||||
|
||||
/// Iterator over all [`LayerSurface`]s currently mapped on a given layer.
|
||||
pub fn layers_on(&self, layer: WlrLayer) -> impl DoubleEndedIterator<Item = &LayerSurface> {
|
||||
self.layers
|
||||
.iter()
|
||||
.filter(move |l| l.layer().map(|l| l == layer).unwrap_or(false))
|
||||
}
|
||||
|
||||
/// Returns the [`LayerSurface`] matching a given [`WlSurface`], if any.
|
||||
pub fn layer_for_surface(&self, surface: &WlSurface) -> Option<&LayerSurface> {
|
||||
if !surface.as_ref().is_alive() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.layers
|
||||
.iter()
|
||||
.find(|w| w.get_surface().map(|x| x == surface).unwrap_or(false))
|
||||
}
|
||||
|
||||
/// Force re-arranging the layer surfaces, e.g. when the output size changes.
|
||||
///
|
||||
/// Note: Mapping or unmapping a layer surface will automatically cause a re-arrangement.
|
||||
pub fn arrange(&mut self) {
|
||||
if let Some(output) = self.output() {
|
||||
let output_rect = Rectangle::from_loc_and_size(
|
||||
(0, 0),
|
||||
output
|
||||
.current_mode()
|
||||
.map(|mode| mode.size.to_logical(output.current_scale()))
|
||||
.unwrap_or_else(|| (0, 0).into()),
|
||||
);
|
||||
let mut zone = output_rect;
|
||||
slog::debug!(
|
||||
crate::slog_or_fallback(None),
|
||||
"Arranging layers into {:?}",
|
||||
output_rect.size
|
||||
);
|
||||
|
||||
for layer in self.layers.iter() {
|
||||
let surface = if let Some(surface) = layer.get_surface() {
|
||||
surface
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let data = with_states(surface, |states| {
|
||||
*states.cached_state.current::<LayerSurfaceCachedState>()
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let source = match data.exclusive_zone {
|
||||
ExclusiveZone::Neutral | ExclusiveZone::Exclusive(_) => &zone,
|
||||
ExclusiveZone::DontCare => &output_rect,
|
||||
};
|
||||
|
||||
let mut size = data.size;
|
||||
if size.w == 0 {
|
||||
size.w = source.size.w / 2;
|
||||
}
|
||||
if size.h == 0 {
|
||||
size.h = source.size.h / 2;
|
||||
}
|
||||
if data.anchor.anchored_horizontally() {
|
||||
size.w = source.size.w;
|
||||
}
|
||||
if data.anchor.anchored_vertically() {
|
||||
size.h = source.size.h;
|
||||
}
|
||||
|
||||
let x = if data.anchor.contains(Anchor::LEFT) {
|
||||
source.loc.x + data.margin.left
|
||||
} else if data.anchor.contains(Anchor::RIGHT) {
|
||||
source.loc.x + (source.size.w - size.w) - data.margin.right
|
||||
} else {
|
||||
source.loc.x + ((source.size.w / 2) - (size.w / 2))
|
||||
};
|
||||
|
||||
let y = if data.anchor.contains(Anchor::TOP) {
|
||||
source.loc.y + data.margin.top
|
||||
} else if data.anchor.contains(Anchor::BOTTOM) {
|
||||
source.loc.y + (source.size.h - size.h) - data.margin.bottom
|
||||
} else {
|
||||
source.loc.y + ((source.size.h / 2) - (size.h / 2))
|
||||
};
|
||||
|
||||
let location: Point<i32, Logical> = (x, y).into();
|
||||
|
||||
if let ExclusiveZone::Exclusive(amount) = data.exclusive_zone {
|
||||
match data.anchor {
|
||||
x if x.contains(Anchor::LEFT) && !x.contains(Anchor::RIGHT) => {
|
||||
zone.loc.x += amount as i32 + data.margin.left + data.margin.right
|
||||
}
|
||||
x if x.contains(Anchor::TOP) && !x.contains(Anchor::BOTTOM) => {
|
||||
zone.loc.y += amount as i32 + data.margin.top + data.margin.bottom
|
||||
}
|
||||
x if x.contains(Anchor::RIGHT) && !x.contains(Anchor::LEFT) => {
|
||||
zone.size.w -= amount as i32 + data.margin.left + data.margin.right
|
||||
}
|
||||
x if x.contains(Anchor::BOTTOM) && !x.contains(Anchor::TOP) => {
|
||||
zone.size.h -= amount as i32 + data.margin.top + data.margin.top
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
slog::debug!(
|
||||
crate::slog_or_fallback(None),
|
||||
"Setting layer to pos {:?} and size {:?}",
|
||||
location,
|
||||
size
|
||||
);
|
||||
let size_changed = layer
|
||||
.0
|
||||
.surface
|
||||
.with_pending_state(|state| {
|
||||
state.size.replace(size).map(|old| old != size).unwrap_or(true)
|
||||
})
|
||||
.unwrap();
|
||||
if size_changed {
|
||||
layer.0.surface.send_configure();
|
||||
}
|
||||
|
||||
layer_state(layer).location = location;
|
||||
}
|
||||
|
||||
slog::debug!(crate::slog_or_fallback(None), "Remaining zone {:?}", zone);
|
||||
self.zone = zone;
|
||||
}
|
||||
}
|
||||
|
||||
fn output(&self) -> Option<Output> {
|
||||
self.output.upgrade().map(|inner| Output { inner })
|
||||
}
|
||||
|
||||
/// Cleanup some internally used resources.
|
||||
///
|
||||
/// This function needs to be called periodically (though not necessarily frequently)
|
||||
/// to be able cleanup internally used resources.
|
||||
pub fn cleanup(&mut self) {
|
||||
self.layers.retain(|layer| layer.alive())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct LayerState {
|
||||
pub location: Point<i32, Logical>,
|
||||
}
|
||||
|
||||
type LayerUserdata = RefCell<Option<LayerState>>;
|
||||
pub fn layer_state(layer: &LayerSurface) -> RefMut<'_, LayerState> {
|
||||
let userdata = layer.user_data();
|
||||
userdata.insert_if_missing(LayerUserdata::default);
|
||||
RefMut::map(userdata.get::<LayerUserdata>().unwrap().borrow_mut(), |opt| {
|
||||
if opt.is_none() {
|
||||
*opt = Some(LayerState::default());
|
||||
}
|
||||
opt.as_mut().unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
/// A [`LayerSurface`] represents a single layer surface as given by the wlr-layer-shell protocol.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LayerSurface(pub(crate) Rc<LayerSurfaceInner>);
|
||||
|
||||
impl PartialEq for LayerSurface {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.id == other.0.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for LayerSurface {}
|
||||
|
||||
impl Hash for LayerSurface {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct LayerSurfaceInner {
|
||||
pub(crate) id: usize,
|
||||
surface: WlrLayerSurface,
|
||||
namespace: String,
|
||||
userdata: UserDataMap,
|
||||
}
|
||||
|
||||
impl Drop for LayerSurfaceInner {
|
||||
fn drop(&mut self) {
|
||||
LAYER_IDS.lock().unwrap().remove(&self.id);
|
||||
}
|
||||
}
|
||||
|
||||
impl LayerSurface {
|
||||
/// Create a new [`LayerSurface`] from a given [`WlrLayerSurface`] and its namespace.
|
||||
pub fn new(surface: WlrLayerSurface, namespace: String) -> LayerSurface {
|
||||
LayerSurface(Rc::new(LayerSurfaceInner {
|
||||
id: next_layer_id(),
|
||||
surface,
|
||||
namespace,
|
||||
userdata: UserDataMap::new(),
|
||||
}))
|
||||
}
|
||||
|
||||
/// Checks if the surface is still alive
|
||||
pub fn alive(&self) -> bool {
|
||||
self.0.surface.alive()
|
||||
}
|
||||
|
||||
/// Returns the underlying [`WlrLayerSurface`]
|
||||
pub fn layer_surface(&self) -> &WlrLayerSurface {
|
||||
&self.0.surface
|
||||
}
|
||||
|
||||
/// Returns the underlying [`WlSurface`]
|
||||
pub fn get_surface(&self) -> Option<&WlSurface> {
|
||||
self.0.surface.get_surface()
|
||||
}
|
||||
|
||||
/// Returns the cached protocol state
|
||||
pub fn cached_state(&self) -> Option<LayerSurfaceCachedState> {
|
||||
self.0.surface.get_surface().map(|surface| {
|
||||
with_states(surface, |states| {
|
||||
*states.cached_state.current::<LayerSurfaceCachedState>()
|
||||
})
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns true, if the surface has indicated, that it is able to process keyboard events.
|
||||
pub fn can_receive_keyboard_focus(&self) -> bool {
|
||||
self.0
|
||||
.surface
|
||||
.get_surface()
|
||||
.map(|surface| {
|
||||
with_states(surface, |states| {
|
||||
match states
|
||||
.cached_state
|
||||
.current::<LayerSurfaceCachedState>()
|
||||
.keyboard_interactivity
|
||||
{
|
||||
KeyboardInteractivity::Exclusive | KeyboardInteractivity::OnDemand => true,
|
||||
KeyboardInteractivity::None => false,
|
||||
}
|
||||
})
|
||||
.unwrap()
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Returns the layer this surface resides on, if any yet.
|
||||
pub fn layer(&self) -> Option<WlrLayer> {
|
||||
self.0.surface.get_surface().map(|surface| {
|
||||
with_states(surface, |states| {
|
||||
states.cached_state.current::<LayerSurfaceCachedState>().layer
|
||||
})
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the namespace of this surface
|
||||
pub fn namespace(&self) -> &str {
|
||||
&self.0.namespace
|
||||
}
|
||||
|
||||
/// Returns the bounding box over this layer surface and its subsurfaces.
|
||||
pub fn bbox(&self) -> Rectangle<i32, Logical> {
|
||||
if let Some(surface) = self.0.surface.get_surface() {
|
||||
bbox_from_surface_tree(surface, (0, 0))
|
||||
} else {
|
||||
Rectangle::from_loc_and_size((0, 0), (0, 0))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the bounding box over this layer surface, it subsurfaces as well as any popups.
|
||||
///
|
||||
/// Note: You need to use a [`PopupManager`] to track popups, otherwise the bounding box
|
||||
/// will not include the popups.
|
||||
pub fn bbox_with_popups(&self) -> Rectangle<i32, Logical> {
|
||||
let mut bounding_box = self.bbox();
|
||||
if let Some(surface) = self.0.surface.get_surface() {
|
||||
for (popup, location) in PopupManager::popups_for_surface(surface)
|
||||
.ok()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
if let Some(surface) = popup.get_surface() {
|
||||
bounding_box = bounding_box.merge(bbox_from_surface_tree(surface, location));
|
||||
}
|
||||
}
|
||||
}
|
||||
bounding_box
|
||||
}
|
||||
|
||||
/// Finds the topmost surface under this point if any and returns it together with the location of this
|
||||
/// surface.
|
||||
///
|
||||
/// - `point` needs to be relative to (0,0) of the layer surface.
|
||||
pub fn surface_under<P: Into<Point<f64, Logical>>>(
|
||||
&self,
|
||||
point: P,
|
||||
) -> Option<(WlSurface, Point<i32, Logical>)> {
|
||||
let point = point.into();
|
||||
if let Some(surface) = self.get_surface() {
|
||||
for (popup, location) in PopupManager::popups_for_surface(surface)
|
||||
.ok()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
if let Some(result) = popup
|
||||
.get_surface()
|
||||
.and_then(|surface| under_from_surface_tree(surface, point, location))
|
||||
{
|
||||
return Some(result);
|
||||
}
|
||||
}
|
||||
|
||||
under_from_surface_tree(surface, point, (0, 0))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the damage of all the surfaces of this layer surface.
|
||||
///
|
||||
/// If `for_values` is `Some(_)` it will only return the damage on the
|
||||
/// first call for a given [`Space`] and [`Output`], if the buffer hasn't changed.
|
||||
/// Subsequent calls will return an empty vector until the buffer is updated again.
|
||||
pub(super) fn accumulated_damage(
|
||||
&self,
|
||||
for_values: Option<(&Space, &Output)>,
|
||||
) -> Vec<Rectangle<i32, Logical>> {
|
||||
let mut damage = Vec::new();
|
||||
if let Some(surface) = self.get_surface() {
|
||||
damage.extend(
|
||||
damage_from_surface_tree(surface, (0, 0), for_values)
|
||||
.into_iter()
|
||||
.flat_map(|rect| rect.intersection(self.bbox())),
|
||||
);
|
||||
for (popup, location) in PopupManager::popups_for_surface(surface)
|
||||
.ok()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
if let Some(surface) = popup.get_surface() {
|
||||
let bbox = bbox_from_surface_tree(surface, location);
|
||||
let popup_damage = damage_from_surface_tree(surface, location, for_values);
|
||||
damage.extend(popup_damage.into_iter().flat_map(|rect| rect.intersection(bbox)));
|
||||
}
|
||||
}
|
||||
}
|
||||
damage
|
||||
}
|
||||
|
||||
/// Sends the frame callback to all the subsurfaces in this
|
||||
/// window that requested it
|
||||
pub fn send_frame(&self, time: u32) {
|
||||
if let Some(wl_surface) = self.0.surface.get_surface() {
|
||||
send_frames_surface_tree(wl_surface, time)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a [`UserDataMap`] to allow associating arbitrary data with this surface.
|
||||
pub fn user_data(&self) -> &UserDataMap {
|
||||
&self.0.userdata
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders a given [`LayerSurface`] using a provided renderer and frame.
|
||||
///
|
||||
/// - `scale` needs to be equivalent to the fractional scale the rendered result should have.
|
||||
/// - `location` is the position the layer surface should be drawn at.
|
||||
/// - `damage` is the set of regions of the layer surface that should be drawn.
|
||||
///
|
||||
/// Note: This function will render nothing, if you are not using
|
||||
/// [`crate::backend::renderer::utils::on_commit_buffer_handler`]
|
||||
/// to let smithay handle buffer management.
|
||||
pub fn draw_layer_surface<R, E, F, T, P>(
|
||||
renderer: &mut R,
|
||||
frame: &mut F,
|
||||
layer: &LayerSurface,
|
||||
scale: f64,
|
||||
location: P,
|
||||
damage: &[Rectangle<i32, Logical>],
|
||||
log: &slog::Logger,
|
||||
) -> Result<(), R::Error>
|
||||
where
|
||||
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportAll,
|
||||
F: Frame<Error = E, TextureId = T>,
|
||||
E: std::error::Error,
|
||||
T: Texture + 'static,
|
||||
P: Into<Point<i32, Logical>>,
|
||||
{
|
||||
let location = location.into();
|
||||
if let Some(surface) = layer.get_surface() {
|
||||
draw_surface_tree(renderer, frame, surface, scale, location, damage, log)?;
|
||||
for (popup, p_location) in PopupManager::popups_for_surface(surface)
|
||||
.ok()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
if let Some(surface) = popup.get_surface() {
|
||||
let damage = damage
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|mut geo| {
|
||||
geo.loc -= p_location;
|
||||
geo
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
draw_surface_tree(
|
||||
renderer,
|
||||
frame,
|
||||
surface,
|
||||
scale,
|
||||
location + p_location,
|
||||
&damage,
|
||||
log,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
//! Desktop management helpers
|
||||
//!
|
||||
//! This module contains helpers to organize and interact with desktop-style shells.
|
||||
//!
|
||||
//! It is therefore a lot more opinionated than for example the [xdg-shell handler](crate::wayland::shell::xdg::xdg_shell_init)
|
||||
//! and tightly integrates with some protocols (e.g. xdg-shell).
|
||||
//!
|
||||
//! The usage of this module is therefor entirely optional and depending on your use-case you might also only want
|
||||
//! to use a limited set of the helpers provided.
|
||||
//!
|
||||
//! ## Helpers
|
||||
//!
|
||||
//! ### [`Window`]
|
||||
//!
|
||||
//! A window represents what is typically understood by the end-user as a single application window.
|
||||
//!
|
||||
//! Currently it abstracts over xdg-shell toplevels and Xwayland surfaces (TODO).
|
||||
//! It provides a bunch of methods to calculate and retrieve its size, manage itself, attach additional user_data
|
||||
//! as well as a [drawing function](`draw_window`) to ease rendering it's related surfaces.
|
||||
//!
|
||||
//! Note that a [`Window`] on it's own has no position. For that it needs to be placed inside a [`Space`].
|
||||
//!
|
||||
//! ### [`Space`]
|
||||
//!
|
||||
//! A space represents a two-dimensional plane of undefined dimensions.
|
||||
//! [`Window`]s and [`Output`](crate::wayland::output::Output)s can be mapped onto it.
|
||||
//!
|
||||
//! Windows get a position and stacking order through mapping. Outputs become views of a part of the [`Space`]
|
||||
//! and can be rendered via [`Space::render_output`]. Rendering results of spaces are automatically damage-tracked.
|
||||
//!
|
||||
//! ### Layer Shell
|
||||
//!
|
||||
//! A [`LayerSurface`] represents a surface as provided by e.g. the layer-shell protocol.
|
||||
//! It provides similar helper methods as a [`Window`] does to toplevel surfaces.
|
||||
//!
|
||||
//! Each [`Output`](crate::wayland::output::Output) can be associated a [`LayerMap`] by calling [`layer_map_for_output`],
|
||||
//! which [`LayerSurface`]s can be mapped upon. Associated layer maps are automatically rendered by [`Space::render_output`],
|
||||
//! but a [draw function](`draw_layer_surface`) is also provided for manual layer-surface management.
|
||||
//!
|
||||
//! ### Popups
|
||||
//!
|
||||
//! Provides a [`PopupManager`], which can be used to automatically keep track of popups and their
|
||||
//! relations to one-another. Popups are then automatically rendered with their matching toplevel surfaces,
|
||||
//! when either [`draw_window`], [`draw_layer_surface`] or [`Space::render_output`] is called.
|
||||
//!
|
||||
//! ## Remarks
|
||||
//!
|
||||
//! Note that the desktop abstractions are concerned with easing rendering different clients and therefore need to be able
|
||||
//! to manage client buffers to do so. If you plan to use the provided drawing functions, you need to use
|
||||
//! [`on_commit_buffer_handler`](crate::backend::renderer::utils::on_commit_buffer_handler).
|
||||
|
||||
pub(crate) mod layer;
|
||||
mod popup;
|
||||
pub mod space;
|
||||
pub mod utils;
|
||||
mod window;
|
||||
|
||||
pub use self::layer::{draw_layer_surface, layer_map_for_output, LayerMap, LayerSurface};
|
||||
pub use self::popup::*;
|
||||
pub use self::space::Space;
|
||||
pub use self::window::*;
|
|
@ -0,0 +1,288 @@
|
|||
use crate::{
|
||||
utils::{DeadResource, Logical, Point, Rectangle},
|
||||
wayland::{
|
||||
compositor::{get_role, with_states},
|
||||
shell::xdg::{PopupSurface, SurfaceCachedState, XdgPopupSurfaceRoleAttributes, XDG_POPUP_ROLE},
|
||||
},
|
||||
};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use wayland_server::protocol::wl_surface::WlSurface;
|
||||
|
||||
/// Helper to track popups.
|
||||
#[derive(Debug)]
|
||||
pub struct PopupManager {
|
||||
unmapped_popups: Vec<PopupKind>,
|
||||
popup_trees: Vec<PopupTree>,
|
||||
logger: ::slog::Logger,
|
||||
}
|
||||
|
||||
impl PopupManager {
|
||||
/// Create a new [`PopupManager`].
|
||||
pub fn new<L: Into<Option<::slog::Logger>>>(logger: L) -> Self {
|
||||
PopupManager {
|
||||
unmapped_popups: Vec::new(),
|
||||
popup_trees: Vec::new(),
|
||||
logger: crate::slog_or_fallback(logger),
|
||||
}
|
||||
}
|
||||
|
||||
/// Start tracking a new popup.
|
||||
pub fn track_popup(&mut self, kind: PopupKind) -> Result<(), DeadResource> {
|
||||
if kind.parent().is_some() {
|
||||
self.add_popup(kind)
|
||||
} else {
|
||||
slog::trace!(self.logger, "Adding unmapped popups: {:?}", kind);
|
||||
self.unmapped_popups.push(kind);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Needs to be called for [`PopupManager`] to correctly update its internal state.
|
||||
pub fn commit(&mut self, surface: &WlSurface) {
|
||||
if get_role(surface) == Some(XDG_POPUP_ROLE) {
|
||||
if let Some(i) = self
|
||||
.unmapped_popups
|
||||
.iter()
|
||||
.position(|p| p.get_surface() == Some(surface))
|
||||
{
|
||||
slog::trace!(self.logger, "Popup got mapped");
|
||||
let popup = self.unmapped_popups.swap_remove(i);
|
||||
// at this point the popup must have a parent,
|
||||
// or it would have raised a protocol error
|
||||
let _ = self.add_popup(popup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_popup(&mut self, popup: PopupKind) -> Result<(), DeadResource> {
|
||||
let mut parent = popup.parent().unwrap();
|
||||
while get_role(&parent) == Some(XDG_POPUP_ROLE) {
|
||||
parent = with_states(&parent, |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<Mutex<XdgPopupSurfaceRoleAttributes>>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.parent
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.unwrap()
|
||||
})?;
|
||||
}
|
||||
|
||||
with_states(&parent, |states| {
|
||||
let tree = PopupTree::default();
|
||||
if states.data_map.insert_if_missing(|| tree.clone()) {
|
||||
self.popup_trees.push(tree);
|
||||
};
|
||||
let tree = states.data_map.get::<PopupTree>().unwrap();
|
||||
if !tree.alive() {
|
||||
// if it previously had no popups, we likely removed it from our list already
|
||||
self.popup_trees.push(tree.clone());
|
||||
}
|
||||
slog::trace!(self.logger, "Adding popup {:?} to parent {:?}", popup, parent);
|
||||
tree.insert(popup);
|
||||
})
|
||||
}
|
||||
|
||||
/// Finds the popup belonging to a given [`WlSurface`], if any.
|
||||
pub fn find_popup(&self, surface: &WlSurface) -> Option<PopupKind> {
|
||||
self.unmapped_popups
|
||||
.iter()
|
||||
.find(|p| p.get_surface() == Some(surface))
|
||||
.cloned()
|
||||
.or_else(|| {
|
||||
self.popup_trees
|
||||
.iter()
|
||||
.map(|tree| tree.iter_popups())
|
||||
.flatten()
|
||||
.find(|(p, _)| p.get_surface() == Some(surface))
|
||||
.map(|(p, _)| p)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the popups and their relative positions for a given toplevel surface, if any.
|
||||
pub fn popups_for_surface(
|
||||
surface: &WlSurface,
|
||||
) -> Result<impl Iterator<Item = (PopupKind, Point<i32, Logical>)>, DeadResource> {
|
||||
with_states(surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<PopupTree>()
|
||||
.map(|x| x.iter_popups())
|
||||
.into_iter()
|
||||
.flatten()
|
||||
})
|
||||
}
|
||||
|
||||
/// Needs to be called periodically (but not necessarily frequently)
|
||||
/// to cleanup internal resources.
|
||||
pub fn cleanup(&mut self) {
|
||||
// retain_mut is sadly still unstable
|
||||
self.popup_trees.iter_mut().for_each(|tree| tree.cleanup());
|
||||
self.popup_trees.retain(|tree| tree.alive());
|
||||
self.unmapped_popups.retain(|surf| surf.alive());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
struct PopupTree(Arc<Mutex<Vec<PopupNode>>>);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct PopupNode {
|
||||
surface: PopupKind,
|
||||
children: Vec<PopupNode>,
|
||||
}
|
||||
|
||||
impl PopupTree {
|
||||
fn iter_popups(&self) -> impl Iterator<Item = (PopupKind, Point<i32, Logical>)> {
|
||||
self.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|n| n.iter_popups_relative_to((0, 0)).map(|(p, l)| (p.clone(), l)))
|
||||
.flatten()
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
}
|
||||
|
||||
fn insert(&self, popup: PopupKind) {
|
||||
let children = &mut *self.0.lock().unwrap();
|
||||
for child in children.iter_mut() {
|
||||
if child.insert(popup.clone()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
children.push(PopupNode::new(popup));
|
||||
}
|
||||
|
||||
fn cleanup(&mut self) {
|
||||
let mut children = self.0.lock().unwrap();
|
||||
for child in children.iter_mut() {
|
||||
child.cleanup();
|
||||
}
|
||||
children.retain(|n| n.surface.alive());
|
||||
}
|
||||
|
||||
fn alive(&self) -> bool {
|
||||
!self.0.lock().unwrap().is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl PopupNode {
|
||||
fn new(surface: PopupKind) -> Self {
|
||||
PopupNode {
|
||||
surface,
|
||||
children: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn iter_popups_relative_to<P: Into<Point<i32, Logical>>>(
|
||||
&self,
|
||||
loc: P,
|
||||
) -> impl Iterator<Item = (&PopupKind, Point<i32, Logical>)> {
|
||||
let relative_to = loc.into() + self.surface.location();
|
||||
std::iter::once((&self.surface, relative_to)).chain(
|
||||
self.children
|
||||
.iter()
|
||||
.map(move |x| {
|
||||
Box::new(x.iter_popups_relative_to(relative_to))
|
||||
as Box<dyn Iterator<Item = (&PopupKind, Point<i32, Logical>)>>
|
||||
})
|
||||
.flatten(),
|
||||
)
|
||||
}
|
||||
|
||||
fn insert(&mut self, popup: PopupKind) -> bool {
|
||||
let parent = popup.parent().unwrap();
|
||||
if self.surface.get_surface() == Some(&parent) {
|
||||
self.children.push(PopupNode::new(popup));
|
||||
true
|
||||
} else {
|
||||
for child in &mut self.children {
|
||||
if child.insert(popup.clone()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn cleanup(&mut self) {
|
||||
for child in &mut self.children {
|
||||
child.cleanup();
|
||||
}
|
||||
self.children.retain(|n| n.surface.alive());
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a popup surface
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PopupKind {
|
||||
/// xdg-shell [`PopupSurface`]
|
||||
Xdg(PopupSurface),
|
||||
}
|
||||
|
||||
impl PopupKind {
|
||||
fn alive(&self) -> bool {
|
||||
match *self {
|
||||
PopupKind::Xdg(ref t) => t.alive(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the underlying [`WlSurface`]
|
||||
pub fn get_surface(&self) -> Option<&WlSurface> {
|
||||
match *self {
|
||||
PopupKind::Xdg(ref t) => t.get_surface(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parent(&self) -> Option<WlSurface> {
|
||||
match *self {
|
||||
PopupKind::Xdg(ref t) => t.get_parent_surface(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the surface geometry as set by the client using `xdg_surface::set_window_geometry`
|
||||
pub fn geometry(&self) -> Rectangle<i32, Logical> {
|
||||
let wl_surface = match self.get_surface() {
|
||||
Some(s) => s,
|
||||
None => return Rectangle::from_loc_and_size((0, 0), (0, 0)),
|
||||
};
|
||||
|
||||
with_states(wl_surface, |states| {
|
||||
states
|
||||
.cached_state
|
||||
.current::<SurfaceCachedState>()
|
||||
.geometry
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn location(&self) -> Point<i32, Logical> {
|
||||
let wl_surface = match self.get_surface() {
|
||||
Some(s) => s,
|
||||
None => return (0, 0).into(),
|
||||
};
|
||||
with_states(wl_surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<Mutex<XdgPopupSurfaceRoleAttributes>>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.current
|
||||
.geometry
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.loc
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PopupSurface> for PopupKind {
|
||||
fn from(p: PopupSurface) -> PopupKind {
|
||||
PopupKind::Xdg(p)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
use crate::{
|
||||
backend::renderer::{Frame, ImportAll, Renderer, Texture},
|
||||
desktop::{space::*, utils::*},
|
||||
utils::{Logical, Point, Rectangle},
|
||||
wayland::output::Output,
|
||||
};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
use wayland_server::protocol::wl_surface::WlSurface;
|
||||
|
||||
/// Trait for custom elements to be rendered during [`Space::render_output`].
|
||||
pub trait RenderElement<R, F, E, T>
|
||||
where
|
||||
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportAll,
|
||||
F: Frame<Error = E, TextureId = T>,
|
||||
E: std::error::Error,
|
||||
T: Texture + 'static,
|
||||
Self: Any + 'static,
|
||||
{
|
||||
/// Returns an id unique to this element for the type of Self.
|
||||
fn id(&self) -> usize;
|
||||
#[doc(hidden)]
|
||||
fn type_of(&self) -> TypeId {
|
||||
std::any::Any::type_id(self)
|
||||
}
|
||||
/// Returns the bounding box of this element including its position in the space.
|
||||
fn geometry(&self) -> Rectangle<i32, Logical>;
|
||||
/// Returns the damage of the element since it's last update.
|
||||
///
|
||||
/// If you receive `Some(_)` for `for_values` you may cache that you
|
||||
/// send the damage for this `Space` and `Output` combination once
|
||||
/// and return an empty vector for subsequent calls until the contents
|
||||
/// of this element actually change again for optimization reasons.
|
||||
///
|
||||
/// Returning `vec![Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX))]` is always
|
||||
/// correct, but very inefficient.
|
||||
fn accumulated_damage(
|
||||
&self,
|
||||
for_values: Option<SpaceOutputTuple<'_, '_>>,
|
||||
) -> Vec<Rectangle<i32, Logical>>;
|
||||
/// Draws the element using the provided `Frame` and `Renderer`.
|
||||
///
|
||||
/// - `scale` provides the current fractional scale value to render as
|
||||
/// - `damage` provides the regions you need to re-draw and *may* not
|
||||
/// be equivalent to the damage returned by `accumulated_damage`.
|
||||
/// Redrawing other parts of the element is not valid and may cause rendering artifacts.
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
frame: &mut F,
|
||||
scale: f64,
|
||||
damage: &[Rectangle<i32, Logical>],
|
||||
log: &slog::Logger,
|
||||
) -> Result<(), R::Error>;
|
||||
}
|
||||
|
||||
pub(crate) trait SpaceElement<R, F, E, T>
|
||||
where
|
||||
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportAll,
|
||||
F: Frame<Error = E, TextureId = T>,
|
||||
E: std::error::Error,
|
||||
T: Texture,
|
||||
{
|
||||
fn id(&self) -> usize;
|
||||
fn type_of(&self) -> TypeId;
|
||||
fn location(&self, space_id: usize) -> Point<i32, Logical> {
|
||||
self.geometry(space_id).loc
|
||||
}
|
||||
fn geometry(&self, space_id: usize) -> Rectangle<i32, Logical>;
|
||||
fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec<Rectangle<i32, Logical>>;
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn draw(
|
||||
&self,
|
||||
space_id: usize,
|
||||
renderer: &mut R,
|
||||
frame: &mut F,
|
||||
scale: f64,
|
||||
location: Point<i32, Logical>,
|
||||
damage: &[Rectangle<i32, Logical>],
|
||||
log: &slog::Logger,
|
||||
) -> Result<(), R::Error>;
|
||||
}
|
||||
|
||||
impl<R, F, E, T> SpaceElement<R, F, E, T> for Box<dyn RenderElement<R, F, E, T>>
|
||||
where
|
||||
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportAll + 'static,
|
||||
F: Frame<Error = E, TextureId = T> + 'static,
|
||||
E: std::error::Error + 'static,
|
||||
T: Texture + 'static,
|
||||
{
|
||||
fn id(&self) -> usize {
|
||||
(&**self as &dyn RenderElement<R, F, E, T>).id()
|
||||
}
|
||||
fn type_of(&self) -> TypeId {
|
||||
(&**self as &dyn RenderElement<R, F, E, T>).type_of()
|
||||
}
|
||||
fn geometry(&self, _space_id: usize) -> Rectangle<i32, Logical> {
|
||||
(&**self as &dyn RenderElement<R, F, E, T>).geometry()
|
||||
}
|
||||
fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec<Rectangle<i32, Logical>> {
|
||||
(&**self as &dyn RenderElement<R, F, E, T>).accumulated_damage(for_values.map(SpaceOutputTuple::from))
|
||||
}
|
||||
fn draw(
|
||||
&self,
|
||||
_space_id: usize,
|
||||
renderer: &mut R,
|
||||
frame: &mut F,
|
||||
scale: f64,
|
||||
_location: Point<i32, Logical>,
|
||||
damage: &[Rectangle<i32, Logical>],
|
||||
log: &slog::Logger,
|
||||
) -> Result<(), R::Error> {
|
||||
(&**self as &dyn RenderElement<R, F, E, T>).draw(renderer, frame, scale, damage, log)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic helper for drawing [`WlSurface`]s and their subsurfaces
|
||||
/// as custom elements via [`RenderElement`].
|
||||
///
|
||||
/// For example useful for cursor or drag-and-drop surfaces.
|
||||
///
|
||||
/// Note: This element will render nothing, if you are not using
|
||||
/// [`crate::backend::renderer::utils::on_commit_buffer_handler`]
|
||||
/// to let smithay handle buffer management.
|
||||
#[derive(Debug)]
|
||||
pub struct SurfaceTree {
|
||||
/// Surface to be drawn
|
||||
pub surface: WlSurface,
|
||||
/// Position to draw add
|
||||
pub position: Point<i32, Logical>,
|
||||
}
|
||||
|
||||
impl<R, F, E, T> RenderElement<R, F, E, T> for SurfaceTree
|
||||
where
|
||||
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportAll,
|
||||
F: Frame<Error = E, TextureId = T>,
|
||||
E: std::error::Error,
|
||||
T: Texture + 'static,
|
||||
{
|
||||
fn id(&self) -> usize {
|
||||
self.surface.as_ref().id() as usize
|
||||
}
|
||||
|
||||
fn geometry(&self) -> Rectangle<i32, Logical> {
|
||||
let mut bbox = bbox_from_surface_tree(&self.surface, (0, 0));
|
||||
bbox.loc += self.position;
|
||||
bbox
|
||||
}
|
||||
|
||||
fn accumulated_damage(
|
||||
&self,
|
||||
for_values: Option<SpaceOutputTuple<'_, '_>>,
|
||||
) -> Vec<Rectangle<i32, Logical>> {
|
||||
damage_from_surface_tree(&self.surface, (0, 0), for_values.map(|x| (x.0, x.1)))
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
frame: &mut F,
|
||||
scale: f64,
|
||||
damage: &[Rectangle<i32, Logical>],
|
||||
log: &slog::Logger,
|
||||
) -> Result<(), R::Error> {
|
||||
crate::backend::renderer::utils::draw_surface_tree(
|
||||
renderer,
|
||||
frame,
|
||||
&self.surface,
|
||||
scale,
|
||||
self.position,
|
||||
damage,
|
||||
log,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Newtype for (&Space, &Output) to provide a `Hash` implementation for damage tracking
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct SpaceOutputTuple<'a, 'b>(pub &'a Space, pub &'b Output);
|
||||
|
||||
impl<'a, 'b> Hash for SpaceOutputTuple<'a, 'b> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.id.hash(state);
|
||||
(std::sync::Arc::as_ptr(&self.1.inner) as *const () as usize).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> SpaceOutputTuple<'a, 'b> {
|
||||
/// Returns an owned version that produces and equivalent hash
|
||||
pub fn owned_hash(&self) -> SpaceOutputHash {
|
||||
SpaceOutputHash(
|
||||
self.0.id,
|
||||
std::sync::Arc::as_ptr(&self.1.inner) as *const () as usize,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> From<(&'a Space, &'b Output)> for SpaceOutputTuple<'a, 'b> {
|
||||
fn from((space, output): (&'a Space, &'b Output)) -> SpaceOutputTuple<'a, 'b> {
|
||||
SpaceOutputTuple(space, output)
|
||||
}
|
||||
}
|
||||
|
||||
/// Type to use as an owned hashable value equal to [`SpaceOutputTuple`]
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
pub struct SpaceOutputHash(usize, usize);
|
|
@ -0,0 +1,72 @@
|
|||
use crate::{
|
||||
backend::renderer::{Frame, ImportAll, Renderer, Texture},
|
||||
desktop::{
|
||||
layer::{layer_state as output_layer_state, *},
|
||||
space::{Space, SpaceElement},
|
||||
},
|
||||
utils::{Logical, Point, Rectangle},
|
||||
wayland::output::Output,
|
||||
};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
cell::{RefCell, RefMut},
|
||||
collections::HashMap,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LayerState {
|
||||
pub drawn: bool,
|
||||
}
|
||||
|
||||
type LayerUserdata = RefCell<HashMap<usize, LayerState>>;
|
||||
pub fn layer_state(space: usize, l: &LayerSurface) -> RefMut<'_, LayerState> {
|
||||
let userdata = l.user_data();
|
||||
userdata.insert_if_missing(LayerUserdata::default);
|
||||
RefMut::map(userdata.get::<LayerUserdata>().unwrap().borrow_mut(), |m| {
|
||||
m.entry(space).or_default()
|
||||
})
|
||||
}
|
||||
|
||||
impl<R, F, E, T> SpaceElement<R, F, E, T> for LayerSurface
|
||||
where
|
||||
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportAll,
|
||||
F: Frame<Error = E, TextureId = T>,
|
||||
E: std::error::Error,
|
||||
T: Texture + 'static,
|
||||
{
|
||||
fn id(&self) -> usize {
|
||||
self.0.id
|
||||
}
|
||||
|
||||
fn type_of(&self) -> TypeId {
|
||||
TypeId::of::<LayerSurface>()
|
||||
}
|
||||
|
||||
fn geometry(&self, _space_id: usize) -> Rectangle<i32, Logical> {
|
||||
let mut bbox = self.bbox_with_popups();
|
||||
let state = output_layer_state(self);
|
||||
bbox.loc += state.location;
|
||||
bbox
|
||||
}
|
||||
|
||||
fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec<Rectangle<i32, Logical>> {
|
||||
self.accumulated_damage(for_values)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
space_id: usize,
|
||||
renderer: &mut R,
|
||||
frame: &mut F,
|
||||
scale: f64,
|
||||
location: Point<i32, Logical>,
|
||||
damage: &[Rectangle<i32, Logical>],
|
||||
log: &slog::Logger,
|
||||
) -> Result<(), R::Error> {
|
||||
let res = draw_layer_surface(renderer, frame, self, scale, location, damage, log);
|
||||
if res.is_ok() {
|
||||
layer_state(space_id, self).drawn = true;
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
|
@ -0,0 +1,713 @@
|
|||
//! This module contains the [`Space`] helper class as well has related
|
||||
//! rendering helpers to add custom elements or different clients to a space.
|
||||
|
||||
use crate::{
|
||||
backend::renderer::{utils::SurfaceState, Frame, ImportAll, Renderer, Transform},
|
||||
desktop::{
|
||||
layer::{layer_map_for_output, LayerSurface},
|
||||
window::Window,
|
||||
},
|
||||
utils::{Logical, Point, Rectangle},
|
||||
wayland::{
|
||||
compositor::{
|
||||
get_parent, is_sync_subsurface, with_surface_tree_downward, SubsurfaceCachedState,
|
||||
TraversalAction,
|
||||
},
|
||||
output::Output,
|
||||
shell::wlr_layer::Layer as WlrLayer,
|
||||
},
|
||||
};
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use std::{cell::RefCell, collections::VecDeque, fmt};
|
||||
use wayland_server::protocol::wl_surface::WlSurface;
|
||||
|
||||
mod element;
|
||||
mod layer;
|
||||
mod output;
|
||||
mod window;
|
||||
|
||||
pub use self::element::*;
|
||||
use self::layer::*;
|
||||
use self::output::*;
|
||||
use self::window::*;
|
||||
|
||||
crate::utils::ids::id_gen!(next_space_id, SPACE_ID, SPACE_IDS);
|
||||
|
||||
/// Represents two dimensional plane to map windows and outputs upon.
|
||||
#[derive(Debug)]
|
||||
pub struct Space {
|
||||
pub(super) id: usize,
|
||||
// in z-order, back to front
|
||||
windows: IndexSet<Window>,
|
||||
outputs: Vec<Output>,
|
||||
logger: ::slog::Logger,
|
||||
}
|
||||
|
||||
/// Elements rendered by [`Space::render_output`] in addition to windows, layers and popups.
|
||||
pub type DynamicRenderElements<R> =
|
||||
Box<dyn RenderElement<R, <R as Renderer>::Frame, <R as Renderer>::Error, <R as Renderer>::TextureId>>;
|
||||
|
||||
impl PartialEq for Space {
|
||||
fn eq(&self, other: &Space) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Space {
|
||||
fn drop(&mut self) {
|
||||
SPACE_IDS.lock().unwrap().remove(&self.id);
|
||||
}
|
||||
}
|
||||
|
||||
impl Space {
|
||||
/// Create a new [`Space`]
|
||||
pub fn new<L>(log: L) -> Space
|
||||
where
|
||||
L: Into<Option<slog::Logger>>,
|
||||
{
|
||||
Space {
|
||||
id: next_space_id(),
|
||||
windows: IndexSet::new(),
|
||||
outputs: Vec::new(),
|
||||
logger: crate::slog_or_fallback(log),
|
||||
}
|
||||
}
|
||||
|
||||
/// Map a [`Window`] and move it to top of the stack
|
||||
///
|
||||
/// This can safely be called on an already mapped window
|
||||
/// to update its location inside the space.
|
||||
///
|
||||
/// If activate is true it will set the new windows state
|
||||
/// to be activate and removes that state from every
|
||||
/// other mapped window.
|
||||
pub fn map_window<P: Into<Point<i32, Logical>>>(&mut self, window: &Window, location: P, activate: bool) {
|
||||
self.insert_window(window, activate);
|
||||
window_state(self.id, window).location = location.into();
|
||||
}
|
||||
|
||||
/// Moves an already mapped [`Window`] to top of the stack
|
||||
///
|
||||
/// This function does nothing for unmapped windows.
|
||||
///
|
||||
/// If activate is true it will set the new windows state
|
||||
/// to be activate and removes that state from every
|
||||
/// other mapped window.
|
||||
pub fn raise_window(&mut self, window: &Window, activate: bool) {
|
||||
if self.windows.shift_remove(window) {
|
||||
self.insert_window(window, activate);
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_window(&mut self, window: &Window, activate: bool) {
|
||||
self.windows.insert(window.clone());
|
||||
|
||||
if activate {
|
||||
window.set_activated(true);
|
||||
for w in self.windows.iter() {
|
||||
if w != window {
|
||||
w.set_activated(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unmap a [`Window`] from this space.
|
||||
///
|
||||
/// This function does nothing for already unmapped windows
|
||||
pub fn unmap_window(&mut self, window: &Window) {
|
||||
if let Some(map) = window.user_data().get::<WindowUserdata>() {
|
||||
map.borrow_mut().remove(&self.id);
|
||||
}
|
||||
self.windows.shift_remove(window);
|
||||
}
|
||||
|
||||
/// Iterate window in z-order back to front
|
||||
pub fn windows(&self) -> impl DoubleEndedIterator<Item = &Window> {
|
||||
self.windows.iter()
|
||||
}
|
||||
|
||||
/// Get a reference to the window under a given point, if any
|
||||
pub fn window_under<P: Into<Point<f64, Logical>>>(&self, point: P) -> Option<&Window> {
|
||||
let point = point.into();
|
||||
self.windows.iter().rev().find(|w| {
|
||||
let bbox = window_rect(w, &self.id);
|
||||
bbox.to_f64().contains(point)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a reference to the outputs under a given point
|
||||
pub fn output_under<P: Into<Point<f64, Logical>>>(&self, point: P) -> impl Iterator<Item = &Output> {
|
||||
let point = point.into();
|
||||
self.outputs.iter().rev().filter(move |o| {
|
||||
let bbox = self.output_geometry(o);
|
||||
bbox.map(|bbox| bbox.to_f64().contains(point)).unwrap_or(false)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the window matching a given surface, if any
|
||||
pub fn window_for_surface(&self, surface: &WlSurface) -> Option<&Window> {
|
||||
if !surface.as_ref().is_alive() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.windows
|
||||
.iter()
|
||||
.find(|w| w.toplevel().get_surface().map(|x| x == surface).unwrap_or(false))
|
||||
}
|
||||
|
||||
/// Returns the layer matching a given surface, if any
|
||||
pub fn layer_for_surface(&self, surface: &WlSurface) -> Option<LayerSurface> {
|
||||
if !surface.as_ref().is_alive() {
|
||||
return None;
|
||||
}
|
||||
self.outputs.iter().find_map(|o| {
|
||||
let map = layer_map_for_output(o);
|
||||
map.layer_for_surface(surface).cloned()
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the geometry of a [`Window`] including its relative position inside the Space.
|
||||
pub fn window_geometry(&self, w: &Window) -> Option<Rectangle<i32, Logical>> {
|
||||
if !self.windows.contains(w) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(window_geo(w, &self.id))
|
||||
}
|
||||
|
||||
/// Returns the bounding box of a [`Window`] including its relative position inside the Space.
|
||||
pub fn window_bbox(&self, w: &Window) -> Option<Rectangle<i32, Logical>> {
|
||||
if !self.windows.contains(w) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(window_rect(w, &self.id))
|
||||
}
|
||||
|
||||
/// Maps an [`Output`] inside the space.
|
||||
///
|
||||
/// Can be safely called on an already mapped
|
||||
/// [`Output`] to update its scale or location.
|
||||
///
|
||||
/// The scale is the what is rendered for the given output
|
||||
/// and may be fractional. It is independent from the integer scale
|
||||
/// reported to clients by the output.
|
||||
///
|
||||
/// *Note:* Remapping an output does reset it's damage memory.
|
||||
pub fn map_output<P: Into<Point<i32, Logical>>>(&mut self, output: &Output, scale: f64, location: P) {
|
||||
let mut state = output_state(self.id, output);
|
||||
*state = OutputState {
|
||||
location: location.into(),
|
||||
render_scale: scale,
|
||||
// keep surfaces, we still need to inform them of leaving,
|
||||
// if they don't overlap anymore during refresh.
|
||||
surfaces: state.surfaces.drain(..).collect::<Vec<_>>(),
|
||||
// resets last_seen and old_damage, if remapped
|
||||
..Default::default()
|
||||
};
|
||||
if !self.outputs.contains(output) {
|
||||
self.outputs.push(output.clone());
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over all mapped [`Output`]s of this space.
|
||||
pub fn outputs(&self) -> impl Iterator<Item = &Output> {
|
||||
self.outputs.iter()
|
||||
}
|
||||
|
||||
/// Unmap an [`Output`] from this space.
|
||||
///
|
||||
/// Does nothing if the output was not previously mapped.
|
||||
pub fn unmap_output(&mut self, output: &Output) {
|
||||
if !self.outputs.contains(output) {
|
||||
return;
|
||||
}
|
||||
if let Some(map) = output.user_data().get::<OutputUserdata>() {
|
||||
map.borrow_mut().remove(&self.id);
|
||||
}
|
||||
self.outputs.retain(|o| o != output);
|
||||
}
|
||||
|
||||
/// Returns the geometry of the output including it's relative position inside the space.
|
||||
///
|
||||
/// The size is matching the amount of logical pixels of the space visible on the output
|
||||
/// given is current mode and render scale.
|
||||
pub fn output_geometry(&self, o: &Output) -> Option<Rectangle<i32, Logical>> {
|
||||
if !self.outputs.contains(o) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let transform: Transform = o.current_transform().into();
|
||||
let state = output_state(self.id, o);
|
||||
o.current_mode().map(|mode| {
|
||||
Rectangle::from_loc_and_size(
|
||||
state.location,
|
||||
transform
|
||||
.transform_size(mode.size)
|
||||
.to_f64()
|
||||
.to_logical(state.render_scale)
|
||||
.to_i32_round(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the reder scale of a mapped output.
|
||||
///
|
||||
/// If the output was not previously mapped to the `Space`
|
||||
/// this function returns `None`.
|
||||
pub fn output_scale(&self, o: &Output) -> Option<f64> {
|
||||
if !self.outputs.contains(o) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let state = output_state(self.id, o);
|
||||
Some(state.render_scale)
|
||||
}
|
||||
|
||||
/// Returns all [`Output`]s a [`Window`] overlaps with.
|
||||
pub fn outputs_for_window(&self, w: &Window) -> Vec<Output> {
|
||||
if !self.windows.contains(w) {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let w_geo = window_rect(w, &self.id);
|
||||
let mut outputs = self
|
||||
.outputs
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|o| {
|
||||
let o_geo = self.output_geometry(o).unwrap();
|
||||
w_geo.overlaps(o_geo)
|
||||
})
|
||||
.collect::<Vec<Output>>();
|
||||
outputs.sort_by(|o1, o2| {
|
||||
let overlap = |rect1: Rectangle<i32, Logical>, rect2: Rectangle<i32, Logical>| -> i32 {
|
||||
// x overlap
|
||||
std::cmp::max(0, std::cmp::min(rect1.loc.x + rect1.size.w, rect2.loc.x + rect2.size.w) - std::cmp::max(rect1.loc.x, rect2.loc.x))
|
||||
// y overlap
|
||||
* std::cmp::max(0, std::cmp::min(rect1.loc.y + rect1.size.h, rect2.loc.y + rect2.size.h) - std::cmp::max(rect1.loc.y, rect2.loc.y))
|
||||
};
|
||||
let o1_area = overlap(self.output_geometry(o1).unwrap(), w_geo);
|
||||
let o2_area = overlap(self.output_geometry(o2).unwrap(), w_geo);
|
||||
o1_area.cmp(&o2_area)
|
||||
});
|
||||
outputs
|
||||
}
|
||||
|
||||
/// Refresh some internal values and update client state,
|
||||
/// meaning this will handle output enter and leave events
|
||||
/// for mapped outputs and windows based on their position.
|
||||
///
|
||||
/// Needs to be called periodically, at best before every
|
||||
/// wayland socket flush.
|
||||
pub fn refresh(&mut self) {
|
||||
self.windows.retain(|w| w.toplevel().alive());
|
||||
|
||||
for output in &mut self.outputs {
|
||||
output_state(self.id, output)
|
||||
.surfaces
|
||||
.retain(|s| s.as_ref().is_alive());
|
||||
}
|
||||
|
||||
for window in &self.windows {
|
||||
let bbox = window_rect(window, &self.id);
|
||||
let kind = window.toplevel();
|
||||
|
||||
for output in &self.outputs {
|
||||
let output_geometry = self
|
||||
.output_geometry(output)
|
||||
.unwrap_or_else(|| Rectangle::from_loc_and_size((0, 0), (0, 0)));
|
||||
let mut output_state = output_state(self.id, output);
|
||||
|
||||
// Check if the bounding box of the toplevel intersects with
|
||||
// the output, if not no surface in the tree can intersect with
|
||||
// the output.
|
||||
if !output_geometry.overlaps(bbox) {
|
||||
if let Some(surface) = kind.get_surface() {
|
||||
with_surface_tree_downward(
|
||||
surface,
|
||||
(),
|
||||
|_, _, _| TraversalAction::DoChildren(()),
|
||||
|wl_surface, _, _| {
|
||||
if output_state.surfaces.contains(wl_surface) {
|
||||
slog::trace!(
|
||||
self.logger,
|
||||
"surface ({:?}) leaving output {:?}",
|
||||
wl_surface,
|
||||
output.name()
|
||||
);
|
||||
output.leave(wl_surface);
|
||||
output_state.surfaces.retain(|s| s != wl_surface);
|
||||
}
|
||||
},
|
||||
|_, _, _| true,
|
||||
)
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(surface) = kind.get_surface() {
|
||||
with_surface_tree_downward(
|
||||
surface,
|
||||
window_loc(window, &self.id),
|
||||
|_, states, location| {
|
||||
let mut location = *location;
|
||||
let data = states.data_map.get::<RefCell<SurfaceState>>();
|
||||
|
||||
if data.is_some() {
|
||||
if states.role == Some("subsurface") {
|
||||
let current = states.cached_state.current::<SubsurfaceCachedState>();
|
||||
location += current.location;
|
||||
}
|
||||
|
||||
TraversalAction::DoChildren(location)
|
||||
} else {
|
||||
// If the parent surface is unmapped, then the child surfaces are hidden as
|
||||
// well, no need to consider them here.
|
||||
TraversalAction::SkipChildren
|
||||
}
|
||||
},
|
||||
|wl_surface, states, &loc| {
|
||||
let data = states.data_map.get::<RefCell<SurfaceState>>();
|
||||
|
||||
if let Some(size) = data.and_then(|d| d.borrow().size()) {
|
||||
let surface_rectangle = Rectangle { loc, size };
|
||||
|
||||
if output_geometry.overlaps(surface_rectangle) {
|
||||
// We found a matching output, check if we already sent enter
|
||||
if !output_state.surfaces.contains(wl_surface) {
|
||||
slog::trace!(
|
||||
self.logger,
|
||||
"surface ({:?}) entering output {:?}",
|
||||
wl_surface,
|
||||
output.name()
|
||||
);
|
||||
output.enter(wl_surface);
|
||||
output_state.surfaces.push(wl_surface.clone());
|
||||
}
|
||||
} else {
|
||||
// Surface does not match output, if we sent enter earlier
|
||||
// we should now send leave
|
||||
if output_state.surfaces.contains(wl_surface) {
|
||||
slog::trace!(
|
||||
self.logger,
|
||||
"surface ({:?}) leaving output {:?}",
|
||||
wl_surface,
|
||||
output.name()
|
||||
);
|
||||
output.leave(wl_surface);
|
||||
output_state.surfaces.retain(|s| s != wl_surface);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Maybe the the surface got unmapped, send leave on output
|
||||
if output_state.surfaces.contains(wl_surface) {
|
||||
slog::trace!(
|
||||
self.logger,
|
||||
"surface ({:?}) leaving output {:?}",
|
||||
wl_surface,
|
||||
output.name()
|
||||
);
|
||||
output.leave(wl_surface);
|
||||
output_state.surfaces.retain(|s| s != wl_surface);
|
||||
}
|
||||
}
|
||||
},
|
||||
|_, _, _| true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Should be called on commit to let the space automatically call [`Window::refresh`]
|
||||
/// for the window that belongs to the given surface, if managed by this space.
|
||||
pub fn commit(&self, surface: &WlSurface) {
|
||||
if is_sync_subsurface(surface) {
|
||||
return;
|
||||
}
|
||||
let mut root = surface.clone();
|
||||
while let Some(parent) = get_parent(&root) {
|
||||
root = parent;
|
||||
}
|
||||
if let Some(window) = self.windows().find(|w| w.toplevel().get_surface() == Some(&root)) {
|
||||
window.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
/// Render a given [`Output`] using a given [`Renderer`].
|
||||
///
|
||||
/// [`Space`] will render all mapped [`Window`]s, mapped [`LayerSurface`](super::LayerSurface)s
|
||||
/// of the given [`Output`] and their popups (if tracked by a [`PopupManager`](super::PopupManager)).
|
||||
/// `clear_color` will be used to fill all unoccupied regions.
|
||||
///
|
||||
/// Rendering using this function will automatically apply damage-tracking.
|
||||
/// To facilitate this you need to provide age values of the buffers bound to
|
||||
/// the given `renderer`. If you stop using `Space` temporarily for rendering
|
||||
/// or apply additional rendering operations, you need to reset the age values
|
||||
/// accordingly as `Space` will be unable to track your custom rendering operations
|
||||
/// to avoid rendering artifacts.
|
||||
///
|
||||
/// To add aditional elements without breaking damage-tracking implement the `RenderElement`
|
||||
/// trait and use `custom_elements` to provide them to this function. `custom_elements are rendered
|
||||
/// after every other element.
|
||||
///
|
||||
/// Returns a list of updated regions (or `None` if that list would be empty) in case of success.
|
||||
pub fn render_output<R>(
|
||||
&mut self,
|
||||
renderer: &mut R,
|
||||
output: &Output,
|
||||
age: usize,
|
||||
clear_color: [f32; 4],
|
||||
custom_elements: &[DynamicRenderElements<R>],
|
||||
) -> Result<Option<Vec<Rectangle<i32, Logical>>>, RenderError<R>>
|
||||
where
|
||||
R: Renderer + ImportAll + 'static,
|
||||
R::TextureId: 'static,
|
||||
R::Error: 'static,
|
||||
R::Frame: 'static,
|
||||
{
|
||||
if !self.outputs.contains(output) {
|
||||
return Err(RenderError::UnmappedOutput);
|
||||
}
|
||||
|
||||
type SpaceElem<R> =
|
||||
dyn SpaceElement<R, <R as Renderer>::Frame, <R as Renderer>::Error, <R as Renderer>::TextureId>;
|
||||
|
||||
let mut state = output_state(self.id, output);
|
||||
let output_size = output
|
||||
.current_mode()
|
||||
.ok_or(RenderError::OutputNoMode)?
|
||||
.size
|
||||
.to_f64()
|
||||
.to_logical(state.render_scale)
|
||||
.to_i32_round();
|
||||
let output_geo = Rectangle::from_loc_and_size(state.location, output_size);
|
||||
let layer_map = layer_map_for_output(output);
|
||||
|
||||
// This will hold all the damage we need for this rendering step
|
||||
let mut damage = Vec::<Rectangle<i32, Logical>>::new();
|
||||
// First add damage for windows gone
|
||||
for old_toplevel in state
|
||||
.last_state
|
||||
.iter()
|
||||
.filter_map(|(id, geo)| {
|
||||
if !self
|
||||
.windows
|
||||
.iter()
|
||||
.map(|w| w as &SpaceElem<R>)
|
||||
.chain(layer_map.layers().map(|l| l as &SpaceElem<R>))
|
||||
.chain(custom_elements.iter().map(|c| c as &SpaceElem<R>))
|
||||
.any(|e| ToplevelId::from(e) == *id)
|
||||
{
|
||||
Some(*geo)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Rectangle<i32, Logical>>>()
|
||||
{
|
||||
slog::trace!(self.logger, "Removing toplevel at: {:?}", old_toplevel);
|
||||
damage.push(old_toplevel);
|
||||
}
|
||||
|
||||
// lets iterate front to back and figure out, what new windows or unmoved windows we have
|
||||
for element in self
|
||||
.windows
|
||||
.iter()
|
||||
.map(|w| w as &SpaceElem<R>)
|
||||
.chain(layer_map.layers().map(|l| l as &SpaceElem<R>))
|
||||
.chain(custom_elements.iter().map(|c| c as &SpaceElem<R>))
|
||||
{
|
||||
let geo = element.geometry(self.id);
|
||||
let old_geo = state.last_state.get(&ToplevelId::from(element)).cloned();
|
||||
|
||||
// window was moved or resized
|
||||
if old_geo.map(|old_geo| old_geo != geo).unwrap_or(false) {
|
||||
// Add damage for the old position of the window
|
||||
damage.push(old_geo.unwrap());
|
||||
damage.push(geo);
|
||||
} else {
|
||||
// window stayed at its place
|
||||
let loc = element.location(self.id);
|
||||
damage.extend(element.accumulated_damage(Some((self, output))).into_iter().map(
|
||||
|mut rect| {
|
||||
rect.loc += loc;
|
||||
rect
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// That is all completely new damage, which we need to store for subsequent renders
|
||||
let new_damage = damage.clone();
|
||||
// We now add old damage states, if we have an age value
|
||||
if age > 0 && state.old_damage.len() >= age {
|
||||
// We do not need even older states anymore
|
||||
state.old_damage.truncate(age);
|
||||
damage.extend(state.old_damage.iter().flatten().copied());
|
||||
} else {
|
||||
// just damage everything, if we have no damage
|
||||
damage = vec![output_geo];
|
||||
}
|
||||
|
||||
// Optimize the damage for rendering
|
||||
damage.dedup();
|
||||
damage.retain(|rect| rect.overlaps(output_geo));
|
||||
damage.retain(|rect| rect.size.h > 0 && rect.size.w > 0);
|
||||
// merge overlapping rectangles
|
||||
damage = damage.into_iter().fold(Vec::new(), |mut new_damage, rect| {
|
||||
if let Some(existing) = new_damage.iter_mut().find(|other| rect.overlaps(**other)) {
|
||||
*existing = existing.merge(rect);
|
||||
} else {
|
||||
new_damage.push(rect);
|
||||
}
|
||||
new_damage
|
||||
});
|
||||
|
||||
if damage.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let output_transform: Transform = output.current_transform().into();
|
||||
let res = renderer.render(
|
||||
output_transform
|
||||
.transform_size(output_size)
|
||||
.to_f64()
|
||||
.to_physical(state.render_scale)
|
||||
.to_i32_round(),
|
||||
output_transform,
|
||||
|renderer, frame| {
|
||||
// First clear all damaged regions
|
||||
slog::trace!(self.logger, "Clearing at {:#?}", damage);
|
||||
frame.clear(
|
||||
clear_color,
|
||||
&damage
|
||||
.iter()
|
||||
.map(|geo| geo.to_f64().to_physical(state.render_scale).to_i32_round())
|
||||
.collect::<Vec<_>>(),
|
||||
)?;
|
||||
|
||||
// Then re-draw all windows & layers overlapping with a damage rect.
|
||||
|
||||
for element in layer_map
|
||||
.layers_on(WlrLayer::Background)
|
||||
.chain(layer_map.layers_on(WlrLayer::Bottom))
|
||||
.map(|l| l as &SpaceElem<R>)
|
||||
.chain(self.windows.iter().map(|w| w as &SpaceElem<R>))
|
||||
.chain(
|
||||
layer_map
|
||||
.layers_on(WlrLayer::Top)
|
||||
.chain(layer_map.layers_on(WlrLayer::Overlay))
|
||||
.map(|l| l as &SpaceElem<R>),
|
||||
)
|
||||
.chain(custom_elements.iter().map(|c| c as &SpaceElem<R>))
|
||||
{
|
||||
let geo = element.geometry(self.id);
|
||||
if damage.iter().any(|d| d.overlaps(geo)) {
|
||||
let loc = element.location(self.id) - output_geo.loc;
|
||||
let damage = damage
|
||||
.iter()
|
||||
.flat_map(|d| d.intersection(geo))
|
||||
.map(|geo| Rectangle::from_loc_and_size(geo.loc - loc, geo.size))
|
||||
.collect::<Vec<_>>();
|
||||
slog::trace!(
|
||||
self.logger,
|
||||
"Rendering toplevel at {:?} with damage {:#?}",
|
||||
geo,
|
||||
damage
|
||||
);
|
||||
element.draw(
|
||||
self.id,
|
||||
renderer,
|
||||
frame,
|
||||
state.render_scale,
|
||||
loc,
|
||||
&damage,
|
||||
&self.logger,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Result::<(), R::Error>::Ok(())
|
||||
},
|
||||
);
|
||||
|
||||
if let Err(err) = res {
|
||||
// if the rendering errors on us, we need to be prepared, that this whole buffer was partially updated and thus now unusable.
|
||||
// thus clean our old states before returning
|
||||
state.old_damage = VecDeque::new();
|
||||
state.last_state = IndexMap::new();
|
||||
return Err(RenderError::Rendering(err));
|
||||
}
|
||||
|
||||
// If rendering was successful capture the state and add the damage
|
||||
state.last_state = self
|
||||
.windows
|
||||
.iter()
|
||||
.map(|w| w as &SpaceElem<R>)
|
||||
.chain(layer_map.layers().map(|l| l as &SpaceElem<R>))
|
||||
.chain(custom_elements.iter().map(|c| c as &SpaceElem<R>))
|
||||
.map(|elem| {
|
||||
let geo = elem.geometry(self.id);
|
||||
(ToplevelId::from(elem), geo)
|
||||
})
|
||||
.collect();
|
||||
state.old_damage.push_front(new_damage.clone());
|
||||
|
||||
Ok(Some(new_damage))
|
||||
}
|
||||
|
||||
/// Sends the frame callback to mapped [`Window`]s and [`LayerSurface`]s.
|
||||
///
|
||||
/// If `all` is set this will be send to `all` mapped surfaces.
|
||||
/// Otherwise only windows and layers previously drawn during the
|
||||
/// previous frame will be send frame events.
|
||||
pub fn send_frames(&self, all: bool, time: u32) {
|
||||
for window in self.windows.iter().filter(|w| {
|
||||
all || {
|
||||
let mut state = window_state(self.id, w);
|
||||
std::mem::replace(&mut state.drawn, false)
|
||||
}
|
||||
}) {
|
||||
window.send_frame(time);
|
||||
}
|
||||
|
||||
for output in self.outputs.iter() {
|
||||
let map = layer_map_for_output(output);
|
||||
for layer in map.layers().filter(|l| {
|
||||
all || {
|
||||
let mut state = layer_state(self.id, l);
|
||||
std::mem::replace(&mut state.drawn, false)
|
||||
}
|
||||
}) {
|
||||
layer.send_frame(time);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors thrown by [`Space::render_output`]
|
||||
#[derive(thiserror::Error)]
|
||||
pub enum RenderError<R: Renderer> {
|
||||
/// The provided [`Renderer`] did return an error during an operation
|
||||
#[error(transparent)]
|
||||
Rendering(R::Error),
|
||||
/// The given [`Output`] has no set mode
|
||||
#[error("Output has no active mode")]
|
||||
OutputNoMode,
|
||||
/// The given [`Output`] is not mapped to this [`Space`].
|
||||
#[error("Output was not mapped to this space")]
|
||||
UnmappedOutput,
|
||||
}
|
||||
|
||||
impl<R: Renderer> fmt::Debug for RenderError<R> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
RenderError::Rendering(err) => fmt::Debug::fmt(err, f),
|
||||
RenderError::OutputNoMode => f.write_str("Output has no active move"),
|
||||
RenderError::UnmappedOutput => f.write_str("Output was not mapped to this space"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
use crate::{
|
||||
backend::renderer::{Frame, ImportAll, Renderer, Texture},
|
||||
desktop::space::SpaceElement,
|
||||
utils::{Logical, Point, Rectangle},
|
||||
wayland::output::Output,
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
use wayland_server::protocol::wl_surface::WlSurface;
|
||||
|
||||
use std::{
|
||||
any::TypeId,
|
||||
cell::{RefCell, RefMut},
|
||||
collections::{HashMap, VecDeque},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct ToplevelId {
|
||||
t_id: TypeId,
|
||||
id: usize,
|
||||
}
|
||||
|
||||
impl<R, F, E, T> From<&dyn SpaceElement<R, F, E, T>> for ToplevelId
|
||||
where
|
||||
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportAll + 'static,
|
||||
F: Frame<Error = E, TextureId = T> + 'static,
|
||||
E: std::error::Error + 'static,
|
||||
T: Texture + 'static,
|
||||
{
|
||||
fn from(elem: &dyn SpaceElement<R, F, E, T>) -> ToplevelId {
|
||||
ToplevelId {
|
||||
t_id: elem.type_of(),
|
||||
id: elem.id(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct OutputState {
|
||||
pub location: Point<i32, Logical>,
|
||||
pub render_scale: f64,
|
||||
|
||||
// damage and last_state are in space coordinate space
|
||||
pub old_damage: VecDeque<Vec<Rectangle<i32, Logical>>>,
|
||||
pub last_state: IndexMap<ToplevelId, Rectangle<i32, Logical>>,
|
||||
|
||||
// surfaces for tracking enter and leave events
|
||||
pub surfaces: Vec<WlSurface>,
|
||||
}
|
||||
|
||||
pub type OutputUserdata = RefCell<HashMap<usize, OutputState>>;
|
||||
pub fn output_state(space: usize, o: &Output) -> RefMut<'_, OutputState> {
|
||||
let userdata = o.user_data();
|
||||
userdata.insert_if_missing(OutputUserdata::default);
|
||||
RefMut::map(userdata.get::<OutputUserdata>().unwrap().borrow_mut(), |m| {
|
||||
m.entry(space).or_default()
|
||||
})
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
use crate::{
|
||||
backend::renderer::{Frame, ImportAll, Renderer, Texture},
|
||||
desktop::{
|
||||
space::{Space, SpaceElement},
|
||||
window::{draw_window, Window},
|
||||
},
|
||||
utils::{Logical, Point, Rectangle},
|
||||
wayland::output::Output,
|
||||
};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
cell::{RefCell, RefMut},
|
||||
collections::HashMap,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WindowState {
|
||||
pub location: Point<i32, Logical>,
|
||||
pub drawn: bool,
|
||||
}
|
||||
|
||||
pub type WindowUserdata = RefCell<HashMap<usize, WindowState>>;
|
||||
pub fn window_state(space: usize, w: &Window) -> RefMut<'_, WindowState> {
|
||||
let userdata = w.user_data();
|
||||
userdata.insert_if_missing(WindowUserdata::default);
|
||||
RefMut::map(userdata.get::<WindowUserdata>().unwrap().borrow_mut(), |m| {
|
||||
m.entry(space).or_default()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn window_geo(window: &Window, space_id: &usize) -> Rectangle<i32, Logical> {
|
||||
let loc = window_loc(window, space_id);
|
||||
let mut wgeo = window.geometry();
|
||||
wgeo.loc = loc;
|
||||
wgeo
|
||||
}
|
||||
|
||||
pub fn window_rect(window: &Window, space_id: &usize) -> Rectangle<i32, Logical> {
|
||||
let loc = window_loc(window, space_id);
|
||||
let mut wgeo = window.bbox();
|
||||
wgeo.loc += loc;
|
||||
wgeo
|
||||
}
|
||||
|
||||
pub fn window_rect_with_popups(window: &Window, space_id: &usize) -> Rectangle<i32, Logical> {
|
||||
let loc = window_loc(window, space_id);
|
||||
let mut wgeo = window.bbox_with_popups();
|
||||
wgeo.loc += loc;
|
||||
wgeo
|
||||
}
|
||||
|
||||
pub fn window_loc(window: &Window, space_id: &usize) -> Point<i32, Logical> {
|
||||
window
|
||||
.user_data()
|
||||
.get::<RefCell<HashMap<usize, WindowState>>>()
|
||||
.unwrap()
|
||||
.borrow()
|
||||
.get(space_id)
|
||||
.unwrap()
|
||||
.location
|
||||
}
|
||||
|
||||
impl<R, F, E, T> SpaceElement<R, F, E, T> for Window
|
||||
where
|
||||
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportAll,
|
||||
F: Frame<Error = E, TextureId = T>,
|
||||
E: std::error::Error,
|
||||
T: Texture + 'static,
|
||||
{
|
||||
fn id(&self) -> usize {
|
||||
self.0.id
|
||||
}
|
||||
|
||||
fn type_of(&self) -> TypeId {
|
||||
TypeId::of::<Window>()
|
||||
}
|
||||
|
||||
fn location(&self, space_id: usize) -> Point<i32, Logical> {
|
||||
window_loc(self, &space_id)
|
||||
}
|
||||
|
||||
fn geometry(&self, space_id: usize) -> Rectangle<i32, Logical> {
|
||||
window_rect_with_popups(self, &space_id)
|
||||
}
|
||||
|
||||
fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec<Rectangle<i32, Logical>> {
|
||||
self.accumulated_damage(for_values)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
space_id: usize,
|
||||
renderer: &mut R,
|
||||
frame: &mut F,
|
||||
scale: f64,
|
||||
location: Point<i32, Logical>,
|
||||
damage: &[Rectangle<i32, Logical>],
|
||||
log: &slog::Logger,
|
||||
) -> Result<(), R::Error> {
|
||||
let res = draw_window(renderer, frame, self, scale, location, damage, log);
|
||||
if res.is_ok() {
|
||||
window_state(space_id, self).drawn = true;
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
//! Helper functions to ease dealing with surface trees
|
||||
|
||||
use crate::{
|
||||
backend::renderer::utils::SurfaceState,
|
||||
desktop::Space,
|
||||
utils::{Logical, Point, Rectangle, Size},
|
||||
wayland::{
|
||||
compositor::{
|
||||
with_surface_tree_downward, with_surface_tree_upward, Damage, SubsurfaceCachedState,
|
||||
SurfaceAttributes, TraversalAction,
|
||||
},
|
||||
output::Output,
|
||||
},
|
||||
};
|
||||
use wayland_server::protocol::wl_surface;
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
impl SurfaceState {
|
||||
/// Returns the size of the surface.
|
||||
pub fn size(&self) -> Option<Size<i32, Logical>> {
|
||||
self.buffer_dimensions
|
||||
.map(|dims| dims.to_logical(self.buffer_scale))
|
||||
}
|
||||
|
||||
fn contains_point<P: Into<Point<f64, Logical>>>(&self, attrs: &SurfaceAttributes, point: P) -> bool {
|
||||
let point = point.into();
|
||||
let size = match self.size() {
|
||||
None => return false, // If the surface has no size, it can't have an input region.
|
||||
Some(size) => size,
|
||||
};
|
||||
|
||||
let rect = Rectangle {
|
||||
loc: (0, 0).into(),
|
||||
size,
|
||||
}
|
||||
.to_f64();
|
||||
|
||||
// The input region is always within the surface itself, so if the surface itself doesn't contain the
|
||||
// point we can return false.
|
||||
if !rect.contains(point) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there's no input region, we're done.
|
||||
if attrs.input_region.is_none() {
|
||||
return true;
|
||||
}
|
||||
|
||||
attrs
|
||||
.input_region
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.contains(point.to_i32_round())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the bounding box of a given surface and all its subsurfaces.
|
||||
///
|
||||
/// - `location` can be set to offset the returned bounding box.
|
||||
pub fn bbox_from_surface_tree<P>(surface: &wl_surface::WlSurface, location: P) -> Rectangle<i32, Logical>
|
||||
where
|
||||
P: Into<Point<i32, Logical>>,
|
||||
{
|
||||
let location = location.into();
|
||||
let mut bounding_box = Rectangle::from_loc_and_size(location, (0, 0));
|
||||
with_surface_tree_downward(
|
||||
surface,
|
||||
location,
|
||||
|_, states, loc: &Point<i32, Logical>| {
|
||||
let mut loc = *loc;
|
||||
let data = states.data_map.get::<RefCell<SurfaceState>>();
|
||||
|
||||
if let Some(size) = data.and_then(|d| d.borrow().size()) {
|
||||
if states.role == Some("subsurface") {
|
||||
let current = states.cached_state.current::<SubsurfaceCachedState>();
|
||||
loc += current.location;
|
||||
}
|
||||
|
||||
// Update the bounding box.
|
||||
bounding_box = bounding_box.merge(Rectangle::from_loc_and_size(loc, size));
|
||||
|
||||
TraversalAction::DoChildren(loc)
|
||||
} else {
|
||||
// If the parent surface is unmapped, then the child surfaces are hidden as
|
||||
// well, no need to consider them here.
|
||||
TraversalAction::SkipChildren
|
||||
}
|
||||
},
|
||||
|_, _, _| {},
|
||||
|_, _, _| true,
|
||||
);
|
||||
bounding_box
|
||||
}
|
||||
|
||||
/// Returns the damage rectangles of the current buffer for a given surface and its subsurfaces.
|
||||
///
|
||||
/// - `location` can be set to offset the returned bounding box.
|
||||
/// - if a `key` is set the damage is only returned on the first call with the given key values.
|
||||
/// Subsequent calls will return an empty vector until the buffer is updated again and new
|
||||
/// damage values may be retrieved.
|
||||
pub fn damage_from_surface_tree<P>(
|
||||
surface: &wl_surface::WlSurface,
|
||||
location: P,
|
||||
key: Option<(&Space, &Output)>,
|
||||
) -> Vec<Rectangle<i32, Logical>>
|
||||
where
|
||||
P: Into<Point<i32, Logical>>,
|
||||
{
|
||||
use super::space::SpaceOutputTuple;
|
||||
|
||||
let mut damage = Vec::new();
|
||||
let key = key.map(|x| SpaceOutputTuple::from(x).owned_hash());
|
||||
with_surface_tree_upward(
|
||||
surface,
|
||||
location.into(),
|
||||
|_surface, states, location| {
|
||||
let mut location = *location;
|
||||
if let Some(data) = states.data_map.get::<RefCell<SurfaceState>>() {
|
||||
let data = data.borrow();
|
||||
if key
|
||||
.as_ref()
|
||||
.map(|key| !data.damage_seen.contains(key))
|
||||
.unwrap_or(true)
|
||||
&& states.role == Some("subsurface")
|
||||
{
|
||||
let current = states.cached_state.current::<SubsurfaceCachedState>();
|
||||
location += current.location;
|
||||
}
|
||||
}
|
||||
TraversalAction::DoChildren(location)
|
||||
},
|
||||
|_surface, states, location| {
|
||||
let mut location = *location;
|
||||
if let Some(data) = states.data_map.get::<RefCell<SurfaceState>>() {
|
||||
let mut data = data.borrow_mut();
|
||||
let attributes = states.cached_state.current::<SurfaceAttributes>();
|
||||
|
||||
if key
|
||||
.as_ref()
|
||||
.map(|key| !data.damage_seen.contains(key))
|
||||
.unwrap_or(true)
|
||||
{
|
||||
if states.role == Some("subsurface") {
|
||||
let current = states.cached_state.current::<SubsurfaceCachedState>();
|
||||
location += current.location;
|
||||
}
|
||||
|
||||
damage.extend(attributes.damage.iter().map(|dmg| {
|
||||
let mut rect = match dmg {
|
||||
Damage::Buffer(rect) => rect.to_logical(attributes.buffer_scale),
|
||||
Damage::Surface(rect) => *rect,
|
||||
};
|
||||
rect.loc += location;
|
||||
rect
|
||||
}));
|
||||
|
||||
if let Some(key) = key {
|
||||
data.damage_seen.insert(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|_, _, _| true,
|
||||
);
|
||||
damage
|
||||
}
|
||||
|
||||
/// Returns the (sub-)surface under a given position given a surface, if any.
|
||||
///
|
||||
/// - `point` has to be the position to query, relative to (0, 0) of the given surface + `location`.
|
||||
/// - `location` can be used to offset the returned point.
|
||||
pub fn under_from_surface_tree<P>(
|
||||
surface: &wl_surface::WlSurface,
|
||||
point: Point<f64, Logical>,
|
||||
location: P,
|
||||
) -> Option<(wl_surface::WlSurface, Point<i32, Logical>)>
|
||||
where
|
||||
P: Into<Point<i32, Logical>>,
|
||||
{
|
||||
let found = RefCell::new(None);
|
||||
with_surface_tree_downward(
|
||||
surface,
|
||||
location.into(),
|
||||
|wl_surface, states, location: &Point<i32, Logical>| {
|
||||
let mut location = *location;
|
||||
let data = states.data_map.get::<RefCell<SurfaceState>>();
|
||||
|
||||
if states.role == Some("subsurface") {
|
||||
let current = states.cached_state.current::<SubsurfaceCachedState>();
|
||||
location += current.location;
|
||||
}
|
||||
|
||||
let contains_the_point = data
|
||||
.map(|data| {
|
||||
data.borrow()
|
||||
.contains_point(&*states.cached_state.current(), point - location.to_f64())
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if contains_the_point {
|
||||
*found.borrow_mut() = Some((wl_surface.clone(), location));
|
||||
}
|
||||
|
||||
TraversalAction::DoChildren(location)
|
||||
},
|
||||
|_, _, _| {},
|
||||
|_, _, _| {
|
||||
// only continue if the point is not found
|
||||
found.borrow().is_none()
|
||||
},
|
||||
);
|
||||
found.into_inner()
|
||||
}
|
||||
|
||||
/// Sends frame callbacks for a surface and its subsurfaces with the given `time`.
|
||||
pub fn send_frames_surface_tree(surface: &wl_surface::WlSurface, time: u32) {
|
||||
with_surface_tree_downward(
|
||||
surface,
|
||||
(),
|
||||
|_, _, &()| TraversalAction::DoChildren(()),
|
||||
|_surf, states, &()| {
|
||||
// the surface may not have any user_data if it is a subsurface and has not
|
||||
// yet been commited
|
||||
for callback in states
|
||||
.cached_state
|
||||
.current::<SurfaceAttributes>()
|
||||
.frame_callbacks
|
||||
.drain(..)
|
||||
{
|
||||
callback.done(time);
|
||||
}
|
||||
},
|
||||
|_, _, &()| true,
|
||||
);
|
||||
}
|
|
@ -0,0 +1,336 @@
|
|||
use crate::{
|
||||
backend::renderer::{utils::draw_surface_tree, Frame, ImportAll, Renderer, Texture},
|
||||
desktop::{utils::*, PopupManager, Space},
|
||||
utils::{Logical, Point, Rectangle},
|
||||
wayland::{
|
||||
compositor::with_states,
|
||||
output::Output,
|
||||
shell::xdg::{SurfaceCachedState, ToplevelSurface},
|
||||
},
|
||||
};
|
||||
use std::{
|
||||
cell::Cell,
|
||||
hash::{Hash, Hasher},
|
||||
rc::Rc,
|
||||
};
|
||||
use wayland_commons::user_data::UserDataMap;
|
||||
use wayland_protocols::xdg_shell::server::xdg_toplevel;
|
||||
use wayland_server::protocol::wl_surface;
|
||||
|
||||
crate::utils::ids::id_gen!(next_window_id, WINDOW_ID, WINDOW_IDS);
|
||||
|
||||
/// Abstraction around different toplevel kinds
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Kind {
|
||||
/// xdg-shell [`ToplevelSurface`]
|
||||
Xdg(ToplevelSurface),
|
||||
/// XWayland surface (TODO)
|
||||
#[cfg(feature = "xwayland")]
|
||||
X11(X11Surface),
|
||||
}
|
||||
|
||||
/// TODO
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct X11Surface {
|
||||
surface: wl_surface::WlSurface,
|
||||
}
|
||||
|
||||
impl std::cmp::PartialEq for X11Surface {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.alive() && other.alive() && self.surface == other.surface
|
||||
}
|
||||
}
|
||||
|
||||
impl X11Surface {
|
||||
/// Checks if the surface is still alive.
|
||||
pub fn alive(&self) -> bool {
|
||||
self.surface.as_ref().is_alive()
|
||||
}
|
||||
|
||||
/// Returns the underlying [`WlSurface`](wl_surface::WlSurface), if still any.
|
||||
pub fn get_surface(&self) -> Option<&wl_surface::WlSurface> {
|
||||
if self.alive() {
|
||||
Some(&self.surface)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Kind {
|
||||
/// Checks if the surface is still alive.
|
||||
pub fn alive(&self) -> bool {
|
||||
match *self {
|
||||
Kind::Xdg(ref t) => t.alive(),
|
||||
#[cfg(feature = "xwayland")]
|
||||
Kind::X11(ref t) => t.alive(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the underlying [`WlSurface`](wl_surface::WlSurface), if still any.
|
||||
pub fn get_surface(&self) -> Option<&wl_surface::WlSurface> {
|
||||
match *self {
|
||||
Kind::Xdg(ref t) => t.get_surface(),
|
||||
#[cfg(feature = "xwayland")]
|
||||
Kind::X11(ref t) => t.get_surface(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct WindowInner {
|
||||
pub(super) id: usize,
|
||||
toplevel: Kind,
|
||||
bbox: Cell<Rectangle<i32, Logical>>,
|
||||
user_data: UserDataMap,
|
||||
}
|
||||
|
||||
impl Drop for WindowInner {
|
||||
fn drop(&mut self) {
|
||||
WINDOW_IDS.lock().unwrap().remove(&self.id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a single application window
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Window(pub(super) Rc<WindowInner>);
|
||||
|
||||
impl PartialEq for Window {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.id == other.0.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Window {}
|
||||
|
||||
impl Hash for Window {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
/// Construct a new [`Window`] from a given compatible toplevel surface
|
||||
pub fn new(toplevel: Kind) -> Window {
|
||||
let id = next_window_id();
|
||||
|
||||
Window(Rc::new(WindowInner {
|
||||
id,
|
||||
toplevel,
|
||||
bbox: Cell::new(Rectangle::from_loc_and_size((0, 0), (0, 0))),
|
||||
user_data: UserDataMap::new(),
|
||||
}))
|
||||
}
|
||||
|
||||
/// Returns the geometry of this window.
|
||||
pub fn geometry(&self) -> Rectangle<i32, Logical> {
|
||||
// It's the set geometry with the full bounding box as the fallback.
|
||||
with_states(self.0.toplevel.get_surface().unwrap(), |states| {
|
||||
states.cached_state.current::<SurfaceCachedState>().geometry
|
||||
})
|
||||
.unwrap()
|
||||
.unwrap_or_else(|| self.bbox())
|
||||
}
|
||||
|
||||
/// Returns a bounding box over this window and its children.
|
||||
pub fn bbox(&self) -> Rectangle<i32, Logical> {
|
||||
if self.0.toplevel.get_surface().is_some() {
|
||||
self.0.bbox.get()
|
||||
} else {
|
||||
Rectangle::from_loc_and_size((0, 0), (0, 0))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a bounding box over this window and children including popups.
|
||||
///
|
||||
/// Note: You need to use a [`PopupManager`] to track popups, otherwise the bounding box
|
||||
/// will not include the popups.
|
||||
pub fn bbox_with_popups(&self) -> Rectangle<i32, Logical> {
|
||||
let mut bounding_box = self.bbox();
|
||||
if let Some(surface) = self.0.toplevel.get_surface() {
|
||||
for (popup, location) in PopupManager::popups_for_surface(surface)
|
||||
.ok()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
if let Some(surface) = popup.get_surface() {
|
||||
let offset = self.geometry().loc + location - popup.geometry().loc;
|
||||
bounding_box = bounding_box.merge(bbox_from_surface_tree(surface, offset));
|
||||
}
|
||||
}
|
||||
}
|
||||
bounding_box
|
||||
}
|
||||
|
||||
/// Activate/Deactivate this window
|
||||
pub fn set_activated(&self, active: bool) -> bool {
|
||||
match self.0.toplevel {
|
||||
Kind::Xdg(ref t) => t
|
||||
.with_pending_state(|state| {
|
||||
if active {
|
||||
state.states.set(xdg_toplevel::State::Activated)
|
||||
} else {
|
||||
state.states.unset(xdg_toplevel::State::Activated)
|
||||
}
|
||||
})
|
||||
.unwrap_or(false),
|
||||
#[cfg(feature = "xwayland")]
|
||||
Kind::X11(ref _t) => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Commit any changes to this window
|
||||
pub fn configure(&self) {
|
||||
match self.0.toplevel {
|
||||
Kind::Xdg(ref t) => t.send_configure(),
|
||||
#[cfg(feature = "xwayland")]
|
||||
Kind::X11(ref _t) => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends the frame callback to all the subsurfaces in this
|
||||
/// window that requested it
|
||||
pub fn send_frame(&self, time: u32) {
|
||||
if let Some(surface) = self.0.toplevel.get_surface() {
|
||||
send_frames_surface_tree(surface, time);
|
||||
for (popup, _) in PopupManager::popups_for_surface(surface)
|
||||
.ok()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
if let Some(surface) = popup.get_surface() {
|
||||
send_frames_surface_tree(surface, time);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates internal values
|
||||
///
|
||||
/// Needs to be called whenever the toplevel surface or any unsynchronized subsurfaces of this window are updated
|
||||
/// to correctly update the bounding box of this window.
|
||||
pub fn refresh(&self) {
|
||||
if let Some(surface) = self.0.toplevel.get_surface() {
|
||||
self.0.bbox.set(bbox_from_surface_tree(surface, (0, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the topmost surface under this point if any and returns it together with the location of this
|
||||
/// surface.
|
||||
///
|
||||
/// - `point` should be relative to (0,0) of the window.
|
||||
pub fn surface_under<P: Into<Point<f64, Logical>>>(
|
||||
&self,
|
||||
point: P,
|
||||
) -> Option<(wl_surface::WlSurface, Point<i32, Logical>)> {
|
||||
let point = point.into();
|
||||
if let Some(surface) = self.0.toplevel.get_surface() {
|
||||
for (popup, location) in PopupManager::popups_for_surface(surface)
|
||||
.ok()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
let offset = self.geometry().loc + location - popup.geometry().loc;
|
||||
if let Some(result) = popup
|
||||
.get_surface()
|
||||
.and_then(|surface| under_from_surface_tree(surface, point, offset))
|
||||
{
|
||||
return Some(result);
|
||||
}
|
||||
}
|
||||
|
||||
under_from_surface_tree(surface, point, (0, 0))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Damage of all the surfaces of this window.
|
||||
///
|
||||
/// If `for_values` is `Some(_)` it will only return the damage on the
|
||||
/// first call for a given [`Space`] and [`Output`], if the buffer hasn't changed.
|
||||
/// Subsequent calls will return an empty vector until the buffer is updated again.
|
||||
pub fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec<Rectangle<i32, Logical>> {
|
||||
let mut damage = Vec::new();
|
||||
if let Some(surface) = self.0.toplevel.get_surface() {
|
||||
damage.extend(
|
||||
damage_from_surface_tree(surface, (0, 0), for_values)
|
||||
.into_iter()
|
||||
.flat_map(|rect| rect.intersection(self.bbox())),
|
||||
);
|
||||
for (popup, location) in PopupManager::popups_for_surface(surface)
|
||||
.ok()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
if let Some(surface) = popup.get_surface() {
|
||||
let offset = self.geometry().loc + location - popup.geometry().loc;
|
||||
let bbox = bbox_from_surface_tree(surface, offset);
|
||||
let popup_damage = damage_from_surface_tree(surface, offset, for_values);
|
||||
damage.extend(popup_damage.into_iter().flat_map(|rect| rect.intersection(bbox)));
|
||||
}
|
||||
}
|
||||
}
|
||||
damage
|
||||
}
|
||||
|
||||
/// Returns the underlying toplevel
|
||||
pub fn toplevel(&self) -> &Kind {
|
||||
&self.0.toplevel
|
||||
}
|
||||
|
||||
/// Returns a [`UserDataMap`] to allow associating arbitrary data with this window.
|
||||
pub fn user_data(&self) -> &UserDataMap {
|
||||
&self.0.user_data
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders a given [`Window`] using a provided renderer and frame.
|
||||
///
|
||||
/// - `scale` needs to be equivalent to the fractional scale the rendered result should have.
|
||||
/// - `location` is the position the window should be drawn at.
|
||||
/// - `damage` is the set of regions of the window that should be drawn.
|
||||
///
|
||||
/// Note: This function will render nothing, if you are not using
|
||||
/// [`crate::backend::renderer::utils::on_commit_buffer_handler`]
|
||||
/// to let smithay handle buffer management.
|
||||
pub fn draw_window<R, E, F, T, P>(
|
||||
renderer: &mut R,
|
||||
frame: &mut F,
|
||||
window: &Window,
|
||||
scale: f64,
|
||||
location: P,
|
||||
damage: &[Rectangle<i32, Logical>],
|
||||
log: &slog::Logger,
|
||||
) -> Result<(), R::Error>
|
||||
where
|
||||
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportAll,
|
||||
F: Frame<Error = E, TextureId = T>,
|
||||
E: std::error::Error,
|
||||
T: Texture + 'static,
|
||||
P: Into<Point<i32, Logical>>,
|
||||
{
|
||||
let location = location.into();
|
||||
if let Some(surface) = window.toplevel().get_surface() {
|
||||
draw_surface_tree(renderer, frame, surface, scale, location, damage, log)?;
|
||||
for (popup, p_location) in PopupManager::popups_for_surface(surface)
|
||||
.ok()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
if let Some(surface) = popup.get_surface() {
|
||||
let offset = window.geometry().loc + p_location - popup.geometry().loc;
|
||||
let damage = damage
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|mut geo| {
|
||||
geo.loc -= offset;
|
||||
geo
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
draw_surface_tree(renderer, frame, surface, scale, location + offset, &damage, log)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
|
@ -51,6 +51,8 @@
|
|||
pub extern crate nix;
|
||||
|
||||
pub mod backend;
|
||||
#[cfg(feature = "desktop")]
|
||||
pub mod desktop;
|
||||
pub mod utils;
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
pub mod wayland;
|
||||
|
|
|
@ -296,6 +296,23 @@ impl<N: Coordinate, Kind> Point<N, Kind> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<N: Coordinate, Kind> Point<N, Kind> {
|
||||
/// Constrain this [`Point`] within a [`Rectangle`] with the same coordinates
|
||||
///
|
||||
/// The [`Point`] returned is guaranteed to be not smaller than the [`Rectangle`]
|
||||
/// location and not greater than the [`Rectangle`] size.
|
||||
#[inline]
|
||||
pub fn constrain(self, rect: impl Into<Rectangle<N, Kind>>) -> Point<N, Kind> {
|
||||
let rect = rect.into();
|
||||
|
||||
Point {
|
||||
x: self.x.max(rect.loc.x).min(rect.size.w),
|
||||
y: self.y.max(rect.loc.y).min(rect.size.h),
|
||||
_kind: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Coordinate, Kind> Point<N, Kind> {
|
||||
/// Convert the underlying numerical type to f64 for floating point manipulations
|
||||
#[inline]
|
||||
|
@ -543,6 +560,20 @@ impl<N: Coordinate, Kind> Size<N, Kind> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<N: Coordinate, Kind> Size<N, Kind> {
|
||||
/// Restrict this [`Size`] to min and max [`Size`] with the same coordinates
|
||||
pub fn clamp(self, min: impl Into<Size<N, Kind>>, max: impl Into<Size<N, Kind>>) -> Size<N, Kind> {
|
||||
let min = min.into();
|
||||
let max = max.into();
|
||||
|
||||
Size {
|
||||
w: self.w.max(min.w).min(max.w),
|
||||
h: self.h.max(min.h).min(max.h),
|
||||
_kind: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Coordinate, Kind> Size<N, Kind> {
|
||||
/// Convert the underlying numerical type to f64 for floating point manipulations
|
||||
#[inline]
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
macro_rules! id_gen {
|
||||
($func_name:ident, $id_name:ident, $ids_name:ident) => {
|
||||
static $id_name: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
|
||||
lazy_static::lazy_static! {
|
||||
static ref $ids_name: std::sync::Mutex<std::collections::HashSet<usize>> =
|
||||
std::sync::Mutex::new(std::collections::HashSet::new());
|
||||
}
|
||||
|
||||
fn $func_name() -> usize {
|
||||
let mut ids = $ids_name.lock().unwrap();
|
||||
if ids.len() == usize::MAX {
|
||||
panic!("Out of ids");
|
||||
}
|
||||
|
||||
let id = loop {
|
||||
let new_id = $id_name.fetch_update(
|
||||
std::sync::atomic::Ordering::SeqCst,
|
||||
std::sync::atomic::Ordering::SeqCst,
|
||||
|mut id| {
|
||||
while ids.iter().any(|k| *k == id) {
|
||||
id += 1;
|
||||
}
|
||||
Some(id)
|
||||
},
|
||||
);
|
||||
if let Ok(id) = new_id {
|
||||
break id;
|
||||
}
|
||||
};
|
||||
|
||||
ids.insert(id);
|
||||
id
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use id_gen;
|
|
@ -6,6 +6,8 @@ pub mod signaling;
|
|||
#[cfg(feature = "x11rb_event_source")]
|
||||
pub mod x11rb;
|
||||
|
||||
#[cfg(feature = "desktop")]
|
||||
pub(crate) mod ids;
|
||||
pub mod user_data;
|
||||
|
||||
pub use self::geometry::{Buffer, Coordinate, Logical, Physical, Point, Raw, Rectangle, Size};
|
||||
|
|
|
@ -449,7 +449,7 @@ mod tests {
|
|||
#[test]
|
||||
fn region_attributes_empty() {
|
||||
let region = RegionAttributes { rects: vec![] };
|
||||
assert_eq!(region.contains((0, 0)), false);
|
||||
assert!(!region.contains((0, 0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -458,7 +458,7 @@ mod tests {
|
|||
rects: vec![(RectangleKind::Add, Rectangle::from_loc_and_size((0, 0), (10, 10)))],
|
||||
};
|
||||
|
||||
assert_eq!(region.contains((0, 0)), true);
|
||||
assert!(region.contains((0, 0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -473,8 +473,8 @@ mod tests {
|
|||
],
|
||||
};
|
||||
|
||||
assert_eq!(region.contains((0, 0)), false);
|
||||
assert_eq!(region.contains((5, 5)), true);
|
||||
assert!(!region.contains((0, 0)));
|
||||
assert!(region.contains((5, 5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -490,8 +490,8 @@ mod tests {
|
|||
],
|
||||
};
|
||||
|
||||
assert_eq!(region.contains((0, 0)), false);
|
||||
assert_eq!(region.contains((5, 5)), true);
|
||||
assert_eq!(region.contains((2, 2)), true);
|
||||
assert!(!region.contains((0, 0)));
|
||||
assert!(region.contains((5, 5)));
|
||||
assert!(region.contains((2, 2)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ use wayland_server::protocol::{
|
|||
};
|
||||
use wayland_server::{
|
||||
protocol::wl_output::{Mode as WMode, WlOutput},
|
||||
Client, Display, Filter, Global, Main,
|
||||
Client, Display, Filter, Global, Main, UserDataMap,
|
||||
};
|
||||
|
||||
use slog::{info, o, trace, warn};
|
||||
|
@ -100,7 +100,7 @@ pub struct PhysicalProperties {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Inner {
|
||||
pub(crate) struct Inner {
|
||||
name: String,
|
||||
log: ::slog::Logger,
|
||||
instances: Vec<WlOutput>,
|
||||
|
@ -115,6 +115,8 @@ struct Inner {
|
|||
xdg_output: Option<XdgOutput>,
|
||||
}
|
||||
|
||||
type InnerType = Arc<(Mutex<Inner>, UserDataMap)>;
|
||||
|
||||
impl Inner {
|
||||
fn new_global(&mut self, output: WlOutput) {
|
||||
trace!(self.log, "New global instantiated.");
|
||||
|
@ -166,9 +168,9 @@ impl Inner {
|
|||
///
|
||||
/// This handle is stored in the event loop, and allows you to notify clients
|
||||
/// about any change in the properties of this output.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Output {
|
||||
inner: Arc<Mutex<Inner>>,
|
||||
pub(crate) inner: InnerType,
|
||||
}
|
||||
|
||||
impl Output {
|
||||
|
@ -190,7 +192,8 @@ impl Output {
|
|||
|
||||
info!(log, "Creating new wl_output"; "name" => &name);
|
||||
|
||||
let inner = Arc::new(Mutex::new(Inner {
|
||||
let inner = Arc::new((
|
||||
Mutex::new(Inner {
|
||||
name,
|
||||
log,
|
||||
instances: Vec::new(),
|
||||
|
@ -202,7 +205,9 @@ impl Output {
|
|||
current_mode: None,
|
||||
preferred_mode: None,
|
||||
xdg_output: None,
|
||||
}));
|
||||
}),
|
||||
UserDataMap::default(),
|
||||
));
|
||||
|
||||
let output = Output { inner: inner.clone() };
|
||||
|
||||
|
@ -210,8 +215,9 @@ impl Output {
|
|||
3,
|
||||
Filter::new(move |(output, _version): (Main<WlOutput>, _), _, _| {
|
||||
output.assign_destructor(Filter::new(|output: WlOutput, _, _| {
|
||||
let inner = output.as_ref().user_data().get::<Arc<Mutex<Inner>>>().unwrap();
|
||||
let inner = output.as_ref().user_data().get::<InnerType>().unwrap();
|
||||
inner
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.instances
|
||||
|
@ -221,7 +227,7 @@ impl Output {
|
|||
let inner = inner.clone();
|
||||
move || inner
|
||||
});
|
||||
inner.lock().unwrap().new_global(output.deref().clone());
|
||||
inner.0.lock().unwrap().new_global(output.deref().clone());
|
||||
}),
|
||||
);
|
||||
|
||||
|
@ -233,7 +239,7 @@ impl Output {
|
|||
output
|
||||
.as_ref()
|
||||
.user_data()
|
||||
.get::<Arc<Mutex<Inner>>>()
|
||||
.get::<InnerType>()
|
||||
.cloned()
|
||||
.map(|inner| Output { inner })
|
||||
}
|
||||
|
@ -243,7 +249,7 @@ impl Output {
|
|||
/// If the provided mode was not previously known to this output, it is added to its
|
||||
/// internal list.
|
||||
pub fn set_preferred(&self, mode: Mode) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
let mut inner = self.inner.0.lock().unwrap();
|
||||
inner.preferred_mode = Some(mode);
|
||||
if inner.modes.iter().all(|&m| m != mode) {
|
||||
inner.modes.push(mode);
|
||||
|
@ -252,18 +258,38 @@ impl Output {
|
|||
|
||||
/// Adds a mode to the list of known modes to this output
|
||||
pub fn add_mode(&self, mode: Mode) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
let mut inner = self.inner.0.lock().unwrap();
|
||||
if inner.modes.iter().all(|&m| m != mode) {
|
||||
inner.modes.push(mode);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the currently advertised mode of the output
|
||||
pub fn current_mode(&self) -> Option<Mode> {
|
||||
self.inner.0.lock().unwrap().current_mode
|
||||
}
|
||||
|
||||
/// Returns the currently advertised transformation of the output
|
||||
pub fn current_transform(&self) -> Transform {
|
||||
self.inner.0.lock().unwrap().transform
|
||||
}
|
||||
|
||||
/// Returns the currenly advertised scale of the output
|
||||
pub fn current_scale(&self) -> i32 {
|
||||
self.inner.0.lock().unwrap().scale
|
||||
}
|
||||
|
||||
/// Returns the name of the output
|
||||
pub fn name(&self) -> String {
|
||||
self.inner.0.lock().unwrap().name.clone()
|
||||
}
|
||||
|
||||
/// Removes a mode from the list of known modes
|
||||
///
|
||||
/// It will not de-advertise it from existing clients (the protocol does not
|
||||
/// allow it), but it won't be advertised to now clients from now on.
|
||||
pub fn delete_mode(&self, mode: Mode) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
let mut inner = self.inner.0.lock().unwrap();
|
||||
inner.modes.retain(|&m| m != mode);
|
||||
if inner.current_mode == Some(mode) {
|
||||
inner.current_mode = None;
|
||||
|
@ -289,7 +315,7 @@ impl Output {
|
|||
new_scale: Option<i32>,
|
||||
new_location: Option<Point<i32, Logical>>,
|
||||
) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
let mut inner = self.inner.0.lock().unwrap();
|
||||
if let Some(mode) = new_mode {
|
||||
if inner.modes.iter().all(|&m| m != mode) {
|
||||
inner.modes.push(mode);
|
||||
|
@ -337,6 +363,7 @@ impl Output {
|
|||
/// Check is given [`wl_output`](WlOutput) instance is managed by this [`Output`].
|
||||
pub fn owns(&self, output: &WlOutput) -> bool {
|
||||
self.inner
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.instances
|
||||
|
@ -351,6 +378,7 @@ impl Output {
|
|||
F: FnMut(&WlOutput),
|
||||
{
|
||||
self.inner
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.instances
|
||||
|
@ -377,4 +405,17 @@ impl Output {
|
|||
self.with_client_outputs(client, |output| surface.leave(output))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the user data of this output
|
||||
pub fn user_data(&self) -> &UserDataMap {
|
||||
&self.inner.1
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Output {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Arc::ptr_eq(&self.inner, &other.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Output {}
|
||||
|
|
|
@ -159,7 +159,7 @@ where
|
|||
output: wl_output,
|
||||
} => {
|
||||
let output = Output::from_resource(&wl_output).unwrap();
|
||||
let mut inner = output.inner.lock().unwrap();
|
||||
let mut inner = output.inner.0.lock().unwrap();
|
||||
|
||||
if inner.xdg_output.is_none() {
|
||||
inner.xdg_output = Some(XdgOutput::new(&inner, log.clone()));
|
||||
|
|
|
@ -469,6 +469,11 @@ impl KeyboardHandle {
|
|||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Check if keyboard has focus
|
||||
pub fn is_focused(&self) -> bool {
|
||||
self.arc.internal.borrow_mut().focus.is_some()
|
||||
}
|
||||
|
||||
/// Register a new keyboard to this handler
|
||||
///
|
||||
/// The keymap will automatically be sent to it
|
||||
|
|
|
@ -50,7 +50,7 @@ impl Default for Layer {
|
|||
/// - some applications are not interested in keyboard events
|
||||
/// and not allowing them to be focused can improve the desktop experience
|
||||
/// - some applications will want to take exclusive keyboard focus.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum KeyboardInteractivity {
|
||||
/// This value indicates that this surface is not interested in keyboard events
|
||||
/// and the compositor should never assign it the keyboard focus.
|
||||
|
|
|
@ -18,6 +18,7 @@ use smithay::{
|
|||
Client, Display,
|
||||
},
|
||||
},
|
||||
utils::Rectangle,
|
||||
wayland::{
|
||||
output::{Mode, PhysicalProperties},
|
||||
seat::CursorImageStatus,
|
||||
|
@ -103,7 +104,10 @@ pub fn run(channel: Channel<WlcsEvent>) {
|
|||
|
||||
renderer
|
||||
.render((800, 600).into(), Transform::Normal, |renderer, frame| {
|
||||
frame.clear([0.8, 0.8, 0.9, 1.0])?;
|
||||
frame.clear(
|
||||
[0.8, 0.8, 0.9, 1.0],
|
||||
&[Rectangle::from_loc_and_size((0, 0), (800, 600))],
|
||||
)?;
|
||||
|
||||
// draw the windows
|
||||
draw_windows(
|
||||
|
|
|
@ -97,7 +97,7 @@ impl Frame for DummyFrame {
|
|||
type Error = SwapBuffersError;
|
||||
type TextureId = DummyTexture;
|
||||
|
||||
fn clear(&mut self, _color: [f32; 4]) -> Result<(), Self::Error> {
|
||||
fn clear(&mut self, _color: [f32; 4], _damage: &[Rectangle<i32, Physical>]) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -106,11 +106,16 @@ impl Frame for DummyFrame {
|
|||
_texture: &Self::TextureId,
|
||||
_src: Rectangle<i32, Buffer>,
|
||||
_dst: Rectangle<f64, Physical>,
|
||||
_damage: &[Rectangle<i32, Physical>],
|
||||
_src_transform: Transform,
|
||||
_alpha: f32,
|
||||
) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transformation(&self) -> Transform {
|
||||
Transform::Normal
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DummyTexture {
|
||||
|
|
Loading…
Reference in New Issue