From 00f1b029da61d1cedf678a33bb21831fe773778e Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 28 Sep 2022 12:01:29 +0200 Subject: [PATCH 01/62] wip: New shell logic --- Cargo.lock | 7 - Cargo.toml | 1 - config.ron | 3 +- src/backend/kms/mod.rs | 55 +- src/backend/mod.rs | 26 +- src/backend/render/cursor.rs | 222 ++- src/backend/render/mod.rs | 423 ++---- src/backend/winit.rs | 46 +- src/backend/x11.rs | 25 +- src/config/mod.rs | 6 +- src/input/mod.rs | 342 ++--- src/main.rs | 2 +- src/shell/element/mod.rs | 631 +++++++++ src/shell/element/stack.rs | 293 ++++ src/shell/element/window.rs | 231 ++++ src/shell/focus/mod.rs | 231 ++-- src/shell/focus/target.rs | 274 +++- src/shell/grabs.rs | 11 +- src/shell/layout/floating/grabs.rs | 9 +- src/shell/layout/floating/mod.rs | 322 +++-- src/shell/layout/mod.rs | 19 +- src/shell/layout/tiling/mod.rs | 1165 ++++++++++------ src/shell/mod.rs | 1223 +++++++++-------- src/shell/workspace.rs | 367 ++++- src/state.rs | 152 +- src/utils/prelude.rs | 32 +- src/wayland/handlers/compositor.rs | 11 +- src/wayland/handlers/export_dmabuf.rs | 2 +- src/wayland/handlers/layer_shell.rs | 4 +- src/wayland/handlers/mod.rs | 2 +- src/wayland/handlers/seat.rs | 26 +- src/wayland/handlers/toplevel_management.rs | 33 +- src/wayland/handlers/workspace.rs | 15 +- src/wayland/handlers/xdg_shell/mod.rs | 113 +- src/wayland/handlers/xdg_shell/popup.rs | 86 +- src/wayland/protocols/mod.rs | 2 +- src/wayland/protocols/output_configuration.rs | 5 +- src/wayland/protocols/toplevel_info.rs | 4 +- src/wayland/protocols/workspace.rs | 4 +- 39 files changed, 3922 insertions(+), 2503 deletions(-) create mode 100644 src/shell/element/mod.rs create mode 100644 src/shell/element/stack.rs create mode 100644 src/shell/element/window.rs diff --git a/Cargo.lock b/Cargo.lock index 3e15fee6..df0d2da0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,12 +83,6 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "983cd8b9d4b02a6dc6ffa557262eb5858a27a0038ffffe21a0f133eaa819a164" -[[package]] -name = "atomic_float" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62af46d040ba9df09edc6528dae9d8e49f5f3e82f55b7d2ec31a733c38dbc49d" - [[package]] name = "atomic_refcell" version = "0.1.8" @@ -292,7 +286,6 @@ name = "cosmic-comp" version = "0.1.0" dependencies = [ "anyhow", - "atomic_float", "bitflags", "cosmic-protocols", "edid-rs", diff --git a/Cargo.toml b/Cargo.toml index d351a463..43310f4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,6 @@ xkbcommon = "0.4" indexmap = "1.8.0" xdg = "^2.1" ron = "0.7" -atomic_float = "0.1" libsystemd = "0.5" wayland-backend = "=0.1.0-beta.10" wayland-scanner = "=0.30.0-beta.10" diff --git a/config.ron b/config.ron index 5a28839b..f8a583c0 100644 --- a/config.ron +++ b/config.ron @@ -37,7 +37,7 @@ (modifiers: [Logo], key: "y"): ToggleTiling, (modifiers: [Logo], key: "g"): ToggleWindowFloating, (modifiers: [Logo, Shift], key: "f"): Fullscreen, - (modifiers: [Logo, Shift], key: "s"): Screenshot, + //(modifiers: [Logo, Shift], key: "s"): Screenshot, //TODO: ability to select default web browser (modifiers: [Logo], key: "b"): Spawn("firefox"), //TODO: ability to select default file browser @@ -53,5 +53,6 @@ (modifiers: [], key: "XF86MonBrightnessDown"): Spawn("busctl --user call com.system76.CosmicSettingsDaemon /com/system76/CosmicSettingsDaemon com.system76.CosmicSettingsDaemon DecreaseDisplayBrightness"), }, workspace_mode: OutputBound, + workspace_amount: Dynamic, floating_default: false, ) diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index fd860432..bdb69036 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -20,7 +20,8 @@ use smithay::{ input::InputEvent, libinput::{LibinputInputBackend, LibinputSessionInterface}, renderer::{ - gles2::Gles2Renderbuffer, + damage::DamageTrackedRenderer, + gles2::{Gles2Renderbuffer, Gles2Renderer}, multigpu::{egl::EglGlesBackend, GpuManager}, Bind, }, @@ -51,7 +52,7 @@ use std::{ os::unix::io::{FromRawFd, OwnedFd}, path::PathBuf, rc::Rc, - time::{Duration, Instant}, + time::Duration, }; mod drm_helpers; @@ -60,9 +61,11 @@ mod socket; use session_fd::*; use socket::*; +use super::render::GlMultiRenderer; + pub struct KmsState { devices: HashMap, - pub api: GpuManager, + pub api: GpuManager>, pub primary: DrmNode, session: AutoSession, signaler: Signaler, @@ -83,9 +86,9 @@ pub struct Device { pub struct Surface { surface: Option>>, SessionFd>>, + damage_tracker: DamageTrackedRenderer, connector: connector::Handle, output: Output, - last_render: Option<(Dmabuf, Instant)>, last_submit: Option, refresh_rate: u32, vrr: bool, @@ -142,7 +145,8 @@ pub fn init_backend( .map_err(|err| err.error) .context("Failed to initialize session event source")?; - let api = GpuManager::new(EglGlesBackend, None).context("Failed to initialize renderers")?; + let api = GpuManager::new(EglGlesBackend::::default(), None) + .context("Failed to initialize renderers")?; // TODO get this info from system76-power, if available and setup a watcher let primary = if let Some(path) = std::env::var("COSMIC_RENDER_DEVICE") @@ -370,15 +374,7 @@ impl State { Some(Ok(_)) => { surface.last_submit = metadata.take().map(|data| data.time); surface.pending = false; - data.state - .common - .shell - .active_space_mut(&surface.output) - .space - .send_frames( - data.state.common.start_time.elapsed().as_millis() - as u32, - ); + data.state.common.send_frames(&surface.output); } Some(Err(err)) => { slog_scope::warn!("Failed to submit frame: {}", err) @@ -689,12 +685,12 @@ impl Device { let data = Surface { output: output.clone(), + damage_tracker: DamageTrackedRenderer::from_output(&output), surface: None, connector: conn, vrr, refresh_rate, last_submit: None, - last_render: None, pending: false, render_timer_token: None, #[cfg(feature = "debug")] @@ -717,8 +713,8 @@ fn render_node_for_output( let workspace = shell.active_space(output); let nodes = workspace .get_fullscreen(output) - .map(|w| vec![w]) - .unwrap_or_else(|| workspace.space.windows().collect::>()) + .map(|w| vec![w.clone()]) + .unwrap_or_else(|| workspace.windows().collect::>()) .into_iter() .flat_map(|w| { dh.get_client(w.toplevel().wl_surface().id()) @@ -750,7 +746,7 @@ impl Surface { pub fn render_output( &mut self, dh: &DisplayHandle, - api: &mut GpuManager, + api: &mut GpuManager>, target_node: &DrmNode, state: &mut Common, ) -> Result<()> { @@ -758,12 +754,8 @@ impl Surface { return Ok(()); } - if render::needs_buffer_reset(&self.output, state) { - self.surface.as_mut().unwrap().reset_buffers(); - } - let render_node = render_node_for_output(dh, &self.output, *target_node, &state.shell); - let mut renderer = api.renderer(&render_node, &target_node).unwrap(); + let mut renderer: GlMultiRenderer = api.renderer(&render_node, &target_node).unwrap(); let surface = self.surface.as_mut().unwrap(); let (buffer, age) = surface @@ -777,7 +769,8 @@ impl Surface { match render::render_output( Some(&render_node), &mut renderer, - age, + &mut self.damage_tracker, + age as usize, state, &self.output, false, @@ -785,7 +778,6 @@ impl Surface { Some(&mut self.fps), ) { Ok(_) => { - self.last_render = Some((buffer, Instant::now())); surface .queue_buffer() .with_context(|| "Failed to submit buffer for display")?; @@ -1026,17 +1018,4 @@ impl KmsState { } Ok(()) } - - pub fn capture_output(&self, output: &Output) -> Option<(DrmNode, Dmabuf, Instant)> { - self.devices.values().find_map(|dev| { - dev.surfaces - .values() - .find(|s| &s.output == output) - .and_then(|s| { - s.last_render - .clone() - .map(|(buf, time)| (dev.render_node.clone(), buf, time)) - }) - }) - } } diff --git a/src/backend/mod.rs b/src/backend/mod.rs index a3b198eb..b90ff5c5 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -39,18 +39,22 @@ pub fn init_backend_auto( } } }; + if res.is_ok() { - for seat in &state.common.seats { - let output = state - .common - .shell - .outputs() - .next() - .with_context(|| "Backend initialized without output")? - .clone(); - seat.user_data() - .insert_if_missing(|| crate::input::ActiveOutput(std::cell::RefCell::new(output))); - } + let output = state + .common + .shell + .outputs() + .next() + .with_context(|| "Backend initialized without output")?; + let initial_seat = crate::input::add_seat( + dh, + &mut state.common.seat_state, + output, + &state.common.config, + "seat-0".into(), + ); + state.common.add_seat(initial_seat); } res } diff --git a/src/backend/render/cursor.rs b/src/backend/render/cursor.rs index 7b5e8715..64182f44 100644 --- a/src/backend/render/cursor.rs +++ b/src/backend/render/cursor.rs @@ -2,14 +2,20 @@ use crate::utils::prelude::*; use smithay::{ - backend::renderer::{Frame, ImportAll, ImportMem, Renderer, Texture}, - desktop::space::{RenderElement, SpaceOutputTuple, SurfaceTree}, + backend::renderer::{ + element::{ + surface::{render_elements_from_surface_tree, WaylandSurfaceRenderElement}, + texture::{TextureBuffer, TextureRenderElement}, + }, + ImportAll, ImportMem, Renderer, + }, input::{ pointer::{CursorImageAttributes, CursorImageStatus}, Seat, }, reexports::wayland_server::protocol::wl_surface, - utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Size, Transform}, + render_elements, + utils::{IsAlive, Logical, Point, Scale, Transform}, wayland::compositor::{get_role, with_states}, }; use std::{ @@ -119,13 +125,21 @@ fn load_icon(theme: &CursorTheme) -> Result, Error> { parse_xcursor(&cursor_data).ok_or(Error::Parse) } -pub fn draw_surface_cursor( - surface: wl_surface::WlSurface, +render_elements! { + pub CursorRenderElement where R: ImportAll; + Static=TextureRenderElement<::TextureId>, + Surface=WaylandSurfaceRenderElement, +} + +pub fn draw_surface_cursor( + surface: &wl_surface::WlSurface, location: impl Into>, -) -> SurfaceTree + scale: impl Into>, +) -> Vec> where { let mut position = location.into(); + let scale = scale.into(); let h = with_states(&surface, |states| { states .data_map @@ -136,125 +150,26 @@ where .hotspot }); position -= h; - SurfaceTree { - surface, - position, - z_index: 100, - } + + render_elements_from_surface_tree(surface, position.to_physical_precise_round(scale), scale) } -pub fn draw_dnd_icon( - surface: wl_surface::WlSurface, +pub fn draw_dnd_icon( + surface: &wl_surface::WlSurface, location: impl Into>, -) -> SurfaceTree { + scale: impl Into>, +) -> Vec> { if get_role(&surface) != Some("dnd_icon") { slog_scope::warn!( "Trying to display as a dnd icon a surface that does not have the DndIcon role." ); } - SurfaceTree { + let scale = scale.into(); + render_elements_from_surface_tree( surface, - position: location.into(), - z_index: 100, - } -} - -pub struct PointerElement { - seat_id: usize, - texture: T, - position: Point, - size: Size, - new_frame: bool, -} - -impl PointerElement { - pub fn new( - seat: &Seat, - texture: T, - relative_pointer_pos: Point, - new_frame: bool, - ) -> PointerElement { - let size = texture.size().to_logical(1, Transform::Normal); - PointerElement { - seat_id: seat.id(), - texture, - position: relative_pointer_pos, - size, - new_frame, - } - } -} - -impl RenderElement for PointerElement<::TextureId> -where - R: Renderer + ImportAll, - ::TextureId: 'static, -{ - fn id(&self) -> usize { - self.seat_id - } - - fn location(&self, scale: impl Into>) -> Point { - self.position.to_physical(scale) - } - - fn geometry(&self, scale: impl Into>) -> Rectangle { - Rectangle::from_loc_and_size(self.position, self.size.to_f64()) - .to_physical(scale) - .to_i32_round() - } - - fn accumulated_damage( - &self, - scale: impl Into>, - _: Option>, - ) -> Vec> { - if self.new_frame { - let scale = scale.into(); - vec![Rectangle::from_loc_and_size( - self.position.to_physical(scale).to_i32_round(), - self.size.to_physical_precise_round(scale), - )] - } else { - vec![] - } - } - - fn opaque_regions( - &self, - _scale: impl Into>, - ) -> Option>> { - None - } - - fn draw( - &self, - _renderer: &mut R, - frame: &mut ::Frame, - scale: impl Into>, - position: Point, - damage: &[Rectangle], - _log: &slog::Logger, - ) -> Result<(), ::Error> { - let scale = scale.into(); - frame.render_texture_at( - &self.texture, - position.to_i32_round(), - 1, - scale, - Transform::Normal, - &damage - .iter() - .copied() - .map(|mut rect| { - rect.loc -= self.position.to_physical(scale).to_i32_round(); - rect - }) - .collect::>(), - 1.0, - )?; - Ok(()) - } + location.into().to_physical_precise_round(scale), + scale, + ) } struct CursorState { @@ -273,15 +188,15 @@ impl Default for CursorState { } } -pub fn draw_cursor( +pub fn draw_cursor( renderer: &mut R, seat: &Seat, location: Point, + scale: Scale, start_time: &std::time::Instant, draw_default: bool, -) -> Option +) -> Vec> where - I: From + From::TextureId>>, R: Renderer + ImportAll + ImportMem, ::TextureId: Clone + 'static, { @@ -302,50 +217,69 @@ where }) .unwrap_or(CursorImageStatus::Default); - if let CursorImageStatus::Surface(wl_surface) = cursor_status { - Some(draw_surface_cursor(wl_surface.clone(), location.to_i32_round()).into()) + if let CursorImageStatus::Surface(ref wl_surface) = cursor_status { + return draw_surface_cursor(wl_surface, location.to_i32_round(), scale); } else if draw_default { + let integer_scale = scale.x.max(scale.y).ceil() as u32; + let seat_userdata = seat.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); - let new_frame = state.current_image.borrow().as_ref() != Some(&frame); + .get_image(integer_scale, start_time.elapsed().as_millis() as u32); let mut cache = state.image_cache.borrow_mut(); let pointer_images = cache - .entry((TypeId::of::<::TextureId>(), renderer.id())) + .entry(( + TypeId::of::::TextureId>>(), + renderer.id(), + )) .or_default(); - let pointer_image = pointer_images + + let maybe_image = pointer_images .iter() .find_map(|(image, texture)| if image == &frame { Some(texture) } else { None }) .and_then(|texture| { - texture - .downcast_ref::<::TextureId>() - .cloned() - }) - .unwrap_or_else(|| { - let texture = renderer - .import_memory( - &frame.pixels_rgba, - (frame.width as i32, frame.height as i32).into(), - false, - ) - .expect("Failed to import cursor bitmap"); - pointer_images.push((frame.clone(), Box::new(texture.clone()))); - texture + texture.downcast_ref::::TextureId>>() }); + let pointer_image = match maybe_image { + Some(image) => image, + None => { + let texture = TextureBuffer::from_memory( + renderer, + &frame.pixels_rgba, + (frame.width as i32, frame.height as i32), + false, + integer_scale as i32, + Transform::Normal, + None, + ) + .expect("Failed to import cursor bitmap"); + pointer_images.push((frame.clone(), Box::new(texture.clone()))); + pointer_images + .last() + .and_then(|(_, i)| { + i.downcast_ref::::TextureId>>() + }) + .unwrap() + } + }; + let hotspot = Point::::from((frame.xhot as i32, frame.yhot as i32)).to_f64(); *state.current_image.borrow_mut() = Some(frame); - Some( - PointerElement::new(seat, pointer_image.clone(), location - hotspot, new_frame) - .into(), - ) + return vec![CursorRenderElement::Static( + TextureRenderElement::from_texture_buffer( + (location - hotspot).to_physical(scale), + pointer_image, + None, + None, + ), + )]; } else { - None + Vec::new() } } } diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 5692974e..0ba64e8d 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -6,162 +6,74 @@ use crate::{ state::Fps, utils::prelude::*, }; +//grabs::{MoveGrabRenderElement, SeatMoveGrabState}, use crate::{ - shell::grabs::{MoveGrabRenderElement, SeatMoveGrabState}, - state::Common, - wayland::handlers::data_device::get_dnd_icon, + shell::WorkspaceRenderElement, state::Common, wayland::handlers::data_device::get_dnd_icon, }; -use slog::Logger; use smithay::{ backend::{ drm::DrmNode, renderer::{ - gles2::{Gles2Renderbuffer, Gles2Renderer, Gles2Texture}, - multigpu::{egl::EglGlesBackend, Error as MultiError, MultiFrame, MultiRenderer}, - Frame, ImportAll, Renderer, + damage::{ + DamageTrackedRenderer, DamageTrackedRendererError as RenderError, OutputNoMode, + }, + gles2::{Gles2Renderbuffer, Gles2Renderer}, + multigpu::{egl::EglGlesBackend, MultiFrame, MultiRenderer}, + ImportAll, ImportMem, Renderer, }, }, - desktop::{ - draw_layer_popups, draw_layer_surface, draw_window, draw_window_popups, - layer_map_for_output, - space::{RenderElement, RenderError, SpaceOutputTuple, SurfaceTree}, - utils::damage_from_surface_tree, - Window, - }, output::Output, - utils::{Physical, Point, Rectangle, Scale, Transform}, - wayland::shell::wlr_layer::Layer as WlrLayer, + utils::{Physical, Rectangle}, }; pub mod cursor; -use self::cursor::PointerElement; +use self::cursor::CursorRenderElement; -pub type GlMultiRenderer<'a> = - MultiRenderer<'a, 'a, EglGlesBackend, EglGlesBackend, Gles2Renderbuffer>; -pub type GlMultiFrame = MultiFrame; +pub type GlMultiRenderer<'a> = MultiRenderer< + 'a, + 'a, + EglGlesBackend, + EglGlesBackend, + Gles2Renderbuffer, +>; +pub type GlMultiFrame = MultiFrame, EglGlesBackend>; static CLEAR_COLOR: [f32; 4] = [0.153, 0.161, 0.165, 1.0]; -smithay::custom_elements! { - pub CustomElem<=Gles2Renderer>; - SurfaceTree=SurfaceTree, - PointerElement=PointerElement::, - MoveGrabRenderElement=MoveGrabRenderElement, - #[cfg(feature = "debug")] - EguiFrame=EguiFrame, +smithay::render_elements! { + pub CosmicElement where R: ImportAll; + WorkspaceElement=WorkspaceRenderElement, + CursorElement=CursorRenderElement, + //MoveGrabRenderElement=MoveGrabRenderElement, + //#[cfg(feature = "debug")] + //EguiFrame=EguiFrame, } -// TODO: due to the lifetime of MultiRenderer, we cannot be generic over CustomElem's renderer -// util after GATs land. So we generate with the macro for Gles2 and then -// do a manual impl for MultiRenderer. -impl RenderElement> for CustomElem { - fn id(&self) -> usize { - RenderElement::::id(self) - } - - fn location(&self, scale: impl Into>) -> Point { - RenderElement::::location(self, scale) - } - - fn geometry(&self, scale: impl Into>) -> Rectangle { - RenderElement::::geometry(self, scale) - } - - fn accumulated_damage( - &self, - scale: impl Into>, - for_values: Option>, - ) -> Vec> { - RenderElement::::accumulated_damage(self, scale, for_values) - } - - fn opaque_regions( - &self, - scale: impl Into>, - ) -> Option>> { - RenderElement::::opaque_regions(self, scale) - } - - fn draw( - &self, - renderer: &mut GlMultiRenderer<'_>, - frame: &mut GlMultiFrame, - scale: impl Into>, - location: Point, - damage: &[Rectangle], - log: &Logger, - ) -> Result<(), MultiError> { - RenderElement::::draw( - self, - renderer.as_mut(), - frame.as_mut(), - scale, - location, - damage, - log, - ) - .map_err(MultiError::Render) - } - - fn z_index(&self) -> u8 { - RenderElement::::z_index(self) - } -} - -pub trait AsGles2Renderer { - fn as_gles2(&mut self) -> &mut Gles2Renderer; -} -impl AsGles2Renderer for Gles2Renderer { - fn as_gles2(&mut self) -> &mut Gles2Renderer { - self - } -} -impl AsGles2Renderer for GlMultiRenderer<'_> { - fn as_gles2(&mut self) -> &mut Gles2Renderer { - self.as_mut() - } -} - -pub fn needs_buffer_reset(output: &Output, state: &Common) -> bool { - use std::sync::atomic::{AtomicBool, Ordering}; - struct DidCustomRendering(AtomicBool); - - let will_render_custom = { - let workspace = state.shell.active_space(output); - workspace.get_fullscreen(output).is_some() - }; - - let userdata = output.user_data(); - userdata.insert_if_missing(|| DidCustomRendering(AtomicBool::new(false))); - userdata - .get::() - .unwrap() - .0 - .swap(will_render_custom, Ordering::AcqRel) - != will_render_custom -} - -pub fn cursor_custom_elements( +pub fn cursor_elements( renderer: &mut R, state: &Common, output: &Output, hardware_cursor: bool, -) -> Vec +) -> Vec where - R: AsGles2Renderer, + R: Renderer + ImportAll + ImportMem, + ::TextureId: Clone + 'static, + E: From>, { - let mut custom_elements = Vec::new(); + let scale = output.current_scale().fractional_scale(); + let mut elements = Vec::new(); - for seat in &state.seats { + for seat in state.seats() { let pointer = match seat.get_pointer() { Some(ptr) => ptr, None => continue, }; let location = state .shell - .space_relative_output_geometry(pointer.current_location().to_i32_round(), output); + .map_global_to_space(pointer.current_location().to_i32_round(), output); + /* if let Some(grab) = seat .user_data() .get::() @@ -172,147 +84,92 @@ where { custom_elements.push(grab); } + */ if let Some(wl_surface) = get_dnd_icon(seat) { - custom_elements.push(cursor::draw_dnd_icon(wl_surface, location.to_i32_round()).into()); + elements.extend( + cursor::draw_dnd_icon(&wl_surface, location.to_i32_round(), scale) + .into_iter() + .map(E::from), + ); } - if let Some(cursor) = cursor::draw_cursor( - renderer.as_gles2(), - seat, - location, - &state.start_time, - !hardware_cursor, - ) { - custom_elements.push(cursor) - } + elements.extend( + cursor::draw_cursor( + renderer, + seat, + location, + scale.into(), + &state.start_time, + !hardware_cursor, + ) + .into_iter() + .map(E::from), + ); } - custom_elements + elements } pub fn render_output( gpu: Option<&DrmNode>, renderer: &mut R, - age: u8, + damage_tracker: &mut DamageTrackedRenderer, + age: usize, state: &mut Common, output: &Output, hardware_cursor: bool, #[cfg(feature = "debug")] mut fps: Option<&mut Fps>, ) -> Result>>, RenderError> where - R: Renderer + ImportAll + AsGles2Renderer, + R: Renderer + ImportAll + ImportMem, ::TextureId: Clone + 'static, - CustomElem: RenderElement, { - let workspace = state.shell.active_space(output).idx; + let idx = state.shell.workspaces.active_num(output); render_workspace( gpu, renderer, + damage_tracker, age, state, - workspace, output, + idx, hardware_cursor, ) } pub fn render_workspace( - gpu: Option<&DrmNode>, + _gpu: Option<&DrmNode>, renderer: &mut R, - age: u8, + damage_tracker: &mut DamageTrackedRenderer, + age: usize, state: &mut Common, - space_idx: u8, output: &Output, + idx: usize, hardware_cursor: bool, #[cfg(feature = "debug")] mut fps: Option<&mut Fps>, ) -> Result>>, RenderError> where - R: Renderer + ImportAll + AsGles2Renderer, + R: Renderer + ImportAll + ImportMem, ::TextureId: Clone + 'static, - CustomElem: RenderElement, { #[cfg(feature = "debug")] if let Some(ref mut fps) = fps { fps.start(); } - let space_idx = space_idx as usize; - let workspace = &mut state.shell.spaces[space_idx]; - let maybe_fullscreen_window = workspace.get_fullscreen(output).cloned(); + let workspace = &state + .shell + .workspaces + .get(idx, output) + .ok_or(OutputNoMode)?; - let res = if let Some(window) = maybe_fullscreen_window { - #[cfg(not(feature = "debug"))] - { - render_fullscreen(gpu, renderer, window, state, output, hardware_cursor) - } - #[cfg(feature = "debug")] - { - render_fullscreen( - gpu, - renderer, - window, - state, - output, - hardware_cursor, - fps.as_deref_mut(), - ) - } - } else { - #[cfg(not(feature = "debug"))] - { - render_desktop( - gpu, - renderer, - age, - state, - space_idx, - output, - hardware_cursor, - ) - } - #[cfg(feature = "debug")] - { - render_desktop( - gpu, - renderer, - age, - state, - space_idx, - output, - hardware_cursor, - fps.as_deref_mut(), - ) - } - }; - - #[cfg(feature = "debug")] - if let Some(ref mut fps) = fps { - fps.end(); - } - - res -} - -fn render_desktop( - _gpu: Option<&DrmNode>, - renderer: &mut R, - age: u8, - state: &mut Common, - space_idx: usize, - output: &Output, - hardware_cursor: bool, - #[cfg(feature = "debug")] fps: Option<&mut Fps>, -) -> Result>>, RenderError> -where - R: Renderer + ImportAll + AsGles2Renderer, - ::TextureId: Clone + 'static, - CustomElem: RenderElement, -{ - let mut custom_elements = Vec::::new(); + let mut elements: Vec> = + cursor_elements(renderer, state, output, hardware_cursor); #[cfg(feature = "debug")] { + // TODO add debug elements let workspace = &state.shell.spaces[space_idx]; let output_geo = workspace .space @@ -346,132 +203,20 @@ where } } - custom_elements.extend(cursor_custom_elements( - renderer, - state, - output, - hardware_cursor, - )); + elements.extend( + workspace + .render_output(output) + .map_err(|_| OutputNoMode)? + .into_iter() + .map(Into::into), + ); - state.shell.spaces[space_idx].space.render_output( - renderer, - &output, - age as usize, - CLEAR_COLOR, - &*custom_elements, - ) -} - -fn render_fullscreen( - _gpu: Option<&DrmNode>, - renderer: &mut R, - window: Window, - state: &mut Common, - output: &Output, - hardware_cursor: bool, - #[cfg(feature = "debug")] fps: Option<&mut Fps>, -) -> Result>>, RenderError> -where - R: Renderer + ImportAll + AsGles2Renderer, - ::TextureId: Clone + 'static, - CustomElem: RenderElement, -{ - let transform = Transform::from(output.current_transform()); - let mode = output.current_mode().unwrap(); - let scale = output.current_scale().fractional_scale(); - - let mut custom_elements = Vec::::new(); + let res = damage_tracker.render_output(renderer, age, &elements, CLEAR_COLOR, None); #[cfg(feature = "debug")] - if let Some(fps) = fps { - let output_geo = output.geometry(); - let fps_overlay = fps_ui( - _gpu, - state, - fps, - Rectangle::from_loc_and_size((0, 0), output_geo.size) - .to_f64() - .to_physical(scale), - scale, - ); - custom_elements.push(fps_overlay.into()); + if let Some(ref mut fps) = fps { + fps.end(); } - custom_elements.extend(cursor_custom_elements( - renderer, - state, - output, - hardware_cursor, - )); - - renderer - .render(mode.size, transform, |renderer, frame| { - let mut damage = window.accumulated_damage((0.0, 0.0), scale, None); - frame.clear( - CLEAR_COLOR, - &[Rectangle::from_loc_and_size((0, 0), mode.size)], - )?; - draw_window( - renderer, - frame, - &window, - scale, - (0.0, 0.0), - &[Rectangle::from_loc_and_size((0, 0), mode.size)], - &slog_scope::logger(), - )?; - draw_window_popups( - renderer, - frame, - &window, - scale, - (0.0, 0.0), - &[Rectangle::from_loc_and_size((0, 0), mode.size)], - &slog_scope::logger(), - )?; - let layer_map = layer_map_for_output(output); - for layer_surface in layer_map.layers_on(WlrLayer::Overlay) { - let geo = layer_map.layer_geometry(&layer_surface).unwrap(); - draw_layer_surface( - renderer, - frame, - layer_surface, - scale, - geo.loc.to_f64().to_physical(scale), - &[Rectangle::from_loc_and_size( - (0, 0), - geo.size.to_physical_precise_round(scale), - )], - &slog_scope::logger(), - )?; - draw_layer_popups( - renderer, - frame, - layer_surface, - scale, - geo.loc.to_f64().to_physical(scale), - &[Rectangle::from_loc_and_size( - (0, 0), - geo.size.to_physical_precise_round(scale), - )], - &slog_scope::logger(), - )?; - damage.extend(damage_from_surface_tree( - layer_surface.wl_surface(), - geo.loc.to_f64().to_physical(scale), - scale, - None, - )); - } - for elem in custom_elements { - let loc = elem.location(scale); - let geo = elem.geometry(scale); - let elem_damage = elem.accumulated_damage(scale, None); - elem.draw(renderer, frame, scale, loc, &[geo], &slog_scope::logger())?; - damage.extend(elem_damage) - } - Ok(Some(damage)) - }) - .and_then(std::convert::identity) - .map_err(RenderError::::Rendering) + res } diff --git a/src/backend/winit.rs b/src/backend/winit.rs index b6a67ed1..8456f0ac 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -10,7 +10,7 @@ use crate::{ use anyhow::{anyhow, Context, Result}; use smithay::{ backend::{ - renderer::{ImportDma, ImportEgl}, + renderer::{damage::DamageTrackedRenderer, ImportDma, ImportEgl}, winit::{self, WinitEvent, WinitGraphicsBackend, WinitVirtualDevice}, }, desktop::layer_map_for_output, @@ -30,31 +30,23 @@ pub struct WinitState { // The winit backend currently has no notion of multiple windows pub backend: WinitGraphicsBackend, output: Output, - age_reset: u8, + damage_tracker: DamageTrackedRenderer, #[cfg(feature = "debug")] fps: Fps, } impl WinitState { pub fn render_output(&mut self, state: &mut Common) -> Result<()> { - if render::needs_buffer_reset(&self.output, state) { - self.reset_buffers(); - } - self.backend .bind() .with_context(|| "Failed to bind buffer")?; - let age = if self.age_reset > 0 { - self.age_reset -= 1; - 0 - } else { - self.backend.buffer_age().unwrap_or(0) - }; + let age = self.backend.buffer_age().unwrap_or(0); match render::render_output( None, self.backend.renderer(), - age as u8, + &mut self.damage_tracker, + age, state, &self.output, true, @@ -62,11 +54,7 @@ impl WinitState { Some(&mut self.fps), ) { Ok(damage) => { - state - .shell - .active_space_mut(&self.output) - .space - .send_frames(state.start_time.elapsed().as_millis() as u32); + state.send_frames(&self.output); self.backend .submit(damage.as_ref().map(|x| &**x)) .with_context(|| "Failed to submit buffer for display")?; @@ -104,10 +92,6 @@ impl WinitState { Ok(()) } } - - pub fn reset_buffers(&mut self) { - self.age_reset = 3; - } } pub fn init_backend( @@ -180,8 +164,7 @@ pub fn init_backend( .handle() .insert_source(event_source, move |_, _, data| { match input.dispatch_new_events(|event| { - data.state - .process_winit_event(&data.display.handle(), event, &render_ping_handle) + data.state.process_winit_event(event, &render_ping_handle) }) { Ok(_) => { event_ping_handle.ping(); @@ -202,9 +185,9 @@ pub fn init_backend( state.backend = BackendData::Winit(WinitState { backend, output: output.clone(), + damage_tracker: DamageTrackedRenderer::from_output(&output), #[cfg(feature = "debug")] fps: Fps::default(), - age_reset: 0, }); state .common @@ -250,19 +233,14 @@ fn init_egl_client_side( } impl State { - pub fn process_winit_event( - &mut self, - dh: &DisplayHandle, - event: WinitEvent, - render_ping: &ping::Ping, - ) { + pub fn process_winit_event(&mut self, event: WinitEvent, render_ping: &ping::Ping) { // here we can handle special cases for winit inputs match event { WinitEvent::Focus(true) => { - for seat in self.common.seats.clone().iter() { + for seat in self.common.seats().cloned().collect::>().iter() { let devices = seat.user_data().get::().unwrap(); if devices.has_device(&WinitVirtualDevice) { - set_active_output(seat, &self.backend.winit().output); + seat.set_active_output(&self.backend.winit().output); break; } } @@ -286,7 +264,7 @@ impl State { output.delete_mode(output.current_mode().unwrap()); output.set_preferred(mode); output.change_current_state(Some(mode), None, None, None); - layer_map_for_output(output).arrange(dh); + layer_map_for_output(output).arrange(); self.common.output_configuration_state.update(); self.common.shell.refresh_outputs(); render_ping.ping(); diff --git a/src/backend/x11.rs b/src/backend/x11.rs index 53c4c1ca..fbe8f6f6 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -13,7 +13,9 @@ use smithay::{ allocator::dmabuf::Dmabuf, egl::{EGLContext, EGLDisplay}, input::{Event, InputEvent}, - renderer::{gles2::Gles2Renderer, Bind, ImportDma, ImportEgl}, + renderer::{ + damage::DamageTrackedRenderer, gles2::Gles2Renderer, Bind, ImportDma, ImportEgl, + }, x11::{Window, WindowBuilder, X11Backend, X11Event, X11Handle, X11Input, X11Surface}, }, desktop::layer_map_for_output, @@ -114,6 +116,7 @@ impl X11State { self.surfaces.push(Surface { window, surface, + damage_tracker: DamageTrackedRenderer::from_output(&output), output: output.clone(), render: ping.clone(), dirty: false, @@ -168,6 +171,7 @@ impl X11State { pub struct Surface { window: Window, + damage_tracker: DamageTrackedRenderer, surface: X11Surface, output: Output, render: ping::Ping, @@ -183,10 +187,6 @@ impl Surface { renderer: &mut Gles2Renderer, state: &mut Common, ) -> Result<()> { - if render::needs_buffer_reset(&self.output, state) { - self.surface.reset_buffers(); - } - let (buffer, age) = self .surface .buffer() @@ -198,7 +198,8 @@ impl Surface { match render::render_output( None, renderer, - age as u8, + &mut self.damage_tracker, + age as usize, state, &self.output, true, @@ -206,11 +207,7 @@ impl Surface { Some(&mut self.fps), ) { Ok(_) => { - state - .shell - .active_space_mut(&self.output) - .space - .send_frames(state.start_time.elapsed().as_millis() as u32); + state.send_frames(&self.output); self.surface .submit() .with_context(|| "Failed to submit buffer for display")?; @@ -336,7 +333,7 @@ pub fn init_backend( output.delete_mode(output.current_mode().unwrap()); output.change_current_state(Some(mode), None, None, None); output.set_preferred(mode); - layer_map_for_output(output).arrange(&data.display.handle()); + layer_map_for_output(output).arrange(); data.state.common.output_configuration_state.update(); data.state.common.shell.refresh_outputs(); surface.dirty = true; @@ -405,10 +402,10 @@ impl State { .unwrap(); let device = event.device(); - for seat in self.common.seats.clone().iter() { + for seat in self.common.seats().cloned().collect::>().iter() { let devices = seat.user_data().get::().unwrap(); if devices.has_device(&device) { - set_active_output(seat, &output); + seat.set_active_output(&output); break; } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 3a225ea1..401e24e8 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ - shell::{focus::FocusDirection, Shell}, + shell::{focus::FocusDirection, Shell, WorkspaceAmount}, state::{BackendData, Data}, }; use serde::{Deserialize, Serialize}; @@ -32,6 +32,7 @@ pub struct Config { pub struct StaticConfig { pub key_bindings: HashMap, pub workspace_mode: WorkspaceMode, + pub workspace_amount: WorkspaceAmount, pub floating_default: bool, } @@ -215,6 +216,7 @@ impl Config { StaticConfig { key_bindings: HashMap::new(), workspace_mode: WorkspaceMode::Global, + workspace_amount: WorkspaceAmount::Dynamic, floating_default: false, } } @@ -787,6 +789,6 @@ pub enum Action { ToggleTiling, ToggleWindowFloating, Fullscreen, - Screenshot, + //Screenshot, Spawn(String), } diff --git a/src/input/mod.rs b/src/input/mod.rs index b599f882..d22c64ae 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -2,7 +2,7 @@ use crate::{ config::{Action, Config}, - shell::{grabs::SeatMoveGrabState, Workspace}, + shell::{focus::target::PointerFocusTarget, Workspace}, // shell::grabs::SeatMoveGrabState state::Common, utils::prelude::*, }; @@ -11,17 +11,17 @@ use smithay::{ AbsolutePositionEvent, Axis, AxisSource, Device, DeviceCapability, InputBackend, InputEvent, KeyState, PointerAxisEvent, }, - desktop::{layer_map_for_output, Kind, WindowSurfaceType}, + desktop::layer_map_for_output, input::{ keyboard::{keysyms, FilterResult, KeysymHandle, XkbConfig}, pointer::{AxisFrame, ButtonEvent, CursorImageStatus, MotionEvent}, Seat, SeatState, }, output::Output, - reexports::wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle}, - utils::{Buffer, Logical, Point, Rectangle, Size, SERIAL_COUNTER}, + reexports::wayland_server::DisplayHandle, + utils::{Logical, Point, Rectangle, SERIAL_COUNTER}, wayland::{ - keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat, + keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat, seat::WaylandFocus, shell::wlr_layer::Layer as WlrLayer, }, }; @@ -103,6 +103,7 @@ impl Devices { pub fn add_seat( dh: &DisplayHandle, seat_state: &mut SeatState, + output: &Output, config: &Config, name: String, ) -> Seat { @@ -111,7 +112,8 @@ pub fn add_seat( userdata.insert_if_missing(SeatId::default); userdata.insert_if_missing(Devices::default); userdata.insert_if_missing(SupressedKeys::default); - userdata.insert_if_missing(SeatMoveGrabState::default); + //userdata.insert_if_missing(SeatMoveGrabState::default); + userdata.insert_if_missing(|| ActiveOutput(RefCell::new(output.clone()))); userdata.insert_if_missing(|| RefCell::new(CursorImageStatus::Default)); // A lot of clients bind keyboard and pointer unconditionally once on launch.. @@ -143,7 +145,7 @@ impl State { match event { InputEvent::DeviceAdded { device } => { - let seat = &mut self.common.last_active_seat; + let seat = &mut self.common.last_active_seat(); let userdata = seat.user_data(); let devices = userdata.get::().unwrap(); for cap in devices.add_device(&device) { @@ -159,7 +161,7 @@ impl State { } } InputEvent::DeviceRemoved { device } => { - for seat in &mut self.common.seats { + for seat in &mut self.common.seats() { let userdata = seat.user_data(); let devices = userdata.get::().unwrap(); if devices.has_device(&device) { @@ -182,16 +184,17 @@ impl State { use smithay::backend::input::KeyboardKeyEvent; let device = event.device(); - for seat in self.common.seats.clone().iter() { - let current_output = active_output(seat, &self.common); + for seat in self.common.seats().cloned().collect::>().iter() { + let current_output = seat.active_output(); let workspace = self.common.shell.active_space_mut(¤t_output); let shortcuts_inhibited = workspace - .focus_stack(seat) + .focus_stack + .get(seat) .last() .and_then(|window| { - seat.keyboard_shortcuts_inhibitor_for_surface( - &window.toplevel().wl_surface(), - ) + window.wl_surface().and_then(|surface| { + seat.keyboard_shortcuts_inhibitor_for_surface(&surface) + }) }) .map(|inhibitor| inhibitor.is_active()) .unwrap_or(false); @@ -309,34 +312,31 @@ impl State { slog_scope::info!("Debug overlay not included in this version") } Action::Close => { - let current_output = active_output(seat, &self.common); + let current_output = seat.active_output(); let workspace = self.common.shell.active_space_mut(¤t_output); - if let Some(window) = workspace.focus_stack(seat).last() { - #[allow(irrefutable_let_patterns)] - if let Kind::Xdg(xdg) = &window.toplevel() { - xdg.send_close(); - } + if let Some(window) = workspace.focus_stack.get(seat).last() { + window.send_close(); } } Action::Workspace(key_num) => { - let current_output = active_output(seat, &self.common); + let current_output = seat.active_output(); let workspace = match key_num { 0 => 9, x => x - 1, }; - if let Some(motion_event) = self.common.shell.activate( - seat, - ¤t_output, - workspace as usize, - ) { + if let Some(motion_event) = self + .common + .shell + .activate(¤t_output, workspace as usize) + { if let Some(ptr) = seat.get_pointer() { ptr.motion(self, None, &motion_event); } } } Action::MoveToWorkspace(key_num) => { - let current_output = active_output(seat, &self.common); + let current_output = seat.active_output(); let workspace = match key_num { 0 => 9, x => x - 1, @@ -348,52 +348,47 @@ impl State { ); } Action::Focus(focus) => { - let current_output = active_output(seat, &self.common); + let current_output = seat.active_output(); let workspace = self.common.shell.active_space_mut(¤t_output); - let focus_stack = workspace.focus_stack(seat); - if let Some(window) = workspace.tiling_layer.move_focus( + let focus_stack = workspace.focus_stack.get(seat); + if let Some(target) = workspace.tiling_layer.next_focus( focus, seat, - &mut workspace.space, focus_stack.iter(), ) { std::mem::drop(focus_stack); - Common::set_focus( - self, - Some(window.toplevel().wl_surface()), - seat, - None, - ); + Common::set_focus(self, Some(&target), seat, None); } } Action::Fullscreen => { - let current_output = active_output(seat, &self.common); + let current_output = seat.active_output(); let workspace = self.common.shell.active_space_mut(¤t_output); - let focused_window = workspace.focus_stack(seat).last(); - if let Some(window) = focused_window { + let focus_stack = workspace.focus_stack.get(seat); + let focused_window = focus_stack.last(); + if let Some(window) = focused_window.map(|f| f.active_window()) + { workspace.fullscreen_toggle(&window, ¤t_output); } } Action::Orientation(orientation) => { - let output = active_output(seat, &self.common); + let output = seat.active_output(); let workspace = self.common.shell.active_space_mut(&output); - let focus_stack = workspace.focus_stack(seat); + let focus_stack = workspace.focus_stack.get(seat); workspace.tiling_layer.update_orientation( orientation, &seat, - &mut workspace.space, focus_stack.iter(), ); } Action::ToggleTiling => { - let output = active_output(seat, &self.common); + let output = seat.active_output(); let workspace = self.common.shell.active_space_mut(&output); workspace.toggle_tiling(seat); } Action::ToggleWindowFloating => { - let output = active_output(seat, &self.common); + let output = seat.active_output(); let workspace = self.common.shell.active_space_mut(&output); workspace.toggle_floating_window(seat); } @@ -407,80 +402,81 @@ impl State { { slog_scope::warn!("Failed to spawn: {}", err); } - } - Action::Screenshot => { - let home = match std::env::var("HOME") { - Ok(home) => home, - Err(err) => { - slog_scope::error!( - "$HOME is not set, can't save screenshots: {}", - err - ); - break; - } - }; - let timestamp = match std::time::SystemTime::UNIX_EPOCH - .elapsed() - { - Ok(duration) => duration.as_secs(), - Err(err) => { - slog_scope::error!("Unable to get timestamp, can't save screenshots: {}", err); - break; - } - }; - for output in self.common.shell.outputs.clone().into_iter() { - match self - .backend - .offscreen_for_output(&output, &mut self.common) - { - Ok((buffer, size)) => { - let mut path = std::path::PathBuf::new(); - path.push(&home); - path.push(format!( - "{}_{}.png", - output.name(), - timestamp - )); + } /* + Action::Screenshot => { + let home = match std::env::var("HOME") { + Ok(home) => home, + Err(err) => { + slog_scope::error!( + "$HOME is not set, can't save screenshots: {}", + err + ); + break; + } + }; + let timestamp = match std::time::SystemTime::UNIX_EPOCH + .elapsed() + { + Ok(duration) => duration.as_secs(), + Err(err) => { + slog_scope::error!("Unable to get timestamp, can't save screenshots: {}", err); + break; + } + }; + for output in self.common.shell.outputs.clone().into_iter() { + match self + .backend + .offscreen_for_output(&output, &mut self.common) + { + Ok((buffer, size)) => { + let mut path = std::path::PathBuf::new(); + path.push(&home); + path.push(format!( + "{}_{}.png", + output.name(), + timestamp + )); - fn write_png( - path: impl AsRef, - data: Vec, - size: Size, - ) -> anyhow::Result<()> - { - use std::{fs, io}; + fn write_png( + path: impl AsRef, + data: Vec, + size: Size, + ) -> anyhow::Result<()> + { + use std::{fs, io}; - let file = io::BufWriter::new( - fs::File::create(&path)?, - ); - let mut encoder = png::Encoder::new( - file, - size.w as u32, - size.h as u32, - ); - encoder.set_color(png::ColorType::Rgba); - encoder.set_depth(png::BitDepth::Eight); - let mut writer = encoder.write_header()?; - writer.write_image_data(&data)?; - Ok(()) - } + let file = io::BufWriter::new( + fs::File::create(&path)?, + ); + let mut encoder = png::Encoder::new( + file, + size.w as u32, + size.h as u32, + ); + encoder.set_color(png::ColorType::Rgba); + encoder.set_depth(png::BitDepth::Eight); + let mut writer = encoder.write_header()?; + writer.write_image_data(&data)?; + Ok(()) + } - if let Err(err) = write_png(&path, buffer, size) { - slog_scope::error!( - "Unable to save screenshot at {}: {}", - path.display(), - err - ); - } - } - Err(err) => slog_scope::error!( - "Could not save screenshot for output {}: {}", - output.name(), - err - ), - } - } - } + if let Err(err) = write_png(&path, buffer, size) { + slog_scope::error!( + "Unable to save screenshot at {}: {}", + path.display(), + err + ); + } + } + Err(err) => slog_scope::error!( + "Could not save screenshot for output {}: {}", + output.name(), + err + ), + } + } + } + */ } } break; @@ -491,11 +487,11 @@ impl State { use smithay::backend::input::PointerMotionEvent; let device = event.device(); - for seat in self.common.seats.clone().iter() { + for seat in self.common.seats().cloned().collect::>().iter() { let userdata = seat.user_data(); let devices = userdata.get::().unwrap(); if devices.has_device(&device) { - let current_output = active_output(seat, &self.common); + let current_output = seat.active_output(); let mut position = seat.get_pointer().unwrap().current_location(); position += event.delta(); @@ -508,7 +504,7 @@ impl State { .cloned() .unwrap_or(current_output.clone()); if output != current_output { - set_active_output(seat, &output); + seat.set_active_output(&output); } let output_geometry = output.geometry(); @@ -520,10 +516,7 @@ impl State { .min((output_geometry.loc.y + output_geometry.size.h) as f64); let serial = SERIAL_COUNTER.next_serial(); - let relative_pos = self - .common - .shell - .space_relative_output_geometry(position, &output); + let relative_pos = self.common.shell.map_global_to_space(position, &output); let workspace = self.common.shell.active_space_mut(&output); let under = State::surface_under( position, @@ -559,18 +552,15 @@ impl State { } InputEvent::PointerMotionAbsolute { event, .. } => { let device = event.device(); - for seat in self.common.seats.clone().iter() { + for seat in self.common.seats().cloned().collect::>().iter() { let userdata = seat.user_data(); let devices = userdata.get::().unwrap(); if devices.has_device(&device) { - let output = active_output(seat, &self.common); + let output = seat.active_output(); let geometry = output.geometry(); let position = geometry.loc.to_f64() + event.position_transformed(geometry.size); - let relative_pos = self - .common - .shell - .space_relative_output_geometry(position, &output); + let relative_pos = self.common.shell.map_global_to_space(position, &output); let workspace = self.common.shell.active_space_mut(&output); let serial = SERIAL_COUNTER.next_serial(); let under = State::surface_under( @@ -609,7 +599,7 @@ impl State { use smithay::backend::input::{ButtonState, PointerButtonEvent}; let device = event.device(); - for seat in self.common.seats.clone().iter() { + for seat in self.common.seats().cloned().collect::>().iter() { let userdata = seat.user_data(); let devices = userdata.get::().unwrap(); if devices.has_device(&device) { @@ -649,13 +639,10 @@ impl State { if !seat.get_pointer().unwrap().is_grabbed() && !seat.get_keyboard().map(|k| k.is_grabbed()).unwrap_or(false) { - let output = active_output(seat, &self.common); + let output = seat.active_output(); let pos = seat.get_pointer().unwrap().current_location(); - let output_geo = output.geometry(); - let relative_pos = self - .common - .shell - .space_relative_output_geometry(pos, &output); + let relative_pos = + self.common.shell.map_global_to_space(pos, &output); let workspace = self.common.shell.active_space_mut(&output); let layers = layer_map_for_output(&output); let mut under = None; @@ -665,23 +652,10 @@ impl State { layers.layer_under(WlrLayer::Overlay, relative_pos) { if layer.can_receive_keyboard_focus() { - let layer_loc = - layers.layer_geometry(layer).unwrap().loc; - under = layer - .surface_under( - pos - output_geo.loc.to_f64() - - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) - .map(|(_, _)| layer.wl_surface().clone()); + under = Some(layer.clone().into()); } } else { - under = window - .surface_under( - pos - output_geo.loc.to_f64(), - WindowSurfaceType::ALL, - ) - .map(|(_, _)| window.toplevel().wl_surface().clone()); + under = Some(window.clone().into()); } } else { if let Some(layer) = layers @@ -689,35 +663,18 @@ impl State { .or_else(|| layers.layer_under(WlrLayer::Top, relative_pos)) { if layer.can_receive_keyboard_focus() { - let layer_loc = - layers.layer_geometry(layer).unwrap().loc; - under = layer - .surface_under( - pos - output_geo.loc.to_f64() - - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) - .map(|(_, _)| layer.wl_surface().clone()); + under = Some(layer.clone().into()); } - } else if let Some((window, _, _)) = workspace - .space - .surface_under(relative_pos, WindowSurfaceType::ALL) + } else if let Some((window, _)) = + workspace.element_under(relative_pos) { - under = Some(window.toplevel().wl_surface().clone()); + under = Some(window.clone().into()); } else if let Some(layer) = layers .layer_under(WlrLayer::Bottom, pos) .or_else(|| layers.layer_under(WlrLayer::Background, pos)) { if layer.can_receive_keyboard_focus() { - let layer_loc = - layers.layer_geometry(layer).unwrap().loc; - under = layer - .surface_under( - pos - output_geo.loc.to_f64() - - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) - .map(|(_, _)| layer.wl_surface().clone()); + under = Some(layer.clone().into()); } }; } @@ -740,7 +697,7 @@ impl State { } InputEvent::PointerAxis { event, .. } => { let device = event.device(); - for seat in self.common.seats.clone().iter() { + for seat in self.common.seats().cloned().collect::>().iter() { #[cfg(feature = "debug")] if self.common.seats.iter().position(|x| x == seat).unwrap() == 0 && self.common.egui.active @@ -820,7 +777,7 @@ impl State { output: &Output, output_geo: Rectangle, workspace: &Workspace, - ) -> Option<(WlSurface, Point)> { + ) -> Option<(PointerFocusTarget, Point)> { let layers = layer_map_for_output(output); if let Some(window) = workspace.get_fullscreen(output) { if let Some(layer) = layers @@ -828,16 +785,9 @@ impl State { .or_else(|| layers.layer_under(WlrLayer::Top, relative_pos)) { let layer_loc = layers.layer_geometry(layer).unwrap().loc; - layer - .surface_under( - global_pos - output_geo.loc.to_f64() - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) - .map(|(s, loc)| (s, loc + layer_loc + output_geo.loc)) + Some((layer.clone().into(), output_geo.loc + layer_loc)) } else { - window - .surface_under(global_pos - output_geo.loc.to_f64(), WindowSurfaceType::ALL) - .map(|(s, loc)| (s, loc + output_geo.loc)) + Some((window.clone().into(), output_geo.loc)) } } else { if let Some(layer) = layers @@ -845,28 +795,18 @@ impl State { .or_else(|| layers.layer_under(WlrLayer::Top, relative_pos)) { let layer_loc = layers.layer_geometry(layer).unwrap().loc; - layer - .surface_under( - global_pos - output_geo.loc.to_f64() - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) - .map(|(s, loc)| (s, loc + layer_loc + output_geo.loc)) - } else if let Some((_, surface, loc)) = workspace - .space - .surface_under(relative_pos, WindowSurfaceType::ALL) - { - Some((surface, loc + (global_pos - relative_pos).to_i32_round())) + Some((layer.clone().into(), output_geo.loc + layer_loc)) + } else if let Some((mapped, loc)) = workspace.element_under(relative_pos) { + Some(( + mapped.clone().into(), + loc + (global_pos - relative_pos).to_i32_round(), + )) } else if let Some(layer) = layers .layer_under(WlrLayer::Bottom, relative_pos) .or_else(|| layers.layer_under(WlrLayer::Background, relative_pos)) { let layer_loc = layers.layer_geometry(layer).unwrap().loc; - layer - .surface_under( - global_pos - output_geo.loc.to_f64() - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) - .map(|(s, loc)| (s, loc + layer_loc + output_geo.loc)) + Some((layer.clone().into(), output_geo.loc + layer_loc)) } else { None } diff --git a/src/main.rs b/src/main.rs index 4231f427..a96f3d06 100644 --- a/src/main.rs +++ b/src/main.rs @@ -68,7 +68,7 @@ fn main() -> Result<()> { } // trigger routines - data.state.common.shell.refresh(&data.display.handle()); + data.state.common.shell.refresh(); state::Common::refresh_focus(&mut data.state); // do we need to trigger another render diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs new file mode 100644 index 00000000..20096d06 --- /dev/null +++ b/src/shell/element/mod.rs @@ -0,0 +1,631 @@ +use crate::state::State; +use id_tree::NodeId; +use smithay::{ + backend::{ + input::KeyState, + renderer::{element::AsRenderElements, ImportAll, Renderer}, + }, + desktop::{space::SpaceElement, Kind, PopupManager, Window, WindowSurfaceType}, + input::{ + keyboard::{KeyboardTarget, KeysymHandle, ModifiersState}, + pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerTarget}, + Seat, + }, + output::Output, + reexports::{ + wayland_protocols::xdg::shell::server::xdg_toplevel::State as XdgState, + wayland_server::{backend::ObjectId, protocol::wl_surface::WlSurface}, + }, + render_elements, space_elements, + utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size}, + wayland::{ + compositor::{with_states, with_surface_tree_downward, TraversalAction}, + seat::WaylandFocus, + shell::xdg::XdgToplevelSurfaceRoleAttributes, + }, +}; +use std::{ + hash::Hash, + sync::{Arc, Mutex}, +}; + +pub mod stack; +pub use self::stack::CosmicStack; +pub mod window; +pub use self::window::CosmicWindow; + +use super::focus::FocusDirection; + +space_elements! { + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + CosmicMappedInternal; + Window=CosmicWindow, + Stack=CosmicStack, +} + +#[derive(Debug, Clone)] +pub struct CosmicMapped { + element: CosmicMappedInternal, + + // associated data + + //tiling + pub(super) tiling_node_id: Arc>>, + //floating + pub(super) last_geometry: Arc>>>, +} + +impl PartialEq for CosmicMapped { + fn eq(&self, other: &Self) -> bool { + self.element == other.element + } +} + +impl Eq for CosmicMapped {} + +impl Hash for CosmicMapped { + fn hash(&self, state: &mut H) { + self.element.hash(state) + } +} + +impl CosmicMapped { + pub fn windows(&self) -> impl Iterator)> + '_ { + match &self.element { + CosmicMappedInternal::Stack(stack) => Box::new(stack.windows().map(|w| { + ( + w, + stack + .header + .lock() + .unwrap() + .as_ref() + .map(|header| Point::from((0, header.height() as i32))) + .unwrap_or(Point::from((0, 0))), + ) + })) + as Box)>>, + CosmicMappedInternal::Window(window) => Box::new(std::iter::once(( + window.window.clone(), + window + .header + .lock() + .unwrap() + .as_ref() + .map(|header| Point::from((0, header.height() as i32))) + .unwrap_or(Point::from((0, 0))), + ))), + _ => Box::new(std::iter::empty()), + } + } + + pub fn active_window(&self) -> Window { + match &self.element { + CosmicMappedInternal::Stack(stack) => stack.active(), + CosmicMappedInternal::Window(win) => win.window.clone(), + _ => unreachable!(), + } + } + + pub fn focus_window(&self, window: &Window) { + match &self.element { + CosmicMappedInternal::Stack(stack) => stack.set_active(window), + _ => {} + } + } + + pub fn has_surface(&self, surface: &WlSurface, surface_type: WindowSurfaceType) -> bool { + self.windows().any(|(w, _)| { + let toplevel = w.toplevel().wl_surface(); + + if surface_type.contains(WindowSurfaceType::TOPLEVEL) { + if toplevel == surface { + return true; + } + } + + if surface_type.contains(WindowSurfaceType::SUBSURFACE) { + use std::sync::atomic::{AtomicBool, Ordering}; + + let found = AtomicBool::new(false); + with_surface_tree_downward( + toplevel, + surface, + |_, _, search| TraversalAction::DoChildren(search), + |s, _, search| { + found.fetch_or(s == *search, Ordering::SeqCst); + }, + |_, _, _| !found.load(Ordering::SeqCst), + ); + if found.load(Ordering::SeqCst) { + return true; + } + } + + if surface_type.contains(WindowSurfaceType::POPUP) { + PopupManager::popups_for_surface(toplevel).any(|(p, _)| p.wl_surface() == surface) + } else { + false + } + }) + } + + pub fn handle_focus(&self, direction: FocusDirection) -> bool { + if let CosmicMappedInternal::Stack(stack) = &self.element { + //TODO: stack.handle_focus(direction) + false + } else { + false + } + } + + 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::Window(w) => Some(w.window.toplevel()), + _ => unreachable!(), + } { + match toplevel { + Kind::Xdg(xdg) => xdg.with_pending_state(|state| { + if tiled { + state.states.set(XdgState::TiledLeft); + state.states.set(XdgState::TiledRight); + state.states.set(XdgState::TiledTop); + state.states.set(XdgState::TiledBottom); + } else { + state.states.unset(XdgState::TiledLeft); + state.states.unset(XdgState::TiledRight); + state.states.unset(XdgState::TiledTop); + state.states.unset(XdgState::TiledBottom); + } + }), + // Kind::X11? + }; + } + } + + pub fn is_tiled(&self) -> bool { + let window = match &self.element { + CosmicMappedInternal::Stack(s) => s.active(), + CosmicMappedInternal::Window(w) => w.window.clone(), + _ => unreachable!(), + }; + + match window.toplevel() { + Kind::Xdg(xdg) => xdg.current_state().states.contains(XdgState::TiledLeft), + // Kind::X11? + } + } + + pub fn set_fullscreen(&self, fullscreen: bool) { + for window in match &self.element { + CosmicMappedInternal::Stack(s) => { + Box::new(s.windows()) as Box> + } + CosmicMappedInternal::Window(w) => Box::new(std::iter::once(w.window.clone())), + _ => unreachable!(), + } { + match window.toplevel() { + Kind::Xdg(xdg) => xdg.with_pending_state(|state| { + if fullscreen { + state.states.set(XdgState::Fullscreen); + } else { + state.states.unset(XdgState::Fullscreen); + } + }), + // Kind::X11? + }; + } + } + + pub fn is_fullscreen(&self) -> bool { + let window = match &self.element { + CosmicMappedInternal::Stack(s) => s.active(), + CosmicMappedInternal::Window(w) => w.window.clone(), + _ => unreachable!(), + }; + + match window.toplevel() { + Kind::Xdg(xdg) => xdg.current_state().states.contains(XdgState::Fullscreen), + // Kind::X11? + } + } + + pub fn set_maximized(&self, maximized: bool) { + for window in match &self.element { + CosmicMappedInternal::Stack(s) => { + Box::new(s.windows()) as Box> + } + CosmicMappedInternal::Window(w) => Box::new(std::iter::once(w.window.clone())), + _ => unreachable!(), + } { + match window.toplevel() { + Kind::Xdg(xdg) => xdg.with_pending_state(|state| { + if maximized { + state.states.set(XdgState::Maximized); + } else { + state.states.unset(XdgState::Maximized); + } + }), + // Kind::X11? + }; + } + } + + pub fn is_maximized(&self) -> bool { + let window = match &self.element { + CosmicMappedInternal::Stack(s) => s.active(), + CosmicMappedInternal::Window(w) => w.window.clone(), + _ => unreachable!(), + }; + + match window.toplevel() { + Kind::Xdg(xdg) => xdg.current_state().states.contains(XdgState::Maximized), + // Kind::X11? + } + } + + pub fn set_activated(&self, activated: bool) { + for window in match &self.element { + CosmicMappedInternal::Stack(s) => { + Box::new(s.windows()) as Box> + } + CosmicMappedInternal::Window(w) => Box::new(std::iter::once(w.window.clone())), + _ => unreachable!(), + } { + match window.toplevel() { + Kind::Xdg(xdg) => xdg.with_pending_state(|state| { + if activated { + state.states.set(XdgState::Activated); + } else { + state.states.unset(XdgState::Activated); + } + }), + // Kind::X11? + }; + } + } + + pub fn is_activated(&self) -> bool { + let window = match &self.element { + CosmicMappedInternal::Stack(s) => s.active(), + CosmicMappedInternal::Window(w) => w.window.clone(), + _ => unreachable!(), + }; + + match window.toplevel() { + Kind::Xdg(xdg) => xdg.current_state().states.contains(XdgState::Activated), + // Kind::X11? + } + } + + pub fn set_size(&self, size: Size) { + match &self.element { + CosmicMappedInternal::Stack(s) => s.set_size(size), + CosmicMappedInternal::Window(w) => w.set_size(size), + _ => {} + } + } + + pub fn min_size(&self) -> Size { + match &self.element { + CosmicMappedInternal::Stack(stack) => stack + .windows() + .fold(None, |min_size, window| { + let win_min_size = with_states(window.toplevel().wl_surface(), |states| { + let attrs = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + attrs.min_size + }); + match (min_size, win_min_size) { + (None, x) => Some(x), + (Some(min1), min2) => Some((min1.w.max(min2.w), min1.h.max(min2.h)).into()), + } + }) + .expect("Empty stack?"), + CosmicMappedInternal::Window(window) => { + with_states(window.window.toplevel().wl_surface(), |states| { + let attrs = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + attrs.min_size + }) + } + _ => unreachable!(), + } + } + + pub fn max_size(&self) -> Size { + match &self.element { + CosmicMappedInternal::Stack(stack) => { + let theoretical_max = stack.windows().fold(None, |max_size, window| { + let win_max_size = with_states(window.toplevel().wl_surface(), |states| { + let attrs = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + attrs.max_size + }); + match (max_size, win_max_size) { + (None, x) => Some(x), + (Some(max1), max2) => Some( + ( + if max1.w == 0 { + max2.w + } else if max2.w == 0 { + max1.w + } else { + max1.w.min(max2.w) + }, + if max1.h == 0 { + max2.h + } else if max2.h == 0 { + max1.h + } else { + max1.h.min(max2.h) + }, + ) + .into(), + ), + } + }); + // The problem is, with accumulated sizes, the minimum size could be larger than our maximum... + let min_size = self.min_size(); + match (theoretical_max, min_size) { + (None, _) => (0, 0).into(), + (Some(max), min) => (max.w.max(min.w), max.h.max(min.h)).into(), + } + } + CosmicMappedInternal::Window(window) => { + with_states(window.window.toplevel().wl_surface(), |states| { + let attrs = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + attrs.max_size + }) + } + _ => unreachable!(), + } + } + + pub fn configure(&self) { + for window in match &self.element { + CosmicMappedInternal::Stack(s) => { + Box::new(s.windows()) as Box> + } + CosmicMappedInternal::Window(w) => Box::new(std::iter::once(w.window.clone())), + _ => unreachable!(), + } { + match window.toplevel() { + Kind::Xdg(xdg) => xdg.send_configure(), + // Kind::X11? + }; + } + } + + pub fn send_close(&self) { + let window = match &self.element { + CosmicMappedInternal::Stack(s) => s.active(), + CosmicMappedInternal::Window(w) => w.window.clone(), + _ => unreachable!(), + }; + + match window.toplevel() { + Kind::Xdg(xdg) => xdg.send_close(), + // Kind::X11? + }; + } +} + +impl IsAlive for CosmicMapped { + fn alive(&self) -> bool { + self.element.alive() + } +} + +impl SpaceElement for CosmicMapped { + fn bbox(&self) -> Rectangle { + SpaceElement::bbox(&self.element) + } + fn is_in_input_region(&self, point: &Point) -> bool { + SpaceElement::is_in_input_region(&self.element, point) + } + fn set_activate(&self, activated: bool) { + SpaceElement::set_activate(&self.element, activated) + } + fn output_enter(&self, output: &Output, overlap: Rectangle) { + SpaceElement::output_enter(&self.element, output, overlap) + } + fn output_leave(&self, output: &Output) { + SpaceElement::output_leave(&self.element, output) + } + fn geometry(&self) -> Rectangle { + SpaceElement::geometry(&self.element) + } + fn z_index(&self) -> u8 { + SpaceElement::z_index(&self.element) + } + fn refresh(&self) { + SpaceElement::refresh(&self.element) + } +} + +impl KeyboardTarget for CosmicMapped { + fn enter( + &self, + seat: &Seat, + data: &mut State, + keys: Vec>, + serial: Serial, + ) { + match &self.element { + CosmicMappedInternal::Stack(s) => KeyboardTarget::enter(s, seat, data, keys, serial), + CosmicMappedInternal::Window(w) => KeyboardTarget::enter(w, seat, data, keys, serial), + _ => {} + } + } + fn leave(&self, seat: &Seat, data: &mut State, serial: Serial) { + match &self.element { + CosmicMappedInternal::Stack(s) => KeyboardTarget::leave(s, seat, data, serial), + CosmicMappedInternal::Window(w) => KeyboardTarget::leave(w, seat, data, serial), + _ => {} + } + } + fn key( + &self, + seat: &Seat, + data: &mut State, + key: KeysymHandle<'_>, + state: KeyState, + serial: Serial, + time: u32, + ) { + match &self.element { + CosmicMappedInternal::Stack(s) => { + KeyboardTarget::key(s, seat, data, key, state, serial, time) + } + CosmicMappedInternal::Window(w) => { + KeyboardTarget::key(w, seat, data, key, state, serial, time) + } + _ => {} + } + } + fn modifiers( + &self, + seat: &Seat, + data: &mut State, + modifiers: ModifiersState, + serial: Serial, + ) { + match &self.element { + CosmicMappedInternal::Stack(s) => { + KeyboardTarget::modifiers(s, seat, data, modifiers, serial) + } + CosmicMappedInternal::Window(w) => { + KeyboardTarget::modifiers(w, seat, data, modifiers, serial) + } + _ => {} + } + } +} + +impl PointerTarget for CosmicMapped { + fn enter(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { + match &self.element { + CosmicMappedInternal::Stack(s) => PointerTarget::enter(s, seat, data, event), + CosmicMappedInternal::Window(w) => PointerTarget::enter(w, seat, data, event), + _ => {} + } + } + fn motion(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { + match &self.element { + CosmicMappedInternal::Stack(s) => PointerTarget::motion(s, seat, data, event), + CosmicMappedInternal::Window(w) => PointerTarget::motion(w, seat, data, event), + _ => {} + } + } + fn button(&self, seat: &Seat, data: &mut State, event: &ButtonEvent) { + match &self.element { + CosmicMappedInternal::Stack(s) => PointerTarget::button(s, seat, data, event), + CosmicMappedInternal::Window(w) => PointerTarget::button(w, seat, data, event), + _ => {} + } + } + fn axis(&self, seat: &Seat, data: &mut State, frame: AxisFrame) { + match &self.element { + CosmicMappedInternal::Stack(s) => PointerTarget::axis(s, seat, data, frame), + CosmicMappedInternal::Window(w) => PointerTarget::axis(w, seat, data, frame), + _ => {} + } + } + fn leave(&self, seat: &Seat, data: &mut State, serial: Serial, time: u32) { + match &self.element { + CosmicMappedInternal::Stack(s) => PointerTarget::leave(s, seat, data, serial, time), + CosmicMappedInternal::Window(w) => PointerTarget::leave(w, seat, data, serial, time), + _ => {} + } + } +} + +impl WaylandFocus for CosmicMapped { + fn wl_surface(&self) -> Option { + match &self.element { + CosmicMappedInternal::Window(w) => w.window.wl_surface().clone(), + CosmicMappedInternal::Stack(s) => s.active().wl_surface().clone(), + _ => None, + } + } + + fn same_client_as(&self, object_id: &ObjectId) -> bool { + match &self.element { + CosmicMappedInternal::Window(w) => w.window.same_client_as(object_id), + CosmicMappedInternal::Stack(s) => s.windows().any(|w| w.same_client_as(object_id)), + _ => false, + } + } +} + +impl From for CosmicMapped { + fn from(w: CosmicWindow) -> Self { + CosmicMapped { + element: CosmicMappedInternal::Window(w), + tiling_node_id: Arc::new(Mutex::new(None)), + last_geometry: Arc::new(Mutex::new(None)), + } + } +} + +impl From for CosmicMapped { + fn from(s: CosmicStack) -> Self { + CosmicMapped { + element: CosmicMappedInternal::Stack(s), + tiling_node_id: Arc::new(Mutex::new(None)), + last_geometry: Arc::new(Mutex::new(None)), + } + } +} + +render_elements! { + pub CosmicMappedRenderElement where R: ImportAll; + Stack=self::stack::CosmicStackRenderElement, + Window=self::window::CosmicWindowRenderElement, +} + +impl AsRenderElements for CosmicMapped +where + R: Renderer + ImportAll, + ::TextureId: 'static, +{ + type RenderElement = CosmicMappedRenderElement; + fn render_elements>( + &self, + location: Point, + scale: Scale, + ) -> Vec { + match &self.element { + CosmicMappedInternal::Stack(s) => AsRenderElements::::render_elements::< + CosmicMappedRenderElement, + >(s, location, scale), + CosmicMappedInternal::Window(w) => AsRenderElements::::render_elements::< + CosmicMappedRenderElement, + >(w, location, scale), + _ => Vec::new(), + } + .into_iter() + .map(C::from) + .collect() + } +} diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs new file mode 100644 index 00000000..76fbadd4 --- /dev/null +++ b/src/shell/element/stack.rs @@ -0,0 +1,293 @@ +use crate::state::State; +use smithay::{ + backend::{ + input::KeyState, + renderer::{ + element::{surface::WaylandSurfaceRenderElement, AsRenderElements}, + ImportAll, Renderer, + }, + }, + desktop::{space::SpaceElement, Kind, Window}, + input::{ + keyboard::{KeyboardTarget, KeysymHandle, ModifiersState}, + pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerTarget}, + Seat, + }, + output::Output, + render_elements, + utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size}, +}; +use std::{ + hash::Hash, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, Mutex, + }, +}; + +#[derive(Debug, Clone)] +pub struct CosmicStack { + windows: Arc>>, + active: Arc, + pub(super) header: Arc>>, +} + +impl PartialEq for CosmicStack { + fn eq(&self, other: &Self) -> bool { + *self.windows.lock().unwrap() == *other.windows.lock().unwrap() + } +} + +impl Eq for CosmicStack {} + +impl Hash for CosmicStack { + fn hash(&self, state: &mut H) { + Arc::as_ptr(&self.windows).hash(state) + } +} + +#[derive(Debug)] +pub struct HeaderBar {} + +impl HeaderBar { + pub fn height(&self) -> i32 { + 0 + } +} + +impl CosmicStack { + pub fn active(&self) -> Window { + self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)].clone() + } + + pub fn set_active(&self, window: &Window) { + if let Some(val) = self + .windows + .lock() + .unwrap() + .iter() + .position(|w| w == window) + { + self.active.store(val, Ordering::SeqCst) + } + } + + pub fn set_size(&self, size: Size) { + let surface_size = ( + size.w, + size.h + - self + .header + .lock() + .unwrap() + .as_ref() + .map(|h| h.height()) + .unwrap_or(0), + ) + .into(); + + for window in self.windows.lock().unwrap().iter() { + match window.toplevel() { + Kind::Xdg(xdg) => xdg.with_pending_state(|state| state.size = Some(surface_size)), + }; + } + } +} + +impl IsAlive for CosmicStack { + fn alive(&self) -> bool { + self.windows.lock().unwrap().iter().any(IsAlive::alive) + } +} + +impl SpaceElement for CosmicStack { + fn bbox(&self) -> Rectangle { + SpaceElement::bbox(&self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)]) + } + fn is_in_input_region(&self, point: &Point) -> bool { + SpaceElement::is_in_input_region( + &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], + point, + ) + } + fn set_activate(&self, activated: bool) { + self.windows + .lock() + .unwrap() + .iter() + .for_each(|w| SpaceElement::set_activate(w, activated)) + } + fn output_enter(&self, output: &Output, overlap: Rectangle) { + self.windows + .lock() + .unwrap() + .iter() + .for_each(|w| SpaceElement::output_enter(w, output, overlap)) + } + fn output_leave(&self, output: &Output) { + self.windows + .lock() + .unwrap() + .iter() + .for_each(|w| SpaceElement::output_leave(w, output)) + } + fn geometry(&self) -> Rectangle { + SpaceElement::geometry(&self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)]) + } + fn z_index(&self) -> u8 { + SpaceElement::z_index(&self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)]) + } + fn refresh(&self) { + let mut windows = self.windows.lock().unwrap(); + windows.retain(IsAlive::alive); + let len = windows.len(); + let _ = self + .active + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |active| { + (active > len).then_some(len - 1) + }); + windows.iter().for_each(|w| SpaceElement::refresh(w)) + } +} + +impl KeyboardTarget for CosmicStack { + fn enter( + &self, + seat: &Seat, + data: &mut State, + keys: Vec>, + serial: Serial, + ) { + KeyboardTarget::enter( + &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], + seat, + data, + keys, + serial, + ) + } + fn leave(&self, seat: &Seat, data: &mut State, serial: Serial) { + KeyboardTarget::leave( + &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], + seat, + data, + serial, + ) + } + fn key( + &self, + seat: &Seat, + data: &mut State, + key: KeysymHandle<'_>, + state: KeyState, + serial: Serial, + time: u32, + ) { + KeyboardTarget::key( + &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], + seat, + data, + key, + state, + serial, + time, + ) + } + fn modifiers( + &self, + seat: &Seat, + data: &mut State, + modifiers: ModifiersState, + serial: Serial, + ) { + KeyboardTarget::modifiers( + &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], + seat, + data, + modifiers, + serial, + ) + } +} + +impl PointerTarget for CosmicStack { + fn enter(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { + PointerTarget::enter( + &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], + seat, + data, + event, + ) + } + fn motion(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { + PointerTarget::motion( + &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], + seat, + data, + event, + ) + } + fn button(&self, seat: &Seat, data: &mut State, event: &ButtonEvent) { + PointerTarget::button( + &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], + seat, + data, + event, + ) + } + fn axis(&self, seat: &Seat, data: &mut State, frame: AxisFrame) { + PointerTarget::axis( + &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], + seat, + data, + frame, + ) + } + fn leave(&self, seat: &Seat, data: &mut State, serial: Serial, time: u32) { + PointerTarget::leave( + &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], + seat, + data, + serial, + time, + ) + } +} + +render_elements! { + pub CosmicStackRenderElement where R: ImportAll; + Window=WaylandSurfaceRenderElement, +} + +impl AsRenderElements for CosmicStack +where + R: Renderer + ImportAll, + ::TextureId: 'static, +{ + type RenderElement = CosmicStackRenderElement; + fn render_elements>( + &self, + location: Point, + scale: Scale, + ) -> Vec { + AsRenderElements::::render_elements::>( + &self.windows.lock().unwrap()[self.active.load(Ordering::SeqCst)], + location, + scale, + ) + .into_iter() + .map(C::from) + .collect() + } +} +impl CosmicStack { + pub fn windows(&self) -> impl Iterator { + self.windows + .lock() + .unwrap() + .iter() + .cloned() + .collect::>() + .into_iter() + } +} diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs new file mode 100644 index 00000000..821b4ae4 --- /dev/null +++ b/src/shell/element/window.rs @@ -0,0 +1,231 @@ +use crate::state::State; +use smithay::{ + backend::{ + input::KeyState, + renderer::{ + element::{surface::WaylandSurfaceRenderElement, AsRenderElements}, + ImportAll, Renderer, + }, + }, + desktop::{space::SpaceElement, Kind, Window}, + input::{ + keyboard::{KeyboardTarget, KeysymHandle, ModifiersState}, + pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerTarget}, + Seat, + }, + output::Output, + reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode as DecorationMode, + render_elements, + utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size}, + wayland::shell::xdg::ToplevelSurface, +}; +use std::{ + hash::Hash, + sync::{Arc, Mutex}, +}; + +#[derive(Debug, Clone)] +pub struct CosmicWindow { + pub(super) window: Window, + pub(super) header: Arc>>, +} + +impl PartialEq for CosmicWindow { + fn eq(&self, other: &Window) -> bool { + &self.window == other + } +} + +impl PartialEq for Window { + fn eq(&self, other: &CosmicWindow) -> bool { + self == &other.window + } +} + +impl PartialEq for CosmicWindow { + fn eq(&self, other: &Self) -> bool { + self.window == other.window + } +} + +impl Eq for CosmicWindow {} + +impl Hash for CosmicWindow { + fn hash(&self, state: &mut H) { + self.window.hash(state) + } +} + +impl CosmicWindow { + pub fn set_size(&self, size: Size) { + let surface_size = ( + size.w, + size.h + - self + .header + .lock() + .unwrap() + .as_ref() + .map(|h| h.height()) + .unwrap_or(0), + ) + .into(); + match self.window.toplevel() { + Kind::Xdg(xdg) => xdg.with_pending_state(|state| state.size = Some(surface_size)), + }; + } +} + +impl From for CosmicWindow { + fn from(window: Window) -> Self { + let is_ssd = matches!( + match window.toplevel() { + Kind::Xdg(xdg) => xdg.current_state().decoration_mode, + }, + Some(DecorationMode::ServerSide) + ); + CosmicWindow { + window, + header: Arc::new(Mutex::new(is_ssd.then_some(HeaderBar::default()))), + } + } +} + +impl From for CosmicWindow { + fn from(surf: ToplevelSurface) -> Self { + let is_ssd = matches!( + surf.current_state().decoration_mode, + Some(DecorationMode::ServerSide) + ); + CosmicWindow { + window: Window::new(Kind::Xdg(surf)), + header: Arc::new(Mutex::new(is_ssd.then_some(HeaderBar::default()))), + } + } +} + +#[derive(Debug, Default, PartialEq)] +pub(super) struct HeaderBar { + pointer_loc: Option>, + close_button_hover: bool, + maximize_button_hover: bool, +} + +impl HeaderBar { + pub fn height(&self) -> i32 { + 0 + } +} + +impl IsAlive for CosmicWindow { + fn alive(&self) -> bool { + self.window.alive() + } +} + +impl SpaceElement for CosmicWindow { + fn bbox(&self) -> Rectangle { + SpaceElement::bbox(&self.window) + } + fn is_in_input_region(&self, point: &Point) -> bool { + SpaceElement::is_in_input_region(&self.window, point) + } + fn set_activate(&self, activated: bool) { + SpaceElement::set_activate(&self.window, activated) + } + fn output_enter(&self, output: &Output, overlap: Rectangle) { + SpaceElement::output_enter(&self.window, output, overlap) + } + fn output_leave(&self, output: &Output) { + SpaceElement::output_leave(&self.window, output) + } + fn geometry(&self) -> Rectangle { + SpaceElement::geometry(&self.window) + } + fn z_index(&self) -> u8 { + SpaceElement::z_index(&self.window) + } + fn refresh(&self) { + SpaceElement::refresh(&self.window) + } +} + +impl KeyboardTarget for CosmicWindow { + fn enter( + &self, + seat: &Seat, + data: &mut State, + keys: Vec>, + serial: Serial, + ) { + KeyboardTarget::enter(&self.window, seat, data, keys, serial) + } + fn leave(&self, seat: &Seat, data: &mut State, serial: Serial) { + KeyboardTarget::leave(&self.window, seat, data, serial) + } + fn key( + &self, + seat: &Seat, + data: &mut State, + key: KeysymHandle<'_>, + state: KeyState, + serial: Serial, + time: u32, + ) { + KeyboardTarget::key(&self.window, seat, data, key, state, serial, time) + } + fn modifiers( + &self, + seat: &Seat, + data: &mut State, + modifiers: ModifiersState, + serial: Serial, + ) { + KeyboardTarget::modifiers(&self.window, seat, data, modifiers, serial) + } +} + +impl PointerTarget for CosmicWindow { + fn enter(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { + PointerTarget::enter(&self.window, seat, data, event) + } + fn motion(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { + PointerTarget::motion(&self.window, seat, data, event) + } + fn button(&self, seat: &Seat, data: &mut State, event: &ButtonEvent) { + PointerTarget::button(&self.window, seat, data, event) + } + fn axis(&self, seat: &Seat, data: &mut State, frame: AxisFrame) { + PointerTarget::axis(&self.window, seat, data, frame) + } + fn leave(&self, seat: &Seat, data: &mut State, serial: Serial, time: u32) { + PointerTarget::leave(&self.window, seat, data, serial, time) + } +} + +render_elements! { + pub CosmicWindowRenderElement where R: ImportAll; + Window=WaylandSurfaceRenderElement, +} + +impl AsRenderElements for CosmicWindow +where + R: Renderer + ImportAll, + ::TextureId: 'static, +{ + type RenderElement = CosmicWindowRenderElement; + fn render_elements>( + &self, + location: Point, + scale: Scale, + ) -> Vec { + AsRenderElements::::render_elements::>( + &self.window, + location - self.window.geometry().loc.to_physical_precise_round(scale), + scale, + ) + .into_iter() + .map(C::from) + .collect() + } +} diff --git a/src/shell/focus/mod.rs b/src/shell/focus/mod.rs index 09886893..6adb1245 100644 --- a/src/shell/focus/mod.rs +++ b/src/shell/focus/mod.rs @@ -1,24 +1,18 @@ use crate::{ - shell::{OutputBoundState, Shell, Workspace, WorkspaceMode}, + shell::{element::CosmicMapped, Shell, Workspace}, state::Common, utils::prelude::*, wayland::handlers::xdg_shell::PopupGrabData, }; use indexmap::IndexSet; use smithay::{ - desktop::{layer_map_for_output, PopupUngrabStrategy, Window, WindowSurfaceType}, + desktop::{layer_map_for_output, PopupUngrabStrategy}, input::Seat, - reexports::wayland_server::protocol::wl_surface::WlSurface, utils::{IsAlive, Serial, SERIAL_COUNTER}, - wayland::{ - compositor::get_role, - shell::{wlr_layer::LAYER_SURFACE_ROLE, xdg::XDG_TOPLEVEL_ROLE}, - }, -}; -use std::{ - cell::{Ref, RefCell, RefMut}, - collections::HashMap, }; +use std::cell::RefCell; + +use self::target::{KeyboardFocusTarget, WindowGroup}; pub mod target; @@ -32,80 +26,59 @@ pub enum FocusDirection { Out, } -pub struct FocusStack<'a>(Ref<'a, IndexSet>); -pub struct FocusStackMut<'a>(RefMut<'a, IndexSet>); +pub struct FocusStack<'a>(pub(super) Option<&'a IndexSet>); +pub struct FocusStackMut<'a>(pub(super) &'a mut IndexSet); impl<'a> FocusStack<'a> { - pub fn last(&self) -> Option { - self.0.iter().rev().find(|w| w.toplevel().alive()).cloned() + pub fn last(&self) -> Option<&CosmicMapped> { + self.0 + .as_ref() + .and_then(|set| set.iter().rev().find(|w| w.alive())) } - pub fn iter(&self) -> impl Iterator { - self.0.iter().rev().filter(|w| w.toplevel().alive()) + pub fn iter(&self) -> impl Iterator { + self.0 + .iter() + .flat_map(|set| set.iter().rev().filter(|w| w.alive())) } } impl<'a> FocusStackMut<'a> { - pub fn append(&mut self, window: &Window) { - self.0.retain(|w| w.toplevel().alive()); + pub fn append(&mut self, window: &CosmicMapped) { + self.0.retain(|w| w.alive()); self.0.shift_remove(window); self.0.insert(window.clone()); } - pub fn last(&self) -> Option { - self.0.iter().rev().find(|w| w.toplevel().alive()).cloned() + pub fn last(&self) -> Option<&CosmicMapped> { + self.0.iter().rev().find(|w| w.alive()) } - pub fn iter(&self) -> impl Iterator { - self.0.iter().rev().filter(|w| w.toplevel().alive()) + pub fn iter(&self) -> impl Iterator { + self.0.iter().rev().filter(|w| w.alive()) } } -type FocusStackData = RefCell<(HashMap>, IndexSet)>; +impl Workspace {} -impl Workspace { - pub fn focus_stack<'a, 'b>(&'b self, seat: &'a Seat) -> FocusStack<'a> { - seat.user_data() - .insert_if_missing(|| FocusStackData::new((HashMap::new(), IndexSet::new()))); - let idx = self.idx; - FocusStack(Ref::map( - seat.user_data().get::().unwrap().borrow(), - |map| map.0.get(&idx).unwrap_or(&map.1), //TODO: workaround until Ref::filter_map goes stable - )) - } - - pub fn focus_stack_mut<'a, 'b>(&'b self, seat: &'a Seat) -> FocusStackMut<'a> { - seat.user_data() - .insert_if_missing(|| FocusStackData::new((HashMap::new(), IndexSet::new()))); - let idx = self.idx; - FocusStackMut(RefMut::map( - seat.user_data() - .get::() - .unwrap() - .borrow_mut(), - |map| map.0.entry(idx).or_insert_with(|| IndexSet::new()), - )) - } -} - -pub struct ActiveFocus(RefCell>); +pub struct ActiveFocus(RefCell>); impl ActiveFocus { - fn set(seat: &Seat, surface: Option) { + fn set(seat: &Seat, target: Option) { if !seat .user_data() - .insert_if_missing(|| ActiveFocus(RefCell::new(surface.clone()))) + .insert_if_missing(|| ActiveFocus(RefCell::new(target.clone()))) { *seat .user_data() .get::() .unwrap() .0 - .borrow_mut() = surface; + .borrow_mut() = target; } } - fn get(seat: &Seat) -> Option { + fn get(seat: &Seat) -> Option { seat.user_data() .get::() .and_then(|a| a.0.borrow().clone()) @@ -115,30 +88,25 @@ impl ActiveFocus { impl Shell { pub fn set_focus<'a>( state: &mut State, - surface: Option<&WlSurface>, + target: Option<&KeyboardFocusTarget>, active_seat: &Seat, serial: Option, ) { // update FocusStack and notify layouts about new focus (if any window) - if let Some(surface) = surface { - if let Some(workspace) = state.common.shell.space_for_window_mut(surface) { - if let Some(window) = workspace - .space - .window_for_surface(surface, WindowSurfaceType::ALL) - { - let mut focus_stack = workspace.focus_stack_mut(active_seat); - if Some(window) != focus_stack.last().as_ref() { - slog_scope::debug!("Focusing window: {:?}", window); - focus_stack.append(window); - // also remove popup grabs, if we are switching focus - if let Some(mut popup_grab) = active_seat - .user_data() - .get::() - .and_then(|x| x.take()) - { - if !popup_grab.has_ended() { - popup_grab.ungrab(PopupUngrabStrategy::All); - } + if let Some(KeyboardFocusTarget::Element(mapped)) = target { + if let Some(workspace) = state.common.shell.space_for_mut(mapped) { + let mut focus_stack = workspace.focus_stack.get_mut(active_seat); + if Some(mapped) != focus_stack.last() { + slog_scope::debug!("Focusing window: {:?}", mapped); + focus_stack.append(mapped); + // also remove popup grabs, if we are switching focus + if let Some(mut popup_grab) = active_seat + .user_data() + .get::() + .and_then(|x| x.take()) + { + if !popup_grab.has_ended() { + popup_grab.ungrab(PopupUngrabStrategy::All); } } } @@ -147,10 +115,10 @@ impl Shell { // update keyboard focus if let Some(keyboard) = active_seat.get_keyboard() { - ActiveFocus::set(active_seat, surface.cloned()); + ActiveFocus::set(active_seat, target.cloned()); keyboard.set_focus( state, - surface.cloned(), + target.cloned(), serial.unwrap_or_else(|| SERIAL_COUNTER.next_serial()), ); } @@ -160,30 +128,23 @@ impl Shell { // update activate status let focused_windows = seats .flat_map(|seat| { - self.outputs - .iter() - .flat_map(|o| self.active_space(o).focus_stack(seat).last().clone()) + self.outputs.iter().flat_map(|o| { + let space = self.active_space(o); + let stack = space.focus_stack.get(seat); + stack.last().cloned() + }) }) .collect::>(); for output in self.outputs.iter() { - let workspace = match &self.workspace_mode { - WorkspaceMode::OutputBound => { - let active = output - .user_data() - .get::() - .unwrap() - .active - .get(); - &mut self.spaces[active] - } - WorkspaceMode::Global { active, .. } => &mut self.spaces[*active], - }; + let workspace = self.workspaces.active_mut(output); for focused in focused_windows.iter() { - workspace.space.raise_window(focused, true); + if workspace.floating_layer.mapped().any(|m| m == focused) { + workspace.floating_layer.space.raise_element(focused, true); + } } - for window in workspace.space.windows() { - window.set_activated(focused_windows.contains(window)); + for window in workspace.mapped() { + window.set_activated(focused_windows.contains(&window)); window.configure(); } } @@ -193,55 +154,50 @@ impl Shell { impl Common { pub fn set_focus( state: &mut State, - surface: Option<&WlSurface>, + target: Option<&KeyboardFocusTarget>, active_seat: &Seat, serial: Option, ) { - Shell::set_focus(state, surface, active_seat, serial); - state.common.shell.update_active(state.common.seats.iter()); + Shell::set_focus(state, target, active_seat, serial); + let seats = state.common.seats().cloned().collect::>(); + state.common.shell.update_active(seats.iter()); } pub fn refresh_focus(state: &mut State) { - let seats = state.common.seats.clone(); + let seats = state.common.seats().cloned().collect::>(); for seat in seats { - let output = active_output(&seat, &state.common); + let output = seat.active_output(); let last_known_focus = ActiveFocus::get(&seat); - if let Some(surface) = last_known_focus { - if surface.alive() { - let is_toplevel = matches!(get_role(&surface), Some(XDG_TOPLEVEL_ROLE)); - let is_layer = matches!(get_role(&surface), Some(LAYER_SURFACE_ROLE)); - - if let Some(popup) = state.common.shell.popups.find_popup(&surface) { - if popup.alive() { - continue; - } - } else if is_layer { - if layer_map_for_output(&output) - .layer_for_surface(&surface, WindowSurfaceType::ALL) - .is_some() - { - continue; // Focus is valid - } - } else if is_toplevel { - let workspace = state.common.shell.active_space(&output); - if let Some(window) = workspace - .space - .window_for_surface(&surface, WindowSurfaceType::ALL) - { - let focus_stack = workspace.focus_stack(&seat); - if !focus_stack.last().map(|w| &w != window).unwrap_or(true) { + if let Some(target) = last_known_focus { + if target.alive() { + match target { + KeyboardFocusTarget::Element(mapped) => { + let workspace = state.common.shell.active_space(&output); + let focus_stack = workspace.focus_stack.get(&seat); + if focus_stack.last().map(|m| m == &mapped).unwrap_or(false) { continue; // Focus is valid } else { slog_scope::debug!("Wrong Window, focus fixup"); } - } else { - slog_scope::debug!("Different workspaces Window, focus fixup"); } - } else { - // unknown surface type, fixup - slog_scope::debug!("Surface unmapped, focus fixup"); - } + KeyboardFocusTarget::LayerSurface(layer) => { + if layer_map_for_output(&output).layers().any(|l| l == &layer) { + continue; // Focus is valid + } + } + KeyboardFocusTarget::Group(WindowGroup { + output: weak_output, + .. + }) => { + if weak_output == output { + continue; // Focus is valid, + } + } + KeyboardFocusTarget::Popup(_) | KeyboardFocusTarget::Fullscreen(_) => { + continue; + } // Focus is valid + }; } else { slog_scope::debug!("Surface dead, focus fixup"); } @@ -263,21 +219,24 @@ impl Common { } // update keyboard focus - let surface = state + let target = state .common .shell .active_space(&output) - .focus_stack(&seat) + .focus_stack + .get(&seat) .last() - .map(|w| w.toplevel().wl_surface().clone()); + .cloned() + .map(KeyboardFocusTarget::from); if let Some(keyboard) = seat.get_keyboard() { - slog_scope::info!("restoring focus to: {:?}", surface.as_ref()); - keyboard.set_focus(state, surface.clone(), SERIAL_COUNTER.next_serial()); - ActiveFocus::set(&seat, surface); + slog_scope::info!("restoring focus to: {:?}", target.as_ref()); + keyboard.set_focus(state, target.clone(), SERIAL_COUNTER.next_serial()); + ActiveFocus::set(&seat, target); } } } - state.common.shell.update_active(state.common.seats.iter()) + let seats = state.common.seats().cloned().collect::>(); + state.common.shell.update_active(seats.iter()) } } diff --git a/src/shell/focus/target.rs b/src/shell/focus/target.rs index 81861424..6781bf08 100644 --- a/src/shell/focus/target.rs +++ b/src/shell/focus/target.rs @@ -1,5 +1,8 @@ -use crate::utils::prelude::*; -pub use smithay::{ +use std::sync::Weak; + +use crate::{shell::element::CosmicMapped, utils::prelude::*}; +use id_tree::NodeId; +use smithay::{ backend::input::KeyState, desktop::{LayerSurface, PopupKind, Window}, input::{ @@ -7,83 +10,139 @@ pub use smithay::{ pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerTarget}, Seat, }, + output::WeakOutput, reexports::wayland_server::{backend::ObjectId, protocol::wl_surface::WlSurface, Resource}, utils::{IsAlive, Serial}, wayland::seat::WaylandFocus, }; #[derive(Debug, Clone, PartialEq)] -pub enum FocusTarget { - Window(Window), +pub enum PointerFocusTarget { + Element(CosmicMapped), + Fullscreen(Window), LayerSurface(LayerSurface), Popup(PopupKind), } -impl IsAlive for FocusTarget { - fn alive(&self) -> bool { - match self { - FocusTarget::Window(w) => w.alive(), - FocusTarget::LayerSurface(l) => l.alive(), - FocusTarget::Popup(p) => p.alive(), +#[derive(Debug, Clone, PartialEq)] +pub enum KeyboardFocusTarget { + Element(CosmicMapped), + Fullscreen(Window), + Group(WindowGroup), + LayerSurface(LayerSurface), + Popup(PopupKind), +} + +impl From for PointerFocusTarget { + fn from(target: KeyboardFocusTarget) -> Self { + match target { + KeyboardFocusTarget::Element(elem) => PointerFocusTarget::Element(elem), + KeyboardFocusTarget::Fullscreen(elem) => PointerFocusTarget::Fullscreen(elem), + KeyboardFocusTarget::LayerSurface(layer) => PointerFocusTarget::LayerSurface(layer), + KeyboardFocusTarget::Popup(popup) => PointerFocusTarget::Popup(popup), + _ => unreachable!("A window grab cannot start a popup grab"), } } } -impl PointerTarget for FocusTarget { +#[derive(Debug, Clone)] +pub struct WindowGroup { + pub(in crate::shell) node: NodeId, + pub(in crate::shell) output: WeakOutput, + pub(in crate::shell) alive: Weak<()>, +} + +impl PartialEq for WindowGroup { + fn eq(&self, other: &Self) -> bool { + self.node == other.node + && self.output == other.output + && Weak::ptr_eq(&self.alive, &other.alive) + } +} + +impl IsAlive for PointerFocusTarget { + fn alive(&self) -> bool { + match self { + PointerFocusTarget::Element(e) => e.alive(), + PointerFocusTarget::Fullscreen(f) => f.alive(), + PointerFocusTarget::LayerSurface(l) => l.alive(), + PointerFocusTarget::Popup(p) => p.alive(), + } + } +} + +impl IsAlive for KeyboardFocusTarget { + fn alive(&self) -> bool { + match self { + KeyboardFocusTarget::Element(e) => e.alive(), + KeyboardFocusTarget::Fullscreen(f) => f.alive(), + KeyboardFocusTarget::Group(g) => g.alive.upgrade().is_some(), + KeyboardFocusTarget::LayerSurface(l) => l.alive(), + KeyboardFocusTarget::Popup(p) => p.alive(), + } + } +} + +impl PointerTarget for PointerFocusTarget { fn enter(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { match self { - FocusTarget::Window(w) => { - PointerTarget::enter(w.toplevel().wl_surface(), seat, data, event) + PointerFocusTarget::Element(w) => PointerTarget::enter(w, seat, data, event), + PointerFocusTarget::Fullscreen(w) => PointerTarget::enter(w, seat, data, event), + PointerFocusTarget::LayerSurface(l) => { + PointerTarget::enter(l.wl_surface(), seat, data, event) } - FocusTarget::LayerSurface(l) => PointerTarget::enter(l.wl_surface(), seat, data, event), - FocusTarget::Popup(p) => PointerTarget::enter(p.wl_surface(), seat, data, event), + PointerFocusTarget::Popup(p) => PointerTarget::enter(p.wl_surface(), seat, data, event), } } fn motion(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { match self { - FocusTarget::Window(w) => { - PointerTarget::motion(w.toplevel().wl_surface(), seat, data, event) - } - FocusTarget::LayerSurface(l) => { + PointerFocusTarget::Element(w) => PointerTarget::motion(w, seat, data, event), + PointerFocusTarget::Fullscreen(w) => PointerTarget::motion(w, seat, data, event), + PointerFocusTarget::LayerSurface(l) => { PointerTarget::motion(l.wl_surface(), seat, data, event) } - FocusTarget::Popup(p) => PointerTarget::motion(p.wl_surface(), seat, data, event), + PointerFocusTarget::Popup(p) => { + PointerTarget::motion(p.wl_surface(), seat, data, event) + } } } fn button(&self, seat: &Seat, data: &mut State, event: &ButtonEvent) { match self { - FocusTarget::Window(w) => { - PointerTarget::button(w.toplevel().wl_surface(), seat, data, event) - } - FocusTarget::LayerSurface(l) => { + PointerFocusTarget::Element(w) => PointerTarget::button(w, seat, data, event), + PointerFocusTarget::Fullscreen(w) => PointerTarget::button(w, seat, data, event), + PointerFocusTarget::LayerSurface(l) => { PointerTarget::button(l.wl_surface(), seat, data, event) } - FocusTarget::Popup(p) => PointerTarget::button(p.wl_surface(), seat, data, event), + PointerFocusTarget::Popup(p) => { + PointerTarget::button(p.wl_surface(), seat, data, event) + } } } fn axis(&self, seat: &Seat, data: &mut State, frame: AxisFrame) { match self { - FocusTarget::Window(w) => { - PointerTarget::axis(w.toplevel().wl_surface(), seat, data, frame) + PointerFocusTarget::Element(w) => PointerTarget::axis(w, seat, data, frame), + PointerFocusTarget::Fullscreen(w) => PointerTarget::axis(w, seat, data, frame), + PointerFocusTarget::LayerSurface(l) => { + PointerTarget::axis(l.wl_surface(), seat, data, frame) } - FocusTarget::LayerSurface(l) => PointerTarget::axis(l.wl_surface(), seat, data, frame), - FocusTarget::Popup(p) => PointerTarget::axis(p.wl_surface(), seat, data, frame), + PointerFocusTarget::Popup(p) => PointerTarget::axis(p.wl_surface(), seat, data, frame), } } fn leave(&self, seat: &Seat, data: &mut State, serial: Serial, time: u32) { match self { - FocusTarget::Window(w) => { - PointerTarget::leave(w.toplevel().wl_surface(), seat, data, serial, time) - } - FocusTarget::LayerSurface(l) => { + PointerFocusTarget::Element(w) => PointerTarget::leave(w, seat, data, serial, time), + PointerFocusTarget::Fullscreen(w) => PointerTarget::leave(w, seat, data, serial, time), + PointerFocusTarget::LayerSurface(l) => { PointerTarget::leave(l.wl_surface(), seat, data, serial, time) } - FocusTarget::Popup(p) => PointerTarget::leave(p.wl_surface(), seat, data, serial, time), + PointerFocusTarget::Popup(p) => { + PointerTarget::leave(p.wl_surface(), seat, data, serial, time) + } } } } -impl KeyboardTarget for FocusTarget { +impl KeyboardTarget for KeyboardFocusTarget { fn enter( &self, seat: &Seat, @@ -92,26 +151,30 @@ impl KeyboardTarget for FocusTarget { serial: Serial, ) { match self { - FocusTarget::Window(w) => { - KeyboardTarget::enter(w.toplevel().wl_surface(), seat, data, keys, serial) + KeyboardFocusTarget::Element(w) => KeyboardTarget::enter(w, seat, data, keys, serial), + KeyboardFocusTarget::Fullscreen(w) => { + KeyboardTarget::enter(w, seat, data, keys, serial) } - FocusTarget::LayerSurface(l) => { + KeyboardFocusTarget::Group(_) => {} + KeyboardFocusTarget::LayerSurface(l) => { KeyboardTarget::enter(l.wl_surface(), seat, data, keys, serial) } - FocusTarget::Popup(p) => { + KeyboardFocusTarget::Popup(p) => { KeyboardTarget::enter(p.wl_surface(), seat, data, keys, serial) } } } fn leave(&self, seat: &Seat, data: &mut State, serial: Serial) { match self { - FocusTarget::Window(w) => { - KeyboardTarget::leave(w.toplevel().wl_surface(), seat, data, serial) - } - FocusTarget::LayerSurface(l) => { + KeyboardFocusTarget::Element(w) => KeyboardTarget::leave(w, seat, data, serial), + KeyboardFocusTarget::Fullscreen(w) => KeyboardTarget::leave(w, seat, data, serial), + KeyboardFocusTarget::Group(_) => {} + KeyboardFocusTarget::LayerSurface(l) => { KeyboardTarget::leave(l.wl_surface(), seat, data, serial) } - FocusTarget::Popup(p) => KeyboardTarget::leave(p.wl_surface(), seat, data, serial), + KeyboardFocusTarget::Popup(p) => { + KeyboardTarget::leave(p.wl_surface(), seat, data, serial) + } } } fn key( @@ -124,19 +187,17 @@ impl KeyboardTarget for FocusTarget { time: u32, ) { match self { - FocusTarget::Window(w) => KeyboardTarget::key( - w.toplevel().wl_surface(), - seat, - data, - key, - state, - serial, - time, - ), - FocusTarget::LayerSurface(l) => { + KeyboardFocusTarget::Element(w) => { + KeyboardTarget::key(w, seat, data, key, state, serial, time) + } + KeyboardFocusTarget::Fullscreen(w) => { + KeyboardTarget::key(w, seat, data, key, state, serial, time) + } + KeyboardFocusTarget::Group(_) => {} + KeyboardFocusTarget::LayerSurface(l) => { KeyboardTarget::key(l.wl_surface(), seat, data, key, state, serial, time) } - FocusTarget::Popup(p) => { + KeyboardFocusTarget::Popup(p) => { KeyboardTarget::key(p.wl_surface(), seat, data, key, state, serial, time) } } @@ -149,50 +210,113 @@ impl KeyboardTarget for FocusTarget { serial: Serial, ) { match self { - FocusTarget::Window(w) => { - KeyboardTarget::modifiers(w.toplevel().wl_surface(), seat, data, modifiers, serial) + KeyboardFocusTarget::Element(w) => { + KeyboardTarget::modifiers(w, seat, data, modifiers, serial) } - FocusTarget::LayerSurface(l) => { + KeyboardFocusTarget::Fullscreen(w) => { + KeyboardTarget::modifiers(w, seat, data, modifiers, serial) + } + KeyboardFocusTarget::Group(_) => {} + KeyboardFocusTarget::LayerSurface(l) => { KeyboardTarget::modifiers(l.wl_surface(), seat, data, modifiers, serial) } - FocusTarget::Popup(p) => { + KeyboardFocusTarget::Popup(p) => { KeyboardTarget::modifiers(p.wl_surface(), seat, data, modifiers, serial) } } } } -impl WaylandFocus for FocusTarget { - fn wl_surface(&self) -> Option<&WlSurface> { - Some(match self { - FocusTarget::Window(w) => w.toplevel().wl_surface(), - FocusTarget::LayerSurface(l) => l.wl_surface(), - FocusTarget::Popup(p) => p.wl_surface(), - }) +impl WaylandFocus for KeyboardFocusTarget { + fn wl_surface(&self) -> Option { + match self { + KeyboardFocusTarget::Element(w) => WaylandFocus::wl_surface(w), + KeyboardFocusTarget::Fullscreen(w) => WaylandFocus::wl_surface(w), + KeyboardFocusTarget::Group(_) => None, + KeyboardFocusTarget::LayerSurface(l) => Some(l.wl_surface().clone()), + KeyboardFocusTarget::Popup(p) => Some(p.wl_surface().clone()), + } } fn same_client_as(&self, object_id: &ObjectId) -> bool { match self { - FocusTarget::Window(w) => w.toplevel().wl_surface().id().same_client_as(object_id), - FocusTarget::LayerSurface(l) => l.wl_surface().id().same_client_as(object_id), - FocusTarget::Popup(p) => p.wl_surface().id().same_client_as(object_id), + KeyboardFocusTarget::Element(w) => WaylandFocus::same_client_as(w, object_id), + KeyboardFocusTarget::Fullscreen(w) => WaylandFocus::same_client_as(w, object_id), + KeyboardFocusTarget::Group(_) => false, + KeyboardFocusTarget::LayerSurface(l) => l.wl_surface().id().same_client_as(object_id), + KeyboardFocusTarget::Popup(p) => p.wl_surface().id().same_client_as(object_id), } } } -impl From for FocusTarget { +impl WaylandFocus for PointerFocusTarget { + fn wl_surface(&self) -> Option { + Some(match self { + PointerFocusTarget::Element(w) => WaylandFocus::wl_surface(w)?, + PointerFocusTarget::Fullscreen(w) => WaylandFocus::wl_surface(w)?, + PointerFocusTarget::LayerSurface(l) => l.wl_surface().clone(), + PointerFocusTarget::Popup(p) => p.wl_surface().clone(), + }) + } + fn same_client_as(&self, object_id: &ObjectId) -> bool { + match self { + PointerFocusTarget::Element(w) => WaylandFocus::same_client_as(w, object_id), + PointerFocusTarget::Fullscreen(w) => WaylandFocus::same_client_as(w, object_id), + PointerFocusTarget::LayerSurface(l) => l.wl_surface().id().same_client_as(object_id), + PointerFocusTarget::Popup(p) => p.wl_surface().id().same_client_as(object_id), + } + } +} + +impl From for PointerFocusTarget { + fn from(w: CosmicMapped) -> Self { + PointerFocusTarget::Element(w) + } +} + +impl From for PointerFocusTarget { fn from(w: Window) -> Self { - FocusTarget::Window(w) + PointerFocusTarget::Fullscreen(w) } } -impl From for FocusTarget { +impl From for PointerFocusTarget { fn from(l: LayerSurface) -> Self { - FocusTarget::LayerSurface(l) + PointerFocusTarget::LayerSurface(l) } } -impl From for FocusTarget { +impl From for PointerFocusTarget { fn from(p: PopupKind) -> Self { - FocusTarget::Popup(p) + PointerFocusTarget::Popup(p) + } +} + +impl From for KeyboardFocusTarget { + fn from(w: CosmicMapped) -> Self { + KeyboardFocusTarget::Element(w) + } +} + +impl From for KeyboardFocusTarget { + fn from(w: Window) -> Self { + KeyboardFocusTarget::Fullscreen(w) + } +} + +impl From for KeyboardFocusTarget { + fn from(w: WindowGroup) -> Self { + KeyboardFocusTarget::Group(w) + } +} + +impl From for KeyboardFocusTarget { + fn from(l: LayerSurface) -> Self { + KeyboardFocusTarget::LayerSurface(l) + } +} + +impl From for KeyboardFocusTarget { + fn from(p: PopupKind) -> Self { + KeyboardFocusTarget::Popup(p) } } diff --git a/src/shell/grabs.rs b/src/shell/grabs.rs index cdb06a57..d27a2f62 100644 --- a/src/shell/grabs.rs +++ b/src/shell/grabs.rs @@ -51,10 +51,11 @@ impl Shell { .space .outputs_for_window(&window) .into_iter() - .find(|o| o.geometry().contains(pos.to_i32_round())) { - Some(o) => o, - None => return, - }; + .find(|o| o.geometry().contains(pos.to_i32_round())) + { + Some(o) => o, + None => return, + }; let mut initial_window_location = workspace.space.window_location(&window).unwrap(); match &window.toplevel() { @@ -356,7 +357,7 @@ impl MoveSurfaceGrab { time: u32, ) { // No more buttons are pressed, release the grab. - let output = active_output(&self.seat, &state.common); + let output = self.seat.active_output(); let seat = self.seat.clone(); state.common.event_loop_handle.insert_idle(move |data| { diff --git a/src/shell/layout/floating/grabs.rs b/src/shell/layout/floating/grabs.rs index f1ad8f1f..800150aa 100644 --- a/src/shell/layout/floating/grabs.rs +++ b/src/shell/layout/floating/grabs.rs @@ -1,16 +1,13 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::utils::prelude::*; +use crate::{shell::focus::target::PointerFocusTarget, utils::prelude::*}; use smithay::{ desktop::{Kind, Window}, input::pointer::{ AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle, }, - reexports::{ - wayland_protocols::xdg::shell::server::xdg_toplevel, - wayland_server::protocol::wl_surface::WlSurface, - }, + reexports::wayland_protocols::xdg::shell::server::xdg_toplevel, utils::{IsAlive, Logical, Point, Serial, Size}, wayland::{ compositor::with_states, @@ -90,7 +87,7 @@ impl PointerGrab for ResizeSurfaceGrab { &mut self, data: &mut State, handle: &mut PointerInnerHandle<'_, State>, - _focus: Option<(WlSurface, Point)>, + _focus: Option<(PointerFocusTarget, Point)>, event: &MotionEvent, ) { // While the grab is active, no client has pointer focus diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 7cd99d0a..a64539c0 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -1,99 +1,86 @@ // SPDX-License-Identifier: GPL-3.0-only use smithay::{ - desktop::{layer_map_for_output, space::RenderZindex, Kind, Space, Window}, - input::{ - pointer::{Focus, GrabStartData as PointerGrabStartData}, - Seat, - }, + backend::renderer::{ImportAll, Renderer}, + desktop::{layer_map_for_output, space::SpaceElement, Space, Window}, + input::Seat, output::Output, - reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::{ - ResizeEdge, State as XdgState, - }, - utils::{IsAlive, Logical, Point, Rectangle, Serial}, - wayland::{compositor::with_states, shell::xdg::XdgToplevelSurfaceRoleAttributes}, + render_elements, + utils::{Logical, Point, Rectangle}, }; -use std::{collections::HashSet, sync::Mutex}; +use std::collections::HashMap; -use crate::state::State; +use crate::{ + shell::{ + element::{CosmicMapped, CosmicMappedRenderElement}, + OutputNotMapped, + }, + state::State, + utils::prelude::*, +}; mod grabs; pub use self::grabs::*; -pub const FLOATING_INDEX: u8 = RenderZindex::Shell as u8 + 1; - -#[derive(Debug, Default)] +#[derive(Debug)] pub struct FloatingLayout { - pending_windows: Vec, - pub windows: HashSet, + pub(in crate::shell) space: Space, } -#[derive(Default)] -pub struct WindowUserDataInner { - last_geometry: Rectangle, +impl Default for FloatingLayout { + fn default() -> Self { + FloatingLayout { + space: Space::new(None), + } + } } -pub type WindowUserData = Mutex; impl FloatingLayout { pub fn new() -> FloatingLayout { Default::default() } - pub fn map_window( + pub fn map_output(&mut self, output: &Output, location: Point) { + self.space.map_output(output, location) + } + + pub fn unmap_output(&mut self, output: &Output) { + self.space.unmap_output(output); + self.refresh(); + } + + pub fn map( &mut self, - space: &mut Space, - window: Window, + mapped: impl Into, seat: &Seat, position: impl Into>>, ) { - if let Some(output) = super::output_from_seat(Some(seat), space) { - self.map_window_internal(space, window, &output, position.into()); - } else { - self.pending_windows.push(window); - } + let mapped = mapped.into(); + let output = seat.active_output(); + let position = position.into(); + + self.map_internal(mapped, &output, position) } - pub fn refresh(&mut self, space: &mut Space) { - self.pending_windows.retain(|w| w.toplevel().alive()); - if let Some(output) = super::output_from_seat(None, space) { - for window in std::mem::take(&mut self.pending_windows).into_iter() { - self.map_window_internal(space, window, &output, None); - } - } - // TODO make sure all windows are still visible on any output or move them - } - - fn map_window_internal( + pub(in crate::shell) fn map_internal( &mut self, - space: &mut Space, - window: Window, + mapped: CosmicMapped, output: &Output, position: Option>, ) { - let last_geometry = window - .user_data() - .get::() - .map(|u| u.lock().unwrap().last_geometry); - let mut win_geo = window.geometry(); + let mut win_geo = mapped.geometry(); let layers = layer_map_for_output(&output); let geometry = layers.non_exclusive_zone(); + let last_geometry = mapped.last_geometry.lock().unwrap().clone(); let mut geo_updated = false; - if let Some(size) = last_geometry.clone().map(|g| g.size) { + if let Some(size) = last_geometry.map(|g| g.size) { geo_updated = win_geo.size == size; win_geo.size = size; } { - let (min_size, max_size) = with_states(window.toplevel().wl_surface(), |states| { - let attrs = states - .data_map - .get::>() - .unwrap() - .lock() - .unwrap(); - (attrs.min_size, attrs.max_size) - }); + let (min_size, max_size) = (mapped.min_size(), mapped.max_size()); if win_geo.size.w > geometry.size.w / 3 * 2 { // try a more reasonable size let mut width = geometry.size.w / 3 * 2; @@ -136,103 +123,67 @@ impl FloatingLayout { .into() }); - #[allow(irrefutable_let_patterns)] - if let Kind::Xdg(xdg) = &window.toplevel() { - xdg.with_pending_state(|state| { - state.states.unset(XdgState::TiledLeft); - state.states.unset(XdgState::TiledRight); - state.states.unset(XdgState::TiledTop); - state.states.unset(XdgState::TiledBottom); - if geo_updated { - state.size = Some(win_geo.size); - } - }); - xdg.send_configure(); + // TODO: Move this into CosmicMapped, this needs to differciate between stacks and windows + mapped.set_tiled(false); + if geo_updated { + mapped.set_size(win_geo.size); } + mapped.configure(); - space.map_window(&window, position, FLOATING_INDEX, false); - self.windows.insert(window); + self.space.map_element(mapped, position, false); } - pub fn unmap_window(&mut self, space: &mut Space, window: &Window) { + pub fn unmap(&mut self, window: &CosmicMapped) -> bool { #[allow(irrefutable_let_patterns)] - let is_maximized = match &window.toplevel() { - Kind::Xdg(surface) => { - surface.with_pending_state(|state| state.states.contains(XdgState::Maximized)) - } - }; + let is_maximized = window.is_maximized(); if !is_maximized { - if let Some(location) = space.window_location(window) { - let user_data = window.user_data(); - user_data.insert_if_missing(|| WindowUserData::default()); - user_data - .get::() - .unwrap() - .lock() - .unwrap() - .last_geometry = Rectangle::from_loc_and_size(location, window.geometry().size); + if let Some(location) = self.space.element_location(window) { + *window.last_geometry.lock().unwrap() = Some(Rectangle::from_loc_and_size( + location, + window.geometry().size, + )); } } - space.unmap_window(window); - self.pending_windows.retain(|w| w != window); - self.windows.remove(window); + let was_unmaped = self.space.elements().any(|e| e == window); + self.space.unmap_elem(&window); + was_unmaped } - pub fn maximize_request(&mut self, space: &mut Space, window: &Window, output: &Output) { + pub fn element_geometry(&self, elem: &CosmicMapped) -> Option> { + self.space.element_geometry(elem) + } + + pub fn maximize_request(&mut self, window: &CosmicMapped, output: &Output) { let layers = layer_map_for_output(&output); let geometry = layers.non_exclusive_zone(); - if let Some(location) = space.window_location(window) { - let user_data = window.user_data(); - user_data.insert_if_missing(|| WindowUserData::default()); - user_data - .get::() - .unwrap() - .lock() - .unwrap() - .last_geometry = Rectangle::from_loc_and_size(location, window.geometry().size); + if let Some(location) = self.space.element_location(window) { + *window.last_geometry.lock().unwrap() = Some(Rectangle::from_loc_and_size( + location, + window.geometry().size, + )); } - space.map_window( - &window, - (geometry.loc.x, geometry.loc.y), - FLOATING_INDEX, - true, - ); - #[allow(irrefutable_let_patterns)] - if let Kind::Xdg(surface) = &window.toplevel() { - surface.with_pending_state(|state| { - state.states.set(XdgState::Maximized); - state.size = Some(geometry.size); - }); - window.configure(); - } + self.space.map_element(window.clone(), geometry.loc, true); + window.set_maximized(true); + window.set_size(geometry.size); + window.configure(); } - pub fn unmaximize_request(&mut self, space: &mut Space, window: &Window) { - let last_geometry = window - .user_data() - .get::() - .map(|u| u.lock().unwrap().last_geometry); - match window.toplevel() { - Kind::Xdg(toplevel) => { - toplevel.with_pending_state(|state| { - state.states.unset(XdgState::Maximized); - state.size = last_geometry.map(|g| g.size); - }); - toplevel.send_configure(); - } - } - if let Some(last_location) = last_geometry.map(|g| g.loc) { - space.map_window(&window, last_location, FLOATING_INDEX, true); - } + pub fn unmaximize_request(&mut self, window: &CosmicMapped) { + let last_geometry = window.last_geometry.lock().unwrap().clone(); + window.set_maximized(false); + window.set_size(last_geometry.map(|g| g.size).expect("No previous size?")); + window.configure(); + let last_location = last_geometry.map(|g| g.loc).expect("No previous location?"); + self.space.map_element(window.clone(), last_location, true); } + /* pub fn resize_request( - state: &mut State, - window: &Window, + window: &CosmicWindow, seat: &Seat, serial: Serial, start_data: PointerGrabStartData, @@ -256,4 +207,111 @@ impl FloatingLayout { pointer.set_grab(state, grab, serial, Focus::Clear); } } + */ + + pub fn mapped(&self) -> impl Iterator { + self.space.elements() + } + + pub fn windows(&self) -> impl Iterator + '_ { + self.mapped().flat_map(|e| e.windows().map(|(w, _)| w)) + } + + pub fn refresh(&mut self) { + for element in self + .space + .elements() + .filter(|e| self.space.outputs_for_element(e).is_empty()) + .cloned() + .collect::>() + .into_iter() + { + // TODO what about windows leaving to the top with no headerbar to drag? can that happen? (Probably if the user is moving outputs down) + *element.last_geometry.lock().unwrap() = None; + let output = self.space.outputs().next().unwrap().clone(); + self.map_internal(element, &output, None); + } + self.space.refresh() + } + + pub fn most_overlapped_output_for_element(&self, elem: &CosmicMapped) -> Option { + let elem_geo = self.space.element_geometry(elem)?; + + if self.space.outputs().nth(1).is_none() { + return self.space.outputs().next().cloned(); + } + + Some( + self.space + .outputs_for_element(elem) + .into_iter() + .max_by_key(|o| { + let output_geo = self.space.output_geometry(o).unwrap(); + if let Some(intersection) = output_geo.intersection(elem_geo) { + intersection.size.w * intersection.size.h + } else { + 0 + } + }) + .unwrap_or(self.space.outputs().next().unwrap().clone()), + ) + } + + pub fn merge(&mut self, other: FloatingLayout) { + let mut output_pos_map = HashMap::new(); + for output in self.space.outputs() { + output_pos_map.insert( + output.clone(), + self.space.output_geometry(output).unwrap().loc + - other + .space + .output_geometry(output) + .map(|geo| geo.loc) + .unwrap_or_else(|| (0, 0).into()), + ); + } + for element in other.space.elements() { + let mut elem_geo = other.space.element_geometry(element).unwrap(); + let output = other + .space + .outputs_for_element(element) + .into_iter() + .filter(|o| self.space.outputs().any(|o2| o == o2)) + .max_by_key(|o| { + let output_geo = other.space.output_geometry(o).unwrap(); + let intersection = output_geo.intersection(elem_geo).unwrap(); + intersection.size.w * intersection.size.h + }) + .unwrap_or(self.space.outputs().next().unwrap().clone()); + elem_geo.loc += output_pos_map + .get(&output) + .copied() + .unwrap_or_else(|| (0, 0).into()); + self.space.map_element(element.clone(), elem_geo.loc, false); + } + self.refresh(); //fixup any out of bounds elements + } + + pub fn render_output( + &self, + output: &Output, + ) -> Result>, OutputNotMapped> + where + R: Renderer + ImportAll, + ::TextureId: 'static, + { + let output_scale = output.current_scale().fractional_scale(); + let output_geo = self.space.output_geometry(output).ok_or(OutputNotMapped)?; + Ok(self + .space + .render_elements_for_region::(&output_geo, output_scale) + .into_iter() + .map(FloatingRenderElement::from) + .collect()) + } +} + +render_elements! { + pub FloatingRenderElement where R: ImportAll; + Window=CosmicMappedRenderElement, } diff --git a/src/shell/layout/mod.rs b/src/shell/layout/mod.rs index 8f002037..24353c66 100644 --- a/src/shell/layout/mod.rs +++ b/src/shell/layout/mod.rs @@ -1,11 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::{input::ActiveOutput, state::State}; use regex::RegexSet; use smithay::{ - desktop::{Space, Window}, - input::Seat, - output::Output, + desktop::Window, wayland::{compositor::with_states, shell::xdg::XdgToplevelSurfaceRoleAttributes}, }; use std::sync::Mutex; @@ -118,17 +115,3 @@ pub fn should_be_floating(window: &Window) -> bool { false }) } - -fn output_from_seat(seat: Option<&Seat>, space: &Space) -> Option { - seat.and_then(|seat| { - seat.user_data() - .get::() - .map(|active| active.0.borrow().clone()) - .or_else(|| { - seat.get_pointer() - .map(|ptr| space.output_under(ptr.current_location()).next().unwrap()) - .cloned() - }) - }) - .or_else(|| space.outputs().next().cloned()) -} diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 5496e4e8..8a824935 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -1,63 +1,226 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ - shell::{focus::FocusDirection, layout::Orientation}, + shell::{ + element::{CosmicMapped, CosmicMappedRenderElement}, + focus::{ + target::{KeyboardFocusTarget, WindowGroup}, + FocusDirection, + }, + layout::Orientation, + OutputNotMapped, + }, utils::prelude::*, }; -use atomic_float::AtomicF64; use id_tree::{InsertBehavior, MoveBehavior, Node, NodeId, NodeIdError, RemoveBehavior, Tree}; use smithay::{ - desktop::{layer_map_for_output, Kind, Space, Window}, + backend::renderer::{element::AsRenderElements, ImportAll, Renderer}, + desktop::{layer_map_for_output, Window}, input::{ pointer::{Focus, GrabStartData as PointerGrabStartData}, Seat, }, - reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::{ - ResizeEdge, State as XdgState, - }, - utils::{IsAlive, Rectangle, Serial}, + output::{Output, WeakOutput}, + render_elements, + utils::{IsAlive, Logical, Point, Rectangle, Scale, Serial}, }; use std::{ + borrow::Borrow, cell::RefCell, - collections::HashSet, - sync::{atomic::Ordering, Arc}, + collections::HashMap, + hash::Hash, + sync::{atomic::AtomicBool, Arc}, }; +/* mod grabs; pub use self::grabs::*; +*/ -#[derive(Debug)] -pub struct TilingLayout { - gaps: (i32, i32), - trees: Vec>, - pub windows: HashSet, +#[derive(Debug, Clone)] +struct OutputData { + output: Output, + location: Point, } -#[derive(Debug)] -pub enum Data { - Fork { - orientation: Orientation, - ratio: Arc, - }, - Stack { - active: usize, - len: usize, - }, - Window(Window), +impl Borrow for OutputData { + fn borrow(&self) -> &Output { + &self.output + } +} + +impl PartialEq for OutputData { + fn eq(&self, other: &Self) -> bool { + self.output == other.output + } +} + +impl Eq for OutputData {} + +impl PartialEq for OutputData { + fn eq(&self, other: &Output) -> bool { + &self.output == other + } +} + +impl Hash for OutputData { + fn hash(&self, state: &mut H) { + self.output.hash(state) + } } #[derive(Debug, Clone)] -pub struct WindowInfo { - node: NodeId, - output: usize, +pub struct TilingLayout { + gaps: (i32, i32), + trees: HashMap>, +} + +#[derive(Debug, Clone)] +pub enum Data { + Group { + orientation: Orientation, + sizes: Vec, + last_geometry: Rectangle, + alive: Arc<()>, + }, + Mapped { + mapped: CosmicMapped, + last_geometry: Rectangle, + }, } impl Data { - fn fork() -> Data { - Data::Fork { - orientation: Orientation::Vertical, - ratio: Arc::new(AtomicF64::new(0.5)), + fn new_group(orientation: Orientation, geo: Rectangle) -> Data { + Data::Group { + orientation, + sizes: vec![ + match orientation { + Orientation::Vertical => geo.size.w / 2, + Orientation::Horizontal => geo.size.h / 2, + }; + 2 + ], + last_geometry: geo, + alive: Arc::new(()), + } + } + + fn is_group(&self) -> bool { + matches!(self, Data::Group { .. }) + } + fn is_mapped(&self, mapped: Option<&CosmicMapped>) -> bool { + match mapped { + Some(m) => matches!(self, Data::Mapped { mapped, .. } if m == mapped), + None => matches!(self, Data::Mapped { .. }), + } + } + + fn orientation(&self) -> Orientation { + match self { + Data::Group { orientation, .. } => *orientation, + _ => panic!("Not a group"), + } + } + + fn add_window(&mut self, idx: usize) { + match self { + Data::Group { + sizes, + last_geometry, + orientation, + .. + } => { + let last_length = match orientation { + Orientation::Horizontal => last_geometry.size.h, + Orientation::Vertical => last_geometry.size.w, + }; + let equal_sizing = last_length / (sizes.len() as i32 + 1); // new window size + let remainder = last_length - equal_sizing; // size for the rest of the windowns + + for size in sizes.iter_mut() { + *size = ((*size as f64 / last_length as f64) * remainder as f64).round() as i32; + } + let used_size: i32 = sizes.iter().sum(); + let new_size = last_length - used_size; + + sizes.insert(idx, new_size); + } + Data::Mapped { .. } => panic!("Adding window to leaf?"), + } + } + + fn remove_window(&mut self, idx: usize) { + match self { + Data::Group { + sizes, + last_geometry, + orientation, + .. + } => { + let last_length = match orientation { + Orientation::Horizontal => last_geometry.size.h, + Orientation::Vertical => last_geometry.size.w, + }; + let old_size = sizes.remove(idx); + for size in sizes.iter_mut() { + *size += + ((old_size as f64 / last_length as f64) * (*size as f64)).round() as i32; + } + let used_size: i32 = sizes.iter().sum(); + let overflow = last_length - used_size; + if overflow != 0 { + *sizes.last_mut().unwrap() += overflow; + } + } + Data::Mapped { .. } => panic!("Added window to leaf?"), + } + } + + fn geometry(&self) -> &Rectangle { + match self { + Data::Group { last_geometry, .. } => last_geometry, + Data::Mapped { last_geometry, .. } => last_geometry, + } + } + + fn update_geometry(&mut self, geo: Rectangle) { + match self { + Data::Group { + orientation, + sizes, + last_geometry, + .. + } => { + let previous_length = match orientation { + Orientation::Horizontal => last_geometry.size.h, + Orientation::Vertical => last_geometry.size.w, + }; + let new_length = match orientation { + Orientation::Horizontal => geo.size.h, + Orientation::Vertical => geo.size.w, + }; + + sizes.iter_mut().for_each(|len| { + *len = (((*len as f64) / (previous_length as f64)) * (new_length as f64)) + .round() as i32; + }); + let sum: i32 = sizes.iter().sum(); + if sum < new_length { + *sizes.last_mut().unwrap() += new_length - sum; + } + *last_geometry = geo; + } + Data::Mapped { last_geometry, .. } => { + *last_geometry = geo; + } + } + } + + fn len(&self) -> usize { + match self { + Data::Group { sizes, .. } => sizes.len(), + Data::Mapped { .. } => 1, } } } @@ -66,79 +229,353 @@ impl TilingLayout { pub fn new() -> TilingLayout { TilingLayout { gaps: (0, 4), - trees: Vec::new(), - windows: HashSet::new(), + trees: HashMap::new(), } } } impl TilingLayout { - pub fn map_window<'a>( - &mut self, - space: &mut Space, - window: Window, - seat: &Seat, - focus_stack: impl Iterator + 'a, - ) { - self.map_window_internal(space, &window, Some(seat), Some(focus_stack)); - self.windows.insert(window); - self.refresh(space); + pub fn map_output(&mut self, output: &Output, location: Point) { + if !self.trees.contains_key(output) { + self.trees.insert( + OutputData { + output: output.clone(), + location, + }, + Tree::new(), + ); + } else { + let tree = self.trees.remove(output).unwrap(); + self.trees.insert( + OutputData { + output: output.clone(), + location, + }, + tree, + ); + } } - pub fn move_focus<'a>( + pub fn unmap_output(&mut self, output: &Output) { + if let Some(src) = self.trees.remove(output) { + // TODO: expects last remaining output + let (output, dst) = self.trees.iter_mut().next().unwrap(); + let orientation = match output.output.geometry().size { + x if x.w >= x.h => Orientation::Horizontal, + _ => Orientation::Vertical, + }; + TilingLayout::merge_trees(src, dst, orientation); + self.refresh() + } + } + + pub fn map<'a>( + &mut self, + window: CosmicMapped, + seat: &Seat, + focus_stack: impl Iterator + 'a, + ) { + let output = seat.active_output(); + self.map_internal(window, &output, Some(focus_stack)); + self.refresh(); + } + + fn map_internal<'a>( + &mut self, + window: impl Into, + output: &Output, + focus_stack: Option + 'a>, + ) { + let tree = self.trees.get_mut(output).expect("Output not mapped?"); + let window = window.into(); + let new_window = Node::new(Data::Mapped { + mapped: window.clone(), + last_geometry: Rectangle::from_loc_and_size((0, 0), (100, 100)), + }); + + let last_active = + focus_stack.and_then(|focus_stack| TilingLayout::last_active_window(tree, focus_stack)); + + let window_id = if let Some((_last_active_window, ref node_id)) = last_active { + let orientation = { + let window_size = tree.get(node_id).unwrap().data().geometry().size; + if window_size.w > window_size.h { + Orientation::Vertical + } else { + Orientation::Horizontal + } + }; + + let parent_id = tree.get(node_id).unwrap().parent().cloned(); + match parent_id { + Some(group_id) if tree.get(&group_id).unwrap().data().is_group() => { + TilingLayout::new_group(tree, &group_id, new_window, orientation) + } + None => { + // if there is no window, we couldn't have found the window_id. + // so if there is no parent, we have found a root, thus it is safe to unwrap. + let root_id = tree.root_node_id().cloned().unwrap(); + TilingLayout::new_group(tree, &root_id, new_window, orientation) + } + _ => panic!("Illegal tiling tree structure"), + } + } else { + // nothing? then we add to the root + if let Some(root_id) = tree.root_node_id().cloned() { + let orientation = { + let output_size = output.geometry().size; + if output_size.w > output_size.h { + Orientation::Vertical + } else { + Orientation::Horizontal + } + }; + TilingLayout::new_group(tree, &root_id, new_window, orientation) + } else { + tree.insert(new_window, InsertBehavior::AsRoot) + } + } + .unwrap(); + + *window.tiling_node_id.lock().unwrap() = Some(window_id); + } + + pub fn unmap(&mut self, window: &CosmicMapped) -> bool { + if self.unmap_window_internal(window) { + window.set_tiled(false); + self.refresh(); + true + } else { + false + } + } + + fn unmap_window_internal(&mut self, mapped: &CosmicMapped) -> bool { + if let Some(node_id) = mapped.tiling_node_id.lock().unwrap().as_ref() { + if let Some(tree) = self.trees.values_mut().find(|tree| { + tree.get(node_id) + .map(|node| node.data().is_mapped(Some(mapped))) + .unwrap_or(false) + }) { + let parent_id = tree + .get(&node_id) + .ok() + .and_then(|node| node.parent()) + .cloned(); + let position = parent_id.as_ref().and_then(|parent_id| { + tree.children_ids(&parent_id) + .unwrap() + .position(|id| id == node_id) + }); + let parent_parent_id = parent_id.as_ref().and_then(|parent_id| { + tree.get(parent_id) + .ok() + .and_then(|node| node.parent()) + .cloned() + }); + + // remove self + slog_scope::debug!("Remove window {:?}", mapped); + let _ = tree.remove_node(node_id.clone(), RemoveBehavior::DropChildren); + + // fixup parent node + match parent_id { + Some(id) => { + let position = position.unwrap(); + let group = tree.get_mut(&id).unwrap().data_mut(); + assert!(group.is_group()); + + if group.len() > 2 { + group.remove_window(position); + } else { + slog_scope::debug!("Removing Group"); + let other_child = + tree.children_ids(&id).unwrap().cloned().next().unwrap(); + let fork_pos = parent_parent_id.as_ref().and_then(|parent_id| { + tree.children_ids(parent_id).unwrap().position(|i| i == &id) + }); + let _ = tree.remove_node(id.clone(), RemoveBehavior::OrphanChildren); + tree.move_node( + &other_child, + parent_parent_id + .as_ref() + .map(|parent_id| MoveBehavior::ToParent(parent_id)) + .unwrap_or(MoveBehavior::ToRoot), + ) + .unwrap(); + if let Some(old_pos) = fork_pos { + tree.make_nth_sibling(&other_child, old_pos).unwrap(); + } + } + } + None => {} // root + } + + return true; + } + } + false + } + + pub fn output_for_element(&self, elem: &CosmicMapped) -> Option<&Output> { + self.mapped().find_map(|(o, m, _)| (m == elem).then_some(o)) + } + + pub fn element_geometry(&self, elem: &CosmicMapped) -> Option> { + if let Some(id) = elem.tiling_node_id.lock().unwrap().as_ref() { + if let Some(output) = self.output_for_element(elem) { + let (output_data, tree) = self.trees.get_key_value(output).unwrap(); + let node = tree.get(id).ok()?; + let data = node.data(); + assert!(data.is_mapped(Some(elem))); + let mut geo = *data.geometry(); + geo.loc += output_data.location; + return Some(geo); + } + } + None + } + + pub fn next_focus<'a>( &mut self, direction: FocusDirection, seat: &Seat, - space: &mut Space, - focus_stack: impl Iterator + 'a, - ) -> Option { - let output = super::output_from_seat(Some(seat), space); - let idx = space - .outputs() - .position(|o| Some(o) == output.as_ref()) - .unwrap_or(0); - let tree = TilingLayout::active_tree(&mut self.trees, idx); - if let Some(last_active) = TilingLayout::last_active_window(tree, focus_stack) { - let mut node_id = last_active; - while let Some((fork, child)) = TilingLayout::find_fork(tree, node_id) { - if let &Data::Fork { - ref orientation, .. - } = tree.get(&fork).unwrap().data() - { - // found a fork - // which child are we? - let first = tree.children_ids(&fork).unwrap().next() == Some(&child); - let focus_subtree = match (first, orientation, direction) { - (true, Orientation::Horizontal, FocusDirection::Down) - | (true, Orientation::Vertical, FocusDirection::Right) => { - tree.children_ids(&fork).unwrap().skip(1).next() - } - (false, Orientation::Horizontal, FocusDirection::Up) - | (false, Orientation::Vertical, FocusDirection::Left) => { - tree.children_ids(&fork).unwrap().next() - } - _ => None, // continue iterating - }; + focus_stack: impl Iterator + 'a, + ) -> Option { + let output = seat.active_output(); + let tree = self.trees.get_mut(&output).unwrap(); - if focus_subtree.is_some() { - let mut node_id = focus_subtree; - while node_id.is_some() - && !matches!( - tree.get(node_id.as_ref().unwrap()).unwrap().data(), - Data::Window(_) - ) - { - // TODO, if ndoe_id is a stack, we want to assign the active node instead of the first - node_id = tree.children_ids(node_id.as_ref().unwrap()).unwrap().next(); + // TODO: Rather use something like seat.current_keyboard_focus + // TODO https://github.com/Smithay/smithay/pull/777 + if let Some(last_active) = TilingLayout::last_active_window(tree, focus_stack) { + let (last_window, node_id) = last_active; + + // stacks may handle focus internally + if last_window.handle_focus(direction) { + return None; + } + + while let Some(group) = tree.get(&node_id).unwrap().parent() { + let child = node_id.clone(); + let group_data = tree.get(&group).unwrap().data(); + let main_orientation = group_data.orientation(); + assert!(group_data.is_group()); + + if direction == FocusDirection::Out { + return Some( + WindowGroup { + node: group.clone(), + output: output.downgrade(), + alive: match group_data { + &Data::Group { ref alive, .. } => Arc::downgrade(alive), + _ => unreachable!(), + }, } - if let Some(Data::Window(window)) = - node_id.and_then(|i| tree.get(&i).ok()).map(|n| n.data()) - { - return Some(window.clone()); + .into(), + ); + } + + // which child are we? + let idx = tree + .children_ids(&group) + .unwrap() + .position(|id| id == &child) + .unwrap(); + let len = group_data.len(); + + let focus_subtree = match (main_orientation, direction) { + (Orientation::Horizontal, FocusDirection::Down) + | (Orientation::Vertical, FocusDirection::Right) + if idx < (len - 1) => + { + tree.children_ids(&group).unwrap().skip(idx + 1).next() + } + (Orientation::Horizontal, FocusDirection::Up) + | (Orientation::Vertical, FocusDirection::Left) + if idx > 0 => + { + tree.children_ids(&group).unwrap().skip(idx - 1).next() + } + _ => None, // continue iterating + }; + + if focus_subtree.is_some() { + let mut node_id = focus_subtree; + while node_id.is_some() { + match tree.get(node_id.unwrap()).unwrap().data() { + Data::Group { orientation, .. } if orientation == &main_orientation => { + // if the group is layed out in the direction we care about, + // we can just use the first or last element (depending on the direction) + match direction { + FocusDirection::Down | FocusDirection::Right => { + node_id = tree + .children_ids(node_id.as_ref().unwrap()) + .unwrap() + .next(); + } + FocusDirection::Up | FocusDirection::Left => { + node_id = tree + .children_ids(node_id.as_ref().unwrap()) + .unwrap() + .last(); + } + _ => unreachable!(), + } + } + Data::Group { .. } => { + let center = { + let geo = tree.get(&child).unwrap().data().geometry(); + let mut point = geo.loc; + match direction { + FocusDirection::Down => { + point += Point::from((geo.size.w / 2, geo.size.h)) + } + FocusDirection::Up => point.x += geo.size.w, + FocusDirection::Left => point.y += geo.size.h / 2, + FocusDirection::Right => { + point += Point::from((geo.size.w, geo.size.h / 2)) + } + _ => unreachable!(), + }; + point.to_f64() + }; + + node_id = tree + .children_ids(node_id.as_ref().unwrap()) + .unwrap() + .min_by(|node1, node2| { + let distance = |candidate: &&NodeId| -> f64 { + let geo = + tree.get(candidate).unwrap().data().geometry(); + let mut point = geo.loc; + match direction { + FocusDirection::Up => { + point += + Point::from((geo.size.w / 2, geo.size.h)) + } + FocusDirection::Down => point.x += geo.size.w, + FocusDirection::Right => point.y += geo.size.h / 2, + FocusDirection::Left => { + point += + Point::from((geo.size.w, geo.size.h / 2)) + } + _ => unreachable!(), + }; + let point = point.to_f64(); + ((point.x - center.x).powi(2) + + (point.y - center.y).powi(2)) + .sqrt() + }; + + distance(node1).total_cmp(&distance(node2)) + }); + } + Data::Mapped { mapped, .. } => { + return Some(mapped.clone().into()); + } } } } - node_id = fork; } } @@ -149,92 +586,59 @@ impl TilingLayout { &mut self, new_orientation: Orientation, seat: &Seat, - space: &mut Space, - focus_stack: impl Iterator + 'a, + focus_stack: impl Iterator + 'a, ) { - let output = super::output_from_seat(Some(seat), space); - let idx = space - .outputs() - .position(|o| Some(o) == output.as_ref()) - .unwrap_or(0); - let tree = TilingLayout::active_tree(&mut self.trees, idx); - if let Some(last_active) = TilingLayout::last_active_window(tree, focus_stack) { - if let Some((fork, _child)) = TilingLayout::find_fork(tree, last_active) { - if let &mut Data::Fork { + let output = seat.active_output(); + let tree = self.trees.get_mut(&output).unwrap(); + if let Some((_, last_active)) = TilingLayout::last_active_window(tree, focus_stack) { + if let Some(group) = tree.get(&last_active).unwrap().parent().cloned() { + if let &mut Data::Group { ref mut orientation, + ref mut sizes, + ref last_geometry, .. - } = tree.get_mut(&fork).unwrap().data_mut() + } = tree.get_mut(&group).unwrap().data_mut() { + let previous_length = match orientation { + Orientation::Horizontal => last_geometry.size.h, + Orientation::Vertical => last_geometry.size.w, + }; + let new_length = match new_orientation { + Orientation::Horizontal => last_geometry.size.h, + Orientation::Vertical => last_geometry.size.w, + }; + + sizes.iter_mut().for_each(|len| { + *len = (((*len as f64) / (previous_length as f64)) * (new_length as f64)) + .round() as i32; + }); + let sum: i32 = sizes.iter().sum(); + if sum < new_length { + *sizes.last_mut().unwrap() += new_length - sum; + } + *orientation = new_orientation; } } } - self.refresh(space); + self.refresh(); } - pub fn refresh<'a>(&mut self, space: &mut Space) { - let active_outputs = space.outputs().count(); - if self.trees.len() > active_outputs { - for tree in self - .trees - .drain(active_outputs..self.trees.len()) - .collect::>() - .into_iter() - { - if let Some(root_id) = tree.root_node_id() { - for node in tree.traverse_pre_order(root_id).unwrap() { - if let Data::Window(window) = node.data() { - self.map_window_internal( - space, - window, - None, - Option::>::None, - ); - } - } - } - } - } - - let mut changed = false; - while let Some(dead_windows) = Some(TilingLayout::update_space_positions( - &mut self.trees, - space, - self.gaps, - )) - .filter(|v| !v.is_empty()) - { - for window in dead_windows { - self.unmap_window_internal(&window); - } - changed = true; - } - if changed { - for window in &self.windows { - update_reactive_popups(space, window); - } + pub fn refresh<'a>(&mut self) { + let dead_windows = self + .mapped() + .map(|(_, w, _)| w.clone()) + .filter(|w| !w.alive()) + .collect::>(); + for dead_window in dead_windows.iter() { + self.unmap_window_internal(&dead_window); } + TilingLayout::update_space_positions(&mut self.trees, self.gaps); } - pub fn unmap_window(&mut self, space: &mut Space, window: &Window) { - self.unmap_window_internal(window); - space.unmap_window(window); - #[allow(irrefutable_let_patterns)] - if let Kind::Xdg(xdg) = &window.toplevel() { - xdg.with_pending_state(|state| { - state.states.unset(XdgState::TiledLeft); - state.states.unset(XdgState::TiledRight); - state.states.unset(XdgState::TiledTop); - state.states.unset(XdgState::TiledBottom); - }); - } - self.windows.remove(window); - self.refresh(space); - } - + /* pub fn resize_request( - state: &mut State, - window: &Window, + window: &CosmicWindow, seat: &Seat, serial: Serial, start_data: PointerGrabStartData, @@ -293,179 +697,30 @@ impl TilingLayout { } } } - - fn active_tree<'a>(trees: &'a mut Vec>, output: usize) -> &'a mut Tree { - while trees.len() <= output { - trees.push(Tree::new()) - } - &mut trees[output] - } + */ fn last_active_window<'a>( tree: &mut Tree, - mut focus_stack: impl Iterator, - ) -> Option { - let last_active = focus_stack - .find_map(|window| tree.root_node_id() + mut focus_stack: impl Iterator, + ) -> Option<(CosmicMapped, NodeId)> { + focus_stack + .find_map(|mapped| tree.root_node_id() .and_then(|root| tree.traverse_pre_order_ids(root).unwrap() - .find(|id| matches!(tree.get(id).map(|n| n.data()), Ok(Data::Window(w)) if w == window)) - ) - ); - - last_active - } - - fn find_fork(tree: &mut Tree, mut node_id: NodeId) -> Option<(NodeId, NodeId)> { - while let Some(parent_id) = tree.get(&node_id).unwrap().parent().cloned() { - if let &Data::Fork { .. } = tree.get(&parent_id).unwrap().data() { - return Some((parent_id, node_id)); - } - node_id = parent_id; - } - None - } - - fn map_window_internal<'a>( - &mut self, - space: &mut Space, - window: &Window, - seat: Option<&Seat>, - focus_stack: Option + 'a>, - ) { - let output = super::output_from_seat(seat, space); - let idx = space - .outputs() - .position(|o| Some(o) == output.as_ref()) - .unwrap_or(0); - let tree = TilingLayout::active_tree(&mut self.trees, idx); - let new_window = Node::new(Data::Window(window.clone())); - - let last_active = focus_stack.and_then(|mut iter| - iter.find_map(|window| tree.root_node_id() - .and_then(|root| tree.traverse_pre_order_ids(root).unwrap() - .find(|id| matches!(tree.get(id).map(|n| n.data()), Ok(Data::Window(w)) if w == window)) - ) + .find(|id| matches!(tree.get(id).map(|n| n.data()), Ok(Data::Mapped { mapped: m, .. }) if m == mapped)) + ).map(|id| (mapped.clone(), id)) ) - ); - let window_id = if let Some(ref node_id) = last_active { - let parent_id = tree.get(node_id).unwrap().parent().cloned(); - if let Some(stack_id) = - parent_id.filter(|id| matches!(tree.get(id).unwrap().data(), Data::Stack { .. })) - { - // we add to the stack - let window_id = tree - .insert(new_window, InsertBehavior::UnderNode(&stack_id)) - .unwrap(); - if let Data::Stack { - ref mut len, - ref mut active, - } = tree.get_mut(&stack_id).unwrap().data_mut() - { - *active = *len; - *len += 1; - } - Ok(window_id) - } else { - // we create a new fork - TilingLayout::new_fork(tree, node_id, new_window) - } - } else { - // nothing? then we add to the root - if let Some(root_id) = tree.root_node_id().cloned() { - TilingLayout::new_fork(tree, &root_id, new_window) - } else { - tree.insert(new_window, InsertBehavior::AsRoot) - } - } - .unwrap(); - - { - let user_data = window.user_data(); - let window_info = WindowInfo { - node: window_id.clone(), - output: idx, - }; - // insert or update - if !user_data.insert_if_missing(|| RefCell::new(window_info.clone())) { - *user_data.get::>().unwrap().borrow_mut() = window_info; - } - } } - fn unmap_window_internal(&mut self, window: &Window) { - if let Some(info) = window.user_data().get::>() { - let output = info.borrow().output; - let tree = TilingLayout::active_tree(&mut self.trees, output); - - let node_id = info.borrow().node.clone(); - let parent_id = tree - .get(&node_id) - .ok() - .and_then(|node| node.parent()) - .cloned(); - let parent_parent_id = parent_id.as_ref().and_then(|parent_id| { - tree.get(parent_id) - .ok() - .and_then(|node| node.parent()) - .cloned() - }); - - // remove self - slog_scope::debug!("Remove window {:?}", window); - let _ = tree.remove_node(node_id.clone(), RemoveBehavior::DropChildren); - - // fixup parent node - match parent_id { - Some(id) if matches!(tree.get(&id).unwrap().data(), Data::Fork { .. }) => { - slog_scope::debug!("Removing Fork"); - let other_child = tree.children_ids(&id).unwrap().cloned().next().unwrap(); - let fork_pos = parent_parent_id.as_ref().and_then(|parent_id| { - tree.children_ids(parent_id).unwrap().position(|i| i == &id) - }); - let _ = tree.remove_node(id.clone(), RemoveBehavior::OrphanChildren); - tree.move_node( - &other_child, - parent_parent_id - .as_ref() - .map(|parent_id| MoveBehavior::ToParent(parent_id)) - .unwrap_or(MoveBehavior::ToRoot), - ) - .unwrap(); - if let Some(old_pos) = fork_pos { - tree.make_nth_sibling(&other_child, old_pos).unwrap(); - } - } - Some(id) if matches!(tree.get(&id).unwrap().data(), Data::Stack { .. }) => { - if tree.children_ids(&id).unwrap().count() == 0 { - slog_scope::debug!("Removing Stack"); - // remove stack - let _ = tree.remove_node(id.clone(), RemoveBehavior::DropChildren); - // TODO now we need to untangle the parent_parent - // So we should REFACTOR this unmap function to not only work with windows - } else { - // fixup stack values - if let Data::Stack { - ref mut active, - ref mut len, - } = tree.get_mut(&id).unwrap().data_mut() - { - *len -= 1; - *active = std::cmp::max(*active, *len - 1); - } - } - } - None => {} // root - _ => unreachable!(), - } - } - } - - fn new_fork( + fn new_group( tree: &mut Tree, old_id: &NodeId, new: Node, + orientation: Orientation, ) -> Result { - let new_fork = Node::new(Data::fork()); + let new_group = Node::new(Data::new_group( + orientation, + Rectangle::from_loc_and_size((0, 0), (100, 100)), + )); let old = tree.get(old_id)?; let parent_id = old.parent().cloned(); let pos = parent_id.as_ref().and_then(|parent_id| { @@ -474,9 +729,9 @@ impl TilingLayout { .position(|id| id == old_id) }); - let fork_id = tree + let group_id = tree .insert( - new_fork, + new_group, if let Some(parent) = parent_id.as_ref() { InsertBehavior::UnderNode(parent) } else { @@ -485,30 +740,21 @@ impl TilingLayout { ) .unwrap(); - tree.move_node(old_id, MoveBehavior::ToParent(&fork_id)) + tree.move_node(old_id, MoveBehavior::ToParent(&group_id)) .unwrap(); // keep position if let Some(old_pos) = pos { - tree.make_nth_sibling(&fork_id, old_pos).unwrap(); + tree.make_nth_sibling(&group_id, old_pos).unwrap(); } - tree.insert(new, InsertBehavior::UnderNode(&fork_id)) + tree.insert(new, InsertBehavior::UnderNode(&group_id)) } - fn update_space_positions( - trees: &mut Vec>, - space: &mut Space, - gaps: (i32, i32), - ) -> Vec { - let mut dead_windows = Vec::new(); + fn update_space_positions(trees: &mut HashMap>, gaps: (i32, i32)) { let (outer, inner) = gaps; - for (idx, output) in space - .outputs() - .cloned() - .enumerate() - .collect::>() - .into_iter() + for (output, tree) in trees + .iter_mut() + .map(|(output_data, tree)| (&output_data.output, tree)) { - let tree = TilingLayout::active_tree(trees, idx); if let Some(root) = tree.root_node_id() { let mut stack = Vec::new(); @@ -519,98 +765,179 @@ impl TilingLayout { geo.loc.y += outer; geo.size.w -= outer * 2; geo.size.h -= outer * 2; + + if tree.get(root).unwrap().data().geometry() == geo { + continue; + } } - for node in tree.traverse_pre_order(root).unwrap() { + for node_id in tree + .traverse_pre_order_ids(root) + .unwrap() + .collect::>() + .into_iter() + { + let node = tree.get_mut(&node_id).unwrap(); let geo = stack.pop().unwrap_or(geo); - match node.data() { - Data::Fork { orientation, ratio } => { - if let Some(geo) = geo { - match orientation { - Orientation::Horizontal => { - let top_size = ( - geo.size.w, - ((geo.size.h as f64) * ratio.load(Ordering::SeqCst)) - .ceil() - as i32, - ); + if let Some(geo) = geo { + let data = node.data_mut(); + data.update_geometry(geo); + match data { + Data::Group { + orientation, sizes, .. + } => match orientation { + Orientation::Horizontal => { + let mut previous = 0; + for size in sizes { stack.push(Some(Rectangle::from_loc_and_size( - (geo.loc.x, geo.loc.y + top_size.1), - (geo.size.w, geo.size.h - top_size.1), - ))); - stack.push(Some(Rectangle::from_loc_and_size( - geo.loc, top_size, - ))); - } - Orientation::Vertical => { - let left_size = ( - ((geo.size.w as f64) * ratio.load(Ordering::SeqCst)) - .ceil() - as i32, - geo.size.h, - ); - stack.push(Some(Rectangle::from_loc_and_size( - (geo.loc.x + left_size.0, geo.loc.y), - (geo.size.w - left_size.0, geo.size.h), - ))); - stack.push(Some(Rectangle::from_loc_and_size( - geo.loc, left_size, + (geo.loc.x, geo.loc.y + previous), + (geo.size.w, *size), ))); + previous += *size; } } - } else { - stack.push(None); - stack.push(None); - } - } - Data::Stack { active, len } => { - for i in 0..*len { - if i == *active { - stack.push(geo); - } else { - stack.push(None); - } - } - } - Data::Window(window) => { - if window.alive() { - if let Some(geo) = geo { - #[allow(irrefutable_let_patterns)] - if let Kind::Xdg(xdg) = &window.toplevel() { - if xdg.current_state().states.contains(XdgState::Fullscreen) - || xdg.with_pending_state(|pending| { - pending.states.contains(XdgState::Fullscreen) - }) - { - continue; - } - xdg.with_pending_state(|state| { - state.size = Some( - (geo.size.w - inner * 2, geo.size.h - inner * 2) - .into(), - ); - state.states.set(XdgState::TiledLeft); - state.states.set(XdgState::TiledRight); - state.states.set(XdgState::TiledTop); - state.states.set(XdgState::TiledBottom); - }); - xdg.send_configure(); + Orientation::Vertical => { + let mut previous = 0; + for size in sizes { + stack.push(Some(Rectangle::from_loc_and_size( + (geo.loc.x + previous, geo.loc.y), + (*size, geo.size.h), + ))); + previous += *size; } - space.map_window( - &window, - (geo.loc.x + inner, geo.loc.y + inner), - None, - false, + } + }, + Data::Mapped { mapped, .. } => { + if !mapped.is_fullscreen() { + mapped.set_tiled(true); + mapped.set_size( + (geo.size.w - inner * 2, geo.size.h - inner * 2).into(), ); + mapped.configure(); } - } else { - dead_windows.push(window.clone()); } } + } else if node.data().is_group() { + stack.push(None); + stack.push(None); } } } } - dead_windows + } + + pub fn mapped(&self) -> impl Iterator)> { + self.trees + .iter() + .flat_map(|(output_data, tree)| { + if let Some(root) = tree.root_node_id() { + Some( + tree.traverse_pre_order(root) + .unwrap() + .filter(|node| node.data().is_mapped(None)) + .map(|node| match node.data() { + Data::Mapped { + mapped, + last_geometry, + .. + } => ( + &output_data.output, + mapped, + output_data.location + last_geometry.loc, + ), + _ => unreachable!(), + }), + ) + } else { + None + } + }) + .flatten() + } + + pub fn windows(&self) -> impl Iterator)> + '_ { + self.mapped().flat_map(|(output, mapped, loc)| { + mapped + .windows() + .map(move |(w, p)| (output.clone(), w, p + loc)) + }) + } + + pub fn merge(&mut self, other: TilingLayout) { + for (output_data, src) in other.trees { + let mut dst = self.trees.entry(output_data.clone()).or_default(); + let orientation = match output_data.output.geometry().size { + x if x.w >= x.h => Orientation::Horizontal, + _ => Orientation::Vertical, + }; + TilingLayout::merge_trees(src, &mut dst, orientation); + } + self.refresh(); + } + + fn merge_trees(src: Tree, dst: &mut Tree, orientation: Orientation) { + if let Some(root_id) = src.root_node_id() { + let mut stack = Vec::new(); + + let root_node = src.get(root_id).unwrap(); + let new_node = Node::new(root_node.data().clone()); + let into_node_id = match dst.root_node_id().cloned() { + Some(root) => TilingLayout::new_group(dst, &root, new_node, orientation), + None => dst.insert(new_node, InsertBehavior::AsRoot), + } + .unwrap(); + + stack.push((root_id.clone(), into_node_id)); + while let Some((src_id, dst_id)) = stack.pop() { + for child_id in src.children_ids(&src_id).unwrap() { + let src_node = src.get(&child_id).unwrap(); + let new_node = Node::new(src_node.data().clone()); + let new_child_id = dst + .insert(new_node, InsertBehavior::UnderNode(&dst_id)) + .unwrap(); + stack.push((child_id.clone(), new_child_id)); + } + } + } else { + *dst = src; + } + } + + pub fn render_output( + &self, + output: &Output, + ) -> Result>, OutputNotMapped> + where + R: Renderer + ImportAll, + ::TextureId: 'static, + { + let output_scale = output.current_scale().fractional_scale(); + let int_scale = output.current_scale().integer_scale(); + + if !self.trees.contains_key(output) { + return Err(OutputNotMapped); + } + + Ok(self + .mapped() + .flat_map(|(o, mapped, loc)| { + if o == output { + Some((mapped, loc)) + } else { + None + } + }) + .flat_map(|(mapped, loc)| { + mapped.render_elements::>( + loc.to_physical(int_scale), + Scale::from(output_scale), + ) + }) + .collect::>()) } } + +render_elements! { + pub TilingRenderElement where R: ImportAll; + Window=CosmicMappedRenderElement, +} diff --git a/src/shell/mod.rs b/src/shell/mod.rs index b4f7671f..491147b7 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -1,5 +1,7 @@ -use std::{cell::Cell, mem::MaybeUninit}; +use serde::{Deserialize, Serialize}; +use std::{cell::Cell, collections::HashMap}; +use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::State as WState; use smithay::{ desktop::{layer_map_for_output, LayerSurface, PopupManager, Window, WindowSurfaceType}, input::{pointer::MotionEvent, Seat}, @@ -17,11 +19,8 @@ use smithay::{ }, }; -use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::State as WState; - use crate::{ config::{Config, WorkspaceMode as ConfigMode}, - //state::ClientState, utils::prelude::*, wayland::protocols::{ toplevel_info::ToplevelInfoState, @@ -33,19 +32,23 @@ use crate::{ }, }; -pub const MAX_WORKSPACES: usize = 10; +mod element; pub mod focus; -pub mod grabs; +//pub mod grabs; pub mod layout; mod workspace; pub use self::workspace::*; +use self::{ + element::{CosmicMapped, CosmicWindow}, + focus::target::KeyboardFocusTarget, + layout::{floating::FloatingLayout, tiling::TilingLayout}, +}; pub struct Shell { pub popups: PopupManager, - pub spaces: [Workspace; MAX_WORKSPACES], pub outputs: Vec, - pub workspace_mode: WorkspaceMode, - pub shell_mode: ShellMode, + pub workspaces: WorkspaceMode, + pub workspace_amount: WorkspaceAmount, pub floating_default: bool, pub pending_windows: Vec<(Window, Seat)>, pub pending_layers: Vec<(LayerSurface, Output, Seat)>, @@ -58,28 +61,245 @@ pub struct Shell { pub workspace_state: WorkspaceState, } -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Debug)] +pub struct WorkspaceSet { + active: usize, + group: WorkspaceGroupHandle, + workspaces: Vec, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum WorkspaceAmount { + Dynamic, + Static(u8), +} + +fn create_workspace( + state: &mut WorkspaceUpdateGuard<'_, State>, + group_handle: &WorkspaceGroupHandle, + active: bool, +) -> Workspace { + let workspace_handle = state.create_workspace(&group_handle).unwrap(); + if active { + state.add_workspace_state(&workspace_handle, WState::Active); + } + init_workspace_handle(state, 0, &workspace_handle); + Workspace::new(workspace_handle) +} + +impl WorkspaceSet { + fn new(state: &mut WorkspaceUpdateGuard<'_, State>, amount: WorkspaceAmount) -> WorkspaceSet { + let group_handle = state.create_workspace_group(); + + let workspaces = match amount { + WorkspaceAmount::Dynamic => { + vec![create_workspace(state, &group_handle, true)] + } + WorkspaceAmount::Static(len) => (0..len) + .map(|i| create_workspace(state, &group_handle, i == 0)) + .collect(), + }; + + WorkspaceSet { + active: 0, + group: group_handle, + workspaces, + } + } + + fn activate(&mut self, idx: usize, state: &mut WorkspaceUpdateGuard<'_, State>) { + if idx < self.workspaces.len() && self.active != idx { + let old_active = self.active; + state.remove_workspace_state(&self.workspaces[old_active].handle, WState::Active); + state.add_workspace_state(&self.workspaces[idx].handle, WState::Active); + self.active = idx; + } + } + + fn refresh( + &mut self, + amount: WorkspaceAmount, + state: &mut WorkspaceState, + toplevel_info: &mut ToplevelInfoState, + ) { + match amount { + WorkspaceAmount::Dynamic => self.ensure_last_empty(state), + WorkspaceAmount::Static(len) => self.ensure_static(len as usize, state, toplevel_info), + } + self.workspaces[self.active].refresh(); + } + + fn ensure_last_empty(&mut self, state: &mut WorkspaceState) { + // add empty at the end, if necessary + if self.workspaces.last().unwrap().windows().next().is_some() { + self.workspaces + .push(create_workspace(&mut state.update(), &self.group, false)); + } + + let len = self.workspaces.len(); + let mut keep = vec![true; len]; + + // remove empty workspaces in between, if they are not active + for (i, workspace) in self.workspaces.iter().enumerate() { + let has_windows = workspace.windows().next().is_some(); + + if !has_windows && i != self.active && i != len - 1 { + state.update().remove_workspace(workspace.handle); + keep[i] = false; + } + } + + let mut iter = keep.iter(); + self.workspaces.retain(|_| *iter.next().unwrap()); + } + + fn ensure_static( + &mut self, + amount: usize, + state: &mut WorkspaceState, + toplevel_info: &mut ToplevelInfoState, + ) { + if amount < self.workspaces.len() { + let mut state = state.update(); + // merge last ones + let overflow = self.workspaces.split_off(amount); + if self.active >= self.workspaces.len() { + self.active = self.workspaces.len() - 1; + state.add_workspace_state(&self.workspaces[self.active].handle, WState::Active); + } + let last_space = self.workspaces.last_mut().unwrap(); + + for workspace in overflow { + for element in workspace.mapped() { + // fixup toplevel state + for (toplevel, _) in element.windows() { + toplevel_info.toplevel_leave_workspace(&toplevel, &workspace.handle); + toplevel_info.toplevel_enter_workspace(&toplevel, &last_space.handle); + } + } + last_space.tiling_layer.merge(workspace.tiling_layer); + last_space.floating_layer.merge(workspace.floating_layer); + last_space + .fullscreen + .extend(workspace.fullscreen.into_iter()); + state.remove_workspace(workspace.handle); + } + + last_space.refresh(); + } else if amount > self.workspaces.len() { + let mut state = state.update(); + // add empty ones + while amount > self.workspaces.len() { + self.workspaces + .push(create_workspace(&mut state, &self.group, false)); + } + } + } +} + +#[derive(Debug)] pub enum WorkspaceMode { - OutputBound, - Global { - active: usize, - group: WorkspaceGroupHandle, - }, + OutputBound(HashMap), + Global(WorkspaceSet), } -pub enum ShellMode { - Normal, - Resize, - Adjust, -} +impl WorkspaceMode { + pub fn new( + config: crate::config::WorkspaceMode, + amount: WorkspaceAmount, + state: &mut WorkspaceUpdateGuard<'_, State>, + ) -> WorkspaceMode { + match config { + crate::config::WorkspaceMode::Global => { + WorkspaceMode::Global(WorkspaceSet::new(state, amount)) + } + crate::config::WorkspaceMode::OutputBound => WorkspaceMode::OutputBound(HashMap::new()), + } + } -#[derive(Debug, Clone)] -pub struct OutputBoundState { - pub active: Cell, - group: Cell, -} + pub fn get(&self, num: usize, output: &Output) -> Option<&Workspace> { + match self { + WorkspaceMode::Global(set) => set.workspaces.get(num), + WorkspaceMode::OutputBound(sets) => { + sets.get(output).and_then(|set| set.workspaces.get(num)) + } + } + } -const UNINIT_SPACE: MaybeUninit = MaybeUninit::uninit(); + pub fn get_mut(&mut self, num: usize, output: &Output) -> Option<&mut Workspace> { + match self { + WorkspaceMode::Global(set) => set.workspaces.get_mut(num), + WorkspaceMode::OutputBound(sets) => sets + .get_mut(output) + .and_then(|set| set.workspaces.get_mut(num)), + } + } + + pub fn active(&self, output: &Output) -> &Workspace { + match self { + WorkspaceMode::Global(set) => &set.workspaces[set.active], + WorkspaceMode::OutputBound(sets) => { + let set = sets.get(output).unwrap(); + &set.workspaces[set.active] + } + } + } + + pub fn active_mut(&mut self, output: &Output) -> &mut Workspace { + match self { + WorkspaceMode::Global(set) => &mut set.workspaces[set.active], + WorkspaceMode::OutputBound(sets) => { + let set = sets.get_mut(output).unwrap(); + &mut set.workspaces[set.active] + } + } + } + + pub fn active_num(&self, output: &Output) -> usize { + match self { + WorkspaceMode::Global(set) => set.active, + WorkspaceMode::OutputBound(sets) => { + let set = sets.get(output).unwrap(); + set.active + } + } + } + + pub fn spaces(&self) -> impl Iterator { + match self { + WorkspaceMode::Global(set) => { + Box::new(set.workspaces.iter()) as Box> + } + WorkspaceMode::OutputBound(sets) => { + Box::new(sets.values().flat_map(|set| set.workspaces.iter())) + } + } + } + + pub fn spaces_for_output(&self, output: &Output) -> impl Iterator { + match self { + WorkspaceMode::Global(set) => { + Box::new(set.workspaces.iter()) as Box> + } + WorkspaceMode::OutputBound(sets) => Box::new( + sets.get(output) + .into_iter() + .flat_map(|set| set.workspaces.iter()), + ), + } + } + + pub fn spaces_mut(&mut self) -> impl Iterator { + match self { + WorkspaceMode::Global(set) => { + Box::new(set.workspaces.iter_mut()) as Box> + } + WorkspaceMode::OutputBound(sets) => { + Box::new(sets.values_mut().flat_map(|set| set.workspaces.iter_mut())) + } + } + } +} impl Shell { pub fn new(config: &Config, dh: &DisplayHandle) -> Self { @@ -106,32 +326,20 @@ impl Shell { |_| true, ); - let mut spaces = unsafe { - let mut spaces = [UNINIT_SPACE; MAX_WORKSPACES]; - for (idx, space) in spaces.iter_mut().enumerate() { - *space = MaybeUninit::new(Workspace::new( - idx as u8, - std::mem::zeroed(), /* Will be initialized by init_mode */ - )); - } - std::mem::transmute(spaces) - }; - let mode = init_mode( - &config.static_conf.workspace_mode, - None, - &[], - &mut workspace_state, - &mut spaces, + let amount = config.static_conf.workspace_amount; + let mode = WorkspaceMode::new( + config.static_conf.workspace_mode, + config.static_conf.workspace_amount, + &mut workspace_state.update(), ); let floating_default = config.static_conf.floating_default; Shell { popups: PopupManager::new(None), - spaces, outputs: Vec::new(), - workspace_mode: mode, + workspaces: mode, + workspace_amount: amount, floating_default, - shell_mode: ShellMode::Normal, pending_windows: Vec::new(), pending_layers: Vec::new(), @@ -145,65 +353,28 @@ impl Shell { } pub fn add_output(&mut self, output: &Output) { - let was_empty = self.outputs.is_empty(); self.outputs.push(output.clone()); let mut state = self.workspace_state.update(); - match self.workspace_mode { - WorkspaceMode::OutputBound => { - let idx = self - .spaces - .iter() - .position(|x| x.space.outputs().next().is_none()) - .expect("More then 10 outputs?"); - - remap_output( - output, - &mut self.spaces, - None, - idx, - Point::from((0, 0)), - &mut self.toplevel_info_state, - ); - let mut workspace = &mut self.spaces[idx]; - - let group = state.create_workspace_group(); - state.add_group_output(&group, output); - state.remove_workspace(workspace.handle); - let handle = init_workspace_handle(&mut state, &group, &mut workspace); - state.add_workspace_state(&handle, WState::Active); - - let output_state = OutputBoundState { - active: Cell::new(workspace.idx as usize), - group: Cell::new(group), - }; - - if was_empty { - for workspace in self.spaces.iter_mut().skip(1) { - init_workspace_handle(&mut state, &group, workspace); - } + match &mut self.workspaces { + WorkspaceMode::OutputBound(sets) => { + // TODO: Restore previously assigned workspaces, if possible! + if !sets.contains_key(output) { + sets.insert( + output.clone(), + WorkspaceSet::new(&mut state, self.workspace_amount), + ); } - - if !output - .user_data() - .insert_if_missing(|| output_state.clone()) - { - let existing_state = output.user_data().get::().unwrap(); - existing_state.active.set(output_state.active.get()); - existing_state.group.set(output_state.group.get()); + for workspace in &mut sets.get_mut(output).unwrap().workspaces { + workspace.map_output(output, (0, 0).into()); } } - WorkspaceMode::Global { active, group } => { - state.add_group_output(&group, output); - - remap_output( - output, - &mut self.spaces, - None, - active, - output.current_location(), - &mut self.toplevel_info_state, - ); + WorkspaceMode::Global(set) => { + // TODO: Restore any window positions from previous outputs ??? + state.add_group_output(&set.group, output); + for workspace in &mut set.workspaces { + workspace.map_output(output, output.current_location()); + } } } } @@ -212,221 +383,267 @@ impl Shell { let mut state = self.workspace_state.update(); self.outputs.retain(|o| o != output); - match self.workspace_mode { - WorkspaceMode::OutputBound => { - let output_state = output.user_data().get::().unwrap(); - remap_output( - output, - &mut self.spaces, - output_state.active.get(), - None, - None, - &mut self.toplevel_info_state, - ); + match &mut self.workspaces { + WorkspaceMode::OutputBound(sets) => { + if let Some(set) = sets.remove(output) { + // TODO: Heuristic which output to move to. + // It is supposed to be the *most* internal, we just pick the first one for now + // and hope enumeration order works in our favor. + if let Some(new_output) = self.outputs.get(0) { + let new_set = sets.get_mut(new_output).unwrap(); + let workspace_group = new_set.group; + for mut workspace in set.workspaces { + // update workspace protocol state + state.remove_workspace(workspace.handle); + let workspace_handle = + state.create_workspace(&workspace_group).unwrap(); + init_workspace_handle( + &mut state, + new_set.workspaces.len() as u8, + &workspace_handle, + ); + workspace.handle = workspace_handle; - // reassign workspaces to a different output - let new_group = self - .outputs - .iter() - .next() - .map(|o| o.user_data().get::().unwrap().group.get()); - for mut workspace in self.spaces.iter_mut() { - if state - .workspace_belongs_to_group(&output_state.group.get(), &workspace.handle) - { - state.remove_workspace(workspace.handle); - if let Some(new_group) = new_group { - init_workspace_handle(&mut state, &new_group, &mut workspace); + // update mapping + workspace.map_output(new_output, (0, 0).into()); + workspace.unmap_output(output); + workspace.refresh(); + + new_set.workspaces.push(workspace); } + state.remove_workspace_group(set.group); + std::mem::drop(state); + self.refresh(); // cleans up excess of workspaces and empty workspaces } + // if there is no output, we are going to quit anyway, just drop the workspace set } - - // destroy workspace group - state.remove_workspace_group(output_state.group.get()); } - WorkspaceMode::Global { active, group } => { - state.remove_group_output(&group, output); - - remap_output( - output, - &mut self.spaces, - active, - None, - None, - &mut self.toplevel_info_state, - ); + WorkspaceMode::Global(set) => { + state.remove_group_output(&set.group, output); + for workspace in &mut set.workspaces { + workspace.unmap_output(output); + workspace.refresh(); + } } }; } pub fn refresh_outputs(&mut self) { - if let WorkspaceMode::Global { active, .. } = self.workspace_mode { - let workspace = &mut self.spaces[active]; - for output in self.outputs.iter() { - workspace - .space - .map_output(output, output.current_location()); - } - } else { - for output in self.outputs.iter() { - let active = output - .user_data() - .get::() - .unwrap() - .active - .get(); - let workspace = &mut self.spaces[active]; - workspace.space.map_output(output, (0, 0)); + if let WorkspaceMode::Global(set) = &mut self.workspaces { + for workspace in &mut set.workspaces { + for output in self.outputs.iter() { + workspace.map_output(output, output.current_location()); + } } } } pub fn set_mode(&mut self, mode: ConfigMode) { - match (&mut self.workspace_mode, mode) { - (WorkspaceMode::OutputBound, ConfigMode::Global) => { - let new_active = 0; - init_mode( - &mode, - Some(&WorkspaceMode::OutputBound), - &self.outputs, - &mut self.workspace_state, - &mut self.spaces, - ); + let mut state = self.workspace_state.update(); + + match (&mut self.workspaces, mode) { + (dst @ WorkspaceMode::OutputBound(_), ConfigMode::Global) => { + // rustc should really be able to infer that this doesn't need an if. + let sets = if let &mut WorkspaceMode::OutputBound(ref mut sets) = dst { + sets + } else { + unreachable!() + }; + + // in this case we have to merge our sets, preserving placing of windows as nicely as possible + let mut new_set = WorkspaceSet::new(&mut state, WorkspaceAmount::Static(0)); + + // lets construct an iterator of all the pairs of workspaces we have to merge + // we first split of the part of the workspaces that contain the currently active one + let mut second_half = sets + .iter_mut() + .map(|(output, set)| (output.clone(), set.workspaces.split_off(set.active))) + .collect::>(); + + let mut first_half = std::iter::repeat(()) + // we continuously pop the last elements from the first half and group them together. + .map(|_| { + sets.iter_mut() + .flat_map(|(o, w)| w.workspaces.pop().map(|w| (o.clone(), w))) + .collect::>() + }) + // we stop once there is no workspace anymore in the entire set + .filter(|vec| !vec.is_empty()) + .fuse() + .collect::>(); + // we reverse those then to get the proper order + first_half.reverse(); + + let mergers = first_half + .into_iter() + // we need to know, which is supposed to be active and we loose that info by chaining, so lets add a bool + .map(|w| (w, false)) + .chain( + (0..) + // here we continuously remove the first element + .map(|i| { + ( + second_half + .iter_mut() + .flat_map(|&mut (ref o, ref mut w)| { + if !w.is_empty() { + Some((o.clone(), w.remove(0))) + } else { + None + } + }) + .collect::>(), + i == 0, + ) + }) + .filter(|(vec, _)| !vec.is_empty()) + .fuse(), + ); + + for (i, (workspaces, active)) in mergers.into_iter().enumerate() { + // and then we can merge each vector into one and put that into our new set. + let workspace_handle = state.create_workspace(&new_set.group).unwrap(); + init_workspace_handle(&mut state, i as u8, &workspace_handle); + + let mut new_workspace = Workspace::new(workspace_handle); + for output in self.outputs.iter() { + new_workspace.map_output(output, output.current_location()); + } + new_workspace.tiling_enabled = workspaces.iter().any(|(_, w)| w.tiling_enabled); + + for (_output, workspace) in workspaces.into_iter() { + for toplevel in workspace.windows() { + self.toplevel_info_state + .toplevel_leave_workspace(&toplevel, &workspace.handle); + self.toplevel_info_state + .toplevel_enter_workspace(&toplevel, &new_workspace.handle); + } + new_workspace.tiling_layer.merge(workspace.tiling_layer); + new_workspace.floating_layer.merge(workspace.floating_layer); + new_workspace + .fullscreen + .extend(workspace.fullscreen.into_iter()); + state.remove_workspace(workspace.handle); + } + + if active { + new_set.active = new_set.workspaces.len(); + } + new_set.workspaces.push(new_workspace); + } + + for group in sets.values().map(|set| set.group) { + state.remove_workspace_group(group); + } + + *dst = WorkspaceMode::Global(new_set); + } + (dst @ WorkspaceMode::Global(_), ConfigMode::OutputBound) => { + // rustc should really be able to infer that this doesn't need an if. + let set = if let &mut WorkspaceMode::Global(ref mut set) = dst { + set + } else { + unreachable!() + }; + + // split workspaces apart, preserving window positions relative to their outputs + let mut sets = HashMap::new(); for output in &self.outputs { - let old_active = output - .user_data() - .get::() - .unwrap() - .active - .get(); - remap_output( - output, - &mut self.spaces, - old_active, - new_active, - output.current_location(), - &mut self.toplevel_info_state, + sets.insert( + output.clone(), + WorkspaceSet::new(&mut state, WorkspaceAmount::Static(0)), ); } - } - (x @ WorkspaceMode::Global { .. }, ConfigMode::OutputBound) => { - // inits OutputBoundState if it not exists - init_mode( - &mode, - Some(x), - &self.outputs, - &mut self.workspace_state, - &mut self.spaces, - ); - if let WorkspaceMode::Global { ref active, .. } = x { + for (i, workspace) in set.workspaces.drain(..).enumerate() { for output in &self.outputs { - let new_active = output - .user_data() - .get::() - .unwrap() - .active - .get(); - remap_output( - output, - &mut self.spaces, - *active, - new_active, - Point::from((0, 0)), - &mut self.toplevel_info_state, - ); + // copy over everything and then remove other outputs to preserve state + let new_set = sets.get_mut(output).unwrap(); + let new_workspace_handle = state.create_workspace(&new_set.group).unwrap(); + init_workspace_handle(&mut state, i as u8, &new_workspace_handle); + + let mut old_tiling_layer = workspace.tiling_layer.clone(); + let mut new_floating_layer = FloatingLayout::new(); + let mut new_tiling_layer = TilingLayout::new(); + + for element in workspace.mapped() { + for (toplevel, _) in element.windows() { + self.toplevel_info_state + .toplevel_leave_workspace(&toplevel, &workspace.handle); + } + + if workspace + .floating_layer + .most_overlapped_output_for_element(element) + .as_ref() + == Some(output) + { + if let Some(mut old_mapped_loc) = + workspace.floating_layer.space.element_location(element) + { + let old_output_geo = workspace + .floating_layer + .space + .output_geometry(output) + .unwrap(); + old_mapped_loc -= old_output_geo.loc; + new_floating_layer.map_internal( + element.clone(), + output, + Some(old_mapped_loc), + ); + } + } else { + old_tiling_layer.unmap(element); + } + } + + new_floating_layer.map_output(output, (0, 0).into()); + new_tiling_layer.map_output(output, (0, 0).into()); + new_tiling_layer.merge(old_tiling_layer); + + let mut new_workspace = Workspace { + tiling_layer: new_tiling_layer, + floating_layer: new_floating_layer, + tiling_enabled: workspace.tiling_enabled, + fullscreen: workspace + .fullscreen + .iter() + .filter(|(key, _)| *key == output) + .map(|(o, w)| (o.clone(), w.clone())) + .collect(), + ..Workspace::new(new_workspace_handle) + }; + for toplevel in new_workspace.windows() { + self.toplevel_info_state + .toplevel_enter_workspace(&toplevel, &new_workspace_handle); + } + new_workspace.refresh(); + + new_set.workspaces.push(new_workspace); + new_set.active = set.active; } + state.remove_workspace(workspace.handle); } + state.remove_workspace_group(set.group); + + *dst = WorkspaceMode::OutputBound(sets); } _ => {} } + + std::mem::drop(state); + self.refresh(); // get rid of empty workspaces and enforce potential maximum } - pub fn activate( - &mut self, - seat: &Seat, - output: &Output, - idx: usize, - ) -> Option { - if idx > MAX_WORKSPACES { - return None; - } - - match self.workspace_mode { - WorkspaceMode::OutputBound => { - // if the workspace is active on a different output, move the cursor over - for output in self.outputs.iter().filter(|o| o != &output) { - if output - .user_data() - .get::() - .unwrap() - .active - .get() - == idx - { - let geometry = output.geometry(); - set_active_output(seat, output); - return Some(MotionEvent { - location: Point::::from(( - geometry.loc.x + (geometry.size.w / 2), - geometry.loc.y + (geometry.size.h / 2), - )) - .to_f64(), - serial: SERIAL_COUNTER.next_serial(), - time: 0, - }); - } - } - - // else we exchange the workspace on the current output - let output_state = output.user_data().get::().unwrap(); - let old_active = output_state.active.get(); - if idx != old_active { - let mut state = self.workspace_state.update(); - output_state.active.set(idx); - - if !state.workspace_belongs_to_group( - &output_state.group.get(), - &self.spaces[idx].handle, - ) { - state.remove_workspace(self.spaces[idx].handle); - init_workspace_handle( - &mut state, - &output_state.group.get(), - &mut self.spaces[idx], - ); - } - - state.remove_workspace_state(&self.spaces[old_active].handle, WState::Active); - state.add_workspace_state(&self.spaces[idx].handle, WState::Active); - - std::mem::drop(state); - remap_output( - output, - &mut self.spaces, - old_active, - idx, - Point::from((0, 0)), - &mut self.toplevel_info_state, - ); + pub fn activate(&mut self, output: &Output, idx: usize) -> Option { + match &mut self.workspaces { + WorkspaceMode::OutputBound(sets) => { + if let Some(set) = sets.get_mut(output) { + set.activate(idx, &mut self.workspace_state.update()); } } - WorkspaceMode::Global { ref mut active, .. } => { - let old = *active; - *active = idx; - - let mut state = self.workspace_state.update(); - for output in &self.outputs { - remap_output( - output, - &mut self.spaces, - old, - idx, - output.current_location(), - &mut self.toplevel_info_state, - ); - } - state.remove_workspace_state(&self.spaces[old].handle, WState::Active); - state.add_workspace_state(&self.spaces[idx].handle, WState::Active); + WorkspaceMode::Global(set) => { + set.activate(idx, &mut self.workspace_state.update()); } } @@ -434,32 +651,22 @@ impl Shell { } pub fn active_space(&self, output: &Output) -> &Workspace { - match &self.workspace_mode { - WorkspaceMode::OutputBound => { - let active = output - .user_data() - .get::() - .unwrap() - .active - .get(); - &self.spaces[active] + match &self.workspaces { + WorkspaceMode::OutputBound(sets) => { + let set = sets.get(output).unwrap(); + &set.workspaces[set.active] } - WorkspaceMode::Global { active, .. } => &self.spaces[*active], + WorkspaceMode::Global(set) => &set.workspaces[set.active], } } pub fn active_space_mut(&mut self, output: &Output) -> &mut Workspace { - match &self.workspace_mode { - WorkspaceMode::OutputBound => { - let active = output - .user_data() - .get::() - .unwrap() - .active - .get(); - &mut self.spaces[active] + match &mut self.workspaces { + WorkspaceMode::OutputBound(sets) => { + let set = sets.get_mut(output).unwrap(); + &mut set.workspaces[set.active] } - WorkspaceMode::Global { active, .. } => &mut self.spaces[*active], + WorkspaceMode::Global(set) => &mut set.workspaces[set.active], } } @@ -475,39 +682,31 @@ impl Shell { Some(output) => { Box::new(std::iter::once(output.clone())) as Box> } - None => Box::new( - self.spaces - .iter() - .filter_map(|w| { - if let Some(window) = - w.space.window_for_surface(surface, WindowSurfaceType::ALL) - { - Some(w.space.outputs_for_window(&window).into_iter()) - } else { - None - } - }) - .flatten(), - ), + None => Box::new(self.workspaces.spaces().flat_map(|w| { + w.mapped() + .find(|e| e.has_surface(surface, WindowSurfaceType::ALL)) + .into_iter() + .flat_map(|e| w.outputs_for_element(e)) + })), } } - pub fn space_for_window(&self, surface: &WlSurface) -> Option<&Workspace> { - self.spaces.iter().find(|workspace| { - workspace - .space - .window_for_surface(surface, WindowSurfaceType::ALL) - .is_some() - }) + pub fn element_for_surface(&self, surface: &WlSurface) -> Option<&CosmicMapped> { + self.workspaces + .spaces() + .find_map(|w| w.element_for_surface(surface)) } - pub fn space_for_window_mut(&mut self, surface: &WlSurface) -> Option<&mut Workspace> { - self.spaces.iter_mut().find(|workspace| { - workspace - .space - .window_for_surface(surface, WindowSurfaceType::ALL) - .is_some() - }) + pub fn space_for(&self, mapped: &CosmicMapped) -> Option<&Workspace> { + self.workspaces + .spaces() + .find(|workspace| workspace.mapped().any(|m| m == mapped)) + } + + pub fn space_for_mut(&mut self, mapped: &CosmicMapped) -> Option<&mut Workspace> { + self.workspaces + .spaces_mut() + .find(|workspace| workspace.mapped().any(|m| m == mapped)) } pub fn outputs(&self) -> impl Iterator { @@ -527,66 +726,59 @@ impl Shell { .unwrap_or_else(|| Rectangle::from_loc_and_size((0, 0), (0, 0))) } - pub fn space_relative_output_geometry( + pub fn map_global_to_space( &self, global_loc: impl Into>, output: &Output, ) -> Point { - match self.workspace_mode { - WorkspaceMode::Global { .. } => global_loc.into(), - WorkspaceMode::OutputBound => { + match self.workspaces { + WorkspaceMode::Global(_) => global_loc.into(), + WorkspaceMode::OutputBound(_) => { let p = global_loc.into().to_f64() - output.current_location().to_f64(); (C::from_f64(p.x), C::from_f64(p.y)).into() } } } - pub fn refresh(&mut self, dh: &DisplayHandle) { + pub fn map_space_to_global( + &self, + space_loc: impl Into>, + output: &Output, + ) -> Point { + match self.workspaces { + WorkspaceMode::Global(_) => space_loc.into(), + WorkspaceMode::OutputBound(_) => { + let p = space_loc.into().to_f64() + output.current_location().to_f64(); + (C::from_f64(p.x), C::from_f64(p.y)).into() + } + } + } + + pub fn refresh(&mut self) { self.popups.cleanup(); - match &self.workspace_mode { - WorkspaceMode::OutputBound => { - for output in &self.outputs { - let active = output - .user_data() - .get::() - .unwrap() - .active - .get(); - let workspace = &mut self.spaces[active]; - workspace.refresh(dh); - if workspace.space.windows().next().is_none() - && !self - .workspace_state - .workspace_states(&workspace.handle) - .map(|mut i| i.any(|s| s == &WState::Hidden)) - .unwrap_or(true) - { - self.workspace_state - .update() - .add_workspace_state(&workspace.handle, WState::Hidden); - } + + match &mut self.workspaces { + WorkspaceMode::OutputBound(sets) => { + for set in sets.values_mut() { + set.refresh( + self.workspace_amount, + &mut self.workspace_state, + &mut self.toplevel_info_state, + ); } } - WorkspaceMode::Global { active, .. } => { - let workspace = &mut self.spaces[*active]; - workspace.refresh(dh); - if workspace.space.windows().next().is_none() - && !self - .workspace_state - .workspace_states(&workspace.handle) - .map(|mut i| i.any(|s| s == &WState::Hidden)) - .unwrap_or(true) - { - self.workspace_state - .update() - .add_workspace_state(&workspace.handle, WState::Hidden); - } - } - }; + WorkspaceMode::Global(set) => set.refresh( + self.workspace_amount, + &mut self.workspace_state, + &mut self.toplevel_info_state, + ), + } + for output in &self.outputs { let mut map = layer_map_for_output(output); - map.cleanup(dh); + map.cleanup(); } + self.toplevel_info_state .refresh(Some(&self.workspace_state)); } @@ -600,54 +792,34 @@ impl Shell { .position(|(w, _)| w == window) .unwrap(); let (window, seat) = state.common.shell.pending_windows.remove(pos); - let surface = window.toplevel().wl_surface().clone(); - let workspace = match &state.common.shell.workspace_mode { - WorkspaceMode::OutputBound => { - let active = output - .user_data() - .get::() - .unwrap() - .active - .get(); - &mut state.common.shell.spaces[active] - } - WorkspaceMode::Global { active, .. } => &mut state.common.shell.spaces[*active], - }; - state - .common - .shell - .workspace_state - .update() - .remove_workspace_state(&workspace.handle, WState::Hidden); - state - .common - .shell - .toplevel_info_state - .toplevel_enter_workspace(&window, &workspace.handle); + let workspace = state.common.shell.workspaces.active_mut(output); state .common .shell .toplevel_info_state .toplevel_enter_output(&window, &output); + state + .common + .shell + .toplevel_info_state + .toplevel_enter_workspace(&window, &workspace.handle); + + let mapped = CosmicMapped::from(CosmicWindow::from(window.clone())); if layout::should_be_floating(&window) || state.common.shell.floating_default { - workspace - .floating_layer - .map_window(&mut workspace.space, window, &seat, None); + workspace.floating_layer.map(mapped.clone(), &seat, None); } else { - let focus_stack = workspace.focus_stack(&seat); - workspace.tiling_layer.map_window( - &mut workspace.space, - window, - &seat, - focus_stack.iter(), - ); + let focus_stack = workspace.focus_stack.get(&seat); + workspace + .tiling_layer + .map(mapped.clone(), &seat, focus_stack.iter()); } - Shell::set_focus(state, Some(&surface), &seat, None); + Shell::set_focus(state, Some(&KeyboardFocusTarget::from(mapped)), &seat, None); - for window in state.common.shell.active_space(output).space.windows() { - state.common.shell.update_reactive_popups(window); + let active_space = state.common.shell.active_space(output); + for mapped in active_space.mapped() { + state.common.shell.update_reactive_popups(mapped); } } @@ -661,9 +833,8 @@ impl Shell { .unwrap(); let (layer_surface, output, seat) = state.common.shell.pending_layers.remove(pos); - let surface = layer_surface.wl_surface(); let wants_focus = { - with_states(surface, |states| { + with_states(layer_surface.wl_surface(), |states| { let state = states.cached_state.current::(); matches!(state.layer, Layer::Top | Layer::Overlay) && state.keyboard_interactivity != KeyboardInteractivity::None @@ -671,194 +842,78 @@ impl Shell { }; let mut map = layer_map_for_output(&output); - map.map_layer(&state.common.display_handle, &layer_surface) - .unwrap(); + map.map_layer(&layer_surface).unwrap(); if wants_focus { - Shell::set_focus(state, Some(surface), &seat, None) + Shell::set_focus(state, Some(&layer_surface.into()), &seat, None) } } pub fn move_current_window(&mut self, seat: &Seat, output: &Output, idx: usize) { - if idx > MAX_WORKSPACES { + if idx == self.workspaces.active_num(output) { return; } - let workspace = match &self.workspace_mode { - WorkspaceMode::OutputBound => { - let active = output - .user_data() - .get::() - .unwrap() - .active - .get(); - &mut self.spaces[active] - } - WorkspaceMode::Global { active, .. } => &mut self.spaces[*active], - }; - if idx == workspace.idx as usize { - return; - } + let old_workspace = self.workspaces.active_mut(output); + let maybe_window = old_workspace.focus_stack.get(seat).last().cloned(); + if let Some(mapped) = maybe_window { + let was_floating = old_workspace.floating_layer.unmap(&mapped); + let was_tiling = old_workspace.tiling_layer.unmap(&mapped); + assert!(was_floating != was_tiling); - let maybe_window = workspace.focus_stack(seat).last(); - if let Some(window) = maybe_window { - let mut workspace_state = self.workspace_state.update(); - workspace - .floating_layer - .unmap_window(&mut workspace.space, &window); - workspace - .tiling_layer - .unmap_window(&mut workspace.space, &window); - self.toplevel_info_state - .toplevel_leave_workspace(&window, &workspace.handle); - if workspace.space.windows().next().is_none() { - workspace_state.add_workspace_state(&workspace.handle, WState::Hidden); + for (toplevel, _) in mapped.windows() { + self.toplevel_info_state + .toplevel_leave_workspace(&toplevel, &old_workspace.handle); + } + let elements = old_workspace.mapped().cloned().collect::>(); + std::mem::drop(old_workspace); + for mapped in elements.into_iter() { + self.update_reactive_popups(&mapped); } - let new_workspace = &mut self.spaces[idx]; - workspace_state.remove_workspace_state(&new_workspace.handle, WState::Hidden); - self.toplevel_info_state - .toplevel_enter_workspace(&window, &new_workspace.handle); - let focus_stack = new_workspace.focus_stack(&seat); - if layout::should_be_floating(&window) { - new_workspace.floating_layer.map_window( - &mut new_workspace.space, - window, - &seat, - None, - ); + let new_workspace = self.workspaces.get_mut(idx, output).unwrap(); // checked above + let focus_stack = new_workspace.focus_stack.get(&seat); + if was_floating { + new_workspace + .floating_layer + .map(mapped.clone(), &seat, None); } else { - new_workspace.tiling_layer.map_window( - &mut new_workspace.space, - window, - &seat, - focus_stack.iter(), + new_workspace + .tiling_layer + .map(mapped.clone(), &seat, focus_stack.iter()); + } + for (toplevel, _) in mapped.windows() { + self.toplevel_info_state + .toplevel_enter_workspace(&toplevel, &new_workspace.handle); + } + + let mut workspace_state = self.workspace_state.update(); + workspace_state.remove_workspace_state(&new_workspace.handle, WState::Hidden); + } + } + + pub fn update_reactive_popups(&self, mapped: &CosmicMapped) { + if let Some(workspace) = self.space_for(mapped) { + let element_loc = workspace.element_geometry(mapped).unwrap().loc; + for (toplevel, offset) in mapped.windows() { + let window_geo_offset = toplevel.geometry().loc; + update_reactive_popups( + &toplevel, + element_loc + offset + window_geo_offset, + self.outputs.iter(), ); } } - - for window in self.active_space(output).space.windows() { - self.update_reactive_popups(window); - } - for window in self.spaces[idx].space.windows() { - self.update_reactive_popups(window); - } - } -} - -fn init_mode( - config_mode: &ConfigMode, - old_mode: Option<&WorkspaceMode>, - outputs: &[Output], - state: &mut WorkspaceState, - workspaces: &mut [Workspace; MAX_WORKSPACES], -) -> WorkspaceMode { - let mut state = state.update(); - - // cleanup - for workspace in workspaces.iter_mut() { - state.remove_workspace(workspace.handle); - } - - match old_mode { - Some(WorkspaceMode::Global { group, .. }) => state.remove_workspace_group(group.clone()), - Some(WorkspaceMode::OutputBound) => { - for output in outputs { - if let Some(old_state) = output.user_data().get::() { - state.remove_workspace_group(old_state.group.get()); - } - } - } - _ => {} - }; - - // set the new state (especially cosmic_workspace state) - match config_mode { - ConfigMode::Global => { - let group = state.create_workspace_group(); - for output in outputs { - state.add_group_output(&group, output) - } - for workspace in workspaces.iter_mut() { - init_workspace_handle(&mut state, &group, workspace); - } - state.add_workspace_state(&workspaces[0].handle, WState::Active); - state.remove_workspace_state(&workspaces[0].handle, WState::Hidden); - WorkspaceMode::Global { active: 0, group } - } - ConfigMode::OutputBound => { - for (i, output) in outputs.iter().enumerate() { - let group = state.create_workspace_group(); - state.add_group_output(&group, output); - - let workspace = workspaces.get_mut(i).expect("More then ten workspaces?!?"); - let handle = init_workspace_handle(&mut state, &group, workspace); - state.add_workspace_state(&handle, WState::Active); - state.remove_workspace_state(&handle, WState::Hidden); - - let output_state = OutputBoundState { - active: Cell::new(i), - group: Cell::new(group), - }; - let map = output.user_data(); - if !map.insert_if_missing(|| output_state) { - let old_state = map.get::().unwrap(); - old_state.active.set(i); - old_state.group.set(group); - } - } - if !outputs.is_empty() { - for workspace in workspaces.iter_mut().skip(outputs.iter().count()) { - let group = outputs[0] - .user_data() - .get::() - .unwrap() - .group - .get(); - init_workspace_handle(&mut state, &group, workspace); - } - } - WorkspaceMode::OutputBound - } } } fn init_workspace_handle<'a>( state: &mut WorkspaceUpdateGuard<'a, State>, - group: &WorkspaceGroupHandle, - workspace: &mut Workspace, -) -> WorkspaceHandle { - let handle = state.create_workspace(&group).unwrap(); - state.set_workspace_capabilities(&handle, [WorkspaceCapabilities::Activate].into_iter()); - state.set_workspace_name(&handle, format!("{}", workspace.idx + 1)); - state.set_workspace_coordinates(&handle, [Some(workspace.idx as u32), None, None]); - if workspace.space.windows().next().is_none() { - state.add_workspace_state(&handle, WState::Hidden); - } - workspace.handle = handle.clone(); - handle -} - -fn remap_output( - output: &Output, - spaces: &mut [Workspace], - old: impl Into>, - new: impl Into>, - pos: impl Into>>, - info_state: &mut ToplevelInfoState, + idx: u8, + handle: &WorkspaceHandle, ) { - if let Some(old) = old.into() { - let old_space = &mut spaces[old].space; - old_space.unmap_output(output); - for window in old_space.windows() { - info_state.toplevel_leave_output(window, output); - } - } - if let Some(new) = new.into() { - let new_space = &mut spaces[new].space; - new_space.map_output(output, pos.into().expect("new requires pos")); - for window in new_space.windows() { - info_state.toplevel_enter_output(window, output); - } - } + state.set_workspace_capabilities(&handle, [WorkspaceCapabilities::Activate].into_iter()); + state.set_workspace_name(&handle, format!("{}", idx + 1)); + state.set_workspace_coordinates(&handle, [Some(idx as u32), None, None]); + state.add_workspace_state(&handle, WState::Hidden); } diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 0fbe52d7..00271300 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -1,79 +1,179 @@ use crate::{ - shell::layout::{floating::FloatingLayout, tiling::TilingLayout}, + shell::{ + element::CosmicWindow, + layout::{floating::FloatingLayout, tiling::TilingLayout}, + }, state::State, + utils::prelude::*, wayland::protocols::workspace::WorkspaceHandle, }; +use indexmap::IndexSet; use smithay::{ - desktop::{Kind, Space, Window, WindowSurfaceType}, + backend::renderer::{ + element::{surface::WaylandSurfaceRenderElement, AsRenderElements}, + ImportAll, Renderer, + }, + desktop::{ + layer_map_for_output, space::SpaceElement, Kind, LayerSurface, Space, Window, + WindowSurfaceType, + }, input::{pointer::GrabStartData as PointerGrabStartData, Seat}, - output::Output, + output::{Output, WeakOutput}, reexports::{ wayland_protocols::xdg::shell::server::xdg_toplevel::{self, ResizeEdge}, wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle}, }, - utils::{IsAlive, Serial}, + render_elements, + utils::{IsAlive, Logical, Point, Rectangle, Scale, Serial}, + wayland::shell::wlr_layer::Layer, }; -use std::collections::HashMap; +use std::{collections::HashMap, time::Duration}; +use super::{ + element::CosmicMapped, + focus::{FocusStack, FocusStackMut}, + layout::{floating::FloatingRenderElement, tiling::TilingRenderElement}, +}; + +#[derive(Debug)] pub struct Workspace { - pub idx: u8, - pub space: Space, pub tiling_layer: TilingLayout, pub floating_layer: FloatingLayout, - tiling_enabled: bool, - pub fullscreen: HashMap, + pub tiling_enabled: bool, + pub fullscreen: HashMap, pub handle: WorkspaceHandle, + pub focus_stack: FocusStacks, } +#[derive(Debug, Default)] +pub struct FocusStacks(HashMap, IndexSet>); + impl Workspace { - pub fn new(idx: u8, handle: WorkspaceHandle) -> Workspace { + pub fn new(handle: WorkspaceHandle) -> Workspace { Workspace { - idx, - space: Space::new(None), tiling_layer: TilingLayout::new(), floating_layer: FloatingLayout::new(), tiling_enabled: true, fullscreen: HashMap::new(), handle, + focus_stack: FocusStacks::default(), } } - pub fn refresh(&mut self, dh: &DisplayHandle) { - let outputs = self.space.outputs().collect::>(); - let dead_output_windows = self - .fullscreen - .iter() - .filter(|(name, _)| !outputs.iter().any(|o| o.name() == **name)) - .map(|(_, w)| w) - .cloned() - .collect::>(); - for window in dead_output_windows { - self.unfullscreen_request(&window); - } + pub fn refresh(&mut self) { self.fullscreen.retain(|_, w| w.alive()); - self.floating_layer.refresh(&mut self.space); - self.tiling_layer.refresh(&mut self.space); - self.space.refresh(dh); + self.floating_layer.refresh(); + self.tiling_layer.refresh(); } - pub fn maximize_request(&mut self, window: &Window, output: &Output) { + pub fn commit(&mut self, surface: &WlSurface) { + if let Some(mapped) = self.element_for_surface(surface) { + mapped + .windows() + .find(|(w, _)| w.toplevel().wl_surface() == surface) + .unwrap() + .0 + .on_commit(); + } + } + + pub fn map_output(&mut self, output: &Output, position: Point) { + self.tiling_layer.map_output(output, position); + self.floating_layer.map_output(output, position); + } + + pub fn unmap_output(&mut self, output: &Output) { + if let Some(dead_output_window) = self.fullscreen.remove(output) { + self.unfullscreen_request(&dead_output_window); + } + self.tiling_layer.unmap_output(output); + self.floating_layer.unmap_output(output); + self.refresh(); + } + + pub fn element_for_surface(&self, surface: &WlSurface) -> Option<&CosmicMapped> { + self.floating_layer + .mapped() + .chain(self.tiling_layer.mapped().map(|(_, w, _)| w)) + .find(|e| { + e.windows() + .any(|(w, _)| w.toplevel().wl_surface() == surface) + }) + } + + pub fn outputs_for_element(&self, elem: &CosmicMapped) -> impl Iterator { + self.floating_layer + .space + .outputs_for_element(elem) + .into_iter() + .chain(self.tiling_layer.output_for_element(elem).cloned()) + } + + pub fn output_under(&self, point: Point) -> Option<&Output> { + let space = &self.floating_layer.space; + space.outputs().find(|o| { + let internal_output_geo = space.output_geometry(o).unwrap(); + let external_output_geo = o.geometry(); + internal_output_geo.contains(point - external_output_geo.loc + internal_output_geo.loc) + }) + } + + pub fn element_under( + &self, + location: Point, + ) -> Option<(&CosmicMapped, Point)> { + self.floating_layer + .space + .element_under(location) + .or_else(|| { + self.tiling_layer.mapped().find_map(|(_, mapped, loc)| { + let test_point = location - loc.to_f64(); + mapped + .is_in_input_region(&test_point) + .then_some((mapped, loc)) + }) + }) + } + + pub fn element_geometry(&self, elem: &CosmicMapped) -> Option> { + let space = &self.floating_layer.space; + let outputs = space.outputs().collect::>(); + let offset = if outputs.len() == 1 + && space.output_geometry(&outputs[0]).unwrap().loc == Point::from((0, 0)) + { + outputs[0].geometry().loc + } else { + (0, 0).into() + }; + + self.floating_layer + .space + .element_geometry(elem) + .or_else(|| self.tiling_layer.element_geometry(elem)) + .map(|mut geo| { + geo.loc += offset; + geo + }) + } + + /* + pub fn maximize_request(&mut self, window: &CosmicWindow, output: &Output) { if self.fullscreen.values().any(|w| w == window) { return; } if self.floating_layer.windows.contains(window) { self.floating_layer - .maximize_request(&mut self.space, window, output); + .maximize_request(, window, output); } } - - pub fn unmaximize_request(&mut self, window: &Window) { + pub fn unmaximize_request(&mut self, window: &CosmicMapped) { if self.fullscreen.values().any(|w| w == window) { return self.unfullscreen_request(window); } if self.floating_layer.windows.contains(window) { self.floating_layer - .unmaximize_request(&mut self.space, window); + .unmaximize_request(window); } } @@ -101,9 +201,10 @@ impl Workspace { TilingLayout::resize_request(state, &window, seat, serial, start_data, edges) } } + */ pub fn fullscreen_request(&mut self, window: &Window, output: &Output) { - if self.fullscreen.contains_key(&output.name()) { + if self.fullscreen.contains_key(output) { return; } @@ -123,7 +224,7 @@ impl Workspace { }); xdg.send_configure(); - self.fullscreen.insert(output.name(), window.clone()); + self.fullscreen.insert(output.clone(), window.clone()); } } @@ -135,8 +236,8 @@ impl Workspace { state.states.unset(xdg_toplevel::State::Fullscreen); state.size = None; }); - self.floating_layer.refresh(&mut self.space); - self.tiling_layer.refresh(&mut self.space); + self.floating_layer.refresh(); + self.tiling_layer.refresh(); xdg.send_configure(); } self.fullscreen.retain(|_, w| w != window); @@ -144,7 +245,7 @@ impl Workspace { } pub fn fullscreen_toggle(&mut self, window: &Window, output: &Output) { - if self.fullscreen.contains_key(&output.name()) { + if self.fullscreen.contains_key(output) { self.unfullscreen_request(window) } else { self.fullscreen_request(window, output) @@ -152,26 +253,33 @@ impl Workspace { } pub fn get_fullscreen(&self, output: &Output) -> Option<&Window> { - if !self.space.outputs().any(|o| o == output) { - return None; - } - self.fullscreen.get(&output.name()).filter(|w| w.alive()) + self.fullscreen.get(output).filter(|w| w.alive()) } pub fn toggle_tiling(&mut self, seat: &Seat) { if self.tiling_enabled { - for window in self.tiling_layer.windows.clone().into_iter() { - self.tiling_layer.unmap_window(&mut self.space, &window); - self.floating_layer - .map_window(&mut self.space, window, seat, None); + for window in self + .tiling_layer + .mapped() + .map(|(_, m, _)| m.clone()) + .collect::>() + .into_iter() + { + self.tiling_layer.unmap(&window); + self.floating_layer.map(window, seat, None); } self.tiling_enabled = false; } else { - let focus_stack = self.focus_stack(seat); - for window in self.floating_layer.windows.clone().into_iter() { - self.floating_layer.unmap_window(&mut self.space, &window); - self.tiling_layer - .map_window(&mut self.space, window, seat, focus_stack.iter()) + let focus_stack = self.focus_stack.get(seat); + for window in self + .floating_layer + .mapped() + .cloned() + .collect::>() + .into_iter() + { + self.floating_layer.unmap(&window); + self.tiling_layer.map(window, seat, focus_stack.iter()) } self.tiling_enabled = true; } @@ -179,18 +287,157 @@ impl Workspace { pub fn toggle_floating_window(&mut self, seat: &Seat) { if self.tiling_enabled { - if let Some(window) = self.focus_stack(seat).iter().next().cloned() { - if self.tiling_layer.windows.contains(&window) { - self.tiling_layer.unmap_window(&mut self.space, &window); - self.floating_layer - .map_window(&mut self.space, window, seat, None); - } else if self.floating_layer.windows.contains(&window) { - let focus_stack = self.focus_stack(seat); - self.floating_layer.unmap_window(&mut self.space, &window); - self.tiling_layer - .map_window(&mut self.space, window, seat, focus_stack.iter()) + if let Some(window) = self.focus_stack.get(seat).iter().next().cloned() { + if self.tiling_layer.mapped().any(|(_, m, _)| m == &window) { + self.tiling_layer.unmap(&window); + self.floating_layer.map(window, seat, None); + } else if self.floating_layer.mapped().any(|w| w == &window) { + let focus_stack = self.focus_stack.get(seat); + self.floating_layer.unmap(&window); + self.tiling_layer.map(window, seat, focus_stack.iter()) } } } } + + pub fn mapped(&self) -> impl Iterator { + self.floating_layer + .mapped() + .chain(self.tiling_layer.mapped().map(|(_, w, _)| w)) + } + + pub fn windows(&self) -> impl Iterator + '_ { + self.floating_layer + .windows() + .chain(self.tiling_layer.windows().map(|(_, w, _)| w)) + } + + pub fn render_output( + &self, + output: &Output, + ) -> Result>, OutputNotMapped> + where + R: Renderer + ImportAll, + ::TextureId: 'static, + { + let mut render_elements = Vec::new(); + + let output_scale = output.current_scale().fractional_scale(); + let layer_map = layer_map_for_output(output); + + if let Some(fullscreen) = self.fullscreen.get(output) { + // overlay layer surfaces + render_elements.extend( + layer_map + .layers() + .rev() + .filter(|s| s.layer() == Layer::Overlay) + .filter_map(|surface| { + layer_map + .layer_geometry(surface) + .map(|geo| (geo.loc, surface)) + }) + .flat_map(|(loc, surface)| { + AsRenderElements::::render_elements::>( + surface, + loc.to_physical_precise_round(output_scale), + Scale::from(output_scale), + ) + }), + ); + + // fullscreen window + render_elements.extend(AsRenderElements::::render_elements::< + WorkspaceRenderElement, + >(fullscreen, (0, 0).into(), output_scale.into())); + } else { + // TODO: Handle modes like + // - keyboard window swapping + // - resizing / moving in tiling + + // overlay and top layer surfaces + let lower = { + let (upper, lower): (Vec<&LayerSurface>, Vec<&LayerSurface>) = layer_map + .layers() + .rev() + .partition(|s| matches!(s.layer(), Layer::Background | Layer::Bottom)); + + render_elements.extend( + upper + .into_iter() + .filter_map(|surface| { + layer_map + .layer_geometry(surface) + .map(|geo| (geo.loc, surface)) + }) + .flat_map(|(loc, surface)| { + AsRenderElements::::render_elements::>( + surface, + loc.to_physical_precise_round(output_scale), + Scale::from(output_scale), + ) + }), + ); + + lower + }; + + // floating surfaces + render_elements.extend( + self.floating_layer + .render_output::(output)? + .into_iter() + .map(WorkspaceRenderElement::from), + ); + + //tiling surfaces + render_elements.extend( + self.tiling_layer + .render_output::(output)? + .into_iter() + .map(WorkspaceRenderElement::from), + ); + + // bottom and background layer surfaces + { + render_elements.extend( + lower + .into_iter() + .filter_map(|surface| { + layer_map + .layer_geometry(surface) + .map(|geo| (geo.loc, surface)) + }) + .flat_map(|(loc, surface)| { + AsRenderElements::::render_elements::>( + surface, + loc.to_physical_precise_round(output_scale), + Scale::from(output_scale), + ) + }), + ); + } + } + + Ok(render_elements) + } +} + +impl FocusStacks { + pub fn get<'a>(&'a self, seat: &Seat) -> FocusStack<'a> { + FocusStack(self.0.get(seat)) + } + + pub fn get_mut<'a>(&'a mut self, seat: &Seat) -> FocusStackMut<'a> { + FocusStackMut(self.0.entry(seat.clone()).or_default()) + } +} + +pub struct OutputNotMapped; + +render_elements! { + pub WorkspaceRenderElement where R: ImportAll; + Wayland=WaylandSurfaceRenderElement, + Floating=FloatingRenderElement, + Tiling=TilingRenderElement, } diff --git a/src/state.rs b/src/state.rs index 3eaf3bbe..eeb69cb5 100644 --- a/src/state.rs +++ b/src/state.rs @@ -7,8 +7,8 @@ use crate::{ shell::Shell, utils::prelude::*, wayland::protocols::{ - drm::WlDrmState, export_dmabuf::ExportDmabufState, - output_configuration::OutputConfigurationState, workspace::WorkspaceClientState, + drm::WlDrmState, output_configuration::OutputConfigurationState, + workspace::WorkspaceClientState, }, }; use smithay::{ @@ -22,7 +22,6 @@ use smithay::{ Display, DisplayHandle, }, }, - utils::{Buffer, Size}, wayland::{ compositor::CompositorState, data_device::DataDeviceState, dmabuf::DmabufState, keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitState, output::OutputManagerState, @@ -71,8 +70,8 @@ pub struct Common { pub shell: Shell, pub dirty_flag: Arc, - pub seats: Vec>, - pub last_active_seat: Seat, + seats: Vec>, + last_active_seat: Option>, pub start_time: Instant, pub should_stop: bool, @@ -85,7 +84,6 @@ pub struct Common { pub compositor_state: CompositorState, pub data_device_state: DataDeviceState, pub dmabuf_state: DmabufState, - pub export_dmabuf_state: ExportDmabufState, pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState, pub output_state: OutputManagerState, pub output_configuration_state: OutputConfigurationState, @@ -201,88 +199,6 @@ impl BackendData { _ => unreachable!("No backend was initialized"), } } - - pub fn offscreen_for_output( - &mut self, - output: &Output, - state: &mut Common, - ) -> anyhow::Result<(Vec, Size)> { - use crate::backend::render::{render_output, AsGles2Renderer, CustomElem}; - use anyhow::Context; - use smithay::backend::renderer::{ImportAll, Renderer}; - use smithay::desktop::space::RenderElement; - use smithay::{ - backend::{ - drm::NodeType, - renderer::{gles2::Gles2Renderbuffer, Bind, ExportMem, Offscreen}, - }, - utils::Rectangle, - }; - - fn capture( - gpu: Option, - renderer: &mut R, - output: &Output, - state: &mut Common, - ) -> anyhow::Result<(Vec, Size)> - where - E: std::error::Error + Send + Sync + 'static, - T: Clone + 'static, - R: Renderer - + ImportAll - + AsGles2Renderer - + Offscreen - + Bind - + ExportMem, - CustomElem: RenderElement, - { - let size = output - .geometry() - .size - .to_f64() - .to_buffer( - output.current_scale().fractional_scale(), - output.current_transform().into(), - ) - .to_i32_round(); - let buffer = Offscreen::::create_buffer(renderer, size)?; - renderer.bind(buffer)?; - render_output( - gpu.as_ref(), - renderer, - 0, - state, - output, - false, - #[cfg(feature = "debug")] - None, - ) - .map_err(|err| anyhow::anyhow!("Failed to render output: {:?}", err))?; // lifetime issue, grrr - let mapping = renderer.copy_framebuffer(Rectangle::from_loc_and_size((0, 0), size))?; - let data = Vec::from(renderer.map_texture(&mapping)?); - - Ok((data, size)) - } - - match self { - BackendData::Winit(winit) => capture(None, winit.backend.renderer(), output, state), - BackendData::X11(x11) => capture(None, &mut x11.renderer, output, state), - BackendData::Kms(kms) => { - let node = kms - .target_node_for_output(output) - .unwrap_or(kms.primary) - .node_with_type(NodeType::Render) - .with_context(|| "Unable to find node")??; - capture( - Some(node), - &mut kms.api.renderer::(&node, &node)?, - output, - state, - ) - } - BackendData::Unset => unreachable!(), - } - } } impl State { @@ -297,22 +213,16 @@ impl State { let compositor_state = CompositorState::new::(dh, None); let data_device_state = DataDeviceState::new::(dh, None); let dmabuf_state = DmabufState::new(); - let export_dmabuf_state = ExportDmabufState::new::( - dh, - //|client| client.get_data::().unwrap().privileged, - |_| true, - ); let keyboard_shortcuts_inhibit_state = KeyboardShortcutsInhibitState::new::(dh); let output_state = OutputManagerState::new_with_xdg_output::(dh); let output_configuration_state = OutputConfigurationState::new(dh, |_| true); let primary_selection_state = PrimarySelectionState::new::(dh, None); let shm_state = ShmState::new::(dh, vec![], None); - let mut seat_state = SeatState::::new(); + let seat_state = SeatState::::new(); let viewporter_state = ViewporterState::new::(dh, None); let wl_drm_state = WlDrmState; let shell = Shell::new(&config, dh); - let initial_seat = crate::input::add_seat(dh, &mut seat_state, &config, "seat-0".into()); #[cfg(not(feature = "debug"))] let dirty_flag = Arc::new(AtomicBool::new(false)); @@ -330,8 +240,8 @@ impl State { shell, dirty_flag, - seats: vec![initial_seat.clone()], - last_active_seat: initial_seat, + seats: Vec::new(), + last_active_seat: None, start_time: Instant::now(), should_stop: false, @@ -354,7 +264,6 @@ impl State { compositor_state, data_device_state, dmabuf_state, - export_dmabuf_state, shm_state, seat_state, keyboard_shortcuts_inhibit_state, @@ -376,10 +285,9 @@ impl State { match std::env::var("COSMIC_RENDER_AUTO_ASSIGN").map(|val| val.to_lowercase()) { Ok(val) if val == "y" || val == "yes" || val == "true" => Some( kms_state - .target_node_for_output(&active_output( - &self.common.last_active_seat, - &self.common, - )) + .target_node_for_output( + &self.common.last_active_seat().active_output(), + ) .unwrap_or(kms_state.primary), ), _ => Some(kms_state.primary), @@ -415,6 +323,46 @@ impl State { } } +impl Common { + pub fn add_seat(&mut self, seat: Seat) { + if self.seats.is_empty() { + self.last_active_seat = Some(seat.clone()); + } + self.seats.push(seat); + } + + pub fn remove_seat(&mut self, seat: &Seat) { + self.seats.retain(|s| s != seat); + if self.seats.is_empty() { + self.last_active_seat = None; + } else if self.last_active_seat() == seat { + self.last_active_seat = Some(self.seats[0].clone()); + } + } + + pub fn seats(&self) -> impl Iterator> { + self.seats.iter() + } + + pub fn last_active_seat(&self) -> &Seat { + 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) { + let window = mapped.active_window(); + window.send_frame(self.start_time.elapsed().as_millis() as u32) + } + }); + 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) + } + } +} + #[cfg(feature = "debug")] impl Fps { const WINDOW_SIZE: usize = 100; diff --git a/src/utils/prelude.rs b/src/utils/prelude.rs index 55936e98..20ed2571 100644 --- a/src/utils/prelude.rs +++ b/src/utils/prelude.rs @@ -4,7 +4,6 @@ use smithay::{ output::Output, utils::{Logical, Rectangle, Transform}, }; -use std::cell::RefCell; pub use crate::shell::{Shell, Workspace}; pub use crate::state::{Common, State}; @@ -32,34 +31,25 @@ impl OutputExt for Output { pub trait SeatExt { fn id(&self) -> usize; + + fn active_output(&self) -> Output; + fn set_active_output(&self, output: &Output); } impl SeatExt for Seat { fn id(&self) -> usize { self.user_data().get::().unwrap().0 } -} -pub fn active_output(seat: &Seat, state: &Common) -> Output { - seat.user_data() - .get::() - .map(|x| x.0.borrow().clone()) - .unwrap_or_else(|| { - state - .shell - .outputs() - .next() - .cloned() - .expect("Backend has no outputs?") - }) -} + fn active_output(&self) -> Output { + self.user_data() + .get::() + .map(|x| x.0.borrow().clone()) + .unwrap() + } -pub fn set_active_output(seat: &Seat, output: &Output) { - if !seat - .user_data() - .insert_if_missing(|| ActiveOutput(RefCell::new(output.clone()))) - { - *seat + fn set_active_output(&self, output: &Output) { + *self .user_data() .get::() .unwrap() diff --git a/src/wayland/handlers/compositor.rs b/src/wayland/handlers/compositor.rs index 3e5c766c..c9935665 100644 --- a/src/wayland/handlers/compositor.rs +++ b/src/wayland/handlers/compositor.rs @@ -121,7 +121,7 @@ impl CompositorHandler for State { state.wl_buffer().is_some() }) { - let output = active_output(&seat, &self.common); + let output = seat.active_output(); Shell::map_window(self, &window, &output); } else { return; @@ -147,6 +147,7 @@ impl CompositorHandler for State { self.xdg_popup_ensure_initial_configure(&popup); } + /* // at last handle some special cases, like grabs and changing layer surfaces // If we would re-position the window inside the grab we would get a weird jittery animation. @@ -182,6 +183,7 @@ impl CompositorHandler for State { } } } + */ // We need to know every potential output for importing to the right gpu and scheduling a render, // so call this only after every potential surface map operation has been done. @@ -189,8 +191,8 @@ impl CompositorHandler for State { // and refresh smithays internal state self.common.shell.popups.commit(surface); - for workspace in &self.common.shell.spaces { - workspace.space.commit(surface); + for workspace in self.common.shell.workspaces.spaces_mut() { + workspace.commit(surface); } // re-arrange layer-surfaces (commits may change size and positioning) @@ -199,8 +201,7 @@ impl CompositorHandler for State { map.layer_for_surface(surface, WindowSurfaceType::ALL) .is_some() }) { - let dh = &self.common.display_handle; - layer_map_for_output(output).arrange(dh); + layer_map_for_output(output).arrange(); } // schedule a new render diff --git a/src/wayland/handlers/export_dmabuf.rs b/src/wayland/handlers/export_dmabuf.rs index 5a492a11..81b3b77e 100644 --- a/src/wayland/handlers/export_dmabuf.rs +++ b/src/wayland/handlers/export_dmabuf.rs @@ -213,7 +213,7 @@ impl ExportDmabufHandler for State { if cursor_status != CursorImageStatus::Hidden { let workspace = workspace.as_deref()?; let loc = seat.get_pointer().map(|ptr| ptr.current_location())?; - let output = active_output(seat, &self.common); + let output = seat.active_output(); if self.common.shell.active_space(&output).idx == workspace.idx { let relative = self diff --git a/src/wayland/handlers/layer_shell.rs b/src/wayland/handlers/layer_shell.rs index b5c73350..cc0d2c90 100644 --- a/src/wayland/handlers/layer_shell.rs +++ b/src/wayland/handlers/layer_shell.rs @@ -27,11 +27,11 @@ impl WlrLayerShellHandler for State { namespace: String, ) { super::mark_dirty_on_drop(&self.common, surface.wl_surface()); - let seat = self.common.last_active_seat.clone(); + let seat = self.common.last_active_seat().clone(); let output = wl_output .as_ref() .and_then(Output::from_resource) - .unwrap_or_else(|| active_output(&seat, &self.common)); + .unwrap_or_else(|| seat.active_output()); self.common.shell.pending_layers.push(( LayerSurface::new(surface, namespace), output, diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index 06590066..98ade3f1 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -4,7 +4,7 @@ pub mod buffer; pub mod compositor; pub mod data_device; pub mod dmabuf; -pub mod export_dmabuf; +//pub mod export_dmabuf; pub mod keyboard_shortcuts_inhibit; pub mod layer_shell; pub mod output; diff --git a/src/wayland/handlers/seat.rs b/src/wayland/handlers/seat.rs index 47e87444..1af64bb7 100644 --- a/src/wayland/handlers/seat.rs +++ b/src/wayland/handlers/seat.rs @@ -1,17 +1,23 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::state::State; +use crate::{ + shell::focus::target::{KeyboardFocusTarget, PointerFocusTarget}, + state::State, +}; use smithay::{ delegate_seat, input::{pointer::CursorImageStatus, SeatHandler, SeatState}, - reexports::wayland_server::{protocol::wl_surface::WlSurface, Resource}, - wayland::{data_device::set_data_device_focus, primary_selection::set_primary_focus}, + reexports::wayland_server::Resource, + wayland::{ + data_device::set_data_device_focus, primary_selection::set_primary_focus, + seat::WaylandFocus, + }, }; use std::cell::RefCell; impl SeatHandler for State { - type KeyboardFocus = WlSurface; - type PointerFocus = WlSurface; + type KeyboardFocus = KeyboardFocusTarget; + type PointerFocus = PointerFocusTarget; fn seat_state(&mut self) -> &mut SeatState { &mut self.common.seat_state @@ -35,10 +41,12 @@ impl SeatHandler for State { focused: Option<&Self::KeyboardFocus>, ) { let dh = &self.common.display_handle; - if let Some(client) = focused.and_then(|s| dh.get_client(s.id()).ok()) { - set_data_device_focus(dh, seat, Some(client)); - let client2 = focused.and_then(|s| dh.get_client(s.id()).ok()).unwrap(); - set_primary_focus(dh, seat, Some(client2)) + if let Some(client) = focused + .and_then(|t| t.wl_surface()) + .and_then(|s| dh.get_client(s.id()).ok()) + { + set_data_device_focus(dh, seat, Some(client.clone())); + set_primary_focus(dh, seat, Some(client)) } } } diff --git a/src/wayland/handlers/toplevel_management.rs b/src/wayland/handlers/toplevel_management.rs index ae6bcbe2..68a02331 100644 --- a/src/wayland/handlers/toplevel_management.rs +++ b/src/wayland/handlers/toplevel_management.rs @@ -19,18 +19,35 @@ impl ToplevelManagementHandler for State { } fn activate(&mut self, _dh: &DisplayHandle, window: &Window, seat: Option>) { - if let Some(idx) = self + for output in self .common .shell - .space_for_window(window.toplevel().wl_surface()) - .map(|w| w.idx) + .outputs() + .cloned() + .collect::>() + .iter() { - let seat = seat.unwrap_or(self.common.last_active_seat.clone()); - let output = active_output(&seat, &self.common); - if self.common.shell.active_space(&output).idx != idx { - self.common.shell.activate(&seat, &output, idx as usize); + let maybe = self + .common + .shell + .workspaces + .spaces_for_output(output) + .enumerate() + .find(|(_, w)| w.windows().any(|w| &w == window)); + if let Some((idx, workspace)) = maybe { + let seat = seat.unwrap_or(self.common.last_active_seat().clone()); + let mapped = workspace + .mapped() + .find(|m| m.windows().any(|(w, _)| &w == window)) + .unwrap() + .clone(); + + std::mem::drop(workspace); + self.common.shell.activate(&output, idx as usize); + mapped.focus_window(window); + Common::set_focus(self, Some(&mapped.clone().into()), &seat, None); + return; } - Common::set_focus(self, Some(window.toplevel().wl_surface()), &seat, None); } } diff --git a/src/wayland/handlers/workspace.rs b/src/wayland/handlers/workspace.rs index 41344e1b..26f7aca3 100644 --- a/src/wayland/handlers/workspace.rs +++ b/src/wayland/handlers/workspace.rs @@ -29,16 +29,15 @@ impl WorkspaceHandler for State { for request in requests.into_iter() { match request { Request::Activate(handle) => { - if let Some(idx) = self + let output = self.common.last_active_seat().active_output(); + let maybe_idx = self .common .shell - .spaces - .iter() - .position(|w| w.handle == handle) - { - let seat = &self.common.last_active_seat; - let output = active_output(seat, &self.common); - self.common.shell.activate(seat, &output, idx); + .workspaces + .spaces_for_output(&output) + .position(|w| w.handle == handle); + if let Some(idx) = maybe_idx { + self.common.shell.activate(&output, idx); } } _ => {} diff --git a/src/wayland/handlers/xdg_shell/mod.rs b/src/wayland/handlers/xdg_shell/mod.rs index a43edc8f..e049710b 100644 --- a/src/wayland/handlers/xdg_shell/mod.rs +++ b/src/wayland/handlers/xdg_shell/mod.rs @@ -5,7 +5,7 @@ use smithay::{ delegate_xdg_shell, desktop::{ find_popup_root_surface, Kind, PopupGrab, PopupKeyboardGrab, PopupKind, PopupPointerGrab, - PopupUngrabStrategy, Window, WindowSurfaceType, + PopupUngrabStrategy, Window, }, input::{ pointer::{Focus, GrabStartData as PointerGrabStartData}, @@ -17,8 +17,12 @@ use smithay::{ wayland_server::protocol::{wl_output::WlOutput, wl_seat::WlSeat, wl_surface::WlSurface}, }, utils::Serial, - wayland::shell::xdg::{ - Configure, PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState, + wayland::{ + seat::WaylandFocus, + shell::xdg::{ + Configure, PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, + XdgShellState, + }, }, }; use std::cell::Cell; @@ -35,13 +39,10 @@ impl XdgShellHandler for State { fn new_toplevel(&mut self, surface: ToplevelSurface) { super::mark_dirty_on_drop(&self.common, surface.wl_surface()); - let seat = &self.common.last_active_seat; + let seat = self.common.last_active_seat().clone(); let window = Window::new(Kind::Xdg(surface)); self.common.shell.toplevel_info_state.new_toplevel(&window); - self.common - .shell - .pending_windows - .push((window, seat.clone())); + self.common.shell.pending_windows.push((window, seat)); // We will position the window after the first commit, when we know its size hints } @@ -72,17 +73,9 @@ impl XdgShellHandler for State { // 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(window) = - self.common - .shell - .space_for_window(&surface) - .and_then(|workspace| { - workspace - .space - .window_for_surface(&surface, WindowSurfaceType::TOPLEVEL) - }) - { - crate::shell::layout::floating::ResizeSurfaceGrab::ack_configure(window, configure) + if let Some(mapped) = self.common.shell.element_for_surface(&surface) { + //TODO + //crate::shell::layout::floating::ResizeSurfaceGrab::ack_configure(window, configure) } } } @@ -90,12 +83,16 @@ impl XdgShellHandler for State { fn grab(&mut self, surface: PopupSurface, seat: WlSeat, serial: Serial) { let seat = Seat::from_resource(&seat).unwrap(); let kind = PopupKind::Xdg(surface); - if let Ok(root) = find_popup_root_surface(&kind) { + if let Some(root) = find_popup_root_surface(&kind) + .ok() + .and_then(|root| self.common.shell.element_for_surface(&root)) + { + let target = root.clone().into(); let ret = self .common .shell .popups - .grab_popup(root, kind, &seat, serial); + .grab_popup(target, kind, &seat, serial); if let Ok(mut grab) = ret { if let Some(keyboard) = seat.get_keyboard() { @@ -157,18 +154,9 @@ impl XdgShellHandler for State { fn move_request(&mut self, surface: ToplevelSurface, seat: WlSeat, serial: Serial) { let seat = Seat::from_resource(&seat).unwrap(); if let Some(start_data) = check_grab_preconditions(&seat, surface.wl_surface(), serial) { - let workspace = self - .common - .shell - .space_for_window_mut(surface.wl_surface()) - .unwrap(); - let window = workspace - .space - .window_for_surface(surface.wl_surface(), WindowSurfaceType::TOPLEVEL) - .unwrap() - .clone(); - - Shell::move_request(self, &window, &seat, serial, start_data); + if let Some(mapped) = self.common.shell.element_for_surface(surface.wl_surface()) { + // Shell::move_request(self, &window, &seat, serial, start_data); + } } } @@ -181,35 +169,31 @@ impl XdgShellHandler for State { ) { let seat = Seat::from_resource(&seat).unwrap(); if let Some(start_data) = check_grab_preconditions(&seat, surface.wl_surface(), serial) { - Workspace::resize_request(self, surface.wl_surface(), &seat, serial, start_data, edges); + if let Some(mapped) = self.common.shell.element_for_surface(surface.wl_surface()) { + // Shell::resize_request(self, mapped, &seat, serial, start_data, edges); + } } } fn maximize_request(&mut self, surface: ToplevelSurface) { let surface = surface.wl_surface(); - let seat = &self.common.last_active_seat; - let output = active_output(seat, &self.common); + let seat = self.common.last_active_seat(); + let output = seat.active_output(); - if let Some(workspace) = self.common.shell.space_for_window_mut(surface) { - let window = workspace - .space - .window_for_surface(surface, WindowSurfaceType::TOPLEVEL) - .unwrap() - .clone(); - workspace.maximize_request(&window, &output) + if let Some(mapped) = self.common.shell.element_for_surface(surface).cloned() { + if let Some(workspace) = self.common.shell.space_for_mut(&mapped) { + //workspace.maximize_request(mapped, &output) + } } } fn unmaximize_request(&mut self, surface: ToplevelSurface) { let surface = surface.wl_surface(); - if let Some(workspace) = self.common.shell.space_for_window_mut(surface) { - let window = workspace - .space - .window_for_surface(surface, WindowSurfaceType::TOPLEVEL) - .unwrap() - .clone(); - workspace.unmaximize_request(&window) + if let Some(mapped) = self.common.shell.element_for_surface(surface).cloned() { + if let Some(workspace) = self.common.shell.space_for_mut(&mapped) { + //workspace.unmaximize_request(mapped, &output) + } } } @@ -218,29 +202,25 @@ impl XdgShellHandler for State { .as_ref() .and_then(Output::from_resource) .unwrap_or_else(|| { - let seat = &self.common.last_active_seat; - active_output(seat, &self.common) + let seat = self.common.last_active_seat(); + seat.active_output() }); let surface = surface.wl_surface(); - if let Some(workspace) = self.common.shell.space_for_window_mut(surface) { - let window = workspace - .space - .window_for_surface(surface, WindowSurfaceType::TOPLEVEL) - .unwrap() - .clone(); - workspace.fullscreen_request(&window, &output) + if let Some(mapped) = self.common.shell.element_for_surface(surface).cloned() { + if let Some(workspace) = self.common.shell.space_for_mut(&mapped) { + workspace.fullscreen_request(&mapped.active_window(), &output) + } } } fn unfullscreen_request(&mut self, surface: ToplevelSurface) { - let surface = surface.wl_surface(); - if let Some(workspace) = self.common.shell.space_for_window_mut(surface) { - let window = workspace - .space - .window_for_surface(surface, WindowSurfaceType::TOPLEVEL) - .unwrap() - .clone(); + if let Some((workspace, window)) = self.common.shell.workspaces.spaces_mut().find_map(|w| { + let window = w + .windows() + .find(|w| w.toplevel().wl_surface() == surface.wl_surface()); + window.map(|win| (w, win)) + }) { workspace.unfullscreen_request(&window) } } @@ -270,7 +250,6 @@ fn check_grab_preconditions( .as_ref() .unwrap() .0 - .id() .same_client_as(&surface.id()) { return None; diff --git a/src/wayland/handlers/xdg_shell/popup.rs b/src/wayland/handlers/xdg_shell/popup.rs index ef0aed53..1e8b067e 100644 --- a/src/wayland/handlers/xdg_shell/popup.rs +++ b/src/wayland/handlers/xdg_shell/popup.rs @@ -3,8 +3,7 @@ use crate::{shell::Shell, utils::prelude::*}; use smithay::{ desktop::{ - layer_map_for_output, LayerSurface, PopupKind, PopupManager, Space, Window, - WindowSurfaceType, + layer_map_for_output, LayerSurface, PopupKind, PopupManager, Window, WindowSurfaceType, }, output::Output, reexports::{ @@ -27,12 +26,19 @@ use std::sync::Mutex; impl Shell { pub fn unconstrain_popup(&self, surface: &PopupSurface, positioner: &PositionerState) { if let Some(parent) = get_popup_toplevel(&surface) { - if let Some(workspace) = self.space_for_window(&parent) { - let window = workspace - .space - .window_for_surface(&parent, WindowSurfaceType::ALL) + if let Some(elem) = self.element_for_surface(&parent) { + let workspace = self.space_for(elem).unwrap(); + let element_loc = workspace.element_geometry(elem).unwrap().loc; + let (window, offset) = elem + .windows() + .find(|(w, _)| w.toplevel().wl_surface() == &parent) .unwrap(); - unconstrain_xdg_popup(surface, positioner, &workspace.space, window); + let window_geo_offset = window.geometry().loc; + let window_loc = element_loc + offset + window_geo_offset; + let anchor_point = get_anchor_point(&positioner) + window_loc; + if let Some(output) = workspace.output_under(anchor_point) { + unconstrain_xdg_popup(surface, positioner, window_loc, output.geometry()); + } } else if let Some((output, layer_surface)) = self.outputs().find_map(|o| { let map = layer_map_for_output(o); map.layer_for_surface(&parent, WindowSurfaceType::ALL) @@ -42,15 +48,14 @@ impl Shell { } } } - - pub fn update_reactive_popups(&self, window: &Window) { - if let Some(workspace) = self.space_for_window(window.toplevel().wl_surface()) { - update_reactive_popups(&workspace.space, window); - } - } } -pub fn update_reactive_popups(space: &Space, window: &Window) { +pub fn update_reactive_popups<'a>( + window: &Window, + loc: Point, + outputs: impl Iterator, +) { + let output_geo = outputs.map(|o| o.geometry()).collect::>(); for (popup, _) in PopupManager::popups_for_surface(window.toplevel().wl_surface()) { match popup { PopupKind::Xdg(surface) => { @@ -64,12 +69,19 @@ pub fn update_reactive_popups(space: &Space, window: &Window) { attributes.current.positioner.clone() }); if positioner.reactive { - unconstrain_xdg_popup(&surface, &positioner, space, window); - if let Err(err) = surface.send_configure() { - slog_scope::warn!( - "Compositor bug: Unable to re-configure reactive popup: {}", - err - ); + let anchor_point = get_anchor_point(&positioner) + loc; + if let Some(rect) = output_geo + .iter() + .find(|geo| geo.contains(anchor_point)) + .copied() + { + unconstrain_xdg_popup(&surface, &positioner, loc, rect); + if let Err(err) = surface.send_configure() { + slog_scope::warn!( + "Compositor bug: Unable to re-configure reactive popup: {}", + err + ); + } } } } @@ -80,32 +92,18 @@ pub fn update_reactive_popups(space: &Space, window: &Window) { fn unconstrain_xdg_popup( surface: &PopupSurface, positioner: &PositionerState, - space: &Space, - window: &Window, + window_loc: Point, + rect: Rectangle, ) { - let anchor_point = get_anchor_point(&positioner) + space.window_location(&window).unwrap(); - if let Some(output_rect) = space - .outputs_for_window(window) - .into_iter() - .find(|o| { - space - .output_geometry(o) - .map(|rect| rect.contains(anchor_point)) - .unwrap_or(false) - }) - .map(|o| space.output_geometry(&o).unwrap()) - { - // the output_rect represented relative to the parents coordinate system - let mut relative = output_rect; - relative.loc -= space.window_location(&window).unwrap(); - let offset = check_constrained(&surface, positioner.get_geometry(), relative); + let mut relative = rect; + relative.loc -= window_loc; + let offset = check_constrained(&surface, positioner.get_geometry(), relative); - if offset.x != 0 || offset.y != 0 { - slog_scope::debug!("Unconstraining popup: {:?}", surface); - if !unconstrain_flip(&surface, &positioner, relative) { - if !unconstrain_slide(&surface, &positioner, relative) { - unconstrain_resize(&surface, &positioner, relative); - } + if offset.x != 0 || offset.y != 0 { + slog_scope::debug!("Unconstraining popup: {:?}", surface); + if !unconstrain_flip(&surface, &positioner, relative) { + if !unconstrain_slide(&surface, &positioner, relative) { + unconstrain_resize(&surface, &positioner, relative); } } } diff --git a/src/wayland/protocols/mod.rs b/src/wayland/protocols/mod.rs index 2129bd98..069f16c5 100644 --- a/src/wayland/protocols/mod.rs +++ b/src/wayland/protocols/mod.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pub mod drm; -pub mod export_dmabuf; +//pub mod export_dmabuf; pub mod output_configuration; pub mod toplevel_info; pub mod toplevel_management; diff --git a/src/wayland/protocols/output_configuration.rs b/src/wayland/protocols/output_configuration.rs index 45cb341d..9a98c52d 100644 --- a/src/wayland/protocols/output_configuration.rs +++ b/src/wayland/protocols/output_configuration.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use smithay::{ - output::{Mode, Output, OutputData}, + output::{Mode, Output}, reexports::{ wayland_protocols_wlr::output_management::v1::server::{ zwlr_output_configuration_head_v1::{self, ZwlrOutputConfigurationHeadV1}, @@ -17,6 +17,7 @@ use smithay::{ }, }, utils::{Logical, Physical, Point, Size, Transform}, + wayland::output::WlOutputData, }; use std::{ convert::{TryFrom, TryInto}, @@ -498,7 +499,7 @@ where impl OutputConfigurationState where D: GlobalDispatch - + GlobalDispatch + + GlobalDispatch + Dispatch + Dispatch + Dispatch diff --git a/src/wayland/protocols/toplevel_info.rs b/src/wayland/protocols/toplevel_info.rs index 6a4c9b25..b9d154a3 100644 --- a/src/wayland/protocols/toplevel_info.rs +++ b/src/wayland/protocols/toplevel_info.rs @@ -413,7 +413,7 @@ fn send_toplevel_to_client( .iter() .filter(|o| !handle_state.outputs.contains(o)) { - new_output.with_client_outputs(dh, &client, |_dh, wl_output| { + new_output.with_client_outputs(&client, |wl_output| { instance.output_enter(wl_output); }); changed = true; @@ -423,7 +423,7 @@ fn send_toplevel_to_client( .iter() .filter(|o| !state.outputs.contains(o)) { - old_output.with_client_outputs(dh, &client, |_dh, wl_output| { + old_output.with_client_outputs(&client, |wl_output| { instance.output_leave(wl_output); }); changed = true; diff --git a/src/wayland/protocols/workspace.rs b/src/wayland/protocols/workspace.rs index 07cfdff2..7915b805 100644 --- a/src/wayland/protocols/workspace.rs +++ b/src/wayland/protocols/workspace.rs @@ -821,7 +821,7 @@ where .iter() .filter(|o| !handle_state.outputs.contains(o)) { - new_output.with_client_outputs(dh, &client, |_dh, wl_output| { + new_output.with_client_outputs(&client, |wl_output| { instance.output_enter(wl_output); }); changed = true; @@ -831,7 +831,7 @@ where .iter() .filter(|o| !group.outputs.contains(o)) { - old_output.with_client_outputs(dh, &client, |_dh, wl_output| { + old_output.with_client_outputs(&client, |wl_output| { instance.output_leave(wl_output); }); changed = true; From e3ef569a6bebdd33214ff34dc0334b2dabdb618c Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 21 Oct 2022 21:05:34 +0200 Subject: [PATCH 02/62] shell/tiling: Fix wrong order for window positions --- src/shell/layout/tiling/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 8a824935..4ca57a04 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -28,7 +28,7 @@ use smithay::{ use std::{ borrow::Borrow, cell::RefCell, - collections::HashMap, + collections::{HashMap, VecDeque}, hash::Hash, sync::{atomic::AtomicBool, Arc}, }; @@ -756,7 +756,7 @@ impl TilingLayout { .map(|(output_data, tree)| (&output_data.output, tree)) { if let Some(root) = tree.root_node_id() { - let mut stack = Vec::new(); + let mut stack = VecDeque::new(); let mut geo = Some(layer_map_for_output(&output).non_exclusive_zone()); // TODO saturate? minimum? @@ -778,7 +778,7 @@ impl TilingLayout { .into_iter() { let node = tree.get_mut(&node_id).unwrap(); - let geo = stack.pop().unwrap_or(geo); + let geo = stack.pop_front().unwrap_or(geo); if let Some(geo) = geo { let data = node.data_mut(); data.update_geometry(geo); @@ -789,7 +789,7 @@ impl TilingLayout { Orientation::Horizontal => { let mut previous = 0; for size in sizes { - stack.push(Some(Rectangle::from_loc_and_size( + stack.push_back(Some(Rectangle::from_loc_and_size( (geo.loc.x, geo.loc.y + previous), (geo.size.w, *size), ))); @@ -799,7 +799,7 @@ impl TilingLayout { Orientation::Vertical => { let mut previous = 0; for size in sizes { - stack.push(Some(Rectangle::from_loc_and_size( + stack.push_back(Some(Rectangle::from_loc_and_size( (geo.loc.x + previous, geo.loc.y), (*size, geo.size.h), ))); @@ -818,8 +818,8 @@ impl TilingLayout { } } } else if node.data().is_group() { - stack.push(None); - stack.push(None); + stack.push_back(None); + stack.push_back(None); } } } From 1eb5d1e64fdfae04f673223150f283a1f0294989 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 21 Oct 2022 21:06:20 +0200 Subject: [PATCH 03/62] shell/tiling: Fix off-by-one-parent group creation --- src/shell/layout/tiling/mod.rs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 4ca57a04..009b8b52 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -305,20 +305,7 @@ impl TilingLayout { Orientation::Horizontal } }; - - let parent_id = tree.get(node_id).unwrap().parent().cloned(); - match parent_id { - Some(group_id) if tree.get(&group_id).unwrap().data().is_group() => { - TilingLayout::new_group(tree, &group_id, new_window, orientation) - } - None => { - // if there is no window, we couldn't have found the window_id. - // so if there is no parent, we have found a root, thus it is safe to unwrap. - let root_id = tree.root_node_id().cloned().unwrap(); - TilingLayout::new_group(tree, &root_id, new_window, orientation) - } - _ => panic!("Illegal tiling tree structure"), - } + TilingLayout::new_group(tree, &node_id, new_window, orientation) } else { // nothing? then we add to the root if let Some(root_id) = tree.root_node_id().cloned() { From f59bb8a06e5a455a7bcfbcbeb22217951f8b8290 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 24 Oct 2022 14:05:36 +0200 Subject: [PATCH 04/62] shell: Correctly handle window geometry --- src/shell/element/window.rs | 2 +- src/shell/layout/tiling/mod.rs | 9 ++++++--- src/shell/workspace.rs | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index 821b4ae4..c01a6b18 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -221,7 +221,7 @@ where ) -> Vec { AsRenderElements::::render_elements::>( &self.window, - location - self.window.geometry().loc.to_physical_precise_round(scale), + location, scale, ) .into_iter() diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 009b8b52..91a03aa9 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -16,7 +16,7 @@ use crate::{ use id_tree::{InsertBehavior, MoveBehavior, Node, NodeId, NodeIdError, RemoveBehavior, Tree}; use smithay::{ backend::renderer::{element::AsRenderElements, ImportAll, Renderer}, - desktop::{layer_map_for_output, Window}, + desktop::{layer_map_for_output, space::SpaceElement, Window}, input::{ pointer::{Focus, GrabStartData as PointerGrabStartData}, Seat, @@ -899,7 +899,6 @@ impl TilingLayout { ::TextureId: 'static, { let output_scale = output.current_scale().fractional_scale(); - let int_scale = output.current_scale().integer_scale(); if !self.trees.contains_key(output) { return Err(OutputNotMapped); @@ -916,7 +915,11 @@ impl TilingLayout { }) .flat_map(|(mapped, loc)| { mapped.render_elements::>( - loc.to_physical(int_scale), + loc.to_physical_precise_round(output_scale) + - mapped + .geometry() + .loc + .to_physical_precise_round(output_scale), Scale::from(output_scale), ) }) diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 00271300..1285a96a 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -128,10 +128,10 @@ impl Workspace { .element_under(location) .or_else(|| { self.tiling_layer.mapped().find_map(|(_, mapped, loc)| { - let test_point = location - loc.to_f64(); + let test_point = location - loc.to_f64() + mapped.geometry().loc.to_f64(); mapped .is_in_input_region(&test_point) - .then_some((mapped, loc)) + .then_some((mapped, loc - mapped.geometry().loc)) }) }) } From bb07ab4155723d92dd4e247765ce0384d54762bb Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 24 Oct 2022 18:32:53 +0200 Subject: [PATCH 05/62] shell: Support maximize and fullscreen requests --- src/shell/element/mod.rs | 6 + src/shell/layout/floating/mod.rs | 46 ++++---- src/shell/layout/tiling/mod.rs | 2 +- src/shell/workspace.rs | 152 +++++++++++++++----------- src/wayland/handlers/xdg_shell/mod.rs | 63 ++++++++--- 5 files changed, 166 insertions(+), 103 deletions(-) diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index 20096d06..c0727b3b 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -107,6 +107,12 @@ impl CosmicMapped { } } + pub fn set_active(&self, window: &Window) { + if let CosmicMappedInternal::Stack(stack) = &self.element { + stack.set_active(window); + } + } + pub fn focus_window(&self, window: &Window) { match &self.element { CosmicMappedInternal::Stack(stack) => stack.set_active(window), diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index a64539c0..399f8cc9 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -155,30 +155,34 @@ impl FloatingLayout { self.space.element_geometry(elem) } - pub fn maximize_request(&mut self, window: &CosmicMapped, output: &Output) { - let layers = layer_map_for_output(&output); - let geometry = layers.non_exclusive_zone(); - - if let Some(location) = self.space.element_location(window) { - *window.last_geometry.lock().unwrap() = Some(Rectangle::from_loc_and_size( - location, - window.geometry().size, - )); + pub fn maximize_request(&mut self, window: &Window) { + if let Some(mapped) = self + .space + .elements() + .find(|m| m.windows().any(|(w, _)| &w == window)) + { + if let Some(location) = self.space.element_location(mapped) { + *mapped.last_geometry.lock().unwrap() = Some(Rectangle::from_loc_and_size( + location, + mapped.geometry().size, + )); + } } - - self.space.map_element(window.clone(), geometry.loc, true); - window.set_maximized(true); - window.set_size(geometry.size); - window.configure(); } - pub fn unmaximize_request(&mut self, window: &CosmicMapped) { - let last_geometry = window.last_geometry.lock().unwrap().clone(); - window.set_maximized(false); - window.set_size(last_geometry.map(|g| g.size).expect("No previous size?")); - window.configure(); - let last_location = last_geometry.map(|g| g.loc).expect("No previous location?"); - self.space.map_element(window.clone(), last_location, true); + pub fn unmaximize_request(&mut self, window: &Window) { + let maybe_mapped = self + .space + .elements() + .find(|m| m.windows().any(|(w, _)| &w == window)) + .cloned(); + + if let Some(mapped) = maybe_mapped { + let last_geometry = mapped.last_geometry.lock().unwrap().clone(); + mapped.set_size(last_geometry.map(|g| g.size).expect("No previous size?")); + let last_location = last_geometry.map(|g| g.loc).expect("No previous location?"); + self.space.map_element(mapped, last_location, true); + } } /* diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 91a03aa9..87ee2fd1 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -795,7 +795,7 @@ impl TilingLayout { } }, Data::Mapped { mapped, .. } => { - if !mapped.is_fullscreen() { + if !(mapped.is_fullscreen() || mapped.is_maximized()) { mapped.set_tiled(true); mapped.set_size( (geo.size.w - inner * 2, geo.size.h - inner * 2).into(), diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 1285a96a..2f8c4bcd 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -157,26 +157,103 @@ impl Workspace { }) } - /* - pub fn maximize_request(&mut self, window: &CosmicWindow, output: &Output) { - if self.fullscreen.values().any(|w| w == window) { + pub fn maximize_request(&mut self, window: &Window, output: &Output) { + if self.fullscreen.contains_key(output) { return; } - if self.floating_layer.windows.contains(window) { - self.floating_layer - .maximize_request(, window, output); + + self.floating_layer.maximize_request(window); + + #[allow(irrefutable_let_patterns)] + if let Kind::Xdg(xdg) = &window.toplevel() { + xdg.with_pending_state(|state| { + state.states.set(xdg_toplevel::State::Maximized); + state.states.unset(xdg_toplevel::State::Fullscreen); + }); } + + self.set_fullscreen(window, output) } - pub fn unmaximize_request(&mut self, window: &CosmicMapped) { + pub fn unmaximize_request(&mut self, window: &Window) { if self.fullscreen.values().any(|w| w == window) { + self.floating_layer.unmaximize_request(window); return self.unfullscreen_request(window); } - if self.floating_layer.windows.contains(window) { - self.floating_layer - .unmaximize_request(window); - } } + pub fn fullscreen_request(&mut self, window: &Window, output: &Output) { + if self.fullscreen.contains_key(output) { + return; + } + + #[allow(irrefutable_let_patterns)] + if let Kind::Xdg(xdg) = &window.toplevel() { + xdg.with_pending_state(|state| { + state.states.set(xdg_toplevel::State::Fullscreen); + state.states.unset(xdg_toplevel::State::Maximized); + }); + } + + self.set_fullscreen(window, output) + } + + fn set_fullscreen(&mut self, window: &Window, output: &Output) { + if let Some(mapped) = self + .mapped() + .find(|m| m.windows().any(|(w, _)| &w == window)) + { + mapped.set_active(window); + } + + #[allow(irrefutable_let_patterns)] + if let Kind::Xdg(xdg) = &window.toplevel() { + xdg.with_pending_state(|state| { + state.size = Some( + output + .current_mode() + .map(|m| m.size) + .unwrap_or((0, 0).into()) + .to_f64() + .to_logical(output.current_scale().fractional_scale()) + .to_i32_round(), + ); + }); + + xdg.send_configure(); + } + self.fullscreen.insert(output.clone(), window.clone()); + } + + pub fn unfullscreen_request(&mut self, window: &Window) { + if self.fullscreen.values().any(|w| w == window) { + #[allow(irrefutable_let_patterns)] + if let Kind::Xdg(xdg) = &window.toplevel() { + xdg.with_pending_state(|state| { + state.states.unset(xdg_toplevel::State::Fullscreen); + state.states.unset(xdg_toplevel::State::Maximized); + state.size = None; + }); + self.floating_layer.refresh(); + self.tiling_layer.refresh(); + xdg.send_configure(); + } + self.fullscreen.retain(|_, w| w != window); + } + } + + pub fn fullscreen_toggle(&mut self, window: &Window, output: &Output) { + if self.fullscreen.contains_key(output) { + self.unfullscreen_request(window) + } else { + self.fullscreen_request(window, output) + } + } + + pub fn get_fullscreen(&self, output: &Output) -> Option<&Window> { + self.fullscreen.get(output).filter(|w| w.alive()) + } + + /* pub fn resize_request( state: &mut State, surface: &WlSurface, @@ -203,59 +280,6 @@ impl Workspace { } */ - pub fn fullscreen_request(&mut self, window: &Window, output: &Output) { - if self.fullscreen.contains_key(output) { - return; - } - - #[allow(irrefutable_let_patterns)] - if let Kind::Xdg(xdg) = &window.toplevel() { - xdg.with_pending_state(|state| { - state.states.set(xdg_toplevel::State::Fullscreen); - state.size = Some( - output - .current_mode() - .map(|m| m.size) - .unwrap_or((0, 0).into()) - .to_f64() - .to_logical(output.current_scale().fractional_scale()) - .to_i32_round(), - ); - }); - - xdg.send_configure(); - self.fullscreen.insert(output.clone(), window.clone()); - } - } - - pub fn unfullscreen_request(&mut self, window: &Window) { - if self.fullscreen.values().any(|w| w == window) { - #[allow(irrefutable_let_patterns)] - if let Kind::Xdg(xdg) = &window.toplevel() { - xdg.with_pending_state(|state| { - state.states.unset(xdg_toplevel::State::Fullscreen); - state.size = None; - }); - self.floating_layer.refresh(); - self.tiling_layer.refresh(); - xdg.send_configure(); - } - self.fullscreen.retain(|_, w| w != window); - } - } - - pub fn fullscreen_toggle(&mut self, window: &Window, output: &Output) { - if self.fullscreen.contains_key(output) { - self.unfullscreen_request(window) - } else { - self.fullscreen_request(window, output) - } - } - - pub fn get_fullscreen(&self, output: &Output) -> Option<&Window> { - self.fullscreen.get(output).filter(|w| w.alive()) - } - pub fn toggle_tiling(&mut self, seat: &Seat) { if self.tiling_enabled { for window in self diff --git a/src/wayland/handlers/xdg_shell/mod.rs b/src/wayland/handlers/xdg_shell/mod.rs index e049710b..083ae411 100644 --- a/src/wayland/handlers/xdg_shell/mod.rs +++ b/src/wayland/handlers/xdg_shell/mod.rs @@ -176,23 +176,38 @@ impl XdgShellHandler for State { } fn maximize_request(&mut self, surface: ToplevelSurface) { - let surface = surface.wl_surface(); let seat = self.common.last_active_seat(); let output = seat.active_output(); - if let Some(mapped) = self.common.shell.element_for_surface(surface).cloned() { + if let Some(mapped) = self + .common + .shell + .element_for_surface(surface.wl_surface()) + .cloned() + { if let Some(workspace) = self.common.shell.space_for_mut(&mapped) { - //workspace.maximize_request(mapped, &output) + let (window, _) = mapped + .windows() + .find(|(w, _)| matches!(w.toplevel(), Kind::Xdg(s) if s == &surface)) + .unwrap(); + workspace.maximize_request(&window, &output) } } } fn unmaximize_request(&mut self, surface: ToplevelSurface) { - let surface = surface.wl_surface(); - - if let Some(mapped) = self.common.shell.element_for_surface(surface).cloned() { + if let Some(mapped) = self + .common + .shell + .element_for_surface(surface.wl_surface()) + .cloned() + { if let Some(workspace) = self.common.shell.space_for_mut(&mapped) { - //workspace.unmaximize_request(mapped, &output) + let (window, _) = mapped + .windows() + .find(|(w, _)| matches!(w.toplevel(), Kind::Xdg(s) if s == &surface)) + .unwrap(); + workspace.unmaximize_request(&window) } } } @@ -206,22 +221,36 @@ impl XdgShellHandler for State { seat.active_output() }); - let surface = surface.wl_surface(); - if let Some(mapped) = self.common.shell.element_for_surface(surface).cloned() { + if let Some(mapped) = self + .common + .shell + .element_for_surface(surface.wl_surface()) + .cloned() + { if let Some(workspace) = self.common.shell.space_for_mut(&mapped) { - workspace.fullscreen_request(&mapped.active_window(), &output) + let (window, _) = mapped + .windows() + .find(|(w, _)| matches!(w.toplevel(), Kind::Xdg(s) if s == &surface)) + .unwrap(); + workspace.fullscreen_request(&window, &output) } } } fn unfullscreen_request(&mut self, surface: ToplevelSurface) { - if let Some((workspace, window)) = self.common.shell.workspaces.spaces_mut().find_map(|w| { - let window = w - .windows() - .find(|w| w.toplevel().wl_surface() == surface.wl_surface()); - window.map(|win| (w, win)) - }) { - workspace.unfullscreen_request(&window) + if let Some(mapped) = self + .common + .shell + .element_for_surface(surface.wl_surface()) + .cloned() + { + if let Some(workspace) = self.common.shell.space_for_mut(&mapped) { + let (window, _) = mapped + .windows() + .find(|(w, _)| matches!(w.toplevel(), Kind::Xdg(s) if s == &surface)) + .unwrap(); + workspace.unfullscreen_request(&window) + } } } } From 7d068ab6bc20cd8dc41581cb64b1a6390f354b03 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 25 Oct 2022 14:43:50 +0200 Subject: [PATCH 06/62] shell/floating: Reintroduce resizing --- src/shell/element/mod.rs | 39 ++++- src/shell/layout/floating/grabs.rs | 202 ++++++++------------------ src/shell/layout/floating/mod.rs | 37 +++-- src/shell/workspace.rs | 36 +++-- src/wayland/handlers/compositor.rs | 33 +---- src/wayland/handlers/xdg_shell/mod.rs | 17 ++- 6 files changed, 154 insertions(+), 210 deletions(-) diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index c0727b3b..d7d5daa6 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -34,7 +34,7 @@ pub use self::stack::CosmicStack; pub mod window; pub use self::window::CosmicWindow; -use super::focus::FocusDirection; +use super::{focus::FocusDirection, layout::floating::ResizeState}; space_elements! { #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -53,6 +53,7 @@ pub struct CosmicMapped { pub(super) tiling_node_id: Arc>>, //floating pub(super) last_geometry: Arc>>>, + pub(super) resize_state: Arc>>, } impl PartialEq for CosmicMapped { @@ -165,6 +166,40 @@ impl CosmicMapped { } } + pub fn set_resizing(&self, resizing: bool) { + for window in match &self.element { + CosmicMappedInternal::Stack(s) => { + Box::new(s.windows()) as Box> + } + CosmicMappedInternal::Window(w) => Box::new(std::iter::once(w.window.clone())), + _ => unreachable!(), + } { + match window.toplevel() { + Kind::Xdg(xdg) => xdg.with_pending_state(|state| { + if resizing { + state.states.set(XdgState::Resizing); + } else { + state.states.unset(XdgState::Resizing); + } + }), + // Kind::X11? + }; + } + } + + pub fn is_resizing(&self) -> bool { + let window = match &self.element { + CosmicMappedInternal::Stack(s) => s.active(), + CosmicMappedInternal::Window(w) => w.window.clone(), + _ => unreachable!(), + }; + + match window.toplevel() { + Kind::Xdg(xdg) => xdg.current_state().states.contains(XdgState::Resizing), + // Kind::X11? + } + } + 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 @@ -590,6 +625,7 @@ impl From for CosmicMapped { element: CosmicMappedInternal::Window(w), tiling_node_id: Arc::new(Mutex::new(None)), last_geometry: Arc::new(Mutex::new(None)), + resize_state: Arc::new(Mutex::new(None)), } } } @@ -600,6 +636,7 @@ impl From for CosmicMapped { element: CosmicMappedInternal::Stack(s), tiling_node_id: Arc::new(Mutex::new(None)), last_geometry: Arc::new(Mutex::new(None)), + resize_state: Arc::new(Mutex::new(None)), } } } diff --git a/src/shell/layout/floating/grabs.rs b/src/shell/layout/floating/grabs.rs index 800150aa..8ff20f92 100644 --- a/src/shell/layout/floating/grabs.rs +++ b/src/shell/layout/floating/grabs.rs @@ -1,32 +1,32 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::{shell::focus::target::PointerFocusTarget, utils::prelude::*}; +use crate::{ + shell::{element::CosmicMapped, focus::target::PointerFocusTarget}, + utils::prelude::*, +}; use smithay::{ - desktop::{Kind, Window}, + desktop::space::SpaceElement, input::pointer::{ AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle, }, reexports::wayland_protocols::xdg::shell::server::xdg_toplevel, - utils::{IsAlive, Logical, Point, Serial, Size}, - wayland::{ - compositor::with_states, - shell::xdg::{SurfaceCachedState, ToplevelConfigure, XdgToplevelSurfaceRoleAttributes}, - }, + utils::{IsAlive, Logical, Point, Size}, }; -use std::{cell::RefCell, convert::TryFrom, sync::Mutex}; +use std::convert::TryFrom; bitflags::bitflags! { - struct ResizeEdge: u32 { - const NONE = 0; - const TOP = 1; - const BOTTOM = 2; - const LEFT = 4; - const TOP_LEFT = 5; - const BOTTOM_LEFT = 6; - const RIGHT = 8; - const TOP_RIGHT = 9; - const BOTTOM_RIGHT = 10; + pub struct ResizeEdge: u32 { + const TOP = 0b0001; + const BOTTOM = 0b0010; + const LEFT = 0b0100; + const RIGHT = 0b1000; + + const TOP_LEFT = Self::TOP.bits | Self::LEFT.bits; + const BOTTOM_LEFT = Self::BOTTOM.bits | Self::LEFT.bits; + + const TOP_RIGHT = Self::TOP.bits | Self::RIGHT.bits; + const BOTTOM_RIGHT = Self::BOTTOM.bits | Self::RIGHT.bits; } } @@ -46,7 +46,7 @@ impl From for xdg_toplevel::ResizeEdge { /// Information about the resize operation. #[derive(Debug, Clone, Copy, Eq, PartialEq)] -struct ResizeData { +pub struct ResizeData { /// The edges the surface is being resized with. edges: ResizeEdge, /// The initial window location. @@ -57,26 +57,16 @@ struct ResizeData { /// State of the resize operation. #[derive(Debug, Clone, Copy, Eq, PartialEq)] -enum ResizeState { - /// The surface is not being resized. - NotResizing, +pub enum ResizeState { /// The surface is currently being resized. Resizing(ResizeData), - /// The resize has finished, and the surface needs to ack the final configure. - WaitingForFinalAck(ResizeData, Serial), /// The resize has finished, and the surface needs to commit its final state. WaitingForCommit(ResizeData), } -impl Default for ResizeState { - fn default() -> Self { - ResizeState::NotResizing - } -} - pub struct ResizeSurfaceGrab { start_data: PointerGrabStartData, - window: Window, + window: CosmicMapped, edges: ResizeEdge, initial_window_size: Size, last_window_size: Size, @@ -123,10 +113,7 @@ impl PointerGrab for ResizeSurfaceGrab { new_window_height = (self.initial_window_size.h as f64 + dy) as i32; } - let (min_size, max_size) = with_states(self.window.toplevel().wl_surface(), |states| { - let data = states.cached_state.current::(); - (data.min_size, data.max_size) - }); + let (min_size, max_size) = (self.window.min_size(), self.window.max_size()); let min_width = min_size.w.max(1); let min_height = min_size.h.max(1); @@ -146,15 +133,9 @@ impl PointerGrab for ResizeSurfaceGrab { self.last_window_size = (new_window_width, new_window_height).into(); - match &self.window.toplevel() { - Kind::Xdg(xdg) => { - xdg.with_pending_state(|state| { - state.states.set(xdg_toplevel::State::Resizing); - state.size = Some(self.last_window_size); - }); - xdg.send_configure(); - } - } + self.window.set_resizing(true); + self.window.set_size(self.last_window_size); + self.window.configure(); } fn button( @@ -173,23 +154,13 @@ impl PointerGrab for ResizeSurfaceGrab { return; } - #[allow(irrefutable_let_patterns)] - if let Kind::Xdg(xdg) = &self.window.toplevel() { - xdg.with_pending_state(|state| { - state.states.unset(xdg_toplevel::State::Resizing); - state.size = Some(self.last_window_size); - }); - xdg.send_configure(); - } + self.window.set_resizing(false); + self.window.set_size(self.last_window_size); + self.window.configure(); - let mut resize_state = self - .window - .user_data() - .get::>() - .unwrap() - .borrow_mut(); - if let ResizeState::Resizing(resize_data) = *resize_state { - *resize_state = ResizeState::WaitingForFinalAck(resize_data, event.serial); + let mut resize_state = self.window.resize_state.lock().unwrap(); + if let Some(ResizeState::Resizing(resize_data)) = *resize_state { + *resize_state = Some(ResizeState::WaitingForCommit(resize_data)); } else { panic!("invalid resize state: {:?}", resize_state); } @@ -213,7 +184,7 @@ impl PointerGrab for ResizeSurfaceGrab { impl ResizeSurfaceGrab { pub fn new( start_data: PointerGrabStartData, - window: Window, + mapped: CosmicMapped, edges: xdg_toplevel::ResizeEdge, initial_window_location: Point, initial_window_size: Size, @@ -224,92 +195,27 @@ impl ResizeSurfaceGrab { initial_window_size, }); - window - .user_data() - .insert_if_missing(|| RefCell::new(ResizeState::default())); - *window - .user_data() - .get::>() - .unwrap() - .borrow_mut() = resize_state; + *mapped.resize_state.lock().unwrap() = Some(resize_state); ResizeSurfaceGrab { start_data, - window, + window: mapped, edges: edges.into(), initial_window_size, last_window_size: initial_window_size, } } - pub fn ack_configure(window: &Window, configure: ToplevelConfigure) { - let surface = window.toplevel().wl_surface(); - - let waiting_for_serial = - if let Some(data) = window.user_data().get::>() { - if let ResizeState::WaitingForFinalAck(_, serial) = *data.borrow() { - Some(serial) - } else { - None - } - } else { - None - }; - - if let Some(serial) = waiting_for_serial { - // When the resize grab is released the surface - // resize state will be set to WaitingForFinalAck - // and the client will receive a configure request - // without the resize state to inform the client - // resizing has finished. Here we will wait for - // the client to acknowledge the end of the - // resizing. To check if the surface was resizing - // before sending the configure we need to use - // the current state as the received acknowledge - // will no longer have the resize state set - let is_resizing = with_states(&surface, |states| { - states - .data_map - .get::>() - .unwrap() - .lock() - .unwrap() - .current - .states - .contains(xdg_toplevel::State::Resizing) - }); - - if configure.serial >= serial && is_resizing { - let mut resize_state = window - .user_data() - .get::>() - .unwrap() - .borrow_mut(); - if let ResizeState::WaitingForFinalAck(resize_data, _) = *resize_state { - *resize_state = ResizeState::WaitingForCommit(resize_data); - } else { - unreachable!() - } - } - } - } - - pub fn apply_resize_state( - window: &Window, - mut location: Point, - size: Size, - ) -> Option> { - let mut new_location = None; - - if let Some(resize_state) = window.user_data().get::>() { - let mut resize_state = resize_state.borrow_mut(); + pub fn apply_resize_to_location(window: CosmicMapped, space: &mut Workspace) { + if let Some(mut location) = space.floating_layer.space.element_location(&window) { + let mut new_location = None; + let mut resize_state = window.resize_state.lock().unwrap(); // If the window is being resized by top or left, its location must be adjusted // accordingly. match *resize_state { - ResizeState::Resizing(resize_data) - | ResizeState::WaitingForFinalAck(resize_data, _) - | ResizeState::WaitingForCommit(resize_data) => { + Some(ResizeState::Resizing(resize_data)) + | Some(ResizeState::WaitingForCommit(resize_data)) => { let ResizeData { edges, initial_window_location, @@ -317,6 +223,7 @@ impl ResizeSurfaceGrab { } = resize_data; if edges.intersects(ResizeEdge::TOP_LEFT) { + let size = window.geometry().size; if edges.intersects(ResizeEdge::LEFT) { location.x = initial_window_location.x + (initial_window_size.w - size.w); @@ -329,15 +236,30 @@ impl ResizeSurfaceGrab { new_location = Some(location); } } - ResizeState::NotResizing => (), - } + _ => {} + }; // Finish resizing. - if let ResizeState::WaitingForCommit(_) = *resize_state { - *resize_state = ResizeState::NotResizing; + if let Some(ResizeState::WaitingForCommit(_)) = *resize_state { + if !window.is_resizing() { + *resize_state = None; + } + } + std::mem::drop(resize_state); + + if let Some(new_location) = new_location { + for (window, offset) in window.windows() { + update_reactive_popups( + &window, + new_location + offset, + space.floating_layer.space.outputs(), + ); + } + space + .floating_layer + .space + .map_element(window, new_location, false); } } - - new_location } } diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 399f8cc9..ad05c3ff 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -3,10 +3,11 @@ use smithay::{ backend::renderer::{ImportAll, Renderer}, desktop::{layer_map_for_output, space::SpaceElement, Space, Window}, - input::Seat, + input::{pointer::GrabStartData as PointerGrabStartData, Seat}, output::Output, + reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::ResizeEdge, render_elements, - utils::{Logical, Point, Rectangle}, + utils::{Logical, Point, Rectangle, Serial}, }; use std::collections::HashMap; @@ -185,33 +186,31 @@ impl FloatingLayout { } } - /* pub fn resize_request( - window: &CosmicWindow, + &mut self, + mapped: &CosmicMapped, seat: &Seat, serial: Serial, start_data: PointerGrabStartData, edges: ResizeEdge, - ) { - // it is so stupid, that we have to do this here. TODO: Refactor grabs - let workspace = state - .common - .shell - .space_for_window_mut(window.toplevel().wl_surface()) - .unwrap(); - let space = &mut workspace.space; - + ) -> Option { if let Some(pointer) = seat.get_pointer() { - let location = space.window_location(&window).unwrap(); - let size = window.geometry().size; + let location = self.space.element_location(&mapped).unwrap(); + let size = mapped.geometry().size; - let grab = - grabs::ResizeSurfaceGrab::new(start_data, window.clone(), edges, location, size); + Some(grabs::ResizeSurfaceGrab::new( + start_data, + mapped.clone(), + edges, + location, + size, + )) - pointer.set_grab(state, grab, serial, Focus::Clear); + //pointer.set_grab(state, grab, serial, Focus::Clear); + } else { + None } } - */ pub fn mapped(&self) -> impl Iterator { self.space.elements() diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 2f8c4bcd..6790d7df 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -33,7 +33,10 @@ use std::{collections::HashMap, time::Duration}; use super::{ element::CosmicMapped, focus::{FocusStack, FocusStackMut}, - layout::{floating::FloatingRenderElement, tiling::TilingRenderElement}, + layout::{ + floating::{FloatingRenderElement, ResizeSurfaceGrab}, + tiling::TilingRenderElement, + }, }; #[derive(Debug)] @@ -253,32 +256,27 @@ impl Workspace { self.fullscreen.get(output).filter(|w| w.alive()) } - /* pub fn resize_request( - state: &mut State, - surface: &WlSurface, + &mut self, + mapped: &CosmicMapped, seat: &Seat, serial: Serial, start_data: PointerGrabStartData, edges: ResizeEdge, - ) { - let workspace = state.common.shell.space_for_window_mut(surface).unwrap(); - let window = workspace - .space - .window_for_surface(surface, WindowSurfaceType::TOPLEVEL) - .unwrap() - .clone(); - - if workspace.fullscreen.values().any(|w| w == &window) { - return; + ) -> Option { + if mapped.is_fullscreen() || mapped.is_maximized() { + return None; } - if workspace.floating_layer.windows.contains(&window) { - FloatingLayout::resize_request(state, &window, seat, serial, start_data.clone(), edges) - } else if workspace.tiling_layer.windows.contains(&window) { - TilingLayout::resize_request(state, &window, seat, serial, start_data, edges) + if self.floating_layer.mapped().any(|m| m == mapped) { + self.floating_layer + .resize_request(mapped, seat, serial, start_data.clone(), edges) + } else if self.tiling_layer.mapped().any(|(_, m, _)| m == mapped) { + //self.tiling_layer.resize_request(mapped, seat, serial, start_data, edges) + None + } else { + None } } - */ pub fn toggle_tiling(&mut self, seat: &Seat) { if self.tiling_enabled { diff --git a/src/wayland/handlers/compositor.rs b/src/wayland/handlers/compositor.rs index c9935665..a0199033 100644 --- a/src/wayland/handlers/compositor.rs +++ b/src/wayland/handlers/compositor.rs @@ -147,43 +147,18 @@ impl CompositorHandler for State { self.xdg_popup_ensure_initial_configure(&popup); } - /* // at last handle some special cases, like grabs and changing layer surfaces // 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((space, window)) = - self.common - .shell - .space_for_window_mut(surface) - .and_then(|workspace| { - workspace - .space - .window_for_surface(surface, WindowSurfaceType::TOPLEVEL) - .cloned() - .map(|window| (&mut workspace.space, window)) - }) - { - let new_location = - crate::shell::layout::floating::ResizeSurfaceGrab::apply_resize_state( - &window, - space.window_location(&window).unwrap(), - window.geometry().size, + 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, ); - if let Some(location) = new_location { - space.map_window( - &window, - location, - crate::shell::layout::floating::FLOATING_INDEX, - true, - ); - for window in space.windows() { - update_reactive_popups(space, window); - } } } - */ // We need to know every potential output for importing to the right gpu and scheduling a render, // so call this only after every potential surface map operation has been done. diff --git a/src/wayland/handlers/xdg_shell/mod.rs b/src/wayland/handlers/xdg_shell/mod.rs index 083ae411..f320b129 100644 --- a/src/wayland/handlers/xdg_shell/mod.rs +++ b/src/wayland/handlers/xdg_shell/mod.rs @@ -169,8 +169,21 @@ impl XdgShellHandler for State { ) { let seat = Seat::from_resource(&seat).unwrap(); if let Some(start_data) = check_grab_preconditions(&seat, surface.wl_surface(), serial) { - if let Some(mapped) = self.common.shell.element_for_surface(surface.wl_surface()) { - // Shell::resize_request(self, mapped, &seat, serial, start_data, edges); + if let Some(mapped) = self + .common + .shell + .element_for_surface(surface.wl_surface()) + .cloned() + { + if let Some(workspace) = self.common.shell.space_for_mut(&mapped) { + if let Some(grab) = + workspace.resize_request(&mapped, &seat, serial, start_data, edges) + { + seat.get_pointer() + .unwrap() + .set_grab(self, grab, serial, Focus::Clear); + } + } } } } From 4bafce880c1185935824723d2b88b759c37a5598 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 25 Oct 2022 16:36:25 +0200 Subject: [PATCH 07/62] shell/floating: Fix window size restore on toggle --- src/shell/layout/floating/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index ad05c3ff..728ee8a4 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -77,7 +77,7 @@ impl FloatingLayout { let mut geo_updated = false; if let Some(size) = last_geometry.map(|g| g.size) { - geo_updated = win_geo.size == size; + geo_updated = win_geo.size != size; win_geo.size = size; } { @@ -124,7 +124,6 @@ impl FloatingLayout { .into() }); - // TODO: Move this into CosmicMapped, this needs to differciate between stacks and windows mapped.set_tiled(false); if geo_updated { mapped.set_size(win_geo.size); @@ -221,6 +220,7 @@ impl FloatingLayout { } pub fn refresh(&mut self) { + self.space.refresh(); for element in self .space .elements() @@ -234,7 +234,6 @@ impl FloatingLayout { let output = self.space.outputs().next().unwrap().clone(); self.map_internal(element, &output, None); } - self.space.refresh() } pub fn most_overlapped_output_for_element(&self, elem: &CosmicMapped) -> Option { From 38e31943313cbbeac13dab128b18528e9362088c Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 25 Oct 2022 17:25:22 +0200 Subject: [PATCH 08/62] shell: Fix workspaces without mapped outputs --- src/shell/mod.rs | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 491147b7..0792cdd9 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -116,24 +116,34 @@ impl WorkspaceSet { } } - fn refresh( + fn refresh<'a>( &mut self, amount: WorkspaceAmount, state: &mut WorkspaceState, toplevel_info: &mut ToplevelInfoState, + outputs: impl Iterator)>, ) { match amount { - WorkspaceAmount::Dynamic => self.ensure_last_empty(state), - WorkspaceAmount::Static(len) => self.ensure_static(len as usize, state, toplevel_info), + WorkspaceAmount::Dynamic => self.ensure_last_empty(state, outputs), + WorkspaceAmount::Static(len) => { + self.ensure_static(len as usize, state, toplevel_info, outputs) + } } self.workspaces[self.active].refresh(); } - fn ensure_last_empty(&mut self, state: &mut WorkspaceState) { + fn ensure_last_empty<'a>( + &mut self, + state: &mut WorkspaceState, + outputs: impl Iterator)>, + ) { // add empty at the end, if necessary if self.workspaces.last().unwrap().windows().next().is_some() { - self.workspaces - .push(create_workspace(&mut state.update(), &self.group, false)); + let mut workspace = create_workspace(&mut state.update(), &self.group, false); + for (output, location) in outputs { + workspace.map_output(output, location); + } + self.workspaces.push(workspace); } let len = self.workspaces.len(); @@ -153,11 +163,12 @@ impl WorkspaceSet { self.workspaces.retain(|_| *iter.next().unwrap()); } - fn ensure_static( + fn ensure_static<'a>( &mut self, amount: usize, state: &mut WorkspaceState, toplevel_info: &mut ToplevelInfoState, + outputs: impl Iterator)>, ) { if amount < self.workspaces.len() { let mut state = state.update(); @@ -189,9 +200,13 @@ impl WorkspaceSet { } else if amount > self.workspaces.len() { let mut state = state.update(); // add empty ones + let outputs = outputs.collect::>(); while amount > self.workspaces.len() { - self.workspaces - .push(create_workspace(&mut state, &self.group, false)); + let mut workspace = create_workspace(&mut state, &self.group, false); + for &(output, location) in outputs.iter() { + workspace.map_output(output, location); + } + self.workspaces.push(workspace); } } } @@ -759,11 +774,12 @@ impl Shell { match &mut self.workspaces { WorkspaceMode::OutputBound(sets) => { - for set in sets.values_mut() { + for (output, set) in sets.iter_mut() { set.refresh( self.workspace_amount, &mut self.workspace_state, &mut self.toplevel_info_state, + std::iter::once((output, (0, 0).into())), ); } } @@ -771,6 +787,7 @@ impl Shell { self.workspace_amount, &mut self.workspace_state, &mut self.toplevel_info_state, + self.outputs.iter().map(|o| (o, o.current_location())), ), } From 375d40e3887cfea397f7ddb8ae28ba68afe04b65 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 25 Oct 2022 17:42:05 +0200 Subject: [PATCH 09/62] shell: Move workspace on window move --- src/input/mod.rs | 3 ++- src/shell/mod.rs | 34 ++++++++++++++++++++++++---------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/input/mod.rs b/src/input/mod.rs index d22c64ae..bc922c51 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -341,7 +341,8 @@ impl State { 0 => 9, x => x - 1, }; - self.common.shell.move_current_window( + Shell::move_current_window( + self, seat, ¤t_output, workspace as usize, diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 0792cdd9..ea7ad230 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -866,12 +866,12 @@ impl Shell { } } - pub fn move_current_window(&mut self, seat: &Seat, output: &Output, idx: usize) { - if idx == self.workspaces.active_num(output) { + pub fn move_current_window(state: &mut State, seat: &Seat, output: &Output, idx: usize) { + if idx == state.common.shell.workspaces.active_num(output) { return; } - let old_workspace = self.workspaces.active_mut(output); + let old_workspace = state.common.shell.workspaces.active_mut(output); let maybe_window = old_workspace.focus_stack.get(seat).last().cloned(); if let Some(mapped) = maybe_window { let was_floating = old_workspace.floating_layer.unmap(&mapped); @@ -879,16 +879,19 @@ impl Shell { assert!(was_floating != was_tiling); for (toplevel, _) in mapped.windows() { - self.toplevel_info_state + state + .common + .shell + .toplevel_info_state .toplevel_leave_workspace(&toplevel, &old_workspace.handle); } let elements = old_workspace.mapped().cloned().collect::>(); std::mem::drop(old_workspace); for mapped in elements.into_iter() { - self.update_reactive_popups(&mapped); + state.common.shell.update_reactive_popups(&mapped); } - let new_workspace = self.workspaces.get_mut(idx, output).unwrap(); // checked above + let new_workspace = state.common.shell.workspaces.get_mut(idx, output).unwrap(); // checked above let focus_stack = new_workspace.focus_stack.get(&seat); if was_floating { new_workspace @@ -900,12 +903,24 @@ impl Shell { .map(mapped.clone(), &seat, focus_stack.iter()); } for (toplevel, _) in mapped.windows() { - self.toplevel_info_state + state + .common + .shell + .toplevel_info_state .toplevel_enter_workspace(&toplevel, &new_workspace.handle); } - let mut workspace_state = self.workspace_state.update(); - workspace_state.remove_workspace_state(&new_workspace.handle, WState::Hidden); + for mapped in new_workspace + .mapped() + .cloned() + .collect::>() + .into_iter() + { + state.common.shell.update_reactive_popups(&mapped); + } + + state.common.shell.activate(output, idx); + Common::set_focus(state, Some(&KeyboardFocusTarget::from(mapped)), &seat, None); } } @@ -932,5 +947,4 @@ fn init_workspace_handle<'a>( state.set_workspace_capabilities(&handle, [WorkspaceCapabilities::Activate].into_iter()); state.set_workspace_name(&handle, format!("{}", idx + 1)); state.set_workspace_coordinates(&handle, [Some(idx as u32), None, None]); - state.add_workspace_state(&handle, WState::Hidden); } From 45bd1a4bb519664718fa174553447697b5a63447 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 25 Oct 2022 17:52:18 +0200 Subject: [PATCH 10/62] shell: Adjust active workspace, when removing empty ones --- src/shell/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index ea7ad230..3a1e8300 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -161,6 +161,11 @@ impl WorkspaceSet { let mut iter = keep.iter(); self.workspaces.retain(|_| *iter.next().unwrap()); + self.active -= keep + .iter() + .take(self.active + 1) + .filter(|keep| !**keep) + .count(); } fn ensure_static<'a>( From adb60d8c7faf903ce77e27a064e6b0a3dc40b505 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 25 Oct 2022 18:18:05 +0200 Subject: [PATCH 11/62] shell/tiling: Fix endless focus loop --- src/shell/layout/tiling/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 87ee2fd1..77bab330 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -435,7 +435,7 @@ impl TilingLayout { // TODO: Rather use something like seat.current_keyboard_focus // TODO https://github.com/Smithay/smithay/pull/777 if let Some(last_active) = TilingLayout::last_active_window(tree, focus_stack) { - let (last_window, node_id) = last_active; + let (last_window, mut node_id) = last_active; // stacks may handle focus internally if last_window.handle_focus(direction) { @@ -562,6 +562,8 @@ impl TilingLayout { } } } + } else { + node_id = group.clone(); } } } From 634319a82349c863e7d0eee2a9d901d1bf29fe4d Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 25 Oct 2022 18:26:56 +0200 Subject: [PATCH 12/62] tiling: Fix for children count > 2 --- src/shell/layout/tiling/mod.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 77bab330..09bcf555 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -754,10 +754,6 @@ impl TilingLayout { geo.loc.y += outer; geo.size.w -= outer * 2; geo.size.h -= outer * 2; - - if tree.get(root).unwrap().data().geometry() == geo { - continue; - } } for node_id in tree From 3588978f6822b5f202852b7128769427bc701f80 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 25 Oct 2022 19:08:17 +0200 Subject: [PATCH 13/62] shell/tiling: Fix next_focus for different orientation --- src/shell/layout/tiling/mod.rs | 81 ++++++++++++++++------------------ 1 file changed, 39 insertions(+), 42 deletions(-) diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 09bcf555..a0cbad7a 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -435,13 +435,14 @@ impl TilingLayout { // TODO: Rather use something like seat.current_keyboard_focus // TODO https://github.com/Smithay/smithay/pull/777 if let Some(last_active) = TilingLayout::last_active_window(tree, focus_stack) { - let (last_window, mut node_id) = last_active; + let (last_window, last_node_id) = last_active; // stacks may handle focus internally if last_window.handle_focus(direction) { return None; } + let mut node_id = last_node_id.clone(); while let Some(group) = tree.get(&node_id).unwrap().parent() { let child = node_id.clone(); let group_data = tree.get(&group).unwrap().data(); @@ -511,50 +512,46 @@ impl TilingLayout { } Data::Group { .. } => { let center = { - let geo = tree.get(&child).unwrap().data().geometry(); + let geo = tree.get(&last_node_id).unwrap().data().geometry(); let mut point = geo.loc; match direction { FocusDirection::Down => { - point += Point::from((geo.size.w / 2, geo.size.h)) + point += Point::from((geo.size.w / 2 - 1, geo.size.h)) } - FocusDirection::Up => point.x += geo.size.w, - FocusDirection::Left => point.y += geo.size.h / 2, + FocusDirection::Up => point.x += geo.size.w / 2 - 1, + FocusDirection::Left => point.y += geo.size.h / 2 - 1, FocusDirection::Right => { - point += Point::from((geo.size.w, geo.size.h / 2)) + point += Point::from((geo.size.w, geo.size.h / 2 - 1)) } _ => unreachable!(), }; point.to_f64() }; + let distance = |candidate: &&NodeId| -> f64 { + let geo = tree.get(candidate).unwrap().data().geometry(); + let mut point = geo.loc; + match direction { + FocusDirection::Up => { + point += Point::from((geo.size.w / 2, geo.size.h)) + } + FocusDirection::Down => point.x += geo.size.w, + FocusDirection::Right => point.y += geo.size.h / 2, + FocusDirection::Left => { + point += Point::from((geo.size.w, geo.size.h / 2)) + } + _ => unreachable!(), + }; + let point = point.to_f64(); + ((point.x - center.x).powi(2) + (point.y - center.y).powi(2)) + .sqrt() + }; + node_id = tree .children_ids(node_id.as_ref().unwrap()) .unwrap() .min_by(|node1, node2| { - let distance = |candidate: &&NodeId| -> f64 { - let geo = - tree.get(candidate).unwrap().data().geometry(); - let mut point = geo.loc; - match direction { - FocusDirection::Up => { - point += - Point::from((geo.size.w / 2, geo.size.h)) - } - FocusDirection::Down => point.x += geo.size.w, - FocusDirection::Right => point.y += geo.size.h / 2, - FocusDirection::Left => { - point += - Point::from((geo.size.w, geo.size.h / 2)) - } - _ => unreachable!(), - }; - let point = point.to_f64(); - ((point.x - center.x).powi(2) - + (point.y - center.y).powi(2)) - .sqrt() - }; - - distance(node1).total_cmp(&distance(node2)) + distance(node1).abs().total_cmp(&distance(node2).abs()) }); } Data::Mapped { mapped, .. } => { @@ -745,7 +742,7 @@ impl TilingLayout { .map(|(output_data, tree)| (&output_data.output, tree)) { if let Some(root) = tree.root_node_id() { - let mut stack = VecDeque::new(); + let mut stack = Vec::new(); let mut geo = Some(layer_map_for_output(&output).non_exclusive_zone()); // TODO saturate? minimum? @@ -763,7 +760,7 @@ impl TilingLayout { .into_iter() { let node = tree.get_mut(&node_id).unwrap(); - let geo = stack.pop_front().unwrap_or(geo); + let geo = stack.pop().unwrap_or(geo); if let Some(geo) = geo { let data = node.data_mut(); data.update_geometry(geo); @@ -772,23 +769,23 @@ impl TilingLayout { orientation, sizes, .. } => match orientation { Orientation::Horizontal => { - let mut previous = 0; - for size in sizes { - stack.push_back(Some(Rectangle::from_loc_and_size( + let mut previous: i32 = sizes.iter().sum(); + for size in sizes.iter().rev() { + previous -= *size; + stack.push(Some(Rectangle::from_loc_and_size( (geo.loc.x, geo.loc.y + previous), (geo.size.w, *size), ))); - previous += *size; } } Orientation::Vertical => { - let mut previous = 0; - for size in sizes { - stack.push_back(Some(Rectangle::from_loc_and_size( + let mut previous: i32 = sizes.iter().sum(); + for size in sizes.iter().rev() { + previous -= *size; + stack.push(Some(Rectangle::from_loc_and_size( (geo.loc.x + previous, geo.loc.y), (*size, geo.size.h), ))); - previous += *size; } } }, @@ -803,8 +800,8 @@ impl TilingLayout { } } } else if node.data().is_group() { - stack.push_back(None); - stack.push_back(None); + stack.push(None); + stack.push(None); } } } From 01b34aadd2357923625f132b7441bd20f92a16d3 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 26 Oct 2022 15:26:07 +0200 Subject: [PATCH 14/62] shell/floating: Add interactive move grab --- src/backend/render/mod.rs | 51 +-- src/input/mod.rs | 4 +- src/shell/grabs.rs | 368 ------------------ src/shell/layout/floating/grabs/mod.rs | 5 + src/shell/layout/floating/grabs/moving.rs | 195 ++++++++++ .../floating/{grabs.rs => grabs/resize.rs} | 0 src/shell/mod.rs | 6 +- src/shell/workspace.rs | 56 ++- src/wayland/handlers/xdg_shell/mod.rs | 31 +- 9 files changed, 314 insertions(+), 402 deletions(-) delete mode 100644 src/shell/grabs.rs create mode 100644 src/shell/layout/floating/grabs/mod.rs create mode 100644 src/shell/layout/floating/grabs/moving.rs rename src/shell/layout/floating/{grabs.rs => grabs/resize.rs} (100%) diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 0ba64e8d..a7cba0f0 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -6,9 +6,12 @@ use crate::{ state::Fps, utils::prelude::*, }; -//grabs::{MoveGrabRenderElement, SeatMoveGrabState}, use crate::{ - shell::WorkspaceRenderElement, state::Common, wayland::handlers::data_device::get_dnd_icon, + shell::{ + layout::floating::SeatMoveGrabState, CosmicMappedRenderElement, WorkspaceRenderElement, + }, + state::Common, + wayland::handlers::data_device::get_dnd_icon, }; use smithay::{ @@ -45,7 +48,7 @@ smithay::render_elements! { pub CosmicElement where R: ImportAll; WorkspaceElement=WorkspaceRenderElement, CursorElement=CursorRenderElement, - //MoveGrabRenderElement=MoveGrabRenderElement, + MoveGrabRenderElement=CosmicMappedRenderElement, //#[cfg(feature = "debug")] //EguiFrame=EguiFrame, } @@ -59,7 +62,7 @@ pub fn cursor_elements( where R: Renderer + ImportAll + ImportMem, ::TextureId: Clone + 'static, - E: From>, + E: From> + From>, { let scale = output.current_scale().fractional_scale(); let mut elements = Vec::new(); @@ -73,27 +76,6 @@ where .shell .map_global_to_space(pointer.current_location().to_i32_round(), output); - /* - if let Some(grab) = seat - .user_data() - .get::() - .unwrap() - .borrow() - .as_ref() - .and_then(|state| state.render(seat, output)) - { - custom_elements.push(grab); - } - */ - - if let Some(wl_surface) = get_dnd_icon(seat) { - elements.extend( - cursor::draw_dnd_icon(&wl_surface, location.to_i32_round(), scale) - .into_iter() - .map(E::from), - ); - } - elements.extend( cursor::draw_cursor( renderer, @@ -106,6 +88,25 @@ where .into_iter() .map(E::from), ); + + if let Some(wl_surface) = get_dnd_icon(seat) { + elements.extend( + cursor::draw_dnd_icon(&wl_surface, location.to_i32_round(), scale) + .into_iter() + .map(E::from), + ); + } + + if let Some(grab_elements) = seat + .user_data() + .get::() + .unwrap() + .borrow() + .as_ref() + .map(|state| state.render::(seat, output)) + { + elements.extend(grab_elements); + } } elements diff --git a/src/input/mod.rs b/src/input/mod.rs index bc922c51..23410731 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -2,7 +2,7 @@ use crate::{ config::{Action, Config}, - shell::{focus::target::PointerFocusTarget, Workspace}, // shell::grabs::SeatMoveGrabState + shell::{focus::target::PointerFocusTarget, layout::floating::SeatMoveGrabState, Workspace}, // shell::grabs::SeatMoveGrabState state::Common, utils::prelude::*, }; @@ -112,7 +112,7 @@ pub fn add_seat( userdata.insert_if_missing(SeatId::default); userdata.insert_if_missing(Devices::default); userdata.insert_if_missing(SupressedKeys::default); - //userdata.insert_if_missing(SeatMoveGrabState::default); + userdata.insert_if_missing(SeatMoveGrabState::default); userdata.insert_if_missing(|| ActiveOutput(RefCell::new(output.clone()))); userdata.insert_if_missing(|| RefCell::new(CursorImageStatus::Default)); diff --git a/src/shell/grabs.rs b/src/shell/grabs.rs deleted file mode 100644 index d27a2f62..00000000 --- a/src/shell/grabs.rs +++ /dev/null @@ -1,368 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -use super::Shell; -use crate::utils::prelude::*; - -use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::State as WState; -use smithay::{ - backend::renderer::{ImportAll, Renderer}, - desktop::{ - draw_window, - space::{RenderElement, SpaceOutputTuple}, - Kind, Window, - }, - input::{ - pointer::{ - AxisFrame, ButtonEvent, Focus, GrabStartData as PointerGrabStartData, MotionEvent, - PointerGrab, PointerInnerHandle, - }, - Seat, - }, - output::Output, - reexports::{ - wayland_protocols::xdg::shell::server::xdg_toplevel::State as XdgState, - wayland_server::protocol::wl_surface::WlSurface, - }, - utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial}, -}; -use std::cell::RefCell; - -impl Shell { - pub fn move_request( - state: &mut State, - window: &Window, - seat: &Seat, - serial: Serial, - start_data: PointerGrabStartData, - ) { - // TODO touch grab - if let Some(pointer) = seat.get_pointer() { - let workspace = state - .common - .shell - .space_for_window_mut(window.toplevel().wl_surface()) - .unwrap(); - if workspace.fullscreen.values().any(|w| w == window) { - return; - } - - let pos = pointer.current_location(); - let output = match workspace - .space - .outputs_for_window(&window) - .into_iter() - .find(|o| o.geometry().contains(pos.to_i32_round())) - { - Some(o) => o, - None => return, - }; - let mut initial_window_location = workspace.space.window_location(&window).unwrap(); - - match &window.toplevel() { - Kind::Xdg(surface) => { - // If surface is maximized then unmaximize it - let current_state = surface.current_state(); - if current_state.states.contains(XdgState::Maximized) { - workspace - .floating_layer - .unmaximize_request(&mut workspace.space, window); - let new_size = surface.with_pending_state(|state| state.size); - let ratio = pos.x / output.geometry().size.w as f64; - - initial_window_location = new_size - .map(|size| (pos.x - (size.w as f64 * ratio), pos.y).into()) - .unwrap_or_else(|| pos) - .to_i32_round(); - } - } - }; - - let was_tiled = if workspace.tiling_layer.windows.contains(&window) { - workspace - .tiling_layer - .unmap_window(&mut workspace.space, &window); - true - } else { - workspace - .floating_layer - .unmap_window(&mut workspace.space, &window); - false - }; - - let workspace_handle = workspace.handle; - let workspace_is_empty = workspace.space.windows().next().is_none(); - - if workspace_is_empty { - state - .common - .shell - .workspace_state - .update() - .add_workspace_state(&workspace_handle, WState::Hidden); - } - state - .common - .shell - .toplevel_info_state - .toplevel_leave_workspace(&window, &workspace_handle); - state - .common - .shell - .toplevel_info_state - .toplevel_leave_output(&window, &output); - - let grab_state = MoveGrabState { - window: window.clone(), - was_tiled, - initial_cursor_location: pointer.current_location(), - initial_window_location, - initial_output_location: output.geometry().loc, - }; - let grab = MoveSurfaceGrab::new(start_data, window.clone(), seat); - - *seat - .user_data() - .get::() - .unwrap() - .borrow_mut() = Some(grab_state); - pointer.set_grab(state, grab, serial, Focus::Clear); - } - } - - fn drop_move(state: &mut State, seat: &Seat, output: &Output) { - if let Some(move_state) = seat - .user_data() - .get::() - .unwrap() - .borrow_mut() - .take() - { - let pointer = seat.get_pointer().unwrap(); - let window = move_state.window; - - if window.alive() { - let delta = pointer.current_location() - move_state.initial_cursor_location; - let window_location = (move_state.initial_window_location.to_f64() - + move_state.initial_output_location.to_f64() - - output.geometry().loc.to_f64() - + delta) - .to_i32_round(); - let surface = window.toplevel().wl_surface().clone(); - - let workspace_handle = state.common.shell.active_space(output).handle; - state - .common - .shell - .workspace_state - .update() - .remove_workspace_state(&workspace_handle, WState::Hidden); - state - .common - .shell - .toplevel_info_state - .toplevel_enter_workspace(&window, &workspace_handle); - state - .common - .shell - .toplevel_info_state - .toplevel_enter_output(&window, &output); - - let workspace = state.common.shell.active_space_mut(output); - if move_state.was_tiled { - let focus_stack = workspace.focus_stack(&seat); - workspace.tiling_layer.map_window( - &mut workspace.space, - window, - &seat, - focus_stack.iter(), - ); - } else { - workspace.floating_layer.map_window( - &mut workspace.space, - window, - &seat, - window_location, - ); - } - - Shell::set_focus(state, Some(&surface), &seat, None); - - for window in state.common.shell.active_space(output).space.windows() { - state.common.shell.update_reactive_popups(window); - } - } - } - } -} - -pub type SeatMoveGrabState = RefCell>; - -pub struct MoveGrabState { - window: Window, - was_tiled: bool, - initial_cursor_location: Point, - initial_window_location: Point, - initial_output_location: Point, -} - -pub struct MoveGrabRenderElement { - seat_id: usize, - window: Window, - window_location: Point, -} - -impl RenderElement for MoveGrabRenderElement -where - R: Renderer + ImportAll, - ::TextureId: 'static, -{ - fn id(&self) -> usize { - self.seat_id - } - - fn location(&self, scale: impl Into>) -> Point { - (self.window_location - self.window.geometry().loc.to_f64()).to_physical(scale) - } - - fn geometry(&self, scale: impl Into>) -> Rectangle { - let scale = scale.into(); - self.window - .physical_bbox_with_popups(RenderElement::::location(self, scale), scale) - } - - fn accumulated_damage( - &self, - scale: impl Into>, - for_values: Option>, - ) -> Vec> { - let scale = scale.into(); - self.window.accumulated_damage( - RenderElement::::location(self, scale), - scale, - for_values.map(|t| (t.0, t.1)), - ) - } - - fn opaque_regions( - &self, - scale: impl Into>, - ) -> Option>> { - let scale = scale.into(); - self.window - .opaque_regions(RenderElement::::location(self, scale), scale) - } - - fn draw( - &self, - renderer: &mut R, - frame: &mut ::Frame, - scale: impl Into>, - position: Point, - damage: &[Rectangle], - log: &slog::Logger, - ) -> Result<(), ::Error> { - draw_window(renderer, frame, &self.window, scale, position, damage, log) - } -} - -impl MoveGrabState { - pub fn render(&self, seat: &Seat, output: &Output) -> Option - where - I: From, - { - let cursor_at = seat.get_pointer().unwrap().current_location(); - let delta = cursor_at - self.initial_cursor_location; - let location = - self.initial_output_location.to_f64() + self.initial_window_location.to_f64() + delta; - - let mut window_geo = self.window.bbox(); - window_geo.loc += location.to_i32_round(); - if !output.geometry().intersection(window_geo).is_some() { - return None; - } - - Some(I::from(MoveGrabRenderElement { - seat_id: seat.id(), - window: self.window.clone(), - window_location: location - output.geometry().loc.to_f64(), - })) - } -} - -pub struct MoveSurfaceGrab { - window: Window, - start_data: PointerGrabStartData, - seat: Seat, -} - -impl PointerGrab for MoveSurfaceGrab { - fn motion( - &mut self, - state: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - _focus: Option<(WlSurface, Point)>, - event: &MotionEvent, - ) { - // While the grab is active, no client has pointer focus - handle.motion(state, None, event); - if !self.window.alive() { - self.ungrab(state, handle, event.serial, event.time); - } - } - - fn button( - &mut self, - state: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - event: &ButtonEvent, - ) { - handle.button(state, event); - if handle.current_pressed().is_empty() { - self.ungrab(state, handle, event.serial, event.time); - } - } - - fn axis( - &mut self, - state: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - details: AxisFrame, - ) { - handle.axis(state, details); - } - - fn start_data(&self) -> &PointerGrabStartData { - &self.start_data - } -} - -impl MoveSurfaceGrab { - pub fn new( - start_data: PointerGrabStartData, - window: Window, - seat: &Seat, - ) -> MoveSurfaceGrab { - MoveSurfaceGrab { - window, - start_data, - seat: seat.clone(), - } - } - - fn ungrab( - &mut self, - state: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - serial: Serial, - time: u32, - ) { - // No more buttons are pressed, release the grab. - let output = self.seat.active_output(); - let seat = self.seat.clone(); - - state.common.event_loop_handle.insert_idle(move |data| { - Shell::drop_move(&mut data.state, &seat, &output); - }); - handle.unset_grab(state, serial, time); - } -} diff --git a/src/shell/layout/floating/grabs/mod.rs b/src/shell/layout/floating/grabs/mod.rs new file mode 100644 index 00000000..65700de7 --- /dev/null +++ b/src/shell/layout/floating/grabs/mod.rs @@ -0,0 +1,5 @@ +mod moving; +mod resize; + +pub use self::moving::*; +pub use self::resize::*; diff --git a/src/shell/layout/floating/grabs/moving.rs b/src/shell/layout/floating/grabs/moving.rs new file mode 100644 index 00000000..96d2e73a --- /dev/null +++ b/src/shell/layout/floating/grabs/moving.rs @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::{ + shell::{ + element::{CosmicMapped, CosmicMappedRenderElement}, + focus::target::{KeyboardFocusTarget, PointerFocusTarget}, + }, + utils::prelude::*, +}; + +use smithay::{ + backend::renderer::{element::AsRenderElements, ImportAll, Renderer}, + desktop::space::SpaceElement, + input::{ + pointer::{ + AxisFrame, ButtonEvent, Focus, GrabStartData as PointerGrabStartData, MotionEvent, + PointerGrab, PointerInnerHandle, + }, + Seat, + }, + output::Output, + utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial}, +}; +use std::cell::RefCell; + +pub type SeatMoveGrabState = RefCell>; + +pub struct MoveGrabState { + window: CosmicMapped, + initial_cursor_location: Point, + initial_window_location: Point, + initial_output_location: Point, +} + +impl MoveGrabState { + pub fn render(&self, seat: &Seat, output: &Output) -> Vec + where + R: Renderer + ImportAll, + ::TextureId: 'static, + I: From>, + { + let cursor_at = seat.get_pointer().unwrap().current_location(); + let delta = cursor_at - self.initial_cursor_location; + let location = + self.initial_output_location.to_f64() + self.initial_window_location.to_f64() + delta; + + let mut window_geo = self.window.geometry(); + window_geo.loc += location.to_i32_round(); + if !output.geometry().intersection(window_geo).is_some() { + return Vec::new(); + } + + let scale = output.current_scale().fractional_scale().into(); + self.window.render_elements::( + (location.to_i32_round() - output.geometry().loc - self.window.geometry().loc) + .to_physical_precise_round(scale), + scale, + ) + } +} + +pub struct MoveSurfaceGrab { + window: CosmicMapped, + start_data: PointerGrabStartData, + seat: Seat, +} + +impl PointerGrab for MoveSurfaceGrab { + fn motion( + &mut self, + state: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + _focus: Option<(PointerFocusTarget, Point)>, + event: &MotionEvent, + ) { + // While the grab is active, no client has pointer focus + handle.motion(state, None, event); + if !self.window.alive() { + self.ungrab(state, handle, event.serial, event.time); + } + } + + fn button( + &mut self, + state: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &ButtonEvent, + ) { + handle.button(state, event); + if handle.current_pressed().is_empty() { + self.ungrab(state, handle, event.serial, event.time); + } + } + + fn axis( + &mut self, + state: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + details: AxisFrame, + ) { + handle.axis(state, details); + } + + fn start_data(&self) -> &PointerGrabStartData { + &self.start_data + } +} + +impl MoveSurfaceGrab { + pub fn new( + start_data: PointerGrabStartData, + window: CosmicMapped, + seat: &Seat, + initial_cursor_location: Point, + initial_window_location: Point, + initial_output_location: Point, + ) -> MoveSurfaceGrab { + let grab_state = MoveGrabState { + window: window.clone(), + initial_cursor_location, + initial_window_location, + initial_output_location, + }; + + *seat + .user_data() + .get::() + .unwrap() + .borrow_mut() = Some(grab_state); + + MoveSurfaceGrab { + window, + start_data, + seat: seat.clone(), + } + } + + fn ungrab( + &mut self, + state: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + serial: Serial, + time: u32, + ) { + // No more buttons are pressed, release the grab. + let output = self.seat.active_output(); + + if let Some(grab_state) = self + .seat + .user_data() + .get::() + .and_then(|s| s.borrow_mut().take()) + { + if grab_state.window.alive() { + let delta = handle.current_location() - grab_state.initial_cursor_location; + let window_location = (grab_state.initial_window_location.to_f64() + + grab_state.initial_output_location.to_f64() + - output.geometry().loc.to_f64() + + delta) + .to_i32_round(); + + let workspace_handle = state.common.shell.active_space(&output).handle; + for (window, _) in grab_state.window.windows() { + state + .common + .shell + .toplevel_info_state + .toplevel_enter_workspace(&window, &workspace_handle); + state + .common + .shell + .toplevel_info_state + .toplevel_enter_output(&window, &output); + } + + state + .common + .shell + .active_space_mut(&output) + .floating_layer + .map_internal(grab_state.window, &output, Some(window_location)); + } + } + + handle.unset_grab(state, serial, time); + if self.window.alive() { + Common::set_focus( + state, + Some(&KeyboardFocusTarget::from(self.window.clone())), + &self.seat, + Some(serial), + ) + } + } +} diff --git a/src/shell/layout/floating/grabs.rs b/src/shell/layout/floating/grabs/resize.rs similarity index 100% rename from src/shell/layout/floating/grabs.rs rename to src/shell/layout/floating/grabs/resize.rs diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 3a1e8300..fa83f184 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use std::{cell::Cell, collections::HashMap}; +use std::collections::HashMap; use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::State as WState; use smithay::{ @@ -7,7 +7,7 @@ use smithay::{ input::{pointer::MotionEvent, Seat}, output::Output, reexports::wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle}, - utils::{Logical, Point, Rectangle, SERIAL_COUNTER}, + utils::{Logical, Point, Rectangle}, wayland::{ compositor::with_states, shell::{ @@ -34,9 +34,9 @@ use crate::{ mod element; pub mod focus; -//pub mod grabs; pub mod layout; mod workspace; +pub use self::element::CosmicMappedRenderElement; pub use self::workspace::*; use self::{ element::{CosmicMapped, CosmicWindow}, diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 6790d7df..0ecb3c7b 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -1,7 +1,10 @@ use crate::{ shell::{ element::CosmicWindow, - layout::{floating::FloatingLayout, tiling::TilingLayout}, + layout::{ + floating::{FloatingLayout, MoveSurfaceGrab}, + tiling::TilingLayout, + }, }, state::State, utils::prelude::*, @@ -179,8 +182,8 @@ impl Workspace { } pub fn unmaximize_request(&mut self, window: &Window) { if self.fullscreen.values().any(|w| w == window) { + self.unfullscreen_request(window); self.floating_layer.unmaximize_request(window); - return self.unfullscreen_request(window); } } @@ -278,6 +281,55 @@ impl Workspace { } } + pub fn move_request( + &mut self, + window: &Window, + seat: &Seat, + output: &Output, + _serial: Serial, + start_data: PointerGrabStartData, + ) -> Option { + let pointer = seat.get_pointer().unwrap(); + let pos = pointer.current_location(); + + let mapped = self + .element_for_surface(window.toplevel().wl_surface())? + .clone(); + let mut initial_window_location = self.element_geometry(&mapped).unwrap().loc; + + if mapped.is_fullscreen() || mapped.is_maximized() { + // If surface is maximized then unmaximize it + self.unmaximize_request(window); + let new_size = match window.toplevel() { + Kind::Xdg(toplevel) => toplevel.with_pending_state(|state| state.size), + //_ => unreachable!(), // TODO x11 + }; + let ratio = pos.x / output.geometry().size.w as f64; + + initial_window_location = new_size + .map(|size| (pos.x - (size.w as f64 * ratio), pos.y).into()) + .unwrap_or_else(|| pos) + .to_i32_round(); + } + + let was_floating = self.floating_layer.unmap(&mapped); + //let was_tiled = self.tiling_layer.unmap(&mapped); + //assert!(was_floating != was_tiled); + + if was_floating { + Some(MoveSurfaceGrab::new( + start_data, + mapped, + seat, + pos, + initial_window_location, + output.geometry().loc, + )) + } else { + None // TODO + } + } + pub fn toggle_tiling(&mut self, seat: &Seat) { if self.tiling_enabled { for window in self diff --git a/src/wayland/handlers/xdg_shell/mod.rs b/src/wayland/handlers/xdg_shell/mod.rs index f320b129..2d87311a 100644 --- a/src/wayland/handlers/xdg_shell/mod.rs +++ b/src/wayland/handlers/xdg_shell/mod.rs @@ -154,8 +154,35 @@ impl XdgShellHandler for State { fn move_request(&mut self, surface: ToplevelSurface, seat: WlSeat, serial: Serial) { let seat = Seat::from_resource(&seat).unwrap(); if let Some(start_data) = check_grab_preconditions(&seat, surface.wl_surface(), serial) { - if let Some(mapped) = self.common.shell.element_for_surface(surface.wl_surface()) { - // Shell::move_request(self, &window, &seat, serial, start_data); + if let Some(mapped) = self + .common + .shell + .element_for_surface(surface.wl_surface()) + .cloned() + { + if let Some(workspace) = self.common.shell.space_for_mut(&mapped) { + let output = seat.active_output(); + let (window, _) = mapped + .windows() + .find(|(w, _)| matches!(w.toplevel(), Kind::Xdg(s) if s == &surface)) + .unwrap(); + if let Some(grab) = + workspace.move_request(&window, &seat, &output, serial, start_data) + { + let handle = workspace.handle; + self.common + .shell + .toplevel_info_state + .toplevel_leave_workspace(&window, &handle); + self.common + .shell + .toplevel_info_state + .toplevel_leave_output(&window, &output); + seat.get_pointer() + .unwrap() + .set_grab(self, grab, serial, Focus::Clear); + } + } } } } From 0c47631d9bf70e9f3857fc20a8c6416c4777a1f7 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 26 Oct 2022 15:42:45 +0200 Subject: [PATCH 15/62] shell/tiling: Add option to toggle orientation --- config.ron | 4 +--- src/config/mod.rs | 1 + src/input/mod.rs | 12 +++++++++++- src/shell/layout/mod.rs | 10 ++++++++++ src/shell/layout/tiling/mod.rs | 3 ++- 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/config.ron b/config.ron index f8a583c0..35a1f651 100644 --- a/config.ron +++ b/config.ron @@ -31,9 +31,7 @@ (modifiers: [Logo], key: "j"): Focus(Down), (modifiers: [Logo], key: "k"): Focus(Up), (modifiers: [Logo], key: "l"): Focus(Right), -//TODO: automatic orientation with Logo+o toggling - (modifiers: [Logo], key: "v"): Orientation(Vertical), - (modifiers: [Logo], key: "o"): Orientation(Horizontal), + (modifiers: [Logo], key: "o"): ToggleOrientation, (modifiers: [Logo], key: "y"): ToggleTiling, (modifiers: [Logo], key: "g"): ToggleWindowFloating, (modifiers: [Logo, Shift], key: "f"): Fullscreen, diff --git a/src/config/mod.rs b/src/config/mod.rs index 401e24e8..07bbe179 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -785,6 +785,7 @@ pub enum Action { Workspace(u8), MoveToWorkspace(u8), Focus(FocusDirection), + ToggleOrientation, Orientation(crate::shell::layout::Orientation), ToggleTiling, ToggleWindowFloating, diff --git a/src/input/mod.rs b/src/input/mod.rs index 23410731..e76f7539 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -373,12 +373,22 @@ impl State { workspace.fullscreen_toggle(&window, ¤t_output); } } + Action::ToggleOrientation => { + let output = seat.active_output(); + let workspace = self.common.shell.active_space_mut(&output); + let focus_stack = workspace.focus_stack.get(seat); + workspace.tiling_layer.update_orientation( + None, + &seat, + focus_stack.iter(), + ); + } Action::Orientation(orientation) => { let output = seat.active_output(); let workspace = self.common.shell.active_space_mut(&output); let focus_stack = workspace.focus_stack.get(seat); workspace.tiling_layer.update_orientation( - orientation, + Some(orientation), &seat, focus_stack.iter(), ); diff --git a/src/shell/layout/mod.rs b/src/shell/layout/mod.rs index 24353c66..83083d9f 100644 --- a/src/shell/layout/mod.rs +++ b/src/shell/layout/mod.rs @@ -16,6 +16,16 @@ pub enum Orientation { Vertical, } +impl std::ops::Not for Orientation { + type Output = Self; + fn not(self) -> Self::Output { + match self { + Orientation::Horizontal => Orientation::Vertical, + Orientation::Vertical => Orientation::Horizontal, + } + } +} + lazy_static::lazy_static! { static ref EXCEPTIONS_APPID: RegexSet = RegexSet::new(&[ r"Authy Desktop", diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index a0cbad7a..b655e2e5 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -570,7 +570,7 @@ impl TilingLayout { pub fn update_orientation<'a>( &mut self, - new_orientation: Orientation, + new_orientation: Option, seat: &Seat, focus_stack: impl Iterator + 'a, ) { @@ -589,6 +589,7 @@ impl TilingLayout { Orientation::Horizontal => last_geometry.size.h, Orientation::Vertical => last_geometry.size.w, }; + let new_orientation = new_orientation.unwrap_or(!*orientation); let new_length = match new_orientation { Orientation::Horizontal => last_geometry.size.h, Orientation::Vertical => last_geometry.size.w, From 644d53c2dae4d36bf5c8169bb0406599cf1ccac3 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 27 Oct 2022 16:11:54 +0200 Subject: [PATCH 16/62] tiling: Allow windows to be moved --- config.ron | 8 + src/config/mod.rs | 3 +- src/input/mod.rs | 10 + src/shell/layout/tiling/mod.rs | 361 +++++++++++++++++++++++- src/wayland/handlers/xdg_shell/popup.rs | 2 +- 5 files changed, 369 insertions(+), 15 deletions(-) diff --git a/config.ron b/config.ron index 35a1f651..13e3c72e 100644 --- a/config.ron +++ b/config.ron @@ -31,6 +31,14 @@ (modifiers: [Logo], key: "j"): Focus(Down), (modifiers: [Logo], key: "k"): Focus(Up), (modifiers: [Logo], key: "l"): Focus(Right), + (modifiers: [Logo, Shift], key: "Left"): Move(Left), + (modifiers: [Logo, Shift], key: "Right"): Move(Right), + (modifiers: [Logo, Shift], key: "Up"): Move(Up), + (modifiers: [Logo, Shift], key: "Down"): Move(Down), + (modifiers: [Logo, Shift], key: "h"): Move(Left), + (modifiers: [Logo, Shift], key: "j"): Move(Down), + (modifiers: [Logo, Shift], key: "k"): Move(Up), + (modifiers: [Logo, Shift], key: "l"): Move(Right), (modifiers: [Logo], key: "o"): ToggleOrientation, (modifiers: [Logo], key: "y"): ToggleTiling, (modifiers: [Logo], key: "g"): ToggleWindowFloating, diff --git a/src/config/mod.rs b/src/config/mod.rs index 07bbe179..340c4977 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ - shell::{focus::FocusDirection, Shell, WorkspaceAmount}, + shell::{focus::FocusDirection, layout::tiling::Direction, Shell, WorkspaceAmount}, state::{BackendData, Data}, }; use serde::{Deserialize, Serialize}; @@ -785,6 +785,7 @@ pub enum Action { Workspace(u8), MoveToWorkspace(u8), Focus(FocusDirection), + Move(Direction), ToggleOrientation, Orientation(crate::shell::layout::Orientation), ToggleTiling, diff --git a/src/input/mod.rs b/src/input/mod.rs index e76f7539..b9572360 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -362,6 +362,16 @@ impl State { Common::set_focus(self, Some(&target), seat, None); } } + Action::Move(direction) => { + let current_output = seat.active_output(); + let workspace = + self.common.shell.active_space_mut(¤t_output); + if let Some(_move_further) = + workspace.tiling_layer.move_current_window(direction, seat) + { + // TODO moving across workspaces/displays + } + } Action::Fullscreen => { let current_output = seat.active_output(); let workspace = diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index b655e2e5..941cc003 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -11,12 +11,13 @@ use crate::{ OutputNotMapped, }, utils::prelude::*, + wayland::handlers::xdg_shell::popup::{self, get_popup_toplevel}, }; use id_tree::{InsertBehavior, MoveBehavior, Node, NodeId, NodeIdError, RemoveBehavior, Tree}; use smithay::{ backend::renderer::{element::AsRenderElements, ImportAll, Renderer}, - desktop::{layer_map_for_output, space::SpaceElement, Window}, + desktop::{layer_map_for_output, space::SpaceElement, PopupKind, PopupManager, Window}, input::{ pointer::{Focus, GrabStartData as PointerGrabStartData}, Seat, @@ -70,6 +71,14 @@ impl Hash for OutputData { } } +#[derive(Debug, serde::Deserialize, Clone, Copy, PartialEq, Eq)] +pub enum Direction { + Left, + Right, + Up, + Down, +} + #[derive(Debug, Clone)] pub struct TilingLayout { gaps: (i32, i32), @@ -150,6 +159,15 @@ impl Data { } } + fn swap_windows(&mut self, i: usize, j: usize) { + match self { + Data::Group { sizes, .. } => { + sizes.swap(i, j); + } + Data::Mapped { .. } => panic!("Swapping windows to a leaf?"), + } + } + fn remove_window(&mut self, idx: usize) { match self { Data::Group { @@ -305,7 +323,9 @@ impl TilingLayout { Orientation::Horizontal } }; - TilingLayout::new_group(tree, &node_id, new_window, orientation) + let new_id = tree.insert(new_window, InsertBehavior::AsRoot).unwrap(); + TilingLayout::new_group(tree, &node_id, &new_id, orientation).unwrap(); + new_id } else { // nothing? then we add to the root if let Some(root_id) = tree.root_node_id().cloned() { @@ -317,12 +337,13 @@ impl TilingLayout { Orientation::Horizontal } }; - TilingLayout::new_group(tree, &root_id, new_window, orientation) + let new_id = tree.insert(new_window, InsertBehavior::AsRoot).unwrap(); + TilingLayout::new_group(tree, &root_id, &new_id, orientation).unwrap(); + new_id } else { - tree.insert(new_window, InsertBehavior::AsRoot) + tree.insert(new_window, InsertBehavior::AsRoot).unwrap() } - } - .unwrap(); + }; *window.tiling_node_id.lock().unwrap() = Some(window_id); } @@ -423,6 +444,227 @@ impl TilingLayout { None } + pub fn move_current_window<'a>( + &mut self, + direction: Direction, + seat: &Seat, + ) -> Option { + let output = seat.active_output(); + let tree = self.trees.get_mut(&output).unwrap(); + + let node_id = match TilingLayout::currently_focused_node(tree, seat) { + Some(node_id) => node_id, + None => { + return None; + } + }; + + let mut child_id = node_id.clone(); + // Without a parent to start with, just return + let og_parent = tree.get(&node_id).unwrap().parent().cloned()?; + let og_idx = tree + .children_ids(&og_parent) + .unwrap() + .position(|id| id == &child_id) + .unwrap(); + let mut maybe_parent = Some(og_parent.clone()); + + while let Some(parent) = maybe_parent { + let parent_data = tree.get(&parent).unwrap().data(); + let orientation = parent_data.orientation(); + let len = parent_data.len(); + + // which child are we? + let idx = tree + .children_ids(&parent) + .unwrap() + .position(|id| id == &child_id) + .unwrap(); + + // if the orientation does not match, we want to create a new group with our parent. + if matches!( + (orientation, direction), + (Orientation::Horizontal, Direction::Right) + | (Orientation::Horizontal, Direction::Left) + | (Orientation::Vertical, Direction::Up) + | (Orientation::Vertical, Direction::Down) + ) { + TilingLayout::new_group( + tree, + &parent, + &node_id, + match direction { + Direction::Left | Direction::Right => Orientation::Vertical, + Direction::Up | Direction::Down => Orientation::Horizontal, + }, + ) + .unwrap(); + tree.make_nth_sibling( + &node_id, + if direction == Direction::Left || direction == Direction::Up { + 0 + } else { + 1 + }, + ) + .unwrap(); + + tree.get_mut(&og_parent) + .unwrap() + .data_mut() + .remove_window(og_idx); + self.refresh(); + return None; + } + + // now if the orientation matches + + // if we are not already in this group, we just move into it (up) + if child_id != node_id { + tree.move_node(&node_id, MoveBehavior::ToParent(&parent)) + .unwrap(); + tree.make_nth_sibling( + &node_id, + if direction == Direction::Left || direction == Direction::Up { + idx + } else { + idx + 1 + }, + ) + .unwrap(); + tree.get_mut(&parent).unwrap().data_mut().add_window(idx); + tree.get_mut(&og_parent) + .unwrap() + .data_mut() + .remove_window(og_idx); + self.refresh(); + return None; + } + + // we can maybe move inside the group, if we don't run out of elements + if let Some(next_idx) = match (orientation, direction) { + (Orientation::Horizontal, Direction::Down) + | (Orientation::Vertical, Direction::Right) + if idx < (len - 1) => + { + Some(idx + 1) + } + (Orientation::Horizontal, Direction::Up) + | (Orientation::Vertical, Direction::Left) + if idx > 0 => + { + Some(idx - 1) + } + _ => None, + } { + // if we can, we need to check the next element and move "into" it (down) + let next_child_id = tree + .children_ids(&parent) + .unwrap() + .nth(next_idx) + .unwrap() + .clone(); + if tree.get(&next_child_id).unwrap().data().is_group() { + // if it is a group, we want to move into the group + tree.move_node(&node_id, MoveBehavior::ToParent(&next_child_id)) + .unwrap(); + let group_orientation = tree.get(&next_child_id).unwrap().data().orientation(); + match (group_orientation, direction) { + (Orientation::Horizontal, Direction::Down) + | (Orientation::Vertical, Direction::Right) => { + tree.make_first_sibling(&node_id).unwrap(); + tree.get_mut(&next_child_id) + .unwrap() + .data_mut() + .add_window(0); + } + (Orientation::Horizontal, Direction::Up) + | (Orientation::Vertical, Direction::Left) => { + tree.make_last_sibling(&node_id).unwrap(); + let group = tree.get_mut(&next_child_id).unwrap().data_mut(); + group.add_window(group.len()); + } + _ => { + // we want the middle + let group_len = tree.get(&next_child_id).unwrap().data().len(); + if group_len % 2 == 0 { + tree.make_nth_sibling(&node_id, group_len / 2).unwrap(); + tree.get_mut(&next_child_id) + .unwrap() + .data_mut() + .add_window(group_len / 2); + } else { + // we move again by making a new fork + let old_id = tree + .children_ids(&next_child_id) + .unwrap() + .skip(group_len / 2) + .next() + .unwrap() + .clone(); + TilingLayout::new_group( + tree, + &old_id, + &node_id, + !group_orientation, + ) + .unwrap(); + tree.make_nth_sibling( + &node_id, + if direction == Direction::Left || direction == Direction::Up { + 0 + } else { + 1 + }, + ) + .unwrap(); + } + } + }; + tree.get_mut(&og_parent) + .unwrap() + .data_mut() + .remove_window(og_idx); + } else if len == 2 && child_id == node_id { + // if we are just us two in the group, lets swap + tree.make_nth_sibling(&node_id, next_idx).unwrap(); + // also swap sizes + tree.get_mut(&og_parent) + .unwrap() + .data_mut() + .swap_windows(idx, next_idx); + } else { + // else we make a new fork + TilingLayout::new_group(tree, &next_child_id, &node_id, orientation).unwrap(); + tree.make_nth_sibling( + &node_id, + if direction == Direction::Left || direction == Direction::Up { + 1 + } else { + 0 + }, + ) + .unwrap(); + tree.get_mut(&og_parent) + .unwrap() + .data_mut() + .remove_window(og_idx); + } + self.refresh(); + return None; + } + + // We have reached the end of our parent group, try to move out even higher. + maybe_parent = tree.get(&parent).unwrap().parent().cloned(); + child_id = parent.clone(); + } + + match tree.get(&node_id).unwrap().data() { + Data::Mapped { mapped, .. } => Some(mapped.clone()), + Data::Group { .. } => None, // TODO move groups to other screens + } + } + pub fn next_focus<'a>( &mut self, direction: FocusDirection, @@ -620,6 +862,49 @@ impl TilingLayout { for dead_window in dead_windows.iter() { self.unmap_window_internal(&dead_window); } + // flatten trees + for tree in self.trees.values_mut() { + let root_id = match tree.root_node_id() { + Some(root) => root, + None => { + continue; + } + }; + for node_id in tree + .traverse_pre_order_ids(root_id) + .unwrap() + .collect::>() + .into_iter() + { + let node = tree.get(&node_id).unwrap(); + let data = node.data(); + if data.is_group() && data.len() == 1 { + // RemoveBehavior::LiftChildren sadly does not what we want: lifting them into the same place. + // So we need to fix that manually.. + let child_id = tree + .children_ids(&node_id) + .unwrap() + .cloned() + .next() + .unwrap(); + let idx = node.parent().map(|parent_id| { + tree.children_ids(&parent_id) + .unwrap() + .position(|id| id == &node_id) + .unwrap() + }); + tree.remove_node(node_id, RemoveBehavior::LiftChildren) + .unwrap(); + if let Some(idx) = idx { + tree.make_nth_sibling(&child_id, idx).unwrap(); + } else { + // additionally `RemoveBehavior::LiftChildren` doesn't work, when removing the root-node, + // even with just one child. *sigh* + tree.move_node(&child_id, MoveBehavior::ToRoot).unwrap(); + } + } + } + } TilingLayout::update_space_positions(&mut self.trees, self.gaps); } @@ -698,10 +983,58 @@ impl TilingLayout { ) } + fn currently_focused_node(tree: &mut Tree, seat: &Seat) -> Option { + let mut target = seat.get_keyboard().unwrap().current_focus()?; + + // if the focus is currently on a popup, treat it's toplevel as the target + if let KeyboardFocusTarget::Popup(popup) = target { + let toplevel_surface = match popup { + PopupKind::Xdg(xdg) => get_popup_toplevel(&xdg), + }?; + let root_id = tree.root_node_id()?; + let node = + tree.traverse_pre_order(root_id) + .unwrap() + .find(|node| match node.data() { + Data::Mapped { mapped, .. } => mapped + .windows() + .any(|(w, _)| w.toplevel().wl_surface() == &toplevel_surface), + _ => false, + })?; + + target = KeyboardFocusTarget::Element(match node.data() { + Data::Mapped { mapped, .. } => mapped.clone(), + _ => unreachable!(), + }); + } + + match target { + KeyboardFocusTarget::Element(mapped) => { + let node_id = mapped.tiling_node_id.lock().unwrap().clone()?; + let node = tree.get(&node_id).ok()?; + let data = node.data(); + if data.is_mapped(Some(&mapped)) { + return Some(node_id); + } + } + KeyboardFocusTarget::Group(window_group) => { + if window_group.output == seat.active_output() { + let node = tree.get(&window_group.node).ok()?; + if node.data().is_group() { + return Some(window_group.node); + } + } + } + _ => {} + }; + + None + } + fn new_group( tree: &mut Tree, old_id: &NodeId, - new: Node, + new_id: &NodeId, orientation: Orientation, ) -> Result { let new_group = Node::new(Data::new_group( @@ -733,7 +1066,10 @@ impl TilingLayout { if let Some(old_pos) = pos { tree.make_nth_sibling(&group_id, old_pos).unwrap(); } - tree.insert(new, InsertBehavior::UnderNode(&group_id)) + tree.move_node(new_id, MoveBehavior::ToParent(&group_id)) + .unwrap(); + + Ok(group_id) } fn update_space_positions(trees: &mut HashMap>, gaps: (i32, i32)) { @@ -864,13 +1200,12 @@ impl TilingLayout { let root_node = src.get(root_id).unwrap(); let new_node = Node::new(root_node.data().clone()); - let into_node_id = match dst.root_node_id().cloned() { - Some(root) => TilingLayout::new_group(dst, &root, new_node, orientation), - None => dst.insert(new_node, InsertBehavior::AsRoot), + let new_id = dst.insert(new_node, InsertBehavior::AsRoot).unwrap(); + if let Some(root) = dst.root_node_id().cloned() { + TilingLayout::new_group(dst, &root, &new_id, orientation).unwrap(); } - .unwrap(); - stack.push((root_id.clone(), into_node_id)); + stack.push((root_id.clone(), new_id)); while let Some((src_id, dst_id)) = stack.pop() { for child_id in src.children_ids(&src_id).unwrap() { let src_node = src.get(&child_id).unwrap(); diff --git a/src/wayland/handlers/xdg_shell/popup.rs b/src/wayland/handlers/xdg_shell/popup.rs index 1e8b067e..a677ea24 100644 --- a/src/wayland/handlers/xdg_shell/popup.rs +++ b/src/wayland/handlers/xdg_shell/popup.rs @@ -317,7 +317,7 @@ fn get_anchor_point(positioner: &PositionerState) -> Point { .into() } -fn get_popup_toplevel(popup: &PopupSurface) -> Option { +pub fn get_popup_toplevel(popup: &PopupSurface) -> Option { let mut parent = popup.get_parent_surface()?; while get_role(&parent) == Some(XDG_POPUP_ROLE) { parent = with_states(&parent, |states| { From ff32f48f39160b4fb5983b362a992ecc2fdfa4b2 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 27 Oct 2022 20:40:55 +0200 Subject: [PATCH 17/62] shell/tiling: Allow tiled windows to be resized --- src/shell/grabs.rs | 107 +++++++++++++ src/shell/layout/floating/grabs/resize.rs | 39 +---- src/shell/layout/floating/mod.rs | 2 +- src/shell/layout/tiling/grabs.rs | 185 +++++++++++++++++++--- src/shell/layout/tiling/mod.rs | 122 ++++++-------- src/shell/mod.rs | 1 + src/shell/workspace.rs | 16 +- 7 files changed, 339 insertions(+), 133 deletions(-) create mode 100644 src/shell/grabs.rs diff --git a/src/shell/grabs.rs b/src/shell/grabs.rs new file mode 100644 index 00000000..ac5f6877 --- /dev/null +++ b/src/shell/grabs.rs @@ -0,0 +1,107 @@ +use smithay::{ + input::pointer::{ + AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, + PointerInnerHandle, + }, + reexports::wayland_protocols::xdg::shell::server::xdg_toplevel, + utils::{Logical, Point}, +}; + +use crate::state::State; + +use super::{ + focus::target::PointerFocusTarget, + layout::{floating::ResizeSurfaceGrab, tiling::ResizeForkGrab}, +}; + +bitflags::bitflags! { + pub struct ResizeEdge: u32 { + const TOP = 0b0001; + const BOTTOM = 0b0010; + const LEFT = 0b0100; + const RIGHT = 0b1000; + + const TOP_LEFT = Self::TOP.bits | Self::LEFT.bits; + const BOTTOM_LEFT = Self::BOTTOM.bits | Self::LEFT.bits; + + const TOP_RIGHT = Self::TOP.bits | Self::RIGHT.bits; + const BOTTOM_RIGHT = Self::BOTTOM.bits | Self::RIGHT.bits; + } +} + +impl From for ResizeEdge { + #[inline] + fn from(x: xdg_toplevel::ResizeEdge) -> Self { + Self::from_bits(x.into()).unwrap() + } +} + +impl From for xdg_toplevel::ResizeEdge { + #[inline] + fn from(x: ResizeEdge) -> Self { + Self::try_from(x.bits()).unwrap() + } +} + +pub enum ResizeGrab { + Floating(ResizeSurfaceGrab), + Tiling(ResizeForkGrab), +} + +impl From for ResizeGrab { + fn from(grab: ResizeSurfaceGrab) -> Self { + ResizeGrab::Floating(grab) + } +} + +impl From for ResizeGrab { + fn from(grab: ResizeForkGrab) -> Self { + ResizeGrab::Tiling(grab) + } +} + +impl PointerGrab for ResizeGrab { + fn motion( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + focus: Option<(PointerFocusTarget, Point)>, + event: &MotionEvent, + ) { + match self { + ResizeGrab::Floating(grab) => grab.motion(data, handle, focus, event), + ResizeGrab::Tiling(grab) => grab.motion(data, handle, focus, event), + } + } + + fn button( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &ButtonEvent, + ) { + match self { + ResizeGrab::Floating(grab) => grab.button(data, handle, event), + ResizeGrab::Tiling(grab) => grab.button(data, handle, event), + } + } + + fn axis( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + details: AxisFrame, + ) { + match self { + ResizeGrab::Floating(grab) => grab.axis(data, handle, details), + ResizeGrab::Tiling(grab) => grab.axis(data, handle, details), + } + } + + fn start_data(&self) -> &PointerGrabStartData { + match self { + ResizeGrab::Floating(grab) => grab.start_data(), + ResizeGrab::Tiling(grab) => grab.start_data(), + } + } +} diff --git a/src/shell/layout/floating/grabs/resize.rs b/src/shell/layout/floating/grabs/resize.rs index 8ff20f92..d228d0ee 100644 --- a/src/shell/layout/floating/grabs/resize.rs +++ b/src/shell/layout/floating/grabs/resize.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ - shell::{element::CosmicMapped, focus::target::PointerFocusTarget}, + shell::{element::CosmicMapped, focus::target::PointerFocusTarget, grabs::ResizeEdge}, utils::prelude::*, }; use smithay::{ @@ -10,39 +10,8 @@ use smithay::{ AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle, }, - reexports::wayland_protocols::xdg::shell::server::xdg_toplevel, utils::{IsAlive, Logical, Point, Size}, }; -use std::convert::TryFrom; - -bitflags::bitflags! { - pub struct ResizeEdge: u32 { - const TOP = 0b0001; - const BOTTOM = 0b0010; - const LEFT = 0b0100; - const RIGHT = 0b1000; - - const TOP_LEFT = Self::TOP.bits | Self::LEFT.bits; - const BOTTOM_LEFT = Self::BOTTOM.bits | Self::LEFT.bits; - - const TOP_RIGHT = Self::TOP.bits | Self::RIGHT.bits; - const BOTTOM_RIGHT = Self::BOTTOM.bits | Self::RIGHT.bits; - } -} - -impl From for ResizeEdge { - #[inline] - fn from(x: xdg_toplevel::ResizeEdge) -> Self { - Self::from_bits(x.into()).unwrap() - } -} - -impl From for xdg_toplevel::ResizeEdge { - #[inline] - fn from(x: ResizeEdge) -> Self { - Self::try_from(x.bits()).unwrap() - } -} /// Information about the resize operation. #[derive(Debug, Clone, Copy, Eq, PartialEq)] @@ -185,12 +154,12 @@ impl ResizeSurfaceGrab { pub fn new( start_data: PointerGrabStartData, mapped: CosmicMapped, - edges: xdg_toplevel::ResizeEdge, + edges: ResizeEdge, initial_window_location: Point, initial_window_size: Size, ) -> ResizeSurfaceGrab { let resize_state = ResizeState::Resizing(ResizeData { - edges: edges.into(), + edges, initial_window_location, initial_window_size, }); @@ -200,7 +169,7 @@ impl ResizeSurfaceGrab { ResizeSurfaceGrab { start_data, window: mapped, - edges: edges.into(), + edges, initial_window_size, last_window_size: initial_window_size, } diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 728ee8a4..0b34644d 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -5,7 +5,6 @@ use smithay::{ desktop::{layer_map_for_output, space::SpaceElement, Space, Window}, input::{pointer::GrabStartData as PointerGrabStartData, Seat}, output::Output, - reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::ResizeEdge, render_elements, utils::{Logical, Point, Rectangle, Serial}, }; @@ -14,6 +13,7 @@ use std::collections::HashMap; use crate::{ shell::{ element::{CosmicMapped, CosmicMappedRenderElement}, + grabs::ResizeEdge, OutputNotMapped, }, state::State, diff --git a/src/shell/layout/tiling/grabs.rs b/src/shell/layout/tiling/grabs.rs index a12f5a8e..b23c4965 100644 --- a/src/shell/layout/tiling/grabs.rs +++ b/src/shell/layout/tiling/grabs.rs @@ -1,23 +1,52 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::{shell::layout::Orientation, utils::prelude::*}; -use atomic_float::AtomicF64; +use crate::{ + shell::{focus::target::PointerFocusTarget, layout::Orientation}, + utils::prelude::*, +}; +use id_tree::NodeId; use smithay::{ input::pointer::{ AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle, }, - reexports::wayland_server::protocol::wl_surface::WlSurface, - utils::{Logical, Point, Size}, + output::{Output, WeakOutput}, + utils::{Logical, Point}, }; -use std::sync::{atomic::Ordering, Arc}; + +use super::Data; pub struct ResizeForkGrab { - pub start_data: PointerGrabStartData, - pub orientation: Orientation, - pub initial_size: Size, - pub initial_ratio: f64, - pub ratio: Arc, + start_data: PointerGrabStartData, + idx: usize, + initial_size_upleft: i32, + initial_size_downright: i32, + node: NodeId, + output: WeakOutput, +} + +impl ResizeForkGrab { + pub fn new( + start_data: PointerGrabStartData, + node: NodeId, + output: &Output, + data: &Data, + idx: usize, + ) -> ResizeForkGrab { + let sizes = match data { + Data::Group { ref sizes, .. } => sizes, + _ => panic!("Resizing without a group?!?"), + }; + + ResizeForkGrab { + start_data, + idx, + initial_size_upleft: sizes.iter().take(idx + 1).sum(), + initial_size_downright: sizes.iter().skip(idx + 1).sum(), + node, + output: output.downgrade(), + } + } } impl PointerGrab for ResizeForkGrab { @@ -25,21 +54,139 @@ impl PointerGrab for ResizeForkGrab { &mut self, data: &mut State, handle: &mut PointerInnerHandle<'_, State>, - _focus: Option<(WlSurface, Point)>, + _focus: Option<(PointerFocusTarget, Point)>, event: &MotionEvent, ) { // While the grab is active, no client has pointer focus handle.motion(data, None, event); let delta = event.location - self.start_data.location; - let delta = match self.orientation { - Orientation::Vertical => delta.x / self.initial_size.w as f64, - Orientation::Horizontal => delta.y / self.initial_size.h as f64, - }; - self.ratio.store( - 0.9f64.min(0.1f64.max(self.initial_ratio + delta)), - Ordering::SeqCst, - ); + + if let Some(output) = self.output.upgrade() { + let tiling_layer = &mut data.common.shell.active_space_mut(&output).tiling_layer; + if let Some(tree) = tiling_layer.trees.get_mut(&output) { + if tree.get(&self.node).is_ok() { + let orientation = tree.get(&self.node).unwrap().data().orientation(); + let delta = match orientation { + Orientation::Vertical => delta.x, + Orientation::Horizontal => delta.y, + } + .round() as i32; + + let upleft_node_id = + match tree.children_ids(&self.node).unwrap().skip(self.idx).next() { + Some(elem) => elem, + None => { + return; + } + }; + let downright_node_id = match tree + .children_ids(&self.node) + .unwrap() + .skip(self.idx + 1) + .next() + { + Some(elem) => elem, + None => { + return; + } + }; + + let next_mapped = |mut node| loop { + if let Some(node_id) = node { + match tree.get(&node_id).unwrap().data() { + Data::Group { orientation: o, .. } if o == &orientation => { + node = tree.children_ids(&node_id).unwrap().last().cloned(); + } + _ => { + break node_id; + } + } + } else { + unreachable!() + } + }; + let upleft_mapped_id = next_mapped(Some(upleft_node_id.clone())); + let downright_mapped_id = next_mapped(Some(downright_node_id.clone())); + + let new_upleft_size = self.initial_size_upleft + delta; + let new_downright_size = self.initial_size_downright - delta; + let new_upleft_mapped_size = match orientation { + Orientation::Horizontal => { + tree.get(&upleft_mapped_id) + .unwrap() + .data() + .geometry() + .size + .h + } + Orientation::Vertical => { + tree.get(&upleft_mapped_id) + .unwrap() + .data() + .geometry() + .size + .w + } + } + delta; + let new_downright_mapped_size = match orientation { + Orientation::Horizontal => { + tree.get(&downright_mapped_id) + .unwrap() + .data() + .geometry() + .size + .h + } + Orientation::Vertical => { + tree.get(&downright_mapped_id) + .unwrap() + .data() + .geometry() + .size + .w + } + } - delta; + + if new_upleft_mapped_size > 100 && new_downright_mapped_size > 100 { + // lets update + { + let node = tree.get_mut(&self.node).unwrap(); + let data = node.data_mut(); + match data { + Data::Group { sizes, .. } => { + sizes[self.idx] = new_upleft_size; + sizes[self.idx + 1] = new_downright_size; + } + _ => unreachable!(), + }; + } + for (mapped_id, mapped_size) in &[ + (upleft_mapped_id, new_upleft_mapped_size), + (downright_mapped_id, new_downright_mapped_size), + ] { + let parent = tree.get(mapped_id).unwrap().parent().cloned().unwrap(); + if parent != self.node { + let idx = tree + .children_ids(&parent) + .unwrap() + .position(|id| id == mapped_id) + .unwrap(); + let node = tree.get_mut(&parent).unwrap(); + let data = node.data_mut(); + match data { + Data::Group { sizes, .. } => { + sizes[idx] = *mapped_size; + } + _ => unreachable!(), + }; + } + } + return tiling_layer.refresh(); + } + } + } + } } fn button( diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 941cc003..d716944f 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -7,37 +7,27 @@ use crate::{ target::{KeyboardFocusTarget, WindowGroup}, FocusDirection, }, + grabs::ResizeEdge, layout::Orientation, OutputNotMapped, }, utils::prelude::*, - wayland::handlers::xdg_shell::popup::{self, get_popup_toplevel}, + wayland::handlers::xdg_shell::popup::get_popup_toplevel, }; use id_tree::{InsertBehavior, MoveBehavior, Node, NodeId, NodeIdError, RemoveBehavior, Tree}; use smithay::{ backend::renderer::{element::AsRenderElements, ImportAll, Renderer}, - desktop::{layer_map_for_output, space::SpaceElement, PopupKind, PopupManager, Window}, - input::{ - pointer::{Focus, GrabStartData as PointerGrabStartData}, - Seat, - }, - output::{Output, WeakOutput}, + desktop::{layer_map_for_output, space::SpaceElement, PopupKind, Window}, + input::{pointer::GrabStartData as PointerGrabStartData, Seat}, + output::Output, render_elements, utils::{IsAlive, Logical, Point, Rectangle, Scale, Serial}, }; -use std::{ - borrow::Borrow, - cell::RefCell, - collections::{HashMap, VecDeque}, - hash::Hash, - sync::{atomic::AtomicBool, Arc}, -}; +use std::{borrow::Borrow, collections::HashMap, hash::Hash, sync::Arc}; -/* mod grabs; pub use self::grabs::*; -*/ #[derive(Debug, Clone)] struct OutputData { @@ -908,68 +898,58 @@ impl TilingLayout { TilingLayout::update_space_positions(&mut self.trees, self.gaps); } - /* pub fn resize_request( - window: &CosmicWindow, - seat: &Seat, - serial: Serial, + &self, + mapped: &CosmicMapped, + _seat: &Seat, + _serial: Serial, start_data: PointerGrabStartData, edges: ResizeEdge, - ) { - // it is so stupid, that we have to do this here. TODO: Refactor grabs - let workspace = state - .common - .shell - .space_for_window_mut(window.toplevel().wl_surface()) - .unwrap(); - let space = &mut workspace.space; - let trees = &mut workspace.tiling_layer.trees; + ) -> Option { + let (output, mut node_id) = self.trees.iter().find_map(|(output, tree)| { + let root_id = tree.root_node_id()?; + tree.traverse_pre_order_ids(root_id) + .unwrap() + .find(|id| tree.get(id).unwrap().data().is_mapped(Some(mapped))) + .map(|id| (&output.output, id)) + })?; - if let Some(pointer) = seat.get_pointer() { - if let Some(info) = window.user_data().get::>() { - let output = info.borrow().output; - let tree = TilingLayout::active_tree(trees, output); - let mut node_id = info.borrow().node.clone(); - - while let Some((fork, child)) = TilingLayout::find_fork(tree, node_id) { - if let &Data::Fork { - ref orientation, - ref ratio, - } = tree.get(&fork).unwrap().data() - { - // found a fork - // which child are we? - let first = tree.children_ids(&fork).unwrap().next() == Some(&child); - match (first, orientation, edges) { - (true, Orientation::Horizontal, ResizeEdge::Bottom) - | (false, Orientation::Horizontal, ResizeEdge::Top) - | (true, Orientation::Vertical, ResizeEdge::Right) - | (false, Orientation::Vertical, ResizeEdge::Left) => { - let output = space.outputs().nth(output).cloned(); - if let Some(output) = output { - let grab = ResizeForkGrab { - start_data, - orientation: *orientation, - initial_ratio: ratio.load(Ordering::SeqCst), - initial_size: layer_map_for_output(&output) - .non_exclusive_zone() - .size, - ratio: ratio.clone(), - }; - - pointer.set_grab(state, grab, serial, Focus::Clear); - } - return; - } - _ => {} // continue iterating - } - } - node_id = fork; - } + let tree = self.trees.get(output).unwrap(); + while let Some(group_id) = tree.get(&node_id).unwrap().parent() { + let orientation = tree.get(group_id).unwrap().data().orientation(); + if !((orientation == Orientation::Vertical + && (edges.contains(ResizeEdge::LEFT) || edges.contains(ResizeEdge::RIGHT))) + || (orientation == Orientation::Horizontal + && (edges.contains(ResizeEdge::TOP) || edges.contains(ResizeEdge::BOTTOM)))) + { + node_id = group_id.clone(); + continue; } + + let node_idx = tree + .children_ids(group_id) + .unwrap() + .position(|id| id == &node_id) + .unwrap(); + let idx = match edges { + x if x.intersects(ResizeEdge::TOP_LEFT) => node_idx - 1, + _ => node_idx, + }; + if idx > tree.get(&group_id).unwrap().data().len() { + return None; + } + + return Some(ResizeForkGrab::new( + start_data, + group_id.clone(), + output, + tree.get(&group_id).unwrap().data(), + idx, + )); } + + None } - */ fn last_active_window<'a>( tree: &mut Tree, diff --git a/src/shell/mod.rs b/src/shell/mod.rs index fa83f184..1be6cd36 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -34,6 +34,7 @@ use crate::{ mod element; pub mod focus; +pub mod grabs; pub mod layout; mod workspace; pub use self::element::CosmicMappedRenderElement; diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 0ecb3c7b..7120cdcc 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -36,10 +36,8 @@ use std::{collections::HashMap, time::Duration}; use super::{ element::CosmicMapped, focus::{FocusStack, FocusStackMut}, - layout::{ - floating::{FloatingRenderElement, ResizeSurfaceGrab}, - tiling::TilingRenderElement, - }, + grabs::ResizeGrab, + layout::{floating::FloatingRenderElement, tiling::TilingRenderElement}, }; #[derive(Debug)] @@ -266,16 +264,20 @@ impl Workspace { serial: Serial, start_data: PointerGrabStartData, edges: ResizeEdge, - ) -> Option { + ) -> Option { if mapped.is_fullscreen() || mapped.is_maximized() { return None; } + + let edges = edges.into(); if self.floating_layer.mapped().any(|m| m == mapped) { self.floating_layer .resize_request(mapped, seat, serial, start_data.clone(), edges) + .map(Into::into) } else if self.tiling_layer.mapped().any(|(_, m, _)| m == mapped) { - //self.tiling_layer.resize_request(mapped, seat, serial, start_data, edges) - None + self.tiling_layer + .resize_request(mapped, seat, serial, start_data, edges) + .map(Into::into) } else { None } From dd100d65e43b3bec598f2caef3a76b1a8f0cae23 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 28 Oct 2022 18:34:08 +0200 Subject: [PATCH 18/62] wayland: Add screencopy protocol handler --- Cargo.lock | 81 ++-- Cargo.toml | 8 +- src/wayland/protocols/mod.rs | 1 + src/wayland/protocols/screencopy.rs | 602 ++++++++++++++++++++++++++++ 4 files changed, 655 insertions(+), 37 deletions(-) create mode 100644 src/wayland/protocols/screencopy.rs diff --git a/Cargo.lock b/Cargo.lock index df0d2da0..39f9d03b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -309,7 +309,7 @@ dependencies = [ "smithay-egui", "thiserror", "wayland-backend", - "wayland-scanner 0.30.0-beta.10", + "wayland-scanner 0.30.0-beta.12", "xcursor", "xdg", "xkbcommon 0.4.1", @@ -318,12 +318,12 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols?branch=main#3ff11df30ef551e1ccbdcb091930fe0d72266195" +source = "git+https://github.com/pop-os/cosmic-protocols?branch=screencopy#e96daeceac966b6d21c79f2ebcc4b9934a69fa75" dependencies = [ "bitflags", "wayland-backend", - "wayland-protocols 0.30.0-beta.10", - "wayland-scanner 0.30.0-beta.10", + "wayland-protocols 0.30.0-beta.12", + "wayland-scanner 0.30.0-beta.12", "wayland-server", ] @@ -1048,6 +1048,19 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nix" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "libc", + "memoffset", +] + [[package]] name = "nohash-hasher" version = "0.2.0" @@ -1490,11 +1503,12 @@ checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "smithay" version = "0.3.0" -source = "git+https://github.com/Smithay//smithay?rev=625cbca5#625cbca54981021000d5a33c1bdc711056ff6000" +source = "git+https://github.com/Smithay//smithay?rev=0c7dbfa8#0c7dbfa830496df064a721f9cbbff6b474e6a268" dependencies = [ "appendlist", "bitflags", "calloop", + "cc", "cgmath", "downcast-rs", "drm", @@ -1511,6 +1525,7 @@ dependencies = [ "libseat", "nix 0.24.2", "once_cell", + "pkg-config", "rand", "scan_fmt", "slog", @@ -1520,11 +1535,11 @@ dependencies = [ "udev", "wayland-backend", "wayland-egl", - "wayland-protocols 0.30.0-beta.10", + "wayland-protocols 0.30.0-beta.12", "wayland-protocols-misc", "wayland-protocols-wlr", "wayland-server", - "wayland-sys 0.30.0-beta.10", + "wayland-sys 0.30.0-beta.12", "winit", "x11rb", "xkbcommon 0.5.0", @@ -1791,17 +1806,17 @@ checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "wayland-backend" -version = "0.1.0-beta.10" +version = "0.1.0-beta.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30c089c872513a8dcc2b5cc26209d83f5ffab1841d8c7973edf2583620ebc018" +checksum = "2fe32234ad38fee0755aeec26db486a4b788dd3cc2c9dc86ceda31bb38a53f32" dependencies = [ "cc", "downcast-rs", "io-lifetimes", - "nix 0.24.2", + "nix 0.25.0", "scoped-tls", "smallvec", - "wayland-sys 0.30.0-beta.10", + "wayland-sys 0.30.0-beta.12", ] [[package]] @@ -1845,13 +1860,13 @@ dependencies = [ [[package]] name = "wayland-egl" -version = "0.30.0-beta.10" +version = "0.30.0-beta.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6cb5591d75142e1b180691df4d27f164a09abbd6efc7cba9b119ca40646809c" +checksum = "cfc1309ddfeabd942c09c21e9db2ed3de81b84d0ea7ebcdd2e503a0be0fe9c5f" dependencies = [ "thiserror", "wayland-backend", - "wayland-sys 0.30.0-beta.10", + "wayland-sys 0.30.0-beta.12", ] [[package]] @@ -1868,39 +1883,39 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.30.0-beta.10" +version = "0.30.0-beta.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60406a2fb43ff670ece4976cdd0537bbcd1e583b54c3cd9a1b61065e966df0a7" +checksum = "ca770dc814b3c93db1b4ba12a5bdfe899f8d68f9b4b06fc31e53959261cd0c39" dependencies = [ "bitflags", "wayland-backend", - "wayland-scanner 0.30.0-beta.10", + "wayland-scanner 0.30.0-beta.12", "wayland-server", ] [[package]] name = "wayland-protocols-misc" -version = "0.1.0-beta.10" +version = "0.1.0-beta.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653c3e200343bcfae719b668959efb9475bf9fcf43591cccfead308d32d67049" +checksum = "25bfcd2c16bed3540d1c5662d97708ac4f14d92b7b5c52c85860cdc17a96a126" dependencies = [ "bitflags", "wayland-backend", - "wayland-protocols 0.30.0-beta.10", - "wayland-scanner 0.30.0-beta.10", + "wayland-protocols 0.30.0-beta.12", + "wayland-scanner 0.30.0-beta.12", "wayland-server", ] [[package]] name = "wayland-protocols-wlr" -version = "0.1.0-beta.10" +version = "0.1.0-beta.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1cfe42e0cfcce47a421844ee82e49316cfab3ec6bcbb3a1076a0a2dd6c8d61e" +checksum = "8795f1c689bce3845292c1d84b8866fa65d1d1845e76769b13f71cadeb8b5853" dependencies = [ "bitflags", "wayland-backend", - "wayland-protocols 0.30.0-beta.10", - "wayland-scanner 0.30.0-beta.10", + "wayland-protocols 0.30.0-beta.12", + "wayland-scanner 0.30.0-beta.12", "wayland-server", ] @@ -1917,9 +1932,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.30.0-beta.10" +version = "0.30.0-beta.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1398566f240c3ef845bdfd310c8ee6ba800d4db695d007f23f2190eeedfff93" +checksum = "87def40ed0bc26c3eff1498812543b6d55bbd13b93e36d368052b7390a57c4ac" dependencies = [ "proc-macro2", "quick-xml", @@ -1929,16 +1944,16 @@ dependencies = [ [[package]] name = "wayland-server" -version = "0.30.0-beta.10" +version = "0.30.0-beta.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030557c71dfa3d7e0d0b5975bbdfee2eee319fde3e4c420cde63474c61331347" +checksum = "8d54b4a800b230f4fb0f42cae245cc0aaa02bf14bf7c1c3a5e1a822d05fd2cb3" dependencies = [ "bitflags", "downcast-rs", - "nix 0.24.2", + "nix 0.25.0", "thiserror", "wayland-backend", - "wayland-scanner 0.30.0-beta.10", + "wayland-scanner 0.30.0-beta.12", ] [[package]] @@ -1954,9 +1969,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.30.0-beta.10" +version = "0.30.0-beta.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bacc6b678c3350910e5ad6c057b7503666080952c4ece0a7e8958fd33937b1f5" +checksum = "1117fe4570fe063122ba2b1b1e39e56fb1a73921d395f9288af06af0dd1c7f55" dependencies = [ "dlib", "libc", diff --git a/Cargo.toml b/Cargo.toml index 43310f4a..71f4ce9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,9 +29,9 @@ indexmap = "1.8.0" xdg = "^2.1" ron = "0.7" libsystemd = "0.5" -wayland-backend = "=0.1.0-beta.10" -wayland-scanner = "=0.30.0-beta.10" -cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", branch = "main", default-features = false, features = ["server"] } +wayland-backend = "=0.1.0-beta.12" +wayland-scanner = "=0.30.0-beta.12" +cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", branch = "screencopy", default-features = false, features = ["server"] } [dependencies.smithay] version = "0.3" @@ -61,4 +61,4 @@ debug = true lto = "fat" [patch."https://github.com/Smithay/smithay.git"] -smithay = { git = "https://github.com/Smithay//smithay", rev = "625cbca5" } +smithay = { git = "https://github.com/Smithay//smithay", rev = "0c7dbfa8" } diff --git a/src/wayland/protocols/mod.rs b/src/wayland/protocols/mod.rs index 069f16c5..e84f5ad1 100644 --- a/src/wayland/protocols/mod.rs +++ b/src/wayland/protocols/mod.rs @@ -3,6 +3,7 @@ pub mod drm; //pub mod export_dmabuf; pub mod output_configuration; +pub mod screencopy; pub mod toplevel_info; pub mod toplevel_management; pub mod workspace; diff --git a/src/wayland/protocols/screencopy.rs b/src/wayland/protocols/screencopy.rs new file mode 100644 index 00000000..acd51141 --- /dev/null +++ b/src/wayland/protocols/screencopy.rs @@ -0,0 +1,602 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use std::{ + sync::{Arc, Mutex}, + time::Duration, +}; + +use cosmic_protocols::screencopy::v1::server::{ + zcosmic_screencopy_manager_v1::{self, CursorMode, ZcosmicScreencopyManagerV1}, + zcosmic_screencopy_session_v1::{self, FailureReason, InputType, ZcosmicScreencopySessionV1}, +}; +use smithay::{ + backend::{ + allocator::Fourcc as DrmFourcc, + drm::{DrmNode, NodeType}, + }, + desktop::Window, + reexports::wayland_server::{ + protocol::{ + wl_buffer::WlBuffer, wl_output::WlOutput, wl_seat::WlSeat, wl_shm::Format as ShmFormat, + }, + Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, + }, + utils::{user_data::UserDataMap, Buffer, Point, Rectangle, Size, Transform}, +}; +use wayland_backend::{protocol::WEnum, server::GlobalId}; + +use super::{ + toplevel_info::window_from_handle, + workspace::{WorkspaceHandle, WorkspaceHandler}, +}; + +/// Screencopy global state +pub struct ScreencopyState { + global: GlobalId, +} + +pub struct ScreencopyGlobalData { + cursor_modes: Vec, + filter: Box Fn(&'a Client) -> bool + Send + Sync>, +} + +impl ScreencopyState { + /// Create a new screencopy global + pub fn new( + display: &DisplayHandle, + cursor_modes: I, + client_filter: F, + ) -> ScreencopyState + where + D: GlobalDispatch + + Dispatch + + Dispatch + + ScreencopyHandler + + WorkspaceHandler + + 'static, + I: IntoIterator, + F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static, + { + ScreencopyState { + global: display.create_global::( + 1, + ScreencopyGlobalData { + cursor_modes: Vec::from_iter(cursor_modes), + filter: Box::new(client_filter), + }, + ), + } + } + + /// Returns the screencopy global id + pub fn global(&self) -> GlobalId { + self.global.clone() + } +} + +#[derive(Debug)] +pub enum BufferInfo { + Shm { + format: ShmFormat, + size: Size, + stride: u32, + }, + Dmabuf { + node: DrmNode, + format: DrmFourcc, + size: Size, + }, +} + +#[derive(Debug)] +pub struct SessionDataInnerInner { + gone: bool, + pending_buffer: Option, + aux: AuxData, +} + +impl SessionDataInnerInner { + pub fn is_cursor(&self) -> bool { + match self.aux { + AuxData::Cursor { .. } => true, + _ => false, + } + } +} + +#[derive(Debug)] +enum AuxData { + Normal { cursor_sessions: Vec }, + Cursor { seat: WlSeat }, +} + +impl AuxData { + pub fn seat(&self) -> &WlSeat { + match self { + AuxData::Cursor { seat } => seat, + _ => unreachable!("Unwrapped seat from aux data"), + } + } + + pub fn cursor_sessions(&self) -> &[CursorSession] { + match self { + AuxData::Normal { cursor_sessions } => &cursor_sessions, + _ => unreachable!("Unwrapped cursor_session from aux data"), + } + } +} + +#[derive(Debug)] +pub struct SessionDataInner { + inner: Mutex, + user_data: UserDataMap, +} + +pub type SessionData = Arc; + +#[derive(Debug, Clone)] +pub struct Session { + obj: ZcosmicScreencopySessionV1, + data: SessionData, +} + +impl PartialEq for Session { + fn eq(&self, other: &Self) -> bool { + self.obj == other.obj + } +} + +impl Session { + pub fn cursor_enter(&self, seat: &WlSeat, input_type: InputType) { + self.obj.cursor_enter(seat, input_type) + } + + pub fn cursor_info( + &self, + seat: &WlSeat, + 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, + ); + } + } + + pub fn cursor_leave(&self, seat: &WlSeat, input_type: InputType) { + self.obj.cursor_leave(seat, input_type) + } + + pub fn cursor_sessions(&self) -> impl Iterator { + self.data + .inner + .lock() + .unwrap() + .aux + .cursor_sessions() + .iter() + .cloned() + .collect::>() + .into_iter() + } + + pub fn commit_buffer( + &self, + transform: Transform, + damage: Vec>, + time: Option, + ) { + self.obj.transform(transform.into()); + for rect in damage { + self.obj.damage( + rect.loc.x as u32, + rect.loc.y as u32, + rect.size.w as u32, + rect.size.h as u32, + ); + } + if let Some(time) = time { + let tv_sec_hi = (time.as_secs() >> 32) as u32; + let tv_sec_lo = (time.as_secs() & 0xFFFFFFFF) as u32; + self.obj + .commit_time(tv_sec_hi, tv_sec_lo, time.subsec_nanos()); + } + self.obj.ready() + } + + pub fn failed(&self, reason: FailureReason) { + self.obj.failed(reason); + self.data.inner.lock().unwrap().gone = true; + } + + pub fn user_data(&self) -> &UserDataMap { + &self.data.user_data + } +} + +#[derive(Debug, Clone)] +pub struct CursorSession { + obj: ZcosmicScreencopySessionV1, + data: SessionData, +} + +impl PartialEq for CursorSession { + fn eq(&self, other: &Self) -> bool { + self.obj == other.obj + } +} + +impl CursorSession { + pub fn seat(&self) -> WlSeat { + self.data.inner.lock().unwrap().aux.seat().clone() + } + + pub fn buffer_waiting(&self) -> Option { + self.data.inner.lock().unwrap().pending_buffer.take() + } + + pub fn commit_buffer<'a>( + &self, + transform: Transform, + damage: impl Iterator> + 'a, + ) { + self.obj.transform(transform.into()); + for rect in damage { + self.obj.damage( + rect.loc.x as u32, + rect.loc.y as u32, + rect.size.w as u32, + rect.size.h as u32, + ); + } + } + + pub fn failed(&self, reason: FailureReason) { + self.obj.failed(reason); + self.data.inner.lock().unwrap().gone = true; + } + + pub fn user_data(&self) -> &UserDataMap { + &self.data.user_data + } +} + +#[derive(Debug)] +pub struct BufferParams { + pub buffer: WlBuffer, + pub node: Option, + pub age: u32, +} + +pub trait ScreencopyHandler { + fn capture_output( + &mut self, + output: WlOutput, + cursor: CursorMode, + session: Session, + ) -> Vec; + + fn capture_workspace( + &mut self, + workspace: WorkspaceHandle, + output: WlOutput, + cursor: CursorMode, + session: Session, + ) -> Vec; + + fn capture_toplevel( + &mut self, + toplevel: Window, + cursor: CursorMode, + 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); +} + +impl GlobalDispatch for ScreencopyState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + ScreencopyHandler + + WorkspaceHandler + + 'static, +{ + fn bind( + _state: &mut D, + _handle: &DisplayHandle, + _client: &Client, + resource: New, + global_data: &ScreencopyGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + let global = data_init.init(resource, ()); + for mode in &global_data.cursor_modes { + global.supported_cursor_mode(*mode); + } + } + + fn can_view(client: Client, global_data: &ScreencopyGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +fn init_session( + data_init: &mut DataInit<'_, D>, + session: New, + cursor: WEnum, +) -> Option<(Session, CursorMode)> +where + D: GlobalDispatch + + Dispatch + + Dispatch + + ScreencopyHandler + + WorkspaceHandler + + 'static, +{ + let data = Arc::new(SessionDataInner { + inner: Mutex::new(SessionDataInnerInner { + gone: false, + pending_buffer: None, + aux: AuxData::Normal { + cursor_sessions: Vec::new(), + }, + }), + 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; + } + }; + + let session = Session { obj: session, data }; + + Some((session, cursor_mode)) +} + +impl Dispatch for ScreencopyState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + ScreencopyHandler + + WorkspaceHandler + + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + _resource: &ZcosmicScreencopyManagerV1, + request: ::Request, + _data: &(), + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + zcosmic_screencopy_manager_v1::Request::CaptureOutput { + session, + output, + cursor, + } => { + let (session, cursor_mode) = match init_session(data_init, session, cursor) { + Some(result) => result, + None => { + return; + } + }; + let formats = state.capture_output(output, cursor_mode, session.clone()); + if !session.data.inner.lock().unwrap().gone { + send_formats(&session.obj, formats); + } + } + 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); + } + } + None => { + 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) { + Some(handle) => { + let formats = + state.capture_workspace(handle, output, cursor_mode, session.clone()); + if !session.data.inner.lock().unwrap().gone { + send_formats(&session.obj, formats); + } + } + None => { + session.failed(FailureReason::InvalidOutput); + return; + } + } + } + _ => {} + } + } +} + +impl Dispatch for ScreencopyState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + ScreencopyHandler + + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + resource: &ZcosmicScreencopySessionV1, + request: ::Request, + data: &SessionData, + _dhandle: &DisplayHandle, + 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; + } + } + + 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); + } + } + zcosmic_screencopy_session_v1::Request::AttachBuffer { buffer, node, age } => { + if data.inner.lock().unwrap().gone { + resource.failed(FailureReason::Unspec); + return; + } + let params = BufferParams { + buffer, + node: node.and_then(|p| DrmNode::from_path(p).ok()), + age, + }; + data.inner.lock().unwrap().pending_buffer = Some(params); + } + zcosmic_screencopy_session_v1::Request::Commit { options } => { + { + let resource_data = data.inner.lock().unwrap(); + if resource_data.is_cursor() || resource_data.gone { + resource.failed(FailureReason::Unspec); + return; + } + } + + if let Some(buffer) = data.inner.lock().unwrap().pending_buffer.take() { + let session = Session { + obj: resource.clone(), + data: data.clone(), + }; + state.buffer_attached( + session, + buffer, + options + .into_result() + .ok() + .map(|v| v.contains(zcosmic_screencopy_session_v1::Options::OnDamage)) + .unwrap_or(false), + ); + } else { + resource.failed(FailureReason::InvalidBuffer); + } + } + zcosmic_screencopy_session_v1::Request::Destroy => { + data.inner.lock().unwrap().gone = true; + } + _ => {} + } + } +} + +fn send_formats(session: &ZcosmicScreencopySessionV1, formats: Vec) { + for format in formats { + match format { + BufferInfo::Dmabuf { node, format, size } => { + if let Some(node_path) = node + .dev_path_with_type(NodeType::Render) + .or_else(|| node.dev_path()) + { + session.buffer_info( + zcosmic_screencopy_session_v1::BufferType::Dmabuf, + Some(node_path.as_os_str().to_string_lossy().into_owned()), + format as u32, + size.w as u32, + size.h as u32, + 0, + ); + } + } + BufferInfo::Shm { + format, + size, + stride, + } => session.buffer_info( + zcosmic_screencopy_session_v1::BufferType::WlShm, + None, + format as u32, + size.w as u32, + size.h as u32, + stride, + ), + } + } + + session.init_done(); +} From 5a4df346a888c56761b328e7afff3bedb9a14b77 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 3 Nov 2022 18:51:27 +0100 Subject: [PATCH 19/62] wip: screencopy --- Cargo.lock | 36 +- Cargo.toml | 9 +- src/backend/kms/mod.rs | 72 +- src/backend/render/cursor.rs | 6 +- src/backend/render/mod.rs | 138 +++- src/backend/winit.rs | 33 +- src/backend/x11.rs | 38 +- src/input/mod.rs | 64 ++ src/main.rs | 9 +- src/shell/element/mod.rs | 2 +- src/shell/element/stack.rs | 139 +++- src/shell/element/window.rs | 33 +- src/shell/layout/floating/grabs/moving.rs | 4 +- src/shell/layout/floating/mod.rs | 6 +- src/shell/mod.rs | 55 +- src/shell/workspace.rs | 32 +- src/state.rs | 73 +- src/utils/prelude.rs | 80 +- src/wayland/handlers/compositor.rs | 138 +++- src/wayland/handlers/mod.rs | 1 + src/wayland/handlers/screencopy.rs | 795 +++++++++++++++++++ src/wayland/handlers/xdg_shell/mod.rs | 15 +- src/wayland/protocols/screencopy.rs | 563 +++++++++---- src/wayland/protocols/toplevel_info.rs | 12 +- src/wayland/protocols/toplevel_management.rs | 8 +- src/wayland/protocols/workspace.rs | 12 +- 26 files changed, 2046 insertions(+), 327 deletions(-) create mode 100644 src/wayland/handlers/screencopy.rs 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(); From 9f1284b981ff388242b8f5c77bcea9b3bbffe979 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 3 Nov 2022 21:19:41 +0100 Subject: [PATCH 20/62] wayland: Working screencopy implementation --- src/state.rs | 10 +++++++++- src/wayland/handlers/screencopy.rs | 24 +++++++++++++----------- src/wayland/protocols/screencopy.rs | 26 +++++++++++++++++++++----- 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/state.rs b/src/state.rs index 4734a1f3..e04c8cc4 100644 --- a/src/state.rs +++ b/src/state.rs @@ -9,10 +9,11 @@ use crate::{ wayland::protocols::{ drm::WlDrmState, output_configuration::OutputConfigurationState, - screencopy::{BufferParams, Session as ScreencopySession}, + screencopy::{BufferParams, ScreencopyState, Session as ScreencopySession}, workspace::WorkspaceClientState, }, }; +use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_manager_v1::CursorMode; use smithay::{ backend::{ drm::DrmNode, @@ -94,6 +95,7 @@ pub struct Common { pub output_state: OutputManagerState, pub output_configuration_state: OutputConfigurationState, pub primary_selection_state: PrimarySelectionState, + pub screencopy_state: ScreencopyState, pub seat_state: SeatState, pub shm_state: ShmState, pub wl_drm_state: WlDrmState, @@ -228,6 +230,11 @@ impl State { let output_state = OutputManagerState::new_with_xdg_output::(dh); let output_configuration_state = OutputConfigurationState::new(dh, |_| true); let primary_selection_state = PrimarySelectionState::new::(dh, None); + let screencopy_state = ScreencopyState::new::( + dh, + vec![CursorMode::Embedded, CursorMode::Hidden], + |_| true, + ); // TODO: privileged let shm_state = ShmState::new::(dh, vec![], None); let seat_state = SeatState::::new(); let viewporter_state = ViewporterState::new::(dh, None); @@ -275,6 +282,7 @@ impl State { compositor_state, data_device_state, dmabuf_state, + screencopy_state, shm_state, seat_state, keyboard_shortcuts_inhibit_state, diff --git a/src/wayland/handlers/screencopy.rs b/src/wayland/handlers/screencopy.rs index 80cc9627..6706e009 100644 --- a/src/wayland/handlers/screencopy.rs +++ b/src/wayland/handlers/screencopy.rs @@ -41,8 +41,8 @@ use crate::{ utils::prelude::OutputExt, wayland::protocols::{ screencopy::{ - BufferInfo, BufferParams, CursorMode as ScreencopyCursorMode, CursorSession, - ScreencopyHandler, Session, SessionType, + delegate_screencopy, BufferInfo, BufferParams, CursorMode as ScreencopyCursorMode, + CursorSession, ScreencopyHandler, Session, SessionType, }, workspace::WorkspaceHandle, }, @@ -177,14 +177,14 @@ impl ScreencopyHandler for State { let mut formats = vec![ BufferInfo::Shm { - format: ShmFormat::Abgr8888, + format: ShmFormat::Argb8888, size, - stride: 0, + stride: size.w as u32 * 4, }, BufferInfo::Shm { - format: ShmFormat::Xbgr8888, + format: ShmFormat::Xrgb8888, size, - stride: 0, + stride: size.w as u32 * 4, }, ]; @@ -276,7 +276,7 @@ impl ScreencopyHandler for State { if let Some(BufferType::Shm) = buffer_type(¶ms.buffer) { if with_buffer_contents(¶ms.buffer, |_, info| { - info.format != ShmFormat::Abgr8888 && info.format != ShmFormat::Xbgr8888 + info.format != ShmFormat::Argb8888 && info.format != ShmFormat::Xrgb8888 }) .unwrap() { @@ -415,14 +415,14 @@ fn formats_for_output( let mut formats = vec![ BufferInfo::Shm { - format: ShmFormat::Abgr8888, + format: ShmFormat::Argb8888, size: mode, - stride: 0, + stride: mode.w as u32 * 4, }, BufferInfo::Shm { - format: ShmFormat::Xbgr8888, + format: ShmFormat::Xrgb8888, size: mode, - stride: 0, + stride: mode.w as u32 * 4, }, ]; @@ -793,3 +793,5 @@ impl UserdataExt for Window { .flatten() } } + +delegate_screencopy!(State); diff --git a/src/wayland/protocols/screencopy.rs b/src/wayland/protocols/screencopy.rs index 677ee2ea..376936ad 100644 --- a/src/wayland/protocols/screencopy.rs +++ b/src/wayland/protocols/screencopy.rs @@ -57,7 +57,7 @@ impl ScreencopyState { where D: GlobalDispatch + Dispatch - + Dispatch + + Dispatch + ScreencopyHandler + WorkspaceHandler + 'static, @@ -799,15 +799,16 @@ where data.inner.lock().unwrap().pending_buffer = Some(params); } zcosmic_screencopy_session_v1::Request::Commit { options } => { - { - let resource_data = data.inner.lock().unwrap(); + let buffer = { + let mut resource_data = data.inner.lock().unwrap(); if resource_data.is_cursor() || resource_data.gone { resource.failed(FailureReason::Unspec); return; } - } + resource_data.pending_buffer.take() + }; - if let Some(buffer) = data.inner.lock().unwrap().pending_buffer.take() { + if let Some(buffer) = buffer { let session = Session { obj: SessionResource::Alive(resource.clone()), data: data.clone(), @@ -889,3 +890,18 @@ fn send_formats(session: &SessionResource, formats: Vec) { session.init_done(); } + +macro_rules! delegate_screencopy { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_manager_v1::ZcosmicScreencopyManagerV1: $crate::wayland::protocols::screencopy::ScreencopyGlobalData + ] => $crate::wayland::protocols::screencopy::ScreencopyState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_manager_v1::ZcosmicScreencopyManagerV1: () + ] => $crate::wayland::protocols::screencopy::ScreencopyState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::ZcosmicScreencopySessionV1: $crate::wayland::protocols::screencopy::SessionData + ] => $crate::wayland::protocols::screencopy::ScreencopyState); + }; +} +pub(crate) use delegate_screencopy; From 1eef3b3d781352981577ab5f477c713e331d6af1 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 3 Nov 2022 21:19:50 +0100 Subject: [PATCH 21/62] winit: Fix double wl_outputs --- src/backend/winit.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/backend/winit.rs b/src/backend/winit.rs index e2ebfe06..3a154e9c 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -137,7 +137,6 @@ pub fn init_backend( refresh: 60_000, }; let output = Output::new(name, props, None); - let _global = output.create_global::(dh); output.add_mode(mode); output.set_preferred(mode); output.change_current_state( From 6eb7493ad761b7a38d4fc45296cfff6dab1cfbf3 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 4 Nov 2022 14:14:11 +0100 Subject: [PATCH 22/62] screencopy: Use new error types --- src/backend/render/mod.rs | 10 +++++++- src/wayland/handlers/screencopy.rs | 37 ++++++++++++++++++++++------- src/wayland/protocols/screencopy.rs | 11 +++++---- 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 6e258f12..d6a4886d 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -26,6 +26,7 @@ use smithay::{ allocator::dmabuf::Dmabuf, drm::DrmNode, renderer::{ + buffer_dimensions, damage::{ DamageTrackedRenderer, DamageTrackedRendererError as RenderError, OutputNoMode, }, @@ -36,7 +37,7 @@ use smithay::{ }, }, output::Output, - utils::{Physical, Rectangle}, + utils::{Physical, Rectangle, Transform}, }; pub mod cursor; @@ -254,6 +255,13 @@ where if let Some((source, buffers)) = screencopy { if res.is_ok() { for (session, params) in buffers { + let mode = output.current_mode().unwrap().size; + let buffer_size = buffer_dimensions(¶ms.buffer).unwrap(); + if mode.to_logical(1).to_buffer(1, Transform::Normal) != buffer_size { + session.failed(FailureReason::InvalidSize); + continue; + } + match render_to_buffer( gpu.cloned(), renderer, diff --git a/src/wayland/handlers/screencopy.rs b/src/wayland/handlers/screencopy.rs index 6706e009..ddaa7cdd 100644 --- a/src/wayland/handlers/screencopy.rs +++ b/src/wayland/handlers/screencopy.rs @@ -4,6 +4,7 @@ use std::{ ops::{Deref, DerefMut}, }; +use anyhow::anyhow; use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::{ FailureReason, InputType, }; @@ -112,7 +113,7 @@ impl ScreencopyHandler for State { .unwrap() .0 .borrow_mut() - .push(DropableSession(session, FailureReason::OutputDisabled)); + .push(DropableSession(session, FailureReason::InvalidOutput)); formats } @@ -134,7 +135,7 @@ impl ScreencopyHandler for State { let workspace = match self.common.shell.space_for_handle_mut(&handle) { Some(workspace) => workspace, None => { - session.failed(FailureReason::Unspec); + session.failed(FailureReason::InvalidWorkspace); return Vec::new(); } }; @@ -145,7 +146,7 @@ impl ScreencopyHandler for State { workspace .screencopy_sessions - .push(DropableSession(session, FailureReason::InvalidOutput)); + .push(DropableSession(session, FailureReason::InvalidWorkspace)); formats } @@ -217,7 +218,7 @@ impl ScreencopyHandler for State { .unwrap() .0 .borrow_mut() - .push(DropableSession(session, FailureReason::ToplevelDestroyed)); + .push(DropableSession(session, FailureReason::InvalidToplevel)); formats } @@ -250,7 +251,7 @@ impl ScreencopyHandler for State { if buffer_size.to_physical(1) != mode { slog_scope::warn!("Error during screencopy session: Buffer size doesn't match"); - session.failed(FailureReason::InvalidBuffer); + session.failed(FailureReason::InvalidSize); return; } } @@ -258,7 +259,7 @@ impl ScreencopyHandler for State { 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); + session.failed(FailureReason::InvalidSize); return; } } @@ -302,7 +303,7 @@ impl ScreencopyHandler for State { 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), + None => session.failed(FailureReason::InvalidWorkspace), }; } SessionType::Window(window) => { @@ -397,7 +398,7 @@ fn formats_for_output( let mode = match output.current_mode() { Some(mode) => mode.size.to_logical(1).to_buffer(1, Transform::Normal), None => { - return Err(FailureReason::OutputDisabled); + return Err(FailureReason::InvalidOutput); } }; @@ -557,6 +558,14 @@ pub fn render_output_to_buffer( params: BufferParams, output: &Output, ) -> Result { + let mode = output + .current_mode() + .map(|mode| mode.size.to_logical(1).to_buffer(1, Transform::Normal)); + let buffer_size = buffer_dimensions(¶ms.buffer).unwrap(); + if mode != Some(buffer_size) { + return Err((FailureReason::InvalidSize, anyhow!("Output changed mode"))); + } + let node = node_from_params(¶ms, &mut state.backend, Some(output)); let mut _tmp_multirenderer = None; let renderer = match &mut state.backend { @@ -608,6 +617,14 @@ pub fn render_workspace_to_buffer( output: &Output, handle: &WorkspaceHandle, ) -> Result { + let mode = output + .current_mode() + .map(|mode| mode.size.to_logical(1).to_buffer(1, Transform::Normal)); + let buffer_size = buffer_dimensions(¶ms.buffer).unwrap(); + if mode != Some(buffer_size) { + return Err((FailureReason::InvalidSize, anyhow!("Output changed mode"))); + } + let node = node_from_params(¶ms, &mut state.backend, Some(output)); let mut _tmp_multirenderer = None; let renderer = match &mut state.backend { @@ -660,6 +677,10 @@ pub fn render_window_to_buffer( window: &Window, ) -> Result { let geometry = window.geometry(); + let buffer_size = buffer_dimensions(¶ms.buffer).unwrap(); + if buffer_size != geometry.size.to_buffer(1, Transform::Normal) { + return Err((FailureReason::InvalidSize, anyhow!("Window changed size"))); + } let node = node_from_params(¶ms, &mut state.backend, None); let mut _tmp_multirenderer = None; diff --git a/src/wayland/protocols/screencopy.rs b/src/wayland/protocols/screencopy.rs index 376936ad..7c05cd49 100644 --- a/src/wayland/protocols/screencopy.rs +++ b/src/wayland/protocols/screencopy.rs @@ -566,7 +566,10 @@ where if let Err(err) = cursor.into_result() { slog_scope::warn!("Client did send unknown cursor mode: {}", err); - session.failed(FailureReason::UnknownInput); + session.post_error( + zcosmic_screencopy_session_v1::Error::InvalidCursorMode, + "Unknown cursor mode, wrong protocol version?", + ); return None; }; let session = Session { @@ -661,7 +664,7 @@ where return; } }; - session.obj.failed(FailureReason::ToplevelDestroyed); + session.obj.failed(FailureReason::InvalidToplevel); return; } }, @@ -697,7 +700,7 @@ where return; } }; - session.failed(FailureReason::InvalidOutput); + session.failed(FailureReason::InvalidWorkspace); return; } }, @@ -782,7 +785,7 @@ where return; } }; - session.failed(FailureReason::UnknownInput); + session.failed(FailureReason::InvalidSeat); return; } }, From 6b299a3a2ad21c173199dd452c43471959d10cd1 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 4 Nov 2022 14:14:35 +0100 Subject: [PATCH 23/62] deps: Use public dependencies again --- Cargo.lock | 279 +++++++++++++++++++++++++++++++++-------------------- Cargo.toml | 15 +-- 2 files changed, 179 insertions(+), 115 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index da112559..fc6d9a43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ab_glyph" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04a9283dace1c41c265496614998d5b9c4a97b3eb770e804f007c5144bf03f2b" +checksum = "4dcdbc68024b653943864d436fe8a24b028095bc1cf91a8926f8241e4aaffe59" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -55,9 +55,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.65" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" +checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" dependencies = [ "backtrace", ] @@ -116,16 +116,16 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.5.4", "object", "rustc-demangle", ] [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "bitflags" @@ -150,15 +150,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.11.0" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "bytemuck" -version = "1.12.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da" +checksum = "5aec14f5d4e6e3f927cd0c81f72e5710d95ee9019fbeb4b3021193867491bfd8" dependencies = [ "bytemuck_derive", ] @@ -189,9 +189,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.73" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" [[package]] name = "cfg-if" @@ -211,9 +211,9 @@ dependencies = [ [[package]] name = "cocoa" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" +checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" dependencies = [ "bitflags", "block", @@ -309,7 +309,7 @@ dependencies = [ "smithay-egui", "thiserror", "wayland-backend", - "wayland-scanner 0.30.0-beta.12 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-scanner 0.30.0-beta.13", "xcursor", "xdg", "xkbcommon 0.4.1", @@ -318,12 +318,12 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols?branch=screencopy#e96daeceac966b6d21c79f2ebcc4b9934a69fa75" +source = "git+https://github.com/pop-os/cosmic-protocols?branch=main#aee21196b19591e83c04c803bfa1a865fda46337" dependencies = [ "bitflags", "wayland-backend", - "wayland-protocols 0.30.0-beta.12", - "wayland-scanner 0.30.0-beta.12 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-protocols 0.30.0-beta.13", + "wayland-scanner 0.30.0-beta.13", "wayland-server", ] @@ -601,7 +601,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.5.4", ] [[package]] @@ -669,9 +669,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", @@ -784,7 +784,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f65dae1d3aa98e6877917ab4e6fdbfdfb00e95885ea7c4f4f29e3a5dfc08fdf" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.36.1", ] [[package]] @@ -822,9 +822,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.133" +version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" [[package]] name = "libloading" @@ -954,15 +954,24 @@ dependencies = [ ] [[package]] -name = "mio" -version = "0.8.4" +name = "miniz_oxide" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1136,15 +1145,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "owned_ttf_parser" -version = "0.15.2" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05e6affeb1632d6ff6a23d2cd40ffed138e82f1532571a26f527c8a284bb2fbb" +checksum = "4665508572151759e8d60404e20dc096ef93a99801a05ac2ac6e43bf5b4ca187" dependencies = [ "ttf-parser", ] @@ -1161,15 +1170,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1180,20 +1189,20 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pkg-config" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "png" -version = "0.17.6" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f0e7f4c94ec26ff209cee506314212639d6c91b80afb82984819fafce9df01c" +checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" dependencies = [ "bitflags", "crc32fast", "flate2", - "miniz_oxide", + "miniz_oxide 0.6.2", ] [[package]] @@ -1371,9 +1380,9 @@ checksum = "0b53b0a5db882a8e2fdaae0a43f7b39e7e9082389e978398bdf223a55b581248" [[package]] name = "scoped-tls" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" @@ -1392,18 +1401,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.145" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.145" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" dependencies = [ "proc-macro2", "quote", @@ -1412,9 +1421,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.85" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" dependencies = [ "itoa", "ryu", @@ -1503,6 +1512,7 @@ checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "smithay" version = "0.3.0" +source = "git+https://github.com/Smithay//smithay?rev=b0ec5090df#b0ec5090df54e711a59b67869b495795053574e5" dependencies = [ "appendlist", "bitflags", @@ -1534,11 +1544,11 @@ dependencies = [ "udev", "wayland-backend", "wayland-egl", - "wayland-protocols 0.30.0-beta.12", + "wayland-protocols 0.30.0-beta.13", "wayland-protocols-misc", "wayland-protocols-wlr", "wayland-server", - "wayland-sys 0.30.0-beta.12", + "wayland-sys 0.30.0-beta.13", "winit", "x11rb", "xkbcommon 0.5.0", @@ -1597,9 +1607,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" dependencies = [ "proc-macro2", "quote", @@ -1668,21 +1678,32 @@ dependencies = [ [[package]] name = "time" -version = "0.3.14" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" +checksum = "0fab5c8b9980850e06d92ddbe3ab839c062c801f3927c0fb8abd6fc8e918fbca" dependencies = [ "itoa", "libc", "num_threads", + "serde", + "time-core", "time-macros", ] [[package]] -name = "time-macros" -version = "0.2.4" +name = "time-core" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bb801831d812c562ae7d2bfb531f26e66e4e1f6b17307ba4149c5064710e5b" +dependencies = [ + "time-core", +] [[package]] name = "toml" @@ -1695,9 +1716,9 @@ dependencies = [ [[package]] name = "ttf-parser" -version = "0.15.2" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" +checksum = "375812fa44dab6df41c195cd2f7fecb488f6c09fbaafb62807488cefab642bff" [[package]] name = "typenum" @@ -1805,8 +1826,9 @@ checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "wayland-backend" -version = "0.1.0-beta.12" -source = "git+https://github.com/Smithay/wayland-rs?rev=13f6a9be#13f6a9beb05eabac56aa372e1d38b12bfa987982" +version = "0.1.0-beta.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f96c52ca34b33e6bb55497327428be54dad648973aa5d3d1e02982d2fcc298" dependencies = [ "cc", "downcast-rs", @@ -1814,7 +1836,7 @@ dependencies = [ "nix 0.25.0", "scoped-tls", "smallvec", - "wayland-sys 0.30.0-beta.12", + "wayland-sys 0.30.0-beta.13", ] [[package]] @@ -1858,12 +1880,13 @@ dependencies = [ [[package]] name = "wayland-egl" -version = "0.30.0-beta.12" -source = "git+https://github.com/Smithay/wayland-rs?rev=13f6a9be#13f6a9beb05eabac56aa372e1d38b12bfa987982" +version = "0.30.0-beta.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da29b4a38d5cd89b90786305b62c21889d7d97bb1e2ccf46a94906832898e20e" dependencies = [ "thiserror", "wayland-backend", - "wayland-sys 0.30.0-beta.12", + "wayland-sys 0.30.0-beta.13", ] [[package]] @@ -1880,39 +1903,39 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.30.0-beta.12" +version = "0.30.0-beta.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca770dc814b3c93db1b4ba12a5bdfe899f8d68f9b4b06fc31e53959261cd0c39" +checksum = "e32104ba51988754f4429dbb59c7b7fd2ccf1feaa0009bb59e49a5e2f91e985d" dependencies = [ "bitflags", "wayland-backend", - "wayland-scanner 0.30.0-beta.12 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-scanner 0.30.0-beta.13", "wayland-server", ] [[package]] name = "wayland-protocols-misc" -version = "0.1.0-beta.12" +version = "0.1.0-beta.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25bfcd2c16bed3540d1c5662d97708ac4f14d92b7b5c52c85860cdc17a96a126" +checksum = "d6d0be545a3b35869060efa6e19622f6b071a9ee8d09da5ffa48f072524fddaa" dependencies = [ "bitflags", "wayland-backend", - "wayland-protocols 0.30.0-beta.12", - "wayland-scanner 0.30.0-beta.12 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-protocols 0.30.0-beta.13", + "wayland-scanner 0.30.0-beta.13", "wayland-server", ] [[package]] name = "wayland-protocols-wlr" -version = "0.1.0-beta.12" +version = "0.1.0-beta.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8795f1c689bce3845292c1d84b8866fa65d1d1845e76769b13f71cadeb8b5853" +checksum = "130613a48629204e4ab50ea4b5de2efa2e17c58f71a5af15b39535f99421f8c3" dependencies = [ "bitflags", "wayland-backend", - "wayland-protocols 0.30.0-beta.12", - "wayland-scanner 0.30.0-beta.12 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-protocols 0.30.0-beta.13", + "wayland-scanner 0.30.0-beta.13", "wayland-server", ] @@ -1929,20 +1952,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.30.0-beta.12" +version = "0.30.0-beta.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87def40ed0bc26c3eff1498812543b6d55bbd13b93e36d368052b7390a57c4ac" -dependencies = [ - "proc-macro2", - "quick-xml", - "quote", - "syn", -] - -[[package]] -name = "wayland-scanner" -version = "0.30.0-beta.12" -source = "git+https://github.com/Smithay/wayland-rs?rev=13f6a9be#13f6a9beb05eabac56aa372e1d38b12bfa987982" +checksum = "1fb55ca28710dcc155078db4e2bd29aacf24bd311261c48750f38c6c5430bf1f" dependencies = [ "proc-macro2", "quick-xml", @@ -1952,15 +1964,16 @@ dependencies = [ [[package]] name = "wayland-server" -version = "0.30.0-beta.12" -source = "git+https://github.com/Smithay/wayland-rs?rev=13f6a9be#13f6a9beb05eabac56aa372e1d38b12bfa987982" +version = "0.30.0-beta.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5ed6de3d02e0bcfee03cf23b8f8e923fb3707d826bcb1ef10ef34647778e20" dependencies = [ "bitflags", "downcast-rs", "nix 0.25.0", "thiserror", "wayland-backend", - "wayland-scanner 0.30.0-beta.12 (git+https://github.com/Smithay/wayland-rs?rev=13f6a9be)", + "wayland-scanner 0.30.0-beta.13", ] [[package]] @@ -1976,8 +1989,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.30.0-beta.12" -source = "git+https://github.com/Smithay/wayland-rs?rev=13f6a9be#13f6a9beb05eabac56aa372e1d38b12bfa987982" +version = "0.30.0-beta.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882bda56e8397384ad66ec0ebe763e0b3b53153a6327344f7d74b85981a271e1" dependencies = [ "dlib", "libc", @@ -2033,37 +2047,88 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", ] +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + [[package]] name = "windows_aarch64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + [[package]] name = "windows_i686_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + [[package]] name = "windows_i686_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + [[package]] name = "windows_x86_64_msvc" version = "0.36.1" @@ -2071,10 +2136,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" [[package]] -name = "winit" -version = "0.27.3" +name = "windows_x86_64_msvc" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a22e94ba35ca3ff11820044bfa0dc48b95a3a15569c0068555566a12ef41c9e5" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + +[[package]] +name = "winit" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb796d6fbd86b2fd896c9471e6f04d39d750076ebe5680a3958f00f5ab97657c" dependencies = [ "bitflags", "cocoa", @@ -2098,7 +2169,7 @@ dependencies = [ "wayland-client", "wayland-protocols 0.29.5", "web-sys", - "windows-sys", + "windows-sys 0.36.1", "x11-dl", ] diff --git a/Cargo.toml b/Cargo.toml index 49de6c29..1dbebd11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,9 +29,9 @@ indexmap = "1.8.0" xdg = "^2.1" ron = "0.7" libsystemd = "0.5" -wayland-backend = "=0.1.0-beta.12" -wayland-scanner = "=0.30.0-beta.12" -cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", branch = "screencopy", default-features = false, features = ["server"] } +wayland-backend = "=0.1.0-beta.13" +wayland-scanner = "=0.30.0-beta.13" +cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", branch = "main", default-features = false, features = ["server"] } [dependencies.smithay] version = "0.3" @@ -61,11 +61,4 @@ debug = true lto = "fat" [patch."https://github.com/Smithay/smithay.git"] -#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" } +smithay = { git = "https://github.com/Smithay//smithay", rev = "b0ec5090df" } From bcf3e43fcc078183c428c58aaba607c87eaf51de Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 4 Nov 2022 14:28:12 +0100 Subject: [PATCH 24/62] screencopy: Proper error for unsupport/unknown cursor modes --- src/wayland/protocols/screencopy.rs | 246 ++++++++++++++++------------ 1 file changed, 141 insertions(+), 105 deletions(-) diff --git a/src/wayland/protocols/screencopy.rs b/src/wayland/protocols/screencopy.rs index 7c05cd49..fe1b8ac8 100644 --- a/src/wayland/protocols/screencopy.rs +++ b/src/wayland/protocols/screencopy.rs @@ -56,7 +56,7 @@ impl ScreencopyState { ) -> ScreencopyState where D: GlobalDispatch - + Dispatch + + Dispatch> + Dispatch + ScreencopyHandler + WorkspaceHandler @@ -508,7 +508,7 @@ pub trait ScreencopyHandler { impl GlobalDispatch for ScreencopyState where D: GlobalDispatch - + Dispatch + + Dispatch> + Dispatch + ScreencopyHandler + WorkspaceHandler @@ -522,7 +522,7 @@ where global_data: &ScreencopyGlobalData, data_init: &mut DataInit<'_, D>, ) { - let global = data_init.init(resource, ()); + let global = data_init.init(resource, global_data.cursor_modes.clone()); for mode in &global_data.cursor_modes { global.supported_cursor_mode(*mode); } @@ -533,15 +533,43 @@ where } } +fn check_cursor( + cursor: WEnum, + supported: &[WlCursorMode], + resource: &ZcosmicScreencopyManagerV1, +) -> Option { + match cursor.into_result() { + Ok(mode) => { + if !supported.contains(&mode) { + slog_scope::warn!("Client did send unsupported cursor mode: {:?}", mode); + resource.post_error( + zcosmic_screencopy_manager_v1::Error::InvalidCursorMode, + "Unsupported cursor mode", + ); + return None; + } + Some(mode) + } + Err(err) => { + slog_scope::warn!("Client did send unknown cursor mode: {}", err); + resource.post_error( + zcosmic_screencopy_manager_v1::Error::InvalidCursorMode, + "Unknown cursor mode, wrong protocol version?", + ); + None + } + } +} + fn init_session( data_init: &mut DataInit<'_, D>, session: New, - cursor: WEnum, + cursor: WlCursorMode, _type: SessionType, ) -> Option where D: GlobalDispatch - + Dispatch + + Dispatch> + Dispatch + ScreencopyHandler + WorkspaceHandler @@ -552,9 +580,9 @@ where gone: false, pending_buffer: None, aux: AuxData::Normal { - cursor: match cursor.into_result() { - Ok(WlCursorMode::Capture) => CursorMode::Captured(Vec::new()), - Ok(WlCursorMode::Embedded) => CursorMode::Embedded, + cursor: match cursor { + WlCursorMode::Capture => CursorMode::Captured(Vec::new()), + WlCursorMode::Embedded => CursorMode::Embedded, _ => CursorMode::None, }, }, @@ -564,14 +592,6 @@ where }); let session = data_init.init(session, data.clone()); - if let Err(err) = cursor.into_result() { - slog_scope::warn!("Client did send unknown cursor mode: {}", err); - session.post_error( - zcosmic_screencopy_session_v1::Error::InvalidCursorMode, - "Unknown cursor mode, wrong protocol version?", - ); - return None; - }; let session = Session { obj: SessionResource::Alive(session), data, @@ -580,10 +600,10 @@ where Some(session) } -impl Dispatch for ScreencopyState +impl Dispatch, D> for ScreencopyState where D: GlobalDispatch - + Dispatch + + Dispatch> + Dispatch + ScreencopyHandler + WorkspaceHandler @@ -592,9 +612,9 @@ where fn request( state: &mut D, _client: &Client, - _resource: &ZcosmicScreencopyManagerV1, + resource: &ZcosmicScreencopyManagerV1, request: ::Request, - _data: &(), + data: &Vec, _dhandle: &DisplayHandle, data_init: &mut DataInit<'_, D>, ) { @@ -603,91 +623,23 @@ where session, output, cursor, - } => 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); - } - } - 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, - } => 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; - } - }; + } => { + let Some(cursor) = check_cursor(cursor, &data, resource) else { 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::InvalidToplevel); - return; - } - }, - zcosmic_screencopy_manager_v1::Request::CaptureWorkspace { - session, - workspace, - output, - cursor, - } => match Output::from_resource(&output) { - Some(output) => match state.workspace_state().workspace_handle(&workspace) { - Some(handle) => { + match Output::from_resource(&output) { + Some(output) => { let session = match init_session( data_init, session, cursor, - SessionType::Workspace(output.clone(), handle.clone()), + SessionType::Output(output.clone()), ) { Some(result) => result, None => { return; } }; - let formats = state.capture_workspace(handle, output, session.clone()); + let formats = state.capture_output(output, session.clone()); if !session.data.inner.lock().unwrap().gone { send_formats(&session.obj, formats); } @@ -700,22 +652,106 @@ where return; } }; - session.failed(FailureReason::InvalidWorkspace); + session.failed(FailureReason::InvalidOutput); return; } - }, - None => { - let session = - match init_session(data_init, session, cursor, SessionType::Unknown) { + } + } + zcosmic_screencopy_manager_v1::Request::CaptureToplevel { + session, + toplevel, + cursor, + } => { + let Some(cursor) = check_cursor(cursor, &data, resource) else { return; }; + + 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; } }; - session.failed(FailureReason::InvalidOutput); - 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::InvalidToplevel); + return; + } } - }, + } + zcosmic_screencopy_manager_v1::Request::CaptureWorkspace { + session, + workspace, + output, + cursor, + } => { + let Some(cursor) = check_cursor(cursor, &data, resource) else { return; }; + + match Output::from_resource(&output) { + Some(output) => match state.workspace_state().workspace_handle(&workspace) { + Some(handle) => { + 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::InvalidWorkspace); + return; + } + }, + None => { + let session = + match init_session(data_init, session, cursor, SessionType::Unknown) { + Some(result) => result, + None => { + return; + } + }; + session.failed(FailureReason::InvalidOutput); + return; + } + } + } _ => {} } } @@ -724,7 +760,7 @@ where impl Dispatch for ScreencopyState where D: GlobalDispatch - + Dispatch + + Dispatch> + Dispatch + ScreencopyHandler + WorkspaceHandler @@ -777,7 +813,7 @@ where let session = match init_session( data_init, session, - WEnum::Value(WlCursorMode::Capture), + WlCursorMode::Capture, SessionType::Unknown, ) { Some(result) => result, @@ -900,7 +936,7 @@ macro_rules! delegate_screencopy { cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_manager_v1::ZcosmicScreencopyManagerV1: $crate::wayland::protocols::screencopy::ScreencopyGlobalData ] => $crate::wayland::protocols::screencopy::ScreencopyState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_manager_v1::ZcosmicScreencopyManagerV1: () + cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_manager_v1::ZcosmicScreencopyManagerV1: std::vec::Vec ] => $crate::wayland::protocols::screencopy::ScreencopyState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::ZcosmicScreencopySessionV1: $crate::wayland::protocols::screencopy::SessionData From cab52fbeefe4e713855222096e047dfda5856198 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 4 Nov 2022 16:57:42 +0100 Subject: [PATCH 25/62] screencopy: Capture cursor for window capture --- Cargo.lock | 2 +- src/shell/element/mod.rs | 54 ++++++++++++++++++++++- src/wayland/handlers/screencopy.rs | 70 ++++++++++++++++++++++++++---- 3 files changed, 116 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fc6d9a43..3336870d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -318,7 +318,7 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols?branch=main#aee21196b19591e83c04c803bfa1a865fda46337" +source = "git+https://github.com/pop-os/cosmic-protocols?branch=main#a3e0aa740a3e0f8f7b486fef0d62fa09a1dfa328" dependencies = [ "bitflags", "wayland-backend", diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index 021a85a9..590b3046 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -1,4 +1,4 @@ -use crate::state::State; +use crate::{state::State, utils::prelude::SeatExt}; use id_tree::NodeId; use smithay::{ backend::{ @@ -25,6 +25,7 @@ use smithay::{ }, }; use std::{ + collections::HashMap, hash::Hash, sync::{Arc, Mutex}, }; @@ -48,6 +49,7 @@ pub struct CosmicMapped { element: CosmicMappedInternal, // associated data + last_cursor_position: Arc>>>, //tiling pub(super) tiling_node_id: Arc>>, @@ -108,6 +110,45 @@ impl CosmicMapped { } } + pub fn active_window_offset(&self) -> Rectangle { + match &self.element { + CosmicMappedInternal::Stack(stack) => { + let location = ( + 0, + stack + .header + .lock() + .unwrap() + .as_ref() + .map_or(0, |header| header.height()), + ); + let size = stack.active().geometry().size; + Rectangle::from_loc_and_size(location, size) + } + CosmicMappedInternal::Window(win) => { + let location = ( + 0, + win.header + .lock() + .unwrap() + .as_ref() + .map_or(0, |header| header.height()), + ); + let size = win.window.geometry().size; + Rectangle::from_loc_and_size(location, size) + } + _ => unreachable!(), + } + } + + pub fn cursor_position(&self, seat: &Seat) -> Option> { + self.last_cursor_position + .lock() + .unwrap() + .get(&seat.id()) + .cloned() + } + pub fn set_active(&self, window: &Window) { if let CosmicMappedInternal::Stack(stack) = &self.element { stack.set_active(window); @@ -565,6 +606,10 @@ impl KeyboardTarget for CosmicMapped { impl PointerTarget for CosmicMapped { fn enter(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { + self.last_cursor_position + .lock() + .unwrap() + .insert(seat.id(), event.location); match &self.element { CosmicMappedInternal::Stack(s) => PointerTarget::enter(s, seat, data, event), CosmicMappedInternal::Window(w) => PointerTarget::enter(w, seat, data, event), @@ -572,6 +617,10 @@ impl PointerTarget for CosmicMapped { } } fn motion(&self, seat: &Seat, data: &mut State, event: &MotionEvent) { + self.last_cursor_position + .lock() + .unwrap() + .insert(seat.id(), event.location); match &self.element { CosmicMappedInternal::Stack(s) => PointerTarget::motion(s, seat, data, event), CosmicMappedInternal::Window(w) => PointerTarget::motion(w, seat, data, event), @@ -593,6 +642,7 @@ impl PointerTarget for CosmicMapped { } } fn leave(&self, seat: &Seat, data: &mut State, serial: Serial, time: u32) { + self.last_cursor_position.lock().unwrap().remove(&seat.id()); match &self.element { CosmicMappedInternal::Stack(s) => PointerTarget::leave(s, seat, data, serial, time), CosmicMappedInternal::Window(w) => PointerTarget::leave(w, seat, data, serial, time), @@ -623,6 +673,7 @@ impl From for CosmicMapped { fn from(w: CosmicWindow) -> Self { CosmicMapped { element: CosmicMappedInternal::Window(w), + last_cursor_position: Arc::new(Mutex::new(HashMap::new())), tiling_node_id: Arc::new(Mutex::new(None)), last_geometry: Arc::new(Mutex::new(None)), resize_state: Arc::new(Mutex::new(None)), @@ -634,6 +685,7 @@ impl From for CosmicMapped { fn from(s: CosmicStack) -> Self { CosmicMapped { element: CosmicMappedInternal::Stack(s), + last_cursor_position: Arc::new(Mutex::new(HashMap::new())), tiling_node_id: Arc::new(Mutex::new(None)), last_geometry: Arc::new(Mutex::new(None)), resize_state: Arc::new(Mutex::new(None)), diff --git a/src/wayland/handlers/screencopy.rs b/src/wayland/handlers/screencopy.rs index ddaa7cdd..fbc3ae65 100644 --- a/src/wayland/handlers/screencopy.rs +++ b/src/wayland/handlers/screencopy.rs @@ -20,7 +20,7 @@ use smithay::{ surface::WaylandSurfaceRenderElement, AsRenderElements, RenderElementStates, }, gles2::{Gles2Renderbuffer, Gles2Renderer}, - Bind, BufferType, ExportMem, Offscreen, Renderer, + Bind, BufferType, ExportMem, ImportAll, Offscreen, Renderer, }, }, desktop::Window, @@ -37,7 +37,7 @@ use smithay::{ }; use crate::{ - backend::render::{render_output, render_workspace, CursorMode, CLEAR_COLOR}, + backend::render::{cursor, render_output, render_workspace, CursorMode, CLEAR_COLOR}, state::{BackendData, ClientState, Common, State}, utils::prelude::OutputExt, wayland::protocols::{ @@ -49,6 +49,8 @@ use crate::{ }, }; +use super::data_device::get_dnd_icon; + pub type PendingScreencopyBuffers = RefCell>; #[derive(Debug, Default)] @@ -670,6 +672,12 @@ pub fn render_workspace_to_buffer( .map_err(|err| (FailureReason::Unspec, err.into())) } +smithay::render_elements! { + pub WindowCaptureElement where R: ImportAll; + WaylandElement=WaylandSurfaceRenderElement, + CursorElement=cursor::CursorRenderElement, +} + pub fn render_window_to_buffer( state: &mut State, session: &Session, @@ -706,12 +714,58 @@ pub fn render_window_to_buffer( 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), - ); + let mut elements = AsRenderElements::::render_elements::< + WindowCaptureElement, + >( + window, + (-geometry.loc.x, -geometry.loc.y).into(), + Scale::from(1.0), + ); + + for seat in state.common.seats() { + if let Some(location) = { + // we need to find the mapped element in that case + if let Some(mapped) = state + .common + .shell + .element_for_surface(window.toplevel().wl_surface()) + { + mapped.cursor_position(seat).and_then(|mut p| { + p -= mapped.active_window_offset().loc.to_f64(); + if p.x < 0. || p.y < 0. { + None + } else { + Some(p) + } + }) + } else { + None + } + } { + if session.cursor_mode() == ScreencopyCursorMode::Embedded { + elements.extend( + cursor::draw_cursor( + renderer, + seat, + location, + 1.0.into(), + &state.common.start_time, + true, + ) + .into_iter() + .map(WindowCaptureElement::from), + ); + } + + if let Some(wl_surface) = get_dnd_icon(seat) { + elements.extend( + cursor::draw_dnd_icon(&wl_surface, location.to_i32_round(), 1.0) + .into_iter() + .map(WindowCaptureElement::from), + ); + } + } + } dtr.render_output(renderer, age, &elements, CLEAR_COLOR, None) }, From 9af7e1ecb238634dbe68a204e53c7e532d27f348 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 4 Nov 2022 17:34:17 +0100 Subject: [PATCH 26/62] render: More notes for fast-copy screencopy path --- src/backend/render/mod.rs | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index d6a4886d..96de627a 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -14,7 +14,9 @@ use crate::{ wayland::{ handlers::{data_device::get_dnd_icon, screencopy::render_to_buffer}, protocols::{ - screencopy::{BufferParams, Session as ScreencopySession}, + screencopy::{ + BufferParams, CursorMode as ScreencopyCursorMode, Session as ScreencopySession, + }, workspace::WorkspaceHandle, }, }, @@ -26,7 +28,6 @@ use smithay::{ allocator::dmabuf::Dmabuf, drm::DrmNode, renderer::{ - buffer_dimensions, damage::{ DamageTrackedRenderer, DamageTrackedRendererError as RenderError, OutputNoMode, }, @@ -37,7 +38,7 @@ use smithay::{ }, }, output::Output, - utils::{Physical, Rectangle, Transform}, + utils::{Physical, Rectangle}, }; pub mod cursor; @@ -176,7 +177,7 @@ pub fn render_workspace( state: &mut Common, output: &Output, handle: &WorkspaceHandle, - cursor_mode: CursorMode, + mut cursor_mode: CursorMode, screencopy: Option<(Source, &[(ScreencopySession, BufferParams)])>, #[cfg(feature = "debug")] mut fps: Option<&mut Fps>, ) -> Result<(Option>>, RenderElementStates), RenderError> @@ -199,6 +200,21 @@ where let workspace = state.shell.space_for_handle(&handle).ok_or(OutputNoMode)?; + let screencopy_contains_embedded = screencopy.as_ref().map_or(false, |(_, sessions)| { + sessions + .iter() + .any(|(s, _)| s.cursor_mode() == ScreencopyCursorMode::Embedded) + }); + // cursor handling without a cursor_plane in this case is horrible. + // because what if some session disagree and/or the backend wants to render with a different mode? + // It seems we would need to render to an offscreen buffer in those cases (and do multiple renders, which messes with damage tracking). + // So for now, we just pick the worst mode (embedded), if any requires it. + // + // Once we move to a cursor_plane, the default framebuffer will never contain a cursor and we can just composite the cursor for each session separately on top (or not). + if screencopy_contains_embedded { + cursor_mode = CursorMode::All; + }; + let mut elements: Vec> = cursor_elements(renderer, state, output, cursor_mode); #[cfg(feature = "debug")] @@ -255,13 +271,6 @@ where if let Some((source, buffers)) = screencopy { if res.is_ok() { for (session, params) in buffers { - let mode = output.current_mode().unwrap().size; - let buffer_size = buffer_dimensions(¶ms.buffer).unwrap(); - if mode.to_logical(1).to_buffer(1, Transform::Normal) != buffer_size { - session.failed(FailureReason::InvalidSize); - continue; - } - match render_to_buffer( gpu.cloned(), renderer, From a8e0f99e4baf9c6ff56c2d6167ddbf5ba991d143 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Sun, 6 Nov 2022 11:49:51 +0100 Subject: [PATCH 27/62] shell: Fix layer_shell --- src/shell/workspace.rs | 2 +- src/wayland/handlers/compositor.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 00c77374..8cc19c2c 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -437,7 +437,7 @@ impl Workspace { // overlay and top layer surfaces let lower = { - let (upper, lower): (Vec<&LayerSurface>, Vec<&LayerSurface>) = layer_map + let (lower, upper): (Vec<&LayerSurface>, Vec<&LayerSurface>) = layer_map .layers() .rev() .partition(|s| matches!(s.layer(), Layer::Background | Layer::Bottom)); diff --git a/src/wayland/handlers/compositor.rs b/src/wayland/handlers/compositor.rs index 77db7e30..9804e78e 100644 --- a/src/wayland/handlers/compositor.rs +++ b/src/wayland/handlers/compositor.rs @@ -100,7 +100,7 @@ impl State { if !initial_configure_sent { // compute initial dimensions by mapping Shell::map_layer(self, &surface); - // this will also send a configure + surface.layer_surface().send_configure(); } initial_configure_sent } From 5057dec552fffd6cb5e27502e5cbac9f7da6a9b3 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Sun, 6 Nov 2022 11:50:09 +0100 Subject: [PATCH 28/62] kms: Don't queue frames only after vblank --- src/backend/kms/mod.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 7081ad1a..f03db215 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -94,7 +94,6 @@ pub struct Surface { damage_tracker: DamageTrackedRenderer, connector: connector::Handle, output: Output, - last_submit: Option, refresh_rate: u32, vrr: bool, pending: bool, @@ -382,10 +381,6 @@ impl State { Some(Ok(_)) => { let _submit_time = metadata.take().map(|data| data.time); surface.pending = false; - data.state.common.send_frames( - &surface.output, - &surface.last_submit.take().unwrap(), - ); } Some(Err(err)) => { slog_scope::warn!("Failed to submit frame: {}", err) @@ -701,7 +696,6 @@ impl Device { connector: conn, vrr, refresh_rate, - last_submit: None, pending: false, render_timer_token: None, #[cfg(feature = "debug")] @@ -791,7 +785,7 @@ impl Surface { Some(&mut self.fps), ) { Ok((_damage, states)) => { - self.last_submit = Some(states); + state.send_frames(&self.output, &states); surface .queue_buffer() .with_context(|| "Failed to submit buffer for display")?; @@ -801,6 +795,7 @@ impl Surface { anyhow::bail!("Rendering failed: {}", err); } }; + Ok(()) } } From 78b9b07cec72ca0a613bf6b67f435681bdf63491 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 8 Nov 2022 09:38:43 +0100 Subject: [PATCH 29/62] shell: Fix workspace naming --- src/shell/mod.rs | 70 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index acf4aec0..055aa50b 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -84,7 +84,10 @@ fn create_workspace( if active { state.add_workspace_state(&workspace_handle, WState::Active); } - init_workspace_handle(state, 0, &workspace_handle); + state.set_workspace_capabilities( + &workspace_handle, + [WorkspaceCapabilities::Activate].into_iter(), + ); Workspace::new(workspace_handle) } @@ -94,10 +97,24 @@ impl WorkspaceSet { let workspaces = match amount { WorkspaceAmount::Dynamic => { - vec![create_workspace(state, &group_handle, true)] + let workspace = create_workspace(state, &group_handle, true); + workspace_set_idx(state, 1, &workspace.handle); + state.set_workspace_capabilities( + &workspace.handle, + [WorkspaceCapabilities::Activate].into_iter(), + ); + vec![workspace] } WorkspaceAmount::Static(len) => (0..len) - .map(|i| create_workspace(state, &group_handle, i == 0)) + .map(|i| { + let workspace = create_workspace(state, &group_handle, i == 0); + workspace_set_idx(state, i + 1, &workspace.handle); + state.set_workspace_capabilities( + &workspace.handle, + [WorkspaceCapabilities::Activate].into_iter(), + ); + workspace + }) .collect(), }; @@ -138,9 +155,15 @@ impl WorkspaceSet { state: &mut WorkspaceState, outputs: impl Iterator)>, ) { + let mut state = state.update(); + // add empty at the end, if necessary if self.workspaces.last().unwrap().windows().next().is_some() { - let mut workspace = create_workspace(&mut state.update(), &self.group, false); + let mut workspace = create_workspace(&mut state, &self.group, false); + state.set_workspace_capabilities( + &workspace.handle, + [WorkspaceCapabilities::Activate].into_iter(), + ); for (output, location) in outputs { workspace.map_output(output, location); } @@ -149,13 +172,12 @@ impl WorkspaceSet { let len = self.workspaces.len(); let mut keep = vec![true; len]; - // remove empty workspaces in between, if they are not active for (i, workspace) in self.workspaces.iter().enumerate() { let has_windows = workspace.windows().next().is_some(); if !has_windows && i != self.active && i != len - 1 { - state.update().remove_workspace(workspace.handle); + state.remove_workspace(workspace.handle); keep[i] = false; } } @@ -167,6 +189,12 @@ impl WorkspaceSet { .take(self.active + 1) .filter(|keep| !**keep) .count(); + + if keep.iter().any(|val| *val == false) { + for (i, workspace) in self.workspaces.iter().enumerate() { + workspace_set_idx(&mut state, i as u8 + 1, &workspace.handle); + } + } } fn ensure_static<'a>( @@ -209,6 +237,15 @@ impl WorkspaceSet { let outputs = outputs.collect::>(); while amount > self.workspaces.len() { let mut workspace = create_workspace(&mut state, &self.group, false); + workspace_set_idx( + &mut state, + self.workspaces.len() as u8 + 1, + &workspace.handle, + ); + state.set_workspace_capabilities( + &workspace.handle, + [WorkspaceCapabilities::Activate].into_iter(), + ); for &(output, location) in outputs.iter() { workspace.map_output(output, location); } @@ -418,7 +455,11 @@ impl Shell { state.remove_workspace(workspace.handle); let workspace_handle = state.create_workspace(&workspace_group).unwrap(); - init_workspace_handle( + state.set_workspace_capabilities( + &workspace_handle, + [WorkspaceCapabilities::Activate].into_iter(), + ); + workspace_set_idx( &mut state, new_set.workspaces.len() as u8, &workspace_handle, @@ -524,7 +565,11 @@ impl Shell { for (i, (workspaces, active)) in mergers.into_iter().enumerate() { // and then we can merge each vector into one and put that into our new set. let workspace_handle = state.create_workspace(&new_set.group).unwrap(); - init_workspace_handle(&mut state, i as u8, &workspace_handle); + state.set_workspace_capabilities( + &workspace_handle, + [WorkspaceCapabilities::Activate].into_iter(), + ); + workspace_set_idx(&mut state, i as u8, &workspace_handle); let mut new_workspace = Workspace::new(workspace_handle); for output in self.outputs.iter() { @@ -580,7 +625,11 @@ impl Shell { // copy over everything and then remove other outputs to preserve state let new_set = sets.get_mut(output).unwrap(); let new_workspace_handle = state.create_workspace(&new_set.group).unwrap(); - init_workspace_handle(&mut state, i as u8, &new_workspace_handle); + state.set_workspace_capabilities( + &new_workspace_handle, + [WorkspaceCapabilities::Activate].into_iter(), + ); + workspace_set_idx(&mut state, i as u8, &new_workspace_handle); let mut old_tiling_layer = workspace.tiling_layer.clone(); let mut new_floating_layer = FloatingLayout::new(); @@ -994,12 +1043,11 @@ impl Shell { } } -fn init_workspace_handle<'a>( +fn workspace_set_idx<'a>( state: &mut WorkspaceUpdateGuard<'a, State>, idx: u8, handle: &WorkspaceHandle, ) { - state.set_workspace_capabilities(&handle, [WorkspaceCapabilities::Activate].into_iter()); state.set_workspace_name(&handle, format!("{}", idx + 1)); state.set_workspace_coordinates(&handle, [Some(idx as u32), None, None]); } From 38c0acb9438c02e19f21bf8074fe97cfc0d88b71 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 8 Nov 2022 10:32:53 +0100 Subject: [PATCH 30/62] wayland: Explicitly handle destroyed surfaces --- src/main.rs | 18 +---- src/state.rs | 8 -- src/wayland/handlers/compositor.rs | 104 ++----------------------- src/wayland/handlers/layer_shell.rs | 41 +++++++++- src/wayland/handlers/mod.rs | 25 ------ src/wayland/handlers/screencopy.rs | 105 +++++++++++++++++++++++++- src/wayland/handlers/xdg_shell/mod.rs | 47 ++++++++++-- 7 files changed, 191 insertions(+), 157 deletions(-) diff --git a/src/main.rs b/src/main.rs index e9fb78be..088b22ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,11 +9,7 @@ use smithay::{ }; use anyhow::{Context, Result}; -use std::{ - ffi::OsString, - os::unix::prelude::AsRawFd, - sync::{atomic::Ordering, Arc}, -}; +use std::{ffi::OsString, os::unix::prelude::AsRawFd, sync::Arc}; pub mod backend; pub mod config; @@ -71,18 +67,6 @@ fn main() -> Result<()> { data.state.common.shell.refresh(); state::Common::refresh_focus(&mut data.state); - // 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, - None, - ) - } - } - // send out events let _ = data.display.flush_clients(); })?; diff --git a/src/state.rs b/src/state.rs index e04c8cc4..075aec61 100644 --- a/src/state.rs +++ b/src/state.rs @@ -39,7 +39,6 @@ use smithay::{ use std::{ cell::RefCell, ffi::OsString, - sync::{atomic::AtomicBool, Arc}, time::{Duration, Instant}, }; #[cfg(feature = "debug")] @@ -75,7 +74,6 @@ pub struct Common { //pub output_conf: ConfigurationManager, pub shell: Shell, - pub dirty_flag: Arc, seats: Vec>, last_active_seat: Option>, @@ -242,11 +240,6 @@ impl State { let shell = Shell::new(&config, dh); - #[cfg(not(feature = "debug"))] - let dirty_flag = Arc::new(AtomicBool::new(false)); - #[cfg(feature = "debug")] - let dirty_flag = log.dirty_flag.clone(); - State { common: Common { config, @@ -256,7 +249,6 @@ impl State { event_loop_signal: signal, shell, - dirty_flag, seats: Vec::new(), last_active_seat: None, diff --git a/src/wayland/handlers/compositor.rs b/src/wayland/handlers/compositor.rs index 9804e78e..f7b2f81a 100644 --- a/src/wayland/handlers/compositor.rs +++ b/src/wayland/handlers/compositor.rs @@ -1,19 +1,11 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::{ - state::BackendData, - utils::prelude::*, - wayland::{ - handlers::screencopy::UserdataExt, - protocols::screencopy::{BufferParams, Session as ScreencopySession, SessionType}, - }, -}; +use crate::{state::BackendData, utils::prelude::*, wayland::protocols::screencopy::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::{ @@ -26,7 +18,7 @@ use smithay::{ }; use std::sync::Mutex; -use super::screencopy::{self, PendingScreencopyBuffers}; +use super::screencopy::PendingScreencopyBuffers; impl State { fn early_import_surface(&mut self, surface: &WlSurface) { @@ -170,36 +162,11 @@ impl CompositorHandler for State { ); 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); - } - } - }); - } - } } + //handle window screencopy sessions + self.schedule_window_session(surface); + // We need to know every potential output for importing to the right gpu and scheduling a render, // so call this only after every potential surface map operation has been done. self.early_import_surface(surface); @@ -216,66 +183,7 @@ 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!(); - } - } - } - } + let mut scheduled_sessions = self.schedule_workspace_sessions(surface); // schedule a new render for output in self.common.shell.visible_outputs_for_surface(surface) { diff --git a/src/wayland/handlers/layer_shell.rs b/src/wayland/handlers/layer_shell.rs index cc0d2c90..b6124487 100644 --- a/src/wayland/handlers/layer_shell.rs +++ b/src/wayland/handlers/layer_shell.rs @@ -3,7 +3,7 @@ use crate::utils::prelude::*; use smithay::{ delegate_layer_shell, - desktop::{LayerSurface, PopupKind}, + desktop::{layer_map_for_output, LayerSurface, PopupKind, WindowSurfaceType}, output::Output, reexports::wayland_server::protocol::wl_output::WlOutput, wayland::shell::{ @@ -14,6 +14,8 @@ use smithay::{ }, }; +use super::screencopy::PendingScreencopyBuffers; + impl WlrLayerShellHandler for State { fn shell_state(&mut self) -> &mut WlrLayerShellState { &mut self.common.shell.layer_shell_state @@ -26,7 +28,6 @@ impl WlrLayerShellHandler for State { _layer: Layer, namespace: String, ) { - super::mark_dirty_on_drop(&self.common, surface.wl_surface()); let seat = self.common.last_active_seat().clone(); let output = wl_output .as_ref() @@ -51,6 +52,42 @@ impl WlrLayerShellHandler for State { .unwrap(); } } + + fn layer_destroyed(&mut self, surface: WlrLayerSurface) { + let maybe_output = self + .common + .shell + .outputs() + .find(|o| { + let map = layer_map_for_output(o); + map.layer_for_surface(surface.wl_surface(), WindowSurfaceType::TOPLEVEL) + .is_some() + }) + .cloned(); + + if let Some(output) = maybe_output { + let mut map = layer_map_for_output(&output); + let layer = map + .layer_for_surface(surface.wl_surface(), WindowSurfaceType::TOPLEVEL) + .unwrap() + .clone(); + map.unmap_layer(&layer); + + // collect screencopy sessions needing an update + let mut scheduled_sessions = self.schedule_workspace_sessions(surface.wl_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, + ); + } + } } delegate_layer_shell!(State); diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index 16cb4288..5f93b207 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -19,28 +19,3 @@ pub mod viewporter; pub mod wl_drm; pub mod workspace; pub mod xdg_shell; - -use crate::state::Common; -use smithay::{ - reexports::wayland_server::protocol::wl_surface::WlSurface, - wayland::compositor::{add_destruction_hook, with_states}, -}; - -fn mark_dirty_on_drop(state: &Common, wl_surface: &WlSurface) { - use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }; - - let dirty = state.dirty_flag.clone(); - struct DirtyFlag(Arc); - - with_states(wl_surface, |data| { - data.data_map.insert_if_missing(|| DirtyFlag(dirty)); - }); - add_destruction_hook(wl_surface, |data| { - if let Some(DirtyFlag(dirty)) = data.data_map.get::() { - dirty.store(true, Ordering::SeqCst); - } - }) -} diff --git a/src/wayland/handlers/screencopy.rs b/src/wayland/handlers/screencopy.rs index fbc3ae65..5327134b 100644 --- a/src/wayland/handlers/screencopy.rs +++ b/src/wayland/handlers/screencopy.rs @@ -26,10 +26,10 @@ use smithay::{ desktop::Window, output::Output, reexports::wayland_server::{ - protocol::{wl_buffer::WlBuffer, wl_shm::Format as ShmFormat}, + protocol::{wl_buffer::WlBuffer, wl_shm::Format as ShmFormat, wl_surface::WlSurface}, Resource, }, - utils::{Physical, Rectangle, Scale, Transform}, + utils::{IsAlive, Physical, Rectangle, Scale, Transform}, wayland::{ dmabuf::get_dmabuf, shm::{with_buffer_contents, with_buffer_contents_mut}, @@ -869,4 +869,105 @@ impl UserdataExt for Window { } } +impl State { + pub fn schedule_window_session(&mut self, surface: &WlSurface) { + if let Some(element) = self.common.shell.element_for_surface(surface).cloned() { + 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 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); + } + } + }); + } + } + } + } + + pub fn schedule_workspace_sessions( + &mut self, + surface: &WlSurface, + ) -> Option> { + // here we store additional workspace_sessions, we should handle, when rendering the corresponding output anyway + let mut output_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); + output_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 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!(); + } + } + } + } + + output_sessions + } +} + delegate_screencopy!(State); diff --git a/src/wayland/handlers/xdg_shell/mod.rs b/src/wayland/handlers/xdg_shell/mod.rs index c012cd1b..a87a7435 100644 --- a/src/wayland/handlers/xdg_shell/mod.rs +++ b/src/wayland/handlers/xdg_shell/mod.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::utils::prelude::*; +use crate::{utils::prelude::*, wayland::protocols::screencopy::SessionType}; use smithay::{ delegate_xdg_shell, desktop::{ @@ -26,6 +26,8 @@ use smithay::{ }; use std::cell::Cell; +use super::screencopy::PendingScreencopyBuffers; + pub mod popup; pub type PopupGrabData = Cell>>; @@ -36,8 +38,6 @@ impl XdgShellHandler for State { } fn new_toplevel(&mut self, surface: ToplevelSurface) { - super::mark_dirty_on_drop(&self.common, surface.wl_surface()); - let seat = self.common.last_active_seat().clone(); let window = Window::new(Kind::Xdg(surface)); self.common.shell.toplevel_info_state.new_toplevel(&window); @@ -46,8 +46,6 @@ impl XdgShellHandler for State { } fn new_popup(&mut self, surface: PopupSurface, positioner: PositionerState) { - super::mark_dirty_on_drop(&self.common, surface.wl_surface()); - surface.with_pending_state(|state| { state.geometry = positioner.get_geometry(); state.positioner = positioner; @@ -280,6 +278,45 @@ impl XdgShellHandler for State { } } } + + fn toplevel_destroyed(&mut self, surface: ToplevelSurface) { + let outputs = self + .common + .shell + .visible_outputs_for_surface(surface.wl_surface()) + .collect::>(); + for output in outputs.iter() { + self.common.shell.active_space_mut(output).refresh(); + } + + // screencopy + let mut scheduled_sessions = self.schedule_workspace_sessions(surface.wl_surface()); + for output in outputs.into_iter() { + 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::>() + }), + ); + } + } } fn check_grab_preconditions( From fcf39337a74c65dbc14b4422a2578ed6341ee1c3 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 8 Nov 2022 13:57:42 +0100 Subject: [PATCH 31/62] shell/tiling: Fix segfault on detaching last output --- rust-toolchain.toml | 2 +- src/shell/layout/tiling/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index e403b651..70772a38 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.63" \ No newline at end of file +channel = "1.65" \ No newline at end of file diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index d716944f..731b4f89 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -267,7 +267,7 @@ impl TilingLayout { pub fn unmap_output(&mut self, output: &Output) { if let Some(src) = self.trees.remove(output) { // TODO: expects last remaining output - let (output, dst) = self.trees.iter_mut().next().unwrap(); + let Some((output, dst)) = self.trees.iter_mut().next() else { return; }; let orientation = match output.output.geometry().size { x if x.w >= x.h => Orientation::Horizontal, _ => Orientation::Vertical, From 48c071466f6ee1305c2c9c1430aa20fb71d3b9ee Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 9 Nov 2022 12:39:47 +0100 Subject: [PATCH 32/62] kms: Fix damage issues due to timing --- src/backend/kms/mod.rs | 55 +++++++++++++++++++++++------- src/wayland/handlers/screencopy.rs | 12 +++++++ 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index f03db215..559fec51 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -10,7 +10,7 @@ use crate::{ state::{BackendData, ClientState, Common, Data}, utils::prelude::*, wayland::{ - handlers::screencopy::UserdataExt, + handlers::screencopy::{PendingScreencopyBuffers, UserdataExt}, protocols::screencopy::{BufferParams, Session as ScreencopySession}, }, }; @@ -97,6 +97,7 @@ pub struct Surface { refresh_rate: u32, vrr: bool, pending: bool, + dirty: bool, render_timer_token: Option, #[cfg(feature = "debug")] fps: Fps, @@ -375,18 +376,44 @@ impl State { let dispatcher = Dispatcher::new(drm, move |event, metadata, data: &mut Data| match event { DrmEvent::VBlank(crtc) => { - if let Some(device) = data.state.backend.kms().devices.get_mut(&drm_node) { - if let Some(surface) = device.surfaces.get_mut(&crtc) { - match surface.surface.as_mut().map(|x| x.frame_submitted()) { - Some(Ok(_)) => { - let _submit_time = metadata.take().map(|data| data.time); - surface.pending = false; + let rescheduled_output = + if let Some(device) = data.state.backend.kms().devices.get_mut(&drm_node) { + if let Some(surface) = device.surfaces.get_mut(&crtc) { + match surface.surface.as_mut().map(|x| x.frame_submitted()) { + Some(Ok(_)) => { + let _submit_time = metadata.take().map(|data| data.time); + surface.pending = false; + surface.dirty.then(|| surface.output.clone()) + } + Some(Err(err)) => { + slog_scope::warn!("Failed to submit frame: {}", err); + None + } + _ => None, // got disabled } - Some(Err(err)) => { - slog_scope::warn!("Failed to submit frame: {}", err) - } - None => {} // got disabled - }; + } else { + None + } + } else { + None + }; + + if let Some(output) = rescheduled_output { + let mut scheduled_sessions = + data.state.workspace_session_for_output(&output); + if let Some(sessions) = output.user_data().get::() + { + scheduled_sessions + .get_or_insert_with(Vec::new) + .extend(sessions.borrow_mut().drain(..)); + } + + if let Err(err) = data.state.backend.kms().schedule_render( + &data.state.common.event_loop_handle, + &output, + scheduled_sessions, + ) { + slog_scope::warn!("Failed to schedule render: {}", err); } } } @@ -697,6 +724,7 @@ impl Device { vrr, refresh_rate, pending: false, + dirty: false, render_timer_token: None, #[cfg(feature = "debug")] fps: Fps::default(), @@ -986,6 +1014,7 @@ impl KmsState { return Ok(()); } if !surface.pending { + surface.dirty = false; surface.pending = true; /* let instant = surface @@ -1039,6 +1068,8 @@ impl KmsState { TimeoutAction::Drop }, )?); + } else { + surface.dirty = true; } } Ok(()) diff --git a/src/wayland/handlers/screencopy.rs b/src/wayland/handlers/screencopy.rs index 5327134b..a3180f84 100644 --- a/src/wayland/handlers/screencopy.rs +++ b/src/wayland/handlers/screencopy.rs @@ -901,6 +901,18 @@ impl State { } } + pub fn workspace_session_for_output( + &mut self, + output: &Output, + ) -> Option> { + let workspace = self.common.shell.active_space_mut(output); + if !workspace.pending_buffers.is_empty() { + Some(std::mem::take(&mut workspace.pending_buffers)) + } else { + None + } + } + pub fn schedule_workspace_sessions( &mut self, surface: &WlSurface, From 2bafd2230d1aff709cb3bbe7d0ed319ac17e36cf Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 10 Nov 2022 12:51:59 +0100 Subject: [PATCH 33/62] layer: Fix panic on destroying layer-shell surface --- src/wayland/handlers/layer_shell.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/wayland/handlers/layer_shell.rs b/src/wayland/handlers/layer_shell.rs index b6124487..2083d541 100644 --- a/src/wayland/handlers/layer_shell.rs +++ b/src/wayland/handlers/layer_shell.rs @@ -66,12 +66,14 @@ impl WlrLayerShellHandler for State { .cloned(); if let Some(output) = maybe_output { - let mut map = layer_map_for_output(&output); - let layer = map - .layer_for_surface(surface.wl_surface(), WindowSurfaceType::TOPLEVEL) - .unwrap() - .clone(); - map.unmap_layer(&layer); + { + let mut map = layer_map_for_output(&output); + let layer = map + .layer_for_surface(surface.wl_surface(), WindowSurfaceType::TOPLEVEL) + .unwrap() + .clone(); + map.unmap_layer(&layer); + } // collect screencopy sessions needing an update let mut scheduled_sessions = self.schedule_workspace_sessions(surface.wl_surface()); From dec2a8e0b6912e962d821976d742c6c88742f307 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 10 Nov 2022 12:57:36 +0100 Subject: [PATCH 34/62] shell/tiling: Simplify sizing logic --- src/shell/layout/tiling/mod.rs | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 731b4f89..5cf42adb 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -1059,16 +1059,12 @@ impl TilingLayout { .map(|(output_data, tree)| (&output_data.output, tree)) { if let Some(root) = tree.root_node_id() { - let mut stack = Vec::new(); - - let mut geo = Some(layer_map_for_output(&output).non_exclusive_zone()); - // TODO saturate? minimum? - if let Some(mut geo) = geo.as_mut() { - geo.loc.x += outer; - geo.loc.y += outer; - geo.size.w -= outer * 2; - geo.size.h -= outer * 2; - } + let mut geo = layer_map_for_output(&output).non_exclusive_zone(); + geo.loc.x += outer; + geo.loc.y += outer; + geo.size.w -= outer * 2; + geo.size.h -= outer * 2; + let mut stack = vec![geo]; for node_id in tree .traverse_pre_order_ids(root) @@ -1077,8 +1073,7 @@ impl TilingLayout { .into_iter() { let node = tree.get_mut(&node_id).unwrap(); - let geo = stack.pop().unwrap_or(geo); - if let Some(geo) = geo { + if let Some(geo) = stack.pop() { let data = node.data_mut(); data.update_geometry(geo); match data { @@ -1089,20 +1084,20 @@ impl TilingLayout { let mut previous: i32 = sizes.iter().sum(); for size in sizes.iter().rev() { previous -= *size; - stack.push(Some(Rectangle::from_loc_and_size( + stack.push(Rectangle::from_loc_and_size( (geo.loc.x, geo.loc.y + previous), (geo.size.w, *size), - ))); + )); } } Orientation::Vertical => { let mut previous: i32 = sizes.iter().sum(); for size in sizes.iter().rev() { previous -= *size; - stack.push(Some(Rectangle::from_loc_and_size( + stack.push(Rectangle::from_loc_and_size( (geo.loc.x + previous, geo.loc.y), (*size, geo.size.h), - ))); + )); } } }, @@ -1116,9 +1111,6 @@ impl TilingLayout { } } } - } else if node.data().is_group() { - stack.push(None); - stack.push(None); } } } From 4f443a3aabd0f54965b9b7c6df39cec09b4f28f8 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 10 Nov 2022 13:01:58 +0100 Subject: [PATCH 35/62] shell: Fix workspace numbering off-by-one --- src/shell/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 055aa50b..9b316787 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -461,7 +461,7 @@ impl Shell { ); workspace_set_idx( &mut state, - new_set.workspaces.len() as u8, + new_set.workspaces.len() as u8 + 1, &workspace_handle, ); workspace.handle = workspace_handle; @@ -569,7 +569,7 @@ impl Shell { &workspace_handle, [WorkspaceCapabilities::Activate].into_iter(), ); - workspace_set_idx(&mut state, i as u8, &workspace_handle); + workspace_set_idx(&mut state, i as u8 + 1, &workspace_handle); let mut new_workspace = Workspace::new(workspace_handle); for output in self.outputs.iter() { @@ -629,7 +629,7 @@ impl Shell { &new_workspace_handle, [WorkspaceCapabilities::Activate].into_iter(), ); - workspace_set_idx(&mut state, i as u8, &new_workspace_handle); + workspace_set_idx(&mut state, i as u8 + 1, &new_workspace_handle); let mut old_tiling_layer = workspace.tiling_layer.clone(); let mut new_floating_layer = FloatingLayout::new(); @@ -1048,6 +1048,6 @@ fn workspace_set_idx<'a>( idx: u8, handle: &WorkspaceHandle, ) { - state.set_workspace_name(&handle, format!("{}", idx + 1)); + state.set_workspace_name(&handle, format!("{}", idx)); state.set_workspace_coordinates(&handle, [Some(idx as u32), None, None]); } From 6cec3cb7e0488a99a38c8a09163c1fcd6d073b2b Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 10 Nov 2022 17:22:16 +0100 Subject: [PATCH 36/62] shell: Track workspace amount per set --- src/shell/mod.rs | 74 ++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 9b316787..48018596 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -49,7 +49,6 @@ pub struct Shell { pub popups: PopupManager, pub outputs: Vec, pub workspaces: WorkspaceMode, - pub workspace_amount: WorkspaceAmount, pub floating_default: bool, pub pending_windows: Vec<(Window, Seat)>, pub pending_layers: Vec<(LayerSurface, Output, Seat)>, @@ -65,6 +64,7 @@ pub struct Shell { #[derive(Debug)] pub struct WorkspaceSet { active: usize, + amount: WorkspaceAmount, group: WorkspaceGroupHandle, workspaces: Vec, } @@ -120,6 +120,7 @@ impl WorkspaceSet { WorkspaceSet { active: 0, + amount, group: group_handle, workspaces, } @@ -136,12 +137,11 @@ impl WorkspaceSet { fn refresh<'a>( &mut self, - amount: WorkspaceAmount, state: &mut WorkspaceState, toplevel_info: &mut ToplevelInfoState, outputs: impl Iterator)>, ) { - match amount { + match self.amount { WorkspaceAmount::Dynamic => self.ensure_last_empty(state, outputs), WorkspaceAmount::Static(len) => { self.ensure_static(len as usize, state, toplevel_info, outputs) @@ -257,7 +257,7 @@ impl WorkspaceSet { #[derive(Debug)] pub enum WorkspaceMode { - OutputBound(HashMap), + OutputBound(HashMap, WorkspaceAmount), Global(WorkspaceSet), } @@ -271,14 +271,16 @@ impl WorkspaceMode { crate::config::WorkspaceMode::Global => { WorkspaceMode::Global(WorkspaceSet::new(state, amount)) } - crate::config::WorkspaceMode::OutputBound => WorkspaceMode::OutputBound(HashMap::new()), + crate::config::WorkspaceMode::OutputBound => { + WorkspaceMode::OutputBound(HashMap::new(), amount) + } } } pub fn get(&self, num: usize, output: &Output) -> Option<&Workspace> { match self { WorkspaceMode::Global(set) => set.workspaces.get(num), - WorkspaceMode::OutputBound(sets) => { + WorkspaceMode::OutputBound(sets, _) => { sets.get(output).and_then(|set| set.workspaces.get(num)) } } @@ -287,7 +289,7 @@ impl WorkspaceMode { pub fn get_mut(&mut self, num: usize, output: &Output) -> Option<&mut Workspace> { match self { WorkspaceMode::Global(set) => set.workspaces.get_mut(num), - WorkspaceMode::OutputBound(sets) => sets + WorkspaceMode::OutputBound(sets, _) => sets .get_mut(output) .and_then(|set| set.workspaces.get_mut(num)), } @@ -296,7 +298,7 @@ impl WorkspaceMode { pub fn active(&self, output: &Output) -> &Workspace { match self { WorkspaceMode::Global(set) => &set.workspaces[set.active], - WorkspaceMode::OutputBound(sets) => { + WorkspaceMode::OutputBound(sets, _) => { let set = sets.get(output).unwrap(); &set.workspaces[set.active] } @@ -306,17 +308,17 @@ impl WorkspaceMode { pub fn active_mut(&mut self, output: &Output) -> &mut Workspace { match self { WorkspaceMode::Global(set) => &mut set.workspaces[set.active], - WorkspaceMode::OutputBound(sets) => { + WorkspaceMode::OutputBound(sets, _) => { let set = sets.get_mut(output).unwrap(); &mut set.workspaces[set.active] } } } - fn active_num(&self, output: &Output) -> usize { + pub fn active_num(&self, output: &Output) -> usize { match self { WorkspaceMode::Global(set) => set.active, - WorkspaceMode::OutputBound(sets) => { + WorkspaceMode::OutputBound(sets, _) => { let set = sets.get(output).unwrap(); set.active } @@ -328,7 +330,7 @@ impl WorkspaceMode { WorkspaceMode::Global(set) => { Box::new(set.workspaces.iter()) as Box> } - WorkspaceMode::OutputBound(sets) => { + WorkspaceMode::OutputBound(sets, _) => { Box::new(sets.values().flat_map(|set| set.workspaces.iter())) } } @@ -339,7 +341,7 @@ impl WorkspaceMode { WorkspaceMode::Global(set) => { Box::new(set.workspaces.iter()) as Box> } - WorkspaceMode::OutputBound(sets) => Box::new( + WorkspaceMode::OutputBound(sets, _) => Box::new( sets.get(output) .into_iter() .flat_map(|set| set.workspaces.iter()), @@ -352,7 +354,7 @@ impl WorkspaceMode { WorkspaceMode::Global(set) => { Box::new(set.workspaces.iter_mut()) as Box> } - WorkspaceMode::OutputBound(sets) => { + WorkspaceMode::OutputBound(sets, _) => { Box::new(sets.values_mut().flat_map(|set| set.workspaces.iter_mut())) } } @@ -384,7 +386,6 @@ impl Shell { |_| true, ); - let amount = config.static_conf.workspace_amount; let mode = WorkspaceMode::new( config.static_conf.workspace_mode, config.static_conf.workspace_amount, @@ -396,7 +397,6 @@ impl Shell { popups: PopupManager::new(None), outputs: Vec::new(), workspaces: mode, - workspace_amount: amount, floating_default, pending_windows: Vec::new(), @@ -415,13 +415,10 @@ impl Shell { let mut state = self.workspace_state.update(); match &mut self.workspaces { - WorkspaceMode::OutputBound(sets) => { + WorkspaceMode::OutputBound(sets, amount) => { // TODO: Restore previously assigned workspaces, if possible! if !sets.contains_key(output) { - sets.insert( - output.clone(), - WorkspaceSet::new(&mut state, self.workspace_amount), - ); + sets.insert(output.clone(), WorkspaceSet::new(&mut state, *amount)); } for workspace in &mut sets.get_mut(output).unwrap().workspaces { workspace.map_output(output, (0, 0).into()); @@ -442,7 +439,7 @@ impl Shell { self.outputs.retain(|o| o != output); match &mut self.workspaces { - WorkspaceMode::OutputBound(sets) => { + WorkspaceMode::OutputBound(sets, _) => { if let Some(set) = sets.remove(output) { // TODO: Heuristic which output to move to. // It is supposed to be the *most* internal, we just pick the first one for now @@ -504,13 +501,14 @@ impl Shell { let mut state = self.workspace_state.update(); match (&mut self.workspaces, mode) { - (dst @ WorkspaceMode::OutputBound(_), ConfigMode::Global) => { + (dst @ WorkspaceMode::OutputBound(_, _), ConfigMode::Global) => { // rustc should really be able to infer that this doesn't need an if. - let sets = if let &mut WorkspaceMode::OutputBound(ref mut sets) = dst { - sets - } else { - unreachable!() - }; + let (sets, amount) = + if let &mut WorkspaceMode::OutputBound(ref mut sets, ref amount) = dst { + (sets, *amount) + } else { + unreachable!() + }; // in this case we have to merge our sets, preserving placing of windows as nicely as possible let mut new_set = WorkspaceSet::new(&mut state, WorkspaceAmount::Static(0)); @@ -602,6 +600,7 @@ impl Shell { state.remove_workspace_group(group); } + new_set.amount = amount; *dst = WorkspaceMode::Global(new_set); } (dst @ WorkspaceMode::Global(_), ConfigMode::OutputBound) => { @@ -696,7 +695,10 @@ impl Shell { } state.remove_workspace_group(set.group); - *dst = WorkspaceMode::OutputBound(sets); + for new_set in sets.values_mut() { + new_set.amount = set.amount; + } + *dst = WorkspaceMode::OutputBound(sets, set.amount); } _ => {} } @@ -707,7 +709,7 @@ impl Shell { pub fn activate(&mut self, output: &Output, idx: usize) -> Option { match &mut self.workspaces { - WorkspaceMode::OutputBound(sets) => { + WorkspaceMode::OutputBound(sets, _) => { if let Some(set) = sets.get_mut(output) { set.activate(idx, &mut self.workspace_state.update()); } @@ -722,7 +724,7 @@ impl Shell { pub fn active_space(&self, output: &Output) -> &Workspace { match &self.workspaces { - WorkspaceMode::OutputBound(sets) => { + WorkspaceMode::OutputBound(sets, _) => { let set = sets.get(output).unwrap(); &set.workspaces[set.active] } @@ -732,7 +734,7 @@ impl Shell { pub fn active_space_mut(&mut self, output: &Output) -> &mut Workspace { match &mut self.workspaces { - WorkspaceMode::OutputBound(sets) => { + WorkspaceMode::OutputBound(sets, _) => { let set = sets.get_mut(output).unwrap(); &mut set.workspaces[set.active] } @@ -852,7 +854,7 @@ impl Shell { ) -> Point { match self.workspaces { WorkspaceMode::Global(_) => global_loc.into(), - WorkspaceMode::OutputBound(_) => { + WorkspaceMode::OutputBound(_, _) => { let p = global_loc.into().to_f64() - output.current_location().to_f64(); (C::from_f64(p.x), C::from_f64(p.y)).into() } @@ -866,7 +868,7 @@ impl Shell { ) -> Point { match self.workspaces { WorkspaceMode::Global(_) => space_loc.into(), - WorkspaceMode::OutputBound(_) => { + WorkspaceMode::OutputBound(_, _) => { let p = space_loc.into().to_f64() + output.current_location().to_f64(); (C::from_f64(p.x), C::from_f64(p.y)).into() } @@ -877,10 +879,9 @@ impl Shell { self.popups.cleanup(); match &mut self.workspaces { - WorkspaceMode::OutputBound(sets) => { + WorkspaceMode::OutputBound(sets, _) => { for (output, set) in sets.iter_mut() { set.refresh( - self.workspace_amount, &mut self.workspace_state, &mut self.toplevel_info_state, std::iter::once((output, (0, 0).into())), @@ -888,7 +889,6 @@ impl Shell { } } WorkspaceMode::Global(set) => set.refresh( - self.workspace_amount, &mut self.workspace_state, &mut self.toplevel_info_state, self.outputs.iter().map(|o| (o, o.current_location())), From 544acecd2e5431dd3e177ab3f068c76c3b6218d5 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 10 Nov 2022 18:42:11 +0100 Subject: [PATCH 37/62] input: Various new keybindings --- config.ron | 130 +++++---- src/config/mod.rs | 23 +- src/input/mod.rs | 520 ++++++++++++++++++++------------- src/shell/layout/tiling/mod.rs | 17 +- src/shell/mod.rs | 60 ++-- src/shell/workspace.rs | 6 +- 6 files changed, 476 insertions(+), 280 deletions(-) diff --git a/config.ron b/config.ron index 13e3c72e..c1795c9c 100644 --- a/config.ron +++ b/config.ron @@ -1,57 +1,89 @@ ( key_bindings: { - (modifiers: [Logo, Shift], key: "Escape"): Terminate, - (modifiers: [Logo], key: "Escape"): Debug, - (modifiers: [Logo], key: "q"): Close, - (modifiers: [Logo], key: "1"): Workspace(1), - (modifiers: [Logo], key: "2"): Workspace(2), - (modifiers: [Logo], key: "3"): Workspace(3), - (modifiers: [Logo], key: "4"): Workspace(4), - (modifiers: [Logo], key: "5"): Workspace(5), - (modifiers: [Logo], key: "6"): Workspace(6), - (modifiers: [Logo], key: "7"): Workspace(7), - (modifiers: [Logo], key: "8"): Workspace(8), - (modifiers: [Logo], key: "9"): Workspace(9), - (modifiers: [Logo], key: "0"): Workspace(0), - (modifiers: [Logo, Shift], key: "1"): MoveToWorkspace(1), - (modifiers: [Logo, Shift], key: "2"): MoveToWorkspace(2), - (modifiers: [Logo, Shift], key: "3"): MoveToWorkspace(3), - (modifiers: [Logo, Shift], key: "4"): MoveToWorkspace(4), - (modifiers: [Logo, Shift], key: "5"): MoveToWorkspace(5), - (modifiers: [Logo, Shift], key: "6"): MoveToWorkspace(6), - (modifiers: [Logo, Shift], key: "7"): MoveToWorkspace(7), - (modifiers: [Logo, Shift], key: "8"): MoveToWorkspace(8), - (modifiers: [Logo, Shift], key: "9"): MoveToWorkspace(9), - (modifiers: [Logo, Shift], key: "0"): MoveToWorkspace(0), - (modifiers: [Logo], key: "Left"): Focus(Left), - (modifiers: [Logo], key: "Right"): Focus(Right), - (modifiers: [Logo], key: "Up"): Focus(Up), - (modifiers: [Logo], key: "Down"): Focus(Down), - (modifiers: [Logo], key: "h"): Focus(Left), - (modifiers: [Logo], key: "j"): Focus(Down), - (modifiers: [Logo], key: "k"): Focus(Up), - (modifiers: [Logo], key: "l"): Focus(Right), - (modifiers: [Logo, Shift], key: "Left"): Move(Left), - (modifiers: [Logo, Shift], key: "Right"): Move(Right), - (modifiers: [Logo, Shift], key: "Up"): Move(Up), - (modifiers: [Logo, Shift], key: "Down"): Move(Down), - (modifiers: [Logo, Shift], key: "h"): Move(Left), - (modifiers: [Logo, Shift], key: "j"): Move(Down), - (modifiers: [Logo, Shift], key: "k"): Move(Up), - (modifiers: [Logo, Shift], key: "l"): Move(Right), - (modifiers: [Logo], key: "o"): ToggleOrientation, - (modifiers: [Logo], key: "y"): ToggleTiling, - (modifiers: [Logo], key: "g"): ToggleWindowFloating, - (modifiers: [Logo, Shift], key: "f"): Fullscreen, - //(modifiers: [Logo, Shift], key: "s"): Screenshot, + (modifiers: [Super, Shift], key: "Escape"): Terminate, + (modifiers: [Super], key: "Escape"): Debug, + (modifiers: [Super], key: "q"): Close, + + (modifiers: [Super], key: "1"): Workspace(1), + (modifiers: [Super], key: "2"): Workspace(2), + (modifiers: [Super], key: "3"): Workspace(3), + (modifiers: [Super], key: "4"): Workspace(4), + (modifiers: [Super], key: "5"): Workspace(5), + (modifiers: [Super], key: "6"): Workspace(6), + (modifiers: [Super], key: "7"): Workspace(7), + (modifiers: [Super], key: "8"): Workspace(8), + (modifiers: [Super], key: "9"): Workspace(9), + (modifiers: [Super], key: "0"): LastWorkspace, + (modifiers: [Super, Shift], key: "1"): MoveToWorkspace(1), + (modifiers: [Super, Shift], key: "2"): MoveToWorkspace(2), + (modifiers: [Super, Shift], key: "3"): MoveToWorkspace(3), + (modifiers: [Super, Shift], key: "4"): MoveToWorkspace(4), + (modifiers: [Super, Shift], key: "5"): MoveToWorkspace(5), + (modifiers: [Super, Shift], key: "6"): MoveToWorkspace(6), + (modifiers: [Super, Shift], key: "7"): MoveToWorkspace(7), + (modifiers: [Super, Shift], key: "8"): MoveToWorkspace(8), + (modifiers: [Super, Shift], key: "9"): MoveToWorkspace(9), + (modifiers: [Super, Shift], key: "0"): MoveToLastWorkspace, + + // TODO: Depends on workspace orientation + (modifiers: [Super, Ctrl], key: "Right"): NextWorkspace, + (modifiers: [Super, Ctrl], key: "Left"): PreviousWorkspace, + (modifiers: [Super, Ctrl, Shift], key: "Right"): MoveToNextWorkspace, + (modifiers: [Super, Ctrl, Shift], key: "Left"): MoveToPreviousWorkspace, + (modifiers: [Super, Ctrl], key: "l"): NextWorkspace, + (modifiers: [Super, Ctrl], key: "h"): PreviousWorkspace, + (modifiers: [Super, Ctrl, Shift], key: "l"): MoveToNextWorkspace, + (modifiers: [Super, Ctrl, Shift], key: "h"): MoveToPreviousWorkspace, + + (modifiers: [Super, Ctrl], key: "Down"): NextOutput, + (modifiers: [Super, Ctrl], key: "Up"): PreviousOutput, + (modifiers: [Super, Ctrl, Alt], key: "Down"): NextOutput, + (modifiers: [Super, Ctrl, Alt], key: "Up"): PreviousOutput, + (modifiers: [Super, Ctrl], key: "j"): NextOutput, + (modifiers: [Super, Ctrl], key: "k"): PreviousOutput, + (modifiers: [Super, Ctrl, Alt], key: "j"): NextOutput, + (modifiers: [Super, Ctrl, Alt], key: "k"): PreviousOutput, + + (modifiers: [Super], key: "Period"): NextOutput, + (modifiers: [Super], key: "Comma"): PreviousOutput, + (modifiers: [Super, Shift], key: "Period"): MoveToNextOutput, + (modifiers: [Super, Shift], key: "Comma"): MoveToPreviousOutput, + + (modifiers: [Super], key: "Left"): Focus(Left), + (modifiers: [Super], key: "Right"): Focus(Right), + (modifiers: [Super], key: "Up"): Focus(Up), + (modifiers: [Super], key: "Down"): Focus(Down), + (modifiers: [Super], key: "h"): Focus(Left), + (modifiers: [Super], key: "j"): Focus(Down), + (modifiers: [Super], key: "k"): Focus(Up), + (modifiers: [Super], key: "l"): Focus(Right), + + (modifiers: [Super, Shift], key: "Left"): Move(Left), + (modifiers: [Super, Shift], key: "Right"): Move(Right), + (modifiers: [Super, Shift], key: "Up"): Move(Up), + (modifiers: [Super, Shift], key: "Down"): Move(Down), + (modifiers: [Super, Shift], key: "h"): Move(Left), + (modifiers: [Super, Shift], key: "j"): Move(Down), + (modifiers: [Super, Shift], key: "k"): Move(Up), + (modifiers: [Super, Shift], key: "l"): Move(Right), + + (modifiers: [Super], key: "o"): ToggleOrientation, + + (modifiers: [Super], key: "y"): ToggleTiling, + (modifiers: [Super], key: "g"): ToggleWindowFloating, + + (modifiers: [Super], key: "m"): Maximize, + //TODO: ability to select default web browser - (modifiers: [Logo], key: "b"): Spawn("firefox"), + (modifiers: [Super], key: "b"): Spawn("firefox"), //TODO: ability to select default file browser - (modifiers: [Logo], key: "f"): Spawn("nautilus"), + (modifiers: [Super], key: "f"): Spawn("nautilus"), //TODO: ability to select default terminal - (modifiers: [Logo], key: "t"): Spawn("gnome-terminal"), - (modifiers: [Logo], key: "a"): Spawn("busctl --user call com.system76.CosmicAppletHost /com/system76/CosmicAppletHost com.system76.CosmicAppletHost Toggle s 'com.system76.CosmicAppLibrary'"), - (modifiers: [Logo], key: "slash"): Spawn("busctl --user call com.system76.CosmicAppletHost /com/system76/CosmicAppletHost com.system76.CosmicAppletHost Toggle s 'com.system76.CosmicLauncher'"), + (modifiers: [Super], key: "t"): Spawn("gnome-terminal"), + + (modifiers: [Super], key: "a"): Spawn("busctl --user call com.system76.CosmicAppletHost /com/system76/CosmicAppletHost com.system76.CosmicAppletHost Toggle s 'com.system76.CosmicAppLibrary'"), + (modifiers: [Super], key: "slash"): Spawn("busctl --user call com.system76.CosmicAppletHost /com/system76/CosmicAppletHost com.system76.CosmicAppletHost Toggle s 'com.system76.CosmicLauncher'"), + (modifiers: [], key: "XF86AudioRaiseVolume"): Spawn("amixer sset Master 5%+"), (modifiers: [], key: "XF86AudioLowerVolume"): Spawn("amixer sset Master 5%-"), (modifiers: [], key: "XF86AudioMute"): Spawn("amixer sset Master toggle"), diff --git a/src/config/mod.rs b/src/config/mod.rs index 340c4977..16dd0f23 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -691,7 +691,7 @@ pub enum KeyModifier { Ctrl, Alt, Shift, - Logo, + Super, CapsLock, NumLock, } @@ -723,7 +723,7 @@ impl std::ops::AddAssign for KeyModifiers { KeyModifier::Ctrl => self.ctrl = true, KeyModifier::Alt => self.alt = true, KeyModifier::Shift => self.shift = true, - KeyModifier::Logo => self.logo = true, + KeyModifier::Super => self.logo = true, KeyModifier::CapsLock => self.caps_lock = true, KeyModifier::NumLock => self.num_lock = true, }; @@ -782,15 +782,30 @@ pub enum Action { Terminate, Debug, Close, + Workspace(u8), + NextWorkspace, + PreviousWorkspace, + LastWorkspace, MoveToWorkspace(u8), + MoveToNextWorkspace, + MoveToPreviousWorkspace, + MoveToLastWorkspace, + + NextOutput, + PreviousOutput, + MoveToNextOutput, + MoveToPreviousOutput, + Focus(FocusDirection), Move(Direction), + ToggleOrientation, Orientation(crate::shell::layout::Orientation), + ToggleTiling, ToggleWindowFloating, - Fullscreen, - //Screenshot, + + Maximize, Spawn(String), } diff --git a/src/input/mod.rs b/src/input/mod.rs index de70aaf5..ee60d2de 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -2,7 +2,14 @@ use crate::{ config::{Action, Config}, - shell::{focus::target::PointerFocusTarget, layout::floating::SeatMoveGrabState, Workspace}, // shell::grabs::SeatMoveGrabState + shell::{ + focus::{target::PointerFocusTarget, FocusDirection}, + layout::{ + floating::SeatMoveGrabState, + tiling::{Direction, FocusResult}, + }, + Workspace, + }, // shell::grabs::SeatMoveGrabState state::Common, utils::prelude::*, wayland::{handlers::screencopy::ScreencopySessions, protocols::screencopy::Session}, @@ -301,206 +308,7 @@ impl State { ) .flatten() { - match action { - Action::Terminate => { - self.common.should_stop = true; - } - #[cfg(feature = "debug")] - Action::Debug => { - self.common.egui.active = !self.common.egui.active; - } - #[cfg(not(feature = "debug"))] - Action::Debug => { - slog_scope::info!("Debug overlay not included in this version") - } - Action::Close => { - let current_output = seat.active_output(); - let workspace = - self.common.shell.active_space_mut(¤t_output); - if let Some(window) = workspace.focus_stack.get(seat).last() { - window.send_close(); - } - } - Action::Workspace(key_num) => { - let current_output = seat.active_output(); - let workspace = match key_num { - 0 => 9, - x => x - 1, - }; - if let Some(motion_event) = self - .common - .shell - .activate(¤t_output, workspace as usize) - { - if let Some(ptr) = seat.get_pointer() { - ptr.motion(self, None, &motion_event); - } - } - } - Action::MoveToWorkspace(key_num) => { - let current_output = seat.active_output(); - let workspace = match key_num { - 0 => 9, - x => x - 1, - }; - Shell::move_current_window( - self, - seat, - ¤t_output, - workspace as usize, - ); - } - Action::Focus(focus) => { - let current_output = seat.active_output(); - let workspace = - self.common.shell.active_space_mut(¤t_output); - let focus_stack = workspace.focus_stack.get(seat); - if let Some(target) = workspace.tiling_layer.next_focus( - focus, - seat, - focus_stack.iter(), - ) { - std::mem::drop(focus_stack); - Common::set_focus(self, Some(&target), seat, None); - } - } - Action::Move(direction) => { - let current_output = seat.active_output(); - let workspace = - self.common.shell.active_space_mut(¤t_output); - if let Some(_move_further) = - workspace.tiling_layer.move_current_window(direction, seat) - { - // TODO moving across workspaces/displays - } - } - Action::Fullscreen => { - let current_output = seat.active_output(); - let workspace = - self.common.shell.active_space_mut(¤t_output); - let focus_stack = workspace.focus_stack.get(seat); - let focused_window = focus_stack.last(); - if let Some(window) = focused_window.map(|f| f.active_window()) - { - workspace.fullscreen_toggle(&window, ¤t_output); - } - } - Action::ToggleOrientation => { - let output = seat.active_output(); - let workspace = self.common.shell.active_space_mut(&output); - let focus_stack = workspace.focus_stack.get(seat); - workspace.tiling_layer.update_orientation( - None, - &seat, - focus_stack.iter(), - ); - } - Action::Orientation(orientation) => { - let output = seat.active_output(); - let workspace = self.common.shell.active_space_mut(&output); - let focus_stack = workspace.focus_stack.get(seat); - workspace.tiling_layer.update_orientation( - Some(orientation), - &seat, - focus_stack.iter(), - ); - } - Action::ToggleTiling => { - let output = seat.active_output(); - let workspace = self.common.shell.active_space_mut(&output); - workspace.toggle_tiling(seat); - } - Action::ToggleWindowFloating => { - let output = seat.active_output(); - let workspace = self.common.shell.active_space_mut(&output); - workspace.toggle_floating_window(seat); - } - Action::Spawn(command) => { - if let Err(err) = std::process::Command::new("/bin/sh") - .arg("-c") - .arg(command) - .env("WAYLAND_DISPLAY", &self.common.socket) - .env_remove("COSMIC_SESSION_SOCK") - .spawn() - { - slog_scope::warn!("Failed to spawn: {}", err); - } - } /* - Action::Screenshot => { - let home = match std::env::var("HOME") { - Ok(home) => home, - Err(err) => { - slog_scope::error!( - "$HOME is not set, can't save screenshots: {}", - err - ); - break; - } - }; - let timestamp = match std::time::SystemTime::UNIX_EPOCH - .elapsed() - { - Ok(duration) => duration.as_secs(), - Err(err) => { - slog_scope::error!("Unable to get timestamp, can't save screenshots: {}", err); - break; - } - }; - for output in self.common.shell.outputs.clone().into_iter() { - match self - .backend - .offscreen_for_output(&output, &mut self.common) - { - Ok((buffer, size)) => { - let mut path = std::path::PathBuf::new(); - path.push(&home); - path.push(format!( - "{}_{}.png", - output.name(), - timestamp - )); - - fn write_png( - path: impl AsRef, - data: Vec, - size: Size, - ) -> anyhow::Result<()> - { - use std::{fs, io}; - - let file = io::BufWriter::new( - fs::File::create(&path)?, - ); - let mut encoder = png::Encoder::new( - file, - size.w as u32, - size.h as u32, - ); - encoder.set_color(png::ColorType::Rgba); - encoder.set_depth(png::BitDepth::Eight); - let mut writer = encoder.write_header()?; - writer.write_image_data(&data)?; - Ok(()) - } - - if let Err(err) = write_png(&path, buffer, size) { - slog_scope::error!( - "Unable to save screenshot at {}: {}", - path.display(), - err - ); - } - } - Err(err) => slog_scope::error!( - "Could not save screenshot for output {}: {}", - output.name(), - err - ), - } - } - } - */ - } + self.handle_action(action, seat) } break; } @@ -815,6 +623,316 @@ impl State { } } + fn handle_action(&mut self, action: Action, seat: &Seat) { + match action { + Action::Terminate => { + self.common.should_stop = true; + } + #[cfg(feature = "debug")] + Action::Debug => { + self.common.egui.active = !self.common.egui.active; + } + #[cfg(not(feature = "debug"))] + Action::Debug => { + slog_scope::info!("Debug overlay not included in this version") + } + Action::Close => { + let current_output = seat.active_output(); + let workspace = self.common.shell.active_space_mut(¤t_output); + if let Some(window) = workspace.focus_stack.get(seat).last() { + window.send_close(); + } + } + Action::Workspace(key_num) => { + let current_output = seat.active_output(); + let workspace = match key_num { + 0 => 9, + x => x - 1, + }; + if let Some(motion_event) = self + .common + .shell + .activate(¤t_output, workspace as usize) + { + if let Some(ptr) = seat.get_pointer() { + ptr.motion(self, None, &motion_event); + } + } + } + Action::NextWorkspace => { + let current_output = seat.active_output(); + let workspace = self + .common + .shell + .workspaces + .active_num(¤t_output) + .saturating_add(1); + // TODO: Possibly move to next output, if idx to large + if let Some(motion_event) = self.common.shell.activate(¤t_output, workspace) { + if let Some(ptr) = seat.get_pointer() { + ptr.motion(self, None, &motion_event); + } + } + } + Action::PreviousWorkspace => { + let current_output = seat.active_output(); + let workspace = self + .common + .shell + .workspaces + .active_num(¤t_output) + .saturating_sub(1); + // TODO: Possibly move to prev output, if idx < 0 + if let Some(motion_event) = self.common.shell.activate(¤t_output, workspace) { + if let Some(ptr) = seat.get_pointer() { + ptr.motion(self, None, &motion_event); + } + } + } + Action::LastWorkspace => { + let current_output = seat.active_output(); + let workspace = self + .common + .shell + .workspaces + .len(¤t_output) + .saturating_sub(1); + if let Some(motion_event) = self.common.shell.activate(¤t_output, workspace) { + if let Some(ptr) = seat.get_pointer() { + ptr.motion(self, None, &motion_event); + } + } + } + Action::MoveToWorkspace(key_num) => { + let current_output = seat.active_output(); + let workspace = match key_num { + 0 => 9, + x => x - 1, + }; + Shell::move_current_window( + self, + seat, + ¤t_output, + (¤t_output, Some(workspace as usize)), + ); + } + Action::MoveToNextWorkspace => { + let current_output = seat.active_output(); + let workspace = self + .common + .shell + .workspaces + .active_num(¤t_output) + .saturating_add(1); + // TODO: Possibly move to next output, if idx too large + Shell::move_current_window( + self, + seat, + ¤t_output, + (¤t_output, Some(workspace as usize)), + ); + } + Action::MoveToPreviousWorkspace => { + let current_output = seat.active_output(); + let workspace = self + .common + .shell + .workspaces + .active_num(¤t_output) + .saturating_sub(1); + // TODO: Possibly move to prev output, if idx < 0 + Shell::move_current_window( + self, + seat, + ¤t_output, + (¤t_output, Some(workspace as usize)), + ); + } + Action::MoveToLastWorkspace => { + let current_output = seat.active_output(); + let workspace = self + .common + .shell + .workspaces + .len(¤t_output) + .saturating_sub(1); + Shell::move_current_window( + self, + seat, + ¤t_output, + (¤t_output, Some(workspace as usize)), + ); + } + Action::NextOutput => { + let current_output = seat.active_output(); + if let Some(next_output) = self + .common + .shell + .outputs + .iter() + .skip_while(|o| *o != ¤t_output) + .skip(1) + .next() + .cloned() + { + let idx = self.common.shell.workspaces.active_num(&next_output); + if let Some(motion_event) = self.common.shell.activate(&next_output, idx) { + if let Some(ptr) = seat.get_pointer() { + ptr.motion(self, None, &motion_event); + } + } + } + } + Action::PreviousOutput => { + let current_output = seat.active_output(); + if let Some(prev_output) = self + .common + .shell + .outputs + .iter() + .rev() + .skip_while(|o| *o != ¤t_output) + .skip(1) + .next() + .cloned() + { + let idx = self.common.shell.workspaces.active_num(&prev_output); + if let Some(motion_event) = self.common.shell.activate(&prev_output, idx) { + if let Some(ptr) = seat.get_pointer() { + ptr.motion(self, None, &motion_event); + } + } + } + } + Action::MoveToNextOutput => { + let current_output = seat.active_output(); + if let Some(next_output) = self + .common + .shell + .outputs + .iter() + .skip_while(|o| *o != ¤t_output) + .skip(1) + .next() + .cloned() + { + Shell::move_current_window(self, seat, ¤t_output, (&next_output, None)); + } + } + Action::MoveToPreviousOutput => { + let current_output = seat.active_output(); + if let Some(prev_output) = self + .common + .shell + .outputs + .iter() + .rev() + .skip_while(|o| *o != ¤t_output) + .skip(1) + .next() + .cloned() + { + Shell::move_current_window(self, seat, ¤t_output, (&prev_output, None)); + } + } + Action::Focus(focus) => { + let current_output = seat.active_output(); + let workspace = self.common.shell.active_space_mut(¤t_output); + let focus_stack = workspace.focus_stack.get(seat); + match workspace + .tiling_layer + .next_focus(focus, seat, focus_stack.iter()) + { + FocusResult::None => { + // TODO: Handle Workspace orientation + match focus { + FocusDirection::Left => { + self.handle_action(Action::PreviousWorkspace, seat) + } + FocusDirection::Right => { + self.handle_action(Action::NextWorkspace, seat) + } + FocusDirection::Up => self.handle_action(Action::PreviousOutput, seat), + FocusDirection::Down => self.handle_action(Action::NextOutput, seat), + _ => {} + } + } + FocusResult::Handled => {} + FocusResult::Some(target) => { + std::mem::drop(focus_stack); + Common::set_focus(self, Some(&target), seat, None); + } + } + } + Action::Move(direction) => { + let current_output = seat.active_output(); + let workspace = self.common.shell.active_space_mut(¤t_output); + if let Some(_move_further) = + workspace.tiling_layer.move_current_window(direction, seat) + { + // TODO: Handle Workspace orientation + // TODO: Being able to move Groups (move_further should be KeyboardFocusTarget instead) + match direction { + Direction::Left => { + self.handle_action(Action::MoveToPreviousWorkspace, seat) + } + Direction::Right => self.handle_action(Action::MoveToNextWorkspace, seat), + Direction::Up => self.handle_action(Action::MoveToPreviousOutput, seat), + Direction::Down => self.handle_action(Action::MoveToNextOutput, seat), + } + } + } + Action::Maximize => { + let current_output = seat.active_output(); + let workspace = self.common.shell.active_space_mut(¤t_output); + let focus_stack = workspace.focus_stack.get(seat); + let focused_window = focus_stack.last(); + if let Some(window) = focused_window.map(|f| f.active_window()) { + workspace.maximize_toggle(&window, ¤t_output); + } + } + Action::ToggleOrientation => { + let output = seat.active_output(); + let workspace = self.common.shell.active_space_mut(&output); + let focus_stack = workspace.focus_stack.get(seat); + workspace + .tiling_layer + .update_orientation(None, &seat, focus_stack.iter()); + } + Action::Orientation(orientation) => { + let output = seat.active_output(); + let workspace = self.common.shell.active_space_mut(&output); + let focus_stack = workspace.focus_stack.get(seat); + workspace.tiling_layer.update_orientation( + Some(orientation), + &seat, + focus_stack.iter(), + ); + } + Action::ToggleTiling => { + let output = seat.active_output(); + let workspace = self.common.shell.active_space_mut(&output); + workspace.toggle_tiling(seat); + } + Action::ToggleWindowFloating => { + let output = seat.active_output(); + let workspace = self.common.shell.active_space_mut(&output); + workspace.toggle_floating_window(seat); + } + Action::Spawn(command) => { + if let Err(err) = std::process::Command::new("/bin/sh") + .arg("-c") + .arg(command) + .env("WAYLAND_DISPLAY", &self.common.socket) + .env_remove("COSMIC_SESSION_SOCK") + .spawn() + { + slog_scope::warn!("Failed to spawn: {}", err); + } + } + } + } + pub fn surface_under( global_pos: Point, relative_pos: Point, diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 5cf42adb..97b392ae 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -69,6 +69,13 @@ pub enum Direction { Down, } +#[derive(Debug, Clone, PartialEq)] +pub enum FocusResult { + None, + Handled, + Some(KeyboardFocusTarget), +} + #[derive(Debug, Clone)] pub struct TilingLayout { gaps: (i32, i32), @@ -660,7 +667,7 @@ impl TilingLayout { direction: FocusDirection, seat: &Seat, focus_stack: impl Iterator + 'a, - ) -> Option { + ) -> FocusResult { let output = seat.active_output(); let tree = self.trees.get_mut(&output).unwrap(); @@ -671,7 +678,7 @@ impl TilingLayout { // stacks may handle focus internally if last_window.handle_focus(direction) { - return None; + return FocusResult::Handled; } let mut node_id = last_node_id.clone(); @@ -682,7 +689,7 @@ impl TilingLayout { assert!(group_data.is_group()); if direction == FocusDirection::Out { - return Some( + return FocusResult::Some( WindowGroup { node: group.clone(), output: output.downgrade(), @@ -787,7 +794,7 @@ impl TilingLayout { }); } Data::Mapped { mapped, .. } => { - return Some(mapped.clone().into()); + return FocusResult::Some(mapped.clone().into()); } } } @@ -797,7 +804,7 @@ impl TilingLayout { } } - None + FocusResult::None } pub fn update_orientation<'a>( diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 48018596..7e32c841 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -325,6 +325,16 @@ impl WorkspaceMode { } } + pub fn len(&self, output: &Output) -> usize { + match self { + WorkspaceMode::Global(set) => set.workspaces.len(), + WorkspaceMode::OutputBound(sets, _) => { + let set = sets.get(output).unwrap(); + set.workspaces.len() + } + } + } + pub fn spaces(&self) -> impl Iterator { match self { WorkspaceMode::Global(set) => { @@ -970,16 +980,27 @@ impl Shell { } } - pub fn move_current_window(state: &mut State, seat: &Seat, output: &Output, idx: usize) { - if idx == state.common.shell.workspaces.active_num(output) { + pub fn move_current_window( + state: &mut State, + seat: &Seat, + from_output: &Output, + to: (&Output, Option), + ) { + let (to_output, to_idx) = to; + let to_idx = to_idx.unwrap_or(state.common.shell.workspaces.active_num(to_output)); + + if from_output == to_output + && to_idx == state.common.shell.workspaces.active_num(from_output) + { return; } - let old_workspace = state.common.shell.workspaces.active_mut(output); - let maybe_window = old_workspace.focus_stack.get(seat).last().cloned(); + let from_workspace = state.common.shell.workspaces.active_mut(from_output); + let maybe_window = from_workspace.focus_stack.get(seat).last().cloned(); + if let Some(mapped) = maybe_window { - let was_floating = old_workspace.floating_layer.unmap(&mapped); - let was_tiling = old_workspace.tiling_layer.unmap(&mapped); + let was_floating = from_workspace.floating_layer.unmap(&mapped); + let was_tiling = from_workspace.tiling_layer.unmap(&mapped); assert!(was_floating != was_tiling); for (toplevel, _) in mapped.windows() { @@ -987,22 +1008,25 @@ impl Shell { .common .shell .toplevel_info_state - .toplevel_leave_workspace(&toplevel, &old_workspace.handle); + .toplevel_leave_workspace(&toplevel, &from_workspace.handle); } - let elements = old_workspace.mapped().cloned().collect::>(); - std::mem::drop(old_workspace); + let elements = from_workspace.mapped().cloned().collect::>(); + std::mem::drop(from_workspace); for mapped in elements.into_iter() { state.common.shell.update_reactive_popups(&mapped); } - let new_workspace = state.common.shell.workspaces.get_mut(idx, output).unwrap(); // checked above - let focus_stack = new_workspace.focus_stack.get(&seat); + let to_workspace = state + .common + .shell + .workspaces + .get_mut(to_idx, to_output) + .unwrap(); // checked above + let focus_stack = to_workspace.focus_stack.get(&seat); if was_floating { - new_workspace - .floating_layer - .map(mapped.clone(), &seat, None); + to_workspace.floating_layer.map(mapped.clone(), &seat, None); } else { - new_workspace + to_workspace .tiling_layer .map(mapped.clone(), &seat, focus_stack.iter()); } @@ -1011,10 +1035,10 @@ impl Shell { .common .shell .toplevel_info_state - .toplevel_enter_workspace(&toplevel, &new_workspace.handle); + .toplevel_enter_workspace(&toplevel, &to_workspace.handle); } - for mapped in new_workspace + for mapped in to_workspace .mapped() .cloned() .collect::>() @@ -1023,7 +1047,7 @@ impl Shell { state.common.shell.update_reactive_popups(&mapped); } - state.common.shell.activate(output, idx); + state.common.shell.activate(to_output, to_idx); Common::set_focus(state, Some(&KeyboardFocusTarget::from(mapped)), &seat, None); } } diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 8cc19c2c..5eb46487 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -249,11 +249,11 @@ impl Workspace { } } - pub fn fullscreen_toggle(&mut self, window: &Window, output: &Output) { + pub fn maximize_toggle(&mut self, window: &Window, output: &Output) { if self.fullscreen.contains_key(output) { - self.unfullscreen_request(window) + self.unmaximize_request(window) } else { - self.fullscreen_request(window, output) + self.maximize_request(window, output) } } From bca72a9b0e78ba0f41da37dca6d6f4ffc9c12ee8 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 11 Nov 2022 21:36:42 +0100 Subject: [PATCH 38/62] kms: Correctly apply disabled confs --- src/backend/kms/mod.rs | 63 ++++++++---------------------------------- src/backend/winit.rs | 5 +--- src/backend/x11.rs | 5 +--- src/config/mod.rs | 24 ++++++++++++++-- src/shell/mod.rs | 8 ++++++ 5 files changed, 43 insertions(+), 62 deletions(-) diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 559fec51..2637313d 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -25,7 +25,6 @@ use smithay::{ libinput::{LibinputInputBackend, LibinputSessionInterface}, renderer::{ damage::DamageTrackedRenderer, - element::RenderElementStates, gles2::{Gles2Renderbuffer, Gles2Renderer}, multigpu::{egl::EglGlesBackend, GpuManager}, Bind, @@ -246,20 +245,13 @@ pub fn init_backend( } } } - data.state.common.output_configuration_state.update(); data.state.common.config.read_outputs( - data.state.common.output_configuration_state.outputs(), + &mut data.state.common.output_configuration_state, &mut data.state.backend, &mut data.state.common.shell, &data.state.common.event_loop_handle, ); - data.state.common.shell.refresh_outputs(); - data.state - .common - .config - .write_outputs(data.state.common.output_configuration_state.outputs()); - for surface in data .state .backend @@ -473,27 +465,12 @@ impl State { self.common .output_configuration_state .add_heads(wl_outputs.iter()); - self.common.output_configuration_state.update(); - for output in wl_outputs { - if let Err(err) = self.backend.kms().apply_config_for_output( - &output, - &mut self.common.shell, - false, - &self.common.event_loop_handle, - ) { - slog_scope::warn!("Failed to initialize output: {}", err); - } - } self.common.config.read_outputs( - self.common.output_configuration_state.outputs(), + &mut self.common.output_configuration_state, &mut self.backend, &mut self.common.shell, &self.common.event_loop_handle, ); - self.common.shell.refresh_outputs(); - self.common - .config - .write_outputs(self.common.output_configuration_state.outputs()); Ok(()) } @@ -541,30 +518,15 @@ impl State { self.common .output_configuration_state .add_heads(outputs_added.iter()); - for output in outputs_added { - if let Err(err) = self.backend.kms().apply_config_for_output( - &output, - &mut self.common.shell, - false, - &self.common.event_loop_handle, - ) { - slog_scope::warn!("Failed to initialize output: {}", err); - } - } for output in outputs_removed { self.common.shell.remove_output(&output); } - self.common.output_configuration_state.update(); self.common.config.read_outputs( - self.common.output_configuration_state.outputs(), + &mut self.common.output_configuration_state, &mut self.backend, &mut self.common.shell, &self.common.event_loop_handle, ); - self.common.shell.refresh_outputs(); - self.common - .config - .write_outputs(self.common.output_configuration_state.outputs()); Ok(()) } @@ -593,22 +555,19 @@ impl State { self.common .output_configuration_state .remove_heads(outputs_removed.iter()); - self.common.output_configuration_state.update(); if self.backend.kms().session.is_active() { for output in outputs_removed { self.common.shell.remove_output(&output); } self.common.config.read_outputs( - self.common.output_configuration_state.outputs(), + &mut self.common.output_configuration_state, &mut self.backend, &mut self.common.shell, &self.common.event_loop_handle, ); - self.common.shell.refresh_outputs(); - self.common - .config - .write_outputs(self.common.output_configuration_state.outputs()); + } else { + self.common.output_configuration_state.update(); } Ok(()) @@ -858,10 +817,11 @@ impl KmsState { if !output_config.enabled { if !test_only { + shell.remove_output(output); if surface.surface.take().is_some() { // just drop it - shell.remove_output(output); surface.pending = false; + surface.dirty = false; } } false @@ -885,7 +845,7 @@ impl KmsState { .ok_or(anyhow::anyhow!("Unknown mode"))?; if !test_only { - if let Some(gbm_surface) = surface.surface.as_mut() { + let res = if let Some(gbm_surface) = surface.surface.as_mut() { if output_config.vrr != surface.vrr { surface.vrr = drm_helpers::set_vrr( drm, @@ -917,9 +877,10 @@ impl KmsState { ) })?; surface.surface = Some(target); - shell.add_output(output); true - } + }; + shell.add_output(output); + res } else { false } diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 3a154e9c..78965c6e 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -213,16 +213,13 @@ pub fn init_backend( .common .output_configuration_state .add_heads(std::iter::once(&output)); - state.common.output_configuration_state.update(); state.common.shell.add_output(&output); state.common.config.read_outputs( - std::iter::once(&output), + &mut state.common.output_configuration_state, &mut state.backend, &mut state.common.shell, &state.common.event_loop_handle, ); - state.common.shell.refresh_outputs(); - state.common.config.write_outputs(std::iter::once(&output)); Ok(()) } diff --git a/src/backend/x11.rs b/src/backend/x11.rs index 5ad7ba37..70ebd11b 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -287,16 +287,13 @@ pub fn init_backend( .common .output_configuration_state .add_heads(std::iter::once(&output)); - state.common.output_configuration_state.update(); state.common.shell.add_output(&output); state.common.config.read_outputs( - std::iter::once(&output), + &mut state.common.output_configuration_state, &mut state.backend, &mut state.common.shell, &state.common.event_loop_handle, ); - state.common.shell.refresh_outputs(); - state.common.config.write_outputs(std::iter::once(&output)); event_loop .handle() diff --git a/src/config/mod.rs b/src/config/mod.rs index 16dd0f23..289ff43e 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -2,7 +2,8 @@ use crate::{ shell::{focus::FocusDirection, layout::tiling::Direction, Shell, WorkspaceAmount}, - state::{BackendData, Data}, + state::{BackendData, Data, State}, + wayland::protocols::output_configuration::OutputConfigurationState, }; use serde::{Deserialize, Serialize}; pub use smithay::{ @@ -278,12 +279,12 @@ impl Config { pub fn read_outputs( &mut self, - outputs: impl Iterator>, + output_state: &mut OutputConfigurationState, backend: &mut BackendData, shell: &mut Shell, loop_handle: &LoopHandle<'_, Data>, ) { - let outputs = outputs.map(|x| x.borrow().clone()).collect::>(); + let outputs = output_state.outputs().collect::>(); let mut infos = outputs .iter() .cloned() @@ -307,6 +308,7 @@ impl Config { for (name, output_config) in infos.iter().map(|o| &o.connector).zip(configs.into_iter()) { let output = outputs.iter().find(|o| &o.name() == name).unwrap().clone(); + let enabled = output_config.enabled; *output .user_data() .get::>() @@ -322,6 +324,12 @@ impl Config { ); reset = true; break; + } else { + if enabled { + output_state.enable_head(&output); + } else { + output_state.disable_head(&output); + } } } @@ -331,6 +339,7 @@ impl Config { .into_iter() .zip(known_good_configs.into_iter()) { + let enabled = output_config.enabled; *output .user_data() .get::>() @@ -344,9 +353,18 @@ impl Config { output.name(), err ); + } else { + if enabled { + output_state.enable_head(&output); + } else { + output_state.disable_head(&output); + } } } } + + output_state.update(); + self.write_outputs(output_state.outputs()); } } diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 7e32c841..ec546cd5 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -421,6 +421,10 @@ impl Shell { } pub fn add_output(&mut self, output: &Output) { + if self.outputs.contains(output) { + return; + } + self.outputs.push(output.clone()); let mut state = self.workspace_state.update(); @@ -445,6 +449,10 @@ impl Shell { } pub fn remove_output(&mut self, output: &Output) { + if !self.outputs.contains(output) { + return; + } + let mut state = self.workspace_state.update(); self.outputs.retain(|o| o != output); From 5bf7059a4314e136a710dcb9151d6f59cca9a09d Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 11 Nov 2022 23:23:38 +0100 Subject: [PATCH 39/62] input: More precise layer-shell input regions --- src/input/mod.rs | 74 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 19 deletions(-) diff --git a/src/input/mod.rs b/src/input/mod.rs index ee60d2de..4c5e0dd3 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -20,7 +20,7 @@ use smithay::{ AbsolutePositionEvent, Axis, AxisSource, Device, DeviceCapability, InputBackend, InputEvent, KeyState, PointerAxisEvent, }, - desktop::layer_map_for_output, + desktop::{layer_map_for_output, WindowSurfaceType}, input::{ keyboard::{keysyms, FilterResult, KeysymHandle, XkbConfig}, pointer::{AxisFrame, ButtonEvent, CursorImageStatus, MotionEvent}, @@ -503,7 +503,15 @@ impl State { if let Some(layer) = layers.layer_under(WlrLayer::Overlay, relative_pos) { - if layer.can_receive_keyboard_focus() { + let layer_loc = layers.layer_geometry(layer).unwrap().loc; + if layer.can_receive_keyboard_focus() + && layer + .surface_under( + relative_pos - layer_loc.to_f64(), + WindowSurfaceType::ALL, + ) + .is_some() + { under = Some(layer.clone().into()); } } else { @@ -514,7 +522,15 @@ impl State { .layer_under(WlrLayer::Overlay, relative_pos) .or_else(|| layers.layer_under(WlrLayer::Top, relative_pos)) { - if layer.can_receive_keyboard_focus() { + let layer_loc = layers.layer_geometry(layer).unwrap().loc; + if layer.can_receive_keyboard_focus() + && layer + .surface_under( + relative_pos - layer_loc.to_f64(), + WindowSurfaceType::ALL, + ) + .is_some() + { under = Some(layer.clone().into()); } } else if let Some((window, _)) = @@ -525,7 +541,15 @@ impl State { .layer_under(WlrLayer::Bottom, pos) .or_else(|| layers.layer_under(WlrLayer::Background, pos)) { - if layer.can_receive_keyboard_focus() { + let layer_loc = layers.layer_geometry(layer).unwrap().loc; + if layer.can_receive_keyboard_focus() + && layer + .surface_under( + relative_pos - layer_loc.to_f64(), + WindowSurfaceType::ALL, + ) + .is_some() + { under = Some(layer.clone().into()); } }; @@ -942,36 +966,48 @@ impl State { ) -> Option<(PointerFocusTarget, Point)> { let layers = layer_map_for_output(output); if let Some(window) = workspace.get_fullscreen(output) { - if let Some(layer) = layers - .layer_under(WlrLayer::Overlay, relative_pos) - .or_else(|| layers.layer_under(WlrLayer::Top, relative_pos)) - { + if let Some(layer) = layers.layer_under(WlrLayer::Overlay, relative_pos) { let layer_loc = layers.layer_geometry(layer).unwrap().loc; - Some((layer.clone().into(), output_geo.loc + layer_loc)) - } else { - Some((window.clone().into(), output_geo.loc)) + if layer + .surface_under(relative_pos - layer_loc.to_f64(), WindowSurfaceType::ALL) + .is_some() + { + return Some((layer.clone().into(), output_geo.loc + layer_loc)); + } } + Some((window.clone().into(), output_geo.loc)) } else { if let Some(layer) = layers .layer_under(WlrLayer::Overlay, relative_pos) .or_else(|| layers.layer_under(WlrLayer::Top, relative_pos)) { let layer_loc = layers.layer_geometry(layer).unwrap().loc; - Some((layer.clone().into(), output_geo.loc + layer_loc)) - } else if let Some((mapped, loc)) = workspace.element_under(relative_pos) { - Some(( + if layer + .surface_under(relative_pos - layer_loc.to_f64(), WindowSurfaceType::ALL) + .is_some() + { + return Some((layer.clone().into(), output_geo.loc + layer_loc)); + } + } + if let Some((mapped, loc)) = workspace.element_under(relative_pos) { + return Some(( mapped.clone().into(), loc + (global_pos - relative_pos).to_i32_round(), - )) - } else if let Some(layer) = layers + )); + } + if let Some(layer) = layers .layer_under(WlrLayer::Bottom, relative_pos) .or_else(|| layers.layer_under(WlrLayer::Background, relative_pos)) { let layer_loc = layers.layer_geometry(layer).unwrap().loc; - Some((layer.clone().into(), output_geo.loc + layer_loc)) - } else { - None + if layer + .surface_under(relative_pos - layer_loc.to_f64(), WindowSurfaceType::ALL) + .is_some() + { + return Some((layer.clone().into(), output_geo.loc + layer_loc)); + } } + None } } } From 15fe9af3428425ff7619172dce3e936a15644817 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 11 Nov 2022 23:24:19 +0100 Subject: [PATCH 40/62] shell/tiling: Allow moving alone windows --- src/shell/layout/tiling/mod.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 97b392ae..e335739d 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -458,7 +458,14 @@ impl TilingLayout { let mut child_id = node_id.clone(); // Without a parent to start with, just return - let og_parent = tree.get(&node_id).unwrap().parent().cloned()?; + let Some(og_parent) = tree.get(&node_id).unwrap().parent().cloned() else { + let data = tree.get(&node_id).unwrap().data(); + assert!(data.is_mapped(None)); + return match data { + Data::Mapped { mapped, .. } => Some(mapped.clone()), + _ => unreachable!(), + }; + }; let og_idx = tree .children_ids(&og_parent) .unwrap() From 77858d3628f46b065d4e31c159d1444f821b070e Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Sat, 12 Nov 2022 13:51:32 +0100 Subject: [PATCH 41/62] tiling: Properly center windows with gaps --- src/shell/layout/tiling/mod.rs | 48 ++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index e335739d..6312325e 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -1087,34 +1087,36 @@ impl TilingLayout { .into_iter() { let node = tree.get_mut(&node_id).unwrap(); - if let Some(geo) = stack.pop() { + if let Some(mut geo) = stack.pop() { let data = node.data_mut(); - data.update_geometry(geo); match data { Data::Group { orientation, sizes, .. - } => match orientation { - Orientation::Horizontal => { - let mut previous: i32 = sizes.iter().sum(); - for size in sizes.iter().rev() { - previous -= *size; - stack.push(Rectangle::from_loc_and_size( - (geo.loc.x, geo.loc.y + previous), - (geo.size.w, *size), - )); + } => { + match orientation { + Orientation::Horizontal => { + let mut previous: i32 = sizes.iter().sum(); + for size in sizes.iter().rev() { + previous -= *size; + stack.push(Rectangle::from_loc_and_size( + (geo.loc.x, geo.loc.y + previous), + (geo.size.w, *size), + )); + } + } + Orientation::Vertical => { + let mut previous: i32 = sizes.iter().sum(); + for size in sizes.iter().rev() { + previous -= *size; + stack.push(Rectangle::from_loc_and_size( + (geo.loc.x + previous, geo.loc.y), + (*size, geo.size.h), + )); + } } } - Orientation::Vertical => { - let mut previous: i32 = sizes.iter().sum(); - for size in sizes.iter().rev() { - previous -= *size; - stack.push(Rectangle::from_loc_and_size( - (geo.loc.x + previous, geo.loc.y), - (*size, geo.size.h), - )); - } - } - }, + data.update_geometry(geo); + } Data::Mapped { mapped, .. } => { if !(mapped.is_fullscreen() || mapped.is_maximized()) { mapped.set_tiled(true); @@ -1123,6 +1125,8 @@ impl TilingLayout { ); mapped.configure(); } + geo.loc += (inner, inner).into(); + data.update_geometry(geo); } } } From 7e45e5178105e1635c501fc5cc81a7e67f281067 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 14 Nov 2022 11:54:22 +0100 Subject: [PATCH 42/62] tiling: Add proper output_enter/leave events --- src/shell/layout/tiling/mod.rs | 32 +++++++----- src/shell/mod.rs | 93 ++++++++++++++++------------------ src/shell/workspace.rs | 25 +++++++++ 3 files changed, 90 insertions(+), 60 deletions(-) diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 6312325e..4f4a3755 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -291,6 +291,7 @@ impl TilingLayout { focus_stack: impl Iterator + 'a, ) { let output = seat.active_output(); + window.output_enter(&output, window.bbox()); self.map_internal(window, &output, Some(focus_stack)); self.refresh(); } @@ -345,17 +346,27 @@ impl TilingLayout { *window.tiling_node_id.lock().unwrap() = Some(window_id); } - pub fn unmap(&mut self, window: &CosmicMapped) -> bool { - if self.unmap_window_internal(window) { - window.set_tiled(false); - self.refresh(); - true - } else { - false - } + pub fn unmap(&mut self, window: &CosmicMapped) -> Option { + let output = { + let node_id = window.tiling_node_id.lock().unwrap().clone()?; + self.trees + .iter() + .find(|(_, tree)| { + tree.get(&node_id) + .map(|node| node.data().is_mapped(Some(window))) + .unwrap_or(false) + }) + .map(|(o, _)| o.output.clone())? + }; + + self.unmap_window_internal(window); + window.output_leave(&output); + window.set_tiled(false); + self.refresh(); + Some(output) } - fn unmap_window_internal(&mut self, mapped: &CosmicMapped) -> bool { + fn unmap_window_internal(&mut self, mapped: &CosmicMapped) { if let Some(node_id) = mapped.tiling_node_id.lock().unwrap().as_ref() { if let Some(tree) = self.trees.values_mut().find(|tree| { tree.get(node_id) @@ -415,11 +426,8 @@ impl TilingLayout { } None => {} // root } - - return true; } } - false } pub fn output_for_element(&self, elem: &CosmicMapped) -> Option<&Output> { diff --git a/src/shell/mod.rs b/src/shell/mod.rs index ec546cd5..a6d8fe10 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -1006,58 +1006,55 @@ impl Shell { let from_workspace = state.common.shell.workspaces.active_mut(from_output); let maybe_window = from_workspace.focus_stack.get(seat).last().cloned(); - if let Some(mapped) = maybe_window { - let was_floating = from_workspace.floating_layer.unmap(&mapped); - let was_tiling = from_workspace.tiling_layer.unmap(&mapped); - assert!(was_floating != was_tiling); + let Some(mapped) = maybe_window else { return; }; + let Some(window_state) = from_workspace.unmap(&mapped) else { return; }; - for (toplevel, _) in mapped.windows() { - state - .common - .shell - .toplevel_info_state - .toplevel_leave_workspace(&toplevel, &from_workspace.handle); - } - let elements = from_workspace.mapped().cloned().collect::>(); - std::mem::drop(from_workspace); - for mapped in elements.into_iter() { - state.common.shell.update_reactive_popups(&mapped); - } - - let to_workspace = state + for (toplevel, _) in mapped.windows() { + state .common .shell - .workspaces - .get_mut(to_idx, to_output) - .unwrap(); // checked above - let focus_stack = to_workspace.focus_stack.get(&seat); - if was_floating { - to_workspace.floating_layer.map(mapped.clone(), &seat, None); - } else { - to_workspace - .tiling_layer - .map(mapped.clone(), &seat, focus_stack.iter()); - } - for (toplevel, _) in mapped.windows() { - state - .common - .shell - .toplevel_info_state - .toplevel_enter_workspace(&toplevel, &to_workspace.handle); - } - - for mapped in to_workspace - .mapped() - .cloned() - .collect::>() - .into_iter() - { - state.common.shell.update_reactive_popups(&mapped); - } - - state.common.shell.activate(to_output, to_idx); - Common::set_focus(state, Some(&KeyboardFocusTarget::from(mapped)), &seat, None); + .toplevel_info_state + .toplevel_leave_workspace(&toplevel, &from_workspace.handle); } + let elements = from_workspace.mapped().cloned().collect::>(); + std::mem::drop(from_workspace); + for mapped in elements.into_iter() { + state.common.shell.update_reactive_popups(&mapped); + } + + let to_workspace = state + .common + .shell + .workspaces + .get_mut(to_idx, to_output) + .unwrap(); // checked above + let focus_stack = to_workspace.focus_stack.get(&seat); + if window_state == ManagedState::Floating { + to_workspace.floating_layer.map(mapped.clone(), &seat, None); + } else { + to_workspace + .tiling_layer + .map(mapped.clone(), &seat, focus_stack.iter()); + } + for (toplevel, _) in mapped.windows() { + state + .common + .shell + .toplevel_info_state + .toplevel_enter_workspace(&toplevel, &to_workspace.handle); + } + + for mapped in to_workspace + .mapped() + .cloned() + .collect::>() + .into_iter() + { + state.common.shell.update_reactive_popups(&mapped); + } + + state.common.shell.activate(to_output, to_idx); + Common::set_focus(state, Some(&KeyboardFocusTarget::from(mapped)), &seat, None); } pub fn update_reactive_popups(&self, mapped: &CosmicMapped) { diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 5eb46487..b9c0dbc3 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -55,6 +55,12 @@ pub struct Workspace { #[derive(Debug, Default)] pub struct FocusStacks(HashMap, IndexSet>); +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ManagedState { + Tiling, + Floating, +} + impl Workspace { pub fn new(handle: WorkspaceHandle) -> Workspace { Workspace { @@ -100,6 +106,25 @@ impl Workspace { self.refresh(); } + pub fn unmap(&mut self, mapped: &CosmicMapped) -> Option { + let was_floating = self.floating_layer.unmap(&mapped); + let was_tiling = self.tiling_layer.unmap(&mapped).is_some(); + if was_floating || was_tiling { + assert!(was_floating != was_tiling); + } + self.focus_stack + .0 + .values_mut() + .for_each(|set| set.retain(|m| m != mapped)); + if was_floating { + Some(ManagedState::Floating) + } else if was_tiling { + Some(ManagedState::Tiling) + } else { + None + } + } + pub fn element_for_surface(&self, surface: &WlSurface) -> Option<&CosmicMapped> { self.floating_layer .mapped() From aa3ee245d1ddeed4a198c9cb4386c730404b1012 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 17 Nov 2022 12:25:41 +0100 Subject: [PATCH 43/62] screencopy: Fix format and copy code --- src/state.rs | 7 +++++- src/wayland/handlers/screencopy.rs | 36 ++++++++++++++++++++++++------ 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/state.rs b/src/state.rs index 075aec61..16a4e159 100644 --- a/src/state.rs +++ b/src/state.rs @@ -26,6 +26,7 @@ use smithay::{ calloop::{LoopHandle, LoopSignal}, wayland_server::{ backend::{ClientData, ClientId, DisconnectReason}, + protocol::wl_shm, Display, DisplayHandle, }, }, @@ -233,7 +234,11 @@ impl State { vec![CursorMode::Embedded, CursorMode::Hidden], |_| true, ); // TODO: privileged - let shm_state = ShmState::new::(dh, vec![], None); + let shm_state = ShmState::new::( + dh, + vec![wl_shm::Format::Xbgr8888, wl_shm::Format::Abgr8888], + None, + ); let seat_state = SeatState::::new(); let viewporter_state = ViewporterState::new::(dh, None); let wl_drm_state = WlDrmState; diff --git a/src/wayland/handlers/screencopy.rs b/src/wayland/handlers/screencopy.rs index a3180f84..540be69c 100644 --- a/src/wayland/handlers/screencopy.rs +++ b/src/wayland/handlers/screencopy.rs @@ -180,12 +180,12 @@ impl ScreencopyHandler for State { let mut formats = vec![ BufferInfo::Shm { - format: ShmFormat::Argb8888, + format: ShmFormat::Abgr8888, size, stride: size.w as u32 * 4, }, BufferInfo::Shm { - format: ShmFormat::Xrgb8888, + format: ShmFormat::Xbgr8888, size, stride: size.w as u32 * 4, }, @@ -279,7 +279,7 @@ impl ScreencopyHandler for State { if let Some(BufferType::Shm) = buffer_type(¶ms.buffer) { if with_buffer_contents(¶ms.buffer, |_, info| { - info.format != ShmFormat::Argb8888 && info.format != ShmFormat::Xrgb8888 + info.format != ShmFormat::Abgr8888 && info.format != ShmFormat::Xbgr8888 }) .unwrap() { @@ -418,12 +418,12 @@ fn formats_for_output( let mut formats = vec![ BufferInfo::Shm { - format: ShmFormat::Argb8888, + format: ShmFormat::Abgr8888, size: mode, stride: mode.w as u32 * 4, }, BufferInfo::Shm { - format: ShmFormat::Xrgb8888, + format: ShmFormat::Xbgr8888, size: mode, stride: mode.w as u32 * 4, }, @@ -500,11 +500,33 @@ where { if matches!(buffer_type(buffer), Some(BufferType::Shm)) { let buffer_size = buffer_dimensions(buffer).unwrap(); - with_buffer_contents_mut(buffer, |data, _info| { + with_buffer_contents_mut(buffer, |slice, data| { + let offset = data.offset as i32; + let width = data.width as i32; + let height = data.height as i32; + let stride = data.stride as i32; + + // number of bytes per pixel + // TODO: compute from data.format + let pixelsize = 4i32; + + // ensure consistency, the SHM handler of smithay should ensure this + assert!((offset + (height - 1) * stride + width * pixelsize) as usize <= slice.len()); + 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); + assert!((width * height * pixelsize) as usize <= gl_data.len()); + + for i in 0..height { + unsafe { + std::ptr::copy_nonoverlapping::( + gl_data.as_ptr().offset((width * pixelsize * i) as isize), + slice.as_mut_ptr().offset((offset + stride * i) as isize), + (width * pixelsize) as usize, + ); + } + } Ok(()) }) .unwrap()?; From 7a034c8e525448e7d3627d754b50a0626d459c90 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 17 Nov 2022 20:32:54 +0100 Subject: [PATCH 44/62] deps: smithay + egui update --- Cargo.lock | 132 +++++++--- Cargo.toml | 13 +- src/backend/kms/mod.rs | 116 ++++++--- src/backend/render/cursor.rs | 147 +++++------ src/backend/render/element.rs | 302 ++++++++++++++++++++++ src/backend/render/mod.rs | 129 ++++----- src/backend/winit.rs | 26 +- src/backend/x11.rs | 47 ++-- src/debug.rs | 176 +++---------- src/input/mod.rs | 72 +----- src/logger/mod.rs | 111 +------- src/shell/element/stack.rs | 2 +- src/shell/element/window.rs | 2 +- src/shell/layout/floating/grabs/moving.rs | 3 +- src/shell/layout/tiling/mod.rs | 3 +- src/state.rs | 92 +++++-- src/utils/prelude.rs | 10 +- src/wayland/handlers/mod.rs | 2 +- src/wayland/handlers/presentation.rs | 6 + src/wayland/handlers/screencopy.rs | 172 +++++++----- 20 files changed, 906 insertions(+), 657 deletions(-) create mode 100644 src/backend/render/element.rs create mode 100644 src/wayland/handlers/presentation.rs diff --git a/Cargo.lock b/Cargo.lock index 3336870d..0e65aefa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,10 +35,11 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.7.6" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "bf6ccdb167abbf410dcb915cabd428929d7f6a04980b54a11f26a39f1c7f7107" dependencies = [ + "cfg-if", "getrandom", "once_cell", "version_check", @@ -156,18 +157,18 @@ checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "bytemuck" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aec14f5d4e6e3f927cd0c81f72e5710d95ee9019fbeb4b3021193867491bfd8" +checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9e1f5fa78f69496407a27ae9ed989e3c3b072310286f5ef385525e4cbc24a9" +checksum = "5fe233b960f12f8007e3db2d136e3cb1c291bfd7396e384ee76025fc1a3932b4" dependencies = [ "proc-macro2", "quote", @@ -176,9 +177,9 @@ dependencies = [ [[package]] name = "calloop" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a22a6a8f622f797120d452c630b0ab12e1331a1a753e2039ce7868d4ac77b4ee" +checksum = "595eb0438b3c6d262395fe30e6de9a61beb57ea56290b00a07f227fe6e20cbf2" dependencies = [ "log", "nix 0.24.2", @@ -189,9 +190,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.74" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" +checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" [[package]] name = "cfg-if" @@ -535,9 +536,9 @@ checksum = "2ab5fa33485cd85ac354df485819a63360fefa312fe04cffe65e6f175be1522c" [[package]] name = "egui" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb095a8b9feb9b7ff8f00b6776dffcef059538a3f4a91238e03c900e9c9ad9a2" +checksum = "fc9fcd393c3daaaf5909008a1d948319d538b79c51871e4df0993260260a94e4" dependencies = [ "ahash", "epaint", @@ -545,20 +546,39 @@ dependencies = [ ] [[package]] -name = "emath" -version = "0.18.0" +name = "egui_glow" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c223f58c7e38abe1770f367b969f1b3fbd4704b67666bcb65dbb1adb0980ba72" +checksum = "ad77d4a00402bae9658ee64be148f4b2a0b38e4fc7874970575ca01ed1c5b75d" +dependencies = [ + "bytemuck", + "egui", + "glow", + "memoffset", + "tracing", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "emath" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9542a40106fdba943a055f418d1746a050e1a903a049b030c2b097d4686a33cf" +dependencies = [ + "bytemuck", +] [[package]] name = "epaint" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c29567088888e8ac3e8f61bbb2ddc820207ebb8d69eefde5bcefa06d65e4e89" +checksum = "5ba04741be7f6602b1a1b28f1082cce45948a7032961c52814f8946b28493300" dependencies = [ "ab_glyph", "ahash", "atomic_refcell", + "bytemuck", "emath", "nohash-hasher", "parking_lot", @@ -695,6 +715,18 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glow" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bd5877156a19b8ac83a29b2306fe20537429d318f3ff0a1a2119f8d9c61919" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -828,9 +860,9 @@ checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" [[package]] name = "libloading" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if", "winapi", @@ -922,9 +954,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" +checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc" dependencies = [ "libc", ] @@ -1151,9 +1183,9 @@ checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "owned_ttf_parser" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4665508572151759e8d60404e20dc096ef93a99801a05ac2ac6e43bf5b4ca187" +checksum = "18904d3c65493a9f0d7542293d1a7f69bfdc309a6b9ef4f46dc3e58b0577edc5" dependencies = [ "ttf-parser", ] @@ -1187,6 +1219,12 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + [[package]] name = "pkg-config" version = "0.3.26" @@ -1207,9 +1245,9 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro-crate" @@ -1319,9 +1357,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" dependencies = [ "aho-corasick", "memchr", @@ -1330,9 +1368,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "remove_dir_all" @@ -1512,7 +1550,7 @@ checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "smithay" version = "0.3.0" -source = "git+https://github.com/Smithay//smithay?rev=b0ec5090df#b0ec5090df54e711a59b67869b495795053574e5" +source = "git+https://github.com/pop-os/smithay?rev=c8aaa059e8#c8aaa059e841886ed9e689e0cc0d0cb821b97b71" dependencies = [ "appendlist", "bitflags", @@ -1525,6 +1563,7 @@ dependencies = [ "drm-fourcc", "gbm", "gl_generator", + "glow", "indexmap", "input", "io-lifetimes", @@ -1576,13 +1615,12 @@ dependencies = [ [[package]] name = "smithay-egui" version = "0.1.0" -source = "git+https://github.com/Smithay/smithay-egui.git?rev=939febaf#939febafd4df990b412213abff992ab4a4b9bd44" +source = "git+https://github.com/Smithay/smithay-egui.git?rev=7334d0c53#7334d0c533ad307b3359cd4931bfb1ad4c34b178" dependencies = [ "cgmath", "egui", - "lazy_static", + "egui_glow", "memoffset", - "slog", "smithay", "xkbcommon 0.4.1", ] @@ -1678,9 +1716,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fab5c8b9980850e06d92ddbe3ab839c062c801f3927c0fb8abd6fc8e918fbca" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ "itoa", "libc", @@ -1698,9 +1736,9 @@ checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] name = "time-macros" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bb801831d812c562ae7d2bfb531f26e66e4e1f6b17307ba4149c5064710e5b" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" dependencies = [ "time-core", ] @@ -1714,6 +1752,26 @@ dependencies = [ "serde", ] +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + [[package]] name = "ttf-parser" version = "0.17.1" diff --git a/Cargo.toml b/Cargo.toml index 1dbebd11..954ec444 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ slog-stdlog = "4.1" serde = { version = "1", features = ["derive"] } serde_json = "1" sendfd = "0.4.1" -egui = { version = "0.18.1", optional = true } +egui = { version = "0.19.0", optional = true } edid-rs = { version = "0.1" } png = "0.17.5" lazy_static = "1.4.0" @@ -36,19 +36,18 @@ cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", branch [dependencies.smithay] version = "0.3" git = "https://github.com/Smithay/smithay.git" -rev = "606d2d5c" +rev = "b297c93edc" default-features = false -features = ["backend_drm", "backend_gbm", "backend_egl", "backend_libinput", "backend_session_libseat", "backend_udev", "backend_winit", "backend_x11", "desktop", "use_system_lib", "renderer_gl", "renderer_multi", "wayland_frontend", "slog-stdlog"] +features = ["backend_drm", "backend_gbm", "backend_egl", "backend_libinput", "backend_session_libseat", "backend_udev", "backend_winit", "backend_x11", "desktop", "use_system_lib", "renderer_glow", "renderer_multi", "wayland_frontend", "slog-stdlog"] [dependencies.smithay-egui] git = "https://github.com/Smithay/smithay-egui.git" -rev = "939febaf" +rev = "7334d0c53" optional = true [features] -default = [] +default = ["debug"] debug = ["egui", "smithay-egui"] -experimental = [] [profile.dev] lto = "thin" @@ -61,4 +60,4 @@ debug = true lto = "fat" [patch."https://github.com/Smithay/smithay.git"] -smithay = { git = "https://github.com/Smithay//smithay", rev = "b0ec5090df" } +smithay = { git = "https://github.com/pop-os/smithay", rev = "c8aaa059e8" } diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 2637313d..c64f984f 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -19,19 +19,20 @@ use anyhow::{Context, Result}; use smithay::{ backend::{ allocator::{dmabuf::Dmabuf, gbm::GbmDevice, Format}, - drm::{DrmDevice, DrmEvent, DrmNode, GbmBufferedSurface, NodeType}, + drm::{DrmDevice, DrmEvent, DrmEventTime, DrmNode, GbmBufferedSurface, NodeType}, egl::{EGLContext, EGLDevice, EGLDisplay}, input::InputEvent, libinput::{LibinputInputBackend, LibinputSessionInterface}, renderer::{ damage::DamageTrackedRenderer, - gles2::{Gles2Renderbuffer, Gles2Renderer}, + gles2::Gles2Renderbuffer, + glow::GlowRenderer, multigpu::{egl::EglGlesBackend, GpuManager}, - Bind, }, session::{auto::AutoSession, Session, Signal}, udev::{all_gpus, primary_gpu, UdevBackend, UdevEvent}, }, + desktop::utils::OutputPresentationFeedback, output::{Mode as OutputMode, Output, PhysicalProperties, Subpixel}, reexports::{ calloop::{ @@ -41,6 +42,7 @@ use smithay::{ drm::control::{connector, crtc, Device as ControlDevice, ModeTypeFlags}, input::Libinput, nix::{fcntl::OFlag, sys::stat::dev_t}, + wayland_protocols::wp::presentation_time::server::wp_presentation_feedback, wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle, Resource}, }, utils::{ @@ -69,7 +71,7 @@ use super::render::{CursorMode, GlMultiRenderer}; pub struct KmsState { devices: HashMap, - pub api: GpuManager>, + pub api: GpuManager>, pub primary: DrmNode, session: AutoSession, signaler: Signaler, @@ -89,7 +91,13 @@ pub struct Device { } pub struct Surface { - surface: Option>>, SessionFd>>, + surface: Option< + GbmBufferedSurface< + Rc>>, + SessionFd, + Option, + >, + >, damage_tracker: DamageTrackedRenderer, connector: connector::Handle, output: Output, @@ -131,6 +139,7 @@ pub fn init_backend( &data.state.common.event_loop_handle, output, None, + None, ) { slog_scope::crit!( "Error scheduling event loop for output {}: {:?}", @@ -148,7 +157,7 @@ pub fn init_backend( .map_err(|err| err.error) .context("Failed to initialize session event source")?; - let api = GpuManager::new(EglGlesBackend::::default(), None) + let api = GpuManager::new(EglGlesBackend::::default(), None) .context("Failed to initialize renderers")?; // TODO get this info from system76-power, if available and setup a watcher @@ -267,6 +276,7 @@ pub fn init_backend( if let Err(err) = data.state.backend.kms().schedule_render( &data.state.common.event_loop_handle, output, + None, if !sessions.is_empty() { Some(sessions) } else { @@ -372,8 +382,44 @@ impl State { if let Some(device) = data.state.backend.kms().devices.get_mut(&drm_node) { if let Some(surface) = device.surfaces.get_mut(&crtc) { match surface.surface.as_mut().map(|x| x.frame_submitted()) { - Some(Ok(_)) => { - let _submit_time = metadata.take().map(|data| data.time); + Some(Ok(feedback)) => { + if let Some(mut feedback) = feedback.flatten() { + let submit_time = + match metadata.take().map(|data| data.time) { + Some(DrmEventTime::Monotonic(tp)) => Some(tp), + _ => None, + }; + let seq = metadata + .as_ref() + .map(|metadata| metadata.sequence) + .unwrap_or(0); + + let (clock, flags) = if let Some(tp) = submit_time { + ( + tp.into(), + wp_presentation_feedback::Kind::Vsync + | wp_presentation_feedback::Kind::HwClock + | wp_presentation_feedback::Kind::HwCompletion, + ) + } else { + ( + data.state.common.clock.now(), + wp_presentation_feedback::Kind::Vsync, + ) + }; + + feedback.presented( + clock, + surface + .output + .current_mode() + .map(|mode| mode.refresh as u32) + .unwrap_or_default(), + seq as u64, + flags, + ); + } + surface.pending = false; surface.dirty.then(|| surface.output.clone()) } @@ -400,9 +446,19 @@ impl State { .extend(sessions.borrow_mut().drain(..)); } + let output_refresh = match output.current_mode() { + Some(mode) => mode.refresh, + None => return, + }; + // TODO: Record rendering times and use sliding window to estimate duration for next render + let repaint_delay = Duration::from_secs_f64( + ((1000.0 / output_refresh as f64) * 0.6).max(0.003), // for now we assume we need at least 3ms + ); + if let Err(err) = data.state.backend.kms().schedule_render( &data.state.common.event_loop_handle, &output, + Some(repaint_delay), scheduled_sessions, ) { slog_scope::warn!("Failed to schedule render: {}", err); @@ -738,7 +794,7 @@ impl Surface { pub fn render_output( &mut self, dh: &DisplayHandle, - api: &mut GpuManager>, + api: &mut GpuManager>, target_node: &DrmNode, state: &mut Common, screencopy: Option<&[(ScreencopySession, BufferParams)]>, @@ -755,13 +811,10 @@ impl Surface { .next_buffer() .with_context(|| "Failed to allocate buffer")?; - renderer - .bind(buffer.clone()) - .with_context(|| "Failed to bind buffer")?; - - match render::render_output::<_, Gles2Renderbuffer, _>( + match render::render_output::( Some(&render_node), &mut renderer, + buffer.clone(), &mut self.damage_tracker, age as usize, state, @@ -771,10 +824,15 @@ impl Surface { #[cfg(feature = "debug")] Some(&mut self.fps), ) { - Ok((_damage, states)) => { + Ok((damage, states)) => { + let feedback = if damage.is_some() { + Some(state.take_presentation_feedback(&self.output, &states)) + } else { + None + }; state.send_frames(&self.output, &states); surface - .queue_buffer() + .queue_buffer(feedback) .with_context(|| "Failed to submit buffer for display")?; } Err(err) => { @@ -895,6 +953,7 @@ impl KmsState { if let Err(err) = self.schedule_render( loop_handle, output, + None, if !sessions.is_empty() { Some(sessions) } else { @@ -963,6 +1022,7 @@ impl KmsState { &mut self, loop_handle: &LoopHandle<'_, Data>, output: &Output, + delay: Option, mut screencopy_sessions: Option>, ) -> Result<(), InsertError> { if let Some((device, crtc, surface)) = self @@ -977,19 +1037,6 @@ impl KmsState { if !surface.pending { surface.dirty = false; surface.pending = true; - /* - let instant = surface - .last_submit - .as_ref() - .and_then(|x| match x { - DrmEventTime::Monotonic(instant) => Some(instant), - DrmEventTime::Realtime(_) => None, - }) - .map(|i| { - *i + Duration::from_secs_f64(1.0 / surface.refresh_rate as f64) - - Duration::from_millis(20) // render budget - }); - */ let device = *device; let crtc = *crtc; @@ -997,10 +1044,11 @@ impl KmsState { loop_handle.remove(token); } surface.render_timer_token = Some(loop_handle.insert_source( - //if surface.vrr || instant.is_none() { - Timer::immediate(), /*} else { - Timer::from_deadline(instant.unwrap()) - }*/ + if surface.vrr || delay.is_none() { + Timer::immediate() + } else { + Timer::from_duration(delay.unwrap()) + }, move |_time, _, data| { let backend = data.state.backend.kms(); if let Some(device) = backend.devices.get_mut(&device) { @@ -1020,7 +1068,7 @@ impl KmsState { if backend.session.is_active() { slog_scope::error!("Error rendering: {}", err); return TimeoutAction::ToDuration(Duration::from_secs_f64( - 1.0 / surface.refresh_rate as f64, + (1000.0 / surface.refresh_rate as f64) - 0.003, )); } } diff --git a/src/backend/render/cursor.rs b/src/backend/render/cursor.rs index dd2fa461..9c68a62d 100644 --- a/src/backend/render/cursor.rs +++ b/src/backend/render/cursor.rs @@ -15,7 +15,7 @@ use smithay::{ }, reexports::wayland_server::protocol::wl_surface, render_elements, - utils::{IsAlive, Logical, Point, Scale, Transform}, + utils::{IsAlive, Logical, Monotonic, Point, Scale, Time, Transform}, wayland::compositor::{get_role, with_states}, }; use std::{ @@ -24,6 +24,7 @@ use std::{ collections::HashMap, io::Read, sync::Mutex, + time::Duration, }; use xcursor::{ parser::{parse_xcursor, Image}, @@ -193,93 +194,85 @@ pub fn draw_cursor( seat: &Seat, location: Point, scale: Scale, - start_time: &std::time::Instant, + time: Time, draw_default: bool, ) -> Vec> where - R: Renderer + ImportAll + ImportMem, + R: Renderer + ImportMem + ImportAll, ::TextureId: Clone + 'static, { // draw the cursor as relevant - { - // reset the cursor if the surface is no longer alive - let cursor_status = seat - .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; - } + // reset the cursor if the surface is no longer alive + let cursor_status = seat + .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); + } + cursor_status.clone() + }) + .unwrap_or(CursorImageStatus::Default); - if let CursorImageStatus::Surface(ref wl_surface) = cursor_status { - return draw_surface_cursor(wl_surface, location.to_i32_round(), scale); - } else if draw_default && CursorImageStatus::Default == cursor_status { - let integer_scale = scale.x.max(scale.y).ceil() as u32; + if let CursorImageStatus::Surface(ref wl_surface) = cursor_status { + return draw_surface_cursor(wl_surface, location.to_i32_round(), scale); + } 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(); - seat_userdata.insert_if_missing(CursorState::default); - let state = seat_userdata.get::().unwrap(); - let frame = state - .cursor - .get_image(integer_scale, start_time.elapsed().as_millis() as u32); + let seat_userdata = seat.user_data(); + seat_userdata.insert_if_missing(CursorState::default); + let state = seat_userdata.get::().unwrap(); + let frame = state.cursor.get_image( + integer_scale, + Into::::into(time).as_millis() as u32, + ); - let mut cache = state.image_cache.borrow_mut(); - let pointer_images = cache - .entry(( - TypeId::of::::TextureId>>(), - renderer.id(), - )) - .or_default(); + let mut cache = state.image_cache.borrow_mut(); + let pointer_images = cache + .entry((TypeId::of::>(), renderer.id())) + .or_default(); - let maybe_image = pointer_images - .iter() - .find_map(|(image, texture)| if image == &frame { Some(texture) } else { None }) - .and_then(|texture| { - texture.downcast_ref::::TextureId>>() - }); - let pointer_image = match maybe_image { - Some(image) => image, - None => { - let texture = TextureBuffer::from_memory( - renderer, - &frame.pixels_rgba, - (frame.width as i32, frame.height as i32), - false, - integer_scale as i32, - Transform::Normal, - None, - ) - .expect("Failed to import cursor bitmap"); - pointer_images.push((frame.clone(), Box::new(texture.clone()))); - pointer_images - .last() - .and_then(|(_, i)| { - i.downcast_ref::::TextureId>>() - }) - .unwrap() - } - }; - - let hotspot = - Point::::from((frame.xhot as i32, frame.yhot as i32)).to_f64(); - *state.current_image.borrow_mut() = Some(frame); - - return vec![CursorRenderElement::Static( - TextureRenderElement::from_texture_buffer( - (location - hotspot).to_physical(scale), - pointer_image, + let maybe_image = pointer_images + .iter() + .find_map(|(image, texture)| if image == &frame { Some(texture) } else { None }) + .and_then(|texture| texture.downcast_ref::>()); + let pointer_image = match maybe_image { + Some(image) => image, + None => { + let texture = TextureBuffer::from_memory( + renderer, + &frame.pixels_rgba, + (frame.width as i32, frame.height as i32), + false, + integer_scale as i32, + Transform::Normal, None, - None, - ), - )]; - } else { - Vec::new() - } + ) + .expect("Failed to import cursor bitmap"); + pointer_images.push((frame.clone(), Box::new(texture.clone()))); + pointer_images + .last() + .and_then(|(_, i)| i.downcast_ref::>()) + .unwrap() + } + }; + + let hotspot = Point::::from((frame.xhot as i32, frame.yhot as i32)).to_f64(); + *state.current_image.borrow_mut() = Some(frame); + + return vec![CursorRenderElement::Static( + TextureRenderElement::from_texture_buffer( + (location - hotspot).to_physical(scale), + pointer_image, + None, + None, + None, + ), + )]; + } else { + Vec::new() } } diff --git a/src/backend/render/element.rs b/src/backend/render/element.rs new file mode 100644 index 00000000..5513ec40 --- /dev/null +++ b/src/backend/render/element.rs @@ -0,0 +1,302 @@ +use crate::shell::{CosmicMappedRenderElement, WorkspaceRenderElement}; + +use smithay::{ + backend::renderer::{ + element::{texture::TextureRenderElement, Element, RenderElement, UnderlyingStorage}, + gles2::{Gles2Frame, Gles2Texture}, + glow::GlowRenderer, + multigpu::Error as MultiError, + Frame, ImportAll, Renderer, + }, + utils::{Physical, Point, Rectangle, Scale}, +}; + +use super::{cursor::CursorRenderElement, GlMultiFrame, GlMultiRenderer}; + +pub enum CosmicElement +where + R: AsGlowRenderer + Renderer + ImportAll, + ::TextureId: 'static, + ::Frame: AsGles2Frame, +{ + Workspace(WorkspaceRenderElement), + Cursor(CursorRenderElement), + MoveGrab(CosmicMappedRenderElement), + #[cfg(feature = "debug")] + Egui(TextureRenderElement), +} + +impl Element for CosmicElement +where + R: AsGlowRenderer + Renderer + ImportAll, + ::TextureId: 'static, + ::Frame: AsGles2Frame, +{ + fn id(&self) -> &smithay::backend::renderer::element::Id { + match self { + CosmicElement::Workspace(elem) => elem.id(), + CosmicElement::Cursor(elem) => elem.id(), + CosmicElement::MoveGrab(elem) => elem.id(), + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => elem.id(), + } + } + + fn current_commit(&self) -> smithay::backend::renderer::utils::CommitCounter { + match self { + CosmicElement::Workspace(elem) => elem.current_commit(), + CosmicElement::Cursor(elem) => elem.current_commit(), + CosmicElement::MoveGrab(elem) => elem.current_commit(), + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => elem.current_commit(), + } + } + + fn src(&self) -> Rectangle { + match self { + CosmicElement::Workspace(elem) => elem.src(), + CosmicElement::Cursor(elem) => elem.src(), + CosmicElement::MoveGrab(elem) => elem.src(), + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => elem.src(), + } + } + + fn geometry(&self, scale: Scale) -> Rectangle { + match self { + CosmicElement::Workspace(elem) => elem.geometry(scale), + CosmicElement::Cursor(elem) => elem.geometry(scale), + CosmicElement::MoveGrab(elem) => elem.geometry(scale), + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => elem.geometry(scale), + } + } + + fn location(&self, scale: Scale) -> Point { + match self { + CosmicElement::Workspace(elem) => elem.location(scale), + CosmicElement::Cursor(elem) => elem.location(scale), + CosmicElement::MoveGrab(elem) => elem.location(scale), + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => elem.location(scale), + } + } + + fn transform(&self) -> smithay::utils::Transform { + match self { + CosmicElement::Workspace(elem) => elem.transform(), + CosmicElement::Cursor(elem) => elem.transform(), + CosmicElement::MoveGrab(elem) => elem.transform(), + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => elem.transform(), + } + } + + fn damage_since( + &self, + scale: Scale, + commit: Option, + ) -> Vec> { + match self { + CosmicElement::Workspace(elem) => elem.damage_since(scale, commit), + CosmicElement::Cursor(elem) => elem.damage_since(scale, commit), + CosmicElement::MoveGrab(elem) => elem.damage_since(scale, commit), + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => elem.damage_since(scale, commit), + } + } + + fn opaque_regions(&self, scale: Scale) -> Vec> { + match self { + CosmicElement::Workspace(elem) => elem.opaque_regions(scale), + CosmicElement::Cursor(elem) => elem.opaque_regions(scale), + CosmicElement::MoveGrab(elem) => elem.opaque_regions(scale), + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => elem.opaque_regions(scale), + } + } +} + +impl RenderElement for CosmicElement { + fn draw( + &self, + renderer: &mut GlowRenderer, + frame: &mut ::Frame, + location: Point, + scale: Scale, + damage: &[Rectangle], + log: &slog::Logger, + ) -> Result<(), ::Error> { + match self { + CosmicElement::Workspace(elem) => { + elem.draw(renderer, frame, location, scale, damage, log) + } + CosmicElement::Cursor(elem) => elem.draw(renderer, frame, location, scale, damage, log), + CosmicElement::MoveGrab(elem) => { + elem.draw(renderer, frame, location, scale, damage, log) + } + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => elem.draw(renderer, frame, location, scale, damage, log), + } + } + + fn underlying_storage( + &self, + renderer: &GlowRenderer, + ) -> Option> { + match self { + CosmicElement::Workspace(elem) => elem.underlying_storage(renderer), + CosmicElement::Cursor(elem) => elem.underlying_storage(renderer), + CosmicElement::MoveGrab(elem) => elem.underlying_storage(renderer), + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => elem.underlying_storage(renderer), + } + } +} + +impl<'a> RenderElement> for CosmicElement> { + fn draw( + &self, + renderer: &mut GlMultiRenderer<'a>, + frame: &mut as Renderer>::Frame, + location: Point, + scale: Scale, + damage: &[Rectangle], + log: &slog::Logger, + ) -> Result<(), as Renderer>::Error> { + match self { + CosmicElement::Workspace(elem) => { + elem.draw(renderer, frame, location, scale, damage, log) + } + CosmicElement::Cursor(elem) => elem.draw(renderer, frame, location, scale, damage, log), + CosmicElement::MoveGrab(elem) => { + elem.draw(renderer, frame, location, scale, damage, log) + } + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => { + let glow_renderer = renderer.glow_renderer_mut(); + let gles2_frame = frame.gles2_frame_mut(); + elem.draw(glow_renderer, gles2_frame, location, scale, damage, log) + .map_err(|err| MultiError::Render(err)) + } + } + } + + fn underlying_storage( + &self, + renderer: &GlMultiRenderer<'a>, + ) -> Option>> { + match self { + CosmicElement::Workspace(elem) => elem.underlying_storage(renderer), + CosmicElement::Cursor(elem) => elem.underlying_storage(renderer), + CosmicElement::MoveGrab(elem) => elem.underlying_storage(renderer), + #[cfg(feature = "debug")] + CosmicElement::Egui(elem) => { + let glow_renderer = renderer.glow_renderer(); + match elem.underlying_storage(glow_renderer) { + Some(UnderlyingStorage::Wayland(buffer)) => { + Some(UnderlyingStorage::Wayland(buffer)) + } + _ => None, + } + } + } + } +} + +impl From> for CosmicElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, +{ + fn from(elem: WorkspaceRenderElement) -> Self { + Self::Workspace(elem) + } +} + +impl From> for CosmicElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, +{ + fn from(elem: CursorRenderElement) -> Self { + Self::Cursor(elem) + } +} + +impl From> for CosmicElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, +{ + fn from(elem: CosmicMappedRenderElement) -> Self { + Self::MoveGrab(elem) + } +} + +impl From> for CosmicElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, +{ + fn from(elem: TextureRenderElement) -> Self { + Self::Egui(elem) + } +} + +pub trait AsGlowRenderer +where + Self: Renderer, + ::Frame: AsGles2Frame, +{ + fn glow_renderer(&self) -> &GlowRenderer; + fn glow_renderer_mut(&mut self) -> &mut GlowRenderer; +} + +impl AsGlowRenderer for GlowRenderer { + fn glow_renderer(&self) -> &GlowRenderer { + self + } + fn glow_renderer_mut(&mut self) -> &mut GlowRenderer { + self + } +} + +impl<'a> AsGlowRenderer for GlMultiRenderer<'a> { + fn glow_renderer(&self) -> &GlowRenderer { + self.as_ref() + } + fn glow_renderer_mut(&mut self) -> &mut GlowRenderer { + self.as_mut() + } +} + +pub trait AsGles2Frame +where + Self: Frame, +{ + fn gles2_frame(&self) -> &Gles2Frame; + fn gles2_frame_mut(&mut self) -> &mut Gles2Frame; +} + +impl AsGles2Frame for Gles2Frame { + fn gles2_frame(&self) -> &Gles2Frame { + self + } + fn gles2_frame_mut(&mut self) -> &mut Gles2Frame { + self + } +} + +impl AsGles2Frame for GlMultiFrame { + fn gles2_frame(&self) -> &Gles2Frame { + self.as_ref() + } + fn gles2_frame_mut(&mut self) -> &mut Gles2Frame { + self.as_mut() + } +} diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 96de627a..02cd7fd8 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -1,18 +1,12 @@ // SPDX-License-Identifier: GPL-3.0-only #[cfg(feature = "debug")] +use crate::{debug::fps_ui, state::Fps, utils::prelude::*}; use crate::{ - debug::{debug_ui, fps_ui, log_ui, EguiFrame}, - state::Fps, - utils::prelude::*, -}; -use crate::{ - shell::{ - layout::floating::SeatMoveGrabState, CosmicMappedRenderElement, WorkspaceRenderElement, - }, + shell::{layout::floating::SeatMoveGrabState, CosmicMappedRenderElement}, state::Common, wayland::{ - handlers::{data_device::get_dnd_icon, screencopy::render_to_buffer}, + handlers::{data_device::get_dnd_icon, screencopy::render_session}, protocols::{ screencopy::{ BufferParams, CursorMode as ScreencopyCursorMode, Session as ScreencopySession, @@ -28,42 +22,38 @@ use smithay::{ allocator::dmabuf::Dmabuf, drm::DrmNode, renderer::{ + buffer_dimensions, damage::{ DamageTrackedRenderer, DamageTrackedRendererError as RenderError, OutputNoMode, }, - element::RenderElementStates, - gles2::{Gles2Renderbuffer, Gles2Renderer}, + element::{RenderElement, RenderElementStates}, + gles2::{Gles2Error, Gles2Renderbuffer}, + glow::GlowRenderer, multigpu::{egl::EglGlesBackend, MultiFrame, MultiRenderer}, Bind, Blit, ExportMem, ImportAll, ImportMem, Offscreen, Renderer, TextureFilter, }, }, output::Output, utils::{Physical, Rectangle}, + wayland::dmabuf::get_dmabuf, }; pub mod cursor; use self::cursor::CursorRenderElement; +pub mod element; +use self::element::{AsGles2Frame, AsGlowRenderer, CosmicElement}; pub type GlMultiRenderer<'a> = MultiRenderer< 'a, 'a, - EglGlesBackend, - EglGlesBackend, + EglGlesBackend, + EglGlesBackend, Gles2Renderbuffer, >; -pub type GlMultiFrame = MultiFrame, EglGlesBackend>; +pub type GlMultiFrame = MultiFrame, EglGlesBackend>; pub static CLEAR_COLOR: [f32; 4] = [0.153, 0.161, 0.165, 1.0]; -smithay::render_elements! { - pub CosmicElement where R: ImportAll; - WorkspaceElement=WorkspaceRenderElement, - CursorElement=CursorRenderElement, - MoveGrabRenderElement=CosmicMappedRenderElement, - //#[cfg(feature = "debug")] - //EguiFrame=EguiFrame, -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CursorMode { None, @@ -79,6 +69,7 @@ pub fn cursor_elements( ) -> Vec where R: Renderer + ImportAll + ImportMem, + ::Frame: AsGles2Frame, ::TextureId: Clone + 'static, E: From> + From>, { @@ -101,7 +92,7 @@ where seat, location, scale.into(), - &state.start_time, + state.clock.now(), mode != CursorMode::NotDefault, ) .into_iter() @@ -132,16 +123,17 @@ where elements } -pub fn render_output( +pub fn render_output( gpu: Option<&DrmNode>, renderer: &mut R, + target: Target, damage_tracker: &mut DamageTrackedRenderer, age: usize, state: &mut Common, output: &Output, cursor_mode: CursorMode, screencopy: Option<(Source, &[(ScreencopySession, BufferParams)])>, - #[cfg(feature = "debug")] mut fps: Option<&mut Fps>, + #[cfg(feature = "debug")] fps: Option<&mut Fps>, ) -> Result<(Option>>, RenderElementStates), RenderError> where R: Renderer @@ -149,16 +141,21 @@ where + ImportMem + ExportMem + Bind - + Offscreen - + Bind - + Blit, + + Bind + + Offscreen + + Blit + + AsGlowRenderer, + ::Frame: AsGles2Frame, ::TextureId: Clone + 'static, + ::Error: From, + CosmicElement: RenderElement, Source: Clone, { let handle = state.shell.workspaces.active(output).handle; render_workspace( gpu, renderer, + target, damage_tracker, age, state, @@ -166,12 +163,15 @@ where &handle, cursor_mode, screencopy, + #[cfg(feature = "debug")] + fps, ) } -pub fn render_workspace( +pub fn render_workspace( gpu: Option<&DrmNode>, renderer: &mut R, + target: Target, damage_tracker: &mut DamageTrackedRenderer, age: usize, state: &mut Common, @@ -187,11 +187,15 @@ where + ImportMem + ExportMem + Bind - + Offscreen - + Bind - + Blit, - Source: Clone, + + Bind + + Offscreen + + Blit + + AsGlowRenderer, + ::Frame: AsGles2Frame, ::TextureId: Clone + 'static, + ::Error: From, + CosmicElement: RenderElement, + Source: Clone, { #[cfg(feature = "debug")] if let Some(ref mut fps) = fps { @@ -219,68 +223,67 @@ where #[cfg(feature = "debug")] { - // TODO add debug elements - let workspace = &state.shell.spaces[space_idx]; - let output_geo = workspace - .space - .output_geometry(output) - .unwrap_or(Rectangle::from_loc_and_size((0, 0), (0, 0))); + let output_geo = output.geometry(); let scale = output.current_scale().fractional_scale(); - if let Some(fps) = fps { + if let Some(fps) = fps.as_mut() { let fps_overlay = fps_ui( - _gpu, + gpu, state, + renderer.glow_renderer_mut(), fps, - output_geo.to_f64().to_physical(scale), + Rectangle::from_loc_and_size( + (0, 0), + (output_geo.size.w.min(400), output_geo.size.h.min(800)), + ), scale, - ); - custom_elements.push(fps_overlay.into()); - } - - let area = Rectangle::::from_loc_and_size( - state - .shell - .space_relative_output_geometry((0.0f64, 0.0f64), output), - state.shell.global_space().to_f64().size, - ) - .to_physical(scale); - if let Some(log_ui) = log_ui(state, area, scale, output_geo.size.w as f32 * 0.6) { - custom_elements.push(log_ui.into()); - } - if let Some(debug_overlay) = debug_ui(state, area, scale) { - custom_elements.push(debug_overlay.into()); + ) + .map_err(::Error::from) + .map_err(RenderError::Rendering)?; + elements.push(fps_overlay.into()); } } elements.extend( workspace - .render_output(output) + .render_output::(output) .map_err(|_| OutputNoMode)? .into_iter() .map(Into::into), ); + renderer.bind(target).map_err(RenderError::Rendering)?; let res = damage_tracker.render_output(renderer, age, &elements, CLEAR_COLOR, None); #[cfg(feature = "debug")] - if let Some(ref mut fps) = fps { + if let Some(fps) = fps.as_mut() { fps.end(); } if let Some((source, buffers)) = screencopy { if res.is_ok() { for (session, params) in buffers { - match render_to_buffer( + match render_session( gpu.cloned(), renderer, &session, params, output.current_transform(), - |_node, renderer, dtr, age| { + |_node, buffer, renderer, dtr, age| { let res = dtr.damage_output(age, &elements, slog_scope::logger())?; if let (Some(ref damage), _) = &res { + if let Ok(dmabuf) = get_dmabuf(buffer) { + renderer.bind(dmabuf).map_err(RenderError::Rendering)?; + } else { + let size = buffer_dimensions(buffer).unwrap(); + let render_buffer = renderer + .create_buffer(size) + .map_err(RenderError::Rendering)?; + renderer + .bind(render_buffer) + .map_err(RenderError::Rendering)?; + } for rect in damage { renderer .blit_from(source.clone(), *rect, *rect, TextureFilter::Nearest) diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 78965c6e..1790aef1 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -11,13 +11,17 @@ use crate::{ use anyhow::{anyhow, Context, Result}; use smithay::{ backend::{ - renderer::{damage::DamageTrackedRenderer, gles2::Gles2Renderbuffer, ImportDma, ImportEgl}, + renderer::{ + damage::DamageTrackedRenderer, gles2::Gles2Renderbuffer, glow::GlowRenderer, ImportDma, + ImportEgl, + }, winit::{self, WinitEvent, WinitGraphicsBackend, WinitVirtualDevice}, }, desktop::layer_map_for_output, output::{Mode, Output, PhysicalProperties, Scale, Subpixel}, reexports::{ calloop::{ping, EventLoop}, + wayland_protocols::wp::presentation_time::server::wp_presentation_feedback, wayland_server::DisplayHandle, }, utils::Transform, @@ -31,7 +35,7 @@ use super::render::CursorMode; pub struct WinitState { // The winit backend currently has no notion of multiple windows - pub backend: WinitGraphicsBackend, + pub backend: WinitGraphicsBackend, output: Output, damage_tracker: DamageTrackedRenderer, screencopy: Vec<(ScreencopySession, BufferParams)>, @@ -47,9 +51,10 @@ impl WinitState { let age = self.backend.buffer_age().unwrap_or(0); let surface = self.backend.egl_surface(); - match render::render_output::<_, Gles2Renderbuffer, _>( + match render::render_output::<_, _, Gles2Renderbuffer, _>( None, self.backend.renderer(), + surface.clone(), &mut self.damage_tracker, age, state, @@ -69,6 +74,19 @@ impl WinitState { .submit(damage.as_deref()) .with_context(|| "Failed to submit buffer for display")?; state.send_frames(&self.output, &states); + if damage.is_some() { + let mut output_presentation_feedback = + state.take_presentation_feedback(&self.output, &states); + output_presentation_feedback.presented( + state.clock.now(), + self.output + .current_mode() + .map(|mode| mode.refresh as u32) + .unwrap_or_default(), + 0, + wp_presentation_feedback::Kind::Vsync, + ) + } } Err(err) => { for (session, params) in self.screencopy.drain(..) { @@ -227,7 +245,7 @@ pub fn init_backend( fn init_egl_client_side( dh: &DisplayHandle, state: &mut State, - renderer: &mut WinitGraphicsBackend, + renderer: &mut WinitGraphicsBackend, ) -> Result<()> { let bind_result = renderer.renderer().bind_wl_display(dh); match bind_result { diff --git a/src/backend/x11.rs b/src/backend/x11.rs index 70ebd11b..7c9e903b 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -15,9 +15,8 @@ use smithay::{ egl::{EGLContext, EGLDisplay}, input::{Event, InputEvent}, renderer::{ - damage::DamageTrackedRenderer, - gles2::{Gles2Renderbuffer, Gles2Renderer}, - Bind, ImportDma, ImportEgl, + damage::DamageTrackedRenderer, gles2::Gles2Renderbuffer, glow::GlowRenderer, Bind, + ImportDma, ImportEgl, }, x11::{Window, WindowBuilder, X11Backend, X11Event, X11Handle, X11Input, X11Surface}, }, @@ -26,6 +25,7 @@ use smithay::{ reexports::{ calloop::{ping, EventLoop, LoopHandle}, gbm::{Device as GbmDevice, FdWrapper}, + wayland_protocols::wp::presentation_time::server::wp_presentation_feedback, wayland_server::DisplayHandle, }, utils::Transform, @@ -41,7 +41,7 @@ use crate::state::Fps; pub struct X11State { allocator: Arc>>, _egl: EGLDisplay, - pub renderer: Gles2Renderer, + pub renderer: GlowRenderer, surfaces: Vec, handle: X11Handle, } @@ -194,22 +194,15 @@ pub struct Surface { } impl Surface { - pub fn render_output( - &mut self, - renderer: &mut Gles2Renderer, - state: &mut Common, - ) -> Result<()> { + pub fn render_output(&mut self, renderer: &mut GlowRenderer, state: &mut Common) -> Result<()> { let (buffer, age) = self .surface .buffer() .with_context(|| "Failed to allocate buffer")?; - renderer - .bind(buffer.clone()) - .with_context(|| "Failed to bind buffer")?; - - match render::render_output::<_, Gles2Renderbuffer, _>( + match render::render_output::<_, _, Gles2Renderbuffer, _>( None, renderer, + buffer.clone(), &mut self.damage_tracker, age as usize, state, @@ -223,12 +216,25 @@ impl Surface { #[cfg(feature = "debug")] Some(&mut self.fps), ) { - Ok((_damage, states)) => { + Ok((damage, states)) => { self.screencopy.clear(); self.surface .submit() .with_context(|| "Failed to submit buffer for display")?; state.send_frames(&self.output, &states); + if damage.is_some() { + let mut output_presentation_feedback = + state.take_presentation_feedback(&self.output, &states); + output_presentation_feedback.presented( + state.clock.now(), + self.output + .current_mode() + .map(|mode| mode.refresh as u32) + .unwrap_or_default(), + 0, + wp_presentation_feedback::Kind::Vsync, + ) + } } Err(err) => { for (session, params) in self.screencopy.drain(..) { @@ -265,7 +271,7 @@ pub fn init_backend( // Create the OpenGL context let context = EGLContext::new(&egl, None).with_context(|| "Failed to create EGL context")?; // Create a renderer - let mut renderer = unsafe { Gles2Renderer::new(context, None) } + let mut renderer = unsafe { GlowRenderer::new(context, None) } .with_context(|| "Failed to initialize renderer")?; init_egl_client_side(dh, state, &mut renderer)?; @@ -383,11 +389,10 @@ pub fn init_backend( Ok(()) } -fn init_egl_client_side( - dh: &DisplayHandle, - state: &mut State, - renderer: &mut Gles2Renderer, -) -> Result<()> { +fn init_egl_client_side(dh: &DisplayHandle, state: &mut State, renderer: &mut R) -> Result<()> +where + R: ImportEgl + ImportDma, +{ let bind_result = renderer.bind_wl_display(dh); match bind_result { Ok(_) => { diff --git a/src/debug.rs b/src/debug.rs index f6d03048..c8abf923 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -2,20 +2,27 @@ use crate::state::{Common, Fps}; use smithay::{ - backend::drm::DrmNode, + backend::{ + drm::DrmNode, + renderer::{ + element::texture::TextureRenderElement, + gles2::{Gles2Error, Gles2Texture}, + glow::GlowRenderer, + }, + }, desktop::layer_map_for_output, reexports::wayland_server::Resource, - utils::{IsAlive, Physical, Rectangle}, + utils::{IsAlive, Logical, Rectangle}, }; -pub use smithay_egui::EguiFrame; pub fn fps_ui( gpu: Option<&DrmNode>, state: &Common, + renderer: &mut GlowRenderer, fps: &mut Fps, - area: Rectangle, + area: Rectangle, scale: f64, -) -> EguiFrame { +) -> Result, Gles2Error> { use egui::widgets::plot::{Bar, BarChart, HLine, Legend, Plot}; let (max, min, avg, avg_fps) = ( @@ -24,25 +31,27 @@ pub fn fps_ui( fps.avg_frametime().as_secs_f64(), fps.avg_fps(), ); + let amount = dbg!(avg_fps.round() as usize * 2); let bars = fps .frames .iter() .rev() - .take(30) + .take(amount) .rev() .enumerate() .map(|(i, (_, d))| { let value = d.as_secs_f64(); let transformed = ((value - min) / (max - min) * 255.0).round() as u8; + Bar::new(i as f64, transformed as f64).fill(egui::Color32::from_rgb( transformed, 255 - transformed, 0, )) }) - .collect(); + .collect::>(); - fps.state.run( + fps.state.render( |ctx| { egui::Area::new("main") .anchor(egui::Align2::LEFT_TOP, (10.0, 10.0)) @@ -51,14 +60,14 @@ pub fn fps_ui( "cosmic-comp version {}", std::env!("CARGO_PKG_VERSION") )); - if let Some(hash) = std::option_env!("GIT_HASH").and_then(|x| x.get(0..8)) { + if let Some(hash) = std::option_env!("GIT_HASH").and_then(|x| x.get(0..10)) { ui.label(hash); } if !state.egui.active { - ui.label("Press Mod+Escape for debug menu"); + ui.label("Press Super+Escape for debug menu"); } else { - ui.set_max_width(label_res.rect.min.x + label_res.rect.width()); + ui.set_max_width(400.0); ui.separator(); if let Some(gpu) = gpu { @@ -73,31 +82,34 @@ pub fn fps_ui( Plot::new("FPS") .legend(Legend::default()) - .view_aspect(33.0) + .view_aspect(50.0) .include_x(0.0) - .include_x(30.0) + .include_x(amount as f64) .include_y(0.0) - .include_y(300.0) + .include_y(300) .show_x(false) .show(ui, |plot_ui| { plot_ui.bar_chart(fps_chart); + /* plot_ui.hline( HLine::new(avg) .highlight(true) .color(egui::Color32::LIGHT_BLUE), ); + */ }); } }); }, + renderer, area, scale, - 1.0, - &state.start_time, - fps.modifiers.clone(), + 0.8, + state.clock.now().into(), ) } +/* pub fn debug_ui( state: &mut Common, area: Rectangle, @@ -296,132 +308,4 @@ pub fn debug_ui( state.egui.modifiers.clone(), )) } - -pub fn log_ui( - state: &mut Common, - area: Rectangle, - scale: f64, - default_width: f32, -) -> Option { - if !state.egui.active { - return None; - } - - Some(state.egui.log_state.run( - |ctx| { - egui::SidePanel::right("Log") - .frame(egui::Frame { - inner_margin: egui::Vec2::new(10.0, 10.0).into(), - outer_margin: egui::Vec2::new(0.0, 0.0).into(), - rounding: 5.0.into(), - shadow: egui::epaint::Shadow { - extrusion: 0.0, - color: egui::Color32::TRANSPARENT, - }, - fill: egui::Color32::from_black_alpha(100), - stroke: egui::Stroke::none(), - }) - .default_width(default_width) - .show(ctx, |ui| { - egui::ScrollArea::vertical() - .always_show_scroll(true) - .stick_to_bottom() - .show(ui, |ui| { - for (_i, record) in state - .log - .debug_buffer - .lock() - .unwrap() - .iter() - .rev() - .enumerate() - { - let mut message = egui::text::LayoutJob::single_section( - record.level.as_short_str().to_string(), - egui::TextFormat::simple( - egui::FontId::monospace(16.0), - match record.level { - slog::Level::Critical => egui::Color32::RED, - slog::Level::Error => egui::Color32::LIGHT_RED, - slog::Level::Warning => egui::Color32::LIGHT_YELLOW, - slog::Level::Info => egui::Color32::LIGHT_BLUE, - slog::Level::Debug => egui::Color32::LIGHT_GREEN, - slog::Level::Trace => egui::Color32::GRAY, - }, - ), - ); - message.append( - &record.message, - 6.0, - egui::TextFormat::simple( - egui::FontId::default(), - egui::Color32::WHITE, - ), - ); - ui.vertical(|ui| { - ui.add(egui::Label::new(message)); - ui.add_space(4.0); - for (k, v) in &record.kv { - ui.horizontal(|ui| { - ui.add( - egui::Label::new(egui::RichText::new(k).code()) - .sense(egui::Sense::click()), - ) - .on_hover_cursor(egui::CursorIcon::PointingHand); - render_value(ui, v); - }); - } - }); - } - }) - }); - }, - area, - scale, - state.egui.alpha, - &state.start_time, - state.egui.modifiers.clone(), - )) -} - -fn render_value(ui: &mut egui::Ui, value: &serde_json::Value) { - use serde_json::Value::*; - - match value { - Null => { - ui.label(egui::RichText::new("null").code()); - } - Bool(val) => { - ui.label(egui::RichText::new(format!("{}", val)).code()); - } - Number(val) => { - ui.label(egui::RichText::new(format!("{}", val)).code()); - } - String(val) => { - ui.label(val); - } - Array(list) => { - ui.vertical(|ui| { - ui.label("["); - for val in list { - ui.horizontal(|ui| { - ui.add_space(4.0); - render_value(ui, val); - }); - } - ui.label("]"); - }); - } - Object(map) => { - ui.vertical(|ui| { - for (k, val) in map { - ui.horizontal(|ui| { - ui.add_space(4.0); - ui.add(egui::Label::new(egui::RichText::new(k).code())); - render_value(ui, val); - }); - } - }); - } - }; -} +*/ diff --git a/src/input/mod.rs b/src/input/mod.rs index 4c5e0dd3..03f58d64 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -17,8 +17,8 @@ use crate::{ use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType; use smithay::{ backend::input::{ - AbsolutePositionEvent, Axis, AxisSource, Device, DeviceCapability, InputBackend, - InputEvent, KeyState, PointerAxisEvent, + Axis, AxisSource, Device, DeviceCapability, InputBackend, InputEvent, KeyState, + PointerAxisEvent, }, desktop::{layer_map_for_output, WindowSurfaceType}, input::{ @@ -166,7 +166,6 @@ impl State { #[cfg(feature = "debug")] { self.common.egui.debug_state.handle_device_added(&device); - self.common.egui.log_state.handle_device_added(&device); } } InputEvent::DeviceRemoved { device } => { @@ -185,8 +184,7 @@ impl State { } #[cfg(feature = "debug")] { - self.common.egui.debug_state.handle_device_added(&device); - self.common.egui.log_state.handle_device_added(&device); + self.common.egui.debug_state.handle_device_removed(&device); } } InputEvent::Keyboard { event, .. } => { @@ -234,8 +232,7 @@ impl State { } #[cfg(feature = "debug")] { - if data.common.seats.iter().position(|x| x == seat).unwrap() - == 0 + if data.common.seats().position(|x| x == seat).unwrap() == 0 && data.common.egui.active { if data.common.egui.debug_state.wants_keyboard() { @@ -250,18 +247,6 @@ impl State { .add(&handle); return FilterResult::Intercept(None); } - if data.common.egui.log_state.wants_keyboard() { - data.common.egui.log_state.handle_keyboard( - &handle, - state == KeyState::Pressed, - modifiers.clone(), - ); - userdata - .get::() - .unwrap() - .add(&handle); - return FilterResult::Intercept(None); - } } } @@ -372,7 +357,7 @@ impl State { output.current_transform(), &output.geometry().size.to_f64(), ), - &self.common.start_time, + self.common.clock.now(), ) { session.cursor_info(seat, InputType::Pointer, geometry, offset); } @@ -388,15 +373,11 @@ impl State { ); #[cfg(feature = "debug")] - if self.common.seats.iter().position(|x| x == seat).unwrap() == 0 { + if self.common.seats().position(|x| x == seat).unwrap() == 0 { self.common .egui .debug_state .handle_pointer_motion(position.to_i32_round()); - self.common - .egui - .log_state - .handle_pointer_motion(position.to_i32_round()); } break; } @@ -410,8 +391,11 @@ impl State { if devices.has_device(&device) { let output = seat.active_output(); let geometry = output.geometry(); - let position = - geometry.loc.to_f64() + event.position_transformed(geometry.size); + let position = geometry.loc.to_f64() + + smithay::backend::input::AbsolutePositionEvent::position_transformed( + &event, + geometry.size, + ); let relative_pos = self.common.shell.map_global_to_space(position, &output); let workspace = self.common.shell.active_space_mut(&output); let serial = SERIAL_COUNTER.next_serial(); @@ -433,15 +417,11 @@ impl State { ); #[cfg(feature = "debug")] - if self.common.seats.iter().position(|x| x == seat).unwrap() == 0 { + if self.common.seats().position(|x| x == seat).unwrap() == 0 { self.common .egui .debug_state .handle_pointer_motion(position.to_i32_round()); - self.common - .egui - .log_state - .handle_pointer_motion(position.to_i32_round()); } break; } @@ -456,7 +436,7 @@ impl State { let devices = userdata.get::().unwrap(); if devices.has_device(&device) { #[cfg(feature = "debug")] - if self.common.seats.iter().position(|x| x == seat).unwrap() == 0 + if self.common.seats().position(|x| x == seat).unwrap() == 0 && self.common.egui.active { if self.common.egui.debug_state.wants_pointer() { @@ -464,17 +444,6 @@ impl State { self.common.egui.debug_state.handle_pointer_button( button, event.state() == ButtonState::Pressed, - self.common.egui.modifiers.clone(), - ); - } - break; - } - if self.common.egui.log_state.wants_pointer() { - if let Some(button) = event.button() { - self.common.egui.log_state.handle_pointer_button( - button, - event.state() == ButtonState::Pressed, - self.common.egui.modifiers.clone(), ); } break; @@ -575,7 +544,7 @@ impl State { let device = event.device(); for seat in self.common.seats().cloned().collect::>().iter() { #[cfg(feature = "debug")] - if self.common.seats.iter().position(|x| x == seat).unwrap() == 0 + if self.common.seats().position(|x| x == seat).unwrap() == 0 && self.common.egui.active { if self.common.egui.debug_state.wants_pointer() { @@ -591,19 +560,6 @@ impl State { ); break; } - if self.common.egui.log_state.wants_pointer() { - self.common.egui.log_state.handle_pointer_axis( - event - .amount_discrete(Axis::Horizontal) - .or_else(|| event.amount(Axis::Horizontal).map(|x| x * 3.0)) - .unwrap_or(0.0), - event - .amount_discrete(Axis::Vertical) - .or_else(|| event.amount(Axis::Vertical).map(|x| x * 3.0)) - .unwrap_or(0.0), - ); - break; - } } let userdata = seat.user_data(); diff --git a/src/logger/mod.rs b/src/logger/mod.rs index 4818c7da..14793437 100644 --- a/src/logger/mod.rs +++ b/src/logger/mod.rs @@ -1,104 +1,16 @@ // SPDX-License-Identifier: GPL-3.0-only -#[cfg(feature = "debug")] -use std::{ - collections::VecDeque, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, Mutex, - }, -}; - use anyhow::Result; use slog::Drain; -#[cfg(feature = "debug")] -mod serializer; - -#[cfg(feature = "debug")] -const MAX_RECORDS: usize = 1000; - -#[cfg(feature = "debug")] -pub type LogBuffer = Arc>>; - -#[cfg(feature = "debug")] -#[derive(Clone)] -struct DebugDrain { - buffer: LogBuffer, - dirty_flag: Arc, -} - pub struct LogState { _guard: slog_scope::GlobalLoggerGuard, - #[cfg(feature = "debug")] - pub dirty_flag: Arc, - #[cfg(feature = "debug")] - pub debug_buffer: LogBuffer, -} - -#[cfg(feature = "debug")] -pub struct OwnedRecord { - pub message: String, - pub level: slog::Level, - pub kv: serde_json::map::Map, -} - -#[cfg(feature = "debug")] -impl DebugDrain { - fn new() -> (DebugDrain, LogBuffer, Arc) { - let dirty_flag = Arc::new(AtomicBool::new(false)); - let buffer = Arc::new(Mutex::new(VecDeque::new())); - ( - DebugDrain { - buffer: buffer.clone(), - dirty_flag: dirty_flag.clone(), - }, - buffer, - dirty_flag, - ) - } -} - -#[cfg(feature = "debug")] -impl Drain for DebugDrain { - type Ok = (); - type Err = slog::Error; - - fn log( - &self, - record: &slog::Record<'_>, - values: &slog::OwnedKVList, - ) -> Result { - use serde_json::value::{Serializer as ValueSerializer, Value}; - use serializer::SerdeSerializer; - use slog::KV; - - let mut serializer = SerdeSerializer::start(ValueSerializer, None)?; - values.serialize(record, &mut serializer)?; - record.kv().serialize(record, &mut serializer)?; - let value = match serializer.end().map_err(|_| slog::Error::Other)? { - Value::Object(map) => map, - _ => unreachable!(), - }; - - let mut buffer = self.buffer.lock().unwrap(); - buffer.push_front(OwnedRecord { - message: format!("{}", record.msg()), - level: record.level(), - kv: value, - }); - buffer.truncate(MAX_RECORDS); - self.dirty_flag.store(true, Ordering::SeqCst); - - Ok(()) - } } pub fn init_logger() -> Result { let decorator = slog_term::TermDecorator::new().stderr().build(); // usually we would not want to use a Mutex here, but this is usefull for a prototype, // to make sure we do not miss any in-flight messages, when we crash. - #[cfg(not(feature = "debug"))] let logger = slog::Logger::root( std::sync::Mutex::new( slog_term::CompactFormat::new(decorator) @@ -108,21 +20,6 @@ pub fn init_logger() -> Result { .fuse(), slog::o!(), ); - #[cfg(feature = "debug")] - let (debug_drain, debug_buffer, dirty_flag) = DebugDrain::new(); - #[cfg(feature = "debug")] - let logger = slog::Logger::root( - slog::Duplicate::new( - std::sync::Mutex::new( - slog_term::CompactFormat::new(decorator) - .build() - .ignore_res(), - ), - debug_drain, - ) - .fuse(), - slog::o!(), - ); let _guard = slog_scope::set_global_logger(logger); slog_stdlog::init().unwrap(); @@ -135,11 +32,5 @@ pub fn init_logger() -> Result { ); } - Ok(LogState { - _guard, - #[cfg(feature = "debug")] - debug_buffer, - #[cfg(feature = "debug")] - dirty_flag, - }) + Ok(LogState { _guard }) } diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index f39cafb0..8377ff90 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -308,7 +308,7 @@ impl PointerTarget for CosmicStack { 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) + seat.cursor_geometry(buffer_loc, data.common.clock.now()) { session.cursor_info(seat, InputType::Pointer, geo, hotspot); } diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index 9df7f253..c65ca09c 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -206,7 +206,7 @@ impl PointerTarget for CosmicWindow { 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) + seat.cursor_geometry(buffer_loc, data.common.clock.now()) { session.cursor_info(seat, InputType::Pointer, geo, hotspot); } diff --git a/src/shell/layout/floating/grabs/moving.rs b/src/shell/layout/floating/grabs/moving.rs index 02b52713..a04cf043 100644 --- a/src/shell/layout/floating/grabs/moving.rs +++ b/src/shell/layout/floating/grabs/moving.rs @@ -51,7 +51,8 @@ impl MoveGrabState { } let scale = output.current_scale().fractional_scale().into(); - self.window.render_elements::( + AsRenderElements::::render_elements::( + &self.window, (location.to_i32_round() - output.geometry().loc - self.window.geometry().loc) .to_physical_precise_round(scale), scale, diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 4f4a3755..3760bfd3 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -1243,7 +1243,8 @@ impl TilingLayout { } }) .flat_map(|(mapped, loc)| { - mapped.render_elements::>( + AsRenderElements::::render_elements::>( + mapped, loc.to_physical_precise_round(output_scale) - mapped .geometry() diff --git a/src/state.rs b/src/state.rs index 16a4e159..9b996f56 100644 --- a/src/state.rs +++ b/src/state.rs @@ -19,7 +19,10 @@ use smithay::{ drm::DrmNode, renderer::element::{default_primary_scanout_output_compare, RenderElementStates}, }, - desktop::utils::{surface_primary_scanout_output, update_surface_primary_scanout_output}, + desktop::utils::{ + surface_presentation_feedback_flags_from_states, surface_primary_scanout_output, + update_surface_primary_scanout_output, OutputPresentationFeedback, + }, input::{Seat, SeatState}, output::{Mode as OutputMode, Output, Scale}, reexports::{ @@ -30,20 +33,18 @@ use smithay::{ Display, DisplayHandle, }, }, + utils::{Clock, Monotonic, Rectangle}, wayland::{ compositor::CompositorState, data_device::DataDeviceState, dmabuf::DmabufState, keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitState, output::OutputManagerState, - primary_selection::PrimarySelectionState, shm::ShmState, viewporter::ViewporterState, + presentation::PresentationState, primary_selection::PrimarySelectionState, shm::ShmState, + viewporter::ViewporterState, }, }; -use std::{ - cell::RefCell, - ffi::OsString, - time::{Duration, Instant}, -}; +use std::{cell::RefCell, ffi::OsString, time::Duration}; #[cfg(feature = "debug")] -use std::{collections::VecDeque, time::Duration}; +use std::{collections::VecDeque, time::Instant}; pub struct ClientState { pub workspace_client_state: WorkspaceClientState, @@ -79,7 +80,7 @@ pub struct Common { seats: Vec>, last_active_seat: Option>, - pub start_time: Instant, + pub clock: Clock, pub should_stop: bool, pub log: LogState, @@ -93,6 +94,7 @@ pub struct Common { pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState, pub output_state: OutputManagerState, pub output_configuration_state: OutputConfigurationState, + pub presentation_state: PresentationState, pub primary_selection_state: PrimarySelectionState, pub screencopy_state: ScreencopyState, pub seat_state: SeatState, @@ -104,8 +106,6 @@ pub struct Common { #[cfg(feature = "debug")] pub struct Egui { pub debug_state: smithay_egui::EguiState, - pub log_state: smithay_egui::EguiState, - pub modifiers: smithay::wayland::seat::ModifiersState, pub active: bool, pub alpha: f32, } @@ -113,11 +113,19 @@ pub struct Egui { #[cfg(feature = "debug")] pub struct Fps { pub state: smithay_egui::EguiState, - pub modifiers: smithay::wayland::seat::ModifiersState, pub frames: VecDeque<(Instant, Duration)>, pub start: Instant, } +#[cfg(feature = "debug")] +pub struct Frame { + start: Instant, + duration_elements: Duration, + duration_render: Duration, + duration_screencopy: Duration, + duration_displayed: Duration, +} + pub enum BackendData { X11(X11State), Winit(WinitState), @@ -204,7 +212,7 @@ impl BackendData { // Swapping with damage (which should be empty on these frames) is likely good enough anyway. 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, screencopy) { + if let Err(err) = state.schedule_render(loop_handle, output, None, screencopy) { slog_scope::crit!("Failed to schedule event, are we shutting down? {:?}", err); } } @@ -221,6 +229,7 @@ impl State { signal: LoopSignal, log: LogState, ) -> State { + let clock = Clock::new().expect("Failed to initialize clock"); let config = Config::load(); let compositor_state = CompositorState::new::(dh, None); let data_device_state = DataDeviceState::new::(dh, None); @@ -228,6 +237,7 @@ impl State { let keyboard_shortcuts_inhibit_state = KeyboardShortcutsInhibitState::new::(dh); let output_state = OutputManagerState::new_with_xdg_output::(dh); let output_configuration_state = OutputConfigurationState::new(dh, |_| true); + let presentation_state = PresentationState::new::(dh, clock.id() as u32); let primary_selection_state = PrimarySelectionState::new::(dh, None); let screencopy_state = ScreencopyState::new::( dh, @@ -258,20 +268,16 @@ impl State { seats: Vec::new(), last_active_seat: None, - start_time: Instant::now(), + clock, should_stop: false, log, #[cfg(feature = "debug")] egui: Egui { - debug_state: smithay_egui::EguiState::new(smithay_egui::EguiMode::Continuous), - log_state: { - let mut state = - smithay_egui::EguiState::new(smithay_egui::EguiMode::Continuous); - state.set_zindex(0); - state - }, - modifiers: Default::default(), + debug_state: smithay_egui::EguiState::new(Rectangle::from_loc_and_size( + (0, 0), + (400, 800), + )), active: false, alpha: 1.0, }, @@ -285,6 +291,7 @@ impl State { keyboard_shortcuts_inhibit_state, output_state, output_configuration_state, + presentation_state, primary_selection_state, viewporter_state, wl_drm_state, @@ -365,7 +372,7 @@ impl Common { } pub fn send_frames(&self, output: &Output, render_element_states: &RenderElementStates) { - let time = self.start_time.elapsed(); + let time = self.clock.now(); let throttle = Some(Duration::from_secs(1)); let active = self.shell.active_space(output); @@ -413,11 +420,43 @@ impl Common { layer_surface.send_frame(output, time, throttle, surface_primary_scanout_output); } } + + pub fn take_presentation_feedback( + &self, + output: &Output, + render_element_states: &RenderElementStates, + ) -> OutputPresentationFeedback { + let mut output_presentation_feedback = OutputPresentationFeedback::new(output); + + let active = self.shell.active_space(output); + active.mapped().for_each(|mapped| { + mapped.active_window().take_presentation_feedback( + &mut output_presentation_feedback, + surface_primary_scanout_output, + |surface, _| { + surface_presentation_feedback_flags_from_states(surface, render_element_states) + }, + ); + }); + + let map = smithay::desktop::layer_map_for_output(output); + for layer_surface in map.layers() { + layer_surface.take_presentation_feedback( + &mut output_presentation_feedback, + surface_primary_scanout_output, + |surface, _| { + surface_presentation_feedback_flags_from_states(surface, render_element_states) + }, + ); + } + + output_presentation_feedback + } } #[cfg(feature = "debug")] impl Fps { - const WINDOW_SIZE: usize = 100; + const WINDOW_SIZE: usize = 1000; pub fn start(&mut self) { self.start = Instant::now(); @@ -478,14 +517,13 @@ impl Default for Fps { fn default() -> Fps { Fps { state: { - let mut state = smithay_egui::EguiState::new(smithay_egui::EguiMode::Continuous); + let state = + smithay_egui::EguiState::new(Rectangle::from_loc_and_size((0, 0), (400, 800))); let mut visuals: egui::style::Visuals = Default::default(); visuals.window_shadow.extrusion = 0.0; state.context().set_visuals(visuals); - state.set_zindex(110); // always render on top state }, - modifiers: Default::default(), frames: VecDeque::with_capacity(Fps::WINDOW_SIZE + 1), start: Instant::now(), } diff --git a/src/utils/prelude.rs b/src/utils/prelude.rs index 18deee0b..57348025 100644 --- a/src/utils/prelude.rs +++ b/src/utils/prelude.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, sync::Mutex}; +use std::{cell::RefCell, sync::Mutex, time::Duration}; use crate::{ backend::render::cursor::CursorState, @@ -11,7 +11,7 @@ use smithay::{ Seat, }, output::Output, - utils::{Buffer, IsAlive, Logical, Point, Rectangle, Transform}, + utils::{Buffer, IsAlive, Logical, Monotonic, Point, Rectangle, Time, Transform}, wayland::compositor::with_states, }; @@ -47,7 +47,7 @@ pub trait SeatExt { fn cursor_geometry( &self, loc: impl Into>, - start_time: &std::time::Instant, + time: Time, ) -> Option<(Rectangle, Point)>; } @@ -75,7 +75,7 @@ impl SeatExt for Seat { fn cursor_geometry( &self, loc: impl Into>, - start_time: &std::time::Instant, + time: Time, ) -> Option<(Rectangle, Point)> { let location = loc.into().to_i32_round(); @@ -117,7 +117,7 @@ impl SeatExt for Seat { let state = seat_userdata.get::().unwrap(); let frame = state .cursor - .get_image(1, start_time.elapsed().as_millis() as u32); + .get_image(1, Into::::into(time).as_millis() as u32); Some(( Rectangle::from_loc_and_size( diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index 5f93b207..6b6028d5 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -4,11 +4,11 @@ pub mod buffer; pub mod compositor; pub mod data_device; pub mod dmabuf; -//pub mod export_dmabuf; pub mod keyboard_shortcuts_inhibit; pub mod layer_shell; pub mod output; pub mod output_configuration; +pub mod presentation; pub mod primary_selection; pub mod screencopy; pub mod seat; diff --git a/src/wayland/handlers/presentation.rs b/src/wayland/handlers/presentation.rs new file mode 100644 index 00000000..96dd14e7 --- /dev/null +++ b/src/wayland/handlers/presentation.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::state::State; +use smithay::delegate_presentation; + +delegate_presentation!(State); diff --git a/src/wayland/handlers/screencopy.rs b/src/wayland/handlers/screencopy.rs index 540be69c..9bab636b 100644 --- a/src/wayland/handlers/screencopy.rs +++ b/src/wayland/handlers/screencopy.rs @@ -19,7 +19,8 @@ use smithay::{ element::{ surface::WaylandSurfaceRenderElement, AsRenderElements, RenderElementStates, }, - gles2::{Gles2Renderbuffer, Gles2Renderer}, + gles2::Gles2Renderbuffer, + glow::GlowRenderer, Bind, BufferType, ExportMem, ImportAll, Offscreen, Renderer, }, }, @@ -471,23 +472,6 @@ fn node_from_params( } } -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, @@ -537,7 +521,7 @@ where Ok(()) } -pub fn render_to_buffer( +pub fn render_session( node: Option, renderer: &mut R, session: &Session, @@ -546,9 +530,10 @@ pub fn render_to_buffer( render_fn: F, ) -> Result> where - R: Bind + Offscreen + ExportMem, + R: ExportMem, F: FnOnce( Option<&DrmNode>, + &WlBuffer, &mut R, &mut DamageTrackedRenderer, usize, @@ -557,15 +542,19 @@ where 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)?; + let res = render_fn( + node.as_ref(), + ¶ms.buffer, + renderer, + &mut *dtr, + params.age as usize, + )?; if let (Some(damage), _) = res { submit_buffer(session, ¶ms.buffer, renderer, transform, damage) @@ -607,28 +596,50 @@ pub fn render_output_to_buffer( }; let common = &mut state.common; - render_to_buffer::<_, _, Gles2Renderbuffer>( + render_session::<_, _>( 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, - ) + |node, buffer, renderer, dtr, age| { + let cursor_mode = match session.cursor_mode() { + ScreencopyCursorMode::Embedded => CursorMode::All, + ScreencopyCursorMode::Captured(_) | ScreencopyCursorMode::None => CursorMode::None, + }; + + if let Ok(dmabuf) = get_dmabuf(buffer) { + render_output::<_, _, Gles2Renderbuffer, Dmabuf>( + node, + renderer, + dmabuf, + dtr, + age, + common, + &output, + cursor_mode, + None, + #[cfg(feature = "debug")] + None, + ) + } else { + let size = buffer_dimensions(buffer).unwrap(); + let render_buffer = Offscreen::::create_buffer(renderer, size) + .map_err(DamageTrackedRendererError::Rendering)?; + render_output::<_, _, Gles2Renderbuffer, Dmabuf>( + node, + renderer, + render_buffer, + dtr, + age, + common, + &output, + cursor_mode, + None, + #[cfg(feature = "debug")] + None, + ) + } }, ) .map_err(|err| (FailureReason::Unspec, err.into())) @@ -666,29 +677,51 @@ pub fn render_workspace_to_buffer( }; let common = &mut state.common; - render_to_buffer::<_, _, Gles2Renderbuffer>( + render_session::<_, _>( 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, - ) + |node, buffer, renderer, dtr, age| { + let cursor_mode = match session.cursor_mode() { + ScreencopyCursorMode::Embedded => CursorMode::All, + ScreencopyCursorMode::Captured(_) | ScreencopyCursorMode::None => CursorMode::None, + }; + if let Ok(dmabuf) = get_dmabuf(buffer) { + render_workspace::<_, _, Gles2Renderbuffer, Dmabuf>( + node, + renderer, + dmabuf, + dtr, + age, + common, + &output, + handle, + cursor_mode, + None, + #[cfg(feature = "debug")] + None, + ) + } else { + let size = buffer_dimensions(buffer).unwrap(); + let render_buffer = Offscreen::::create_buffer(renderer, size) + .map_err(DamageTrackedRendererError::Rendering)?; + render_workspace::<_, _, Gles2Renderbuffer, Dmabuf>( + node, + renderer, + render_buffer, + dtr, + age, + common, + &output, + handle, + cursor_mode, + None, + #[cfg(feature = "debug")] + None, + ) + } }, ) .map_err(|err| (FailureReason::Unspec, err.into())) @@ -728,16 +761,16 @@ pub fn render_window_to_buffer( _ => unreachable!(), }; - render_to_buffer::<_, _, Gles2Renderbuffer>( + render_session::<_, _>( node, renderer, session, ¶ms, Transform::Normal, - |_node, renderer, dtr, age| { + |_node, buffer, renderer, dtr, age| { // TODO cursor elements! - let mut elements = AsRenderElements::::render_elements::< - WindowCaptureElement, + let mut elements = AsRenderElements::::render_elements::< + WindowCaptureElement, >( window, (-geometry.loc.x, -geometry.loc.y).into(), @@ -771,7 +804,7 @@ pub fn render_window_to_buffer( seat, location, 1.0.into(), - &state.common.start_time, + state.common.clock.now(), true, ) .into_iter() @@ -789,6 +822,19 @@ pub fn render_window_to_buffer( } } + if let Ok(dmabuf) = get_dmabuf(buffer) { + renderer + .bind(dmabuf) + .map_err(DamageTrackedRendererError::Rendering)?; + } else { + let size = buffer_dimensions(buffer).unwrap(); + let render_buffer = Offscreen::::create_buffer(renderer, size) + .map_err(DamageTrackedRendererError::Rendering)?; + renderer + .bind(render_buffer) + .map_err(DamageTrackedRendererError::Rendering)?; + } + dtr.render_output(renderer, age, &elements, CLEAR_COLOR, None) }, ) From 9823b42c20d4fa3f79c54b649af3b5a7c7ccfcc2 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 18 Nov 2022 13:35:36 +0100 Subject: [PATCH 45/62] Fix fullscreen window geometry --- src/shell/element/mod.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index 590b3046..890ac9cd 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -236,7 +236,10 @@ impl CosmicMapped { }; match window.toplevel() { - Kind::Xdg(xdg) => xdg.current_state().states.contains(XdgState::Resizing), + Kind::Xdg(xdg) => { + xdg.current_state().states.contains(XdgState::Resizing) + || xdg.with_pending_state(|states| states.states.contains(XdgState::Resizing)) + } // Kind::X11? } } @@ -309,8 +312,10 @@ impl CosmicMapped { }; match window.toplevel() { - Kind::Xdg(xdg) => xdg.current_state().states.contains(XdgState::Fullscreen), - // Kind::X11? + Kind::Xdg(xdg) => { + xdg.current_state().states.contains(XdgState::Fullscreen) + || xdg.with_pending_state(|states| states.states.contains(XdgState::Fullscreen)) + } // Kind::X11? } } @@ -343,8 +348,10 @@ impl CosmicMapped { }; match window.toplevel() { - Kind::Xdg(xdg) => xdg.current_state().states.contains(XdgState::Maximized), - // Kind::X11? + Kind::Xdg(xdg) => { + xdg.current_state().states.contains(XdgState::Maximized) + || xdg.with_pending_state(|states| states.states.contains(XdgState::Maximized)) + } // Kind::X11? } } @@ -377,7 +384,10 @@ impl CosmicMapped { }; match window.toplevel() { - Kind::Xdg(xdg) => xdg.current_state().states.contains(XdgState::Activated), + Kind::Xdg(xdg) => { + xdg.current_state().states.contains(XdgState::Activated) + || xdg.with_pending_state(|states| states.states.contains(XdgState::Activated)) + } // Kind::X11? } } From 36cb2ac719df4e21d083916fe88428a179412169 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 18 Nov 2022 17:20:52 +0100 Subject: [PATCH 46/62] debug: Better frame graph --- src/backend/kms/mod.rs | 3 + src/backend/render/mod.rs | 11 ++- src/backend/winit.rs | 2 + src/backend/x11.rs | 2 + src/debug.rs | 82 ++++++++++++----- src/state.rs | 187 ++++++++++++++++++++++++++++---------- 6 files changed, 215 insertions(+), 72 deletions(-) diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index c64f984f..d8eb8856 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -381,6 +381,9 @@ impl State { let rescheduled_output = if let Some(device) = data.state.backend.kms().devices.get_mut(&drm_node) { if let Some(surface) = device.surfaces.get_mut(&crtc) { + #[cfg(feature = "debug")] + surface.fps.displayed(); + match surface.surface.as_mut().map(|x| x.frame_submitted()) { Some(Ok(feedback)) => { if let Some(mut feedback) = feedback.flatten() { diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 02cd7fd8..c3f2e96e 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -252,12 +252,17 @@ where .map(Into::into), ); + #[cfg(feature = "debug")] + if let Some(fps) = fps.as_mut() { + fps.elements(); + } + renderer.bind(target).map_err(RenderError::Rendering)?; let res = damage_tracker.render_output(renderer, age, &elements, CLEAR_COLOR, None); #[cfg(feature = "debug")] if let Some(fps) = fps.as_mut() { - fps.end(); + fps.render(); } if let Some((source, buffers)) = screencopy { @@ -303,6 +308,10 @@ where } } } + #[cfg(feature = "debug")] + if let Some(fps) = fps.as_mut() { + fps.screencopy(); + } } res diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 1790aef1..174dffb2 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -73,6 +73,8 @@ impl WinitState { self.backend .submit(damage.as_deref()) .with_context(|| "Failed to submit buffer for display")?; + #[cfg(feature = "debug")] + self.fps.displayed(); state.send_frames(&self.output, &states); if damage.is_some() { let mut output_presentation_feedback = diff --git a/src/backend/x11.rs b/src/backend/x11.rs index 7c9e903b..da5eb1fc 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -221,6 +221,8 @@ impl Surface { self.surface .submit() .with_context(|| "Failed to submit buffer for display")?; + #[cfg(feature = "debug")] + self.fps.displayed(); state.send_frames(&self.output, &states); if damage.is_some() { let mut output_presentation_feedback = diff --git a/src/debug.rs b/src/debug.rs index c8abf923..8e41261c 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::state::{Common, Fps}; +use egui::Color32; use smithay::{ backend::{ drm::DrmNode, @@ -15,6 +16,11 @@ use smithay::{ utils::{IsAlive, Logical, Rectangle}, }; +pub const ELEMENTS_COLOR: Color32 = Color32::from_rgb(70, 198, 115); +pub const RENDER_COLOR: Color32 = Color32::from_rgb(29, 114, 58); +pub const SCREENCOPY_COLOR: Color32 = Color32::from_rgb(253, 178, 39); +pub const DISPLAY_COLOR: Color32 = Color32::from_rgb(41, 184, 209); + pub fn fps_ui( gpu: Option<&DrmNode>, state: &Common, @@ -31,43 +37,70 @@ pub fn fps_ui( fps.avg_frametime().as_secs_f64(), fps.avg_fps(), ); + let (max_disp, min_disp) = ( + fps.max_time_to_display().as_secs_f64(), + fps.min_time_to_display().as_secs_f64(), + ); + let amount = dbg!(avg_fps.round() as usize * 2); - let bars = fps + let ((bars_elements, bars_render), (bars_screencopy, bars_displayed)): ( + (Vec, Vec), + (Vec, Vec), + ) = fps .frames .iter() .rev() .take(amount) .rev() .enumerate() - .map(|(i, (_, d))| { - let value = d.as_secs_f64(); - let transformed = ((value - min) / (max - min) * 255.0).round() as u8; + .map(|(i, frame)| { + let elements_val = frame.duration_elements.as_secs_f64(); + let render_val = frame.duration_render.as_secs_f64(); + let screencopy_val = frame + .duration_screencopy + .as_ref() + .map(|val| val.as_secs_f64()) + .unwrap_or(0.0); + let displayed_val = frame.duration_displayed.as_secs_f64(); - Bar::new(i as f64, transformed as f64).fill(egui::Color32::from_rgb( - transformed, - 255 - transformed, - 0, - )) + let transformed_elements = + ((elements_val - min_disp) / (max_disp - min_disp) * 255.0).round() as u8; + let transformed_render = + ((render_val - min_disp) / (max_disp - min_disp) * 255.0).round() as u8; + let transformed_screencopy = + ((screencopy_val - min_disp) / (max_disp - min_disp) * 255.0).round() as u8; + let transformed_displayed = + ((displayed_val - min_disp) / (max_disp - min_disp) * 255.0).round() as u8; + ( + ( + Bar::new(i as f64, transformed_elements as f64).fill(ELEMENTS_COLOR), + Bar::new(i as f64, transformed_render as f64).fill(RENDER_COLOR), + ), + ( + Bar::new(i as f64, transformed_screencopy as f64).fill(SCREENCOPY_COLOR), + Bar::new(i as f64, transformed_displayed as f64).fill(DISPLAY_COLOR), + ), + ) }) - .collect::>(); + .unzip(); fps.state.render( |ctx| { egui::Area::new("main") .anchor(egui::Align2::LEFT_TOP, (10.0, 10.0)) .show(ctx, |ui| { - let label_res = ui.label(format!( + ui.label(format!( "cosmic-comp version {}", std::env!("CARGO_PKG_VERSION") )); if let Some(hash) = std::option_env!("GIT_HASH").and_then(|x| x.get(0..10)) { - ui.label(hash); + ui.label(format!(" :{hash}")); } if !state.egui.active { ui.label("Press Super+Escape for debug menu"); } else { - ui.set_max_width(400.0); + ui.set_max_width(300.0); ui.separator(); if let Some(gpu) = gpu { @@ -78,7 +111,16 @@ pub fn fps_ui( ui.label(egui::RichText::new(format!("avg: {:>7.6}", avg)).code()); ui.label(egui::RichText::new(format!("min: {:>7.6}", min)).code()); ui.label(egui::RichText::new(format!("max: {:>7.6}", max)).code()); - let fps_chart = BarChart::new(bars).vertical(); + let elements_chart = BarChart::new(bars_elements).vertical(); + let render_chart = BarChart::new(bars_render) + .stack_on(&[&elements_chart]) + .vertical(); + let screencopy_chart = BarChart::new(bars_screencopy) + .stack_on(&[&elements_chart, &render_chart]) + .vertical(); + let display_chart = BarChart::new(bars_displayed) + .stack_on(&[&elements_chart, &render_chart, &screencopy_chart]) + .vertical(); Plot::new("FPS") .legend(Legend::default()) @@ -89,14 +131,10 @@ pub fn fps_ui( .include_y(300) .show_x(false) .show(ui, |plot_ui| { - plot_ui.bar_chart(fps_chart); - /* - plot_ui.hline( - HLine::new(avg) - .highlight(true) - .color(egui::Color32::LIGHT_BLUE), - ); - */ + plot_ui.bar_chart(elements_chart); + plot_ui.bar_chart(render_chart); + plot_ui.bar_chart(screencopy_chart); + plot_ui.bar_chart(display_chart); }); } }); diff --git a/src/state.rs b/src/state.rs index 9b996f56..7adc3af9 100644 --- a/src/state.rs +++ b/src/state.rs @@ -103,29 +103,6 @@ pub struct Common { pub viewporter_state: ViewporterState, } -#[cfg(feature = "debug")] -pub struct Egui { - pub debug_state: smithay_egui::EguiState, - pub active: bool, - pub alpha: f32, -} - -#[cfg(feature = "debug")] -pub struct Fps { - pub state: smithay_egui::EguiState, - pub frames: VecDeque<(Instant, Duration)>, - pub start: Instant, -} - -#[cfg(feature = "debug")] -pub struct Frame { - start: Instant, - duration_elements: Duration, - duration_render: Duration, - duration_screencopy: Duration, - duration_displayed: Duration, -} - pub enum BackendData { X11(X11State), Winit(WinitState), @@ -455,48 +432,158 @@ impl Common { } #[cfg(feature = "debug")] -impl Fps { - const WINDOW_SIZE: usize = 1000; +pub struct Egui { + pub debug_state: smithay_egui::EguiState, + pub active: bool, + pub alpha: f32, +} - pub fn start(&mut self) { - self.start = Instant::now(); +#[cfg(feature = "debug")] +pub struct Fps { + pub state: smithay_egui::EguiState, + pending_frame: Option, + pub frames: VecDeque, +} + +#[cfg(feature = "debug")] +#[derive(Debug)] +struct PendingFrame { + start: Instant, + duration_elements: Option, + duration_render: Option, + duration_screencopy: Option, + duration_displayed: Option, +} + +#[cfg(feature = "debug")] +#[derive(Debug)] +pub struct Frame { + pub start: Instant, + pub duration_elements: Duration, + pub duration_render: Duration, + pub duration_screencopy: Option, + pub duration_displayed: Duration, +} + +impl Frame { + fn frame_time(&self) -> Duration { + self.duration_elements + + self.duration_render + + self.duration_screencopy.clone().unwrap_or(Duration::ZERO) } - pub fn end(&mut self) { - let frame_time = Instant::now().duration_since(self.start); + fn time_to_display(&self) -> Duration { + self.duration_elements + + self.duration_render + + self.duration_screencopy.clone().unwrap_or(Duration::ZERO) + + self.duration_displayed + } +} - self.frames.push_back((self.start, frame_time)); - if self.frames.len() > Fps::WINDOW_SIZE { - self.frames.pop_front(); +impl From for Frame { + fn from(pending: PendingFrame) -> Self { + Frame { + start: pending.start, + duration_elements: pending.duration_elements.unwrap_or(Duration::ZERO), + duration_render: pending.duration_render.unwrap_or(Duration::ZERO), + duration_screencopy: pending.duration_screencopy, + duration_displayed: pending.duration_displayed.unwrap_or(Duration::ZERO), + } + } +} + +#[cfg(feature = "debug")] +impl Fps { + const WINDOW_SIZE: usize = 360; + + pub fn start(&mut self) { + self.pending_frame = Some(PendingFrame { + start: Instant::now(), + duration_elements: None, + duration_render: None, + duration_screencopy: None, + duration_displayed: None, + }); + } + + pub fn elements(&mut self) { + if let Some(frame) = self.pending_frame.as_mut() { + frame.duration_elements = Some(Instant::now().duration_since(frame.start)); } } - pub fn max_frametime(&self) -> &Duration { - self.frames - .iter() - .map(|(_, f)| f) - .max() - .unwrap_or(&Duration::ZERO) + pub fn render(&mut self) { + if let Some(frame) = self.pending_frame.as_mut() { + frame.duration_render = Some( + Instant::now().duration_since(frame.start) + - frame.duration_elements.clone().unwrap_or(Duration::ZERO), + ); + } } - pub fn min_frametime(&self) -> &Duration { + pub fn screencopy(&mut self) { + if let Some(frame) = self.pending_frame.as_mut() { + frame.duration_screencopy = Some( + Instant::now().duration_since(frame.start) + - frame.duration_elements.clone().unwrap_or(Duration::ZERO) + - frame.duration_render.clone().unwrap_or(Duration::ZERO), + ); + } + } + + pub fn displayed(&mut self) { + if let Some(mut frame) = self.pending_frame.take() { + frame.duration_displayed = Some( + Instant::now().duration_since(frame.start) + - frame.duration_elements.clone().unwrap_or(Duration::ZERO) + - frame.duration_render.clone().unwrap_or(Duration::ZERO) + - frame.duration_screencopy.clone().unwrap_or(Duration::ZERO), + ); + + self.frames.push_back(frame.into()); + if self.frames.len() > Fps::WINDOW_SIZE { + self.frames.pop_front(); + } + } + } + + pub fn max_frametime(&self) -> Duration { self.frames .iter() - .map(|(_, f)| f) + .map(|f| f.frame_time()) + .max() + .unwrap_or(Duration::ZERO) + } + + pub fn min_frametime(&self) -> Duration { + self.frames + .iter() + .map(|f| f.frame_time()) .min() - .unwrap_or(&Duration::ZERO) + .unwrap_or(Duration::ZERO) + } + + pub fn max_time_to_display(&self) -> Duration { + self.frames + .iter() + .map(|f| f.time_to_display()) + .max() + .unwrap_or(Duration::ZERO) + } + + pub fn min_time_to_display(&self) -> Duration { + self.frames + .iter() + .map(|f| f.time_to_display()) + .min() + .unwrap_or(Duration::ZERO) } pub fn avg_frametime(&self) -> Duration { if self.frames.is_empty() { return Duration::ZERO; } - self.frames - .iter() - .map(|(_, f)| f) - .cloned() - .sum::() - / (self.frames.len() as u32) + self.frames.iter().map(|f| f.frame_time()).sum::() / (self.frames.len() as u32) } pub fn avg_fps(&self) -> f64 { @@ -504,7 +591,9 @@ impl Fps { return 0.0; } let secs = match (self.frames.front(), self.frames.back()) { - (Some((start, _)), Some((end, dur))) => end.duration_since(*start) + *dur, + (Some(Frame { start, .. }), Some(end_frame)) => { + end_frame.start.duration_since(*start) + end_frame.frame_time() + } _ => Duration::ZERO, } .as_secs_f64(); @@ -524,8 +613,8 @@ impl Default for Fps { state.context().set_visuals(visuals); state }, + pending_frame: None, frames: VecDeque::with_capacity(Fps::WINDOW_SIZE + 1), - start: Instant::now(), } } } From ec7fd17b8f9e160b5413dd3306458a60532a1919 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 18 Nov 2022 18:25:59 +0100 Subject: [PATCH 47/62] protocols: Drop export-dmabuf --- src/wayland/handlers/export_dmabuf.rs | 424 ------------------------- src/wayland/protocols/export_dmabuf.rs | 308 ------------------ 2 files changed, 732 deletions(-) delete mode 100644 src/wayland/handlers/export_dmabuf.rs delete mode 100644 src/wayland/protocols/export_dmabuf.rs diff --git a/src/wayland/handlers/export_dmabuf.rs b/src/wayland/handlers/export_dmabuf.rs deleted file mode 100644 index 81b3b77e..00000000 --- a/src/wayland/handlers/export_dmabuf.rs +++ /dev/null @@ -1,424 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -use anyhow::{anyhow, Context, Result}; - -use std::{cell::RefCell, time::Instant}; - -use smithay::{ - backend::{ - drm::{DrmNode, NodeType}, - egl::EGLDevice, - renderer::{ - gles2::{Gles2Error, Gles2Renderbuffer, Gles2Renderer}, - utils::with_renderer_surface_state, - Bind, ExportDma, ImportAll, Offscreen, Renderer, - }, - }, - desktop::{draw_window, draw_window_popups, space::RenderElement, Kind, Window}, - input::pointer::CursorImageStatus, - output::Output, - reexports::wayland_server::{protocol::wl_output::WlOutput, DisplayHandle, Resource}, - utils::{IsAlive, Size, Transform}, - wayland::{ - compositor::{get_children, with_states, SurfaceAttributes}, - dmabuf::get_dmabuf, - }, -}; - -use crate::{ - backend::render::{ - cursor::draw_cursor, render_output, render_workspace, AsGles2Renderer, CustomElem, - }, - state::{BackendData, ClientState, Common}, - utils::prelude::*, - wayland::protocols::{ - export_dmabuf::{delegate_export_dmabuf, Capture, CaptureError, ExportDmabufHandler}, - workspace::WorkspaceHandle, - }, -}; - -impl ExportDmabufHandler for State { - fn capture_output( - &mut self, - _dh: &DisplayHandle, - output: WlOutput, - overlay_cursor: bool, - ) -> Result { - let output = Output::from_resource(&output) - .ok_or(CaptureError::Permanent(anyhow!("Output is gone").into()))?; - - let renderer = match self.backend { - BackendData::Kms(ref mut kms) => { - // the kms backend just keeps its dmabufs easily accessible for capture. - return kms - .capture_output(&output) - .map(|(device, dmabuf, presentation_time)| Capture { - device, - dmabuf, - presentation_time, - }) - .ok_or(CaptureError::Temporary( - anyhow!("Surface not initialized yet").into(), - )); - } - BackendData::Winit(ref mut winit) => winit.backend.renderer(), - BackendData::X11(ref mut x11) => &mut x11.renderer, - _ => unreachable!(), - }; - let device = device_from_renderer(renderer) - .context("Failed to find DrmNode") - .map_err(|err| CaptureError::Permanent(err.into()))?; - - let size = output - .geometry() - .size - .to_f64() - .to_buffer( - output.current_scale().fractional_scale(), - output.current_transform().into(), - ) - .to_i32_round(); - let buffer = Offscreen::::create_buffer(renderer, size) - .context("Failed to create render buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - renderer - .bind(buffer) - .context("Failed to bind render buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - render_output( - None, - renderer, - 0, - &mut self.common, - &output, - !overlay_cursor, - #[cfg(feature = "debug")] - None, - ) - .context("Failed to render desktop for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - let dmabuf = renderer - .export_framebuffer(size) - .context("Failed to export buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - - Ok(Capture { - device, - dmabuf, - presentation_time: Instant::now(), - }) - } - - fn capture_workspace( - &mut self, - _dh: &DisplayHandle, - workspace: WorkspaceHandle, - wl_output: WlOutput, - overlay_cursor: bool, - ) -> Result { - let output = Output::from_resource(&wl_output) - .ok_or(CaptureError::Permanent(anyhow!("Output is gone").into()))?; - let workspace = self - .common - .shell - .spaces - .iter() - .find(|w| w.handle == workspace) - .ok_or(CaptureError::Permanent(anyhow!("Workspace is gone").into()))? - .idx; - if self.common.shell.active_space(&output).idx == workspace { - self.capture_output(_dh, wl_output, overlay_cursor) - } else { - match self.backend { - BackendData::Winit(ref mut winit) => { - let device = device_from_renderer(winit.backend.renderer()) - .context("Failed to find DrmNode") - .map_err(|err| CaptureError::Permanent(err.into()))?; - capture_workspace( - device, - winit.backend.renderer(), - &output, - workspace, - &mut self.common, - ) - } - BackendData::X11(ref mut x11) => { - let device = device_from_renderer(&x11.renderer) - .context("Failed to find DrmNode") - .map_err(|err| CaptureError::Permanent(err.into()))?; - capture_workspace( - device, - &mut x11.renderer, - &output, - workspace, - &mut self.common, - ) - } - BackendData::Kms(ref mut kms) => { - let node = kms - .target_node_for_output(&output) - .unwrap_or(kms.primary) - .node_with_type(NodeType::Render) - .with_context(|| "Unable to find node") - .map_err(|x| CaptureError::Permanent(x.into()))? - .map_err(|x| CaptureError::Permanent(x.into()))?; - let mut renderer = kms - .api - .renderer::(&node, &node) - .with_context(|| format!("Failed to optain renderer for {:?}", node)) - .map_err(|x| CaptureError::Permanent(x.into()))?; - capture_workspace(node, &mut renderer, &output, workspace, &mut self.common) - } - BackendData::Unset => unreachable!(), - } - } - } - - fn capture_toplevel( - &mut self, - dh: &DisplayHandle, - window: Window, - overlay_cursor: bool, - ) -> Result { - let Kind::Xdg(xdg) = window.toplevel(); - let surface = xdg.wl_surface(); - let window_transform = with_states(surface, |states| { - states - .cached_state - .current::() - .buffer_transform - .into() - }); - - let workspace = self.common.shell.space_for_window(surface); - let pointers = if overlay_cursor && workspace.is_some() { - self.common - .seats - .iter() - .filter_map(|seat| { - let cursor_status = seat - .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); - - if cursor_status != CursorImageStatus::Hidden { - let workspace = workspace.as_deref()?; - let loc = seat.get_pointer().map(|ptr| ptr.current_location())?; - let output = seat.active_output(); - - if self.common.shell.active_space(&output).idx == workspace.idx { - let relative = self - .common - .shell - .space_relative_output_geometry(loc, &output); - // unwrap is safe, because we got this workspace from `space_for_window`. It has to contain the window. - let bbox = workspace.space.window_bbox(&window).unwrap(); - bbox.contains(relative.to_i32_round()) - .then_some((seat, (relative - bbox.loc.to_f64()).to_i32_round())) - } else { - None - } - } else { - None - } - }) - .collect::>() - } else { - Vec::with_capacity(0) - }; - - let device = match self.backend { - BackendData::Winit(ref mut winit) => device_from_renderer(winit.backend.renderer()), - BackendData::X11(ref x11) => device_from_renderer(&x11.renderer), - BackendData::Kms(ref kms) => Ok(dh - .get_client(window.toplevel().wl_surface().id()) - .ok() - .with_context(|| "Unable to find matching wayland client") - .map_err(|x| CaptureError::Permanent(x.into()))? - .get_data::() - .unwrap() - .drm_node - .clone() - .unwrap_or_else(|| kms.primary.clone())), - _ => unreachable!(), - } - .context("Failed to find DrmNode") - .map_err(|err| CaptureError::Permanent(err.into()))?; - - // first lets check, if we can just send a dmabuf from the client directly - if pointers.is_empty() - && window_transform == Transform::Normal - && get_children(surface).is_empty() - && self.common.shell.popups.find_popup(surface).is_none() - { - let dmabuf = with_renderer_surface_state(surface, |state| { - state.wl_buffer().and_then(|buf| get_dmabuf(buf).ok()) - }); - if let Some(dmabuf) = dmabuf { - return Ok(Capture { - device, - dmabuf, - presentation_time: std::time::Instant::now(), - }); - } - } - - // we need to composite - let mut _tmp_multirenderer = None; - let renderer = match self.backend { - BackendData::Winit(ref mut winit) => winit.backend.renderer(), - BackendData::X11(ref mut x11) => &mut x11.renderer, - BackendData::Kms(ref mut kms) => { - _tmp_multirenderer = Some( - kms.api - .renderer::(&device, &device) - .with_context(|| format!("Failed to optain renderer for {:?}", device)) - .map_err(|x| CaptureError::Permanent(x.into()))?, - ); - _tmp_multirenderer.as_mut().unwrap().as_gles2() - } - BackendData::Unset => unreachable!(), - }; - - let bbox = window.bbox_with_popups(); - let size = bbox.size + Size::from((-bbox.loc.x, -bbox.loc.y)); - let buffer = Offscreen::::create_buffer( - renderer, - size.to_buffer(1, window_transform), - ) - .context("Failed to create render buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - renderer - .bind(buffer) - .context("Failed to bind render buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - renderer - .render(size.to_physical(1), Transform::Normal, |renderer, frame| { - let log = slog_scope::logger(); - let damage = &[window.physical_bbox_with_popups((0.0, 0.0), 1.0)]; - draw_window(renderer, frame, &window, 1.0, (0.0, 0.0), damage, &log)?; - draw_window_popups(renderer, frame, &window, 1.0, (0.0, 0.0), damage, &log)?; - for (seat, loc) in pointers.into_iter() { - if let Some(cursor_elem) = draw_cursor::<_, CustomElem>( - renderer, - seat, - loc, - &self.common.start_time, - true, - ) { - let damage = RenderElement::::accumulated_damage( - &cursor_elem, - 1.0, - None, - ); - cursor_elem.draw( - renderer, - frame, - 1.0, - loc.to_physical(1.0), - &damage, - &log, - )?; - } - } - Result::<(), Gles2Error>::Ok(()) - }) - .context("Failed to render window for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))? - .context("Failed to render window for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - - let dmabuf = renderer - .export_framebuffer(size.to_buffer(1, window_transform)) - .context("Failed to export buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - - Ok(Capture { - device, - dmabuf, - presentation_time: Instant::now(), - }) - } - - fn start_time(&mut self) -> Instant { - self.common.start_time - } -} - -fn capture_workspace( - gpu: DrmNode, - renderer: &mut R, - output: &Output, - idx: u8, - state: &mut Common, -) -> Result -where - E: std::error::Error + Send + Sync + 'static, - T: Clone + 'static, - R: Renderer - + ImportAll - + AsGles2Renderer - + Offscreen - + Bind - + ExportDma, - CustomElem: RenderElement, -{ - let size = output - .geometry() - .size - .to_f64() - .to_buffer( - output.current_scale().fractional_scale(), - output.current_transform().into(), - ) - .to_i32_round(); - let buffer = Offscreen::::create_buffer(renderer, size) - .context("Failed to create render buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - renderer - .bind(buffer) - .context("Failed to bind render buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - render_workspace( - Some(&gpu), - renderer, - 0, - state, - idx, - &output, - true, - #[cfg(feature = "debug")] - None, - ) - .map_err(|err| anyhow!("Failed to render desktop for offscreen capture: {:?}", err)) // meh.. - .map_err(|err| CaptureError::Temporary(err.into()))?; - let dmabuf = renderer - .export_framebuffer(size) - .context("Failed to export buffer for offscreen capture") - .map_err(|err| CaptureError::Temporary(err.into()))?; - - Ok(Capture { - device: gpu, - dmabuf, - presentation_time: Instant::now(), - }) -} - -fn device_from_renderer(renderer: &Gles2Renderer) -> Result { - EGLDevice::device_for_display(renderer.egl_context().display())? - .try_get_render_node()? - .ok_or(anyhow!( - "No node associated with context (software context?)" - )) -} - -delegate_export_dmabuf!(State); diff --git a/src/wayland/protocols/export_dmabuf.rs b/src/wayland/protocols/export_dmabuf.rs deleted file mode 100644 index 0288eebf..00000000 --- a/src/wayland/protocols/export_dmabuf.rs +++ /dev/null @@ -1,308 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -use smithay::{ - backend::{ - allocator::{dmabuf::Dmabuf, Buffer}, - drm::DrmNode, - }, - desktop::Window, - reexports::wayland_server::{ - self, backend::GlobalId, protocol::wl_output::WlOutput, Client, Dispatch, DisplayHandle, - GlobalDispatch, - }, -}; -use std::{ - fs::File, - io::{Seek, SeekFrom}, - os::unix::io::{AsRawFd, FromRawFd, IntoRawFd}, - time::Instant, -}; - -use cosmic_protocols::export_dmabuf::v1::server::{ - zcosmic_export_dmabuf_frame_v1::{self, CancelReason, Flags, ZcosmicExportDmabufFrameV1}, - zcosmic_export_dmabuf_manager_v1::{self, ZcosmicExportDmabufManagerV1}, -}; - -use crate::wayland::protocols::{ - toplevel_info::{window_from_handle, ToplevelInfoHandler}, - workspace::{WorkspaceHandle, WorkspaceHandler}, -}; - -/// Export Dmabuf global state -#[derive(Debug)] -pub struct ExportDmabufState { - global: GlobalId, -} - -pub struct ExportDmabufGlobalData { - filter: Box Fn(&'a Client) -> bool + Send + Sync>, -} - -impl ExportDmabufState { - /// Create a new dmabuf global - pub fn new(display: &DisplayHandle, client_filter: F) -> ExportDmabufState - where - D: GlobalDispatch - + Dispatch - + Dispatch - + ExportDmabufHandler - + 'static, - F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static, - { - ExportDmabufState { - global: display.create_global::( - 1, - ExportDmabufGlobalData { - filter: Box::new(client_filter), - }, - ), - } - } - - /// Returns the export dmabuf global. - pub fn global(&self) -> GlobalId { - self.global.clone() - } -} - -pub enum CaptureError { - Temporary(Box), - Permanent(Box), - Resizing, -} - -pub struct Capture { - pub device: DrmNode, - pub dmabuf: Dmabuf, - pub presentation_time: Instant, -} - -pub trait ExportDmabufHandler { - fn capture_output( - &mut self, - dh: &DisplayHandle, - output: WlOutput, - overlay_cursor: bool, - ) -> Result; - fn capture_workspace( - &mut self, - dh: &DisplayHandle, - workspace: WorkspaceHandle, - output: WlOutput, - overlay_cursor: bool, - ) -> Result; - fn capture_toplevel( - &mut self, - dh: &DisplayHandle, - toplevel: Window, - overlay_cursor: bool, - ) -> Result; - fn start_time(&mut self) -> Instant; -} - -impl GlobalDispatch - for ExportDmabufState -where - D: GlobalDispatch - + Dispatch - + Dispatch - + ExportDmabufHandler, -{ - fn bind( - _state: &mut D, - _handle: &DisplayHandle, - _client: &Client, - resource: wayland_server::New, - _global_data: &ExportDmabufGlobalData, - data_init: &mut wayland_server::DataInit<'_, D>, - ) { - data_init.init(resource, ()); - } - - fn can_view(client: Client, global_data: &ExportDmabufGlobalData) -> bool { - (global_data.filter)(&client) - } -} - -impl Dispatch for ExportDmabufState -where - D: GlobalDispatch - + Dispatch - + Dispatch - + ExportDmabufHandler - + WorkspaceHandler - + ToplevelInfoHandler, -{ - fn request( - state: &mut D, - _client: &wayland_server::Client, - _resource: &ZcosmicExportDmabufManagerV1, - request: ::Request, - _data: &(), - dhandle: &DisplayHandle, - data_init: &mut wayland_server::DataInit<'_, D>, - ) { - let start_time = state.start_time(); - match request { - zcosmic_export_dmabuf_manager_v1::Request::CaptureOutput { - frame, - overlay_cursor, - output, - } => { - let frame = data_init.init(frame, ()); - match state.capture_output(dhandle, output, overlay_cursor != 0) { - Ok(capture) => handle_capture(capture, frame, start_time), - Err(err) => frame.cancel(err.into()), - } - } - zcosmic_export_dmabuf_manager_v1::Request::CaptureWorkspace { - frame, - overlay_cursor, - workspace, - output, - } => { - let frame = data_init.init(frame, ()); - match state.workspace_state().workspace_handle(&workspace) { - Some(workspace) => { - match state.capture_workspace( - dhandle, - workspace, - output, - overlay_cursor != 0, - ) { - Ok(capture) => handle_capture(capture, frame, start_time), - Err(err) => frame.cancel(err.into()), - } - } - None => frame.cancel(CancelReason::Permanent), - } - } - zcosmic_export_dmabuf_manager_v1::Request::CaptureToplevel { - frame, - overlay_cursor, - toplevel, - } => { - let frame = data_init.init(frame, ()); - match window_from_handle(toplevel) { - Some(window) => { - match state.capture_toplevel(dhandle, window, overlay_cursor != 0) { - Ok(capture) => handle_capture(capture, frame, start_time), - Err(err) => frame.cancel(err.into()), - } - } - None => frame.cancel(CancelReason::Permanent), - } - } - zcosmic_export_dmabuf_manager_v1::Request::Destroy => {} - _ => {} - } - } -} - -impl From for CancelReason { - fn from(err: CaptureError) -> Self { - match err { - CaptureError::Temporary(err) => { - slog_scope::debug!("Temporary Capture Error: {}", err); - CancelReason::Temporary - } - CaptureError::Permanent(err) => { - slog_scope::warn!("Permanent Capture Error: {}", err); - CancelReason::Permanent - } - CaptureError::Resizing => CancelReason::Resizing, - } - } -} - -fn handle_capture(capture: Capture, frame: ZcosmicExportDmabufFrameV1, start_time: Instant) { - let Capture { - device, - dmabuf, - presentation_time, - } = capture; - let format = dmabuf.format(); - let modifier: u64 = format.modifier.into(); - - frame.device(Vec::from(device.dev_id().to_ne_bytes())); - frame.frame( - dmabuf.width(), - dmabuf.height(), - 0, - 0, - if dmabuf.y_inverted() { 1 } else { 0 }, - Flags::Transient, - format.code as u32, - (modifier >> 32) as u32, - (modifier & 0xFFFFFFFF) as u32, - dmabuf.num_planes() as u32, - ); - - for (i, (handle, (offset, stride))) in dmabuf - .handles() - .zip(dmabuf.offsets().zip(dmabuf.strides())) - .enumerate() - { - // SAFETY: BorrowedFd is used for seeking - let mut file = unsafe { File::from_raw_fd(handle.as_raw_fd()) }; - let size = match file.seek(SeekFrom::End(0)) { - Ok(size) => size, - Err(err) => { - slog_scope::debug!("Temporary Capture Error: {}", err); - frame.cancel(zcosmic_export_dmabuf_frame_v1::CancelReason::Temporary); - return; - } - }; - if let Err(err) = file.rewind() { - slog_scope::debug!("Temporary Capture Error: {}", err); - frame.cancel(zcosmic_export_dmabuf_frame_v1::CancelReason::Temporary); - return; - } - // SAFETY: Converted back to raw_fd, no chance in ownership - let handle = file.into_raw_fd(); - // FDs are dup'ed by wayland-rs before sending them - frame.object(i as u32, handle, size as u32, offset, stride, i as u32); - } - - let duration = presentation_time.duration_since(start_time); - let (tv_sec, tv_nsec) = (duration.as_secs(), duration.subsec_nanos()); - frame.ready((tv_sec >> 32) as u32, (tv_sec & 0xFFFFFFFF) as u32, tv_nsec); -} - -impl Dispatch for ExportDmabufState -where - D: GlobalDispatch - + Dispatch - + Dispatch - + ExportDmabufHandler, -{ - fn request( - _state: &mut D, - _client: &wayland_server::Client, - _resource: &ZcosmicExportDmabufFrameV1, - request: ::Request, - _data: &(), - _dhandle: &DisplayHandle, - _data_init: &mut wayland_server::DataInit<'_, D>, - ) { - match request { - zcosmic_export_dmabuf_frame_v1::Request::Destroy => {} - _ => {} - } - } -} - -macro_rules! delegate_export_dmabuf { - ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { - smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - cosmic_protocols::export_dmabuf::v1::server::zcosmic_export_dmabuf_manager_v1::ZcosmicExportDmabufManagerV1: $crate::wayland::protocols::export_dmabuf::ExportDmabufGlobalData - ] => $crate::wayland::protocols::export_dmabuf::ExportDmabufState); - smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - cosmic_protocols::export_dmabuf::v1::server::zcosmic_export_dmabuf_manager_v1::ZcosmicExportDmabufManagerV1: () - ] => $crate::wayland::protocols::export_dmabuf::ExportDmabufState); - smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - cosmic_protocols::export_dmabuf::v1::server::zcosmic_export_dmabuf_frame_v1::ZcosmicExportDmabufFrameV1: () - ] => $crate::wayland::protocols::export_dmabuf::ExportDmabufState); - }; -} -pub(crate) use delegate_export_dmabuf; From 541f53e418249ebb1727062b0e8c93b9db365d1b Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 18 Nov 2022 18:26:22 +0100 Subject: [PATCH 48/62] debug: Get rid of some left over println --- src/debug.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debug.rs b/src/debug.rs index 8e41261c..1915287c 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -42,7 +42,7 @@ pub fn fps_ui( fps.min_time_to_display().as_secs_f64(), ); - let amount = dbg!(avg_fps.round() as usize * 2); + let amount = avg_fps.round() as usize * 2; let ((bars_elements, bars_render), (bars_screencopy, bars_displayed)): ( (Vec, Vec), (Vec, Vec), From cd5c1854409924bb7d771ad8d751417646b5c95d Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 21 Nov 2022 10:09:32 +0100 Subject: [PATCH 49/62] input: Fix layer-shell popups --- src/shell/focus/target.rs | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/src/shell/focus/target.rs b/src/shell/focus/target.rs index 6781bf08..ef337347 100644 --- a/src/shell/focus/target.rs +++ b/src/shell/focus/target.rs @@ -88,9 +88,7 @@ impl PointerTarget for PointerFocusTarget { match self { PointerFocusTarget::Element(w) => PointerTarget::enter(w, seat, data, event), PointerFocusTarget::Fullscreen(w) => PointerTarget::enter(w, seat, data, event), - PointerFocusTarget::LayerSurface(l) => { - PointerTarget::enter(l.wl_surface(), seat, data, event) - } + PointerFocusTarget::LayerSurface(l) => PointerTarget::enter(l, seat, data, event), PointerFocusTarget::Popup(p) => PointerTarget::enter(p.wl_surface(), seat, data, event), } } @@ -98,9 +96,7 @@ impl PointerTarget for PointerFocusTarget { match self { PointerFocusTarget::Element(w) => PointerTarget::motion(w, seat, data, event), PointerFocusTarget::Fullscreen(w) => PointerTarget::motion(w, seat, data, event), - PointerFocusTarget::LayerSurface(l) => { - PointerTarget::motion(l.wl_surface(), seat, data, event) - } + PointerFocusTarget::LayerSurface(l) => PointerTarget::motion(l, seat, data, event), PointerFocusTarget::Popup(p) => { PointerTarget::motion(p.wl_surface(), seat, data, event) } @@ -110,9 +106,7 @@ impl PointerTarget for PointerFocusTarget { match self { PointerFocusTarget::Element(w) => PointerTarget::button(w, seat, data, event), PointerFocusTarget::Fullscreen(w) => PointerTarget::button(w, seat, data, event), - PointerFocusTarget::LayerSurface(l) => { - PointerTarget::button(l.wl_surface(), seat, data, event) - } + PointerFocusTarget::LayerSurface(l) => PointerTarget::button(l, seat, data, event), PointerFocusTarget::Popup(p) => { PointerTarget::button(p.wl_surface(), seat, data, event) } @@ -122,9 +116,7 @@ impl PointerTarget for PointerFocusTarget { match self { PointerFocusTarget::Element(w) => PointerTarget::axis(w, seat, data, frame), PointerFocusTarget::Fullscreen(w) => PointerTarget::axis(w, seat, data, frame), - PointerFocusTarget::LayerSurface(l) => { - PointerTarget::axis(l.wl_surface(), seat, data, frame) - } + PointerFocusTarget::LayerSurface(l) => PointerTarget::axis(l, seat, data, frame), PointerFocusTarget::Popup(p) => PointerTarget::axis(p.wl_surface(), seat, data, frame), } } @@ -133,7 +125,7 @@ impl PointerTarget for PointerFocusTarget { PointerFocusTarget::Element(w) => PointerTarget::leave(w, seat, data, serial, time), PointerFocusTarget::Fullscreen(w) => PointerTarget::leave(w, seat, data, serial, time), PointerFocusTarget::LayerSurface(l) => { - PointerTarget::leave(l.wl_surface(), seat, data, serial, time) + PointerTarget::leave(l, seat, data, serial, time) } PointerFocusTarget::Popup(p) => { PointerTarget::leave(p.wl_surface(), seat, data, serial, time) @@ -157,7 +149,7 @@ impl KeyboardTarget for KeyboardFocusTarget { } KeyboardFocusTarget::Group(_) => {} KeyboardFocusTarget::LayerSurface(l) => { - KeyboardTarget::enter(l.wl_surface(), seat, data, keys, serial) + KeyboardTarget::enter(l, seat, data, keys, serial) } KeyboardFocusTarget::Popup(p) => { KeyboardTarget::enter(p.wl_surface(), seat, data, keys, serial) @@ -169,9 +161,7 @@ impl KeyboardTarget for KeyboardFocusTarget { KeyboardFocusTarget::Element(w) => KeyboardTarget::leave(w, seat, data, serial), KeyboardFocusTarget::Fullscreen(w) => KeyboardTarget::leave(w, seat, data, serial), KeyboardFocusTarget::Group(_) => {} - KeyboardFocusTarget::LayerSurface(l) => { - KeyboardTarget::leave(l.wl_surface(), seat, data, serial) - } + KeyboardFocusTarget::LayerSurface(l) => KeyboardTarget::leave(l, seat, data, serial), KeyboardFocusTarget::Popup(p) => { KeyboardTarget::leave(p.wl_surface(), seat, data, serial) } @@ -195,7 +185,7 @@ impl KeyboardTarget for KeyboardFocusTarget { } KeyboardFocusTarget::Group(_) => {} KeyboardFocusTarget::LayerSurface(l) => { - KeyboardTarget::key(l.wl_surface(), seat, data, key, state, serial, time) + KeyboardTarget::key(l, seat, data, key, state, serial, time) } KeyboardFocusTarget::Popup(p) => { KeyboardTarget::key(p.wl_surface(), seat, data, key, state, serial, time) @@ -218,7 +208,7 @@ impl KeyboardTarget for KeyboardFocusTarget { } KeyboardFocusTarget::Group(_) => {} KeyboardFocusTarget::LayerSurface(l) => { - KeyboardTarget::modifiers(l.wl_surface(), seat, data, modifiers, serial) + KeyboardTarget::modifiers(l, seat, data, modifiers, serial) } KeyboardFocusTarget::Popup(p) => { KeyboardTarget::modifiers(p.wl_surface(), seat, data, modifiers, serial) From 1de4b97bca540ea6468f006e05adb02e4c2077ef Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 21 Nov 2022 10:10:50 +0100 Subject: [PATCH 50/62] debug: Add image support and gpu vendor logos --- Cargo.lock | 390 ++++++++++++++++++++++++++++++++-- Cargo.toml | 4 +- resources/icons/amd.svg | 1 + resources/icons/intel.svg | 12 ++ resources/icons/nvidia.svg | 1 + src/backend/kms/mod.rs | 104 +++++---- src/backend/render/element.rs | 1 + src/backend/winit.rs | 5 +- src/backend/x11.rs | 2 +- src/debug.rs | 50 ++++- src/state.rs | 42 +++- 11 files changed, 541 insertions(+), 71 deletions(-) create mode 100644 resources/icons/amd.svg create mode 100644 resources/icons/intel.svg create mode 100644 resources/icons/nvidia.svg diff --git a/Cargo.lock b/Cargo.lock index 0e65aefa..a524fa81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,6 +84,24 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "983cd8b9d4b02a6dc6ffa557262eb5858a27a0038ffffe21a0f133eaa819a164" +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + [[package]] name = "atomic_refcell" version = "0.1.8" @@ -176,13 +194,19 @@ dependencies = [ ] [[package]] -name = "calloop" -version = "0.10.2" +name = "byteorder" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eb0438b3c6d262395fe30e6de9a61beb57ea56290b00a07f227fe6e20cbf2" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "calloop" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bcf530afb40e45e14440701e5e996d7fd139e84a912a4d83a8d6a0fb3e58663" dependencies = [ "log", - "nix 0.24.2", + "nix 0.25.0", "slotmap", "thiserror", "vec_map", @@ -241,6 +265,12 @@ dependencies = [ "objc", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "core-foundation" version = "0.9.3" @@ -418,10 +448,19 @@ dependencies = [ ] [[package]] -name = "digest" -version = "0.10.5" +name = "data-url" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "3a30bfce702bcfa94e906ef82421f2c0e61c076ad76030c16ee5d2e9a32fe193" +dependencies = [ + "matches", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", "crypto-common", @@ -545,6 +584,18 @@ dependencies = [ "nohash-hasher", ] +[[package]] +name = "egui_extras" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f698f685bb0ad39e87109e2f695ded0bccde77d5d40bbf7590cb5561c1e3039d" +dependencies = [ + "egui", + "resvg", + "tiny-skia", + "usvg", +] + [[package]] name = "egui_glow" version = "0.19.0" @@ -624,12 +675,39 @@ dependencies = [ "miniz_oxide 0.5.4", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fontconfig-parser" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2be17a530a842f8a7a60f4397a08e8f08872849a5e31b20c7bd7301dac483296" +dependencies = [ + "roxmltree 0.15.1", +] + +[[package]] +name = "fontdb" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52186a39c335aa6f79fc0bf1c3cf854870b6ad4e50a7bb8a59b4ba1331f478a" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "ttf-parser 0.17.1", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -698,6 +776,16 @@ dependencies = [ "wasi", ] +[[package]] +name = "gif" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gimli" version = "0.26.2" @@ -767,10 +855,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] -name = "indexmap" -version = "1.9.1" +name = "image" +version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-rational", + "num-traits", +] + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", @@ -831,6 +932,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jpeg-decoder" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9478aa10f73e7528198d75109c8be5cd7d15fb530238040148d5f9a22d4c5b3b" + [[package]] name = "js-sys" version = "0.3.60" @@ -846,6 +953,15 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "kurbo" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a53776d271cfb873b17c618af0298445c88afc52837f3e948fa3fafd131f449" +dependencies = [ + "arrayvec 0.7.2", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -946,6 +1062,12 @@ dependencies = [ "libc", ] +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + [[package]] name = "memchr" version = "2.5.0" @@ -1118,6 +1240,27 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -1187,7 +1330,7 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18904d3c65493a9f0d7542293d1a7f69bfdc309a6b9ef4f46dc3e58b0577edc5" dependencies = [ - "ttf-parser", + "ttf-parser 0.17.1", ] [[package]] @@ -1219,6 +1362,12 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -1335,6 +1484,12 @@ dependencies = [ "cty", ] +[[package]] +name = "rctree" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae028b272a6e99d9f8260ceefa3caa09300a8d6c8d2b2001316474bc52122e9" + [[package]] name = "redox_syscall" version = "0.2.16" @@ -1381,6 +1536,33 @@ dependencies = [ "winapi", ] +[[package]] +name = "resvg" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34489194784b86c03c3d688258e2ba73f3c82700ba4673ee2ecad5ae540b9438" +dependencies = [ + "gif", + "jpeg-decoder", + "log", + "pico-args", + "png", + "rgb", + "svgfilters", + "svgtypes", + "tiny-skia", + "usvg", +] + +[[package]] +name = "rgb" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3603b7d71ca82644f79b5a06d1220e9a58ede60bd32255f698cb1af8838b8db3" +dependencies = [ + "bytemuck", +] + [[package]] name = "ron" version = "0.7.1" @@ -1392,6 +1574,24 @@ dependencies = [ "serde", ] +[[package]] +name = "roxmltree" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "roxmltree" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9de9831a129b122e7e61f242db509fa9d0838008bf0b29bb0624669edfe48a" +dependencies = [ + "xmlparser", +] + [[package]] name = "rustc-demangle" version = "0.1.21" @@ -1404,12 +1604,37 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" +[[package]] +name = "rustybuzz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a617c811f5c9a7060fe511d35d13bf5b9f0463ce36d63ce666d05779df2b4eba" +dependencies = [ + "bitflags", + "bytemuck", + "smallvec", + "ttf-parser 0.15.2", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-general-category", + "unicode-script", +] + [[package]] name = "ryu" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +[[package]] +name = "safe_arch" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ff3d6d9696af502cc3110dacce942840fb06ff4514cad92236ecc455f2ce05" +dependencies = [ + "bytemuck", +] + [[package]] name = "scan_fmt" version = "0.2.6" @@ -1459,9 +1684,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "8e8b3801309262e8184d9687fb697586833e939767aea0dda89f5a8e650e8bd7" dependencies = [ "itoa", "ryu", @@ -1479,6 +1704,21 @@ dependencies = [ "digest", ] +[[package]] +name = "simplecss" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "slog" version = "2.7.0" @@ -1615,11 +1855,13 @@ dependencies = [ [[package]] name = "smithay-egui" version = "0.1.0" -source = "git+https://github.com/Smithay/smithay-egui.git?rev=7334d0c53#7334d0c533ad307b3359cd4931bfb1ad4c34b178" +source = "git+https://github.com/Smithay/smithay-egui.git?branch=feature/image#9fcb589d5c99ac8143f0a967504cdbf691d6cea4" dependencies = [ "cgmath", "egui", + "egui_extras", "egui_glow", + "image", "memoffset", "smithay", "xkbcommon 0.4.1", @@ -1643,6 +1885,25 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +[[package]] +name = "svgfilters" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "639abcebc15fdc2df179f37d6f5463d660c1c79cd552c12343a4600827a04bce" +dependencies = [ + "float-cmp", + "rgb", +] + +[[package]] +name = "svgtypes" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22975e8a2bac6a76bb54f898a6b18764633b00e780330f0b689f65afb3975564" +dependencies = [ + "siphasher", +] + [[package]] name = "syn" version = "1.0.103" @@ -1743,6 +2004,20 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-skia" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d049bfef0eaa2521e75d9ffb5ce86ad54480932ae19b85f78bec6f52c4d30d78" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "bytemuck", + "cfg-if", + "png", + "safe_arch", +] + [[package]] name = "toml" version = "0.5.9" @@ -1772,6 +2047,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "ttf-parser" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" + [[package]] name = "ttf-parser" version = "0.17.1" @@ -1795,12 +2076,75 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" + +[[package]] +name = "unicode-ccc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" + +[[package]] +name = "unicode-general-category" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07547e3ee45e28326cc23faac56d44f58f16ab23e413db526debce3b0bfd2742" + [[package]] name = "unicode-ident" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" +[[package]] +name = "unicode-script" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d817255e1bed6dfd4ca47258685d14d2bdcfbc64fdc9e3819bd5848057b8ecc" + +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + +[[package]] +name = "usvg" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a82565b5c96dcbb58c9bdbb6aa3642abd395a6a6b480658532c6f74c3c4b7a" +dependencies = [ + "base64", + "data-url", + "flate2", + "float-cmp", + "fontdb", + "kurbo", + "log", + "pico-args", + "rctree", + "roxmltree 0.14.1", + "rustybuzz", + "simplecss", + "siphasher", + "svgtypes", + "ttf-parser 0.15.2", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + [[package]] name = "uuid" version = "0.8.2" @@ -2068,6 +2412,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + [[package]] name = "winapi" version = "0.3.9" @@ -2306,3 +2656,15 @@ name = "xml-rs" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] +name = "xmlparser" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd" + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" diff --git a/Cargo.toml b/Cargo.toml index 954ec444..064b52e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,9 @@ features = ["backend_drm", "backend_gbm", "backend_egl", "backend_libinput", "ba [dependencies.smithay-egui] git = "https://github.com/Smithay/smithay-egui.git" -rev = "7334d0c53" +#rev = "ee25d401ac" +branch = "feature/image" +features = ["svg"] optional = true [features] diff --git a/resources/icons/amd.svg b/resources/icons/amd.svg new file mode 100644 index 00000000..59275eda --- /dev/null +++ b/resources/icons/amd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/icons/intel.svg b/resources/icons/intel.svg new file mode 100644 index 00000000..b7253316 --- /dev/null +++ b/resources/icons/intel.svg @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/resources/icons/nvidia.svg b/resources/icons/nvidia.svg new file mode 100644 index 00000000..2b25de27 --- /dev/null +++ b/resources/icons/nvidia.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index d8eb8856..4eedaf78 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -504,22 +504,32 @@ impl State { let outputs = device.enumerate_surfaces()?.added; // There are no removed outputs on newly added devices let mut wl_outputs = Vec::new(); let mut w = self.common.shell.global_space().size.w; - for (crtc, conn) in outputs { - match device.setup_surface(crtc, conn, (w, 0)) { - Ok(output) => { - w += output - .user_data() - .get::>() - .unwrap() - .borrow() - .mode_size() - .w; - wl_outputs.push(output); - } - Err(err) => slog_scope::warn!("Failed to initialize output: {}", err), - }; + { + let backend = self.backend.kms(); + for (crtc, conn) in outputs { + let mut renderer = match backend.api.renderer(&render_node, &render_node) { + Ok(renderer) => renderer, + Err(err) => { + slog_scope::warn!("Failed to initialize output: {}", err); + continue; + } + }; + match device.setup_surface(crtc, conn, (w, 0), &mut renderer) { + Ok(output) => { + w += output + .user_data() + .get::>() + .unwrap() + .borrow() + .mode_size() + .w; + wl_outputs.push(output); + } + Err(err) => slog_scope::warn!("Failed to initialize output: {}", err), + }; + } + backend.devices.insert(drm_node, device); } - self.backend.kms().devices.insert(drm_node, device); self.common .output_configuration_state @@ -542,32 +552,45 @@ impl State { let drm_node = DrmNode::from_dev_id(dev)?; let mut outputs_removed = Vec::new(); let mut outputs_added = Vec::new(); - if let Some(device) = self.backend.kms().devices.get_mut(&drm_node) { - let changes = device.enumerate_surfaces()?; - let mut w = self.common.shell.global_space().size.w; - for crtc in changes.removed { - if let Some(surface) = device.surfaces.remove(&crtc) { - if let Some(token) = surface.render_timer_token { - self.common.event_loop_handle.remove(token); + { + let backend = self.backend.kms(); + if let Some(device) = backend.devices.get_mut(&drm_node) { + let changes = device.enumerate_surfaces()?; + let mut w = self.common.shell.global_space().size.w; + for crtc in changes.removed { + if let Some(surface) = device.surfaces.remove(&crtc) { + if let Some(token) = surface.render_timer_token { + self.common.event_loop_handle.remove(token); + } + w -= surface.output.current_mode().map(|m| m.size.w).unwrap_or(0); + outputs_removed.push(surface.output.clone()); } - w -= surface.output.current_mode().map(|m| m.size.w).unwrap_or(0); - outputs_removed.push(surface.output.clone()); } - } - for (crtc, conn) in changes.added { - match device.setup_surface(crtc, conn, (w, 0)) { - Ok(output) => { - w += output - .user_data() - .get::>() - .unwrap() - .borrow() - .mode_size() - .w; - outputs_added.push(output); - } - Err(err) => slog_scope::warn!("Failed to initialize output: {}", err), - }; + for (crtc, conn) in changes.added { + let mut renderer = match backend + .api + .renderer(&device.render_node, &device.render_node) + { + Ok(renderer) => renderer, + Err(err) => { + slog_scope::warn!("Failed to initialize output: {}", err); + continue; + } + }; + match device.setup_surface(crtc, conn, (w, 0), &mut renderer) { + Ok(output) => { + w += output + .user_data() + .get::>() + .unwrap() + .borrow() + .mode_size() + .w; + outputs_added.push(output); + } + Err(err) => slog_scope::warn!("Failed to initialize output: {}", err), + }; + } } } @@ -671,6 +694,7 @@ impl Device { crtc: crtc::Handle, conn: connector::Handle, position: (i32, i32), + renderer: &mut GlMultiRenderer<'_>, ) -> Result { let drm = &mut *self.drm.as_source_mut(); let crtc_info = drm.get_crtc(crtc)?; @@ -745,7 +769,7 @@ impl Device { dirty: false, render_timer_token: None, #[cfg(feature = "debug")] - fps: Fps::default(), + fps: Fps::new(renderer.as_mut()), }; self.surfaces.insert(crtc, data); diff --git a/src/backend/render/element.rs b/src/backend/render/element.rs index 5513ec40..f0372606 100644 --- a/src/backend/render/element.rs +++ b/src/backend/render/element.rs @@ -237,6 +237,7 @@ where } } +#[cfg(feature = "debug")] impl From> for CosmicElement where R: Renderer + ImportAll + AsGlowRenderer, diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 174dffb2..e70c5404 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -221,13 +221,16 @@ pub fn init_backend( .map_err(|_| anyhow::anyhow!("Failed to init eventloop timer for winit"))?; event_ping.ping(); + #[cfg(feature = "debug")] + let fps = Fps::new(backend.renderer()); + state.backend = BackendData::Winit(WinitState { backend, output: output.clone(), damage_tracker: DamageTrackedRenderer::from_output(&output), screencopy: Vec::new(), #[cfg(feature = "debug")] - fps: Fps::default(), + fps, }); state .common diff --git a/src/backend/x11.rs b/src/backend/x11.rs index da5eb1fc..1b8b148d 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -126,7 +126,7 @@ impl X11State { pending: true, screencopy: Vec::new(), #[cfg(feature = "debug")] - fps: Fps::default(), + fps: Fps::new(&mut self.renderer), }); // schedule first render diff --git a/src/debug.rs b/src/debug.rs index 1915287c..012e9201 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -1,7 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only +use std::collections::HashMap; + use crate::state::{Common, Fps}; -use egui::Color32; +use egui::{Color32, Vec2}; use smithay::{ backend::{ drm::DrmNode, @@ -29,7 +31,7 @@ pub fn fps_ui( area: Rectangle, scale: f64, ) -> Result, Gles2Error> { - use egui::widgets::plot::{Bar, BarChart, HLine, Legend, Plot}; + use egui::widgets::plot::{Bar, BarChart, Legend, Plot}; let (max, min, avg, avg_fps) = ( fps.max_frametime().as_secs_f64(), @@ -84,6 +86,33 @@ pub fn fps_ui( }) .unzip(); + let vendors = HashMap::from([ + ( + "0x10de", + fps.state + .with_image(renderer, "nvidia", |image, ctx| { + (image.texture_id(ctx), image.size_vec2()) + }) + .expect("Logo images not loaded?"), + ), + ( + "0x1002", + fps.state + .with_image(renderer, "amd", |image, ctx| { + (image.texture_id(ctx), image.size_vec2()) + }) + .expect("Logo images not loaded?"), + ), + ( + "0x8086", + fps.state + .with_image(renderer, "intel", |image, ctx| { + (image.texture_id(ctx), image.size_vec2()) + }) + .expect("Logo images not loaded?"), + ), + ]); + fps.state.render( |ctx| { egui::Area::new("main") @@ -104,7 +133,22 @@ pub fn fps_ui( ui.separator(); if let Some(gpu) = gpu { - ui.label(egui::RichText::new(format!("renderD{}", gpu.minor())).code()); + ui.horizontal(|ui| { + let resp = ui.label( + egui::RichText::new(format!("renderD{}", gpu.minor())).code(), + ); + if let Ok(vendor) = std::fs::read_to_string(format!( + "/sys/class/drm/renderD{}/device/vendor", + gpu.minor() + )) { + if let Some((texture_id, mut size)) = vendors.get(vendor.trim()) + { + let factor = resp.rect.height() / size.y; + size = Vec2::from([size.x * factor, resp.rect.height()]); + ui.image(*texture_id, size); + } + } + }); } ui.label(egui::RichText::new(format!("FPS: {:>7.3}", avg_fps)).heading()); ui.label("Frame Times:"); diff --git a/src/state.rs b/src/state.rs index 7adc3af9..31827868 100644 --- a/src/state.rs +++ b/src/state.rs @@ -14,6 +14,8 @@ use crate::{ }, }; use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_manager_v1::CursorMode; +#[cfg(feature = "debug")] +use smithay::backend::renderer::glow::GlowRenderer; use smithay::{ backend::{ drm::DrmNode, @@ -465,6 +467,7 @@ pub struct Frame { pub duration_displayed: Duration, } +#[cfg(feature = "debug")] impl Frame { fn frame_time(&self) -> Duration { self.duration_elements @@ -480,6 +483,7 @@ impl Frame { } } +#[cfg(feature = "debug")] impl From for Frame { fn from(pending: PendingFrame) -> Self { Frame { @@ -541,7 +545,7 @@ impl Fps { ); self.frames.push_back(frame.into()); - if self.frames.len() > Fps::WINDOW_SIZE { + while self.frames.len() > Fps::WINDOW_SIZE { self.frames.pop_front(); } } @@ -601,18 +605,34 @@ impl Fps { } } +static INTEL_LOGO: &'static [u8] = include_bytes!("../resources/icons/intel.svg"); +static AMD_LOGO: &'static [u8] = include_bytes!("../resources/icons/amd.svg"); +static NVIDIA_LOGO: &'static [u8] = include_bytes!("../resources/icons/nvidia.svg"); + #[cfg(feature = "debug")] -impl Default for Fps { - fn default() -> Fps { +impl Fps { + pub fn new(renderer: &mut GlowRenderer) -> Fps { + let state = { + let state = + smithay_egui::EguiState::new(Rectangle::from_loc_and_size((0, 0), (400, 800))); + let mut visuals: egui::style::Visuals = Default::default(); + visuals.window_shadow.extrusion = 0.0; + state.context().set_visuals(visuals); + state + }; + + state + .load_svg(renderer, String::from("intel"), INTEL_LOGO) + .unwrap(); + state + .load_svg(renderer, String::from("amd"), AMD_LOGO) + .unwrap(); + state + .load_svg(renderer, String::from("nvidia"), NVIDIA_LOGO) + .unwrap(); + Fps { - state: { - let state = - smithay_egui::EguiState::new(Rectangle::from_loc_and_size((0, 0), (400, 800))); - let mut visuals: egui::style::Visuals = Default::default(); - visuals.window_shadow.extrusion = 0.0; - state.context().set_visuals(visuals); - state - }, + state, pending_frame: None, frames: VecDeque::with_capacity(Fps::WINDOW_SIZE + 1), } From 9c41c8034586e9ff52b0bc4638c9f866885b8cbb Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 22 Nov 2022 09:57:29 +0100 Subject: [PATCH 51/62] debug: Remove old debug overlay --- src/debug.rs | 209 +---------------------------------------------- src/input/mod.rs | 76 ----------------- src/state.rs | 11 +-- 3 files changed, 4 insertions(+), 292 deletions(-) diff --git a/src/debug.rs b/src/debug.rs index 012e9201..6d90d669 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -13,9 +13,7 @@ use smithay::{ glow::GlowRenderer, }, }, - desktop::layer_map_for_output, - reexports::wayland_server::Resource, - utils::{IsAlive, Logical, Rectangle}, + utils::{Logical, Rectangle}, }; pub const ELEMENTS_COLOR: Color32 = Color32::from_rgb(70, 198, 115); @@ -123,11 +121,11 @@ pub fn fps_ui( std::env!("CARGO_PKG_VERSION") )); if let Some(hash) = std::option_env!("GIT_HASH").and_then(|x| x.get(0..10)) { - ui.label(format!(" :{hash}")); + ui.label(format!(": {hash}")); } if !state.egui.active { - ui.label("Press Super+Escape for debug menu"); + ui.label("Press Super+Escape for debug overlay"); } else { ui.set_max_width(300.0); ui.separator(); @@ -190,204 +188,3 @@ pub fn fps_ui( state.clock.now().into(), ) } - -/* -pub fn debug_ui( - state: &mut Common, - area: Rectangle, - scale: f64, -) -> Option { - if !state.egui.active { - return None; - } - - Some(state.egui.debug_state.run( - |ctx| { - use crate::utils::prelude::*; - - egui::Window::new("Workspaces") - .default_pos([0.0, 300.0]) - .vscroll(true) - .collapsible(true) - .show(ctx, |ui| { - use crate::{ - config::WorkspaceMode as ConfigMode, - shell::{OutputBoundState, WorkspaceMode, MAX_WORKSPACES}, - }; - - ui.set_min_width(250.0); - - // Mode - - ui.label(egui::RichText::new("Mode").heading()); - let mut mode = match &state.shell.workspace_mode { - WorkspaceMode::Global { .. } => ConfigMode::Global, - WorkspaceMode::OutputBound => ConfigMode::OutputBound, - }; - ui.radio_value(&mut mode, ConfigMode::OutputBound, "Output bound"); - ui.radio_value(&mut mode, ConfigMode::Global, "Global"); - state.shell.set_mode(mode); - - let mode = match &state.shell.workspace_mode { - WorkspaceMode::OutputBound => (ConfigMode::OutputBound, None), - WorkspaceMode::Global { ref active, .. } => { - (ConfigMode::Global, Some(*active)) - } - }; - match mode { - (ConfigMode::OutputBound, _) => { - ui.label("Workspaces:"); - for output in state.shell.outputs().cloned().collect::>() { - ui.horizontal(|ui| { - let active = output - .user_data() - .get::() - .unwrap() - .active - .get(); - let mut active_val = active as f64; - ui.label(output.name()); - ui.add( - egui::DragValue::new(&mut active_val) - .clamp_range(0..=(MAX_WORKSPACES - 1)) - .speed(1.0), - ); - if active != active_val as usize { - state.shell.activate( - &state.seats[0], - &output, - active_val as usize, - ); - } - }); - } - } - (ConfigMode::Global, Some(active)) => { - ui.horizontal(|ui| { - let mut active_val = active as f64; - ui.label("Workspace:"); - ui.add( - egui::DragValue::new(&mut active_val) - .clamp_range(0..=(MAX_WORKSPACES - 1)) - .speed(1.0), - ); - if active != active_val as usize { - let output = state.shell.outputs().next().cloned().unwrap(); - state.shell.activate( - &state.seats[0], - &output, - active_val as usize, - ); - } - }); - } - _ => unreachable!(), - } - - // Spaces - for (i, workspace) in state.shell.spaces.iter().enumerate() { - ui.collapsing(format!("Space: {}", i), |ui| { - ui.collapsing(format!("Windows"), |ui| { - for window in workspace.space.windows() { - ui.collapsing(format!("{:?}", window.toplevel()), |ui| { - ui.label(format!("Rect: {:?}", { - let mut geo = window.geometry(); - geo.loc += workspace - .space - .window_location(window) - .unwrap_or((0, 0).into()); - geo - })); - ui.label(format!( - "Bounding box: {:?}", - workspace.space.window_bbox(window) - )); - }); - } - }) - }); - } - }); - - egui::Window::new("Outputs") - .collapsible(true) - .hscroll(true) - .default_pos([300.0, 300.0]) - .show(ctx, |ui| { - ui.label(format!("Global Space: {:?}", state.shell.global_space())); - for output in state - .shell - .outputs() - .cloned() - .collect::>() - .into_iter() - { - ui.separator(); - ui.collapsing(output.name(), |ui| { - ui.label(format!("Mode: {:#?}", output.current_mode())); - ui.label(format!("Scale: {:#?}", output.current_scale())); - ui.label(format!("Transform: {:#?}", output.current_transform())); - ui.label(format!("Geometry: {:?}", output.geometry())); - ui.label(format!( - "Local Geometry: {:?}", - state - .shell - .active_space(&output) - .space - .output_geometry(&output) - )); - ui.label(format!( - "Relative Geometry: {:?}", - state - .shell - .space_relative_output_geometry((0i32, 0i32), &output) - )); - ui.separator(); - ui.collapsing("Layers:", |ui| { - let map = layer_map_for_output(&output); - for layer in map.layers() { - ui.collapsing( - format!( - "{}/{:?}", - layer.wl_surface().id(), - layer.wl_surface().client_id() - ), - |ui| { - ui.label(format!( - "Alive: {:?} {:?} {:?}", - layer.alive(), - layer.layer_surface().alive(), - layer.wl_surface().alive() - )); - ui.label(format!("Layer: {:?}", layer.layer())); - ui.label(format!("Namespace: {:?}", layer.namespace())); - ui.label(format!("Geometry: {:?}", layer.bbox())); - ui.label(format!( - "Anchor: {:?}", - layer.cached_state().anchor - )); - ui.label(format!( - "Margin: {:?}", - layer.cached_state().margin - )); - ui.label(format!( - "Exclusive: {:?}", - layer.cached_state().exclusive_zone - )); - }, - ); - } - ui.label(format!("{:?}", map)); - }); - }); - } - }); - }, - area, - scale, - state.egui.alpha, - &state.start_time, - state.egui.modifiers.clone(), - )) -} -*/ diff --git a/src/input/mod.rs b/src/input/mod.rs index 03f58d64..803f5ef4 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -163,10 +163,6 @@ impl State { _ => {} } } - #[cfg(feature = "debug")] - { - self.common.egui.debug_state.handle_device_added(&device); - } } InputEvent::DeviceRemoved { device } => { for seat in &mut self.common.seats() { @@ -182,10 +178,6 @@ impl State { break; } } - #[cfg(feature = "debug")] - { - self.common.egui.debug_state.handle_device_removed(&device); - } } InputEvent::Keyboard { event, .. } => { use smithay::backend::input::KeyboardKeyEvent; @@ -230,25 +222,6 @@ impl State { { return FilterResult::Intercept(None); } - #[cfg(feature = "debug")] - { - if data.common.seats().position(|x| x == seat).unwrap() == 0 - && data.common.egui.active - { - if data.common.egui.debug_state.wants_keyboard() { - data.common.egui.debug_state.handle_keyboard( - &handle, - state == KeyState::Pressed, - modifiers.clone(), - ); - userdata - .get::() - .unwrap() - .add(&handle); - return FilterResult::Intercept(None); - } - } - } if state == KeyState::Pressed && (keysyms::KEY_XF86Switch_VT_1..=KEY_XF86Switch_VT_12) @@ -371,14 +344,6 @@ impl State { time: event.time(), }, ); - - #[cfg(feature = "debug")] - if self.common.seats().position(|x| x == seat).unwrap() == 0 { - self.common - .egui - .debug_state - .handle_pointer_motion(position.to_i32_round()); - } break; } } @@ -416,13 +381,6 @@ impl State { }, ); - #[cfg(feature = "debug")] - if self.common.seats().position(|x| x == seat).unwrap() == 0 { - self.common - .egui - .debug_state - .handle_pointer_motion(position.to_i32_round()); - } break; } } @@ -435,21 +393,6 @@ impl State { let userdata = seat.user_data(); let devices = userdata.get::().unwrap(); if devices.has_device(&device) { - #[cfg(feature = "debug")] - if self.common.seats().position(|x| x == seat).unwrap() == 0 - && self.common.egui.active - { - if self.common.egui.debug_state.wants_pointer() { - if let Some(button) = event.button() { - self.common.egui.debug_state.handle_pointer_button( - button, - event.state() == ButtonState::Pressed, - ); - } - break; - } - } - let serial = SERIAL_COUNTER.next_serial(); let button = event.button_code(); if event.state() == ButtonState::Pressed { @@ -543,25 +486,6 @@ impl State { InputEvent::PointerAxis { event, .. } => { let device = event.device(); for seat in self.common.seats().cloned().collect::>().iter() { - #[cfg(feature = "debug")] - if self.common.seats().position(|x| x == seat).unwrap() == 0 - && self.common.egui.active - { - if self.common.egui.debug_state.wants_pointer() { - self.common.egui.debug_state.handle_pointer_axis( - event - .amount_discrete(Axis::Horizontal) - .or_else(|| event.amount(Axis::Horizontal).map(|x| x * 3.0)) - .unwrap_or(0.0), - event - .amount_discrete(Axis::Vertical) - .or_else(|| event.amount(Axis::Vertical).map(|x| x * 3.0)) - .unwrap_or(0.0), - ); - break; - } - } - let userdata = seat.user_data(); let devices = userdata.get::().unwrap(); if devices.has_device(&device) { diff --git a/src/state.rs b/src/state.rs index 31827868..991900c2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -252,14 +252,7 @@ impl State { log, #[cfg(feature = "debug")] - egui: Egui { - debug_state: smithay_egui::EguiState::new(Rectangle::from_loc_and_size( - (0, 0), - (400, 800), - )), - active: false, - alpha: 1.0, - }, + egui: Egui { active: false }, compositor_state, data_device_state, @@ -435,9 +428,7 @@ impl Common { #[cfg(feature = "debug")] pub struct Egui { - pub debug_state: smithay_egui::EguiState, pub active: bool, - pub alpha: f32, } #[cfg(feature = "debug")] From b2686424ea0df9acb40f4cd823930d20c18f6318 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 22 Nov 2022 10:07:17 +0100 Subject: [PATCH 52/62] seat: Workaround old active outputs on hotplug --- src/backend/kms/mod.rs | 20 ++++++++++++++--- src/backend/winit.rs | 8 ++++++- src/backend/x11.rs | 9 ++++++-- src/config/mod.rs | 23 +++++++++++++++----- src/shell/focus/mod.rs | 4 ++++ src/shell/mod.rs | 10 ++++++++- src/state.rs | 3 ++- src/wayland/handlers/output_configuration.rs | 3 +++ src/wayland/handlers/screencopy.rs | 3 +++ 9 files changed, 69 insertions(+), 14 deletions(-) diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 4eedaf78..4b9d37ee 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -33,6 +33,7 @@ use smithay::{ udev::{all_gpus, primary_gpu, UdevBackend, UdevEvent}, }, desktop::utils::OutputPresentationFeedback, + input::Seat, output::{Mode as OutputMode, Output, PhysicalProperties, Subpixel}, reexports::{ calloop::{ @@ -255,10 +256,12 @@ pub fn init_backend( } } + let seats = data.state.common.seats().cloned().collect::>(); data.state.common.config.read_outputs( &mut data.state.common.output_configuration_state, &mut data.state.backend, &mut data.state.common.shell, + seats.into_iter(), &data.state.common.event_loop_handle, ); for surface in data @@ -534,10 +537,12 @@ impl State { self.common .output_configuration_state .add_heads(wl_outputs.iter()); + let seats = self.common.seats().cloned().collect::>(); self.common.config.read_outputs( &mut self.common.output_configuration_state, &mut self.backend, &mut self.common.shell, + seats.into_iter(), &self.common.event_loop_handle, ); @@ -600,13 +605,17 @@ impl State { self.common .output_configuration_state .add_heads(outputs_added.iter()); + let seats = self.common.seats().cloned().collect::>(); for output in outputs_removed { - self.common.shell.remove_output(&output); + self.common + .shell + .remove_output(&output, seats.iter().cloned()); } self.common.config.read_outputs( &mut self.common.output_configuration_state, &mut self.backend, &mut self.common.shell, + seats.into_iter(), &self.common.event_loop_handle, ); @@ -638,14 +647,18 @@ impl State { .output_configuration_state .remove_heads(outputs_removed.iter()); + let seats = self.common.seats().cloned().collect::>(); if self.backend.kms().session.is_active() { for output in outputs_removed { - self.common.shell.remove_output(&output); + self.common + .shell + .remove_output(&output, seats.iter().cloned()); } self.common.config.read_outputs( &mut self.common.output_configuration_state, &mut self.backend, &mut self.common.shell, + seats.into_iter(), &self.common.event_loop_handle, ); } else { @@ -880,6 +893,7 @@ impl KmsState { pub fn apply_config_for_output( &mut self, output: &Output, + seats: impl Iterator>, shell: &mut Shell, test_only: bool, loop_handle: &LoopHandle<'_, Data>, @@ -902,7 +916,7 @@ impl KmsState { if !output_config.enabled { if !test_only { - shell.remove_output(output); + shell.remove_output(output, seats); if surface.surface.take().is_some() { // just drop it surface.pending = false; diff --git a/src/backend/winit.rs b/src/backend/winit.rs index e70c5404..b8bf4cb1 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -211,7 +211,11 @@ pub fn init_backend( } Err(winit::WinitError::WindowClosed) => { let output = data.state.backend.winit().output.clone(); - data.state.common.shell.remove_output(&output); + let seats = data.state.common.seats().cloned().collect::>(); + data.state + .common + .shell + .remove_output(&output, seats.into_iter()); if let Some(token) = token.take() { event_loop_handle.remove(token); } @@ -237,10 +241,12 @@ pub fn init_backend( .output_configuration_state .add_heads(std::iter::once(&output)); state.common.shell.add_output(&output); + let seats = state.common.seats().cloned().collect::>(); state.common.config.read_outputs( &mut state.common.output_configuration_state, &mut state.backend, &mut state.common.shell, + seats.iter().cloned(), &state.common.event_loop_handle, ); diff --git a/src/backend/x11.rs b/src/backend/x11.rs index 1b8b148d..a993418b 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -296,16 +296,18 @@ pub fn init_backend( .output_configuration_state .add_heads(std::iter::once(&output)); state.common.shell.add_output(&output); + let seats = state.common.seats().cloned().collect::>(); state.common.config.read_outputs( &mut state.common.output_configuration_state, &mut state.backend, &mut state.common.shell, + seats.iter().cloned(), &state.common.event_loop_handle, ); event_loop .handle() - .insert_source(backend, |event, _, data| match event { + .insert_source(backend, move |event, _, data| match event { X11Event::CloseRequested { window_id } => { // TODO: drain_filter let mut outputs_removed = Vec::new(); @@ -326,7 +328,10 @@ pub fn init_backend( .surfaces .retain(|s| s.window.id() != window_id); for output in outputs_removed.into_iter() { - data.state.common.shell.remove_output(&output); + data.state + .common + .shell + .remove_output(&output, seats.iter().cloned()); } } X11Event::Resized { diff --git a/src/config/mod.rs b/src/config/mod.rs index 289ff43e..c78eecdb 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -6,6 +6,7 @@ use crate::{ wayland::protocols::output_configuration::OutputConfigurationState, }; use serde::{Deserialize, Serialize}; +use smithay::input::Seat; pub use smithay::{ backend::input::KeyState, input::keyboard::{keysyms as KeySyms, Keysym, ModifiersState}, @@ -282,8 +283,10 @@ impl Config { output_state: &mut OutputConfigurationState, backend: &mut BackendData, shell: &mut Shell, + seats: impl Iterator>, loop_handle: &LoopHandle<'_, Data>, ) { + let seats = seats.collect::>(); let outputs = output_state.outputs().collect::>(); let mut infos = outputs .iter() @@ -314,9 +317,13 @@ impl Config { .get::>() .unwrap() .borrow_mut() = output_config; - if let Err(err) = - backend.apply_config_for_output(&output, false, shell, loop_handle) - { + if let Err(err) = backend.apply_config_for_output( + &output, + false, + shell, + seats.iter().cloned(), + loop_handle, + ) { slog_scope::warn!( "Failed to set new config for output {}: {}", output.name(), @@ -345,9 +352,13 @@ impl Config { .get::>() .unwrap() .borrow_mut() = output_config; - if let Err(err) = - backend.apply_config_for_output(&output, false, shell, loop_handle) - { + if let Err(err) = backend.apply_config_for_output( + &output, + false, + shell, + seats.iter().cloned(), + loop_handle, + ) { slog_scope::error!( "Failed to reset config for output {}: {}", output.name(), diff --git a/src/shell/focus/mod.rs b/src/shell/focus/mod.rs index 6adb1245..611101b3 100644 --- a/src/shell/focus/mod.rs +++ b/src/shell/focus/mod.rs @@ -167,6 +167,10 @@ impl Common { let seats = state.common.seats().cloned().collect::>(); for seat in seats { let output = seat.active_output(); + if !state.common.shell.outputs.contains(&output) { + seat.set_active_output(&state.common.shell.outputs[0]); + continue; + } let last_known_focus = ActiveFocus::get(&seat); if let Some(target) = last_known_focus { diff --git a/src/shell/mod.rs b/src/shell/mod.rs index a6d8fe10..53d9b4b4 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -448,7 +448,15 @@ impl Shell { } } - pub fn remove_output(&mut self, output: &Output) { + pub fn remove_output(&mut self, output: &Output, seats: impl Iterator>) { + if let Some(first_output) = self.outputs.get(0) { + for seat in seats { + if &seat.active_output() == output { + seat.set_active_output(first_output); + } + } + } + if !self.outputs.contains(output) { return; } diff --git a/src/state.rs b/src/state.rs index 991900c2..a1a7d6a4 100644 --- a/src/state.rs +++ b/src/state.rs @@ -141,11 +141,12 @@ impl BackendData { output: &Output, test_only: bool, shell: &mut Shell, + seats: impl Iterator>, loop_handle: &LoopHandle<'_, Data>, ) -> Result<(), anyhow::Error> { let result = match self { BackendData::Kms(ref mut state) => { - state.apply_config_for_output(output, shell, test_only, loop_handle) + state.apply_config_for_output(output, seats, shell, test_only, loop_handle) } BackendData::Winit(ref mut state) => state.apply_config_for_output(output, test_only), BackendData::X11(ref mut state) => state.apply_config_for_output(output, test_only), diff --git a/src/wayland/handlers/output_configuration.rs b/src/wayland/handlers/output_configuration.rs index 48594321..92981de0 100644 --- a/src/wayland/handlers/output_configuration.rs +++ b/src/wayland/handlers/output_configuration.rs @@ -81,10 +81,12 @@ impl State { } } + let seats = self.common.seats().cloned().collect::>(); if let Err(err) = self.backend.apply_config_for_output( output, test_only, &mut self.common.shell, + seats.iter().cloned(), &self.common.event_loop_handle, ) { slog_scope::warn!( @@ -106,6 +108,7 @@ impl State { output, false, &mut self.common.shell, + seats.iter().cloned(), &self.common.event_loop_handle, ) { slog_scope::error!( diff --git a/src/wayland/handlers/screencopy.rs b/src/wayland/handlers/screencopy.rs index 9bab636b..ba065160 100644 --- a/src/wayland/handlers/screencopy.rs +++ b/src/wayland/handlers/screencopy.rs @@ -1018,6 +1018,9 @@ impl State { if !session.alive() { return; } + if !data.state.common.shell.outputs.contains(&output) { + return; + } match render_workspace_to_buffer( &mut data.state, &session, From 8430566e5e644f2512af19400de1964e6ba97c42 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 22 Nov 2022 10:10:08 +0100 Subject: [PATCH 53/62] input: Fix variety of output shortcuts --- src/input/mod.rs | 124 ++++++++++++++++++++++++++++++++--------------- src/shell/mod.rs | 21 ++++---- 2 files changed, 97 insertions(+), 48 deletions(-) diff --git a/src/input/mod.rs b/src/input/mod.rs index 803f5ef4..88b96afb 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -28,7 +28,7 @@ use smithay::{ }, output::Output, reexports::wayland_server::DisplayHandle, - utils::{Logical, Point, Rectangle, SERIAL_COUNTER}, + utils::{Logical, Point, Rectangle, Serial, SERIAL_COUNTER}, wayland::{ keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat, seat::WaylandFocus, shell::wlr_layer::Layer as WlrLayer, @@ -266,7 +266,7 @@ impl State { ) .flatten() { - self.handle_action(action, seat) + self.handle_action(action, seat, serial, time) } break; } @@ -527,7 +527,7 @@ impl State { } } - fn handle_action(&mut self, action: Action, seat: &Seat) { + fn handle_action(&mut self, action: Action, seat: &Seat, serial: Serial, time: u32) { match action { Action::Terminate => { self.common.should_stop = true; @@ -553,15 +553,10 @@ impl State { 0 => 9, x => x - 1, }; - if let Some(motion_event) = self + let _ = self .common .shell - .activate(¤t_output, workspace as usize) - { - if let Some(ptr) = seat.get_pointer() { - ptr.motion(self, None, &motion_event); - } - } + .activate(¤t_output, workspace as usize); } Action::NextWorkspace => { let current_output = seat.active_output(); @@ -572,11 +567,7 @@ impl State { .active_num(¤t_output) .saturating_add(1); // TODO: Possibly move to next output, if idx to large - if let Some(motion_event) = self.common.shell.activate(¤t_output, workspace) { - if let Some(ptr) = seat.get_pointer() { - ptr.motion(self, None, &motion_event); - } - } + let _ = self.common.shell.activate(¤t_output, workspace); } Action::PreviousWorkspace => { let current_output = seat.active_output(); @@ -587,11 +578,7 @@ impl State { .active_num(¤t_output) .saturating_sub(1); // TODO: Possibly move to prev output, if idx < 0 - if let Some(motion_event) = self.common.shell.activate(¤t_output, workspace) { - if let Some(ptr) = seat.get_pointer() { - ptr.motion(self, None, &motion_event); - } - } + let _ = self.common.shell.activate(¤t_output, workspace); } Action::LastWorkspace => { let current_output = seat.active_output(); @@ -601,11 +588,7 @@ impl State { .workspaces .len(¤t_output) .saturating_sub(1); - if let Some(motion_event) = self.common.shell.activate(¤t_output, workspace) { - if let Some(ptr) = seat.get_pointer() { - ptr.motion(self, None, &motion_event); - } - } + let _ = self.common.shell.activate(¤t_output, workspace); } Action::MoveToWorkspace(key_num) => { let current_output = seat.active_output(); @@ -680,9 +663,18 @@ impl State { .cloned() { let idx = self.common.shell.workspaces.active_num(&next_output); - if let Some(motion_event) = self.common.shell.activate(&next_output, idx) { + if let Some(new_pos) = self.common.shell.activate(&next_output, idx) { + seat.set_active_output(&next_output); if let Some(ptr) = seat.get_pointer() { - ptr.motion(self, None, &motion_event); + ptr.motion( + self, + None, + &MotionEvent { + location: new_pos.to_f64(), + serial, + time, + }, + ); } } } @@ -701,9 +693,19 @@ impl State { .cloned() { let idx = self.common.shell.workspaces.active_num(&prev_output); - if let Some(motion_event) = self.common.shell.activate(&prev_output, idx) { + seat.set_active_output(&prev_output); + if let Some(new_pos) = self.common.shell.activate(&prev_output, idx) { + seat.set_active_output(&prev_output); if let Some(ptr) = seat.get_pointer() { - ptr.motion(self, None, &motion_event); + ptr.motion( + self, + None, + &MotionEvent { + location: new_pos.to_f64(), + serial, + time, + }, + ); } } } @@ -720,7 +722,24 @@ impl State { .next() .cloned() { - Shell::move_current_window(self, seat, ¤t_output, (&next_output, None)); + if let Some(new_pos) = Shell::move_current_window( + self, + seat, + ¤t_output, + (&next_output, None), + ) { + if let Some(ptr) = seat.get_pointer() { + ptr.motion( + self, + None, + &MotionEvent { + location: new_pos.to_f64(), + serial, + time, + }, + ); + } + } } } Action::MoveToPreviousOutput => { @@ -736,7 +755,24 @@ impl State { .next() .cloned() { - Shell::move_current_window(self, seat, ¤t_output, (&prev_output, None)); + if let Some(new_pos) = Shell::move_current_window( + self, + seat, + ¤t_output, + (&prev_output, None), + ) { + if let Some(ptr) = seat.get_pointer() { + ptr.motion( + self, + None, + &MotionEvent { + location: new_pos.to_f64(), + serial, + time, + }, + ); + } + } } } Action::Focus(focus) => { @@ -751,13 +787,17 @@ impl State { // TODO: Handle Workspace orientation match focus { FocusDirection::Left => { - self.handle_action(Action::PreviousWorkspace, seat) + self.handle_action(Action::PreviousWorkspace, seat, serial, time) } FocusDirection::Right => { - self.handle_action(Action::NextWorkspace, seat) + self.handle_action(Action::NextWorkspace, seat, serial, time) + } + FocusDirection::Up => { + self.handle_action(Action::PreviousOutput, seat, serial, time) + } + FocusDirection::Down => { + self.handle_action(Action::NextOutput, seat, serial, time) } - FocusDirection::Up => self.handle_action(Action::PreviousOutput, seat), - FocusDirection::Down => self.handle_action(Action::NextOutput, seat), _ => {} } } @@ -778,11 +818,17 @@ impl State { // TODO: Being able to move Groups (move_further should be KeyboardFocusTarget instead) match direction { Direction::Left => { - self.handle_action(Action::MoveToPreviousWorkspace, seat) + self.handle_action(Action::MoveToPreviousWorkspace, seat, serial, time) + } + Direction::Right => { + self.handle_action(Action::MoveToNextWorkspace, seat, serial, time) + } + Direction::Up => { + self.handle_action(Action::MoveToPreviousOutput, seat, serial, time) + } + Direction::Down => { + self.handle_action(Action::MoveToNextOutput, seat, serial, time) } - Direction::Right => self.handle_action(Action::MoveToNextWorkspace, seat), - Direction::Up => self.handle_action(Action::MoveToPreviousOutput, seat), - Direction::Down => self.handle_action(Action::MoveToNextOutput, seat), } } } diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 53d9b4b4..21cd8e6a 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::State as WState; use smithay::{ desktop::{layer_map_for_output, LayerSurface, PopupManager, Window, WindowSurfaceType}, - input::{pointer::MotionEvent, Seat}, + input::Seat, output::Output, reexports::wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle}, utils::{Logical, Point, Rectangle}, @@ -733,7 +733,7 @@ impl Shell { self.refresh(); // get rid of empty workspaces and enforce potential maximum } - pub fn activate(&mut self, output: &Output, idx: usize) -> Option { + pub fn activate(&mut self, output: &Output, idx: usize) -> Option> { match &mut self.workspaces { WorkspaceMode::OutputBound(sets, _) => { if let Some(set) = sets.get_mut(output) { @@ -745,7 +745,8 @@ impl Shell { } } - None + let output_geo = output.geometry(); + Some(output_geo.loc + Point::from((output_geo.size.w / 2, output_geo.size.h / 2))) } pub fn active_space(&self, output: &Output) -> &Workspace { @@ -1001,21 +1002,21 @@ impl Shell { seat: &Seat, from_output: &Output, to: (&Output, Option), - ) { + ) -> Option> { let (to_output, to_idx) = to; let to_idx = to_idx.unwrap_or(state.common.shell.workspaces.active_num(to_output)); if from_output == to_output && to_idx == state.common.shell.workspaces.active_num(from_output) { - return; + return None; } let from_workspace = state.common.shell.workspaces.active_mut(from_output); let maybe_window = from_workspace.focus_stack.get(seat).last().cloned(); - let Some(mapped) = maybe_window else { return; }; - let Some(window_state) = from_workspace.unmap(&mapped) else { return; }; + let Some(mapped) = maybe_window else { return None; }; + let Some(window_state) = from_workspace.unmap(&mapped) else { return None; }; for (toplevel, _) in mapped.windows() { state @@ -1030,6 +1031,9 @@ impl Shell { state.common.shell.update_reactive_popups(&mapped); } + seat.set_active_output(&to_output); + let new_pos = state.common.shell.activate(to_output, to_idx); + let to_workspace = state .common .shell @@ -1051,7 +1055,6 @@ impl Shell { .toplevel_info_state .toplevel_enter_workspace(&toplevel, &to_workspace.handle); } - for mapped in to_workspace .mapped() .cloned() @@ -1061,8 +1064,8 @@ impl Shell { state.common.shell.update_reactive_popups(&mapped); } - state.common.shell.activate(to_output, to_idx); Common::set_focus(state, Some(&KeyboardFocusTarget::from(mapped)), &seat, None); + new_pos } pub fn update_reactive_popups(&self, mapped: &CosmicMapped) { From e88f5898f765b3d296f62a42ae1e65e37336ddca Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 22 Nov 2022 10:28:30 +0100 Subject: [PATCH 54/62] window: Add window-specific debug overlay (not enabled) --- src/backend/render/element.rs | 6 + src/backend/render/mod.rs | 5 +- src/shell/element/mod.rs | 399 +++++++++++++++++++++- src/shell/layout/floating/grabs/moving.rs | 10 +- src/shell/layout/floating/mod.rs | 20 +- src/shell/layout/tiling/mod.rs | 20 +- src/shell/workspace.rs | 151 +++++++- 7 files changed, 559 insertions(+), 52 deletions(-) diff --git a/src/backend/render/element.rs b/src/backend/render/element.rs index f0372606..aeff0fc0 100644 --- a/src/backend/render/element.rs +++ b/src/backend/render/element.rs @@ -18,6 +18,7 @@ where R: AsGlowRenderer + Renderer + ImportAll, ::TextureId: 'static, ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, { Workspace(WorkspaceRenderElement), Cursor(CursorRenderElement), @@ -31,6 +32,7 @@ where R: AsGlowRenderer + Renderer + ImportAll, ::TextureId: 'static, ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, { fn id(&self) -> &smithay::backend::renderer::element::Id { match self { @@ -209,6 +211,7 @@ where R: Renderer + ImportAll + AsGlowRenderer, ::TextureId: 'static, ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, { fn from(elem: WorkspaceRenderElement) -> Self { Self::Workspace(elem) @@ -220,6 +223,7 @@ where R: Renderer + ImportAll + AsGlowRenderer, ::TextureId: 'static, ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, { fn from(elem: CursorRenderElement) -> Self { Self::Cursor(elem) @@ -231,6 +235,7 @@ where R: Renderer + ImportAll + AsGlowRenderer, ::TextureId: 'static, ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, { fn from(elem: CosmicMappedRenderElement) -> Self { Self::MoveGrab(elem) @@ -243,6 +248,7 @@ where R: Renderer + ImportAll + AsGlowRenderer, ::TextureId: 'static, ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, { fn from(elem: TextureRenderElement) -> Self { Self::Egui(elem) diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index c3f2e96e..709bca1b 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -68,9 +68,10 @@ pub fn cursor_elements( mode: CursorMode, ) -> Vec where - R: Renderer + ImportAll + ImportMem, + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, ::Frame: AsGles2Frame, ::TextureId: Clone + 'static, + CosmicMappedRenderElement: RenderElement, E: From> + From>, { let scale = output.current_scale().fractional_scale(); @@ -149,6 +150,7 @@ where ::TextureId: Clone + 'static, ::Error: From, CosmicElement: RenderElement, + CosmicMappedRenderElement: RenderElement, Source: Clone, { let handle = state.shell.workspaces.active(output).handle; @@ -195,6 +197,7 @@ where ::TextureId: Clone + 'static, ::Error: From, CosmicElement: RenderElement, + CosmicMappedRenderElement: RenderElement, Source: Clone, { #[cfg(feature = "debug")] diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index 890ac9cd..b2395d73 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -1,9 +1,25 @@ -use crate::{state::State, utils::prelude::SeatExt}; +use crate::{ + backend::render::{ + element::{AsGles2Frame, AsGlowRenderer, CosmicElement}, + GlMultiRenderer, + }, + state::State, + utils::prelude::SeatExt, +}; use id_tree::NodeId; use smithay::{ backend::{ input::KeyState, - renderer::{element::AsRenderElements, ImportAll, Renderer}, + renderer::{ + element::{ + texture::TextureRenderElement, AsRenderElements, Element, RenderElement, + UnderlyingStorage, + }, + gles2::Gles2Texture, + glow::GlowRenderer, + multigpu::Error as MultiError, + ImportAll, Renderer, + }, }, desktop::{space::SpaceElement, Kind, PopupManager, Window, WindowSurfaceType}, input::{ @@ -16,7 +32,7 @@ use smithay::{ wayland_protocols::xdg::shell::server::xdg_toplevel::State as XdgState, wayland_server::{backend::ObjectId, protocol::wl_surface::WlSurface}, }, - render_elements, space_elements, + space_elements, utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size}, wayland::{ compositor::{with_states, with_surface_tree_downward, TraversalAction}, @@ -26,6 +42,7 @@ use smithay::{ }; use std::{ collections::HashMap, + fmt, hash::Hash, sync::{Arc, Mutex}, }; @@ -35,6 +52,11 @@ pub use self::stack::CosmicStack; pub mod window; pub use self::window::CosmicWindow; +#[cfg(feature = "debug")] +use smithay::wayland::shell::xdg::XdgToplevelSurfaceData; +#[cfg(feature = "debug")] +use smithay_egui::EguiState; + use super::{focus::FocusDirection, layout::floating::ResizeState}; space_elements! { @@ -44,7 +66,7 @@ space_elements! { Stack=CosmicStack, } -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct CosmicMapped { element: CosmicMappedInternal, @@ -56,6 +78,20 @@ pub struct CosmicMapped { //floating pub(super) last_geometry: Arc>>>, pub(super) resize_state: Arc>>, + + #[cfg(feature = "debug")] + debug: Arc>, +} + +impl fmt::Debug for CosmicMapped { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CosmicMapped") + .field("element", &self.element) + .field("last_cursor_position", &self.last_cursor_position) + .field("tiling_node_id", &self.tiling_node_id) + .field("resize_state", &self.resize_state) + .finish() + } } impl PartialEq for CosmicMapped { @@ -239,8 +275,7 @@ impl CosmicMapped { Kind::Xdg(xdg) => { xdg.current_state().states.contains(XdgState::Resizing) || xdg.with_pending_state(|states| states.states.contains(XdgState::Resizing)) - } - // Kind::X11? + } // Kind::X11? } } @@ -387,8 +422,7 @@ impl CosmicMapped { Kind::Xdg(xdg) => { xdg.current_state().states.contains(XdgState::Activated) || xdg.with_pending_state(|states| states.states.contains(XdgState::Activated)) - } - // Kind::X11? + } // Kind::X11? } } @@ -687,6 +721,10 @@ impl From for CosmicMapped { tiling_node_id: Arc::new(Mutex::new(None)), last_geometry: Arc::new(Mutex::new(None)), resize_state: Arc::new(Mutex::new(None)), + #[cfg(feature = "debug")] + debug: Arc::new(Mutex::new(smithay_egui::EguiState::new( + Rectangle::from_loc_and_size((10, 10), (100, 100)), + ))), } } } @@ -699,20 +737,235 @@ impl From for CosmicMapped { tiling_node_id: Arc::new(Mutex::new(None)), last_geometry: Arc::new(Mutex::new(None)), resize_state: Arc::new(Mutex::new(None)), + #[cfg(feature = "debug")] + debug: Arc::new(Mutex::new(smithay_egui::EguiState::new( + Rectangle::from_loc_and_size((10, 10), (100, 100)), + ))), } } } -render_elements! { - pub CosmicMappedRenderElement where R: ImportAll; - Stack=self::stack::CosmicStackRenderElement, - Window=self::window::CosmicWindowRenderElement, +pub enum CosmicMappedRenderElement +where + R: AsGlowRenderer + Renderer + ImportAll, + ::TextureId: 'static, + ::Frame: AsGles2Frame, +{ + Stack(self::stack::CosmicStackRenderElement), + Window(self::window::CosmicWindowRenderElement), + #[cfg(feature = "debug")] + Egui(TextureRenderElement), +} + +impl Element for CosmicMappedRenderElement +where + R: AsGlowRenderer + Renderer + ImportAll, + ::TextureId: 'static, + ::Frame: AsGles2Frame, +{ + fn id(&self) -> &smithay::backend::renderer::element::Id { + match self { + CosmicMappedRenderElement::Stack(elem) => elem.id(), + CosmicMappedRenderElement::Window(elem) => elem.id(), + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => elem.id(), + } + } + + fn current_commit(&self) -> smithay::backend::renderer::utils::CommitCounter { + match self { + CosmicMappedRenderElement::Stack(elem) => elem.current_commit(), + CosmicMappedRenderElement::Window(elem) => elem.current_commit(), + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => elem.current_commit(), + } + } + + fn src(&self) -> Rectangle { + match self { + CosmicMappedRenderElement::Stack(elem) => elem.src(), + CosmicMappedRenderElement::Window(elem) => elem.src(), + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => elem.src(), + } + } + + fn geometry(&self, scale: Scale) -> Rectangle { + match self { + CosmicMappedRenderElement::Stack(elem) => elem.geometry(scale), + CosmicMappedRenderElement::Window(elem) => elem.geometry(scale), + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => elem.geometry(scale), + } + } + + fn location(&self, scale: Scale) -> Point { + match self { + CosmicMappedRenderElement::Stack(elem) => elem.location(scale), + CosmicMappedRenderElement::Window(elem) => elem.location(scale), + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => elem.location(scale), + } + } + + fn transform(&self) -> smithay::utils::Transform { + match self { + CosmicMappedRenderElement::Stack(elem) => elem.transform(), + CosmicMappedRenderElement::Window(elem) => elem.transform(), + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => elem.transform(), + } + } + + fn damage_since( + &self, + scale: Scale, + commit: Option, + ) -> Vec> { + match self { + CosmicMappedRenderElement::Stack(elem) => elem.damage_since(scale, commit), + CosmicMappedRenderElement::Window(elem) => elem.damage_since(scale, commit), + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => elem.damage_since(scale, commit), + } + } + + fn opaque_regions(&self, scale: Scale) -> Vec> { + match self { + CosmicMappedRenderElement::Stack(elem) => elem.opaque_regions(scale), + CosmicMappedRenderElement::Window(elem) => elem.opaque_regions(scale), + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => elem.opaque_regions(scale), + } + } +} + +impl RenderElement for CosmicMappedRenderElement { + fn draw( + &self, + renderer: &mut GlowRenderer, + frame: &mut ::Frame, + location: Point, + scale: Scale, + damage: &[Rectangle], + log: &slog::Logger, + ) -> Result<(), ::Error> { + match self { + CosmicMappedRenderElement::Stack(elem) => { + elem.draw(renderer, frame, location, scale, damage, log) + } + CosmicMappedRenderElement::Window(elem) => { + elem.draw(renderer, frame, location, scale, damage, log) + } + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => { + elem.draw(renderer, frame, location, scale, damage, log) + } + } + } + + fn underlying_storage( + &self, + renderer: &GlowRenderer, + ) -> Option> { + match self { + CosmicMappedRenderElement::Stack(elem) => elem.underlying_storage(renderer), + CosmicMappedRenderElement::Window(elem) => elem.underlying_storage(renderer), + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => elem.underlying_storage(renderer), + } + } +} + +impl<'a> RenderElement> for CosmicMappedRenderElement> { + fn draw( + &self, + renderer: &mut GlMultiRenderer<'a>, + frame: &mut as Renderer>::Frame, + location: Point, + scale: Scale, + damage: &[Rectangle], + log: &slog::Logger, + ) -> Result<(), as Renderer>::Error> { + match self { + CosmicMappedRenderElement::Stack(elem) => { + elem.draw(renderer, frame, location, scale, damage, log) + } + CosmicMappedRenderElement::Window(elem) => { + elem.draw(renderer, frame, location, scale, damage, log) + } + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => { + let glow_renderer = renderer.glow_renderer_mut(); + let gles2_frame = frame.gles2_frame_mut(); + elem.draw(glow_renderer, gles2_frame, location, scale, damage, log) + .map_err(|err| MultiError::Render(err)) + } + } + } + + fn underlying_storage( + &self, + renderer: &GlMultiRenderer<'a>, + ) -> Option>> { + match self { + CosmicMappedRenderElement::Stack(elem) => elem.underlying_storage(renderer), + CosmicMappedRenderElement::Window(elem) => elem.underlying_storage(renderer), + #[cfg(feature = "debug")] + CosmicMappedRenderElement::Egui(elem) => { + let glow_renderer = renderer.glow_renderer(); + match elem.underlying_storage(glow_renderer) { + Some(UnderlyingStorage::Wayland(buffer)) => { + Some(UnderlyingStorage::Wayland(buffer)) + } + _ => None, + } + } + } + } +} + +impl From> for CosmicMappedRenderElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, +{ + fn from(elem: stack::CosmicStackRenderElement) -> Self { + CosmicMappedRenderElement::Stack(elem) + } +} +impl From> for CosmicMappedRenderElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, +{ + fn from(elem: window::CosmicWindowRenderElement) -> Self { + CosmicMappedRenderElement::Window(elem) + } +} +#[cfg(feature = "debug")] +impl From> for CosmicMappedRenderElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, +{ + fn from(elem: TextureRenderElement) -> Self { + CosmicMappedRenderElement::Egui(elem) + } } impl AsRenderElements for CosmicMapped where - R: Renderer + ImportAll, + R: Renderer + ImportAll + AsGlowRenderer, ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, { type RenderElement = CosmicMappedRenderElement; fn render_elements>( @@ -720,7 +973,7 @@ where location: Point, scale: Scale, ) -> Vec { - match &self.element { + let mut elements = match &self.element { CosmicMappedInternal::Stack(s) => AsRenderElements::::render_elements::< CosmicMappedRenderElement, >(s, location, scale), @@ -728,9 +981,121 @@ where CosmicMappedRenderElement, >(w, location, scale), _ => Vec::new(), + }; + + /* + #[cfg(feature = "debug")] + if !elements.is_empty() { + let window = self.active_window(); + let (app_id, title, min_size, max_size, size, states) = with_states(&window.kind().wl_surface(), |states| { + let attributes = states + .data_map + .get::() + .unwrap() + .lock() + .unwrap(); + (attributes.app_id.clone(), attributes.title.clone(), attributes.min_size.clone(), attributes.max_size.clone(), attributes.current.size.clone(), attributes.current.states.clone()) + }); + + let win_geo = self.geometry().size; + let area = { + let size = win_size.clamp((10, 10), (100, 100))}; + let offset = (win_size - (100, 100).into()) + .to_point() + .constain(Rectangle::from_loc_and_size((0, 0), (10, 10))); + Rectangle::from_loc_and_size(offset, size) + }; + + match self.debug.lock().unwrap().render( + |ui| { + egui::Frame::none() + .fill(Color32::DARK_GRAY) + .rounding(0.25) + .show(ui, |ui| { + ui.heading(title.as_deref().unwrap_or("")); + ui.label(app_id.as_deref().unwrap_or("")); + ui.horizontal(|ui| { + ui.label("States: "); + if states.contains(XdgState::Maximized) { + ui.label("🗖"); + } + if states.contains(XdgState::Fullscreen) { + ui.label("⬜") + } + if states.contains(XdgState::Activated) { + ui.label("🖱") + } + if states.contains(XdgState::Resizing) { + ui.label("↔") + } + if states.contains(XdgState::TiledLeft) { + ui.label("⏴") + } + if states.contains(XdgState::TiledRight) { + ui.label("⏵") + } + if states.contains(XdgState::TiledTop) { + ui.label("⏶") + } + if states.contains(XdgState::TiledBottom) { + ui.label("⏷") + } + }); + + let plot = Plot::new("Sizes") + .legend(Legend::default().position(Corner::RightBottom)) + .show_x(false) + .show_y(false) + .data_aspect(0.1); + plot.show(ui, |plot_ui| { + let center = ((max_size.w + 20) / 2, (max_size.h + 20) / 2); + let max_size_rect = Polygon::new(PlotPoints::new(vec![ + (10, 10), + (max_size.w + 10, 10), + (max_size.w + 10, max_size.h + 10), + (10, max_size.h + 10), + (10, 10), + ])); + plot_ui.polygon(max_size_rect.name(format!("{}", max_size))); + + if let Some(size) = size { + let size_rect = Polygon::new(PlotPoints::new(vec![ + (center.0 - size.w / 2, center.1 - size.h / 2), + (center.0 + size.w / 2, center.1 - size.w / 2), + (center.0 + size.w / 2, center.1 + size.w / 2), + (center.0 - size.w / 2, center.1 + size.w / 2), + (center.0 - size.w / 2, center.1 - size.w / 2), + ])); + plot_ui.polygon(size_rect.name(format!("{}", size))); + } + + let min_size_rect = Polygon::new(PlotPoints::new(vec![ + (center.0 - min_size.w / 2, center.1 - min_size.h / 2), + (center.0 + min_size.w / 2, center.1 - min_size.w / 2), + (center.0 + min_size.w / 2, center.1 + min_size.w / 2), + (center.0 - min_size.w / 2, center.1 + min_size.w / 2), + (center.0 - min_size.w / 2, center.1 - min_size.w / 2), + ])); + plot_ui.polygon(min_size_rect.name(format!("{}", min_size))); + }) + }); + }, + renderer, + area, + scale, + if self.last_cursor_position.lock().unwrap().values().any(|p| area.contains(p.to_i32_round())) { + 0.4 + } else { + 1.0 + }, + start_time, + ) { + Ok(element) => elements.push(element), + Err(err) => slog_scope::debug!("Error rendering debug overlay: {}", err), + }; } - .into_iter() - .map(C::from) - .collect() + */ + + elements.into_iter().map(C::from).collect() } } diff --git a/src/shell/layout/floating/grabs/moving.rs b/src/shell/layout/floating/grabs/moving.rs index a04cf043..4c12ae05 100644 --- a/src/shell/layout/floating/grabs/moving.rs +++ b/src/shell/layout/floating/grabs/moving.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ + backend::render::element::{AsGles2Frame, AsGlowRenderer}, shell::{ element::{CosmicMapped, CosmicMappedRenderElement}, focus::target::{KeyboardFocusTarget, PointerFocusTarget}, @@ -9,7 +10,10 @@ use crate::{ }; use smithay::{ - backend::renderer::{element::AsRenderElements, ImportAll, Renderer}, + backend::renderer::{ + element::{AsRenderElements, RenderElement}, + ImportAll, Renderer, + }, desktop::space::SpaceElement, input::{ pointer::{ @@ -35,8 +39,10 @@ pub struct MoveGrabState { impl MoveGrabState { pub fn render(&self, seat: &Seat, output: &Output) -> Vec where - R: Renderer + ImportAll, + R: Renderer + ImportAll + AsGlowRenderer, ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, I: From>, { let cursor_at = seat.get_pointer().unwrap().current_location(); diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 24fcc67f..e512ef6a 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -1,16 +1,16 @@ // SPDX-License-Identifier: GPL-3.0-only use smithay::{ - backend::renderer::{ImportAll, Renderer}, + backend::renderer::{element::RenderElement, ImportAll, Renderer}, desktop::{layer_map_for_output, space::SpaceElement, Space, Window}, input::{pointer::GrabStartData as PointerGrabStartData, Seat}, output::Output, - render_elements, utils::{Logical, Point, Rectangle, Serial}, }; use std::collections::HashMap; use crate::{ + backend::render::element::{AsGles2Frame, AsGlowRenderer}, shell::{ element::{CosmicMapped, CosmicMappedRenderElement}, grabs::ResizeEdge, @@ -295,23 +295,17 @@ impl FloatingLayout { pub fn render_output( &self, output: &Output, - ) -> Result>, OutputNotMapped> + ) -> Result>, OutputNotMapped> where - R: Renderer + ImportAll, + R: Renderer + ImportAll + AsGlowRenderer, ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, { let output_scale = output.current_scale().fractional_scale(); let output_geo = self.space.output_geometry(output).ok_or(OutputNotMapped)?; Ok(self .space - .render_elements_for_region::(&output_geo, output_scale) - .into_iter() - .map(FloatingRenderElement::from) - .collect()) + .render_elements_for_region::(&output_geo, output_scale)) } } - -render_elements! { - pub FloatingRenderElement where R: ImportAll; - Window=CosmicMappedRenderElement, -} diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 3760bfd3..a1954490 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ + backend::render::element::{AsGles2Frame, AsGlowRenderer}, shell::{ element::{CosmicMapped, CosmicMappedRenderElement}, focus::{ @@ -17,11 +18,13 @@ use crate::{ use id_tree::{InsertBehavior, MoveBehavior, Node, NodeId, NodeIdError, RemoveBehavior, Tree}; use smithay::{ - backend::renderer::{element::AsRenderElements, ImportAll, Renderer}, + backend::renderer::{ + element::{AsRenderElements, RenderElement}, + ImportAll, Renderer, + }, desktop::{layer_map_for_output, space::SpaceElement, PopupKind, Window}, input::{pointer::GrabStartData as PointerGrabStartData, Seat}, output::Output, - render_elements, utils::{IsAlive, Logical, Point, Rectangle, Scale, Serial}, }; use std::{borrow::Borrow, collections::HashMap, hash::Hash, sync::Arc}; @@ -1222,10 +1225,12 @@ impl TilingLayout { pub fn render_output( &self, output: &Output, - ) -> Result>, OutputNotMapped> + ) -> Result>, OutputNotMapped> where - R: Renderer + ImportAll, + R: Renderer + ImportAll + AsGlowRenderer, ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, { let output_scale = output.current_scale().fractional_scale(); @@ -1243,7 +1248,7 @@ impl TilingLayout { } }) .flat_map(|(mapped, loc)| { - AsRenderElements::::render_elements::>( + AsRenderElements::::render_elements::>( mapped, loc.to_physical_precise_round(output_scale) - mapped @@ -1256,8 +1261,3 @@ impl TilingLayout { .collect::>()) } } - -render_elements! { - pub TilingRenderElement where R: ImportAll; - Window=CosmicMappedRenderElement, -} diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index b9c0dbc3..95c0eba6 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -1,4 +1,5 @@ use crate::{ + backend::render::element::{AsGles2Frame, AsGlowRenderer}, shell::layout::{ floating::{FloatingLayout, MoveSurfaceGrab}, tiling::TilingLayout, @@ -17,7 +18,7 @@ use crate::{ use indexmap::IndexSet; use smithay::{ backend::renderer::{ - element::{surface::WaylandSurfaceRenderElement, AsRenderElements}, + element::{surface::WaylandSurfaceRenderElement, AsRenderElements, Element, RenderElement}, ImportAll, Renderer, }, desktop::{layer_map_for_output, space::SpaceElement, Kind, LayerSurface, Window}, @@ -27,7 +28,6 @@ use smithay::{ wayland_protocols::xdg::shell::server::xdg_toplevel::{self, ResizeEdge}, wayland_server::protocol::wl_surface::WlSurface, }, - render_elements, utils::{IsAlive, Logical, Point, Rectangle, Scale, Serial}, wayland::shell::wlr_layer::Layer, }; @@ -37,7 +37,7 @@ use super::{ element::CosmicMapped, focus::{FocusStack, FocusStackMut}, grabs::ResizeGrab, - layout::{floating::FloatingRenderElement, tiling::TilingRenderElement}, + CosmicMappedRenderElement, }; #[derive(Debug)] @@ -422,8 +422,10 @@ impl Workspace { output: &Output, ) -> Result>, OutputNotMapped> where - R: Renderer + ImportAll, + R: Renderer + ImportAll + AsGlowRenderer, ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, { let mut render_elements = Vec::new(); @@ -540,9 +542,140 @@ impl FocusStacks { pub struct OutputNotMapped; -render_elements! { - pub WorkspaceRenderElement where R: ImportAll; - Wayland=WaylandSurfaceRenderElement, - Floating=FloatingRenderElement, - Tiling=TilingRenderElement, +pub enum WorkspaceRenderElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, +{ + Wayland(WaylandSurfaceRenderElement), + Window(CosmicMappedRenderElement), +} + +impl Element for WorkspaceRenderElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, +{ + fn id(&self) -> &smithay::backend::renderer::element::Id { + match self { + WorkspaceRenderElement::Wayland(elem) => elem.id(), + WorkspaceRenderElement::Window(elem) => elem.id(), + } + } + + fn current_commit(&self) -> smithay::backend::renderer::utils::CommitCounter { + match self { + WorkspaceRenderElement::Wayland(elem) => elem.current_commit(), + WorkspaceRenderElement::Window(elem) => elem.current_commit(), + } + } + + fn src(&self) -> Rectangle { + match self { + WorkspaceRenderElement::Wayland(elem) => elem.src(), + WorkspaceRenderElement::Window(elem) => elem.src(), + } + } + + fn geometry(&self, scale: Scale) -> Rectangle { + match self { + WorkspaceRenderElement::Wayland(elem) => elem.geometry(scale), + WorkspaceRenderElement::Window(elem) => elem.geometry(scale), + } + } + + fn location(&self, scale: Scale) -> Point { + match self { + WorkspaceRenderElement::Wayland(elem) => elem.location(scale), + WorkspaceRenderElement::Window(elem) => elem.location(scale), + } + } + + fn transform(&self) -> smithay::utils::Transform { + match self { + WorkspaceRenderElement::Wayland(elem) => elem.transform(), + WorkspaceRenderElement::Window(elem) => elem.transform(), + } + } + + fn damage_since( + &self, + scale: Scale, + commit: Option, + ) -> Vec> { + match self { + WorkspaceRenderElement::Wayland(elem) => elem.damage_since(scale, commit), + WorkspaceRenderElement::Window(elem) => elem.damage_since(scale, commit), + } + } + + fn opaque_regions(&self, scale: Scale) -> Vec> { + match self { + WorkspaceRenderElement::Wayland(elem) => elem.opaque_regions(scale), + WorkspaceRenderElement::Window(elem) => elem.opaque_regions(scale), + } + } +} + +impl RenderElement for WorkspaceRenderElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, +{ + fn draw( + &self, + renderer: &mut R, + frame: &mut ::Frame, + location: Point, + scale: Scale, + damage: &[Rectangle], + log: &slog::Logger, + ) -> Result<(), ::Error> { + match self { + WorkspaceRenderElement::Wayland(elem) => { + elem.draw(renderer, frame, location, scale, damage, log) + } + WorkspaceRenderElement::Window(elem) => { + elem.draw(renderer, frame, location, scale, damage, log) + } + } + } + + fn underlying_storage( + &self, + renderer: &R, + ) -> Option> { + match self { + WorkspaceRenderElement::Wayland(elem) => elem.underlying_storage(renderer), + WorkspaceRenderElement::Window(elem) => elem.underlying_storage(renderer), + } + } +} + +impl From for WorkspaceRenderElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, +{ + fn from(elem: WaylandSurfaceRenderElement) -> Self { + WorkspaceRenderElement::Wayland(elem) + } +} + +impl From> for WorkspaceRenderElement +where + R: Renderer + ImportAll + AsGlowRenderer, + ::TextureId: 'static, + ::Frame: AsGles2Frame, + CosmicMappedRenderElement: RenderElement, +{ + fn from(elem: CosmicMappedRenderElement) -> Self { + WorkspaceRenderElement::Window(elem) + } } From ae34a0da6241c13de43217b8fbc76d4996e0f3ce Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 22 Nov 2022 15:47:27 +0100 Subject: [PATCH 55/62] tiling: Fix merging workspaces --- src/shell/layout/tiling/mod.rs | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index a1954490..b59e0cae 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -279,8 +279,8 @@ impl TilingLayout { // TODO: expects last remaining output let Some((output, dst)) = self.trees.iter_mut().next() else { return; }; let orientation = match output.output.geometry().size { - x if x.w >= x.h => Orientation::Horizontal, - _ => Orientation::Vertical, + x if x.w >= x.h => Orientation::Vertical, + _ => Orientation::Horizontal, }; TilingLayout::merge_trees(src, dst, orientation); self.refresh() @@ -1187,8 +1187,8 @@ impl TilingLayout { for (output_data, src) in other.trees { let mut dst = self.trees.entry(output_data.clone()).or_default(); let orientation = match output_data.output.geometry().size { - x if x.w >= x.h => Orientation::Horizontal, - _ => Orientation::Vertical, + x if x.w >= x.h => Orientation::Vertical, + _ => Orientation::Horizontal, }; TilingLayout::merge_trees(src, &mut dst, orientation); } @@ -1196,17 +1196,24 @@ impl TilingLayout { } fn merge_trees(src: Tree, dst: &mut Tree, orientation: Orientation) { - if let Some(root_id) = src.root_node_id() { + if let Some(dst_root_id) = dst.root_node_id().cloned() { let mut stack = Vec::new(); - let root_node = src.get(root_id).unwrap(); - let new_node = Node::new(root_node.data().clone()); - let new_id = dst.insert(new_node, InsertBehavior::AsRoot).unwrap(); - if let Some(root) = dst.root_node_id().cloned() { - TilingLayout::new_group(dst, &root, &new_id, orientation).unwrap(); + if let Some(src_root_id) = src.root_node_id() { + let root_node = src.get(src_root_id).unwrap(); + let new_node = Node::new(root_node.data().clone()); + let new_id = dst + .insert(new_node, InsertBehavior::UnderNode(&dst_root_id)) + .unwrap(); + if let &mut Data::Mapped { ref mut mapped, .. } = + dst.get_mut(&new_id).unwrap().data_mut() + { + *mapped.tiling_node_id.lock().unwrap() = Some(new_id.clone()); + } + TilingLayout::new_group(dst, &dst_root_id, &new_id, orientation).unwrap(); + stack.push((src_root_id.clone(), new_id)); } - stack.push((root_id.clone(), new_id)); while let Some((src_id, dst_id)) = stack.pop() { for child_id in src.children_ids(&src_id).unwrap() { let src_node = src.get(&child_id).unwrap(); @@ -1214,6 +1221,11 @@ impl TilingLayout { let new_child_id = dst .insert(new_node, InsertBehavior::UnderNode(&dst_id)) .unwrap(); + if let &mut Data::Mapped { ref mut mapped, .. } = + dst.get_mut(&new_child_id).unwrap().data_mut() + { + *mapped.tiling_node_id.lock().unwrap() = Some(new_child_id.clone()); + } stack.push((child_id.clone(), new_child_id)); } } From a066edab0b6b2d3a19959c5e5d1e1668c6bccc8c Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 22 Nov 2022 15:48:36 +0100 Subject: [PATCH 56/62] move: Fix window offset --- src/shell/layout/floating/grabs/moving.rs | 25 +++++++++++++---------- src/shell/workspace.rs | 1 - 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/shell/layout/floating/grabs/moving.rs b/src/shell/layout/floating/grabs/moving.rs index 4c12ae05..5b5ef2aa 100644 --- a/src/shell/layout/floating/grabs/moving.rs +++ b/src/shell/layout/floating/grabs/moving.rs @@ -33,7 +33,6 @@ pub struct MoveGrabState { window: CosmicMapped, initial_cursor_location: Point, initial_window_location: Point, - initial_output_location: Point, } impl MoveGrabState { @@ -47,8 +46,7 @@ impl MoveGrabState { { let cursor_at = seat.get_pointer().unwrap().current_location(); let delta = cursor_at - self.initial_cursor_location; - let location = - self.initial_output_location.to_f64() + self.initial_window_location.to_f64() + delta; + let location = self.initial_window_location.to_f64() + delta; let mut window_geo = self.window.geometry(); window_geo.loc += location.to_i32_round(); @@ -120,13 +118,11 @@ impl MoveSurfaceGrab { seat: &Seat, initial_cursor_location: Point, initial_window_location: Point, - initial_output_location: Point, ) -> MoveSurfaceGrab { let grab_state = MoveGrabState { window: window.clone(), initial_cursor_location, initial_window_location, - initial_output_location, }; *seat @@ -160,11 +156,9 @@ impl MoveSurfaceGrab { { if grab_state.window.alive() { let delta = handle.current_location() - grab_state.initial_cursor_location; - let window_location = (grab_state.initial_window_location.to_f64() - + grab_state.initial_output_location.to_f64() - - output.geometry().loc.to_f64() - + delta) - .to_i32_round(); + let window_location = (grab_state.initial_window_location.to_f64() + delta) + .to_i32_round() + - output.geometry().loc; let workspace_handle = state.common.shell.active_space(&output).handle; for (window, _) in grab_state.window.windows() { @@ -180,12 +174,21 @@ impl MoveSurfaceGrab { .toplevel_enter_output(&window, &output); } + let offset = state + .common + .shell + .active_space(&output) + .floating_layer + .space + .output_geometry(&output) + .unwrap() + .loc; state .common .shell .active_space_mut(&output) .floating_layer - .map_internal(grab_state.window, &output, Some(window_location)); + .map_internal(grab_state.window, &output, Some(window_location + offset)); } } diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 95c0eba6..b8b610ca 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -354,7 +354,6 @@ impl Workspace { seat, pos, initial_window_location, - output.geometry().loc, )) } else { None // TODO From 57f15da85d2d283ce26c8062c309808919278a24 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 22 Nov 2022 16:31:58 +0100 Subject: [PATCH 57/62] ext_workspace: Fix activation for output-bound --- src/wayland/handlers/workspace.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/wayland/handlers/workspace.rs b/src/wayland/handlers/workspace.rs index 26f7aca3..eecc24c6 100644 --- a/src/wayland/handlers/workspace.rs +++ b/src/wayland/handlers/workspace.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ + shell::WorkspaceMode, state::ClientState, utils::prelude::*, wayland::protocols::workspace::{ @@ -29,14 +30,21 @@ impl WorkspaceHandler for State { for request in requests.into_iter() { match request { Request::Activate(handle) => { - let output = self.common.last_active_seat().active_output(); - let maybe_idx = self - .common - .shell - .workspaces - .spaces_for_output(&output) - .position(|w| w.handle == handle); - if let Some(idx) = maybe_idx { + let maybe = match &self.common.shell.workspaces { + WorkspaceMode::Global(set) => set + .workspaces + .iter() + .position(|w| w.handle == handle) + .map(|i| (self.common.last_active_seat().active_output(), i)), + WorkspaceMode::OutputBound(sets, _) => sets.iter().find_map(|(o, set)| { + set.workspaces + .iter() + .position(|w| w.handle == handle) + .map(|i| (o.clone(), i)) + }), + }; + + if let Some((output, idx)) = maybe { self.common.shell.activate(&output, idx); } } From aa2e7c0a023bc446a0f923af322dd24e3804eb5b Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 22 Nov 2022 17:09:49 +0100 Subject: [PATCH 58/62] ext_workspace: Utilize 2d-coords for workspace outputs --- src/shell/mod.rs | 58 ++++++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 21cd8e6a..b8dbb8f5 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -66,7 +66,8 @@ pub struct WorkspaceSet { active: usize, amount: WorkspaceAmount, group: WorkspaceGroupHandle, - workspaces: Vec, + idx: usize, + pub(crate) workspaces: Vec, } #[derive(Debug, Clone, Copy, Serialize, Deserialize)] @@ -92,13 +93,17 @@ fn create_workspace( } impl WorkspaceSet { - fn new(state: &mut WorkspaceUpdateGuard<'_, State>, amount: WorkspaceAmount) -> WorkspaceSet { + fn new( + state: &mut WorkspaceUpdateGuard<'_, State>, + amount: WorkspaceAmount, + idx: usize, + ) -> WorkspaceSet { let group_handle = state.create_workspace_group(); let workspaces = match amount { WorkspaceAmount::Dynamic => { let workspace = create_workspace(state, &group_handle, true); - workspace_set_idx(state, 1, &workspace.handle); + workspace_set_idx(state, 1, idx, &workspace.handle); state.set_workspace_capabilities( &workspace.handle, [WorkspaceCapabilities::Activate].into_iter(), @@ -108,7 +113,7 @@ impl WorkspaceSet { WorkspaceAmount::Static(len) => (0..len) .map(|i| { let workspace = create_workspace(state, &group_handle, i == 0); - workspace_set_idx(state, i + 1, &workspace.handle); + workspace_set_idx(state, i + 1, idx, &workspace.handle); state.set_workspace_capabilities( &workspace.handle, [WorkspaceCapabilities::Activate].into_iter(), @@ -122,6 +127,7 @@ impl WorkspaceSet { active: 0, amount, group: group_handle, + idx, workspaces, } } @@ -192,7 +198,7 @@ impl WorkspaceSet { if keep.iter().any(|val| *val == false) { for (i, workspace) in self.workspaces.iter().enumerate() { - workspace_set_idx(&mut state, i as u8 + 1, &workspace.handle); + workspace_set_idx(&mut state, i as u8 + 1, self.idx, &workspace.handle); } } } @@ -240,6 +246,7 @@ impl WorkspaceSet { workspace_set_idx( &mut state, self.workspaces.len() as u8 + 1, + self.idx, &workspace.handle, ); state.set_workspace_capabilities( @@ -253,6 +260,13 @@ impl WorkspaceSet { } } } + + fn update_idx(&mut self, state: &mut WorkspaceUpdateGuard<'_, State>, idx: usize) { + self.idx = idx; + for (i, workspace) in self.workspaces.iter().enumerate() { + workspace_set_idx(state, i as u8, idx, &workspace.handle); + } + } } #[derive(Debug)] @@ -269,7 +283,7 @@ impl WorkspaceMode { ) -> WorkspaceMode { match config { crate::config::WorkspaceMode::Global => { - WorkspaceMode::Global(WorkspaceSet::new(state, amount)) + WorkspaceMode::Global(WorkspaceSet::new(state, amount, 0)) } crate::config::WorkspaceMode::OutputBound => { WorkspaceMode::OutputBound(HashMap::new(), amount) @@ -432,7 +446,10 @@ impl Shell { WorkspaceMode::OutputBound(sets, amount) => { // TODO: Restore previously assigned workspaces, if possible! if !sets.contains_key(output) { - sets.insert(output.clone(), WorkspaceSet::new(&mut state, *amount)); + sets.insert( + output.clone(), + WorkspaceSet::new(&mut state, *amount, sets.len()), + ); } for workspace in &mut sets.get_mut(output).unwrap().workspaces { workspace.map_output(output, (0, 0).into()); @@ -482,11 +499,6 @@ impl Shell { &workspace_handle, [WorkspaceCapabilities::Activate].into_iter(), ); - workspace_set_idx( - &mut state, - new_set.workspaces.len() as u8 + 1, - &workspace_handle, - ); workspace.handle = workspace_handle; // update mapping @@ -497,11 +509,14 @@ impl Shell { new_set.workspaces.push(workspace); } state.remove_workspace_group(set.group); - std::mem::drop(state); - self.refresh(); // cleans up excess of workspaces and empty workspaces } // if there is no output, we are going to quit anyway, just drop the workspace set } + for (i, set) in sets.values_mut().enumerate() { + set.update_idx(&mut state, i); + } + std::mem::drop(state); + self.refresh(); // cleans up excess of workspaces and empty workspaces } WorkspaceMode::Global(set) => { state.remove_group_output(&set.group, output); @@ -537,7 +552,7 @@ impl Shell { }; // in this case we have to merge our sets, preserving placing of windows as nicely as possible - let mut new_set = WorkspaceSet::new(&mut state, WorkspaceAmount::Static(0)); + let mut new_set = WorkspaceSet::new(&mut state, WorkspaceAmount::Static(0), 0); // lets construct an iterator of all the pairs of workspaces we have to merge // we first split of the part of the workspaces that contain the currently active one @@ -593,7 +608,7 @@ impl Shell { &workspace_handle, [WorkspaceCapabilities::Activate].into_iter(), ); - workspace_set_idx(&mut state, i as u8 + 1, &workspace_handle); + workspace_set_idx(&mut state, i as u8 + 1, 0, &workspace_handle); let mut new_workspace = Workspace::new(workspace_handle); for output in self.outputs.iter() { @@ -639,14 +654,14 @@ impl Shell { // split workspaces apart, preserving window positions relative to their outputs let mut sets = HashMap::new(); - for output in &self.outputs { + for (i, output) in self.outputs.iter().enumerate() { sets.insert( output.clone(), - WorkspaceSet::new(&mut state, WorkspaceAmount::Static(0)), + WorkspaceSet::new(&mut state, WorkspaceAmount::Static(0), i), ); } for (i, workspace) in set.workspaces.drain(..).enumerate() { - for output in &self.outputs { + for (idx, output) in self.outputs.iter().enumerate() { // copy over everything and then remove other outputs to preserve state let new_set = sets.get_mut(output).unwrap(); let new_workspace_handle = state.create_workspace(&new_set.group).unwrap(); @@ -654,7 +669,7 @@ impl Shell { &new_workspace_handle, [WorkspaceCapabilities::Activate].into_iter(), ); - workspace_set_idx(&mut state, i as u8 + 1, &new_workspace_handle); + workspace_set_idx(&mut state, i as u8 + 1, idx, &new_workspace_handle); let mut old_tiling_layer = workspace.tiling_layer.clone(); let mut new_floating_layer = FloatingLayout::new(); @@ -1086,8 +1101,9 @@ impl Shell { fn workspace_set_idx<'a>( state: &mut WorkspaceUpdateGuard<'a, State>, idx: u8, + output_pos: usize, handle: &WorkspaceHandle, ) { state.set_workspace_name(&handle, format!("{}", idx)); - state.set_workspace_coordinates(&handle, [Some(idx as u32), None, None]); + state.set_workspace_coordinates(&handle, [Some(idx as u32), Some(output_pos as u32), None]); } From b5ef2c5bdef6b1df4d76962399aae35ba81b9dab Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 22 Nov 2022 18:20:20 +0100 Subject: [PATCH 59/62] kms: Use recent average frame_time for delay --- src/backend/kms/mod.rs | 28 +++++-------- src/backend/render/mod.rs | 13 ++---- src/backend/winit.rs | 2 + src/backend/x11.rs | 2 + src/state.rs | 64 ++++++++++++++++++------------ src/wayland/handlers/screencopy.rs | 4 -- 6 files changed, 55 insertions(+), 58 deletions(-) diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 4b9d37ee..6652b632 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -1,13 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-only -#[cfg(feature = "debug")] -use crate::state::Fps; - use crate::{ backend::render, config::OutputConfig, shell::Shell, - state::{BackendData, ClientState, Common, Data}, + state::{BackendData, ClientState, Common, Data, Fps}, utils::prelude::*, wayland::{ handlers::screencopy::{PendingScreencopyBuffers, UserdataExt}, @@ -69,6 +66,8 @@ use session_fd::*; use socket::*; use super::render::{CursorMode, GlMultiRenderer}; +// for now we assume we need at least 3ms +const MIN_RENDER_TIME: Duration = Duration::from_millis(3); pub struct KmsState { devices: HashMap, @@ -107,7 +106,6 @@ pub struct Surface { pending: bool, dirty: bool, render_timer_token: Option, - #[cfg(feature = "debug")] fps: Fps, } @@ -381,7 +379,7 @@ impl State { let dispatcher = Dispatcher::new(drm, move |event, metadata, data: &mut Data| match event { DrmEvent::VBlank(crtc) => { - let rescheduled_output = + let rescheduled = if let Some(device) = data.state.backend.kms().devices.get_mut(&drm_node) { if let Some(surface) = device.surfaces.get_mut(&crtc) { #[cfg(feature = "debug")] @@ -427,7 +425,9 @@ impl State { } surface.pending = false; - surface.dirty.then(|| surface.output.clone()) + surface.dirty.then(|| { + (surface.output.clone(), surface.fps.avg_rendertime(5)) + }) } Some(Err(err)) => { slog_scope::warn!("Failed to submit frame: {}", err); @@ -442,7 +442,7 @@ impl State { None }; - if let Some(output) = rescheduled_output { + if let Some((output, avg_rendertime)) = rescheduled { let mut scheduled_sessions = data.state.workspace_session_for_output(&output); if let Some(sessions) = output.user_data().get::() @@ -452,15 +452,7 @@ impl State { .extend(sessions.borrow_mut().drain(..)); } - let output_refresh = match output.current_mode() { - Some(mode) => mode.refresh, - None => return, - }; - // TODO: Record rendering times and use sliding window to estimate duration for next render - let repaint_delay = Duration::from_secs_f64( - ((1000.0 / output_refresh as f64) * 0.6).max(0.003), // for now we assume we need at least 3ms - ); - + let repaint_delay = std::cmp::max(avg_rendertime, MIN_RENDER_TIME); if let Err(err) = data.state.backend.kms().schedule_render( &data.state.common.event_loop_handle, &output, @@ -781,7 +773,6 @@ impl Device { pending: false, dirty: false, render_timer_token: None, - #[cfg(feature = "debug")] fps: Fps::new(renderer.as_mut()), }; self.surfaces.insert(crtc, data); @@ -861,7 +852,6 @@ impl Surface { &self.output, CursorMode::All, screencopy.map(|sessions| (buffer, sessions)), - #[cfg(feature = "debug")] Some(&mut self.fps), ) { Ok((damage, states)) => { diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 709bca1b..58793018 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -1,10 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-only #[cfg(feature = "debug")] -use crate::{debug::fps_ui, state::Fps, utils::prelude::*}; +use crate::{debug::fps_ui, utils::prelude::*}; use crate::{ shell::{layout::floating::SeatMoveGrabState, CosmicMappedRenderElement}, - state::Common, + state::{Common, Fps}, wayland::{ handlers::{data_device::get_dnd_icon, screencopy::render_session}, protocols::{ @@ -134,7 +134,7 @@ pub fn render_output( output: &Output, cursor_mode: CursorMode, screencopy: Option<(Source, &[(ScreencopySession, BufferParams)])>, - #[cfg(feature = "debug")] fps: Option<&mut Fps>, + fps: Option<&mut Fps>, ) -> Result<(Option>>, RenderElementStates), RenderError> where R: Renderer @@ -165,7 +165,6 @@ where &handle, cursor_mode, screencopy, - #[cfg(feature = "debug")] fps, ) } @@ -181,7 +180,7 @@ pub fn render_workspace( handle: &WorkspaceHandle, mut cursor_mode: CursorMode, screencopy: Option<(Source, &[(ScreencopySession, BufferParams)])>, - #[cfg(feature = "debug")] mut fps: Option<&mut Fps>, + mut fps: Option<&mut Fps>, ) -> Result<(Option>>, RenderElementStates), RenderError> where R: Renderer @@ -200,7 +199,6 @@ where CosmicMappedRenderElement: RenderElement, Source: Clone, { - #[cfg(feature = "debug")] if let Some(ref mut fps) = fps { fps.start(); } @@ -255,7 +253,6 @@ where .map(Into::into), ); - #[cfg(feature = "debug")] if let Some(fps) = fps.as_mut() { fps.elements(); } @@ -263,7 +260,6 @@ where renderer.bind(target).map_err(RenderError::Rendering)?; let res = damage_tracker.render_output(renderer, age, &elements, CLEAR_COLOR, None); - #[cfg(feature = "debug")] if let Some(fps) = fps.as_mut() { fps.render(); } @@ -311,7 +307,6 @@ where } } } - #[cfg(feature = "debug")] if let Some(fps) = fps.as_mut() { fps.screencopy(); } diff --git a/src/backend/winit.rs b/src/backend/winit.rs index b8bf4cb1..0e4a259b 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -65,6 +65,8 @@ impl WinitState { } else { None }, + #[cfg(not(feature = "debug"))] + None, #[cfg(feature = "debug")] Some(&mut self.fps), ) { diff --git a/src/backend/x11.rs b/src/backend/x11.rs index a993418b..6d54691e 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -213,6 +213,8 @@ impl Surface { } else { None }, + #[cfg(not(feature = "debug"))] + None, #[cfg(feature = "debug")] Some(&mut self.fps), ) { diff --git a/src/state.rs b/src/state.rs index a1a7d6a4..9deee434 100644 --- a/src/state.rs +++ b/src/state.rs @@ -14,12 +14,13 @@ use crate::{ }, }; use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_manager_v1::CursorMode; -#[cfg(feature = "debug")] -use smithay::backend::renderer::glow::GlowRenderer; use smithay::{ backend::{ drm::DrmNode, - renderer::element::{default_primary_scanout_output_compare, RenderElementStates}, + renderer::{ + element::{default_primary_scanout_output_compare, RenderElementStates}, + glow::GlowRenderer, + }, }, desktop::utils::{ surface_presentation_feedback_flags_from_states, surface_primary_scanout_output, @@ -35,7 +36,7 @@ use smithay::{ Display, DisplayHandle, }, }, - utils::{Clock, Monotonic, Rectangle}, + utils::{Clock, Monotonic}, wayland::{ compositor::CompositorState, data_device::DataDeviceState, dmabuf::DmabufState, keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitState, output::OutputManagerState, @@ -45,7 +46,6 @@ use smithay::{ }; use std::{cell::RefCell, ffi::OsString, time::Duration}; -#[cfg(feature = "debug")] use std::{collections::VecDeque, time::Instant}; pub struct ClientState { @@ -432,14 +432,13 @@ pub struct Egui { pub active: bool, } -#[cfg(feature = "debug")] pub struct Fps { + #[cfg(feature = "debug")] pub state: smithay_egui::EguiState, pending_frame: Option, pub frames: VecDeque, } -#[cfg(feature = "debug")] #[derive(Debug)] struct PendingFrame { start: Instant, @@ -449,7 +448,6 @@ struct PendingFrame { duration_displayed: Option, } -#[cfg(feature = "debug")] #[derive(Debug)] pub struct Frame { pub start: Instant, @@ -459,8 +457,11 @@ pub struct Frame { pub duration_displayed: Duration, } -#[cfg(feature = "debug")] impl Frame { + fn render_time(&self) -> Duration { + self.duration_elements + self.duration_render + } + fn frame_time(&self) -> Duration { self.duration_elements + self.duration_render @@ -475,7 +476,6 @@ impl Frame { } } -#[cfg(feature = "debug")] impl From for Frame { fn from(pending: PendingFrame) -> Self { Frame { @@ -488,7 +488,6 @@ impl From for Frame { } } -#[cfg(feature = "debug")] impl Fps { const WINDOW_SIZE: usize = 360; @@ -582,6 +581,15 @@ impl Fps { self.frames.iter().map(|f| f.frame_time()).sum::() / (self.frames.len() as u32) } + pub fn avg_rendertime(&self, window: usize) -> Duration { + self.frames + .iter() + .take(window) + .map(|f| f.render_time()) + .sum::() + / window as u32 + } + pub fn avg_fps(&self) -> f64 { if self.frames.is_empty() { return 0.0; @@ -597,33 +605,38 @@ impl Fps { } } +#[cfg(feature = "debug")] static INTEL_LOGO: &'static [u8] = include_bytes!("../resources/icons/intel.svg"); +#[cfg(feature = "debug")] static AMD_LOGO: &'static [u8] = include_bytes!("../resources/icons/amd.svg"); +#[cfg(feature = "debug")] static NVIDIA_LOGO: &'static [u8] = include_bytes!("../resources/icons/nvidia.svg"); -#[cfg(feature = "debug")] impl Fps { - pub fn new(renderer: &mut GlowRenderer) -> Fps { + pub fn new(_renderer: &mut GlowRenderer) -> Fps { + #[cfg(feature = "debug")] let state = { - let state = - smithay_egui::EguiState::new(Rectangle::from_loc_and_size((0, 0), (400, 800))); + let state = smithay_egui::EguiState::new(smithay::utils::Rectangle::from_loc_and_size( + (0, 0), + (400, 800), + )); let mut visuals: egui::style::Visuals = Default::default(); visuals.window_shadow.extrusion = 0.0; state.context().set_visuals(visuals); state + .load_svg(_renderer, String::from("intel"), INTEL_LOGO) + .unwrap(); + state + .load_svg(_renderer, String::from("amd"), AMD_LOGO) + .unwrap(); + state + .load_svg(_renderer, String::from("nvidia"), NVIDIA_LOGO) + .unwrap(); + state }; - state - .load_svg(renderer, String::from("intel"), INTEL_LOGO) - .unwrap(); - state - .load_svg(renderer, String::from("amd"), AMD_LOGO) - .unwrap(); - state - .load_svg(renderer, String::from("nvidia"), NVIDIA_LOGO) - .unwrap(); - Fps { + #[cfg(feature = "debug")] state, pending_frame: None, frames: VecDeque::with_capacity(Fps::WINDOW_SIZE + 1), @@ -631,7 +644,6 @@ impl Fps { } } -#[cfg(feature = "debug")] pub fn avg_fps<'a>(iter: impl Iterator) -> f64 { let sum_secs = iter.map(|d| d.as_secs_f64()).sum::(); 1.0 / (sum_secs / Fps::WINDOW_SIZE as f64) diff --git a/src/wayland/handlers/screencopy.rs b/src/wayland/handlers/screencopy.rs index ba065160..d16d65dd 100644 --- a/src/wayland/handlers/screencopy.rs +++ b/src/wayland/handlers/screencopy.rs @@ -619,7 +619,6 @@ pub fn render_output_to_buffer( &output, cursor_mode, None, - #[cfg(feature = "debug")] None, ) } else { @@ -636,7 +635,6 @@ pub fn render_output_to_buffer( &output, cursor_mode, None, - #[cfg(feature = "debug")] None, ) } @@ -700,7 +698,6 @@ pub fn render_workspace_to_buffer( handle, cursor_mode, None, - #[cfg(feature = "debug")] None, ) } else { @@ -718,7 +715,6 @@ pub fn render_workspace_to_buffer( handle, cursor_mode, None, - #[cfg(feature = "debug")] None, ) } From c25f2ed44766a671b5a078e2ff5786463728b2ce Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 22 Nov 2022 18:21:41 +0100 Subject: [PATCH 60/62] deps: Use pinned smithay-egui and disable debug by default --- Cargo.lock | 2 +- Cargo.toml | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a524fa81..80dce136 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1855,7 +1855,7 @@ dependencies = [ [[package]] name = "smithay-egui" version = "0.1.0" -source = "git+https://github.com/Smithay/smithay-egui.git?branch=feature/image#9fcb589d5c99ac8143f0a967504cdbf691d6cea4" +source = "git+https://github.com/Smithay/smithay-egui.git?rev=9fe1fa5e01#9fe1fa5e01dc39c145f036564e971a50f84864ff" dependencies = [ "cgmath", "egui", diff --git a/Cargo.toml b/Cargo.toml index 064b52e4..f4f6b11e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,13 +42,12 @@ features = ["backend_drm", "backend_gbm", "backend_egl", "backend_libinput", "ba [dependencies.smithay-egui] git = "https://github.com/Smithay/smithay-egui.git" -#rev = "ee25d401ac" -branch = "feature/image" +rev = "9fe1fa5e01" features = ["svg"] optional = true [features] -default = ["debug"] +default = [] debug = ["egui", "smithay-egui"] [profile.dev] From 4a9afd05724a0770b191a0d95fe550c885eff823 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 22 Nov 2022 19:52:13 +0100 Subject: [PATCH 61/62] shell: Fix render positions for global workspace mode --- src/backend/render/mod.rs | 4 +--- src/shell/layout/tiling/mod.rs | 26 ++++++++++++++++++++------ src/shell/mod.rs | 14 -------------- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 58793018..8ea52693 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -82,9 +82,7 @@ where Some(ptr) => ptr, None => continue, }; - let location = state - .shell - .map_global_to_space(pointer.current_location().to_i32_round(), output); + let location = pointer.current_location() - output.current_location().to_f64(); if mode != CursorMode::None { elements.extend( diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index b59e0cae..0ba18ad3 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -1251,14 +1251,28 @@ impl TilingLayout { } Ok(self - .mapped() - .flat_map(|(o, mapped, loc)| { - if o == output { - Some((mapped, loc)) - } else { - None + .trees + .iter() + .flat_map(|(output_data, tree)| { + if &output_data.output != output { + return None; } + let root = tree.root_node_id()?; + Some( + tree.traverse_pre_order(root) + .unwrap() + .filter(|node| node.data().is_mapped(None)) + .map(|node| match node.data() { + Data::Mapped { + mapped, + last_geometry, + .. + } => (mapped, last_geometry.loc), + _ => unreachable!(), + }), + ) }) + .flatten() .flat_map(|(mapped, loc)| { AsRenderElements::::render_elements::>( mapped, diff --git a/src/shell/mod.rs b/src/shell/mod.rs index b8dbb8f5..1bd005e1 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -903,20 +903,6 @@ impl Shell { } } - pub fn map_space_to_global( - &self, - space_loc: impl Into>, - output: &Output, - ) -> Point { - match self.workspaces { - WorkspaceMode::Global(_) => space_loc.into(), - WorkspaceMode::OutputBound(_, _) => { - let p = space_loc.into().to_f64() + output.current_location().to_f64(); - (C::from_f64(p.x), C::from_f64(p.y)).into() - } - } - } - pub fn refresh(&mut self) { self.popups.cleanup(); From f4476b10f22332c89e4d1e915a05afff7c7003ec Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 23 Nov 2022 16:00:33 +0100 Subject: [PATCH 62/62] shell: Fix global+static mode --- src/input/mod.rs | 1 - src/shell/mod.rs | 19 +++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/input/mod.rs b/src/input/mod.rs index 88b96afb..51657b48 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -693,7 +693,6 @@ impl State { .cloned() { let idx = self.common.shell.workspaces.active_num(&prev_output); - seat.set_active_output(&prev_output); if let Some(new_pos) = self.common.shell.activate(&prev_output, idx) { seat.set_active_output(&prev_output); if let Some(ptr) = seat.get_pointer() { diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 1bd005e1..7cad816a 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use std::collections::HashMap; +use std::{collections::HashMap, cell::RefCell}; use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::State as WState; use smithay::{ @@ -20,7 +20,7 @@ use smithay::{ }; use crate::{ - config::{Config, WorkspaceMode as ConfigMode}, + config::{Config, WorkspaceMode as ConfigMode, OutputConfig}, utils::prelude::*, wayland::protocols::{ toplevel_info::ToplevelInfoState, @@ -459,7 +459,13 @@ impl Shell { // TODO: Restore any window positions from previous outputs ??? state.add_group_output(&set.group, output); for workspace in &mut set.workspaces { - workspace.map_output(output, output.current_location()); + workspace.map_output(output, output.user_data() + .get::>() + .unwrap() + .borrow() + .position + .into() + ); } } } @@ -532,7 +538,12 @@ impl Shell { if let WorkspaceMode::Global(set) = &mut self.workspaces { for workspace in &mut set.workspaces { for output in self.outputs.iter() { - workspace.map_output(output, output.current_location()); + workspace.map_output(output, output.user_data() + .get::>() + .unwrap() + .borrow() + .position + .into()); } } }