diff --git a/Cargo.lock b/Cargo.lock index 39f9d03b..da112559 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -309,7 +309,7 @@ dependencies = [ "smithay-egui", "thiserror", "wayland-backend", - "wayland-scanner 0.30.0-beta.12", + "wayland-scanner 0.30.0-beta.12 (registry+https://github.com/rust-lang/crates.io-index)", "xcursor", "xdg", "xkbcommon 0.4.1", @@ -323,7 +323,7 @@ dependencies = [ "bitflags", "wayland-backend", "wayland-protocols 0.30.0-beta.12", - "wayland-scanner 0.30.0-beta.12", + "wayland-scanner 0.30.0-beta.12 (registry+https://github.com/rust-lang/crates.io-index)", "wayland-server", ] @@ -1503,7 +1503,6 @@ checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "smithay" version = "0.3.0" -source = "git+https://github.com/Smithay//smithay?rev=0c7dbfa8#0c7dbfa830496df064a721f9cbbff6b474e6a268" dependencies = [ "appendlist", "bitflags", @@ -1807,8 +1806,7 @@ checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "wayland-backend" version = "0.1.0-beta.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fe32234ad38fee0755aeec26db486a4b788dd3cc2c9dc86ceda31bb38a53f32" +source = "git+https://github.com/Smithay/wayland-rs?rev=13f6a9be#13f6a9beb05eabac56aa372e1d38b12bfa987982" dependencies = [ "cc", "downcast-rs", @@ -1861,8 +1859,7 @@ dependencies = [ [[package]] name = "wayland-egl" version = "0.30.0-beta.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc1309ddfeabd942c09c21e9db2ed3de81b84d0ea7ebcdd2e503a0be0fe9c5f" +source = "git+https://github.com/Smithay/wayland-rs?rev=13f6a9be#13f6a9beb05eabac56aa372e1d38b12bfa987982" dependencies = [ "thiserror", "wayland-backend", @@ -1889,7 +1886,7 @@ checksum = "ca770dc814b3c93db1b4ba12a5bdfe899f8d68f9b4b06fc31e53959261cd0c39" dependencies = [ "bitflags", "wayland-backend", - "wayland-scanner 0.30.0-beta.12", + "wayland-scanner 0.30.0-beta.12 (registry+https://github.com/rust-lang/crates.io-index)", "wayland-server", ] @@ -1902,7 +1899,7 @@ dependencies = [ "bitflags", "wayland-backend", "wayland-protocols 0.30.0-beta.12", - "wayland-scanner 0.30.0-beta.12", + "wayland-scanner 0.30.0-beta.12 (registry+https://github.com/rust-lang/crates.io-index)", "wayland-server", ] @@ -1915,7 +1912,7 @@ dependencies = [ "bitflags", "wayland-backend", "wayland-protocols 0.30.0-beta.12", - "wayland-scanner 0.30.0-beta.12", + "wayland-scanner 0.30.0-beta.12 (registry+https://github.com/rust-lang/crates.io-index)", "wayland-server", ] @@ -1942,18 +1939,28 @@ dependencies = [ "syn", ] +[[package]] +name = "wayland-scanner" +version = "0.30.0-beta.12" +source = "git+https://github.com/Smithay/wayland-rs?rev=13f6a9be#13f6a9beb05eabac56aa372e1d38b12bfa987982" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", + "syn", +] + [[package]] name = "wayland-server" version = "0.30.0-beta.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d54b4a800b230f4fb0f42cae245cc0aaa02bf14bf7c1c3a5e1a822d05fd2cb3" +source = "git+https://github.com/Smithay/wayland-rs?rev=13f6a9be#13f6a9beb05eabac56aa372e1d38b12bfa987982" dependencies = [ "bitflags", "downcast-rs", "nix 0.25.0", "thiserror", "wayland-backend", - "wayland-scanner 0.30.0-beta.12", + "wayland-scanner 0.30.0-beta.12 (git+https://github.com/Smithay/wayland-rs?rev=13f6a9be)", ] [[package]] @@ -1970,8 +1977,7 @@ dependencies = [ [[package]] name = "wayland-sys" version = "0.30.0-beta.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1117fe4570fe063122ba2b1b1e39e56fb1a73921d395f9288af06af0dd1c7f55" +source = "git+https://github.com/Smithay/wayland-rs?rev=13f6a9be#13f6a9beb05eabac56aa372e1d38b12bfa987982" dependencies = [ "dlib", "libc", diff --git a/Cargo.toml b/Cargo.toml index 71f4ce9e..49de6c29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,4 +61,11 @@ debug = true lto = "fat" [patch."https://github.com/Smithay/smithay.git"] -smithay = { git = "https://github.com/Smithay//smithay", rev = "0c7dbfa8" } +#smithay = { git = "https://github.com/Smithay//smithay", rev = "5671502cd" } +smithay = { path = "../smithay" } + +[patch.crates-io] +wayland-server = { git = "https://github.com/Smithay/wayland-rs", rev = "13f6a9be" } +wayland-backend = { git = "https://github.com/Smithay/wayland-rs", rev = "13f6a9be" } +wayland-egl = { git = "https://github.com/Smithay/wayland-rs", rev = "13f6a9be" } +wayland-sys = { git = "https://github.com/Smithay/wayland-rs", rev = "13f6a9be" } diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index bdb69036..7081ad1a 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -9,18 +9,23 @@ use crate::{ shell::Shell, state::{BackendData, ClientState, Common, Data}, utils::prelude::*, + wayland::{ + handlers::screencopy::UserdataExt, + protocols::screencopy::{BufferParams, Session as ScreencopySession}, + }, }; use anyhow::{Context, Result}; use smithay::{ backend::{ allocator::{dmabuf::Dmabuf, gbm::GbmDevice, Format}, - drm::{DrmDevice, DrmEvent, DrmEventTime, DrmNode, GbmBufferedSurface, NodeType}, + drm::{DrmDevice, DrmEvent, DrmNode, GbmBufferedSurface, NodeType}, egl::{EGLContext, EGLDevice, EGLDisplay}, input::InputEvent, libinput::{LibinputInputBackend, LibinputSessionInterface}, renderer::{ damage::DamageTrackedRenderer, + element::RenderElementStates, gles2::{Gles2Renderbuffer, Gles2Renderer}, multigpu::{egl::EglGlesBackend, GpuManager}, Bind, @@ -61,7 +66,7 @@ mod socket; use session_fd::*; use socket::*; -use super::render::GlMultiRenderer; +use super::render::{CursorMode, GlMultiRenderer}; pub struct KmsState { devices: HashMap, @@ -89,7 +94,7 @@ pub struct Surface { damage_tracker: DamageTrackedRenderer, connector: connector::Handle, output: Output, - last_submit: Option, + last_submit: Option, refresh_rate: u32, vrr: bool, pending: bool, @@ -123,12 +128,11 @@ pub fn init_backend( } data.state.process_input_event(event); for output in data.state.common.shell.outputs() { - if let Err(err) = data - .state - .backend - .kms() - .schedule_render(&data.state.common.event_loop_handle, output) - { + if let Err(err) = data.state.backend.kms().schedule_render( + &data.state.common.event_loop_handle, + output, + None, + ) { slog_scope::crit!( "Error scheduling event loop for output {}: {:?}", output.name(), @@ -267,12 +271,16 @@ pub fn init_backend( surface.pending = false; } for output in data.state.common.shell.outputs() { - if let Err(err) = data - .state - .backend - .kms() - .schedule_render(&data.state.common.event_loop_handle, output) - { + let sessions = output.pending_buffers().collect::>(); + if let Err(err) = data.state.backend.kms().schedule_render( + &data.state.common.event_loop_handle, + output, + if !sessions.is_empty() { + Some(sessions) + } else { + None + }, + ) { slog_scope::crit!( "Error scheduling event loop for output {}: {:?}", output.name(), @@ -372,9 +380,12 @@ impl State { if let Some(surface) = device.surfaces.get_mut(&crtc) { match surface.surface.as_mut().map(|x| x.frame_submitted()) { Some(Ok(_)) => { - surface.last_submit = metadata.take().map(|data| data.time); + let _submit_time = metadata.take().map(|data| data.time); surface.pending = false; - data.state.common.send_frames(&surface.output); + data.state.common.send_frames( + &surface.output, + &surface.last_submit.take().unwrap(), + ); } Some(Err(err)) => { slog_scope::warn!("Failed to submit frame: {}", err) @@ -749,6 +760,7 @@ impl Surface { api: &mut GpuManager>, target_node: &DrmNode, state: &mut Common, + screencopy: Option<&[(ScreencopySession, BufferParams)]>, ) -> Result<()> { if self.surface.is_none() { return Ok(()); @@ -766,18 +778,20 @@ impl Surface { .bind(buffer.clone()) .with_context(|| "Failed to bind buffer")?; - match render::render_output( + match render::render_output::<_, Gles2Renderbuffer, _>( Some(&render_node), &mut renderer, &mut self.damage_tracker, age as usize, state, &self.output, - false, + CursorMode::All, + screencopy.map(|sessions| (buffer, sessions)), #[cfg(feature = "debug")] Some(&mut self.fps), ) { - Ok(_) => { + Ok((_damage, states)) => { + self.last_submit = Some(states); surface .queue_buffer() .with_context(|| "Failed to submit buffer for display")?; @@ -893,7 +907,16 @@ impl KmsState { shell.refresh_outputs(); if recreated { - if let Err(err) = self.schedule_render(loop_handle, output) { + let sessions = output.pending_buffers().collect::>(); + if let Err(err) = self.schedule_render( + loop_handle, + output, + if !sessions.is_empty() { + Some(sessions) + } else { + None + }, + ) { slog_scope::crit!( "Error scheduling event loop for output {}: {:?}", output.name(), @@ -956,6 +979,7 @@ impl KmsState { &mut self, loop_handle: &LoopHandle<'_, Data>, output: &Output, + mut screencopy_sessions: Option>, ) -> Result<(), InsertError> { if let Some((device, crtc, surface)) = self .devices @@ -1001,7 +1025,13 @@ impl KmsState { &mut backend.api, &device.render_node, &mut data.state.common, + screencopy_sessions.as_deref(), ) { + if let Some(sessions) = screencopy_sessions.as_mut() { + for (session, params) in sessions.drain(..) { + data.state.common.still_pending(session, params); + } + } if backend.session.is_active() { slog_scope::error!("Error rendering: {}", err); return TimeoutAction::ToDuration(Duration::from_secs_f64( diff --git a/src/backend/render/cursor.rs b/src/backend/render/cursor.rs index 64182f44..dd2fa461 100644 --- a/src/backend/render/cursor.rs +++ b/src/backend/render/cursor.rs @@ -172,8 +172,8 @@ pub fn draw_dnd_icon( ) } -struct CursorState { - cursor: Cursor, +pub struct CursorState { + pub cursor: Cursor, current_image: RefCell>, image_cache: RefCell)>>>, } @@ -219,7 +219,7 @@ where if let CursorImageStatus::Surface(ref wl_surface) = cursor_status { return draw_surface_cursor(wl_surface, location.to_i32_round(), scale); - } else if draw_default { + } else if draw_default && CursorImageStatus::Default == cursor_status { let integer_scale = scale.x.max(scale.y).ceil() as u32; let seat_userdata = seat.user_data(); diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index a7cba0f0..6e258f12 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -11,19 +11,28 @@ use crate::{ layout::floating::SeatMoveGrabState, CosmicMappedRenderElement, WorkspaceRenderElement, }, state::Common, - wayland::handlers::data_device::get_dnd_icon, + wayland::{ + handlers::{data_device::get_dnd_icon, screencopy::render_to_buffer}, + protocols::{ + screencopy::{BufferParams, Session as ScreencopySession}, + workspace::WorkspaceHandle, + }, + }, }; +use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::FailureReason; use smithay::{ backend::{ + allocator::dmabuf::Dmabuf, drm::DrmNode, renderer::{ damage::{ DamageTrackedRenderer, DamageTrackedRendererError as RenderError, OutputNoMode, }, + element::RenderElementStates, gles2::{Gles2Renderbuffer, Gles2Renderer}, multigpu::{egl::EglGlesBackend, MultiFrame, MultiRenderer}, - ImportAll, ImportMem, Renderer, + Bind, Blit, ExportMem, ImportAll, ImportMem, Offscreen, Renderer, TextureFilter, }, }, output::Output, @@ -42,7 +51,7 @@ pub type GlMultiRenderer<'a> = MultiRenderer< >; pub type GlMultiFrame = MultiFrame, EglGlesBackend>; -static CLEAR_COLOR: [f32; 4] = [0.153, 0.161, 0.165, 1.0]; +pub static CLEAR_COLOR: [f32; 4] = [0.153, 0.161, 0.165, 1.0]; smithay::render_elements! { pub CosmicElement where R: ImportAll; @@ -53,11 +62,18 @@ smithay::render_elements! { //EguiFrame=EguiFrame, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CursorMode { + None, + NotDefault, + All, +} + pub fn cursor_elements( renderer: &mut R, state: &Common, output: &Output, - hardware_cursor: bool, + mode: CursorMode, ) -> Vec where R: Renderer + ImportAll + ImportMem, @@ -76,18 +92,20 @@ where .shell .map_global_to_space(pointer.current_location().to_i32_round(), output); - elements.extend( - cursor::draw_cursor( - renderer, - seat, - location, - scale.into(), - &state.start_time, - !hardware_cursor, - ) - .into_iter() - .map(E::from), - ); + if mode != CursorMode::None { + elements.extend( + cursor::draw_cursor( + renderer, + seat, + location, + scale.into(), + &state.start_time, + mode != CursorMode::NotDefault, + ) + .into_iter() + .map(E::from), + ); + } if let Some(wl_surface) = get_dnd_icon(seat) { elements.extend( @@ -112,21 +130,30 @@ where elements } -pub fn render_output( +pub fn render_output( gpu: Option<&DrmNode>, renderer: &mut R, damage_tracker: &mut DamageTrackedRenderer, age: usize, state: &mut Common, output: &Output, - hardware_cursor: bool, + cursor_mode: CursorMode, + screencopy: Option<(Source, &[(ScreencopySession, BufferParams)])>, #[cfg(feature = "debug")] mut fps: Option<&mut Fps>, -) -> Result>>, RenderError> +) -> Result<(Option>>, RenderElementStates), RenderError> where - R: Renderer + ImportAll + ImportMem, + R: Renderer + + ImportAll + + ImportMem + + ExportMem + + Bind + + Offscreen + + Bind + + Blit, ::TextureId: Clone + 'static, + Source: Clone, { - let idx = state.shell.workspaces.active_num(output); + let handle = state.shell.workspaces.active(output).handle; render_workspace( gpu, renderer, @@ -134,24 +161,34 @@ where age, state, output, - idx, - hardware_cursor, + &handle, + cursor_mode, + screencopy, ) } -pub fn render_workspace( - _gpu: Option<&DrmNode>, +pub fn render_workspace( + gpu: Option<&DrmNode>, renderer: &mut R, damage_tracker: &mut DamageTrackedRenderer, age: usize, state: &mut Common, output: &Output, - idx: usize, - hardware_cursor: bool, + handle: &WorkspaceHandle, + cursor_mode: CursorMode, + screencopy: Option<(Source, &[(ScreencopySession, BufferParams)])>, #[cfg(feature = "debug")] mut fps: Option<&mut Fps>, -) -> Result>>, RenderError> +) -> Result<(Option>>, RenderElementStates), RenderError> where - R: Renderer + ImportAll + ImportMem, + R: Renderer + + ImportAll + + ImportMem + + ExportMem + + Bind + + Offscreen + + Bind + + Blit, + Source: Clone, ::TextureId: Clone + 'static, { #[cfg(feature = "debug")] @@ -159,14 +196,9 @@ where fps.start(); } - let workspace = &state - .shell - .workspaces - .get(idx, output) - .ok_or(OutputNoMode)?; + let workspace = state.shell.space_for_handle(&handle).ok_or(OutputNoMode)?; - let mut elements: Vec> = - cursor_elements(renderer, state, output, hardware_cursor); + let mut elements: Vec> = cursor_elements(renderer, state, output, cursor_mode); #[cfg(feature = "debug")] { @@ -219,5 +251,39 @@ where fps.end(); } + if let Some((source, buffers)) = screencopy { + if res.is_ok() { + for (session, params) in buffers { + match render_to_buffer( + gpu.cloned(), + renderer, + &session, + params, + output.current_transform(), + |_node, renderer, dtr, age| { + let res = dtr.damage_output(age, &elements, slog_scope::logger())?; + + if let (Some(ref damage), _) = &res { + for rect in damage { + renderer + .blit_from(source.clone(), *rect, *rect, TextureFilter::Nearest) + .map_err(RenderError::Rendering)?; + } + } + + Ok(res) + }, + ) { + Ok(true) => {} // success + Ok(false) => state.still_pending(session.clone(), params.clone()), + Err(err) => { + slog_scope::warn!("Error rendering to screencopy session: {}", err); + session.failed(FailureReason::Unspec); + } + } + } + } + } + res } diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 8456f0ac..e2ebfe06 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -6,11 +6,12 @@ use crate::{ input::Devices, state::{BackendData, Common, Data}, utils::prelude::*, + wayland::protocols::screencopy::{BufferParams, Session as ScreencopySession}, }; use anyhow::{anyhow, Context, Result}; use smithay::{ backend::{ - renderer::{damage::DamageTrackedRenderer, ImportDma, ImportEgl}, + renderer::{damage::DamageTrackedRenderer, gles2::Gles2Renderbuffer, ImportDma, ImportEgl}, winit::{self, WinitEvent, WinitGraphicsBackend, WinitVirtualDevice}, }, desktop::layer_map_for_output, @@ -26,11 +27,14 @@ use std::cell::RefCell; #[cfg(feature = "debug")] use crate::state::Fps; +use super::render::CursorMode; + pub struct WinitState { // The winit backend currently has no notion of multiple windows pub backend: WinitGraphicsBackend, output: Output, damage_tracker: DamageTrackedRenderer, + screencopy: Vec<(ScreencopySession, BufferParams)>, #[cfg(feature = "debug")] fps: Fps, } @@ -42,24 +46,34 @@ impl WinitState { .with_context(|| "Failed to bind buffer")?; let age = self.backend.buffer_age().unwrap_or(0); - match render::render_output( + let surface = self.backend.egl_surface(); + match render::render_output::<_, Gles2Renderbuffer, _>( None, self.backend.renderer(), &mut self.damage_tracker, age, state, &self.output, - true, + CursorMode::NotDefault, + if !self.screencopy.is_empty() { + Some((surface, &self.screencopy)) + } else { + None + }, #[cfg(feature = "debug")] Some(&mut self.fps), ) { - Ok(damage) => { - state.send_frames(&self.output); + Ok((damage, states)) => { + self.screencopy.clear(); self.backend - .submit(damage.as_ref().map(|x| &**x)) + .submit(damage.as_deref()) .with_context(|| "Failed to submit buffer for display")?; + state.send_frames(&self.output, &states); } Err(err) => { + for (session, params) in self.screencopy.drain(..) { + state.still_pending(session, params) + } anyhow::bail!("Rendering failed: {}", err); } }; @@ -92,6 +106,12 @@ impl WinitState { Ok(()) } } + + pub fn pending_screencopy(&mut self, new: Option>) { + if let Some(sessions) = new { + self.screencopy.extend(sessions); + } + } } pub fn init_backend( @@ -186,6 +206,7 @@ pub fn init_backend( backend, output: output.clone(), damage_tracker: DamageTrackedRenderer::from_output(&output), + screencopy: Vec::new(), #[cfg(feature = "debug")] fps: Fps::default(), }); diff --git a/src/backend/x11.rs b/src/backend/x11.rs index fbe8f6f6..5ad7ba37 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -6,6 +6,7 @@ use crate::{ input::Devices, state::{BackendData, Common, Data}, utils::prelude::*, + wayland::protocols::screencopy::{BufferParams, Session as ScreencopySession}, }; use anyhow::{Context, Result}; use smithay::{ @@ -14,7 +15,9 @@ use smithay::{ egl::{EGLContext, EGLDisplay}, input::{Event, InputEvent}, renderer::{ - damage::DamageTrackedRenderer, gles2::Gles2Renderer, Bind, ImportDma, ImportEgl, + damage::DamageTrackedRenderer, + gles2::{Gles2Renderbuffer, Gles2Renderer}, + Bind, ImportDma, ImportEgl, }, x11::{Window, WindowBuilder, X11Backend, X11Event, X11Handle, X11Input, X11Surface}, }, @@ -121,6 +124,7 @@ impl X11State { render: ping.clone(), dirty: false, pending: true, + screencopy: Vec::new(), #[cfg(feature = "debug")] fps: Fps::default(), }); @@ -130,9 +134,16 @@ impl X11State { Ok(output) } - pub fn schedule_render(&mut self, output: &Output) { + pub fn schedule_render( + &mut self, + output: &Output, + screencopy: Option>, + ) { if let Some(surface) = self.surfaces.iter_mut().find(|s| s.output == *output) { surface.dirty = true; + if let Some(sessions) = screencopy { + surface.screencopy.extend(sessions); + } if !surface.pending { surface.render.ping(); } @@ -172,6 +183,7 @@ impl X11State { pub struct Surface { window: Window, damage_tracker: DamageTrackedRenderer, + screencopy: Vec<(ScreencopySession, BufferParams)>, surface: X11Surface, output: Output, render: ping::Ping, @@ -192,27 +204,36 @@ impl Surface { .buffer() .with_context(|| "Failed to allocate buffer")?; renderer - .bind(buffer) + .bind(buffer.clone()) .with_context(|| "Failed to bind buffer")?; - match render::render_output( + match render::render_output::<_, Gles2Renderbuffer, _>( None, renderer, &mut self.damage_tracker, age as usize, state, &self.output, - true, + render::CursorMode::NotDefault, + if !self.screencopy.is_empty() { + Some((buffer, &self.screencopy)) + } else { + None + }, #[cfg(feature = "debug")] Some(&mut self.fps), ) { - Ok(_) => { - state.send_frames(&self.output); + Ok((_damage, states)) => { + self.screencopy.clear(); self.surface .submit() .with_context(|| "Failed to submit buffer for display")?; + state.send_frames(&self.output, &states); } Err(err) => { + for (session, params) in self.screencopy.drain(..) { + state.still_pending(session, params) + } self.surface.reset_buffers(); anyhow::bail!("Rendering failed: {}", err); } @@ -417,8 +438,7 @@ impl State { self.process_input_event(event); // TODO actually figure out the output for output in self.common.shell.outputs() { - self.backend - .schedule_render(&self.common.event_loop_handle, output); + self.backend.x11().schedule_render(output, None); } } } diff --git a/src/input/mod.rs b/src/input/mod.rs index b9572360..de70aaf5 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -5,7 +5,9 @@ use crate::{ shell::{focus::target::PointerFocusTarget, layout::floating::SeatMoveGrabState, Workspace}, // shell::grabs::SeatMoveGrabState state::Common, utils::prelude::*, + wayland::{handlers::screencopy::ScreencopySessions, protocols::screencopy::Session}, }; +use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType; use smithay::{ backend::input::{ AbsolutePositionEvent, Axis, AxisSource, Device, DeviceCapability, InputBackend, @@ -525,6 +527,14 @@ impl State { .cloned() .unwrap_or(current_output.clone()); if output != current_output { + for session in sessions_for_output(&self.common, ¤t_output) { + session.cursor_leave(seat, InputType::Pointer); + } + + for session in sessions_for_output(&self.common, &output) { + session.cursor_enter(seat, InputType::Pointer); + } + seat.set_active_output(&output); } let output_geometry = output.geometry(); @@ -546,6 +556,19 @@ impl State { output_geometry, &workspace, ); + + for session in sessions_for_output(&self.common, &output) { + if let Some((geometry, offset)) = seat.cursor_geometry( + position.to_buffer( + output.current_scale().fractional_scale(), + output.current_transform(), + &output.geometry().size.to_f64(), + ), + &self.common.start_time, + ) { + session.cursor_info(seat, InputType::Pointer, geometry, offset); + } + } seat.get_pointer().unwrap().motion( self, under, @@ -834,3 +857,44 @@ impl State { } } } + +fn sessions_for_output(state: &Common, output: &Output) -> impl Iterator { + let workspace = state.shell.active_space(&output); + let maybe_fullscreen = workspace.get_fullscreen(&output); + workspace + .screencopy_sessions + .iter() + .map(|s| (&**s).clone()) + .chain( + maybe_fullscreen + .and_then(|w| w.user_data().get::()) + .map(|sessions| { + sessions + .0 + .borrow() + .iter() + .map(|s| (&**s).clone()) + .collect::>() + }) + .into_iter() + .flatten(), + ) + .chain( + output + .user_data() + .get::() + .map(|sessions| { + sessions + .0 + .borrow() + .iter() + .map(|s| (&**s).clone()) + .collect::>() + }) + .into_iter() + .into_iter() + .flatten(), + ) + .collect::>() + .into_iter() +} diff --git a/src/main.rs b/src/main.rs index a96f3d06..e9fb78be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -73,10 +73,13 @@ fn main() -> Result<()> { // do we need to trigger another render if data.state.common.dirty_flag.swap(false, Ordering::SeqCst) { + // TODO: Render workspace sessions for output in data.state.common.shell.outputs() { - data.state - .backend - .schedule_render(&data.state.common.event_loop_handle, output) + data.state.backend.schedule_render( + &data.state.common.event_loop_handle, + output, + None, + ) } } diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index d7d5daa6..021a85a9 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -203,7 +203,7 @@ impl CosmicMapped { pub fn set_tiled(&self, tiled: bool) { for toplevel in match &self.element { // we use the tiled state of stack windows anyway to get rid of decorations - CosmicMappedInternal::Stack(s) => None, + CosmicMappedInternal::Stack(_) => None, CosmicMappedInternal::Window(w) => Some(w.window.toplevel()), _ => unreachable!(), } { diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index 76fbadd4..f39cafb0 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -1,4 +1,7 @@ -use crate::state::State; +use crate::{ + state::State, utils::prelude::SeatExt, wayland::handlers::screencopy::ScreencopySessions, +}; +use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType; use smithay::{ backend::{ input::KeyState, @@ -29,6 +32,9 @@ use std::{ pub struct CosmicStack { windows: Arc>>, active: Arc, + last_location: Arc, Serial, u32)>>>, + previous_keyboard: Arc, + previous_pointer: Arc, pub(super) header: Arc>>, } @@ -68,7 +74,9 @@ impl CosmicStack { .iter() .position(|w| w == window) { - self.active.store(val, Ordering::SeqCst) + let old = self.active.swap(val, Ordering::SeqCst); + self.previous_keyboard.store(old, Ordering::SeqCst); + self.previous_pointer.store(old, Ordering::SeqCst); } } @@ -92,6 +100,69 @@ impl CosmicStack { }; } } + + fn keyboard_leave_if_previous( + &self, + seat: &Seat, + data: &mut State, + serial: Serial, + ) -> usize { + let active = self.active.load(Ordering::SeqCst); + let previous = self.previous_keyboard.swap(active, Ordering::SeqCst); + if previous != active { + KeyboardTarget::leave(&self.windows.lock().unwrap()[previous], seat, data, serial); + // TODO: KeyboardTarget::enter(&self.windows.lock().unwrap()[active], seat, data, serial, seat.keys()) + } + active + } + + fn pointer_leave_if_previous( + &self, + seat: &Seat, + data: &mut State, + serial: Serial, + time: u32, + location: Point, + ) -> usize { + let active = self.active.load(Ordering::SeqCst); + let previous = self.previous_pointer.swap(active, Ordering::SeqCst); + if previous != active { + if let Some(sessions) = self.windows.lock().unwrap()[previous] + .user_data() + .get::() + { + for session in &*sessions.0.borrow() { + session.cursor_leave(seat, InputType::Pointer) + } + } + PointerTarget::leave( + &self.windows.lock().unwrap()[previous], + seat, + data, + serial, + time, + ); + if let Some(sessions) = self.windows.lock().unwrap()[active] + .user_data() + .get::() + { + for session in &*sessions.0.borrow() { + session.cursor_enter(seat, InputType::Pointer) + } + } + PointerTarget::enter( + &self.windows.lock().unwrap()[active], + seat, + data, + &MotionEvent { + location, + serial, + time, + }, + ); + } + active + } } impl IsAlive for CosmicStack { @@ -158,6 +229,8 @@ impl KeyboardTarget for CosmicStack { keys: Vec>, serial: Serial, ) { + let active = self.active.load(Ordering::SeqCst); + self.previous_keyboard.store(active, Ordering::SeqCst); KeyboardTarget::enter( &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], seat, @@ -167,12 +240,8 @@ impl KeyboardTarget for CosmicStack { ) } fn leave(&self, seat: &Seat, data: &mut State, serial: Serial) { - KeyboardTarget::leave( - &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], - seat, - data, - serial, - ) + let active = self.keyboard_leave_if_previous(seat, data, serial); + KeyboardTarget::leave(&self.windows.lock().unwrap()[active], seat, data, serial) } fn key( &self, @@ -183,8 +252,9 @@ impl KeyboardTarget for CosmicStack { serial: Serial, time: u32, ) { + let active = self.keyboard_leave_if_previous(seat, data, serial); KeyboardTarget::key( - &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], + &self.windows.lock().unwrap()[active], seat, data, key, @@ -200,8 +270,9 @@ impl KeyboardTarget for CosmicStack { modifiers: ModifiersState, serial: Serial, ) { + let active = self.keyboard_leave_if_previous(seat, data, serial); KeyboardTarget::modifiers( - &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], + &self.windows.lock().unwrap()[active], seat, data, modifiers, @@ -212,6 +283,16 @@ impl KeyboardTarget for CosmicStack { impl PointerTarget for CosmicStack { fn enter(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { + if let Some(sessions) = self.active().user_data().get::() { + for session in &*sessions.0.borrow() { + session.cursor_enter(seat, InputType::Pointer) + } + } + + *self.last_location.lock().unwrap() = Some((event.location, event.serial, event.time)); + let active = self.active.load(Ordering::SeqCst); + self.previous_pointer.store(active, Ordering::SeqCst); + PointerTarget::enter( &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], seat, @@ -220,14 +301,27 @@ impl PointerTarget for CosmicStack { ) } fn motion(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { - PointerTarget::motion( - &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], - seat, - data, - event, - ) + let active = + self.pointer_leave_if_previous(seat, data, event.serial, event.time, event.location); + + if let Some(sessions) = self.active().user_data().get::() { + for session in &*sessions.0.borrow() { + let buffer_loc = (event.location.x, event.location.y); // we always screencast windows at 1x1 scale + if let Some((geo, hotspot)) = + seat.cursor_geometry(buffer_loc, &data.common.start_time) + { + session.cursor_info(seat, InputType::Pointer, geo, hotspot); + } + } + } + + PointerTarget::motion(&self.windows.lock().unwrap()[active], seat, data, event) } fn button(&self, seat: &Seat, data: &mut State, event: &ButtonEvent) { + if let Some((location, _serial, _time)) = self.last_location.lock().unwrap().clone() { + self.pointer_leave_if_previous(seat, data, event.serial, event.time, location); + } + PointerTarget::button( &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], seat, @@ -236,6 +330,10 @@ impl PointerTarget for CosmicStack { ) } fn axis(&self, seat: &Seat, data: &mut State, frame: AxisFrame) { + if let Some((location, serial, time)) = self.last_location.lock().unwrap().clone() { + self.pointer_leave_if_previous(seat, data, serial, time, location); + } + PointerTarget::axis( &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], seat, @@ -244,6 +342,15 @@ impl PointerTarget for CosmicStack { ) } fn leave(&self, seat: &Seat, data: &mut State, serial: Serial, time: u32) { + if let Some((location, serial, time)) = self.last_location.lock().unwrap().clone() { + self.pointer_leave_if_previous(seat, data, serial, time, location); + } + if let Some(sessions) = self.active().user_data().get::() { + for session in &*sessions.0.borrow() { + session.cursor_leave(seat, InputType::Pointer) + } + } + PointerTarget::leave( &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], seat, diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index c01a6b18..9df7f253 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -1,4 +1,6 @@ -use crate::state::State; +use crate::{ + state::State, utils::prelude::SeatExt, wayland::handlers::screencopy::ScreencopySessions, +}; use smithay::{ backend::{ input::KeyState, @@ -187,9 +189,30 @@ impl KeyboardTarget for CosmicWindow { impl PointerTarget for CosmicWindow { fn enter(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { + use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType; + + if let Some(sessions) = self.window.user_data().get::() { + for session in &*sessions.0.borrow() { + session.cursor_enter(seat, InputType::Pointer) + } + } + PointerTarget::enter(&self.window, seat, data, event) } fn motion(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { + use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType; + + if let Some(sessions) = self.window.user_data().get::() { + for session in &*sessions.0.borrow() { + let buffer_loc = (event.location.x, event.location.y); // we always screencast windows at 1x1 scale + if let Some((geo, hotspot)) = + seat.cursor_geometry(buffer_loc, &data.common.start_time) + { + session.cursor_info(seat, InputType::Pointer, geo, hotspot); + } + } + } + PointerTarget::motion(&self.window, seat, data, event) } fn button(&self, seat: &Seat, data: &mut State, event: &ButtonEvent) { @@ -199,6 +222,14 @@ impl PointerTarget for CosmicWindow { PointerTarget::axis(&self.window, seat, data, frame) } fn leave(&self, seat: &Seat, data: &mut State, serial: Serial, time: u32) { + use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType; + + if let Some(sessions) = self.window.user_data().get::() { + for session in &*sessions.0.borrow() { + session.cursor_leave(seat, InputType::Pointer) + } + } + PointerTarget::leave(&self.window, seat, data, serial, time) } } diff --git a/src/shell/layout/floating/grabs/moving.rs b/src/shell/layout/floating/grabs/moving.rs index 96d2e73a..02b52713 100644 --- a/src/shell/layout/floating/grabs/moving.rs +++ b/src/shell/layout/floating/grabs/moving.rs @@ -13,13 +13,13 @@ use smithay::{ desktop::space::SpaceElement, input::{ pointer::{ - AxisFrame, ButtonEvent, Focus, GrabStartData as PointerGrabStartData, MotionEvent, + AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle, }, Seat, }, output::Output, - utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial}, + utils::{IsAlive, Logical, Point, Serial}, }; use std::cell::RefCell; diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 0b34644d..24fcc67f 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -189,11 +189,11 @@ impl FloatingLayout { &mut self, mapped: &CosmicMapped, seat: &Seat, - serial: Serial, + _serial: Serial, start_data: PointerGrabStartData, edges: ResizeEdge, ) -> Option { - if let Some(pointer) = seat.get_pointer() { + if seat.get_pointer().is_some() { let location = self.space.element_location(&mapped).unwrap(); let size = mapped.geometry().size; @@ -204,8 +204,6 @@ impl FloatingLayout { location, size, )) - - //pointer.set_grab(state, grab, serial, Focus::Clear); } else { None } diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 1be6cd36..acf4aec0 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -276,7 +276,7 @@ impl WorkspaceMode { } } - pub fn active_num(&self, output: &Output) -> usize { + fn active_num(&self, output: &Output) -> usize { match self { WorkspaceMode::Global(set) => set.active, WorkspaceMode::OutputBound(sets) => { @@ -691,7 +691,7 @@ impl Shell { } } - pub fn outputs_for_surface<'a>( + pub fn visible_outputs_for_surface<'a>( &'a self, surface: &'a WlSurface, ) -> impl Iterator + 'a { @@ -703,7 +703,7 @@ impl Shell { Some(output) => { Box::new(std::iter::once(output.clone())) as Box> } - None => Box::new(self.workspaces.spaces().flat_map(|w| { + None => Box::new(self.outputs().map(|o| self.active_space(o)).flat_map(|w| { w.mapped() .find(|e| e.has_surface(surface, WindowSurfaceType::ALL)) .into_iter() @@ -712,6 +712,47 @@ impl Shell { } } + pub fn workspaces_for_surface( + &self, + surface: &WlSurface, + ) -> impl Iterator { + match self.outputs.iter().find(|o| { + let map = layer_map_for_output(o); + map.layer_for_surface(surface, WindowSurfaceType::ALL) + .is_some() + }) { + Some(output) => self + .workspaces + .spaces() + .filter(move |workspace| { + workspace + .floating_layer + .space + .outputs() + .any(|o| o == output) + }) + .map(|w| (w.handle.clone(), output.clone())) + .collect::>(), + None => self + .workspaces + .spaces() + .filter_map(|w| { + if let Some(mapped) = w + .mapped() + .find(|e| e.has_surface(surface, WindowSurfaceType::ALL)) + { + let outputs = w.outputs_for_element(mapped); + Some(std::iter::repeat(w.handle.clone()).zip(outputs).fuse()) + } else { + None + } + }) + .flatten() + .collect::>(), + } + .into_iter() + } + pub fn element_for_surface(&self, surface: &WlSurface) -> Option<&CosmicMapped> { self.workspaces .spaces() @@ -730,6 +771,14 @@ impl Shell { .find(|workspace| workspace.mapped().any(|m| m == mapped)) } + pub fn space_for_handle(&self, handle: &WorkspaceHandle) -> Option<&Workspace> { + self.workspaces.spaces().find(|w| &w.handle == handle) + } + + pub fn space_for_handle_mut(&mut self, handle: &WorkspaceHandle) -> Option<&mut Workspace> { + self.workspaces.spaces_mut().find(|w| &w.handle == handle) + } + pub fn outputs(&self) -> impl Iterator { self.outputs.iter() } diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 7120cdcc..00c77374 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -1,14 +1,17 @@ use crate::{ - shell::{ - element::CosmicWindow, - layout::{ - floating::{FloatingLayout, MoveSurfaceGrab}, - tiling::TilingLayout, - }, + shell::layout::{ + floating::{FloatingLayout, MoveSurfaceGrab}, + tiling::TilingLayout, }, state::State, utils::prelude::*, - wayland::protocols::workspace::WorkspaceHandle, + wayland::{ + handlers::screencopy::DropableSession, + protocols::{ + screencopy::{BufferParams, Session as ScreencopySession}, + workspace::WorkspaceHandle, + }, + }, }; use indexmap::IndexSet; @@ -17,21 +20,18 @@ use smithay::{ element::{surface::WaylandSurfaceRenderElement, AsRenderElements}, ImportAll, Renderer, }, - desktop::{ - layer_map_for_output, space::SpaceElement, Kind, LayerSurface, Space, Window, - WindowSurfaceType, - }, + desktop::{layer_map_for_output, space::SpaceElement, Kind, LayerSurface, Window}, input::{pointer::GrabStartData as PointerGrabStartData, Seat}, - output::{Output, WeakOutput}, + output::Output, reexports::{ wayland_protocols::xdg::shell::server::xdg_toplevel::{self, ResizeEdge}, - wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle}, + wayland_server::protocol::wl_surface::WlSurface, }, render_elements, utils::{IsAlive, Logical, Point, Rectangle, Scale, Serial}, wayland::shell::wlr_layer::Layer, }; -use std::{collections::HashMap, time::Duration}; +use std::collections::HashMap; use super::{ element::CosmicMapped, @@ -48,6 +48,8 @@ pub struct Workspace { pub fullscreen: HashMap, pub handle: WorkspaceHandle, pub focus_stack: FocusStacks, + pub pending_buffers: Vec<(ScreencopySession, BufferParams)>, + pub screencopy_sessions: Vec, } #[derive(Debug, Default)] @@ -62,6 +64,8 @@ impl Workspace { fullscreen: HashMap::new(), handle, focus_stack: FocusStacks::default(), + pending_buffers: Vec::new(), + screencopy_sessions: Vec::new(), } } diff --git a/src/state.rs b/src/state.rs index eeb69cb5..4734a1f3 100644 --- a/src/state.rs +++ b/src/state.rs @@ -7,12 +7,18 @@ use crate::{ shell::Shell, utils::prelude::*, wayland::protocols::{ - drm::WlDrmState, output_configuration::OutputConfigurationState, + drm::WlDrmState, + output_configuration::OutputConfigurationState, + screencopy::{BufferParams, Session as ScreencopySession}, workspace::WorkspaceClientState, }, }; use smithay::{ - backend::drm::DrmNode, + backend::{ + drm::DrmNode, + renderer::element::{default_primary_scanout_output_compare, RenderElementStates}, + }, + desktop::utils::{surface_primary_scanout_output, update_surface_primary_scanout_output}, input::{Seat, SeatState}, output::{Mode as OutputMode, Output, Scale}, reexports::{ @@ -33,7 +39,7 @@ use std::{ cell::RefCell, ffi::OsString, sync::{atomic::AtomicBool, Arc}, - time::Instant, + time::{Duration, Instant}, }; #[cfg(feature = "debug")] use std::{collections::VecDeque, time::Duration}; @@ -185,14 +191,19 @@ impl BackendData { result } - pub fn schedule_render(&mut self, loop_handle: &LoopHandle<'_, Data>, output: &Output) { + pub fn schedule_render( + &mut self, + loop_handle: &LoopHandle<'_, Data>, + output: &Output, + screencopy: Option>, + ) { match self { - BackendData::Winit(_) => {} // We cannot do this on the winit backend. + BackendData::Winit(ref mut state) => state.pending_screencopy(screencopy), // We cannot do this on the winit backend. // Winit has a very strict render-loop and skipping frames breaks atleast the wayland winit-backend. // Swapping with damage (which should be empty on these frames) is likely good enough anyway. - BackendData::X11(ref mut state) => state.schedule_render(output), + BackendData::X11(ref mut state) => state.schedule_render(output, screencopy), BackendData::Kms(ref mut state) => { - if let Err(err) = state.schedule_render(loop_handle, output) { + if let Err(err) = state.schedule_render(loop_handle, output, screencopy) { slog_scope::crit!("Failed to schedule event, are we shutting down? {:?}", err); } } @@ -348,17 +359,53 @@ impl Common { self.last_active_seat.as_ref().expect("No seat?") } - pub fn send_frames(&self, output: &Output) { - let workspace = self.shell.active_space(output); - workspace.mapped().for_each(|mapped| { - if workspace.outputs_for_element(mapped).any(|o| &o == output) { + pub fn send_frames(&self, output: &Output, render_element_states: &RenderElementStates) { + let time = self.start_time.elapsed(); + let throttle = Some(Duration::from_secs(1)); + + let active = self.shell.active_space(output); + active.mapped().for_each(|mapped| { + if active.outputs_for_element(mapped).any(|o| &o == output) { let window = mapped.active_window(); - window.send_frame(self.start_time.elapsed().as_millis() as u32) + window.with_surfaces(|surface, states| { + update_surface_primary_scanout_output( + surface, + output, + states, + render_element_states, + default_primary_scanout_output_compare, + ) + }); + window.send_frame(output, time, throttle, surface_primary_scanout_output); } }); + + for space in self + .shell + .workspaces + .spaces() + .filter(|w| w.handle != active.handle) + { + space.mapped().for_each(|mapped| { + if space.outputs_for_element(mapped).any(|o| &o == output) { + let window = mapped.active_window(); + window.send_frame(output, time, throttle, |_, _| None); + } + }); + } + let map = smithay::desktop::layer_map_for_output(output); for layer_surface in map.layers() { - layer_surface.send_frame(self.start_time.elapsed().as_millis() as u32) + layer_surface.with_surfaces(|surface, states| { + update_surface_primary_scanout_output( + surface, + output, + states, + render_element_states, + default_primary_scanout_output_compare, + ) + }); + layer_surface.send_frame(output, time, throttle, surface_primary_scanout_output); } } } diff --git a/src/utils/prelude.rs b/src/utils/prelude.rs index 20ed2571..18deee0b 100644 --- a/src/utils/prelude.rs +++ b/src/utils/prelude.rs @@ -1,8 +1,18 @@ -use crate::input::{ActiveOutput, SeatId}; +use std::{cell::RefCell, sync::Mutex}; + +use crate::{ + backend::render::cursor::CursorState, + input::{ActiveOutput, SeatId}, +}; use smithay::{ - input::Seat, + desktop::utils::bbox_from_surface_tree, + input::{ + pointer::{CursorImageAttributes, CursorImageStatus}, + Seat, + }, output::Output, - utils::{Logical, Rectangle, Transform}, + utils::{Buffer, IsAlive, Logical, Point, Rectangle, Transform}, + wayland::compositor::with_states, }; pub use crate::shell::{Shell, Workspace}; @@ -34,6 +44,11 @@ pub trait SeatExt { fn active_output(&self) -> Output; fn set_active_output(&self, output: &Output); + fn cursor_geometry( + &self, + loc: impl Into>, + start_time: &std::time::Instant, + ) -> Option<(Rectangle, Point)>; } impl SeatExt for Seat { @@ -56,4 +71,63 @@ impl SeatExt for Seat { .0 .borrow_mut() = output.clone(); } + + fn cursor_geometry( + &self, + loc: impl Into>, + start_time: &std::time::Instant, + ) -> Option<(Rectangle, Point)> { + let location = loc.into().to_i32_round(); + + let cursor_status = self + .user_data() + .get::>() + .map(|cell| { + let mut cursor_status = cell.borrow_mut(); + if let CursorImageStatus::Surface(ref surface) = *cursor_status { + if !surface.alive() { + *cursor_status = CursorImageStatus::Default; + } + } + cursor_status.clone() + }) + .unwrap_or(CursorImageStatus::Default); + + match cursor_status { + CursorImageStatus::Surface(surface) => { + let hotspot = with_states(&surface, |states| { + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .hotspot + }); + let geo = bbox_from_surface_tree(&surface, (location.x, location.y)); + let buffer_geo = Rectangle::from_loc_and_size( + (geo.loc.x, geo.loc.y), + geo.size.to_buffer(1, Transform::Normal), + ); + Some((buffer_geo, (hotspot.x, hotspot.y).into())) + } + CursorImageStatus::Default => { + let seat_userdata = self.user_data(); + seat_userdata.insert_if_missing(CursorState::default); + let state = seat_userdata.get::().unwrap(); + let frame = state + .cursor + .get_image(1, start_time.elapsed().as_millis() as u32); + + Some(( + Rectangle::from_loc_and_size( + location, + (frame.width as i32, frame.height as i32), + ), + (frame.xhot as i32, frame.yhot as i32).into(), + )) + } + CursorImageStatus::Hidden => None, + } + } } diff --git a/src/wayland/handlers/compositor.rs b/src/wayland/handlers/compositor.rs index a0199033..77db7e30 100644 --- a/src/wayland/handlers/compositor.rs +++ b/src/wayland/handlers/compositor.rs @@ -1,11 +1,19 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::{state::BackendData, utils::prelude::*}; +use crate::{ + state::BackendData, + utils::prelude::*, + wayland::{ + handlers::screencopy::UserdataExt, + protocols::screencopy::{BufferParams, Session as ScreencopySession, SessionType}, + }, +}; use smithay::{ backend::renderer::utils::{on_commit_buffer_handler, with_renderer_surface_state}, delegate_compositor, desktop::{layer_map_for_output, Kind, LayerSurface, PopupKind, WindowSurfaceType}, reexports::wayland_server::protocol::wl_surface::WlSurface, + utils::IsAlive, wayland::{ compositor::{with_states, CompositorHandler, CompositorState}, shell::{ @@ -18,11 +26,13 @@ use smithay::{ }; use std::sync::Mutex; +use super::screencopy::{self, PendingScreencopyBuffers}; + impl State { fn early_import_surface(&mut self, surface: &WlSurface) { let mut import_nodes = std::collections::HashSet::new(); let dh = &self.common.display_handle; - for output in self.common.shell.outputs_for_surface(&surface) { + for output in self.common.shell.visible_outputs_for_surface(&surface) { if let BackendData::Kms(ref mut kms_state) = &mut self.backend { if let Some(target) = kms_state.target_node_for_output(&output) { if import_nodes.insert(target) { @@ -155,8 +165,38 @@ impl CompositorHandler for State { if let Some(element) = self.common.shell.element_for_surface(surface).cloned() { if let Some(workspace) = self.common.shell.space_for_mut(&element) { crate::shell::layout::floating::ResizeSurfaceGrab::apply_resize_to_location( - element, workspace, + element.clone(), + workspace, ); + workspace.commit(surface); + } + + // handle window screencopy sessions + let active = element.active_window(); + if active.toplevel().wl_surface() == surface { + for (session, params) in active.pending_buffers() { + let window = active.clone(); + self.common.event_loop_handle.insert_idle(move |data| { + if !session.alive() { + return; + } + + match screencopy::render_window_to_buffer( + &mut data.state, + &session, + params.clone(), + &window, + ) { + // rendering yielded no damage, buffer is still pending + Ok(false) => data.state.common.still_pending(session, params), + Ok(true) => {} // success + Err((reason, err)) => { + slog_scope::warn!("Screencopy session failed: {}", err); + session.failed(reason); + } + } + }); + } } } @@ -166,9 +206,6 @@ impl CompositorHandler for State { // and refresh smithays internal state self.common.shell.popups.commit(surface); - for workspace in self.common.shell.workspaces.spaces_mut() { - workspace.commit(surface); - } // re-arrange layer-surfaces (commits may change size and positioning) if let Some(output) = self.common.shell.outputs().find(|o| { @@ -179,10 +216,93 @@ impl CompositorHandler for State { layer_map_for_output(output).arrange(); } + // here we store additional workspace_sessions, we should handle, when rendering the corresponding output anyway + let mut scheduled_sessions: Option> = None; + + // lets check which workspaces this surface belongs to + let active_spaces = self + .common + .shell + .outputs() + .map(|o| (o.clone(), self.common.shell.active_space(o).handle.clone())) + .collect::>(); + for (handle, output) in self.common.shell.workspaces_for_surface(surface) { + let workspace = self.common.shell.space_for_handle_mut(&handle).unwrap(); + if !workspace.pending_buffers.is_empty() { + // TODO: replace with drain_filter.... + let mut i = 0; + while i < workspace.pending_buffers.len() { + if let SessionType::Workspace(o, w) = + workspace.pending_buffers[i].0.session_type() + { + if active_spaces.contains(&(o.clone(), w)) { + // surface is on an active workspace/output combo, add to workspace_sessions + let (session, params) = workspace.pending_buffers.remove(i); + scheduled_sessions + .get_or_insert_with(Vec::new) + .push((session, params)); + } else if handle == w && output == o { + // surface is visible on an offscreen workspace session, schedule a new render + let (session, params) = workspace.pending_buffers.remove(i); + let output = output.clone(); + self.common.event_loop_handle.insert_idle(move |data| { + if !session.alive() { + return; + } + match screencopy::render_workspace_to_buffer( + &mut data.state, + &session, + params.clone(), + &output, + &handle, + ) { + Ok(false) => { + // rendering yielded no new damage, buffer still pending + data.state.common.still_pending(session, params); + } + Ok(true) => {} + Err((reason, err)) => { + slog_scope::warn!("Screencopy session failed: {}", err); + session.failed(reason); + } + } + }); + } else { + i += 1; + } + } else { + unreachable!(); + } + } + } + } + // schedule a new render - for output in self.common.shell.outputs_for_surface(surface) { - self.backend - .schedule_render(&self.common.event_loop_handle, &output); + for output in self.common.shell.visible_outputs_for_surface(surface) { + if let Some(sessions) = output.user_data().get::() { + scheduled_sessions + .get_or_insert_with(Vec::new) + .extend(sessions.borrow_mut().drain(..)); + } + + self.backend.schedule_render( + &self.common.event_loop_handle, + &output, + scheduled_sessions.as_ref().map(|sessions| { + sessions + .iter() + .filter(|(s, _)| match s.session_type() { + SessionType::Output(o) | SessionType::Workspace(o, _) + if o == output => + { + true + } + _ => false, + }) + .cloned() + .collect::>() + }), + ); } } } diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index 98ade3f1..16cb4288 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -10,6 +10,7 @@ pub mod layer_shell; pub mod output; pub mod output_configuration; pub mod primary_selection; +pub mod screencopy; pub mod seat; pub mod shm; pub mod toplevel_info; diff --git a/src/wayland/handlers/screencopy.rs b/src/wayland/handlers/screencopy.rs new file mode 100644 index 00000000..80cc9627 --- /dev/null +++ b/src/wayland/handlers/screencopy.rs @@ -0,0 +1,795 @@ +use std::{ + cell::RefCell, + collections::HashSet, + ops::{Deref, DerefMut}, +}; + +use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::{ + FailureReason, InputType, +}; +use smithay::{ + backend::{ + allocator::dmabuf::Dmabuf, + drm::DrmNode, + egl::EGLDevice, + renderer::{ + buffer_dimensions, buffer_type, + damage::{DamageTrackedRenderer, DamageTrackedRendererError}, + element::{ + surface::WaylandSurfaceRenderElement, AsRenderElements, RenderElementStates, + }, + gles2::{Gles2Renderbuffer, Gles2Renderer}, + Bind, BufferType, ExportMem, Offscreen, Renderer, + }, + }, + desktop::Window, + output::Output, + reexports::wayland_server::{ + protocol::{wl_buffer::WlBuffer, wl_shm::Format as ShmFormat}, + Resource, + }, + utils::{Physical, Rectangle, Scale, Transform}, + wayland::{ + dmabuf::get_dmabuf, + shm::{with_buffer_contents, with_buffer_contents_mut}, + }, +}; + +use crate::{ + backend::render::{render_output, render_workspace, CursorMode, CLEAR_COLOR}, + state::{BackendData, ClientState, Common, State}, + utils::prelude::OutputExt, + wayland::protocols::{ + screencopy::{ + BufferInfo, BufferParams, CursorMode as ScreencopyCursorMode, CursorSession, + ScreencopyHandler, Session, SessionType, + }, + workspace::WorkspaceHandle, + }, +}; + +pub type PendingScreencopyBuffers = RefCell>; + +#[derive(Debug, Default)] +pub struct ScreencopySessions(pub RefCell>); + +#[derive(Debug)] +pub struct DropableSession(Session, FailureReason); +impl Deref for DropableSession { + type Target = Session; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for DropableSession { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} +impl Drop for DropableSession { + fn drop(&mut self) { + self.0.failed(self.1); + } +} +impl PartialEq for DropableSession { + fn eq(&self, other: &Session) -> bool { + &self.0 == other + } +} + +pub type SessionDTR = RefCell; + +impl ScreencopyHandler for State { + fn capture_output(&mut self, output: Output, session: Session) -> Vec { + let formats = match formats_for_output(&output, &mut self.backend) { + Ok(formats) => formats, + Err(reason) => { + session.failed(reason); + return Vec::new(); + } + }; + + for seat in self.common.seats() { + if let Some(pointer) = seat.get_pointer() { + if output + .geometry() + .contains(pointer.current_location().to_i32_round()) + { + session.cursor_enter(seat, InputType::Pointer); + } + } + } + + session + .user_data() + .insert_if_missing(|| SessionDTR::new(DamageTrackedRenderer::from_output(&output))); + output + .user_data() + .insert_if_missing(ScreencopySessions::default); + output + .user_data() + .get::() + .unwrap() + .0 + .borrow_mut() + .push(DropableSession(session, FailureReason::OutputDisabled)); + + formats + } + + fn capture_workspace( + &mut self, + handle: WorkspaceHandle, + output: Output, + session: Session, + ) -> Vec { + let formats = match formats_for_output(&output, &mut self.backend) { + Ok(formats) => formats, + Err(reason) => { + session.failed(reason); + return Vec::new(); + } + }; + + let workspace = match self.common.shell.space_for_handle_mut(&handle) { + Some(workspace) => workspace, + None => { + session.failed(FailureReason::Unspec); + return Vec::new(); + } + }; + + session + .user_data() + .insert_if_missing(|| SessionDTR::new(DamageTrackedRenderer::from_output(&output))); + + workspace + .screencopy_sessions + .push(DropableSession(session, FailureReason::InvalidOutput)); + + formats + } + + fn capture_toplevel(&mut self, toplevel: Window, session: Session) -> Vec { + let surface = toplevel.toplevel().wl_surface(); + let size = toplevel + .bbox_with_popups() + .size + .to_buffer(1, Transform::Normal); + + let mut _kms_renderer = None; + let renderer = match self.backend { + BackendData::Kms(ref mut kms) => { + let node = self + .common + .display_handle + .get_client(surface.id()) + .ok() + .and_then(|client| client.get_data::().unwrap().drm_node.clone()) + .unwrap_or(kms.primary.clone()); + _kms_renderer = Some(kms.api.renderer::(&node, &node).unwrap()); + _kms_renderer.as_mut().unwrap().as_mut() + } + BackendData::Winit(ref mut winit) => winit.backend.renderer(), + BackendData::X11(ref mut x11) => &mut x11.renderer, + _ => unreachable!(), + }; + + let mut formats = vec![ + BufferInfo::Shm { + format: ShmFormat::Abgr8888, + size, + stride: 0, + }, + BufferInfo::Shm { + format: ShmFormat::Xbgr8888, + size, + stride: 0, + }, + ]; + + if let Some(node) = EGLDevice::device_for_display(renderer.egl_context().display()) + .ok() + .and_then(|device| device.try_get_render_node().ok().flatten()) + { + formats.extend( + renderer + .egl_context() + .dmabuf_render_formats() + .iter() + .map(|format| format.code) + .collect::>() + .into_iter() + .map(|format| BufferInfo::Dmabuf { node, format, size }), + ); + } + + let size = toplevel.geometry().size.to_physical(1); + session.user_data().insert_if_missing(|| { + SessionDTR::new(DamageTrackedRenderer::new(size, 1.0, Transform::Normal)) + }); + toplevel + .user_data() + .insert_if_missing(ScreencopySessions::default); + toplevel + .user_data() + .get::() + .unwrap() + .0 + .borrow_mut() + .push(DropableSession(session, FailureReason::ToplevelDestroyed)); + + formats + } + + fn capture_cursor(&mut self, _session: CursorSession) -> Vec { + unimplemented!("We don't advertise the capture cursor mode") + } + + fn buffer_attached(&mut self, session: Session, params: BufferParams, on_damage: bool) { + // verify buffer size + let buffer_size = match buffer_dimensions(¶ms.buffer) { + Some(size) => size.to_logical(1, Transform::Normal), + None => { + slog_scope::warn!("Error during screencopy session: Buffer has no size"); + session.failed(FailureReason::InvalidBuffer); + return; + } + }; + match session.session_type() { + SessionType::Output(output) | SessionType::Workspace(output, _) => { + let mode = match output.current_mode() { + Some(mode) => mode, + None => { + slog_scope::warn!("Error during screencopy session: Output has no mode"); + session.failed(FailureReason::InvalidOutput); + return; + } + } + .size; + + if buffer_size.to_physical(1) != mode { + slog_scope::warn!("Error during screencopy session: Buffer size doesn't match"); + session.failed(FailureReason::InvalidBuffer); + return; + } + } + SessionType::Window(window) => { + let geometry = window.geometry(); + if buffer_size != geometry.size { + slog_scope::warn!("Error during screencopy session: Buffer size doesn't match"); + session.failed(FailureReason::InvalidBuffer); + return; + } + } + _ => {} + }; + + if !matches!( + buffer_type(¶ms.buffer), + Some(BufferType::Shm) | Some(BufferType::Dma) + ) { + slog_scope::warn!("Error during screencopy session: Buffer is neither shm or dma"); + session.failed(FailureReason::InvalidBuffer); + return; + } + + if let Some(BufferType::Shm) = buffer_type(¶ms.buffer) { + if with_buffer_contents(¶ms.buffer, |_, info| { + info.format != ShmFormat::Abgr8888 && info.format != ShmFormat::Xbgr8888 + }) + .unwrap() + { + slog_scope::warn!("Error during screencopy session: Invalid shm buffer format"); + session.failed(FailureReason::InvalidBuffer); + return; + } + } + + if on_damage { + match session.session_type() { + SessionType::Output(output) => { + output + .user_data() + .insert_if_missing(PendingScreencopyBuffers::default); + output + .user_data() + .get::() + .unwrap() + .borrow_mut() + .push((session, params)); + } + SessionType::Workspace(_output, handle) => { + match self.common.shell.space_for_handle_mut(&handle) { + Some(workspace) => workspace.pending_buffers.push((session, params)), + None => session.failed(FailureReason::OutputDisabled), + }; + } + SessionType::Window(window) => { + window + .user_data() + .insert_if_missing(PendingScreencopyBuffers::default); + window + .user_data() + .get::() + .unwrap() + .borrow_mut() + .push((session, params)); + } + _ => unreachable!(), + }; + } else { + let result = match session.session_type() { + SessionType::Output(output) => { + render_output_to_buffer(self, &session, params, &output) + } + SessionType::Workspace(output, handle) => { + render_workspace_to_buffer(self, &session, params, &output, &handle) + } + SessionType::Window(window) => { + render_window_to_buffer(self, &session, params, &window) + } + _ => unreachable!("Session types not supported"), + }; + + match result { + Ok(false) => { + // client didn't wanna wait for damage, so it gets empty damage + session.commit_buffer( + match session.session_type() { + SessionType::Output(output) | SessionType::Workspace(output, _) => { + output.current_transform() + } + _ => Transform::Normal, + }, + Vec::new(), + None, + ); + } + Ok(true) => {} // success + Err((reason, error)) => { + slog_scope::warn!("Error during screencopy session: {}", error); + session.failed(reason); + } + } + } + } + + fn cursor_session_destroyed(&mut self, _session: CursorSession) { + unreachable!("We currently don't support cursor sessions"); + } + + fn session_destroyed(&mut self, session: Session) { + match session.session_type() { + SessionType::Output(output) => { + if let Some(pending_buffers) = output.user_data().get::() + { + pending_buffers.borrow_mut().retain(|(s, _)| s != &session); + } + if let Some(sessions) = output.user_data().get::() { + sessions.0.borrow_mut().retain(|s| s != &session); + } + } + SessionType::Workspace(_, handle) => { + if let Some(workspace) = self.common.shell.space_for_handle_mut(&handle) { + workspace.pending_buffers.retain(|(s, _)| s != &session); + workspace.screencopy_sessions.retain(|s| s != &session); + } + } + SessionType::Window(window) => { + if let Some(pending_buffers) = window.user_data().get::() + { + pending_buffers.borrow_mut().retain(|(s, _)| s != &session); + } + if let Some(sessions) = window.user_data().get::() { + sessions.0.borrow_mut().retain(|s| s != &session); + } + } + _ => {} + } + } +} + +fn formats_for_output( + output: &Output, + backend: &mut BackendData, +) -> Result, FailureReason> { + let mode = match output.current_mode() { + Some(mode) => mode.size.to_logical(1).to_buffer(1, Transform::Normal), + None => { + return Err(FailureReason::OutputDisabled); + } + }; + + let mut _kms_renderer = None; + let renderer = match backend { + BackendData::Kms(ref mut kms) => { + let node = kms.target_node_for_output(&output).unwrap_or(kms.primary); + _kms_renderer = Some(kms.api.renderer::(&node, &node).unwrap()); + _kms_renderer.as_mut().unwrap().as_mut() + } + BackendData::Winit(ref mut winit) => winit.backend.renderer(), + BackendData::X11(ref mut x11) => &mut x11.renderer, + _ => unreachable!(), + }; + + let mut formats = vec![ + BufferInfo::Shm { + format: ShmFormat::Abgr8888, + size: mode, + stride: 0, + }, + BufferInfo::Shm { + format: ShmFormat::Xbgr8888, + size: mode, + stride: 0, + }, + ]; + + if let Some(node) = EGLDevice::device_for_display(renderer.egl_context().display()) + .ok() + .and_then(|device| device.try_get_render_node().ok().flatten()) + { + formats.extend( + renderer + .egl_context() + .dmabuf_render_formats() + .iter() + .map(|format| format.code) + .collect::>() + .into_iter() + .map(|format| BufferInfo::Dmabuf { + node, + format, + size: mode, + }), + ); + } + + Ok(formats) +} + +fn node_from_params( + params: &BufferParams, + backend: &BackendData, + output: Option<&Output>, +) -> Option { + match buffer_type(¶ms.buffer) { + Some(BufferType::Dma) if params.node.is_some() => params.node.clone(), + Some(BufferType::Shm) | Some(BufferType::Dma) => match backend { + BackendData::Kms(kms) => Some( + output + .and_then(|output| kms.target_node_for_output(output)) + .unwrap_or(kms.primary), + ), + _ => None, + }, + _ => unreachable!(), + } +} + +fn prepare_renderer( + renderer: &mut R, + buffer: &WlBuffer, +) -> Result<(), ::Error> +where + R: Bind + Offscreen, +{ + if let Ok(dmabuf) = get_dmabuf(buffer) { + renderer.bind(dmabuf)?; + } else { + let size = buffer_dimensions(buffer).unwrap(); + let render_buffer = renderer.create_buffer(size)?; + renderer.bind(render_buffer)?; + }; + Ok(()) +} + +fn submit_buffer( + session: &Session, + buffer: &WlBuffer, + renderer: &mut R, + transform: Transform, + damage: Vec>, +) -> Result<(), ::Error> +where + R: ExportMem, +{ + if matches!(buffer_type(buffer), Some(BufferType::Shm)) { + let buffer_size = buffer_dimensions(buffer).unwrap(); + with_buffer_contents_mut(buffer, |data, _info| { + let mapping = + renderer.copy_framebuffer(Rectangle::from_loc_and_size((0, 0), buffer_size))?; + let gl_data = renderer.map_texture(&mapping)?; + data.copy_from_slice(gl_data); + Ok(()) + }) + .unwrap()?; + } + + session.commit_buffer(transform, damage, None); + + Ok(()) +} + +pub fn render_to_buffer( + node: Option, + renderer: &mut R, + session: &Session, + params: &BufferParams, + transform: Transform, + render_fn: F, +) -> Result> +where + R: Bind + Offscreen + ExportMem, + F: FnOnce( + Option<&DrmNode>, + &mut R, + &mut DamageTrackedRenderer, + usize, + ) -> Result< + (Option>>, RenderElementStates), + DamageTrackedRendererError, + >, +{ + prepare_renderer(renderer, ¶ms.buffer).map_err(DamageTrackedRendererError::Rendering)?; + + let mut dtr = session + .user_data() + .get::() + .unwrap() + .borrow_mut(); + + let res = render_fn(node.as_ref(), renderer, &mut *dtr, params.age as usize)?; + + if let (Some(damage), _) = res { + submit_buffer(session, ¶ms.buffer, renderer, transform, damage) + .map_err(DamageTrackedRendererError::Rendering)?; + Ok(true) + } else { + Ok(false) + } +} + +pub fn render_output_to_buffer( + state: &mut State, + session: &Session, + params: BufferParams, + output: &Output, +) -> Result { + let node = node_from_params(¶ms, &mut state.backend, Some(output)); + let mut _tmp_multirenderer = None; + let renderer = match &mut state.backend { + BackendData::Kms(kms) => { + _tmp_multirenderer = Some( + kms.api + .renderer::(node.as_ref().unwrap(), node.as_ref().unwrap()) + .map_err(|err| (FailureReason::Unspec, err.into()))?, + ); + _tmp_multirenderer.as_mut().unwrap().as_mut() + } + BackendData::Winit(winit) => winit.backend.renderer(), + BackendData::X11(x11) => &mut x11.renderer, + _ => unreachable!(), + }; + + let common = &mut state.common; + render_to_buffer::<_, _, Gles2Renderbuffer>( + node, + renderer, + session, + ¶ms, + output.current_transform(), + |node, renderer, dtr, age| { + render_output::<_, Gles2Renderbuffer, Dmabuf>( + node, + renderer, + dtr, + age, + common, + &output, + match session.cursor_mode() { + ScreencopyCursorMode::Embedded => CursorMode::All, + ScreencopyCursorMode::Captured(_) | ScreencopyCursorMode::None => { + CursorMode::None + } + }, + None, + ) + }, + ) + .map_err(|err| (FailureReason::Unspec, err.into())) +} + +pub fn render_workspace_to_buffer( + state: &mut State, + session: &Session, + params: BufferParams, + output: &Output, + handle: &WorkspaceHandle, +) -> Result { + let node = node_from_params(¶ms, &mut state.backend, Some(output)); + let mut _tmp_multirenderer = None; + let renderer = match &mut state.backend { + BackendData::Kms(kms) => { + _tmp_multirenderer = Some( + kms.api + .renderer::(node.as_ref().unwrap(), node.as_ref().unwrap()) + .map_err(|err| (FailureReason::Unspec, err.into()))?, + ); + _tmp_multirenderer.as_mut().unwrap().as_mut() + } + BackendData::Winit(winit) => winit.backend.renderer(), + BackendData::X11(x11) => &mut x11.renderer, + _ => unreachable!(), + }; + + let common = &mut state.common; + render_to_buffer::<_, _, Gles2Renderbuffer>( + node, + renderer, + session, + ¶ms, + output.current_transform(), + |node, renderer, dtr, age| { + render_workspace::<_, Gles2Renderbuffer, Dmabuf>( + node, + renderer, + dtr, + age, + common, + &output, + handle, + match session.cursor_mode() { + ScreencopyCursorMode::Embedded => CursorMode::All, + ScreencopyCursorMode::Captured(_) | ScreencopyCursorMode::None => { + CursorMode::None + } + }, + None, + ) + }, + ) + .map_err(|err| (FailureReason::Unspec, err.into())) +} + +pub fn render_window_to_buffer( + state: &mut State, + session: &Session, + params: BufferParams, + window: &Window, +) -> Result { + let geometry = window.geometry(); + + let node = node_from_params(¶ms, &mut state.backend, None); + let mut _tmp_multirenderer = None; + let renderer = match &mut state.backend { + BackendData::Kms(kms) => { + _tmp_multirenderer = Some( + kms.api + .renderer::(node.as_ref().unwrap(), node.as_ref().unwrap()) + .map_err(|err| (FailureReason::Unspec, err.into()))?, + ); + _tmp_multirenderer.as_mut().unwrap().as_mut() + } + BackendData::Winit(winit) => winit.backend.renderer(), + BackendData::X11(x11) => &mut x11.renderer, + _ => unreachable!(), + }; + + render_to_buffer::<_, _, Gles2Renderbuffer>( + node, + renderer, + session, + ¶ms, + Transform::Normal, + |_node, renderer, dtr, age| { + // TODO cursor elements! + let elements = + AsRenderElements::::render_elements::( + window, + (-geometry.loc.x, -geometry.loc.y).into(), + Scale::from(1.0), + ); + + dtr.render_output(renderer, age, &elements, CLEAR_COLOR, None) + }, + ) + .map_err(|err| (FailureReason::Unspec, err.into())) +} + +impl Common { + pub fn still_pending(&mut self, session: Session, params: BufferParams) { + match session.session_type() { + SessionType::Output(output) => { + if output + .user_data() + .get::() + .map_or(false, |sessions| { + sessions.0.borrow().iter().any(|s| &*s == &session) + }) + { + output + .user_data() + .get::() + .unwrap() + .borrow_mut() + .push((session, params)); + } + } + SessionType::Workspace(_output, handle) => { + if let Some(space) = self.shell.space_for_handle_mut(&handle) { + if space.screencopy_sessions.iter().any(|s| s == &session) { + space.pending_buffers.push((session, params)); + } + } + } + SessionType::Window(window) => { + if window + .user_data() + .get::() + .map_or(false, |sessions| { + sessions.0.borrow().iter().any(|s| &*s == &session) + }) + { + window + .user_data() + .get::() + .unwrap() + .borrow_mut() + .push((session, params)); + } + } + _ => {} + } + } +} + +pub trait UserdataExt { + fn sessions(&self) -> Vec; + fn pending_buffers( + &self, + ) -> std::iter::Flatten>>; +} + +impl UserdataExt for Output { + fn sessions(&self) -> Vec { + self.user_data() + .get::() + .map_or(Vec::new(), |sessions| { + sessions.0.borrow().iter().map(|s| s.0.clone()).collect() + }) + } + + fn pending_buffers( + &self, + ) -> std::iter::Flatten>> + { + self.user_data() + .get::() + .map(|pending| pending.borrow_mut().split_off(0).into_iter()) + .into_iter() + .flatten() + } +} + +impl UserdataExt for Window { + fn sessions(&self) -> Vec { + self.user_data() + .get::() + .map_or(Vec::new(), |sessions| { + sessions.0.borrow().iter().map(|s| s.0.clone()).collect() + }) + } + + fn pending_buffers( + &self, + ) -> std::iter::Flatten>> + { + self.user_data() + .get::() + .map(|pending| pending.borrow_mut().split_off(0).into_iter()) + .into_iter() + .flatten() + } +} diff --git a/src/wayland/handlers/xdg_shell/mod.rs b/src/wayland/handlers/xdg_shell/mod.rs index 2d87311a..c012cd1b 100644 --- a/src/wayland/handlers/xdg_shell/mod.rs +++ b/src/wayland/handlers/xdg_shell/mod.rs @@ -20,8 +20,7 @@ use smithay::{ wayland::{ seat::WaylandFocus, shell::xdg::{ - Configure, PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, - XdgShellState, + PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState, }, }, }; @@ -68,18 +67,6 @@ impl XdgShellHandler for State { } } - fn ack_configure(&mut self, surface: WlSurface, configure: Configure) { - if let Configure::Toplevel(configure) = configure { - // If we would re-position the window inside the grab we would get a weird jittery animation. - // We only want to resize once the client has acknoledged & commited the new size, - // so we need to carefully track the state through different handlers. - if let Some(mapped) = self.common.shell.element_for_surface(&surface) { - //TODO - //crate::shell::layout::floating::ResizeSurfaceGrab::ack_configure(window, configure) - } - } - } - fn grab(&mut self, surface: PopupSurface, seat: WlSeat, serial: Serial) { let seat = Seat::from_resource(&seat).unwrap(); let kind = PopupKind::Xdg(surface); diff --git a/src/wayland/protocols/screencopy.rs b/src/wayland/protocols/screencopy.rs index acd51141..677ee2ea 100644 --- a/src/wayland/protocols/screencopy.rs +++ b/src/wayland/protocols/screencopy.rs @@ -6,8 +6,10 @@ use std::{ }; use cosmic_protocols::screencopy::v1::server::{ - zcosmic_screencopy_manager_v1::{self, CursorMode, ZcosmicScreencopyManagerV1}, - zcosmic_screencopy_session_v1::{self, FailureReason, InputType, ZcosmicScreencopySessionV1}, + zcosmic_screencopy_manager_v1::{self, CursorMode as WlCursorMode, ZcosmicScreencopyManagerV1}, + zcosmic_screencopy_session_v1::{ + self, BufferType, FailureReason, InputType, ZcosmicScreencopySessionV1, + }, }; use smithay::{ backend::{ @@ -15,15 +17,20 @@ use smithay::{ drm::{DrmNode, NodeType}, }, desktop::Window, + input::{Seat, SeatHandler}, + output::Output, reexports::wayland_server::{ - protocol::{ - wl_buffer::WlBuffer, wl_output::WlOutput, wl_seat::WlSeat, wl_shm::Format as ShmFormat, - }, + protocol::{wl_buffer::WlBuffer, wl_output, wl_seat::WlSeat, wl_shm::Format as ShmFormat}, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, }, - utils::{user_data::UserDataMap, Buffer, Point, Rectangle, Size, Transform}, + utils::{user_data::UserDataMap, Buffer, IsAlive, Physical, Point, Rectangle, Size, Transform}, }; -use wayland_backend::{protocol::WEnum, server::GlobalId}; +use wayland_backend::{ + protocol::WEnum, + server::{GlobalId, ObjectId}, +}; + +use crate::state::State; use super::{ toplevel_info::window_from_handle, @@ -36,7 +43,7 @@ pub struct ScreencopyState { } pub struct ScreencopyGlobalData { - cursor_modes: Vec, + cursor_modes: Vec, filter: Box Fn(&'a Client) -> bool + Send + Sync>, } @@ -54,7 +61,7 @@ impl ScreencopyState { + ScreencopyHandler + WorkspaceHandler + 'static, - I: IntoIterator, + I: IntoIterator, F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static, { ScreencopyState { @@ -92,6 +99,7 @@ pub enum BufferInfo { pub struct SessionDataInnerInner { gone: bool, pending_buffer: Option, + _type: SessionType, aux: AuxData, } @@ -104,12 +112,29 @@ impl SessionDataInnerInner { } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum SessionType { + Output(Output), + Workspace(Output, WorkspaceHandle), + Window(Window), + Cursor(Seat), + #[doc(hidden)] + Unknown, +} + #[derive(Debug)] enum AuxData { - Normal { cursor_sessions: Vec }, + Normal { cursor: CursorMode }, Cursor { seat: WlSeat }, } +#[derive(Debug, Clone, PartialEq)] +pub enum CursorMode { + Captured(Vec), + Embedded, + None, +} + impl AuxData { pub fn seat(&self) -> &WlSeat { match self { @@ -118,10 +143,19 @@ impl AuxData { } } - pub fn cursor_sessions(&self) -> &[CursorSession] { + pub fn cursor(&self) -> &CursorMode { match self { - AuxData::Normal { cursor_sessions } => &cursor_sessions, - _ => unreachable!("Unwrapped cursor_session from aux data"), + AuxData::Normal { cursor } => &cursor, + _ => unreachable!("Unwrapped cursor from aux data"), + } + } +} + +impl CursorMode { + pub fn sessions<'a>(&'a self) -> impl Iterator { + match self { + CursorMode::Captured(sessions) => Some(sessions.iter()).into_iter().flatten(), + _ => None.into_iter().flatten(), } } } @@ -136,65 +170,197 @@ pub type SessionData = Arc; #[derive(Debug, Clone)] pub struct Session { - obj: ZcosmicScreencopySessionV1, + obj: SessionResource, data: SessionData, } +#[derive(Debug, Clone)] +enum SessionResource { + Alive(ZcosmicScreencopySessionV1), + Destroyed(ObjectId), +} + +impl SessionResource { + fn client(&self) -> Option { + match self { + SessionResource::Alive(obj) => obj.client(), + _ => None, + } + } + + fn buffer_info( + &self, + _type: BufferType, + node: Option, + format: u32, + width: u32, + height: u32, + stride: u32, + ) { + if let SessionResource::Alive(obj) = self { + obj.buffer_info(_type, node, format, width, height, stride) + } + } + + fn init_done(&self) { + if let SessionResource::Alive(obj) = self { + obj.init_done() + } + } + + fn transform(&self, transform: wl_output::Transform) { + if let SessionResource::Alive(obj) = self { + obj.transform(transform) + } + } + + fn damage(&self, x: u32, y: u32, w: u32, h: u32) { + if let SessionResource::Alive(obj) = self { + obj.damage(x, y, w, h) + } + } + + fn commit_time(&self, time_sec_hi: u32, time_sec_lo: u32, time_nsec: u32) { + if let SessionResource::Alive(obj) = self { + obj.commit_time(time_sec_hi, time_sec_lo, time_nsec) + } + } + + fn ready(&self) { + if let SessionResource::Alive(obj) = self { + obj.ready() + } + } + + fn failed(&self, reason: FailureReason) { + if let SessionResource::Alive(obj) = self { + obj.failed(reason) + } + } + + fn cursor_enter(&self, wl_seat: &WlSeat, input_type: InputType) { + if let SessionResource::Alive(obj) = self { + obj.cursor_enter(wl_seat, input_type) + } + } + + fn cursor_info( + &self, + wl_seat: &WlSeat, + input_type: InputType, + x: i32, + y: i32, + w: i32, + h: i32, + dx: i32, + dy: i32, + ) { + if let SessionResource::Alive(obj) = self { + obj.cursor_info(wl_seat, input_type, x, y, w, h, dx, dy) + } + } + + fn cursor_leave(&self, wl_seat: &WlSeat, input_type: InputType) { + if let SessionResource::Alive(obj) = self { + obj.cursor_leave(wl_seat, input_type) + } + } +} + +impl PartialEq for SessionResource { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (SessionResource::Alive(obj1), SessionResource::Alive(obj2)) => obj1 == obj2, + (SessionResource::Alive(obj), SessionResource::Destroyed(id)) + | (SessionResource::Destroyed(id), SessionResource::Alive(obj)) => obj.id() == *id, + (SessionResource::Destroyed(id1), SessionResource::Destroyed(id2)) => id1 == id2, + } + } +} + impl PartialEq for Session { fn eq(&self, other: &Self) -> bool { self.obj == other.obj } } +// TODO: Handle Alive + +// TODO: Better errors + impl Session { - pub fn cursor_enter(&self, seat: &WlSeat, input_type: InputType) { - self.obj.cursor_enter(seat, input_type) + pub fn cursor_enter(&self, seat: &Seat, input_type: InputType) { + if !self.alive() { + return; + } + if let Some(client) = self.obj.client() { + for wl_seat in seat.client_seats(&client) { + self.obj.cursor_enter(&wl_seat, input_type) + } + } } - pub fn cursor_info( + pub fn cursor_info( &self, - seat: &WlSeat, + seat: &Seat, input_type: InputType, geometry: Rectangle, offset: Point, ) { - self.obj.cursor_info( - seat, - input_type, - geometry.loc.x, - geometry.loc.y, - geometry.size.w, - geometry.size.h, - offset.x, - offset.y, - ); - let data = self.data.inner.lock().unwrap(); - for cursor_session in data.aux.cursor_sessions() { - cursor_session.obj.cursor_info( - seat, - input_type, - geometry.loc.x, - geometry.loc.y, - geometry.size.w, - geometry.size.h, - offset.x, - offset.y, - ); + if !self.alive() { + return; + } + if let Some(client) = self.obj.client() { + for wl_seat in seat.client_seats(&client) { + self.obj.cursor_info( + &wl_seat, + input_type, + geometry.loc.x, + geometry.loc.y, + geometry.size.w, + geometry.size.h, + offset.x, + offset.y, + ); + let data = self.data.inner.lock().unwrap(); + for cursor_session in data.aux.cursor().sessions() { + cursor_session.obj.cursor_info( + &wl_seat, + input_type, + geometry.loc.x, + geometry.loc.y, + geometry.size.w, + geometry.size.h, + offset.x, + offset.y, + ); + } + } } } - pub fn cursor_leave(&self, seat: &WlSeat, input_type: InputType) { - self.obj.cursor_leave(seat, input_type) + pub fn cursor_leave(&self, seat: &Seat, input_type: InputType) { + if !self.alive() { + return; + } + if let Some(client) = self.obj.client() { + for wl_seat in seat.client_seats(&client) { + self.obj.cursor_leave(&wl_seat, input_type) + } + } } pub fn cursor_sessions(&self) -> impl Iterator { + if !self.alive() { + return Vec::new().into_iter(); + } self.data .inner .lock() .unwrap() .aux - .cursor_sessions() - .iter() + .cursor() + .sessions() .cloned() .collect::>() .into_iter() @@ -203,9 +369,12 @@ impl Session { pub fn commit_buffer( &self, transform: Transform, - damage: Vec>, + damage: Vec>, time: Option, ) { + if !self.alive() { + return; + } self.obj.transform(transform.into()); for rect in damage { self.obj.damage( @@ -225,6 +394,9 @@ impl Session { } pub fn failed(&self, reason: FailureReason) { + if !self.alive() { + return; + } self.obj.failed(reason); self.data.inner.lock().unwrap().gone = true; } @@ -232,11 +404,25 @@ impl Session { pub fn user_data(&self) -> &UserDataMap { &self.data.user_data } + + pub fn session_type(&self) -> SessionType { + self.data.inner.lock().unwrap()._type.clone() + } + + pub fn cursor_mode(&self) -> CursorMode { + self.data.inner.lock().unwrap().aux.cursor().clone() + } +} + +impl IsAlive for Session { + fn alive(&self) -> bool { + !self.data.inner.lock().unwrap().gone + } } #[derive(Debug, Clone)] pub struct CursorSession { - obj: ZcosmicScreencopySessionV1, + obj: SessionResource, data: SessionData, } @@ -281,7 +467,13 @@ impl CursorSession { } } -#[derive(Debug)] +impl IsAlive for CursorSession { + fn alive(&self) -> bool { + !self.data.inner.lock().unwrap().gone + } +} + +#[derive(Debug, Clone)] pub struct BufferParams { pub buffer: WlBuffer, pub node: Option, @@ -289,33 +481,28 @@ pub struct BufferParams { } pub trait ScreencopyHandler { - fn capture_output( - &mut self, - output: WlOutput, - cursor: CursorMode, - session: Session, - ) -> Vec; + fn capture_output(&mut self, output: Output, session: Session) -> Vec; fn capture_workspace( &mut self, workspace: WorkspaceHandle, - output: WlOutput, - cursor: CursorMode, + output: Output, session: Session, ) -> Vec; - fn capture_toplevel( - &mut self, - toplevel: Window, - cursor: CursorMode, - session: Session, - ) -> Vec; + fn capture_toplevel(&mut self, toplevel: Window, session: Session) -> Vec; fn capture_cursor(&mut self, session: CursorSession) -> Vec; fn buffer_attached(&mut self, session: Session, buffer: BufferParams, on_damage: bool); - fn session_destroyed(&mut self, session: Session); + fn cursor_session_destroyed(&mut self, session: CursorSession) { + let _ = session; + } + + fn session_destroyed(&mut self, session: Session) { + let _ = session; + } } impl GlobalDispatch for ScreencopyState @@ -349,8 +536,9 @@ where fn init_session( data_init: &mut DataInit<'_, D>, session: New, - cursor: WEnum, -) -> Option<(Session, CursorMode)> + cursor: WEnum, + _type: SessionType, +) -> Option where D: GlobalDispatch + Dispatch @@ -364,25 +552,29 @@ where gone: false, pending_buffer: None, aux: AuxData::Normal { - cursor_sessions: Vec::new(), + cursor: match cursor.into_result() { + Ok(WlCursorMode::Capture) => CursorMode::Captured(Vec::new()), + Ok(WlCursorMode::Embedded) => CursorMode::Embedded, + _ => CursorMode::None, + }, }, + _type, }), user_data: UserDataMap::new(), }); let session = data_init.init(session, data.clone()); - let cursor_mode = match cursor.into_result() { - Ok(mode) => mode, - Err(err) => { - slog_scope::warn!("Client did send unknown cursor mode: {}", err); - session.failed(FailureReason::UnknownInput); - return None; - } + if let Err(err) = cursor.into_result() { + slog_scope::warn!("Client did send unknown cursor mode: {}", err); + session.failed(FailureReason::UnknownInput); + return None; + }; + let session = Session { + obj: SessionResource::Alive(session), + data, }; - let session = Session { obj: session, data }; - - Some((session, cursor_mode)) + Some(session) } impl Dispatch for ScreencopyState @@ -408,68 +600,119 @@ where session, output, cursor, - } => { - let (session, cursor_mode) = match init_session(data_init, session, cursor) { - Some(result) => result, - None => { - return; + } => match Output::from_resource(&output) { + Some(output) => { + let session = match init_session( + data_init, + session, + cursor, + SessionType::Output(output.clone()), + ) { + Some(result) => result, + None => { + return; + } + }; + let formats = state.capture_output(output, session.clone()); + if !session.data.inner.lock().unwrap().gone { + send_formats(&session.obj, formats); } - }; - let formats = state.capture_output(output, cursor_mode, session.clone()); - if !session.data.inner.lock().unwrap().gone { - send_formats(&session.obj, formats); } - } + None => { + let session = + match init_session(data_init, session, cursor, SessionType::Unknown) { + Some(result) => result, + None => { + return; + } + }; + session.failed(FailureReason::InvalidOutput); + return; + } + }, zcosmic_screencopy_manager_v1::Request::CaptureToplevel { session, toplevel, cursor, - } => { - let (session, cursor_mode) = match init_session(data_init, session, cursor) { - Some(result) => result, - None => { - return; - } - }; - match window_from_handle(toplevel) { - Some(window) => { - let formats = state.capture_toplevel(window, cursor_mode, session.clone()); - if !session.data.inner.lock().unwrap().gone { - send_formats(&session.obj, formats); + } => match window_from_handle(toplevel) { + Some(window) => { + let session = match init_session( + data_init, + session, + cursor, + SessionType::Window(window.clone()), + ) { + Some(result) => result, + None => { + return; } - } - None => { - session.obj.failed(FailureReason::ToplevelDestroyed); - return; + }; + + let formats = state.capture_toplevel(window, session.clone()); + if !session.data.inner.lock().unwrap().gone { + send_formats(&session.obj, formats); } } - } + None => { + let session = + match init_session(data_init, session, cursor, SessionType::Unknown) { + Some(result) => result, + None => { + return; + } + }; + session.obj.failed(FailureReason::ToplevelDestroyed); + return; + } + }, zcosmic_screencopy_manager_v1::Request::CaptureWorkspace { session, workspace, output, cursor, - } => { - let (session, cursor_mode) = match init_session(data_init, session, cursor) { - Some(result) => result, - None => { - return; - } - }; - match state.workspace_state().workspace_handle(&workspace) { + } => match Output::from_resource(&output) { + Some(output) => match state.workspace_state().workspace_handle(&workspace) { Some(handle) => { - let formats = - state.capture_workspace(handle, output, cursor_mode, session.clone()); + let session = match init_session( + data_init, + session, + cursor, + SessionType::Workspace(output.clone(), handle.clone()), + ) { + Some(result) => result, + None => { + return; + } + }; + let formats = state.capture_workspace(handle, output, session.clone()); if !session.data.inner.lock().unwrap().gone { send_formats(&session.obj, formats); } } None => { + let session = + match init_session(data_init, session, cursor, SessionType::Unknown) { + Some(result) => result, + None => { + return; + } + }; session.failed(FailureReason::InvalidOutput); return; } + }, + None => { + let session = + match init_session(data_init, session, cursor, SessionType::Unknown) { + Some(result) => result, + None => { + return; + } + }; + session.failed(FailureReason::InvalidOutput); + return; } - } + }, _ => {} } } @@ -481,6 +724,7 @@ where + Dispatch + Dispatch + ScreencopyHandler + + WorkspaceHandler + 'static, { fn request( @@ -493,31 +737,55 @@ where data_init: &mut DataInit<'_, D>, ) { match request { - zcosmic_screencopy_session_v1::Request::CaptureCursor { session, seat } => { - { - let resource_data = data.inner.lock().unwrap(); - if resource_data.is_cursor() || resource_data.gone { - resource.failed(FailureReason::Unspec); - return; + zcosmic_screencopy_session_v1::Request::CaptureCursor { + session, + seat: wl_seat, + } => match Seat::from_resource(&wl_seat) { + Some(seat) => { + { + let resource_data = data.inner.lock().unwrap(); + if resource_data.is_cursor() || resource_data.gone { + resource.failed(FailureReason::Unspec); + return; + } + } + + let data = Arc::new(SessionDataInner { + inner: Mutex::new(SessionDataInnerInner { + gone: false, + pending_buffer: None, + aux: AuxData::Cursor { seat: wl_seat }, + _type: SessionType::Cursor(seat), + }), + user_data: UserDataMap::new(), + }); + let session = data_init.init(session, data.clone()); + + let cursor_session = CursorSession { + obj: SessionResource::Alive(session), + data, + }; + let formats = state.capture_cursor(cursor_session.clone()); + if !cursor_session.data.inner.lock().unwrap().gone { + send_formats(&cursor_session.obj, formats); } } - - let data = Arc::new(SessionDataInner { - inner: Mutex::new(SessionDataInnerInner { - gone: false, - pending_buffer: None, - aux: AuxData::Cursor { seat }, - }), - user_data: UserDataMap::new(), - }); - let session = data_init.init(session, data.clone()); - - let cursor_session = CursorSession { obj: session, data }; - let formats = state.capture_cursor(cursor_session.clone()); - if !cursor_session.data.inner.lock().unwrap().gone { - send_formats(&cursor_session.obj, formats); + None => { + let session = match init_session( + data_init, + session, + WEnum::Value(WlCursorMode::Capture), + SessionType::Unknown, + ) { + Some(result) => result, + None => { + return; + } + }; + session.failed(FailureReason::UnknownInput); + return; } - } + }, zcosmic_screencopy_session_v1::Request::AttachBuffer { buffer, node, age } => { if data.inner.lock().unwrap().gone { resource.failed(FailureReason::Unspec); @@ -541,7 +809,7 @@ where if let Some(buffer) = data.inner.lock().unwrap().pending_buffer.take() { let session = Session { - obj: resource.clone(), + obj: SessionResource::Alive(resource.clone()), data: data.clone(), }; state.buffer_attached( @@ -563,9 +831,30 @@ where _ => {} } } + + fn destroyed( + state: &mut D, + _client: wayland_backend::server::ClientId, + resource: wayland_backend::server::ObjectId, + data: &SessionData, + ) { + if data.inner.lock().unwrap().is_cursor() { + let session = CursorSession { + obj: SessionResource::Destroyed(resource), + data: data.clone(), + }; + state.cursor_session_destroyed(session) + } else { + let session = Session { + obj: SessionResource::Destroyed(resource), + data: data.clone(), + }; + state.session_destroyed(session) + } + } } -fn send_formats(session: &ZcosmicScreencopySessionV1, formats: Vec) { +fn send_formats(session: &SessionResource, formats: Vec) { for format in formats { match format { BufferInfo::Dmabuf { node, format, size } => { diff --git a/src/wayland/protocols/toplevel_info.rs b/src/wayland/protocols/toplevel_info.rs index b9d154a3..79cc8848 100644 --- a/src/wayland/protocols/toplevel_info.rs +++ b/src/wayland/protocols/toplevel_info.rs @@ -413,9 +413,9 @@ fn send_toplevel_to_client( .iter() .filter(|o| !handle_state.outputs.contains(o)) { - new_output.with_client_outputs(&client, |wl_output| { - instance.output_enter(wl_output); - }); + for wl_output in new_output.client_outputs(&client) { + instance.output_enter(&wl_output); + } changed = true; } for old_output in handle_state @@ -423,9 +423,9 @@ fn send_toplevel_to_client( .iter() .filter(|o| !state.outputs.contains(o)) { - old_output.with_client_outputs(&client, |wl_output| { - instance.output_leave(wl_output); - }); + for wl_output in old_output.client_outputs(&client) { + instance.output_leave(&wl_output); + } changed = true; } handle_state.outputs = state.outputs.clone(); diff --git a/src/wayland/protocols/toplevel_management.rs b/src/wayland/protocols/toplevel_management.rs index 46b95153..4dbad39f 100644 --- a/src/wayland/protocols/toplevel_management.rs +++ b/src/wayland/protocols/toplevel_management.rs @@ -182,12 +182,12 @@ where let window = window_from_handle(toplevel).unwrap(); if let Some(toplevel_state) = window.user_data().get::() { let mut toplevel_state = toplevel_state.lock().unwrap(); - if let Some(client) = surface.client_id() { + if let Some(client) = surface.client() { if width == 0 && height == 0 { - toplevel_state.rectangles.remove(&client); + toplevel_state.rectangles.remove(&client.id()); } else { toplevel_state.rectangles.insert( - client, + client.id(), ( surface, Rectangle::from_loc_and_size((x, y), (width, height)), @@ -207,7 +207,7 @@ where if !mng_state .instances .iter() - .any(|i| i.client_id().map(|c| c == client).unwrap_or(false)) + .any(|i| i.client().map(|c| c.id() == client).unwrap_or(false)) { for toplevel in state.toplevel_info_state_mut().toplevels.iter() { if let Some(toplevel_state) = toplevel.user_data().get::() { diff --git a/src/wayland/protocols/workspace.rs b/src/wayland/protocols/workspace.rs index 7915b805..6853b7eb 100644 --- a/src/wayland/protocols/workspace.rs +++ b/src/wayland/protocols/workspace.rs @@ -821,9 +821,9 @@ where .iter() .filter(|o| !handle_state.outputs.contains(o)) { - new_output.with_client_outputs(&client, |wl_output| { - instance.output_enter(wl_output); - }); + for wl_output in new_output.client_outputs(&client) { + instance.output_enter(&wl_output); + } changed = true; } for old_output in handle_state @@ -831,9 +831,9 @@ where .iter() .filter(|o| !group.outputs.contains(o)) { - old_output.with_client_outputs(&client, |wl_output| { - instance.output_leave(wl_output); - }); + for wl_output in old_output.client_outputs(&client) { + instance.output_leave(&wl_output); + } changed = true; } handle_state.outputs = group.outputs.clone();