From 50d6dc3d21c5c373d81f294d9b869a76205840a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Mon, 21 Oct 2024 15:26:54 +0200 Subject: [PATCH 01/60] fix(menu): use corner radius from theme --- src/shell/grabs/menu/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shell/grabs/menu/mod.rs b/src/shell/grabs/menu/mod.rs index c0a95019..83cc70f1 100644 --- a/src/shell/grabs/menu/mod.rs +++ b/src/shell/grabs/menu/mod.rs @@ -441,7 +441,7 @@ impl Program for ContextMenu { text_color: Some(component.on.into()), background: Some(Background::Color(component.base.into())), border: Border { - radius: 8.0.into(), + radius: cosmic.radius_s().into(), width: 1.0, color: component.divider.into(), }, From 540ed3d170ef97f9fc26bd3fbfdbc4f265e96034 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 11 Oct 2024 12:00:47 -0700 Subject: [PATCH 02/60] Use `to_ne_bytes()` instead of unsound `Vec::from_raw_parts` `from_raw_parts()` is only sound if the alignment is the same, so it can't be used to covert to a `Vec` from a type with a greater alignment. This isn't hard to avoid, luckily. --- src/wayland/protocols/screencopy.rs | 36 +++++-------- src/wayland/protocols/toplevel_info.rs | 12 ++--- src/wayland/protocols/toplevel_management.rs | 16 +++--- src/wayland/protocols/workspace.rs | 57 +++++++------------- 4 files changed, 43 insertions(+), 78 deletions(-) diff --git a/src/wayland/protocols/screencopy.rs b/src/wayland/protocols/screencopy.rs index 5345c8e7..02fdc213 100644 --- a/src/wayland/protocols/screencopy.rs +++ b/src/wayland/protocols/screencopy.rs @@ -139,14 +139,10 @@ impl Session { let node = Vec::from(dma.node.dev_id().to_ne_bytes()); self.obj.dmabuf_device(node); for (fmt, modifiers) in &dma.formats { - let mut modifiers = modifiers.clone(); - let modifiers: Vec = { - let ptr = modifiers.as_mut_ptr() as *mut u8; - let len = modifiers.len() * 4; - let cap = modifiers.capacity() * 4; - std::mem::forget(modifiers); - unsafe { Vec::from_raw_parts(ptr, len, cap) } - }; + let modifiers = modifiers + .iter() + .flat_map(|modifier| u64::from(*modifier).to_ne_bytes()) + .collect::>(); self.obj.dmabuf_format(*fmt as u32, modifiers); } } @@ -249,14 +245,10 @@ impl CursorSession { let node = Vec::from(dma.node.dev_id().to_ne_bytes()); session_obj.dmabuf_device(node); for (fmt, modifiers) in &dma.formats { - let mut modifiers = modifiers.clone(); - let modifiers: Vec = { - let ptr = modifiers.as_mut_ptr() as *mut u8; - let len = modifiers.len() * 4; - let cap = modifiers.capacity() * 4; - std::mem::forget(modifiers); - unsafe { Vec::from_raw_parts(ptr, len, cap) } - }; + let modifiers = modifiers + .iter() + .flat_map(|modifier| u64::from(*modifier).to_ne_bytes()) + .collect::>(); session_obj.dmabuf_format(*fmt as u32, modifiers); } } @@ -747,14 +739,10 @@ where let node = Vec::from(dma.node.dev_id().to_ne_bytes()); session.dmabuf_device(node); for (fmt, modifiers) in &dma.formats { - let mut modifiers = modifiers.clone(); - let modifiers: Vec = { - let ptr = modifiers.as_mut_ptr() as *mut u8; - let len = modifiers.len() * 4; - let cap = modifiers.capacity() * 4; - std::mem::forget(modifiers); - unsafe { Vec::from_raw_parts(ptr, len, cap) } - }; + let modifiers = modifiers + .iter() + .flat_map(|modifier| u64::from(*modifier).to_ne_bytes()) + .collect::>(); session.dmabuf_format(*fmt as u32, modifiers); } } diff --git a/src/wayland/protocols/toplevel_info.rs b/src/wayland/protocols/toplevel_info.rs index 9eb680b1..48b9e483 100644 --- a/src/wayland/protocols/toplevel_info.rs +++ b/src/wayland/protocols/toplevel_info.rs @@ -509,14 +509,10 @@ where } handle_state.states = states.clone(); - let states: Vec = { - let ratio = std::mem::size_of::() / std::mem::size_of::(); - let ptr = states.as_mut_ptr() as *mut u8; - let len = states.len() * ratio; - let cap = states.capacity() * ratio; - std::mem::forget(states); - unsafe { Vec::from_raw_parts(ptr, len, cap) } - }; + let states = states + .iter() + .flat_map(|state| (*state as u32).to_ne_bytes()) + .collect::>(); instance.state(states); changed = true; } diff --git a/src/wayland/protocols/toplevel_management.rs b/src/wayland/protocols/toplevel_management.rs index efd8d325..6755334c 100644 --- a/src/wayland/protocols/toplevel_management.rs +++ b/src/wayland/protocols/toplevel_management.rs @@ -151,15 +151,13 @@ where data_init: &mut DataInit<'_, D>, ) { let instance = data_init.init(resource, ()); - let capabilities = { - let mut caps = state.toplevel_management_state().capabilities.clone(); - let ratio = std::mem::size_of::() / std::mem::size_of::(); - let ptr = caps.as_mut_ptr() as *mut u8; - let len = caps.len() * ratio; - let cap = caps.capacity() * ratio; - std::mem::forget(caps); - unsafe { Vec::from_raw_parts(ptr, len, cap) } - }; + let capabilities = state + .toplevel_management_state() + .capabilities + .iter() + .flat_map(|cap| (*cap as u32).to_ne_bytes()) + .collect::>(); + instance.capabilities(capabilities); state.toplevel_management_state().instances.push(instance); } diff --git a/src/wayland/protocols/workspace.rs b/src/wayland/protocols/workspace.rs index 60b1380c..6d661ad7 100644 --- a/src/wayland/protocols/workspace.rs +++ b/src/wayland/protocols/workspace.rs @@ -929,15 +929,11 @@ where } if handle_state.capabilities != group.capabilities { - let caps: Vec = { - let mut caps = group.capabilities.clone(); - let ratio = std::mem::size_of::() / std::mem::size_of::(); - let ptr = caps.as_mut_ptr() as *mut u8; - let len = caps.len() * ratio; - let cap = caps.capacity() * ratio; - std::mem::forget(caps); - unsafe { Vec::from_raw_parts(ptr, len, cap) } - }; + let caps = group + .capabilities + .iter() + .flat_map(|cap| (*cap as u32).to_ne_bytes()) + .collect::>(); instance.capabilities(caps); handle_state.capabilities = group.capabilities.clone(); changed = true; @@ -1005,44 +1001,31 @@ where changed = true; } if handle_state.coordinates != workspace.coordinates { - let coords: Vec = { - let mut coords = workspace.coordinates.clone(); - let ratio = std::mem::size_of::() / std::mem::size_of::(); - let ptr = coords.as_mut_ptr() as *mut u8; - let len = coords.len() * ratio; - let cap = coords.capacity() * ratio; - std::mem::forget(coords); - unsafe { Vec::from_raw_parts(ptr, len, cap) } - }; + let coords = workspace + .coordinates + .iter() + .flat_map(|coord| coord.to_ne_bytes()) + .collect::>(); instance.coordinates(coords); handle_state.coordinates = workspace.coordinates.clone(); changed = true; } if handle_state.capabilities != workspace.capabilities { - let caps: Vec = { - let mut caps = workspace.capabilities.clone(); - let ratio = std::mem::size_of::() / std::mem::size_of::(); - let ptr = caps.as_mut_ptr() as *mut u8; - let len = caps.len() * ratio; - let cap = caps.capacity() * ratio; - std::mem::forget(caps); - unsafe { Vec::from_raw_parts(ptr, len, cap) } - }; + let caps = workspace + .capabilities + .iter() + .flat_map(|cap| (*cap as u32).to_ne_bytes()) + .collect::>(); instance.capabilities(caps); handle_state.capabilities = workspace.capabilities.clone(); changed = true; } if handle_state.states != workspace.states { - let states: Vec = { - let mut states = workspace.states.iter().cloned().collect::>(); - let ratio = std::mem::size_of::() - / std::mem::size_of::(); - let ptr = states.as_mut_ptr() as *mut u8; - let len = states.len() * ratio; - let cap = states.capacity() * ratio; - std::mem::forget(states); - unsafe { Vec::from_raw_parts(ptr, len, cap) } - }; + let states = workspace + .states + .iter() + .flat_map(|state| (*state as u32).to_ne_bytes()) + .collect::>(); instance.state(states); handle_state.states = workspace.states.clone(); changed = true; From 6fc36cb00e4f1be85989df4b31de02123e2de353 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 11 Oct 2024 12:20:06 -0700 Subject: [PATCH 03/60] Fix spelling of `prefers` --- src/shell/mod.rs | 6 +++--- src/shell/workspace.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 07acdee0..46136077 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -668,11 +668,11 @@ impl Workspaces { self.sets.insert(output.clone(), set); let mut moved_workspaces = Vec::new(); for set in self.sets.values_mut() { - let (preferrs, doesnt) = set + let (prefers, doesnt) = set .workspaces .drain(..) - .partition(|w| w.preferrs_output(output)); - moved_workspaces.extend(preferrs); + .partition(|w| w.prefers_output(output)); + moved_workspaces.extend(prefers); set.workspaces = doesnt; if set.workspaces.is_empty() { set.add_empty_workspace(workspace_state); diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 44af522e..8da5c2f5 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -374,7 +374,7 @@ impl Workspace { self.output = output.clone(); } - pub fn preferrs_output(&self, output: &Output) -> bool { + pub fn prefers_output(&self, output: &Output) -> bool { self.output_stack.contains(&output.name()) } From 400a7752fdc4a95c7f9c62ea7efff1c85f2446e8 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 11 Oct 2024 12:33:09 -0700 Subject: [PATCH 04/60] When moving workspaces on output remove, don't movve empty workspace This resulted in a somewhat weird effect, where the empty workspace at the end on the monitor has an `output_stack` with the other output, so a window created there is moved back to to second output when it is re-connected. This seems unexpected. --- src/shell/mod.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 46136077..39c62e2e 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -729,13 +729,17 @@ impl Workspaces { let new_set = self.sets.get_mut(&new_output).unwrap(); let workspace_group = new_set.group; for mut workspace in set.workspaces.into_iter() { - // update workspace protocol state - move_workspace_to_group(&mut workspace, &workspace_group, workspace_state); + if workspace.is_empty() { + workspace_state.remove_workspace(workspace.handle); + } else { + // update workspace protocol state + move_workspace_to_group(&mut workspace, &workspace_group, workspace_state); - // update mapping - workspace.set_output(&new_output); - workspace.refresh(xdg_activation_state); - new_set.workspaces.push(workspace); + // update mapping + workspace.set_output(&new_output); + workspace.refresh(xdg_activation_state); + new_set.workspaces.push(workspace); + } } for window in set.sticky_layer.mapped() { From c8ebac354c7788c4edfa50953c99e5aae5acfb9b Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 11 Oct 2024 13:05:51 -0700 Subject: [PATCH 05/60] Preserve active workspace when workspaces are moved back to another output Instead of preserving the active workspace index, which may not match if a workspace before it has been moved back to another output. --- src/shell/mod.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 39c62e2e..998af39f 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -668,6 +668,7 @@ impl Workspaces { self.sets.insert(output.clone(), set); let mut moved_workspaces = Vec::new(); for set in self.sets.values_mut() { + let active_handle = set.workspaces[set.active].handle; let (prefers, doesnt) = set .workspaces .drain(..) @@ -677,7 +678,11 @@ impl Workspaces { if set.workspaces.is_empty() { set.add_empty_workspace(workspace_state); } - set.active = set.active.min(set.workspaces.len() - 1); + set.active = set + .workspaces + .iter() + .position(|w| w.handle == active_handle) + .unwrap_or(set.workspaces.len() - 1); } { let set = self.sets.get_mut(output).unwrap(); From fb2631adce95276a17081f11d8738b9a5ef01f16 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 11 Oct 2024 13:09:44 -0700 Subject: [PATCH 06/60] Fix workspace indices when workspace is moved to another output Previously, workspace numbering could end up with numbers skipped, because an intermediate workspace was moved. --- src/shell/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 998af39f..c3a69295 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -678,6 +678,9 @@ impl Workspaces { if set.workspaces.is_empty() { set.add_empty_workspace(workspace_state); } + for (i, workspace) in set.workspaces.iter_mut().enumerate() { + workspace_set_idx(workspace_state, i as u8 + 1, set.idx, &workspace.handle); + } set.active = set .workspaces .iter() From 229c794aadfd2a7553f75a00defd5e8354676e6b Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 11 Oct 2024 13:18:06 -0700 Subject: [PATCH 07/60] On adding output add `set` after processing other sets Maybe there's some reason for this, but I don't think the first loop needs to handle the new set. I do not expect this to change behavior. --- src/shell/mod.rs | 55 ++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index c3a69295..7b8eb84f 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -647,7 +647,7 @@ impl Workspaces { return; } - let set = self + let mut set = self .backup_set .take() .map(|mut set| { @@ -665,43 +665,48 @@ impl Workspaces { }); workspace_state.add_group_output(&set.group, &output); - self.sets.insert(output.clone(), set); + // Remove workspaces that prefer this output from other sets let mut moved_workspaces = Vec::new(); - for set in self.sets.values_mut() { - let active_handle = set.workspaces[set.active].handle; - let (prefers, doesnt) = set + for other_set in self.sets.values_mut() { + let active_handle = other_set.workspaces[set.active].handle; + let (prefers, doesnt) = other_set .workspaces .drain(..) .partition(|w| w.prefers_output(output)); moved_workspaces.extend(prefers); - set.workspaces = doesnt; - if set.workspaces.is_empty() { - set.add_empty_workspace(workspace_state); + other_set.workspaces = doesnt; + if other_set.workspaces.is_empty() { + other_set.add_empty_workspace(workspace_state); } - for (i, workspace) in set.workspaces.iter_mut().enumerate() { - workspace_set_idx(workspace_state, i as u8 + 1, set.idx, &workspace.handle); + for (i, workspace) in other_set.workspaces.iter_mut().enumerate() { + workspace_set_idx( + workspace_state, + i as u8 + 1, + other_set.idx, + &workspace.handle, + ); } - set.active = set + other_set.active = other_set .workspaces .iter() .position(|w| w.handle == active_handle) - .unwrap_or(set.workspaces.len() - 1); + .unwrap_or(other_set.workspaces.len() - 1); } - { - let set = self.sets.get_mut(output).unwrap(); - for workspace in &mut moved_workspaces { - move_workspace_to_group(workspace, &set.group, workspace_state); - } - set.workspaces.extend(moved_workspaces); - for (i, workspace) in set.workspaces.iter_mut().enumerate() { - workspace.set_output(output); - workspace.refresh(xdg_activation_state); - workspace_set_idx(workspace_state, i as u8 + 1, set.idx, &workspace.handle); - if i == set.active { - workspace_state.add_workspace_state(&workspace.handle, WState::Active); - } + + // Add `moved_workspaces` to set, and update output and index of workspaces + for workspace in &mut moved_workspaces { + move_workspace_to_group(workspace, &set.group, workspace_state); + } + set.workspaces.extend(moved_workspaces); + for (i, workspace) in set.workspaces.iter_mut().enumerate() { + workspace.set_output(output); + workspace.refresh(xdg_activation_state); + workspace_set_idx(workspace_state, i as u8 + 1, set.idx, &workspace.handle); + if i == set.active { + workspace_state.add_workspace_state(&workspace.handle, WState::Active); } } + self.sets.insert(output.clone(), set); } pub fn remove_output<'a>( From 2497992d319ad43f190044f7a7c5ebd0b5f04b2a Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 11 Oct 2024 13:26:11 -0700 Subject: [PATCH 08/60] Don't create initial empty workspace when there are other workspaces `WorkspaceSet::new()` now creates a set with no workspaces, instead of one with a single workspace. --- src/shell/mod.rs | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 7b8eb84f..ca5ec2f8 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -419,22 +419,6 @@ impl WorkspaceSet { theme: cosmic::Theme, ) -> WorkspaceSet { let group_handle = state.create_workspace_group(); - let workspaces = { - let workspace = create_workspace( - state, - output, - &group_handle, - true, - tiling_enabled, - theme.clone(), - ); - workspace_set_idx(state, 1, idx, &workspace.handle); - state.set_workspace_capabilities( - &workspace.handle, - [WorkspaceCapabilities::Activate].into_iter(), - ); - vec![workspace] - }; let sticky_layer = FloatingLayout::new(theme.clone(), output); WorkspaceSet { @@ -446,7 +430,7 @@ impl WorkspaceSet { theme, sticky_layer, minimized_windows: Vec::new(), - workspaces, + workspaces: Vec::new(), output: output.clone(), } } @@ -698,6 +682,9 @@ impl Workspaces { move_workspace_to_group(workspace, &set.group, workspace_state); } set.workspaces.extend(moved_workspaces); + if set.workspaces.is_empty() { + set.add_empty_workspace(workspace_state); + } for (i, workspace) in set.workspaces.iter_mut().enumerate() { workspace.set_output(output); workspace.refresh(xdg_activation_state); From 140d870e7bf26829c13c2f50e680e8f396b2fc2e Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 27 Sep 2024 23:39:08 +0200 Subject: [PATCH 09/60] focus: Introduce render_input_order --- src/shell/focus/mod.rs | 2 + src/shell/focus/order.rs | 374 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 376 insertions(+) create mode 100644 src/shell/focus/order.rs diff --git a/src/shell/focus/mod.rs b/src/shell/focus/mod.rs index e2038375..a3b855a3 100644 --- a/src/shell/focus/mod.rs +++ b/src/shell/focus/mod.rs @@ -21,10 +21,12 @@ use std::{borrow::Cow, mem, sync::Mutex}; use tracing::{debug, trace}; +pub use self::order::{render_input_order, Stage}; use self::target::{KeyboardFocusTarget, WindowGroup}; use super::{grabs::SeatMoveGrabState, layout::floating::FloatingLayout, SeatExt}; +mod order; pub mod target; pub struct FocusStack<'a>(pub(super) Option<&'a IndexSet>); diff --git a/src/shell/focus/order.rs b/src/shell/focus/order.rs new file mode 100644 index 00000000..f035d908 --- /dev/null +++ b/src/shell/focus/order.rs @@ -0,0 +1,374 @@ +use std::{ops::ControlFlow, time::Instant}; + +use cosmic_comp_config::workspace::WorkspaceLayout; +use keyframe::{ease, functions::EaseInOutCubic}; +use smithay::{ + desktop::{layer_map_for_output, LayerSurface, PopupKind, PopupManager}, + output::{Output, OutputNoMode}, + utils::{Logical, Point}, + wayland::{session_lock::LockSurface, shell::wlr_layer::Layer}, + xwayland::X11Surface, +}; + +use crate::{ + backend::render::ElementFilter, + shell::{ + layout::{floating::FloatingLayout, tiling::ANIMATION_DURATION}, + Shell, Workspace, WorkspaceDelta, + }, + utils::{geometry::*, prelude::OutputExt, quirks::WORKSPACE_OVERVIEW_NAMESPACE}, + wayland::protocols::workspace::WorkspaceHandle, +}; + +pub enum Stage<'a> { + SessionLock(Option<&'a LockSurface>), + LayerPopup { + layer: LayerSurface, + popup: &'a PopupKind, + location: Point, + }, + LayerSurface { + layer: LayerSurface, + location: Point, + }, + OverrideRedirect { + surface: &'a X11Surface, + location: Point, + }, + StickyPopups(&'a FloatingLayout), + Sticky(&'a FloatingLayout), + WorkspacePopups { + workspace: &'a Workspace, + offset: Point, + }, + Workspace { + workspace: &'a Workspace, + offset: Point, + }, +} + +pub fn render_input_order( + shell: &Shell, + output: &Output, + previous: Option<(WorkspaceHandle, usize, WorkspaceDelta)>, + current: (WorkspaceHandle, usize), + element_filter: ElementFilter, + callback: impl FnMut(Stage) -> ControlFlow, ()>, +) -> Result { + match render_input_order_internal(shell, output, previous, current, element_filter, callback) { + ControlFlow::Break(result) => result, + ControlFlow::Continue(_) => Ok(R::default()), + } +} + +fn render_input_order_internal( + shell: &Shell, + output: &Output, + previous: Option<(WorkspaceHandle, usize, WorkspaceDelta)>, + current: (WorkspaceHandle, usize), + element_filter: ElementFilter, + mut callback: impl FnMut(Stage) -> ControlFlow, ()>, +) -> ControlFlow, ()> { + // Session Lock + if let Some(session_lock) = &shell.session_lock { + return callback(Stage::SessionLock(session_lock.surfaces.get(output))); + } + + // Overlay-level layer shell + // overlay is above everything + for (layer, popup, location) in layer_popups(output, Layer::Overlay, element_filter) { + callback(Stage::LayerPopup { + layer, + popup: &popup, + location, + })?; + } + for (layer, location) in layer_surfaces(output, Layer::Overlay, element_filter) { + callback(Stage::LayerSurface { layer, location })?; + } + + // calculate a bunch of stuff for workspace transitions + + let Some(set) = shell.workspaces.sets.get(output) else { + return ControlFlow::Break(Err(OutputNoMode)); + }; + let Some(workspace) = set.workspaces.iter().find(|w| w.handle == current.0) else { + return ControlFlow::Break(Err(OutputNoMode)); + }; + let output_size = output.geometry().size; + let has_fullscreen = workspace + .fullscreen + .as_ref() + .filter(|f| !f.is_animating()) + .is_some(); + + let (previous, current_offset) = match previous.as_ref() { + Some((previous, previous_idx, start)) => { + let layout = shell.workspaces.layout; + + let Some(workspace) = shell.workspaces.space_for_handle(&previous) else { + return ControlFlow::Break(Err(OutputNoMode)); + }; + let has_fullscreen = workspace.fullscreen.is_some(); + + let percentage = match start { + WorkspaceDelta::Shortcut(st) => ease( + EaseInOutCubic, + 0.0, + 1.0, + Instant::now().duration_since(*st).as_millis() as f32 + / ANIMATION_DURATION.as_millis() as f32, + ), + WorkspaceDelta::Gesture(prog) => *prog as f32, + WorkspaceDelta::GestureEnd(st, spring) => { + (spring.value_at(Instant::now().duration_since(*st)) as f32).clamp(0.0, 1.0) + } + }; + + let offset = Point::::from(match (layout, *previous_idx < current.1) { + (WorkspaceLayout::Vertical, true) => { + (0, (-output_size.h as f32 * percentage).round() as i32) + } + (WorkspaceLayout::Vertical, false) => { + (0, (output_size.h as f32 * percentage).round() as i32) + } + (WorkspaceLayout::Horizontal, true) => { + ((-output_size.w as f32 * percentage).round() as i32, 0) + } + (WorkspaceLayout::Horizontal, false) => { + ((output_size.w as f32 * percentage).round() as i32, 0) + } + }); + + ( + Some((previous, has_fullscreen, offset)), + Point::::from(match (layout, *previous_idx < current.1) { + (WorkspaceLayout::Vertical, true) => (0, output_size.h + offset.y), + (WorkspaceLayout::Vertical, false) => (0, -(output_size.h - offset.y)), + (WorkspaceLayout::Horizontal, true) => (output_size.w + offset.x, 0), + (WorkspaceLayout::Horizontal, false) => (-(output_size.w - offset.x), 0), + }), + ) + } + None => (None, Point::default()), + }; + + // Top-level layer shell popups + if !has_fullscreen { + for (layer, popup, location) in layer_popups(output, Layer::Top, element_filter) { + callback(Stage::LayerPopup { + layer, + popup: &popup, + location, + })?; + } + } + + if element_filter != ElementFilter::LayerShellOnly { + // overlay redirect windows + // they need to be over sticky windows, because they could be popups of sticky windows, + // and we can't differenciate that. + for (surface, location) in shell + .override_redirect_windows + .iter() + .filter(|or| { + (*or) + .geometry() + .as_global() + .intersection(output.geometry()) + .is_some() + }) + .map(|or| (or, or.geometry().loc.as_global())) + { + callback(Stage::OverrideRedirect { surface, location })?; + } + + // sticky window popups + if !has_fullscreen { + callback(Stage::StickyPopups(&set.sticky_layer))?; + } + } + + if element_filter != ElementFilter::LayerShellOnly { + // previous workspace popups + if let Some((previous_handle, _, offset)) = previous.as_ref() { + let Some(workspace) = shell.workspaces.space_for_handle(previous_handle) else { + return ControlFlow::Break(Err(OutputNoMode)); + }; + + callback(Stage::WorkspacePopups { + workspace, + offset: *offset, + })?; + } + + // current workspace popups + let Some(workspace) = shell.workspaces.space_for_handle(¤t.0) else { + return ControlFlow::Break(Err(OutputNoMode)); + }; + + callback(Stage::WorkspacePopups { + workspace, + offset: current_offset, + })?; + } + + if !has_fullscreen { + // bottom layer popups + for (layer, popup, location) in layer_popups(output, Layer::Bottom, element_filter) { + callback(Stage::LayerPopup { + layer, + popup: &popup, + location, + })?; + } + } + + if let Some((_, has_fullscreen, offset)) = previous.as_ref() { + if !has_fullscreen { + // previous bottom layer popups + for (layer, popup, location) in layer_popups(output, Layer::Bottom, element_filter) { + callback(Stage::LayerPopup { + layer, + popup: &popup, + location: location + offset.as_global(), + })?; + } + } + } + + if !has_fullscreen { + // background layer popups + for (layer, popup, location) in layer_popups(output, Layer::Background, element_filter) { + callback(Stage::LayerPopup { + layer, + popup: &popup, + location, + })?; + } + } + + if let Some((_, has_fullscreen, offset)) = previous.as_ref() { + if !has_fullscreen { + // previous background layer popups + for (layer, popup, location) in layer_popups(output, Layer::Background, element_filter) + { + callback(Stage::LayerPopup { + layer, + popup: &popup, + location: location + offset.as_global(), + })?; + } + } + } + + if !has_fullscreen { + // top-layer shell + for (layer, location) in layer_surfaces(output, Layer::Top, element_filter) { + callback(Stage::LayerSurface { layer, location })?; + } + + // sticky windows + if element_filter != ElementFilter::LayerShellOnly { + callback(Stage::Sticky(&set.sticky_layer))?; + } + } + + if element_filter != ElementFilter::LayerShellOnly { + // workspace windows + callback(Stage::Workspace { + workspace, + offset: current_offset, + })?; + + // previous workspace windows + if let Some((previous_handle, _, offset)) = previous.as_ref() { + let Some(workspace) = shell.workspaces.space_for_handle(previous_handle) else { + return ControlFlow::Break(Err(OutputNoMode)); + }; + callback(Stage::Workspace { + workspace, + offset: *offset, + })?; + } + } + + if !has_fullscreen { + // bottom layer + for (layer, mut location) in layer_surfaces(output, Layer::Bottom, element_filter) { + location += current_offset.as_global(); + callback(Stage::LayerSurface { layer, location })?; + } + } + + if let Some((_, has_fullscreen, offset)) = previous.as_ref() { + if !has_fullscreen { + // previous bottom layer + for (layer, mut location) in layer_surfaces(output, Layer::Bottom, element_filter) { + location += offset.as_global(); + callback(Stage::LayerSurface { layer, location })?; + } + } + } + + if !has_fullscreen { + // background layer + for (layer, mut location) in layer_surfaces(output, Layer::Background, element_filter) { + location += current_offset.as_global(); + callback(Stage::LayerSurface { layer, location })?; + } + } + + if let Some((_, has_fullscreen, offset)) = previous.as_ref() { + if !has_fullscreen { + // previous background layer + for (layer, mut location) in layer_surfaces(output, Layer::Background, element_filter) { + location += offset.as_global(); + callback(Stage::LayerSurface { layer, location })?; + } + } + } + + ControlFlow::Continue(()) +} + +fn layer_popups<'a>( + output: &'a Output, + layer: Layer, + element_filter: ElementFilter, +) -> impl Iterator)> + 'a { + layer_surfaces(output, layer, element_filter).flat_map(move |(surface, location)| { + let location_clone = location.clone(); + let surface_clone = surface.clone(); + PopupManager::popups_for_surface(surface.wl_surface()).map(move |(popup, popup_offset)| { + let offset = (popup_offset - popup.geometry().loc) + .as_local() + .to_global(output); + (surface_clone.clone(), popup, (location_clone + offset)) + }) + }) +} + +fn layer_surfaces<'a>( + output: &'a Output, + layer: Layer, + element_filter: ElementFilter, +) -> impl Iterator)> + 'a { + // we want to avoid deadlocks on the layer-map in callbacks, so we need to clone the layer surfaces + let layers = { + let layer_map = layer_map_for_output(output); + layer_map + .layers_on(layer) + .rev() + .map(|s| (s.clone(), layer_map.layer_geometry(s).unwrap())) + .collect::>() + }; + + layers + .into_iter() + .filter(move |(s, _)| { + !(element_filter == ElementFilter::ExcludeWorkspaceOverview + && s.namespace() == WORKSPACE_OVERVIEW_NAMESPACE) + }) + .map(|(surface, geometry)| (surface, geometry.loc.as_local().to_global(output))) +} From 51c8588f892e8e2239f7f7aa9f1a2e8c7ea9c50d Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 27 Sep 2024 23:41:58 +0200 Subject: [PATCH 10/60] render: Use `render_input_order` --- src/backend/render/element.rs | 6 +- src/backend/render/mod.rs | 580 ++++++++++-------------- src/shell/element/mod.rs | 73 +-- src/shell/element/stack.rs | 59 ++- src/shell/element/surface.rs | 65 +-- src/shell/element/window.rs | 71 ++- src/shell/grabs/moving.rs | 19 +- src/shell/layout/floating/mod.rs | 66 ++- src/shell/layout/tiling/mod.rs | 740 +++++++++++++++++++++---------- src/shell/workspace.rs | 236 ++++++++-- 10 files changed, 1153 insertions(+), 762 deletions(-) diff --git a/src/backend/render/element.rs b/src/backend/render/element.rs index dfedb3b8..3f8af67e 100644 --- a/src/backend/render/element.rs +++ b/src/backend/render/element.rs @@ -24,7 +24,7 @@ where ::TextureId: 'static, CosmicMappedRenderElement: RenderElement, { - Workspace(RelocateRenderElement>), + Workspace(RelocateRenderElement>>), Cursor(RelocateRenderElement>), Dnd(WaylandSurfaceRenderElement), MoveGrab(CosmicMappedRenderElement), @@ -266,13 +266,13 @@ where } } -impl From> for CosmicElement +impl From>> for CosmicElement where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, ::TextureId: 'static, CosmicMappedRenderElement: RenderElement, { - fn from(elem: WorkspaceRenderElement) -> Self { + fn from(elem: CropRenderElement>) -> Self { Self::Workspace(RelocateRenderElement::from_element( elem, (0, 0), diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 80c78225..aa545068 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -4,6 +4,7 @@ use std::{ borrow::Borrow, cell::RefCell, collections::HashMap, + ops::ControlFlow, sync::{Arc, RwLock, Weak}, time::Instant, }; @@ -14,16 +15,13 @@ use crate::{ backend::{kms::render::gles::GbmGlowBackend, render::element::DamageElement}, shell::{ element::CosmicMappedKey, - focus::target::WindowGroup, + focus::{render_input_order, target::WindowGroup, Stage}, grabs::{SeatMenuGrabState, SeatMoveGrabState}, layout::tiling::ANIMATION_DURATION, - CosmicMappedRenderElement, OverviewMode, SeatExt, SessionLock, Trigger, WorkspaceDelta, + CosmicMappedRenderElement, OverviewMode, SeatExt, Trigger, WorkspaceDelta, WorkspaceRenderElement, }, - utils::{ - prelude::*, - quirks::{workspace_overview_is_open, WORKSPACE_OVERVIEW_NAMESPACE}, - }, + utils::{prelude::*, quirks::workspace_overview_is_open}, wayland::{ handlers::{ data_device::get_dnd_icon, @@ -34,9 +32,7 @@ use crate::{ }; use cosmic::Theme; -use cosmic_comp_config::workspace::WorkspaceLayout; use element::FromGlesError; -use keyframe::{ease, functions::EaseInOutCubic}; use smithay::{ backend::{ allocator::dmabuf::Dmabuf, @@ -46,7 +42,7 @@ use smithay::{ damage::{Error as RenderError, OutputDamageTracker, RenderOutputResult}, element::{ surface::{render_elements_from_surface_tree, WaylandSurfaceRenderElement}, - utils::{Relocate, RelocateRenderElement}, + utils::{CropRenderElement, Relocate, RelocateRenderElement}, AsRenderElements, Element, Id, Kind, RenderElement, }, gles::{ @@ -60,13 +56,12 @@ use smithay::{ TextureFilter, }, }, - desktop::{layer_map_for_output, PopupManager}, input::Seat, output::{Output, OutputNoMode}, - utils::{IsAlive, Logical, Monotonic, Physical, Point, Rectangle, Scale, Time, Transform}, + utils::{IsAlive, Logical, Monotonic, Point, Rectangle, Scale, Time, Transform}, wayland::{ dmabuf::get_dmabuf, - shell::wlr_layer::Layer, + session_lock::LockSurface, shm::{shm_format_to_fourcc, with_buffer_contents}, }, }; @@ -504,60 +499,6 @@ pub enum ElementFilter { LayerShellOnly, } -#[derive(Clone, Debug)] -pub struct SplitRenderElements { - pub w_elements: Vec, - pub p_elements: Vec, -} - -impl Default for SplitRenderElements { - fn default() -> Self { - Self { - w_elements: Vec::new(), - p_elements: Vec::new(), - } - } -} - -impl SplitRenderElements { - pub fn extend(&mut self, other: Self) { - self.w_elements.extend(other.w_elements); - self.p_elements.extend(other.p_elements); - } - - pub fn extend_map E>(&mut self, other: SplitRenderElements, mut f: F) { - self.w_elements - .extend(other.w_elements.into_iter().map(&mut f)); - self.p_elements - .extend(other.p_elements.into_iter().map(&mut f)); - } - - pub fn join(mut self) -> Vec { - self.p_elements.extend(self.w_elements); - self.p_elements - } -} - -impl SplitRenderElements> -where - R: Renderer + ImportAll + ImportMem + AsGlowRenderer, - CosmicMappedRenderElement: RenderElement, -{ - fn extend_from_workspace_elements>>( - &mut self, - other: SplitRenderElements, - offset: Point, - ) { - self.extend_map(other, |element| { - CosmicElement::Workspace(RelocateRenderElement::from_element( - element.into(), - offset, - Relocate::Relative, - )) - }) - } -} - #[profiling::function] pub fn workspace_elements( _gpu: Option<&DrmNode>, @@ -578,7 +519,7 @@ where CosmicMappedRenderElement: RenderElement, WorkspaceRenderElement: RenderElement, { - let mut elements = SplitRenderElements::default(); + let mut elements = Vec::new(); let theme = shell.read().unwrap().theme().clone(); let seats = shell @@ -588,8 +529,9 @@ where .iter() .cloned() .collect::>(); + let scale = output.current_scale().fractional_scale(); - elements.p_elements.extend(cursor_elements( + elements.extend(cursor_elements( renderer, seats.iter(), &theme, @@ -602,7 +544,6 @@ where #[cfg(feature = "debug")] { let output_geo = output.geometry(); - let scale = output.current_scale().fractional_scale(); if let Some((state, timings)) = _fps { let debug_active = shell.read().unwrap().debug_active; @@ -621,23 +562,12 @@ where ) .map_err(FromGlesError::from_gles_error) .map_err(RenderError::Rendering)?; - elements.p_elements.push(fps_overlay.into()); + elements.push(fps_overlay.into()); } } let shell = shell.read().unwrap(); - // If session locked, only show session lock surfaces - if let Some(session_lock) = &shell.session_lock { - elements.p_elements.extend( - session_lock_elements(renderer, output, session_lock) - .into_iter() - .map(|x| WorkspaceRenderElement::from(x).into()), - ); - return Ok(elements.join()); - } - - let theme = theme.cosmic(); let overview = shell.overview_mode(); let (resize_mode, resize_indicator) = shell.resize_mode(); let resize_indicator = resize_indicator.map(|indicator| (resize_mode, indicator)); @@ -666,310 +596,252 @@ where .unwrap() .is_some(); let focused_output = last_active_seat.focused_or_active_output(); - let output_size = output.geometry().size; - let output_scale = output.current_scale().fractional_scale(); - let set = shell.workspaces.sets.get(output).ok_or(OutputNoMode)?; let workspace = set .workspaces .iter() .find(|w| w.handle == current.0) .ok_or(OutputNoMode)?; - let is_active_space = workspace.outputs().any(|o| o == &focused_output); - - let has_fullscreen = workspace - .fullscreen - .as_ref() - .filter(|f| !f.is_animating()) - .is_some(); - let overlay_elements = split_layer_elements(renderer, output, Layer::Overlay, element_filter); - - // overlay is above everything - elements - .p_elements - .extend(overlay_elements.p_elements.into_iter().map(Into::into)); - elements - .p_elements - .extend(overlay_elements.w_elements.into_iter().map(Into::into)); - - if !has_fullscreen { - elements.extend_from_workspace_elements( - split_layer_elements(renderer, output, Layer::Top, element_filter), - (0, 0).into(), - ); - }; - + let is_active_space = workspace.output == focused_output; let active_hint = if shell.active_hint { - theme.active_hint as u8 + theme.cosmic().active_hint as u8 } else { 0 }; - // overlay redirect windows - // they need to be over sticky windows, because they could be popups of sticky windows, - // and we can't differenciate that. - if element_filter != ElementFilter::LayerShellOnly { - elements.p_elements.extend( - shell - .override_redirect_windows - .iter() - .filter(|or| { - (*or) - .geometry() - .as_global() - .intersection(workspace.output.geometry()) - .is_some() - }) - .flat_map(|or| { - AsRenderElements::::render_elements::>( - or, - renderer, - (or.geometry().loc - workspace.output.geometry().loc.as_logical()) - .to_physical_precise_round(output_scale), - Scale::from(output_scale), - 1.0, - ) - }) - .map(|p_element| p_element.into()), - ); - } - - // sticky windows - if !has_fullscreen && element_filter != ElementFilter::LayerShellOnly { - let alpha = match &overview.0 { - OverviewMode::Started(_, started) => { - (1.0 - (Instant::now().duration_since(*started).as_millis() - / ANIMATION_DURATION.as_millis()) as f32) - .max(0.0) - * 0.4 - + 0.6 - } - OverviewMode::Ended(_, ended) => { - ((Instant::now().duration_since(*ended).as_millis() - / ANIMATION_DURATION.as_millis()) as f32) - * 0.4 - + 0.6 - } - OverviewMode::Active(_) => 0.6, - OverviewMode::None => 1.0, - }; - - let current_focus = (!move_active && is_active_space) - .then_some(last_active_seat) - .map(|seat| workspace.focus_stack.get(seat)); - - elements.extend_from_workspace_elements( - set.sticky_layer.render( - renderer, - current_focus.as_ref().and_then(|stack| stack.last()), - resize_indicator.clone(), - active_hint, - alpha, - theme, - ), - (0, 0).into(), - ); - } - - let offset = match previous.as_ref() { - Some((previous, previous_idx, start)) => { - let layout = shell.workspaces.layout; + let output_size = output + .geometry() + .size + .as_logical() + .to_physical_precise_round(scale); + let crop_to_output = |element: WorkspaceRenderElement| { + CropRenderElement::from_element( + element.into(), + scale, + Rectangle::from_loc_and_size((0, 0), output_size), + ) + }; - let workspace = shell - .workspaces - .space_for_handle(&previous) - .ok_or(OutputNoMode)?; - let has_fullscreen = workspace.fullscreen.is_some(); - let is_active_space = workspace.outputs().any(|o| o == &focused_output); - - let percentage = match start { - WorkspaceDelta::Shortcut(st) => ease( - EaseInOutCubic, - 0.0, - 1.0, - Instant::now().duration_since(*st).as_millis() as f32 - / ANIMATION_DURATION.as_millis() as f32, - ), - WorkspaceDelta::Gesture(prog) => *prog as f32, - WorkspaceDelta::GestureEnd(st, spring) => { - (spring.value_at(Instant::now().duration_since(*st)) as f32).clamp(0.0, 1.0) + render_input_order( + &*shell, + output, + previous, + current, + element_filter, + |stage| { + match stage { + Stage::SessionLock(lock_surface) => { + elements.extend( + session_lock_elements(renderer, output, lock_surface) + .into_iter() + .map(Into::into) + .flat_map(crop_to_output) + .map(Into::into), + ); } - }; - let offset = Point::::from(match (layout, *previous_idx < current.1) { - (WorkspaceLayout::Vertical, true) => { - (0, (-output_size.h as f32 * percentage).round() as i32) + Stage::LayerPopup { + popup, location, .. + } => { + elements.extend( + render_elements_from_surface_tree::<_, WorkspaceRenderElement<_>>( + renderer, + popup.wl_surface(), + location + .to_local(output) + .as_logical() + .to_physical_precise_round(scale), + Scale::from(scale), + 1.0, + Kind::Unspecified, + ) + .into_iter() + .flat_map(crop_to_output) + .map(Into::into), + ); } - (WorkspaceLayout::Vertical, false) => { - (0, (output_size.h as f32 * percentage).round() as i32) + Stage::LayerSurface { layer, location } => { + elements.extend( + render_elements_from_surface_tree::<_, WorkspaceRenderElement<_>>( + renderer, + &layer.wl_surface(), + location + .to_local(output) + .as_logical() + .to_physical_precise_round(scale), + Scale::from(scale), + 1.0, + Kind::Unspecified, + ) + .into_iter() + .flat_map(crop_to_output) + .map(Into::into), + ); } - (WorkspaceLayout::Horizontal, true) => { - ((-output_size.w as f32 * percentage).round() as i32, 0) + Stage::OverrideRedirect { surface, location } => { + elements.extend( + AsRenderElements::::render_elements::>( + surface, + renderer, + location + .to_local(output) + .as_logical() + .to_physical_precise_round(scale), + Scale::from(scale), + 1.0, + ) + .into_iter() + .flat_map(crop_to_output) + .map(Into::into), + ); } - (WorkspaceLayout::Horizontal, false) => { - ((output_size.w as f32 * percentage).round() as i32, 0) + Stage::StickyPopups(layout) => { + let alpha = match &overview.0 { + OverviewMode::Started(_, started) => { + (1.0 - (Instant::now().duration_since(*started).as_millis() + / ANIMATION_DURATION.as_millis()) + as f32) + .max(0.0) + * 0.4 + + 0.6 + } + OverviewMode::Ended(_, ended) => { + ((Instant::now().duration_since(*ended).as_millis() + / ANIMATION_DURATION.as_millis()) + as f32) + * 0.4 + + 0.6 + } + OverviewMode::Active(_) => 0.6, + OverviewMode::None => 1.0, + }; + + elements.extend( + layout + .render_popups(renderer, alpha) + .into_iter() + .map(Into::into) + .flat_map(crop_to_output) + .map(Into::into), + ); } - }); - - elements.extend_from_workspace_elements( - workspace - .render::( - renderer, - (!move_active && is_active_space).then_some(last_active_seat), - overview.clone(), - resize_indicator.clone(), - active_hint, - theme, + Stage::Sticky(layout) => { + let alpha = match &overview.0 { + OverviewMode::Started(_, started) => { + (1.0 - (Instant::now().duration_since(*started).as_millis() + / ANIMATION_DURATION.as_millis()) + as f32) + .max(0.0) + * 0.4 + + 0.6 + } + OverviewMode::Ended(_, ended) => { + ((Instant::now().duration_since(*ended).as_millis() + / ANIMATION_DURATION.as_millis()) + as f32) + * 0.4 + + 0.6 + } + OverviewMode::Active(_) => 0.6, + OverviewMode::None => 1.0, + }; + + let current_focus = (!move_active && is_active_space) + .then_some(last_active_seat) + .map(|seat| workspace.focus_stack.get(seat)); + + elements.extend( + layout + .render( + renderer, + current_focus.as_ref().and_then(|stack| stack.last()), + resize_indicator.clone(), + active_hint, + alpha, + &theme.cosmic(), + ) + .into_iter() + .map(Into::into) + .flat_map(crop_to_output) + .map(Into::into), ) - .map_err(|_| OutputNoMode)?, - offset.to_physical_precise_round(output_scale), - ); - - if !has_fullscreen { - elements.extend_from_workspace_elements( - background_layer_elements(renderer, output, element_filter), - offset.to_physical_precise_round(output_scale), - ); - } - - Point::::from(match (layout, *previous_idx < current.1) { - (WorkspaceLayout::Vertical, true) => (0, output_size.h + offset.y), - (WorkspaceLayout::Vertical, false) => (0, -(output_size.h - offset.y)), - (WorkspaceLayout::Horizontal, true) => (output_size.w + offset.x, 0), - (WorkspaceLayout::Horizontal, false) => (-(output_size.w - offset.x), 0), - }) - } - None => (0, 0).into(), - }; - - if element_filter != ElementFilter::LayerShellOnly { - elements.extend_from_workspace_elements( - workspace - .render::( - renderer, - (!move_active && is_active_space).then_some(&last_active_seat), - overview, - resize_indicator, - active_hint, - theme, - ) - .map_err(|_| OutputNoMode)?, - offset.to_physical_precise_round(output_scale), - ); - } - - if !has_fullscreen { - elements.extend_from_workspace_elements( - background_layer_elements(renderer, output, element_filter), - offset.to_physical_precise_round(output_scale), - ); - } - - Ok(elements.join()) -} - -pub fn split_layer_elements( - renderer: &mut R, - output: &Output, - layer: Layer, - element_filter: ElementFilter, -) -> SplitRenderElements> -where - R: Renderer + ImportAll + ImportMem + AsGlowRenderer, - ::TextureId: Clone + 'static, - ::Error: FromGlesError, - CosmicMappedRenderElement: RenderElement, - WorkspaceRenderElement: RenderElement, -{ - let layer_map = layer_map_for_output(output); - let output_scale = output.current_scale().fractional_scale(); - - let mut elements = SplitRenderElements::default(); - - layer_map - .layers_on(layer) - .rev() - .filter(|s| { - !(element_filter == ElementFilter::ExcludeWorkspaceOverview - && s.namespace() == WORKSPACE_OVERVIEW_NAMESPACE) - }) - .filter_map(|surface| { - layer_map - .layer_geometry(surface) - .map(|geo| (geo.loc, surface)) - }) - .for_each(|(location, surface)| { - let location = location.to_physical_precise_round(output_scale); - let surface = surface.wl_surface(); - let scale = Scale::from(output_scale); - - elements - .p_elements - .extend(PopupManager::popups_for_surface(surface).flat_map( - |(popup, popup_offset)| { - let offset = (popup_offset - popup.geometry().loc) - .to_f64() - .to_physical(scale) - .to_i32_round(); - - render_elements_from_surface_tree( + } + Stage::WorkspacePopups { workspace, offset } => { + elements.extend( + match workspace.render_popups( renderer, - popup.wl_surface(), - location + offset, - scale, - 1.0, - Kind::Unspecified, - ) - }, - )); - - elements - .w_elements - .extend(render_elements_from_surface_tree( - renderer, - surface, - location, - scale, - 1.0, - Kind::Unspecified, - )); - }); + (!move_active && is_active_space).then_some(last_active_seat), + overview.clone(), + &theme.cosmic(), + ) { + Ok(elements) => { + elements + .into_iter() + .flat_map(crop_to_output) + .map(|element| { + CosmicElement::Workspace( + RelocateRenderElement::from_element( + element, + offset.to_physical_precise_round(scale), + Relocate::Relative, + ), + ) + }) + } + Err(_) => { + return ControlFlow::Break(Err(OutputNoMode)); + } + }, + ); + } + Stage::Workspace { workspace, offset } => { + elements.extend( + match workspace.render( + renderer, + (!move_active && is_active_space).then_some(last_active_seat), + overview.clone(), + resize_indicator.clone(), + active_hint, + &theme.cosmic(), + ) { + Ok(elements) => { + elements + .into_iter() + .flat_map(crop_to_output) + .map(|element| { + CosmicElement::Workspace( + RelocateRenderElement::from_element( + element, + offset.to_physical_precise_round(scale), + Relocate::Relative, + ), + ) + }) + } + Err(_) => { + return ControlFlow::Break(Err(OutputNoMode)); + } + }, + ); + } + }; - elements -} + ControlFlow::Continue(()) + }, + )?; -// bottom and background layer surfaces -pub fn background_layer_elements( - renderer: &mut R, - output: &Output, - element_filter: ElementFilter, -) -> SplitRenderElements> -where - R: Renderer + ImportAll + ImportMem + AsGlowRenderer, - ::TextureId: Clone + 'static, - ::Error: FromGlesError, - CosmicMappedRenderElement: RenderElement, - WorkspaceRenderElement: RenderElement, -{ - let mut elements = split_layer_elements(renderer, output, Layer::Bottom, element_filter); - elements.extend(split_layer_elements( - renderer, - output, - Layer::Background, - element_filter, - )); - elements + Ok(elements) } fn session_lock_elements( renderer: &mut R, output: &Output, - session_lock: &SessionLock, + lock_surface: Option<&LockSurface>, ) -> Vec> where R: Renderer + ImportAll, ::TextureId: Clone + 'static, { - if let Some(surface) = session_lock.surfaces.get(output) { + if let Some(surface) = lock_surface { let scale = Scale::from(output.current_scale().fractional_scale()); render_elements_from_surface_tree( renderer, diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index 4a2cfb67..0860c07d 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -1,8 +1,5 @@ use crate::{ - backend::render::{ - element::{AsGlowRenderer, FromGlesError}, - SplitRenderElements, - }, + backend::render::element::{AsGlowRenderer, FromGlesError}, state::State, utils::{iced::IcedElementInternal, prelude::*}, }; @@ -657,13 +654,42 @@ impl CosmicMapped { } } - pub fn split_render_elements( + pub fn popup_render_elements( &self, renderer: &mut R, location: smithay::utils::Point, scale: smithay::utils::Scale, alpha: f32, - ) -> SplitRenderElements + ) -> Vec + where + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + ::TextureId: Send + Clone + 'static, + CosmicMappedRenderElement: RenderElement, + C: From>, + { + match &self.element { + CosmicMappedInternal::Stack(s) => s + .popup_render_elements::>( + renderer, location, scale, alpha, + ), + CosmicMappedInternal::Window(w) => w + .popup_render_elements::>( + renderer, location, scale, alpha, + ), + _ => unreachable!(), + } + .into_iter() + .map(C::from) + .collect() + } + + pub fn render_elements( + &self, + renderer: &mut R, + location: smithay::utils::Point, + scale: smithay::utils::Scale, + alpha: f32, + ) -> Vec where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, ::TextureId: Send + Clone + 'static, @@ -671,7 +697,7 @@ impl CosmicMapped { C: From>, { #[cfg(feature = "debug")] - let debug_elements = if let Some(debug) = self.debug.lock().unwrap().as_mut() { + let mut elements = if let Some(debug) = self.debug.lock().unwrap().as_mut() { let window = self.active_window(); let window_geo = window.geometry(); let (min_size, max_size, size) = @@ -840,30 +866,21 @@ impl CosmicMapped { Vec::new() }; #[cfg(not(feature = "debug"))] - let debug_elements = Vec::new(); - - let mut elements = SplitRenderElements { - w_elements: debug_elements, - p_elements: Vec::new(), - }; + let mut elements = Vec::new(); #[cfg_attr(not(feature = "debug"), allow(unused_mut))] - elements.extend_map( - match &self.element { - CosmicMappedInternal::Stack(s) => s - .split_render_elements::>( - renderer, location, scale, alpha, - ), - CosmicMappedInternal::Window(w) => w - .split_render_elements::>( - renderer, location, scale, alpha, - ), - _ => unreachable!(), - }, - C::from, - ); + elements.extend(match &self.element { + CosmicMappedInternal::Stack(s) => s.render_elements::>( + renderer, location, scale, alpha, + ), + CosmicMappedInternal::Window(w) => w + .render_elements::>( + renderer, location, scale, alpha, + ), + _ => unreachable!(), + }); - elements + elements.into_iter().map(C::from).collect() } pub(crate) fn update_theme(&self, theme: cosmic::Theme) { diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index 2a9167a4..388eb813 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -1,6 +1,6 @@ use super::{surface::RESIZE_BORDER, window::Focus, CosmicSurface}; use crate::{ - backend::render::{cursor::CursorState, SplitRenderElements}, + backend::render::cursor::CursorState, shell::{ focus::target::PointerFocusTarget, grabs::{ReleaseMode, ResizeEdge}, @@ -541,13 +541,40 @@ impl CosmicStack { self.0.loop_handle() } - pub fn split_render_elements( + pub fn popup_render_elements( &self, renderer: &mut R, location: Point, scale: Scale, alpha: f32, - ) -> SplitRenderElements + ) -> Vec + where + R: Renderer + ImportAll + ImportMem, + ::TextureId: Send + Clone + 'static, + C: From>, + { + let window_loc = location + Point::from((0, (TAB_HEIGHT as f64 * scale.y) as i32)); + self.0.with_program(|p| { + let windows = p.windows.lock().unwrap(); + let active = p.active.load(Ordering::SeqCst); + + windows[active] + .popup_render_elements::>( + renderer, window_loc, scale, alpha, + ) + .into_iter() + .map(C::from) + .collect() + }) + } + + pub fn render_elements( + &self, + renderer: &mut R, + location: Point, + scale: Scale, + alpha: f32, + ) -> Vec where R: Renderer + ImportAll + ImportMem, ::TextureId: Send + Clone + 'static, @@ -564,28 +591,20 @@ impl CosmicStack { let stack_loc = location + offset; let window_loc = location + Point::from((0, (TAB_HEIGHT as f64 * scale.y) as i32)); - let w_elements = AsRenderElements::::render_elements::>( + let mut elements = AsRenderElements::::render_elements::>( &self.0, renderer, stack_loc, scale, alpha, ); - let mut elements = SplitRenderElements { - w_elements: w_elements.into_iter().map(C::from).collect(), - p_elements: Vec::new(), - }; - - elements.extend_map( - self.0.with_program(|p| { - let windows = p.windows.lock().unwrap(); - let active = p.active.load(Ordering::SeqCst); + elements.extend(self.0.with_program(|p| { + let windows = p.windows.lock().unwrap(); + let active = p.active.load(Ordering::SeqCst); - windows[active].split_render_elements::>( - renderer, window_loc, scale, alpha, - ) - }), - C::from, - ); + windows[active].render_elements::>( + renderer, window_loc, scale, alpha, + ) + })); - elements + elements.into_iter().map(C::from).collect() } pub(crate) fn set_theme(&self, theme: cosmic::Theme) { diff --git a/src/shell/element/surface.rs b/src/shell/element/surface.rs index d13272e6..512f7e84 100644 --- a/src/shell/element/surface.rs +++ b/src/shell/element/surface.rs @@ -35,7 +35,9 @@ use smithay::{ }, wayland_server::protocol::wl_surface::WlSurface, }, - utils::{user_data::UserDataMap, IsAlive, Logical, Rectangle, Serial, Size}, + utils::{ + user_data::UserDataMap, IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size, + }, wayland::{ compositor::{with_states, SurfaceData}, seat::WaylandFocus, @@ -45,7 +47,6 @@ use smithay::{ }; use crate::{ - backend::render::SplitRenderElements, state::{State, SurfaceDmabufFeedback}, utils::prelude::*, wayland::handlers::decoration::PreferredDecorationMode, @@ -590,13 +591,13 @@ impl CosmicSurface { self.0.user_data() } - pub fn split_render_elements( + pub fn popup_render_elements( &self, renderer: &mut R, - location: smithay::utils::Point, - scale: smithay::utils::Scale, + location: Point, + scale: Scale, alpha: f32, - ) -> SplitRenderElements + ) -> Vec where R: Renderer + ImportAll, ::TextureId: Clone + 'static, @@ -605,9 +606,8 @@ impl CosmicSurface { match self.0.underlying_surface() { WindowSurface::Wayland(toplevel) => { let surface = toplevel.wl_surface(); - - let p_elements = PopupManager::popups_for_surface(surface) - .flat_map(|(popup, popup_offset)| { + PopupManager::popups_for_surface(surface) + .flat_map(move |(popup, popup_offset)| { let offset = (self.0.geometry().loc + popup_offset - popup.geometry().loc) .to_physical_precise_round(scale); @@ -620,26 +620,40 @@ impl CosmicSurface { element::Kind::Unspecified, ) }) - .collect(); + .collect() + } + WindowSurface::X11(_) => Vec::new(), + } + } + + pub fn render_elements( + &self, + renderer: &mut R, + location: Point, + scale: Scale, + alpha: f32, + ) -> Vec + where + R: Renderer + ImportAll, + ::TextureId: Clone + 'static, + C: From>, + { + match self.0.underlying_surface() { + WindowSurface::Wayland(toplevel) => { + let surface = toplevel.wl_surface(); - let w_elements = render_elements_from_surface_tree( + render_elements_from_surface_tree( renderer, surface, location, scale, alpha, element::Kind::Unspecified, - ); - - SplitRenderElements { - w_elements, - p_elements, - } + ) + } + WindowSurface::X11(surface) => { + surface.render_elements(renderer, location, scale, alpha) } - WindowSurface::X11(surface) => SplitRenderElements { - w_elements: surface.render_elements(renderer, location, scale, alpha), - p_elements: Vec::new(), - }, } } @@ -663,10 +677,7 @@ impl SpaceElement for CosmicSurface { SpaceElement::bbox(&self.0) } - fn is_in_input_region( - &self, - point: &smithay::utils::Point, - ) -> bool { + fn is_in_input_region(&self, point: &Point) -> bool { SpaceElement::is_in_input_region(&self.0, point) } @@ -784,8 +795,8 @@ where fn render_elements>( &self, renderer: &mut R, - location: smithay::utils::Point, - scale: smithay::utils::Scale, + location: Point, + scale: Scale, alpha: f32, ) -> Vec { self.0.render_elements(renderer, location, scale, alpha) diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index 887ce664..9070994c 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -1,5 +1,5 @@ use crate::{ - backend::render::{cursor::CursorState, SplitRenderElements}, + backend::render::cursor::CursorState, shell::{ focus::target::PointerFocusTarget, grabs::{ReleaseMode, ResizeEdge}, @@ -41,7 +41,7 @@ use smithay::{ output::Output, reexports::wayland_server::protocol::wl_surface::WlSurface, render_elements, - utils::{IsAlive, Logical, Point, Rectangle, Serial, Size}, + utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, Size}, wayland::seat::WaylandFocus, }; use std::{ @@ -308,13 +308,13 @@ impl CosmicWindow { self.0.loop_handle() } - pub fn split_render_elements( + pub fn popup_render_elements( &self, renderer: &mut R, - location: smithay::utils::Point, - scale: smithay::utils::Scale, + location: Point, + scale: Scale, alpha: f32, - ) -> SplitRenderElements + ) -> Vec where R: Renderer + ImportAll + ImportMem, ::TextureId: Send + Clone + 'static, @@ -328,17 +328,44 @@ impl CosmicWindow { location }; - let mut elements = SplitRenderElements::default(); + self.0.with_program(|p| { + p.window + .popup_render_elements::>( + renderer, window_loc, scale, alpha, + ) + .into_iter() + .map(C::from) + .collect() + }) + } - elements.extend_map( - self.0.with_program(|p| { - p.window - .split_render_elements::>( - renderer, window_loc, scale, alpha, - ) - }), - C::from, - ); + pub fn render_elements( + &self, + renderer: &mut R, + location: Point, + scale: Scale, + alpha: f32, + ) -> Vec + where + R: Renderer + ImportAll + ImportMem, + ::TextureId: Send + Clone + 'static, + C: From>, + { + let has_ssd = self.0.with_program(|p| p.has_ssd(false)); + + let window_loc = if has_ssd { + location + Point::from((0, (SSD_HEIGHT as f64 * scale.y) as i32)) + } else { + location + }; + + let mut elements = Vec::new(); + + elements.extend(self.0.with_program(|p| { + p.window.render_elements::>( + renderer, window_loc, scale, alpha, + ) + })); if has_ssd { let ssd_loc = location @@ -346,16 +373,12 @@ impl CosmicWindow { .0 .with_program(|p| p.window.geometry().loc) .to_physical_precise_round(scale); - elements.w_elements.extend( - AsRenderElements::::render_elements::>( - &self.0, renderer, ssd_loc, scale, alpha, - ) - .into_iter() - .map(C::from), - ) + elements.extend(AsRenderElements::::render_elements::< + CosmicWindowRenderElement, + >(&self.0, renderer, ssd_loc, scale, alpha)) } - elements + elements.into_iter().map(C::from).collect() } pub(crate) fn set_theme(&self, theme: cosmic::Theme) { diff --git a/src/shell/grabs/moving.rs b/src/shell/grabs/moving.rs index 9a41702e..25106fa6 100644 --- a/src/shell/grabs/moving.rs +++ b/src/shell/grabs/moving.rs @@ -2,8 +2,7 @@ use crate::{ backend::render::{ - cursor::CursorState, element::AsGlowRenderer, BackdropShader, IndicatorShader, Key, - SplitRenderElements, Usage, + cursor::CursorState, element::AsGlowRenderer, BackdropShader, IndicatorShader, Key, Usage, }, shell::{ element::{ @@ -181,12 +180,18 @@ impl MoveGrabState { _ => vec![], }; - let SplitRenderElements { - w_elements, - p_elements, - } = self + let w_elements = self .window - .split_render_elements::>( + .render_elements::>( + renderer, + (render_location - self.window.geometry().loc) + .to_physical_precise_round(output_scale), + output_scale, + alpha, + ); + let p_elements = self + .window + .popup_render_elements::>( renderer, (render_location - self.window.geometry().loc) .to_physical_precise_round(output_scale), diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 140fb5d5..5c80fe62 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -24,7 +24,7 @@ use smithay::{ }; use crate::{ - backend::render::{element::AsGlowRenderer, IndicatorShader, Key, SplitRenderElements, Usage}, + backend::render::{element::AsGlowRenderer, IndicatorShader, Key, Usage}, shell::{ element::{ resize_indicator::ResizeIndicator, @@ -1260,6 +1260,52 @@ impl FloatingLayout { } self.refresh(); //fixup any out of bounds elements } + #[profiling::function] + pub fn render_popups( + &self, + renderer: &mut R, + alpha: f32, + ) -> Vec> + where + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + ::TextureId: Send + Clone + 'static, + CosmicMappedRenderElement: RenderElement, + CosmicWindowRenderElement: RenderElement, + CosmicStackRenderElement: RenderElement, + { + let output = self.space.outputs().next().unwrap(); + let output_scale = output.current_scale().fractional_scale(); + + let mut elements = Vec::default(); + + for elem in self + .animations + .iter() + .filter(|(_, anim)| matches!(anim, Animation::Minimize { .. })) + .map(|(elem, _)| elem) + .chain(self.space.elements().rev()) + { + let (geometry, alpha) = self + .animations + .get(elem) + .map(|anim| (*anim.previous_geometry(), alpha * anim.alpha())) + .unwrap_or_else(|| (self.space.element_geometry(elem).unwrap().as_local(), alpha)); + + let render_location = geometry.loc - elem.geometry().loc.as_local(); + elements.extend( + elem.popup_render_elements( + renderer, + render_location + .as_logical() + .to_physical_precise_round(output_scale), + output_scale.into(), + alpha, + ), + ); + } + + elements + } #[profiling::function] pub fn render( @@ -1270,7 +1316,7 @@ impl FloatingLayout { indicator_thickness: u8, alpha: f32, theme: &cosmic::theme::CosmicTheme, - ) -> SplitRenderElements> + ) -> Vec> where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, ::TextureId: Send + Clone + 'static, @@ -1285,7 +1331,7 @@ impl FloatingLayout { }; let output_scale = output.current_scale().fractional_scale(); - let mut elements = SplitRenderElements::default(); + let mut elements = Vec::default(); for elem in self .animations @@ -1301,10 +1347,7 @@ impl FloatingLayout { .unwrap_or_else(|| (self.space.element_geometry(elem).unwrap().as_local(), alpha)); let render_location = geometry.loc - elem.geometry().loc.as_local(); - let SplitRenderElements { - mut w_elements, - p_elements, - } = elem.split_render_elements( + let mut window_elements = elem.render_elements( renderer, render_location .as_logical() @@ -1331,7 +1374,7 @@ impl FloatingLayout { y: geometry.size.h as f64 / buffer_size.h as f64, }; - w_elements = w_elements + window_elements = window_elements .into_iter() .map(|element| match element { CosmicMappedRenderElement::Stack(elem) => { @@ -1387,7 +1430,7 @@ impl FloatingLayout { resize.resize(resize_geometry.size.as_logical()); resize.output_enter(output, Rectangle::default() /* unused */); - elements.w_elements.extend( + window_elements.extend( resize .render_elements::>( renderer, @@ -1419,12 +1462,11 @@ impl FloatingLayout { active_window_hint.blue, ], ); - elements.w_elements.push(element.into()); + window_elements.push(element.into()); } } - elements.w_elements.extend(w_elements); - elements.p_elements.extend(p_elements); + elements.extend(window_elements); } elements diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index feeebcd8..308f8f8e 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -2,8 +2,8 @@ use crate::{ backend::render::{ - element::AsGlowRenderer, BackdropShader, IndicatorShader, Key, SplitRenderElements, Usage, - ACTIVE_GROUP_COLOR, GROUP_COLOR, + element::AsGlowRenderer, BackdropShader, IndicatorShader, Key, Usage, ACTIVE_GROUP_COLOR, + GROUP_COLOR, }, shell::{ element::{ @@ -60,7 +60,7 @@ use smithay::{ input::Seat, output::Output, reexports::wayland_server::Client, - utils::{IsAlive, Logical, Point, Rectangle, Scale, Size}, + utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Size}, wayland::{compositor::add_blocker, seat::WaylandFocus}, }; use std::{ @@ -3863,7 +3863,7 @@ impl TilingLayout { resize_indicator: Option<(ResizeMode, ResizeIndicator)>, indicator_thickness: u8, theme: &cosmic::theme::CosmicTheme, - ) -> Result>, OutputNotMapped> + ) -> Result>, OutputNotMapped> where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, ::TextureId: Send + Clone + 'static, @@ -3896,7 +3896,7 @@ impl TilingLayout { }; let draw_groups = overview.0.alpha(); - let mut elements = SplitRenderElements::default(); + let mut elements = Vec::default(); let is_overview = !matches!(overview.0, OverviewMode::None); let is_mouse_tiling = (matches!(overview.0.trigger(), Some(Trigger::Pointer(_)))) @@ -3931,7 +3931,7 @@ impl TilingLayout { .unzip(); // all old windows we want to fade out - elements.extend(render_old_tree( + elements.extend(render_old_tree_windows( reference_tree, target_tree, renderer, @@ -3969,7 +3969,7 @@ impl TilingLayout { .unzip(); // all alive windows - elements.extend(render_new_tree( + elements.extend(render_new_tree_windows( target_tree, reference_tree, renderer, @@ -4001,12 +4001,139 @@ impl TilingLayout { // tiling hints if let Some(group_elements) = group_elements { - elements.w_elements.extend(group_elements); + elements.extend(group_elements); } Ok(elements) } + #[profiling::function] + pub fn render_popups( + &self, + renderer: &mut R, + seat: Option<&Seat>, + non_exclusive_zone: Rectangle, + overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree>)>), + theme: &cosmic::theme::CosmicTheme, + ) -> Result>, OutputNotMapped> + where + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + ::TextureId: Send + Clone + 'static, + CosmicMappedRenderElement: RenderElement, + CosmicWindowRenderElement: RenderElement, + CosmicStackRenderElement: RenderElement, + { + let output_scale = self.output.current_scale().fractional_scale(); + + let (target_tree, duration, _) = if self.queue.animation_start.is_some() { + self.queue + .trees + .get(1) + .expect("Animation ongoing, should have two trees") + } else { + self.queue.trees.front().unwrap() + }; + let reference_tree = self + .queue + .animation_start + .is_some() + .then(|| &self.queue.trees.front().unwrap().0); + + let percentage = if let Some(animation_start) = self.queue.animation_start { + let percentage = Instant::now().duration_since(animation_start).as_millis() as f32 + / duration.as_millis() as f32; + ease(EaseInOutCubic, 0.0, 1.0, percentage) + } else { + 1.0 + }; + let draw_groups = overview.0.alpha(); + + let mut elements = Vec::default(); + + let is_mouse_tiling = (matches!(overview.0.trigger(), Some(Trigger::Pointer(_)))) + .then(|| self.last_overview_hover.as_ref().map(|x| &x.1)); + let swap_desc = if let Some(Trigger::KeyboardSwap(_, desc)) = overview.0.trigger() { + Some(desc.clone()) + } else { + None + }; + + // all gone windows and fade them out + let old_geometries = if let Some(reference_tree) = reference_tree.as_ref() { + let (geometries, _) = if let Some(transition) = draw_groups { + Some(geometries_for_groupview( + reference_tree, + &mut *renderer, + non_exclusive_zone, + seat, // TODO: Would be better to be an old focus, + // but for that we have to associate focus with a tree (and animate focus changes properly) + 1.0 - transition, + transition, + output_scale, + &self.placeholder_id, + is_mouse_tiling, + swap_desc.clone(), + overview.1.as_ref().and_then(|(_, tree)| tree.clone()), + theme, + )) + } else { + None + } + .unzip(); + + // all old windows we want to fade out + elements.extend(render_old_tree_popups( + reference_tree, + target_tree, + renderer, + geometries.clone(), + output_scale, + percentage, + swap_desc.is_some(), + )); + + geometries + } else { + None + }; + + let (geometries, _) = if let Some(transition) = draw_groups { + Some(geometries_for_groupview( + target_tree, + &mut *renderer, + non_exclusive_zone, + seat, + transition, + transition, + output_scale, + &self.placeholder_id, + is_mouse_tiling, + swap_desc.clone(), + overview.1.as_ref().and_then(|(_, tree)| tree.clone()), + theme, + )) + } else { + None + } + .unzip(); + + // all alive windows + elements.extend(render_new_tree_popups( + target_tree, + reference_tree, + renderer, + geometries, + old_geometries, + seat, + &self.output, + percentage, + overview, + swap_desc.clone(), + )); + + Ok(elements) + } + fn gaps(&self) -> (i32, i32) { let g = self.theme.cosmic().gaps; (g.0 as i32, g.1 as i32) @@ -4690,7 +4817,48 @@ where (geometries, elements) } -fn render_old_tree( +fn render_old_tree_popups( + reference_tree: &Tree, + target_tree: &Tree, + renderer: &mut R, + geometries: Option>>, + output_scale: f64, + percentage: f32, + is_swap_mode: bool, +) -> Vec> +where + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + ::TextureId: Send + Clone + 'static, + CosmicMappedRenderElement: RenderElement, + CosmicWindowRenderElement: RenderElement, + CosmicStackRenderElement: RenderElement, +{ + let mut elements = Vec::default(); + + render_old_tree( + reference_tree, + target_tree, + geometries, + output_scale, + percentage, + is_swap_mode, + |mapped, elem_geometry, geo, alpha, _| { + elements.extend( + mapped.popup_render_elements::>( + renderer, + geo.loc.as_logical().to_physical_precise_round(output_scale) + - elem_geometry.loc, + Scale::from(output_scale), + alpha, + ), + ); + }, + ); + + elements +} + +fn render_old_tree_windows( reference_tree: &Tree, target_tree: &Tree, renderer: &mut R, @@ -4700,7 +4868,7 @@ fn render_old_tree( indicator_thickness: u8, is_swap_mode: bool, theme: &cosmic::theme::CosmicTheme, -) -> SplitRenderElements> +) -> Vec> where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, ::TextureId: Send + Clone + 'static, @@ -4709,8 +4877,80 @@ where CosmicStackRenderElement: RenderElement, { let window_hint = crate::theme::active_window_hint(theme); - let mut elements = SplitRenderElements::default(); + let mut elements = Vec::default(); + + render_old_tree( + reference_tree, + target_tree, + geometries, + output_scale, + percentage, + is_swap_mode, + |mapped, elem_geometry, geo, alpha, is_minimizing| { + let window_elements = mapped.render_elements::>( + renderer, + geo.loc.as_logical().to_physical_precise_round(output_scale) - elem_geometry.loc, + Scale::from(output_scale), + alpha, + ); + + elements.extend(window_elements.into_iter().flat_map(|element| { + match element { + CosmicMappedRenderElement::Stack(elem) => constrain_render_elements( + std::iter::once(elem), + geo.loc.as_logical().to_physical_precise_round(output_scale) + - elem_geometry.loc, + geo.as_logical().to_physical_precise_round(output_scale), + elem_geometry, + ConstrainScaleBehavior::Stretch, + ConstrainAlign::CENTER, + output_scale, + ) + .next() + .map(CosmicMappedRenderElement::TiledStack), + CosmicMappedRenderElement::Window(elem) => constrain_render_elements( + std::iter::once(elem), + geo.loc.as_logical().to_physical_precise_round(output_scale) + - elem_geometry.loc, + geo.as_logical().to_physical_precise_round(output_scale), + elem_geometry, + ConstrainScaleBehavior::Stretch, + ConstrainAlign::CENTER, + output_scale, + ) + .next() + .map(CosmicMappedRenderElement::TiledWindow), + x => Some(x), + } + })); + if is_minimizing && indicator_thickness > 0 { + elements.push(CosmicMappedRenderElement::FocusIndicator( + IndicatorShader::focus_element( + renderer, + Key::Window(Usage::FocusIndicator, mapped.clone().key()), + geo, + indicator_thickness, + output_scale, + alpha, + [window_hint.red, window_hint.green, window_hint.blue], + ), + )); + } + }, + ); + + elements +} +fn render_old_tree( + reference_tree: &Tree, + target_tree: &Tree, + geometries: Option>>, + output_scale: f64, + percentage: f32, + is_swap_mode: bool, + mut processor: impl FnMut(&CosmicMapped, Rectangle, Rectangle, f32, bool), +) { if let Some(root) = reference_tree.root_node_id() { let geometries = geometries.unwrap_or_default(); reference_tree @@ -4779,71 +5019,71 @@ where }; let elem_geometry = mapped.geometry().to_physical_precise_round(output_scale); - let SplitRenderElements { - w_elements, - p_elements, - } = mapped.split_render_elements::>( - renderer, - geo.loc.as_logical().to_physical_precise_round(output_scale) - - elem_geometry.loc, - Scale::from(output_scale), - alpha, - ); - elements - .w_elements - .extend(w_elements.into_iter().flat_map(|element| { - match element { - CosmicMappedRenderElement::Stack(elem) => constrain_render_elements( - std::iter::once(elem), - geo.loc.as_logical().to_physical_precise_round(output_scale) - - elem_geometry.loc, - geo.as_logical().to_physical_precise_round(output_scale), - elem_geometry, - ConstrainScaleBehavior::Stretch, - ConstrainAlign::CENTER, - output_scale, - ) - .next() - .map(CosmicMappedRenderElement::TiledStack), - CosmicMappedRenderElement::Window(elem) => constrain_render_elements( - std::iter::once(elem), - geo.loc.as_logical().to_physical_precise_round(output_scale) - - elem_geometry.loc, - geo.as_logical().to_physical_precise_round(output_scale), - elem_geometry, - ConstrainScaleBehavior::Stretch, - ConstrainAlign::CENTER, - output_scale, - ) - .next() - .map(CosmicMappedRenderElement::TiledWindow), - x => Some(x), - } - })); - if minimize_geo.is_some() && indicator_thickness > 0 { - elements - .w_elements - .push(CosmicMappedRenderElement::FocusIndicator( - IndicatorShader::focus_element( - renderer, - Key::Window(Usage::FocusIndicator, mapped.clone().key()), - geo, - indicator_thickness, - output_scale, - alpha, - [window_hint.red, window_hint.green, window_hint.blue], - ), - )); - } - elements.p_elements.extend(p_elements); + processor(mapped, elem_geometry, geo, alpha, minimize_geo.is_some()) }); } +} - elements +fn render_new_tree_popups( + target_tree: &Tree, + reference_tree: Option<&Tree>, + renderer: &mut R, + geometries: Option>>, + old_geometries: Option>>, + seat: Option<&Seat>, + output: &Output, + percentage: f32, + overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree>)>), + swap_desc: Option, +) -> Vec> +where + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + ::TextureId: Send + Clone + 'static, + CosmicMappedRenderElement: RenderElement, + CosmicWindowRenderElement: RenderElement, + CosmicStackRenderElement: RenderElement, +{ + let mut popup_elements = Vec::new(); + let output_scale = output.current_scale().fractional_scale(); + + let is_active_output = seat + .map(|seat| &seat.active_output() == output) + .unwrap_or(false); + + let (_, swap_tree) = overview.1.unzip(); + let swap_desc = swap_desc.filter(|_| is_active_output); + let swap_tree = swap_tree.flatten().filter(|_| is_active_output); + + render_new_tree( + target_tree, + reference_tree, + geometries, + old_geometries, + percentage, + swap_tree, + swap_desc.as_ref(), + |_node_id, data, geo, _original_geo, alpha, _| { + if let Data::Mapped { mapped, .. } = data { + let elem_geometry = mapped.geometry().to_physical_precise_round(output_scale); + + popup_elements.extend( + mapped.popup_render_elements::>( + renderer, + geo.loc.as_logical().to_physical_precise_round(output_scale) + - elem_geometry.loc, + Scale::from(output_scale), + alpha, + ), + ); + } + }, + ); + + popup_elements } -fn render_new_tree( +fn render_new_tree_windows( target_tree: &Tree, reference_tree: Option<&Tree>, renderer: &mut R, @@ -4862,7 +5102,7 @@ fn render_new_tree( swapping_stack_surface_id: &Id, placeholder_id: &Id, theme: &cosmic::theme::CosmicTheme, -) -> SplitRenderElements> +) -> Vec> where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, ::TextureId: Send + Clone + 'static, @@ -4905,7 +5145,6 @@ where let mut animating_window_elements = Vec::new(); let mut window_elements = Vec::new(); - let mut popup_elements = Vec::new(); let mut group_backdrop = None; let mut indicators = Vec::new(); @@ -4916,10 +5155,11 @@ where let output_scale = output.current_scale().fractional_scale(); let (swap_indicator, swap_tree) = overview.1.unzip(); - let swap_tree = swap_tree.flatten().filter(|_| is_active_output); let swap_desc = swap_desc.filter(|_| is_active_output); + let swap_tree = swap_tree.flatten().filter(|_| is_active_output); let window_hint = crate::theme::active_window_hint(theme); let group_color = GROUP_COLOR; + // render placeholder, if we are swapping to an empty workspace if target_tree.root_node_id().is_none() && swap_desc.is_some() { window_elements.push( @@ -4968,165 +5208,48 @@ where (swap_geo.loc.as_logical() - window_geo.loc).to_physical_precise_round(output_scale); swap_elements.extend( - window - .render_elements::>( - renderer, - render_loc, - output_scale.into(), - 1.0, - ) - .into_iter() - .map(|window| { - CosmicMappedRenderElement::GrabbedWindow(RescaleRenderElement::from_element( - window, - swap_geo - .loc - .as_logical() - .to_physical_precise_round(output_scale), - ease( - Linear, - 1.0, - swap_factor(window_geo.size), - transition.unwrap_or(1.0), - ), - )) - }), + AsRenderElements::render_elements::>( + &window, + renderer, + render_loc, + output_scale.into(), + 1.0, + ) + .into_iter() + .map(|window| { + CosmicMappedRenderElement::GrabbedWindow(RescaleRenderElement::from_element( + window, + swap_geo + .loc + .as_logical() + .to_physical_precise_round(output_scale), + ease( + Linear, + 1.0, + swap_factor(window_geo.size), + transition.unwrap_or(1.0), + ), + )) + }), ) } // render actual tree nodes - let old_geometries = old_geometries.unwrap_or_default(); - let geometries = geometries.unwrap_or_default(); - target_tree - .root_node_id() - .into_iter() - .flat_map(|root| target_tree.traverse_pre_order_ids(root).unwrap()) - .map(|id| (target_tree, id)) - .chain( - swap_tree - .into_iter() - .flat_map(|tree| { - let sub_root = &swap_desc.as_ref().unwrap().node; - if swap_desc.as_ref().unwrap().stack_window.is_none() { - Some( - tree.traverse_pre_order_ids(sub_root) - .unwrap() - .map(move |id| (tree, id)), - ) - } else { - None - } - }) - .flatten(), - ) - .for_each(|(target_tree, node_id)| { - let data = target_tree.get(&node_id).unwrap().data(); - let (original_geo, scaled_geo) = (data.geometry(), geometries.get(&node_id)); - - let (old_original_geo, old_scaled_geo) = - if let Some(reference_tree) = reference_tree.as_ref() { - if let Some(root) = reference_tree.root_node_id() { - reference_tree - .traverse_pre_order_ids(root) - .unwrap() - .find(|id| &node_id == id) - .map(|node_id| { - ( - reference_tree.get(&node_id).unwrap().data().geometry(), - old_geometries.get(&node_id), - ) - }) - } else { - None - } - } else { - None - } - .unzip(); - let mut old_geo = old_original_geo.map(|original_geo| { - let (scale, offset) = old_scaled_geo - .unwrap() - .map(|adapted_geo| scale_to_center(original_geo, adapted_geo)) - .unwrap_or_else(|| (1.0.into(), (0, 0).into())); - ( - old_scaled_geo - .unwrap() - .map(|adapted_geo| { - Rectangle::from_loc_and_size( - adapted_geo.loc + offset, - ( - (original_geo.size.w as f64 * scale).round() as i32, - (original_geo.size.h as f64 * scale).round() as i32, - ), - ) - }) - .unwrap_or(*original_geo), - 1.0, - ) - }); - - let was_minimized = if let Data::Mapped { - minimize_rect: Some(minimize_rect), - .. - } = &data - { - old_geo = Some((*minimize_rect, (percentage * 2.0).min(1.0))); - true - } else { - false - }; - - let (scale, offset) = scaled_geo - .map(|adapted_geo| scale_to_center(original_geo, adapted_geo)) - .unwrap_or_else(|| (1.0.into(), (0, 0).into())); - let new_geo = scaled_geo - .map(|adapted_geo| { - Rectangle::from_loc_and_size( - adapted_geo.loc + offset, - ( - (original_geo.size.w as f64 * scale).round() as i32, - (original_geo.size.h as f64 * scale).round() as i32, - ), - ) - }) - .unwrap_or(*original_geo); - - let (geo, alpha, animating) = if let Some((old_geo, alpha)) = old_geo.filter(|_| { - swap_desc - .as_ref() - .map(|desc| desc.node != node_id && desc.stack_window.is_none()) - .unwrap_or(true) - }) { - ( - if was_minimized { - ease( - EaseInOutCubic, - EaseRectangle(old_geo), - EaseRectangle(new_geo), - percentage, - ) - .unwrap() - } else { - ease( - Linear, - EaseRectangle(old_geo), - EaseRectangle(new_geo), - percentage, - ) - .unwrap() - }, - alpha, - old_geo != new_geo, - ) - } else { - (new_geo, percentage, false) - }; - + render_new_tree( + target_tree, + reference_tree, + geometries, + old_geometries, + percentage, + swap_tree, + swap_desc.as_ref(), + |node_id, data, geo, original_geo, alpha, animating| { if swap_desc.as_ref().map(|desc| &desc.node) == Some(&node_id) || focused.as_ref() == Some(&node_id) { if indicator_thickness > 0 || data.is_group() { let mut geo = geo.clone(); + if data.is_group() { let outer_gap: i32 = (if is_overview { GAP_KEYBOARD } else { 4 } as f32 * percentage) @@ -5251,10 +5374,8 @@ where if let Data::Mapped { mapped, .. } = data { let elem_geometry = mapped.geometry().to_physical_precise_round(output_scale); - let SplitRenderElements { - mut w_elements, - p_elements, - } = mapped.split_render_elements::>( + + let mut elements = mapped.render_elements::>( renderer, //original_location, geo.loc.as_logical().to_physical_precise_round(output_scale) @@ -5262,6 +5383,7 @@ where Scale::from(output_scale), alpha, ); + if swap_desc .as_ref() .filter(|swap_desc| swap_desc.node == node_id) @@ -5284,7 +5406,7 @@ where { let mut geo = mapped.active_window_geometry().as_local(); geo.loc += original_geo.loc; - w_elements.insert( + elements.insert( 0, CosmicMappedRenderElement::Overlay(BackdropShader::element( renderer, @@ -5305,7 +5427,7 @@ where (ConstrainScaleBehavior::CutOff, ConstrainAlign::TOP_LEFT) }; - let w_elements = w_elements.into_iter().flat_map(|element| match element { + let elements = elements.into_iter().flat_map(|element| match element { CosmicMappedRenderElement::Stack(elem) => constrain_render_elements( std::iter::once(elem), geo.loc.as_logical().to_physical_precise_round(output_scale) @@ -5344,6 +5466,7 @@ where .map(CosmicMappedRenderElement::TiledOverlay), x => Some(x), }); + if swap_desc .as_ref() .map(|swap_desc| { @@ -5356,21 +5479,19 @@ where }) .unwrap_or(false) { - swap_elements.extend(w_elements); + swap_elements.extend(elements); } else { if animating { - animating_window_elements.extend(w_elements); + animating_window_elements.extend(elements); } else { - window_elements.extend(w_elements); - } - if !mapped.is_maximized(false) { - popup_elements.extend(p_elements); + window_elements.extend(elements); } } } - }); + }, + ); - window_elements = resize_elements + resize_elements .into_iter() .flatten() .chain(swap_elements) @@ -5378,12 +5499,147 @@ where .chain(window_elements) .chain(animating_window_elements) .chain(group_backdrop.into_iter().map(Into::into)) - .collect(); + .collect() +} - SplitRenderElements { - w_elements: window_elements, - p_elements: popup_elements, - } +fn render_new_tree( + target_tree: &Tree, + reference_tree: Option<&Tree>, + geometries: Option>>, + old_geometries: Option>>, + percentage: f32, + swap_tree: Option<&Tree>, + swap_desc: Option<&NodeDesc>, + mut processor: impl FnMut(NodeId, &Data, Rectangle, &Rectangle, f32, bool), +) { + let old_geometries = old_geometries.unwrap_or_default(); + let geometries = geometries.unwrap_or_default(); + target_tree + .root_node_id() + .into_iter() + .flat_map(|root| target_tree.traverse_pre_order_ids(root).unwrap()) + .map(|id| (target_tree, id)) + .chain( + swap_tree + .into_iter() + .flat_map(|tree| { + let sub_root = &swap_desc.unwrap().node; + if swap_desc.unwrap().stack_window.is_none() { + Some( + tree.traverse_pre_order_ids(sub_root) + .unwrap() + .map(move |id| (tree, id)), + ) + } else { + None + } + }) + .flatten(), + ) + .for_each(|(target_tree, node_id)| { + let data = target_tree.get(&node_id).unwrap().data(); + let (original_geo, scaled_geo) = (data.geometry(), geometries.get(&node_id)); + + let (old_original_geo, old_scaled_geo) = + if let Some(reference_tree) = reference_tree.as_ref() { + if let Some(root) = reference_tree.root_node_id() { + reference_tree + .traverse_pre_order_ids(root) + .unwrap() + .find(|id| &node_id == id) + .map(|node_id| { + ( + reference_tree.get(&node_id).unwrap().data().geometry(), + old_geometries.get(&node_id), + ) + }) + } else { + None + } + } else { + None + } + .unzip(); + let mut old_geo = old_original_geo.map(|original_geo| { + let (scale, offset) = old_scaled_geo + .unwrap() + .map(|adapted_geo| scale_to_center(original_geo, adapted_geo)) + .unwrap_or_else(|| (1.0.into(), (0, 0).into())); + ( + old_scaled_geo + .unwrap() + .map(|adapted_geo| { + Rectangle::from_loc_and_size( + adapted_geo.loc + offset, + ( + (original_geo.size.w as f64 * scale).round() as i32, + (original_geo.size.h as f64 * scale).round() as i32, + ), + ) + }) + .unwrap_or(*original_geo), + 1.0, + ) + }); + + let was_minimized = if let Data::Mapped { + minimize_rect: Some(minimize_rect), + .. + } = &data + { + old_geo = Some((*minimize_rect, (percentage * 2.0).min(1.0))); + true + } else { + false + }; + + let (scale, offset) = scaled_geo + .map(|adapted_geo| scale_to_center(original_geo, adapted_geo)) + .unwrap_or_else(|| (1.0.into(), (0, 0).into())); + let new_geo = scaled_geo + .map(|adapted_geo| { + Rectangle::from_loc_and_size( + adapted_geo.loc + offset, + ( + (original_geo.size.w as f64 * scale).round() as i32, + (original_geo.size.h as f64 * scale).round() as i32, + ), + ) + }) + .unwrap_or(*original_geo); + + let (geo, alpha, animating) = if let Some((old_geo, alpha)) = old_geo.filter(|_| { + swap_desc + .map(|desc| desc.node != node_id && desc.stack_window.is_none()) + .unwrap_or(true) + }) { + ( + if was_minimized { + ease( + EaseInOutCubic, + EaseRectangle(old_geo), + EaseRectangle(new_geo), + percentage, + ) + .unwrap() + } else { + ease( + Linear, + EaseRectangle(old_geo), + EaseRectangle(new_geo), + percentage, + ) + .unwrap() + }, + alpha, + old_geo != new_geo, + ) + } else { + (new_geo, percentage, false) + }; + + processor(node_id, data, geo, original_geo, alpha, animating) + }); } fn scale_to_center( diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 8da5c2f5..0d0ac5f1 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -1,7 +1,7 @@ use crate::{ backend::render::{ element::{AsGlowRenderer, FromGlesError}, - BackdropShader, SplitRenderElements, + BackdropShader, }, shell::{ layout::{floating::FloatingLayout, tiling::TilingLayout}, @@ -921,10 +921,6 @@ impl Workspace { .chain(self.tiling_layer.mapped().map(|(w, _)| w)) } - pub fn outputs(&self) -> impl Iterator { - self.floating_layer.space.outputs() - } - pub fn is_empty(&self) -> bool { self.floating_layer.mapped().next().is_none() && self.tiling_layer.mapped().next().is_none() @@ -1012,7 +1008,7 @@ impl Workspace { resize_indicator: Option<(ResizeMode, ResizeIndicator)>, indicator_thickness: u8, theme: &CosmicTheme, - ) -> Result>, OutputNotMapped> + ) -> Result>, OutputNotMapped> where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, ::TextureId: Send + Clone + 'static, @@ -1021,7 +1017,7 @@ impl Workspace { CosmicStackRenderElement: RenderElement, WorkspaceRenderElement: RenderElement, { - let mut elements = SplitRenderElements::default(); + let mut elements = Vec::default(); let output_scale = self.output.current_scale().fractional_scale(); let zone = { @@ -1104,26 +1100,19 @@ impl Workspace { y: target_geo.size.h as f64 / bbox.size.h as f64, }; - let SplitRenderElements { - w_elements, - p_elements, - } = fullscreen - .surface - .split_render_elements::>( - renderer, - render_loc, - output_scale.into(), - alpha, - ); - elements.w_elements.extend( - w_elements + elements.extend( + fullscreen + .surface + .render_elements::>( + renderer, + render_loc, + output_scale.into(), + alpha, + ) .into_iter() .map(|elem| RescaleRenderElement::from_element(elem, render_loc, scale)) .map(Into::into), ); - elements - .p_elements - .extend(p_elements.into_iter().map(Into::into)) } if self @@ -1155,16 +1144,18 @@ impl Workspace { OverviewMode::None => 1.0, }; - elements.extend_map( - self.floating_layer.render::( - renderer, - focused.as_ref(), - resize_indicator.clone(), - indicator_thickness, - alpha, - theme, - ), - WorkspaceRenderElement::from, + elements.extend( + self.floating_layer + .render::( + renderer, + focused.as_ref(), + resize_indicator.clone(), + indicator_thickness, + alpha, + theme, + ) + .into_iter() + .map(WorkspaceRenderElement::from), ); let alpha = match &overview.0 { @@ -1181,21 +1172,23 @@ impl Workspace { }; //tiling surfaces - elements.extend_map( - self.tiling_layer.render::( - renderer, - draw_focus_indicator, - zone, - overview, - resize_indicator, - indicator_thickness, - theme, - )?, - WorkspaceRenderElement::from, + elements.extend( + self.tiling_layer + .render::( + renderer, + draw_focus_indicator, + zone, + overview, + resize_indicator, + indicator_thickness, + theme, + )? + .into_iter() + .map(WorkspaceRenderElement::from), ); if let Some(alpha) = alpha { - elements.w_elements.push( + elements.push( Into::>::into(BackdropShader::element( renderer, self.backdrop_id.clone(), @@ -1214,6 +1207,159 @@ impl Workspace { Ok(elements) } + + #[profiling::function] + pub fn render_popups<'a, R>( + &self, + renderer: &mut R, + draw_focus_indicator: Option<&Seat>, + overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree>)>), + theme: &CosmicTheme, + ) -> Result>, OutputNotMapped> + where + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + ::TextureId: Send + Clone + 'static, + CosmicMappedRenderElement: RenderElement, + CosmicWindowRenderElement: RenderElement, + CosmicStackRenderElement: RenderElement, + WorkspaceRenderElement: RenderElement, + { + let mut elements = Vec::default(); + + let output_scale = self.output.current_scale().fractional_scale(); + let zone = { + let layer_map = layer_map_for_output(&self.output); + layer_map.non_exclusive_zone().as_local() + }; + + if let Some(fullscreen) = self.fullscreen.as_ref() { + // fullscreen window + let bbox = fullscreen.surface.bbox().as_local(); + let element_geo = Rectangle::from_loc_and_size( + self.element_for_surface(&fullscreen.surface) + .and_then(|elem| { + self.floating_layer + .element_geometry(elem) + .or_else(|| self.tiling_layer.element_geometry(elem)) + .map(|mut geo| { + geo.loc -= elem.geometry().loc.as_local(); + geo + }) + }) + .unwrap_or(bbox) + .loc, + fullscreen.original_geometry.size.as_local(), + ); + + let mut full_geo = + Rectangle::from_loc_and_size((0, 0), self.output.geometry().size.as_local()); + if fullscreen.start_at.is_none() { + if bbox != full_geo { + if bbox.size.w < full_geo.size.w { + full_geo.loc.x += (full_geo.size.w - bbox.size.w) / 2; + full_geo.size.w = bbox.size.w; + } + if bbox.size.h < full_geo.size.h { + full_geo.loc.y += (full_geo.size.h - bbox.size.h) / 2; + full_geo.size.h = bbox.size.h; + } + } + } + + let (target_geo, alpha) = match (fullscreen.start_at, fullscreen.ended_at) { + (Some(started), _) => { + let duration = Instant::now().duration_since(started).as_secs_f64() + / FULLSCREEN_ANIMATION_DURATION.as_secs_f64(); + ( + ease( + EaseInOutCubic, + EaseRectangle(element_geo), + EaseRectangle(full_geo), + duration, + ) + .0, + ease(EaseInOutCubic, 0.0, 1.0, duration), + ) + } + (_, Some(ended)) => { + let duration = Instant::now().duration_since(ended).as_secs_f64() + / FULLSCREEN_ANIMATION_DURATION.as_secs_f64(); + ( + ease( + EaseInOutCubic, + EaseRectangle(full_geo), + EaseRectangle(element_geo), + duration, + ) + .0, + ease(EaseInOutCubic, 1.0, 0.0, duration), + ) + } + (None, None) => (full_geo, 1.0), + }; + + let render_loc = target_geo + .loc + .as_logical() + .to_physical_precise_round(output_scale); + + elements.extend( + fullscreen + .surface + .popup_render_elements::>( + renderer, + render_loc, + output_scale.into(), + alpha, + ) + .into_iter() + .map(Into::into), + ); + } + + if self + .fullscreen + .as_ref() + .map(|f| f.start_at.is_some() || f.ended_at.is_some()) + .unwrap_or(true) + { + // floating surfaces + let alpha = match &overview.0 { + OverviewMode::Started(_, started) => { + (1.0 - (Instant::now().duration_since(*started).as_millis() + / ANIMATION_DURATION.as_millis()) as f32) + .max(0.0) + * 0.4 + + 0.6 + } + OverviewMode::Ended(_, ended) => { + ((Instant::now().duration_since(*ended).as_millis() + / ANIMATION_DURATION.as_millis()) as f32) + * 0.4 + + 0.6 + } + OverviewMode::Active(_) => 0.6, + OverviewMode::None => 1.0, + }; + + elements.extend( + self.floating_layer + .render_popups::(renderer, alpha) + .into_iter() + .map(WorkspaceRenderElement::from), + ); + + //tiling surfaces + elements.extend( + self.tiling_layer + .render_popups::(renderer, draw_focus_indicator, zone, overview, theme)? + .into_iter() + .map(WorkspaceRenderElement::from), + ); + } + + Ok(elements) + } } impl FocusStacks { From 0092dac08cff7af16c0fd90b9f11d0559163a135 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 10 Oct 2024 23:18:04 +0200 Subject: [PATCH 11/60] input: Use `render_input_order` --- src/input/mod.rs | 423 ++++++++++++++++++++----------- src/shell/element/mod.rs | 5 +- src/shell/element/stack.rs | 68 ++--- src/shell/element/window.rs | 31 +-- src/shell/focus/mod.rs | 9 +- src/shell/grabs/moving.rs | 4 +- src/shell/layout/floating/mod.rs | 118 ++++++++- src/shell/layout/tiling/mod.rs | 124 ++++++--- src/shell/mod.rs | 144 ++--------- src/shell/workspace.rs | 163 +++++++++++- 10 files changed, 707 insertions(+), 382 deletions(-) diff --git a/src/input/mod.rs b/src/input/mod.rs index 846fae87..44d7862f 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ + backend::render::ElementFilter, config::{ key_bindings::{ cosmic_keystate_from_smithay, cosmic_modifiers_eq_smithay, @@ -10,7 +11,11 @@ use crate::{ }, input::gestures::{GestureState, SwipeAction}, shell::{ - focus::target::{KeyboardFocusTarget, PointerFocusTarget}, + focus::{ + render_input_order, + target::{KeyboardFocusTarget, PointerFocusTarget}, + Stage, + }, grabs::{ReleaseMode, ResizeEdge}, layout::{ floating::ResizeGrabMarker, @@ -39,10 +44,7 @@ use smithay::{ TabletToolButtonEvent, TabletToolEvent, TabletToolProximityEvent, TabletToolTipEvent, TabletToolTipState, TouchEvent, }, - desktop::{ - layer_map_for_output, space::SpaceElement, utils::under_from_surface_tree, - WindowSurfaceType, - }, + desktop::{utils::under_from_surface_tree, WindowSurfaceType}, input::{ keyboard::{FilterResult, KeysymHandle, ModifiersState}, pointer::{ @@ -58,15 +60,13 @@ use smithay::{ reexports::{ input::Device as InputDevice, wayland_server::protocol::wl_shm::Format as ShmFormat, }, - utils::{Point, Serial, SERIAL_COUNTER}, + utils::{Point, Rectangle, Serial, SERIAL_COUNTER}, wayland::{ keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat, pointer_constraints::{with_pointer_constraint, PointerConstraint}, seat::WaylandFocus, - shell::wlr_layer::Layer as WlrLayer, tablet_manager::{TabletDescriptor, TabletSeatTrait}, }, - xwayland::X11Surface, }; use tracing::{error, trace}; use xkbcommon::xkb::{Keycode, Keysym}; @@ -76,6 +76,7 @@ use std::{ borrow::Cow, cell::RefCell, collections::HashSet, + ops::ControlFlow, time::{Duration, Instant}, }; @@ -329,9 +330,8 @@ impl State { } else if self.common.config.cosmic_conf.focus_follows_cursor { let shell = self.common.shell.read().unwrap(); let old_keyboard_target = - shell.keyboard_target_from_position(original_position, ¤t_output); - let new_keyboard_target = - shell.keyboard_target_from_position(position, &output); + State::element_under(original_position, ¤t_output, &*shell); + let new_keyboard_target = State::element_under(position, &output, &*shell); if old_keyboard_target != new_keyboard_target && new_keyboard_target.is_some() @@ -497,7 +497,8 @@ impl State { }); } - let shell = self.common.shell.read().unwrap(); + let mut shell = self.common.shell.write().unwrap(); + shell.update_pointer_position(position.to_local(&output), &output); if output != current_output { for session in cursor_sessions_for_output(&*shell, ¤t_output) { @@ -638,15 +639,15 @@ impl State { let global_position = seat.get_pointer().unwrap().current_location().as_global(); - let shell = self.common.shell.write().unwrap(); - let under = shell.keyboard_target_from_position(global_position, &output); - // Don't check override redirect windows, because we don't set keyboard focus to them explicitly. - // These cases are handled by the XwaylandKeyboardGrab. - if let Some(target) = shell.element_under(global_position, &output) { - if seat.get_keyboard().unwrap().modifier_state().logo - && !shortcuts_inhibited - { - if let Some(surface) = target.toplevel().map(Cow::into_owned) { + let under = { + let shell = self.common.shell.read().unwrap(); + State::element_under(global_position, &output, &shell) + }; + if let Some(target) = under { + if let Some(surface) = target.toplevel().map(Cow::into_owned) { + if seat.get_keyboard().unwrap().modifier_state().logo + && !shortcuts_inhibited + { let seat_clone = seat.clone(); let mouse_button = PointerButtonEvent::button(&event); @@ -754,10 +755,9 @@ impl State { } } } - } - std::mem::drop(shell); - Shell::set_focus(self, under.as_ref(), &seat, Some(serial), false); + Shell::set_focus(self, Some(&target), &seat, Some(serial), false); + } } } else { let mut shell = self.common.shell.write().unwrap(); @@ -1814,142 +1814,263 @@ impl State { } } - // TODO: Try to get rid of the *mutable* Shell references (needed for hovered_stack in floating_layout) + pub fn element_under( + global_pos: Point, + output: &Output, + shell: &Shell, + ) -> Option { + let (previous_workspace, workspace) = shell.workspaces.active(output); + let (previous_idx, idx) = shell.workspaces.active_num(output); + let previous_workspace = previous_workspace + .zip(previous_idx) + .map(|((w, start), idx)| (w.handle, idx, start)); + let workspace = (workspace.handle, idx); + let element_filter = if workspace_overview_is_open(output) { + ElementFilter::LayerShellOnly + } else { + ElementFilter::All + }; + + render_input_order( + shell, + output, + previous_workspace, + workspace, + element_filter, + |stage| { + match stage { + Stage::SessionLock(lock_surface) => { + return ControlFlow::Break(Ok(lock_surface + .cloned() + .map(KeyboardFocusTarget::LockSurface))) + } + Stage::LayerPopup { + layer, + popup, + location, + } => { + if layer.can_receive_keyboard_focus() { + let surface = popup.wl_surface(); + if under_from_surface_tree( + surface, + global_pos.as_logical(), + location.as_logical(), + WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, + ) + .is_some() + { + return ControlFlow::Break(Ok(Some( + KeyboardFocusTarget::LayerSurface(layer), + ))); + } + } + } + Stage::LayerSurface { layer, location } => { + if layer.can_receive_keyboard_focus() { + if under_from_surface_tree( + layer.wl_surface(), + global_pos.as_logical(), + location.as_logical(), + WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, + ) + .is_some() + { + return ControlFlow::Break(Ok(Some( + KeyboardFocusTarget::LayerSurface(layer), + ))); + } + } + } + Stage::OverrideRedirect { .. } => { + // Override redirect windows take a grab on their own via + // the Xwayland keyboard grab protocol. Don't focus them via click. + } + Stage::StickyPopups(layout) => { + if let Some(element) = + layout.popup_element_under(global_pos.to_local(output)) + { + return ControlFlow::Break(Ok(Some(element))); + } + } + Stage::Sticky(layout) => { + if let Some(element) = + layout.toplevel_element_under(global_pos.to_local(output)) + { + return ControlFlow::Break(Ok(Some(element))); + } + } + Stage::WorkspacePopups { workspace, offset } => { + let location = global_pos + offset.as_global().to_f64(); + let output = workspace.output(); + let output_geo = output.geometry().to_local(output); + if Rectangle::from_loc_and_size(offset.as_local(), output_geo.size) + .intersection(output_geo) + .is_some_and(|geometry| { + geometry.contains(global_pos.to_local(output).to_i32_round()) + }) + { + if let Some(element) = workspace.popup_element_under(location) { + return ControlFlow::Break(Ok(Some(element))); + } + } + } + Stage::Workspace { workspace, offset } => { + let location = global_pos + offset.as_global().to_f64(); + let output = workspace.output(); + let output_geo = output.geometry().to_local(output); + if Rectangle::from_loc_and_size(offset.as_local(), output_geo.size) + .intersection(output_geo) + .is_some_and(|geometry| { + geometry.contains(global_pos.to_local(output).to_i32_round()) + }) + { + if let Some(element) = workspace.toplevel_element_under(location) { + return ControlFlow::Break(Ok(Some(element))); + } + } + } + } + ControlFlow::Continue(()) + }, + ) + .ok() + .flatten() + } + pub fn surface_under( global_pos: Point, output: &Output, - shell: &mut Shell, + shell: &Shell, ) -> Option<(PointerFocusTarget, Point)> { - let session_lock = shell.session_lock.as_ref(); + let (previous_workspace, workspace) = shell.workspaces.active(output); + let (previous_idx, idx) = shell.workspaces.active_num(output); + let previous_workspace = previous_workspace + .zip(previous_idx) + .map(|((w, start), idx)| (w.handle, idx, start)); + let workspace = (workspace.handle, idx); + + let element_filter = if workspace_overview_is_open(output) { + ElementFilter::LayerShellOnly + } else { + ElementFilter::All + }; + let relative_pos = global_pos.to_local(output); let output_geo = output.geometry(); - - if let Some(session_lock) = session_lock { - return session_lock.surfaces.get(output).map(|surface| { - ( - PointerFocusTarget::WlSurface { - surface: surface.wl_surface().clone(), - toplevel: None, - }, - output_geo.loc.to_f64(), - ) - }); - } - - if let Some(window) = shell.workspaces.active(output).1.get_fullscreen() { - let layers = layer_map_for_output(output); - if let Some(layer) = layers.layer_under(WlrLayer::Overlay, relative_pos.as_logical()) { - let layer_loc = layers.layer_geometry(layer).unwrap().loc; - if let Some((wl_surface, surface_loc)) = layer.surface_under( - relative_pos.as_logical() - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) { - return Some(( - PointerFocusTarget::WlSurface { - surface: wl_surface, - toplevel: None, - }, - (output_geo.loc + layer_loc.as_global() + surface_loc.as_global()).to_f64(), - )); - } - } - if let Some((surface, geo)) = shell - .override_redirect_windows - .iter() - .find(|or| { - or.is_in_input_region( - &(global_pos.as_logical() - X11Surface::geometry(*or).loc.to_f64()), - ) - }) - .and_then(|or| { - or.wl_surface() - .map(|surface| (surface, X11Surface::geometry(or).loc.as_global().to_f64())) - }) - { - return Some(( - PointerFocusTarget::WlSurface { - surface, - toplevel: None, - }, - geo, - )); - } - PointerFocusTarget::under_surface(window, relative_pos.as_logical()).map( - |(target, surface_loc)| { - (target, (output_geo.loc + surface_loc.as_global()).to_f64()) - }, - ) - } else { - { - let layers = layer_map_for_output(output); - if let Some(layer) = layers - .layer_under(WlrLayer::Overlay, relative_pos.as_logical()) - .or_else(|| layers.layer_under(WlrLayer::Top, relative_pos.as_logical())) - { - let layer_loc = layers.layer_geometry(layer).unwrap().loc; - if let Some((wl_surface, surface_loc)) = layer.surface_under( - relative_pos.as_logical() - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) { - return Some(( - PointerFocusTarget::WlSurface { - surface: wl_surface, - toplevel: None, - }, - (output_geo.loc + layer_loc.as_global() + surface_loc.as_global()) - .to_f64(), - )); + let overview = shell.overview_mode().0; + + render_input_order( + shell, + output, + previous_workspace, + workspace, + element_filter, + |stage| { + match stage { + Stage::SessionLock(lock_surface) => { + return ControlFlow::Break(Ok(lock_surface.map(|surface| { + ( + PointerFocusTarget::WlSurface { + surface: surface.wl_surface().clone(), + toplevel: None, + }, + output_geo.loc.to_f64(), + ) + }))); } - } - } - if let Some((surface, geo)) = shell - .override_redirect_windows - .iter() - .find(|or| { - or.is_in_input_region( - &(global_pos.as_logical() - X11Surface::geometry(*or).loc.to_f64()), - ) - }) - .and_then(|or| { - or.wl_surface() - .map(|surface| (surface, X11Surface::geometry(or).loc.as_global().to_f64())) - }) - { - return Some(( - PointerFocusTarget::WlSurface { - surface, - toplevel: None, - }, - geo, - )); - } - if let Some((target, loc)) = shell.surface_under(global_pos, output) { - return Some((target, loc)); - } - { - let layers = layer_map_for_output(output); - if let Some(layer) = layers - .layer_under(WlrLayer::Bottom, relative_pos.as_logical()) - .or_else(|| layers.layer_under(WlrLayer::Background, relative_pos.as_logical())) - { - let layer_loc = layers.layer_geometry(layer).unwrap().loc; - if let Some((wl_surface, surface_loc)) = layer.surface_under( - relative_pos.as_logical() - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) { - return Some(( - PointerFocusTarget::WlSurface { - surface: wl_surface, - toplevel: None, - }, - (output_geo.loc + layer_loc.as_global() + surface_loc.as_global()) - .to_f64(), - )); + Stage::LayerPopup { + popup, location, .. + } => { + let surface = popup.wl_surface(); + if let Some((surface, surface_loc)) = under_from_surface_tree( + surface, + global_pos.as_logical(), + location.as_logical(), + WindowSurfaceType::ALL, + ) { + return ControlFlow::Break(Ok(Some(( + PointerFocusTarget::WlSurface { + surface, + toplevel: None, + }, + surface_loc.as_global().to_f64(), + )))); + } + } + Stage::LayerSurface { layer, location } => { + let surface = layer.wl_surface(); + if let Some((surface, surface_loc)) = under_from_surface_tree( + surface, + global_pos.as_logical(), + location.as_logical(), + WindowSurfaceType::ALL, + ) { + return ControlFlow::Break(Ok(Some(( + PointerFocusTarget::WlSurface { + surface, + toplevel: None, + }, + surface_loc.as_global().to_f64(), + )))); + } + } + Stage::OverrideRedirect { surface, location } => { + if let Some(surface) = surface.wl_surface() { + if let Some((surface, surface_loc)) = under_from_surface_tree( + &surface, + global_pos.as_logical(), + location.as_logical(), + WindowSurfaceType::ALL, + ) { + return ControlFlow::Break(Ok(Some(( + PointerFocusTarget::WlSurface { + surface, + toplevel: None, + }, + surface_loc.as_global().to_f64(), + )))); + } + } + } + Stage::StickyPopups(floating_layer) => { + if let Some(under) = floating_layer + .popup_surface_under(relative_pos) + .map(|(target, point)| (target, point.to_global(output))) + { + return ControlFlow::Break(Ok(Some(under))); + } + } + Stage::Sticky(floating_layer) => { + if let Some(under) = floating_layer + .toplevel_surface_under(relative_pos) + .map(|(target, point)| (target, point.to_global(output))) + { + return ControlFlow::Break(Ok(Some(under))); + } + } + Stage::WorkspacePopups { workspace, offset } => { + let global_pos = global_pos + offset.to_f64().as_global(); + if let Some(under) = + workspace.popup_surface_under(global_pos, overview.clone()) + { + return ControlFlow::Break(Ok(Some(under))); + } + } + Stage::Workspace { workspace, offset } => { + let global_pos = global_pos + offset.to_f64().as_global(); + if let Some(under) = + workspace.toplevel_surface_under(global_pos, overview.clone()) + { + return ControlFlow::Break(Ok(Some(under))); + } } } - } - None - } + + ControlFlow::Continue(()) + }, + ) + .ok() + .flatten() } } diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index 0860c07d..2a8cebab 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -302,10 +302,11 @@ impl CosmicMapped { pub fn focus_under( &self, relative_pos: Point, + surface_type: WindowSurfaceType, ) -> Option<(PointerFocusTarget, Point)> { match &self.element { - CosmicMappedInternal::Stack(stack) => stack.focus_under(relative_pos), - CosmicMappedInternal::Window(window) => window.focus_under(relative_pos), + CosmicMappedInternal::Stack(stack) => stack.focus_under(relative_pos, surface_type), + CosmicMappedInternal::Window(window) => window.focus_under(relative_pos, surface_type), _ => unreachable!(), } } diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index 388eb813..3a0597f6 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -420,48 +420,52 @@ impl CosmicStack { pub fn focus_under( &self, mut relative_pos: Point, + surface_type: WindowSurfaceType, ) -> Option<(PointerFocusTarget, Point)> { self.0.with_program(|p| { let mut stack_ui = None; let geo = p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)].geometry(); - let point_i32 = relative_pos.to_i32_round::(); - if (point_i32.x - geo.loc.x >= -RESIZE_BORDER && point_i32.x - geo.loc.x < 0) - || (point_i32.y - geo.loc.y >= -RESIZE_BORDER && point_i32.y - geo.loc.y < 0) - || (point_i32.x - geo.loc.x >= geo.size.w - && point_i32.x - geo.loc.x < geo.size.w + RESIZE_BORDER) - || (point_i32.y - geo.loc.y >= geo.size.h - && point_i32.y - geo.loc.y < geo.size.h + TAB_HEIGHT + RESIZE_BORDER) - { - stack_ui = Some(( - PointerFocusTarget::StackUI(self.clone()), - Point::from((0., 0.)), - )); - } + if surface_type.contains(WindowSurfaceType::TOPLEVEL) { + let point_i32 = relative_pos.to_i32_round::(); + if (point_i32.x - geo.loc.x >= -RESIZE_BORDER && point_i32.x - geo.loc.x < 0) + || (point_i32.y - geo.loc.y >= -RESIZE_BORDER && point_i32.y - geo.loc.y < 0) + || (point_i32.x - geo.loc.x >= geo.size.w + && point_i32.x - geo.loc.x < geo.size.w + RESIZE_BORDER) + || (point_i32.y - geo.loc.y >= geo.size.h + && point_i32.y - geo.loc.y < geo.size.h + TAB_HEIGHT + RESIZE_BORDER) + { + stack_ui = Some(( + PointerFocusTarget::StackUI(self.clone()), + Point::from((0., 0.)), + )); + } - if point_i32.y - geo.loc.y < TAB_HEIGHT { - stack_ui = Some(( - PointerFocusTarget::StackUI(self.clone()), - Point::from((0., 0.)), - )); + if point_i32.y - geo.loc.y < TAB_HEIGHT { + stack_ui = Some(( + PointerFocusTarget::StackUI(self.clone()), + Point::from((0., 0.)), + )); + } } relative_pos.y -= TAB_HEIGHT as f64; let active_window = &p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)]; - active_window - .0 - .surface_under(relative_pos, WindowSurfaceType::ALL) - .map(|(surface, surface_offset)| { - ( - PointerFocusTarget::WlSurface { - surface, - toplevel: Some(active_window.clone().into()), - }, - surface_offset.to_f64() + Point::from((0., TAB_HEIGHT as f64)), - ) - }) - .or(stack_ui) + stack_ui.or_else(|| { + active_window + .0 + .surface_under(relative_pos, surface_type) + .map(|(surface, surface_offset)| { + ( + PointerFocusTarget::WlSurface { + surface, + toplevel: Some(active_window.clone().into()), + }, + surface_offset.to_f64() + Point::from((0., TAB_HEIGHT as f64)), + ) + }) + }) }) } @@ -1034,7 +1038,7 @@ impl SpaceElement for CosmicStack { }) } fn is_in_input_region(&self, point: &Point) -> bool { - self.focus_under(*point).is_some() + self.focus_under(*point, WindowSurfaceType::ALL).is_some() } fn set_activate(&self, activated: bool) { SpaceElement::set_activate(&self.0, activated); diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index 9070994c..97d24e55 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -243,11 +243,12 @@ impl CosmicWindow { pub fn focus_under( &self, mut relative_pos: Point, + surface_type: WindowSurfaceType, ) -> Option<(PointerFocusTarget, Point)> { self.0.with_program(|p| { let mut offset = Point::from((0., 0.)); let mut window_ui = None; - if p.has_ssd(false) { + if p.has_ssd(false) && surface_type.contains(WindowSurfaceType::TOPLEVEL) { let geo = p.window.geometry(); let point_i32 = relative_pos.to_i32_round::(); @@ -275,19 +276,19 @@ impl CosmicWindow { offset.y += SSD_HEIGHT as f64; } - p.window - .0 - .surface_under(relative_pos, WindowSurfaceType::ALL) - .map(|(surface, surface_offset)| { - ( - PointerFocusTarget::WlSurface { - surface, - toplevel: Some(p.window.clone().into()), - }, - (offset + surface_offset.to_f64()), - ) - }) - .or(window_ui) + window_ui.or_else(|| { + p.window.0.surface_under(relative_pos, surface_type).map( + |(surface, surface_offset)| { + ( + PointerFocusTarget::WlSurface { + surface, + toplevel: Some(p.window.clone().into()), + }, + (offset + surface_offset.to_f64()), + ) + }, + ) + }) }) } @@ -561,7 +562,7 @@ impl SpaceElement for CosmicWindow { }) } fn is_in_input_region(&self, point: &Point) -> bool { - self.focus_under(*point).is_some() + self.focus_under(*point, WindowSurfaceType::ALL).is_some() } fn set_activate(&self, activated: bool) { if self diff --git a/src/shell/focus/mod.rs b/src/shell/focus/mod.rs index a3b855a3..9f3bc71a 100644 --- a/src/shell/focus/mod.rs +++ b/src/shell/focus/mod.rs @@ -232,8 +232,8 @@ fn update_focus_state( if should_update_cursor && state.common.config.cosmic_conf.cursor_follows_focus { if target.is_some() { //need to borrow mutably for surface under - let mut shell = state.common.shell.write().unwrap(); - // get geometry of the target element + let shell = state.common.shell.read().unwrap(); + // get the top left corner of the target element let geometry = shell.focused_geometry(target.unwrap()); if let Some(geometry) = geometry { // get the center of the target element @@ -247,10 +247,9 @@ fn update_focus_state( .cloned() .unwrap_or(seat.active_output()); - let focus = shell - .surface_under(new_pos, &output) + let focus = State::surface_under(new_pos, &output, &*shell) .map(|(focus, loc)| (focus, loc.as_logical())); - //drop here to avoid multiple mutable borrows + //drop here to avoid multiple borrows mem::drop(shell); seat.get_pointer().unwrap().motion( state, diff --git a/src/shell/grabs/moving.rs b/src/shell/grabs/moving.rs index 25106fa6..3bbb83ab 100644 --- a/src/shell/grabs/moving.rs +++ b/src/shell/grabs/moving.rs @@ -27,7 +27,7 @@ use smithay::{ ImportAll, ImportMem, Renderer, }, }, - desktop::{layer_map_for_output, space::SpaceElement}, + desktop::{layer_map_for_output, space::SpaceElement, WindowSurfaceType}, input::{ pointer::{ AxisFrame, ButtonEvent, CursorIcon, GestureHoldBeginEvent, GestureHoldEndEvent, @@ -864,7 +864,7 @@ impl Drop for MoveGrab { let current_location = pointer.current_location(); if let Some((target, offset)) = - mapped.focus_under(current_location - position.as_logical().to_f64()) + mapped.focus_under(current_location - position.as_logical().to_f64(), WindowSurfaceType::ALL) { pointer.motion( state, diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 5c80fe62..a3f10355 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -728,16 +728,115 @@ impl FloatingLayout { self.space.element_geometry(elem).map(RectExt::as_local) } - pub fn element_under(&self, location: Point) -> Option { + pub fn popup_element_under(&self, location: Point) -> Option { self.space - .element_under(location.as_logical()) - .map(|(mapped, _)| mapped.clone().into()) + .elements() + .rev() + .map(|e| (e, self.space.element_location(e).unwrap() - e.geometry().loc)) + .filter(|(e, render_location)| { + let mut bbox = e.bbox(); + bbox.loc += *render_location; + bbox.to_f64().contains(location.as_logical()) + }) + .find_map(|(e, render_location)| { + let render_location = render_location + .as_local() + .to_f64(); + let point = location - render_location; + if e.focus_under(point.as_logical(), WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE).is_some() { + Some(e.clone().into()) + } else { + None + } + }) + } + + pub fn toplevel_element_under(&self, location: Point) -> Option { + self.space + .elements() + .rev() + .map(|e| (e, self.space.element_location(e).unwrap() - e.geometry().loc)) + .filter(|(e, render_location)| { + let mut bbox = e.bbox(); + bbox.loc += *render_location; + bbox.to_f64().contains(location.as_logical()) + }) + .find_map(|(e, render_location)| { + let render_location = render_location + .as_local() + .to_f64(); + let point = location - render_location; + if e.focus_under(point.as_logical(), WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE).is_some() { + Some(e.clone().into()) + } else { + None + } + }) } - pub fn surface_under( - &mut self, + pub fn popup_surface_under( + &self, + location: Point, + ) -> Option<(PointerFocusTarget, Point)> { + self.space + .elements() + .rev() + .map(|e| (e, self.space.element_location(e).unwrap() - e.geometry().loc)) + .filter(|(e, render_location)| { + let mut bbox = e.bbox(); + bbox.loc += *render_location; + bbox.to_f64().contains(location.as_logical()) + }) + .find_map(|(e, render_location)| { + let render_location = render_location + .as_local() + .to_f64(); + let point = location - render_location; + e.focus_under( + point.as_logical(), + WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, + ) + .map(|(surface, surface_offset)| { + (surface, render_location + surface_offset.as_local()) + }) + }) + } + + pub fn toplevel_surface_under( + &self, location: Point, ) -> Option<(PointerFocusTarget, Point)> { + self.space + .elements() + .rev() + .map(|e| (e, self.space.element_location(e).unwrap() - e.geometry().loc)) + .filter(|(e, render_location)| { + let mut bbox = e.bbox(); + bbox.loc += *render_location; + bbox.to_f64().contains(location.as_logical()) + }) + .find_map(|(e, render_location)| { + let render_location = render_location.as_local().to_f64(); + let point = location - render_location; + e.focus_under( + point.as_logical(), + WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, + ) + .map(|(surface, surface_offset)| { + ( + surface, + render_location + surface_offset.as_local(), + ) + }) + }) + } + + pub fn update_pointer_position(&mut self, location: Option>) { + let Some(location) = location else { + self.hovered_stack.take(); + return; + }; + let res = self .space .element_under(location.as_logical()) @@ -754,15 +853,6 @@ impl FloatingLayout { } else { self.hovered_stack.take(); } - - res.and_then(|(element, space_offset)| { - let point = location - space_offset.to_f64(); - element - .focus_under(point.as_logical()) - .map(|(surface, surface_offset)| { - (surface, space_offset.to_f64() + surface_offset.as_local()) - }) - }) } pub fn stacking_indicator(&self) -> Option> { diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 308f8f8e..c814f304 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -56,7 +56,7 @@ use smithay::{ glow::GlowRenderer, ImportAll, ImportMem, Renderer, }, - desktop::{layer_map_for_output, space::SpaceElement, PopupKind}, + desktop::{layer_map_for_output, space::SpaceElement, PopupKind, WindowSurfaceType}, input::Seat, output::Output, reexports::wayland_server::Client, @@ -3104,17 +3104,38 @@ impl TilingLayout { None } + + pub fn popup_element_under(&self, location_f64: Point) -> Option { + let location = location_f64.to_i32_round(); + + for (mapped, geo) in self.mapped() { + if !mapped.bbox().contains((location - geo.loc).as_logical()) { + continue; + } - pub fn element_under(&self, location_f64: Point) -> Option { + if mapped.focus_under( + (location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64(), + WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, + ).is_some() { + return Some(mapped.clone().into()); + } + } + + None + } + + pub fn toplevel_element_under(&self, location_f64: Point) -> Option { let location = location_f64.to_i32_round(); for (mapped, geo) in self.mapped() { if !mapped.bbox().contains((location - geo.loc).as_logical()) { continue; } - if mapped.is_in_input_region( - &((location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64()), - ) { + + if mapped.focus_under( + (location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64(), + WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, + ).is_some() { return Some(mapped.clone().into()); } } @@ -3122,33 +3143,42 @@ impl TilingLayout { None } - pub fn surface_under( - &mut self, + pub fn popup_surface_under( + &self, location_f64: Point, overview: OverviewMode, ) -> Option<(PointerFocusTarget, Point)> { - let gaps = self.gaps(); - let last_overview_hover = &mut self.last_overview_hover; - let placeholder_id = &self.placeholder_id; - let tree = &self.queue.trees.back().unwrap().0; - let root = tree.root_node_id()?; let location = location_f64.to_i32_round(); - { - let output_geo = - Rectangle::from_loc_and_size((0, 0), self.output.geometry().size.as_logical()) - .as_local(); - if !output_geo.contains(location) { - return None; + if matches!(overview, OverviewMode::None) { + for (mapped, geo) in self.mapped() { + if !mapped.bbox().contains((location - geo.loc).as_logical()) { + continue; + } + if let Some((target, surface_offset)) = mapped.focus_under( + (location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64(), + WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, + ) { + return Some(( + target, + geo.loc.to_f64() - mapped.geometry().loc.as_local().to_f64() + + surface_offset.as_local(), + )); + } } } - if !matches!( - overview, - OverviewMode::Started(_, _) | OverviewMode::Active(_) - ) { - last_overview_hover.take(); - } + None + } + + pub fn toplevel_surface_under( + &self, + location_f64: Point, + overview: OverviewMode, + ) -> Option<(PointerFocusTarget, Point)> { + let tree = &self.queue.trees.back().unwrap().0; + let root = tree.root_node_id()?; + let location = location_f64.to_i32_round(); if matches!(overview, OverviewMode::None) { for (mapped, geo) in self.mapped() { @@ -3157,6 +3187,7 @@ impl TilingLayout { } if let Some((target, surface_offset)) = mapped.focus_under( (location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64(), + WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, ) { return Some(( target, @@ -3204,7 +3235,10 @@ impl TilingLayout { + mapped.geometry().loc.to_f64().as_local()) .as_logical(); mapped - .focus_under(test_point) + .focus_under( + test_point, + WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, + ) .map(|(surface, surface_offset)| { ( surface, @@ -3257,7 +3291,37 @@ impl TilingLayout { } _ => None, } - } else if matches!( + } else { + None + } + } + + pub fn update_pointer_position( + &mut self, + location_f64: Option>, + overview: OverviewMode, + ) { + let gaps = self.gaps(); + let last_overview_hover = &mut self.last_overview_hover; + let placeholder_id = &self.placeholder_id; + let tree = &self.queue.trees.back().unwrap().0; + let Some(root) = tree.root_node_id() else { + return; + }; + + if !matches!( + overview, + OverviewMode::Started(_, _) | OverviewMode::Active(_) + ) || location_f64.is_none() + { + last_overview_hover.take(); + return; + } + + let location_f64 = location_f64.unwrap(); + let location = location_f64.to_i32_round(); + + if matches!( overview.active_trigger(), Some(Trigger::Pointer(_) | Trigger::Touch(_)) ) { @@ -3319,7 +3383,9 @@ impl TilingLayout { } if let Some(res_id) = result { - let mut last_geometry = *geometries.get(&res_id)?; + let Some(mut last_geometry) = geometries.get(&res_id).copied() else { + return; + }; let node = tree.get(&res_id).unwrap(); let data = node.data().clone(); @@ -3716,10 +3782,6 @@ impl TilingLayout { } } } - - None - } else { - None } } diff --git a/src/shell/mod.rs b/src/shell/mod.rs index ca5ec2f8..e8e422d5 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -54,8 +54,6 @@ use smithay::{ xwayland::X11Surface, }; -use smithay::wayland::shell::wlr_layer::Layer as WlrLayer; - use crate::{ backend::render::animations::spring::{Spring, SpringParams}, config::Config, @@ -1532,97 +1530,6 @@ impl Shell { } } - /// Derives a keyboard focus target from a global position, and indicates whether the - /// the shell should start a move request event. Used during cursor related focus checks - pub fn keyboard_target_from_position( - &self, - global_position: Point, - output: &Output, - ) -> Option { - let relative_pos = global_position.to_local(output); - - let mut under: Option = None; - // if the lockscreen is active - if let Some(session_lock) = self.session_lock.as_ref() { - under = session_lock - .surfaces - .get(output) - .map(|lock| lock.clone().into()); - // if the output can receive keyboard focus - } else if let Some(window) = self.active_space(output).get_fullscreen() { - let layers = layer_map_for_output(output); - if let Some(layer) = layers.layer_under(WlrLayer::Overlay, relative_pos.as_logical()) { - let layer_loc = layers.layer_geometry(layer).unwrap().loc; - if layer.can_receive_keyboard_focus() - && layer - .surface_under( - relative_pos.as_logical() - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) - .is_some() - { - under = Some(layer.clone().into()); - } - } else { - under = Some(window.clone().into()); - } - } else { - let done = { - let layers = layer_map_for_output(output); - if let Some(layer) = layers - .layer_under(WlrLayer::Overlay, relative_pos.as_logical()) - .or_else(|| layers.layer_under(WlrLayer::Top, relative_pos.as_logical())) - { - let layer_loc = layers.layer_geometry(layer).unwrap().loc; - if layer.can_receive_keyboard_focus() - && layer - .surface_under( - relative_pos.as_logical() - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) - .is_some() - { - under = Some(layer.clone().into()); - true - } else { - false - } - } else { - false - } - }; - if !done { - // Don't check override redirect windows, because we don't set keyboard focus to them explicitly. - // These cases are handled by the XwaylandKeyboardGrab. - if let Some(target) = self.element_under(global_position, output) { - under = Some(target); - } else { - let layers = layer_map_for_output(output); - if let Some(layer) = layers - .layer_under(WlrLayer::Bottom, relative_pos.as_logical()) - .or_else(|| { - layers.layer_under(WlrLayer::Background, relative_pos.as_logical()) - }) - { - let layer_loc = layers.layer_geometry(layer).unwrap().loc; - if layer.can_receive_keyboard_focus() - && layer - .surface_under( - relative_pos.as_logical() - layer_loc.to_f64(), - WindowSurfaceType::ALL, - ) - .is_some() - { - under = Some(layer.clone().into()); - } - }; - } - } - } - - under - } - /// Coerce a keyboard focus target into a CosmicMapped element. This is useful when performing window specific /// actions, such as closing a window pub fn focused_element(&self, focus_target: &KeyboardFocusTarget) -> Option { @@ -2053,6 +1960,26 @@ impl Shell { self.pending_windows.retain(|(s, _, _)| s.alive()); } + pub fn update_pointer_position(&mut self, location: Point, output: &Output) { + for (o, set) in self.workspaces.sets.iter_mut() { + if o == output { + set.sticky_layer.update_pointer_position(Some(location)); + for (i, workspace) in set.workspaces.iter_mut().enumerate() { + if i == set.active { + workspace.update_pointer_position(Some(location), self.overview_mode.clone()); + } else { + workspace.update_pointer_position(None, self.overview_mode.clone()); + } + } + } else { + set.sticky_layer.update_pointer_position(None); + for workspace in &mut set.workspaces { + workspace.update_pointer_position(None, self.overview_mode.clone()); + } + } + } + } + pub fn remap_unfullscreened_window( &mut self, mapped: CosmicMapped, @@ -2382,33 +2309,6 @@ impl Shell { } } - pub fn element_under( - &self, - location: Point, - output: &Output, - ) -> Option { - self.workspaces.sets.get(output).and_then(|set| { - set.sticky_layer - .space - .element_under(location.to_local(output).as_logical()) - .map(|(mapped, _)| mapped.clone().into()) - .or_else(|| set.workspaces[set.active].element_under(location)) - }) - } - pub fn surface_under( - &mut self, - location: Point, - output: &Output, - ) -> Option<(PointerFocusTarget, Point)> { - let overview = self.overview_mode.clone(); - self.workspaces.sets.get_mut(output).and_then(|set| { - set.sticky_layer - .surface_under(location.to_local(output)) - .map(|(target, offset)| (target, offset.to_global(output))) - .or_else(|| set.workspaces[set.active].surface_under(location, overview)) - }) - } - #[must_use] pub fn move_window( &mut self, @@ -2782,7 +2682,7 @@ impl Shell { let mapped = if move_out_of_stack { let new_mapped: CosmicMapped = CosmicWindow::new(window.clone(), evlh.clone(), self.theme.clone()).into(); - start_data.set_focus(new_mapped.focus_under((0., 0.).into())); + start_data.set_focus(new_mapped.focus_under((0., 0.).into(), WindowSurfaceType::ALL)); new_mapped } else { old_mapped.clone() @@ -3250,7 +3150,7 @@ impl Shell { let element_offset = (new_loc - geometry.loc).as_logical(); let focus = mapped - .focus_under(element_offset.to_f64()) + .focus_under(element_offset.to_f64(), WindowSurfaceType::ALL) .map(|(target, surface_offset)| (target, (surface_offset + element_offset.to_f64()))); start_data.set_location(new_loc.as_logical().to_f64()); start_data.set_focus(focus.clone()); diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 0d0ac5f1..00f30285 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -34,7 +34,7 @@ use smithay::{ utils::{DamageSet, OpaqueRegions}, ImportAll, ImportMem, Renderer, }, - desktop::{layer_map_for_output, space::SpaceElement}, + desktop::{layer_map_for_output, space::SpaceElement, WindowSurfaceType}, input::Seat, output::Output, reexports::wayland_server::{Client, Resource}, @@ -434,6 +434,27 @@ impl Workspace { } } + fn fullscreen_geometry(&self) -> Option> { + self.fullscreen.as_ref().map(|fullscreen| { + let bbox = fullscreen.surface.bbox().as_local(); + + let mut full_geo = + Rectangle::from_loc_and_size((0, 0), self.output.geometry().size.as_local()); + if bbox != full_geo { + if bbox.size.w < full_geo.size.w { + full_geo.loc.x += (full_geo.size.w - bbox.size.w) / 2; + full_geo.size.w = bbox.size.w; + } + if bbox.size.h < full_geo.size.h { + full_geo.loc.y += (full_geo.size.h - bbox.size.h) / 2; + full_geo.size.h = bbox.size.h; + } + } + + full_geo + }) + } + pub fn element_for_surface(&self, surface: &S) -> Option<&CosmicMapped> where CosmicSurface: PartialEq, @@ -445,25 +466,151 @@ impl Workspace { .find(|e| e.windows().any(|(w, _)| &w == surface)) } - pub fn element_under(&self, location: Point) -> Option { + pub fn popup_element_under(&self, location: Point) -> Option { + if !self.output.geometry().contains(location.to_i32_round()) { + return None; + } let location = location.to_local(&self.output); + + if let Some(fullscreen) = self.fullscreen.as_ref() { + if !fullscreen.is_animating() { + let geometry = self.fullscreen_geometry().unwrap(); + return fullscreen + .surface + .0 + .surface_under( + (location + geometry.loc.to_f64()).as_logical(), + WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, + ) + .is_some() + .then(|| KeyboardFocusTarget::Fullscreen(fullscreen.surface.clone())); + } + } + self.floating_layer - .element_under(location) - .or_else(|| self.tiling_layer.element_under(location)) + .popup_element_under(location) + .or_else(|| self.tiling_layer.popup_element_under(location)) } - pub fn surface_under( - &mut self, + pub fn toplevel_element_under( + &self, + location: Point, + ) -> Option { + if !self.output.geometry().contains(location.to_i32_round()) { + return None; + } + let location = location.to_local(&self.output); + + if let Some(fullscreen) = self.fullscreen.as_ref() { + if !fullscreen.is_animating() { + let geometry = self.fullscreen_geometry().unwrap(); + return fullscreen + .surface + .0 + .surface_under( + (location + geometry.loc.to_f64()).as_logical(), + WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, + ) + .is_some() + .then(|| KeyboardFocusTarget::Fullscreen(fullscreen.surface.clone())); + } + } + + self.floating_layer + .toplevel_element_under(location) + .or_else(|| self.tiling_layer.toplevel_element_under(location)) + } + + pub fn popup_surface_under( + &self, location: Point, overview: OverviewMode, ) -> Option<(PointerFocusTarget, Point)> { + if !self.output.geometry().contains(location.to_i32_round()) { + return None; + } let location = location.to_local(&self.output); + + if let Some(fullscreen) = self.fullscreen.as_ref() { + if !fullscreen.is_animating() { + let geometry = self.fullscreen_geometry().unwrap(); + return fullscreen + .surface + .0 + .surface_under( + (location + geometry.loc.to_f64()).as_logical(), + WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, + ) + .map(|(surface, surface_offset)| { + ( + PointerFocusTarget::WlSurface { + surface, + toplevel: Some(fullscreen.surface.clone().into()), + }, + (geometry.loc + surface_offset.as_local()) + .to_global(&self.output) + .to_f64(), + ) + }); + } + } + self.floating_layer - .surface_under(location) - .or_else(|| self.tiling_layer.surface_under(location, overview)) + .popup_surface_under(location) + .or_else(|| self.tiling_layer.popup_surface_under(location, overview)) .map(|(m, p)| (m, p.to_global(&self.output))) } + pub fn toplevel_surface_under( + &self, + location: Point, + overview: OverviewMode, + ) -> Option<(PointerFocusTarget, Point)> { + if !self.output.geometry().contains(location.to_i32_round()) { + return None; + } + let location = location.to_local(&self.output); + + if let Some(fullscreen) = self.fullscreen.as_ref() { + if !fullscreen.is_animating() { + let geometry = self.fullscreen_geometry().unwrap(); + return fullscreen + .surface + .0 + .surface_under( + (location + geometry.loc.to_f64()).as_logical(), + WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, + ) + .map(|(surface, surface_offset)| { + ( + PointerFocusTarget::WlSurface { + surface, + toplevel: Some(fullscreen.surface.clone().into()), + }, + (geometry.loc + surface_offset.as_local()) + .to_global(&self.output) + .to_f64(), + ) + }); + } + } + + self.floating_layer + .toplevel_surface_under(location) + .or_else(|| self.tiling_layer.toplevel_surface_under(location, overview)) + .map(|(m, p)| (m, p.to_global(&self.output))) + } + + pub fn update_pointer_position( + &mut self, + location: Option>, + overview: OverviewMode, + ) { + self.floating_layer.update_pointer_position(location); + self.tiling_layer + .update_pointer_position(location, overview); + } + pub fn element_geometry(&self, elem: &CosmicMapped) -> Option> { self.floating_layer .element_geometry(elem) From 9a9eddb132a308bd4de17a143e1e4919bfc9c7b1 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 23 Oct 2024 17:35:06 +0200 Subject: [PATCH 12/60] focus: Fix layer-popup locations --- src/shell/focus/order.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/shell/focus/order.rs b/src/shell/focus/order.rs index f035d908..e03eb607 100644 --- a/src/shell/focus/order.rs +++ b/src/shell/focus/order.rs @@ -341,9 +341,7 @@ fn layer_popups<'a>( let location_clone = location.clone(); let surface_clone = surface.clone(); PopupManager::popups_for_surface(surface.wl_surface()).map(move |(popup, popup_offset)| { - let offset = (popup_offset - popup.geometry().loc) - .as_local() - .to_global(output); + let offset = (popup_offset - popup.geometry().loc).as_global(); (surface_clone.clone(), popup, (location_clone + offset)) }) }) From 65a54706f5477c96b029effb6adcec809494d596 Mon Sep 17 00:00:00 2001 From: may Date: Sun, 29 Sep 2024 22:44:27 +0200 Subject: [PATCH 13/60] set cursor shape for resize grabs --- src/shell/layout/floating/grabs/resize.rs | 26 ++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/shell/layout/floating/grabs/resize.rs b/src/shell/layout/floating/grabs/resize.rs index e0ee2911..5cbd26b4 100644 --- a/src/shell/layout/floating/grabs/resize.rs +++ b/src/shell/layout/floating/grabs/resize.rs @@ -3,6 +3,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use crate::{ + backend::render::cursor::CursorState, shell::{ element::CosmicMapped, focus::target::PointerFocusTarget, @@ -15,7 +16,7 @@ use smithay::{ desktop::{space::SpaceElement, WindowSurface}, input::{ pointer::{ - AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, + AxisFrame, ButtonEvent, CursorIcon, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle, @@ -390,6 +391,22 @@ impl ResizeSurfaceGrab { .0 .store(true, Ordering::SeqCst); + let shape = match edges { + ResizeEdge::TOP_LEFT => Some(CursorIcon::NwResize), + ResizeEdge::TOP_RIGHT => Some(CursorIcon::NeResize), + ResizeEdge::BOTTOM_LEFT => Some(CursorIcon::SwResize), + ResizeEdge::BOTTOM_RIGHT => Some(CursorIcon::SeResize), + ResizeEdge::TOP => Some(CursorIcon::NResize), + ResizeEdge::RIGHT => Some(CursorIcon::EResize), + ResizeEdge::BOTTOM => Some(CursorIcon::SResize), + ResizeEdge::LEFT => Some(CursorIcon::WResize), + _ => None, + }; + if let Some(shape) = shape { + let cursor_state = seat.user_data().get::().unwrap(); + cursor_state.lock().unwrap().set_shape(shape); + } + ResizeSurfaceGrab { start_data, seat: seat.clone(), @@ -524,3 +541,10 @@ impl ResizeSurfaceGrab { } } } + +impl Drop for ResizeSurfaceGrab { + fn drop(&mut self) { + let cursor_state = self.seat.user_data().get::().unwrap(); + cursor_state.lock().unwrap().unset_shape(); + } +} From ea27ec5e2823216e8f60f146ea3dd36a6e7baa22 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 16 Aug 2024 20:49:59 -0700 Subject: [PATCH 14/60] DPMS with `wlr-output-power-management-unstable-v1` protocol --- Cargo.lock | 23 ++- Cargo.toml | 3 +- src/backend/kms/mod.rs | 1 + src/backend/kms/surface/mod.rs | 51 +++++- src/input/mod.rs | 2 + src/state.rs | 4 + src/wayland/handlers/mod.rs | 1 + src/wayland/handlers/output_power.rs | 78 +++++++++ src/wayland/protocols/mod.rs | 1 + src/wayland/protocols/output_power.rs | 231 ++++++++++++++++++++++++++ 10 files changed, 390 insertions(+), 5 deletions(-) create mode 100644 src/wayland/handlers/output_power.rs create mode 100644 src/wayland/protocols/output_power.rs diff --git a/Cargo.lock b/Cargo.lock index 9cd1c16c..c55aeee8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -811,6 +811,7 @@ dependencies = [ "cosmic-config", "cosmic-protocols", "cosmic-settings-config", + "drm-ffi 0.8.0", "edid-rs", "egui", "egui_plot", @@ -1269,6 +1270,16 @@ dependencies = [ "rustix", ] +[[package]] +name = "drm-ffi" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97c98727e48b7ccb4f4aea8cfe881e5b07f702d17b7875991881b41af7278d53" +dependencies = [ + "drm-sys 0.7.0", + "rustix", +] + [[package]] name = "drm-ffi" version = "0.9.0" @@ -1295,6 +1306,16 @@ dependencies = [ "linux-raw-sys 0.6.4", ] +[[package]] +name = "drm-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd39dde40b6e196c2e8763f23d119ddb1a8714534bf7d77fa97a65b0feda3986" +dependencies = [ + "libc", + "linux-raw-sys 0.6.4", +] + [[package]] name = "drm-sys" version = "0.8.0" @@ -4635,7 +4656,7 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smithay" version = "0.3.0" -source = "git+https://github.com/smithay//smithay?rev=df79eeb#df79eeba63a8e9c2d33b9be2418aee6a940135e7" +source = "git+https://github.com/smithay//smithay?rev=05c49f7#05c49f7a193bc89fba12a6484dbac895d5c9f853" dependencies = [ "appendlist", "ash 0.38.0+1.3.281", diff --git a/Cargo.toml b/Cargo.toml index 57adee5b..7f69bca9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ profiling = { version = "1.0" } rustix = { version = "0.38.32", features = ["process"] } smallvec = "1.13.2" rand = "0.8.5" +drm-ffi = "0.8.0" [dependencies.id_tree] branch = "feature/copy_clone" @@ -117,4 +118,4 @@ inherits = "release" lto = "fat" [patch."https://github.com/Smithay/smithay.git"] -smithay = { git = "https://github.com/smithay//smithay", rev = "df79eeb" } +smithay = { git = "https://github.com/smithay//smithay", rev = "05c49f7" } diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index f1e19c28..24327509 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -43,6 +43,7 @@ mod drm_helpers; pub mod render; mod socket; mod surface; +pub(crate) use surface::Surface; use device::*; pub use surface::Timings; diff --git a/src/backend/kms/surface/mod.rs b/src/backend/kms/surface/mod.rs index f03b5c89..c09856c1 100644 --- a/src/backend/kms/surface/mod.rs +++ b/src/backend/kms/surface/mod.rs @@ -102,9 +102,9 @@ static NVIDIA_LOGO: &'static [u8] = include_bytes!("../../../../resources/icons/ #[derive(Debug)] pub struct Surface { - pub(super) connector: connector::Handle, + pub(crate) connector: connector::Handle, pub(super) crtc: crtc::Handle, - pub(super) output: Output, + pub(crate) output: Output, known_nodes: HashSet, active: Arc, @@ -114,6 +114,8 @@ pub struct Surface { loop_handle: LoopHandle<'static, State>, thread_command: Sender, thread_token: RegistrationToken, + + dpms: bool, } pub struct SurfaceThreadState { @@ -238,6 +240,7 @@ pub enum ThreadCommand { ScheduleRender, SetMode(Mode, SyncSender>), End, + DpmsOff, } #[derive(Debug)] @@ -350,6 +353,7 @@ impl Surface { loop_handle: evlh.clone(), thread_command: tx, thread_token, + dpms: true, }) } @@ -380,7 +384,9 @@ impl Surface { } pub fn schedule_render(&self) { - let _ = self.thread_command.send(ThreadCommand::ScheduleRender); + if self.dpms { + let _ = self.thread_command.send(ThreadCommand::ScheduleRender); + } } pub fn set_mirroring(&mut self, output: Option) { @@ -431,6 +437,21 @@ impl Surface { rx.recv().context("Surface thread died")? } + + pub fn get_dpms(&mut self) -> bool { + self.dpms + } + + pub fn set_dpms(&mut self, on: bool) { + if self.dpms != on { + self.dpms = on; + if on { + self.schedule_render(); + } else { + let _ = self.thread_command.send(ThreadCommand::DpmsOff); + } + } + } } impl Drop for Surface { @@ -540,6 +561,30 @@ fn surface_thread( let _ = result.send(Err(anyhow::anyhow!("Set mode with inactive surface"))); } } + Event::Msg(ThreadCommand::DpmsOff) => { + if let Some(compositor) = state.compositor.as_mut() { + if let Err(err) = compositor.clear() { + error!("Failed to set DPMS off: {:?}", err); + } + match std::mem::replace(&mut state.state, QueueState::Idle) { + QueueState::Idle => {} + QueueState::Queued(token) + | QueueState::WaitingForEstimatedVBlank(token) => { + state.loop_handle.remove(token); + } + QueueState::WaitingForVBlank { .. } => { + state.timings.discard_current_frame() + } + QueueState::WaitingForEstimatedVBlankAndQueued { + estimated_vblank, + queued_render, + } => { + state.loop_handle.remove(estimated_vblank); + state.loop_handle.remove(queued_render); + } + }; + } + } Event::Closed | Event::Msg(ThreadCommand::End) => { signal.stop(); signal.wakeup(); diff --git a/src/input/mod.rs b/src/input/mod.rs index 44d7862f..02f97e3b 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -164,6 +164,8 @@ impl State { where ::Device: 'static, { + crate::wayland::handlers::output_power::set_all_surfaces_dpms_on(self); + use smithay::backend::input::Event; match event { InputEvent::DeviceAdded { device } => { diff --git a/src/state.rs b/src/state.rs index 682b0bfd..b3e641ef 100644 --- a/src/state.rs +++ b/src/state.rs @@ -15,6 +15,7 @@ use crate::{ drm::WlDrmState, image_source::ImageSourceState, output_configuration::OutputConfigurationState, + output_power::OutputPowerState, screencopy::ScreencopyState, toplevel_info::ToplevelInfoState, toplevel_management::{ManagementCapabilities, ToplevelManagementState}, @@ -198,6 +199,7 @@ pub struct Common { pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState, pub output_state: OutputManagerState, pub output_configuration_state: OutputConfigurationState, + pub output_power_state: OutputPowerState, pub presentation_state: PresentationState, pub primary_selection_state: PrimarySelectionState, pub data_control_state: Option, @@ -487,6 +489,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, client_is_privileged); + let output_power_state = OutputPowerState::new::(dh, client_is_privileged); let presentation_state = PresentationState::new::(dh, clock.id() as u32); let primary_selection_state = PrimarySelectionState::new::(dh); let image_source_state = ImageSourceState::new::(dh, client_is_privileged); @@ -593,6 +596,7 @@ impl State { keyboard_shortcuts_inhibit_state, output_state, output_configuration_state, + output_power_state, presentation_state, primary_selection_state, data_control_state, diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index 82415aa4..78604722 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -19,6 +19,7 @@ pub mod keyboard_shortcuts_inhibit; pub mod layer_shell; pub mod output; pub mod output_configuration; +pub mod output_power; pub mod pointer_constraints; pub mod pointer_gestures; pub mod presentation; diff --git a/src/wayland/handlers/output_power.rs b/src/wayland/handlers/output_power.rs new file mode 100644 index 00000000..515c604e --- /dev/null +++ b/src/wayland/handlers/output_power.rs @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use smithay::output::Output; + +use crate::{ + backend::kms::Surface, + state::{BackendData, State}, + utils::prelude::OutputExt, + wayland::protocols::output_power::{ + delegate_output_power, OutputPowerHandler, OutputPowerState, + }, +}; + +pub fn set_all_surfaces_dpms_on(state: &mut State) { + let mut changed = false; + for surface in kms_surfaces(state) { + if !surface.get_dpms() { + surface.set_dpms(true); + changed = true; + } + } + + if changed { + OutputPowerState::refresh(state); + } +} + +fn kms_surfaces(state: &mut State) -> impl Iterator { + if let BackendData::Kms(ref mut kms_state) = &mut state.backend { + Some( + kms_state + .drm_devices + .values_mut() + .flat_map(|device| device.surfaces.values_mut()), + ) + } else { + None + } + .into_iter() + .flatten() +} + +// Get KMS `Surface` for output, and for all outputs mirroring it +fn kms_surfaces_for_output<'a>( + state: &'a mut State, + output: &'a Output, +) -> impl Iterator + 'a { + kms_surfaces(state).filter(move |surface| { + surface.output == *output || surface.output.mirroring().as_ref() == Some(&output) + }) +} + +// Get KMS `Surface` for output +fn primary_kms_surface_for_output<'a>( + state: &'a mut State, + output: &Output, +) -> Option<&'a mut Surface> { + kms_surfaces(state).find(|surface| surface.output == *output) +} + +impl OutputPowerHandler for State { + fn output_power_state(&mut self) -> &mut OutputPowerState { + &mut self.common.output_power_state + } + + fn get_dpms(&mut self, output: &Output) -> Option { + let surface = primary_kms_surface_for_output(self, output)?; + Some(surface.get_dpms()) + } + + fn set_dpms(&mut self, output: &Output, on: bool) { + for surface in kms_surfaces_for_output(self, output) { + surface.set_dpms(on); + } + } +} + +delegate_output_power!(State); diff --git a/src/wayland/protocols/mod.rs b/src/wayland/protocols/mod.rs index 6010d69e..7d18b003 100644 --- a/src/wayland/protocols/mod.rs +++ b/src/wayland/protocols/mod.rs @@ -3,6 +3,7 @@ pub mod drm; pub mod image_source; pub mod output_configuration; +pub mod output_power; pub mod overlap_notify; pub mod screencopy; pub mod toplevel_info; diff --git a/src/wayland/protocols/output_power.rs b/src/wayland/protocols/output_power.rs new file mode 100644 index 00000000..25a98de0 --- /dev/null +++ b/src/wayland/protocols/output_power.rs @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use smithay::{ + output::{Output, WeakOutput}, + reexports::{ + wayland_protocols_wlr::output_power_management::v1::server::{ + zwlr_output_power_manager_v1::{self, ZwlrOutputPowerManagerV1}, + zwlr_output_power_v1::{self, ZwlrOutputPowerV1}, + }, + wayland_server::{ + backend::GlobalId, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, + Resource, + }, + }, +}; +use std::{collections::HashMap, mem}; +use wayland_backend::{protocol::WEnum, server::ClientId}; + +pub trait OutputPowerHandler { + fn output_power_state(&mut self) -> &mut OutputPowerState; + fn get_dpms(&mut self, output: &Output) -> Option; + fn set_dpms(&mut self, output: &Output, on: bool); +} + +#[derive(Debug)] +pub struct OutputPowerState { + global: GlobalId, + output_powers: HashMap, +} + +impl OutputPowerState { + pub fn new(dh: &DisplayHandle, client_filter: F) -> OutputPowerState + where + D: GlobalDispatch + 'static, + F: for<'a> Fn(&'a Client) -> bool + Clone + Send + Sync + 'static, + { + let global = dh.create_global::( + 1, + OutputPowerManagerGlobalData { + filter: Box::new(client_filter.clone()), + }, + ); + + OutputPowerState { + global, + output_powers: HashMap::new(), + } + } + + pub fn global_id(&self) -> GlobalId { + self.global.clone() + } + + /// Send `mode` events for any output powers where dpms state has changed. + /// + /// This is handled automatically for changes made through the protocol. + pub fn refresh(state: &mut D) { + let mut output_powers = mem::take(&mut state.output_power_state().output_powers); + for (output_power, old_mode) in output_powers.iter_mut() { + let data = output_power.data::().unwrap(); + if let Some(output) = data.output.as_ref().and_then(|o| o.upgrade()) { + if let Some(on) = state.get_dpms(&output) { + let mode = output_power_mode(on); + if mode != *old_mode { + output_power.mode(mode); + *old_mode = mode; + } + } + } + } + state.output_power_state().output_powers = output_powers; + } +} + +pub struct OutputPowerManagerGlobalData { + filter: Box Fn(&'a Client) -> bool + Send + Sync>, +} + +pub struct OutputPowerData { + output: Option, +} + +impl GlobalDispatch + for OutputPowerState +where + D: GlobalDispatch + + Dispatch + + 'static, +{ + fn bind( + _state: &mut D, + _dh: &DisplayHandle, + _client: &Client, + resource: New, + _global_data: &OutputPowerManagerGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + data_init.init(resource, ()); + } + + fn can_view(client: Client, global_data: &OutputPowerManagerGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +impl Dispatch for OutputPowerState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + OutputPowerHandler + + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + _obj: &ZwlrOutputPowerManagerV1, + request: zwlr_output_power_manager_v1::Request, + _data: &(), + _dh: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + zwlr_output_power_manager_v1::Request::GetOutputPower { id, output } => { + let output = Output::from_resource(&output); + let output_power = data_init.init( + id, + OutputPowerData { + output: output.as_ref().map(|o| o.downgrade()), + }, + ); + if let Some(on) = output.as_ref().and_then(|o| state.get_dpms(o)) { + let mode = output_power_mode(on); + output_power.mode(mode); + state + .output_power_state() + .output_powers + .insert(output_power, mode); + } else { + output_power.failed(); + } + } + zwlr_output_power_manager_v1::Request::Destroy => {} + _ => unreachable!(), + } + } +} + +impl Dispatch for OutputPowerState +where + D: Dispatch + OutputPowerHandler + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + obj: &ZwlrOutputPowerV1, + request: zwlr_output_power_v1::Request, + data: &OutputPowerData, + _dh: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + zwlr_output_power_v1::Request::SetMode { mode } => { + if let Some(output) = data.output.as_ref().and_then(|o| o.upgrade()) { + let on = match mode { + WEnum::Value(zwlr_output_power_v1::Mode::On) => true, + WEnum::Value(zwlr_output_power_v1::Mode::Off) => false, + _ => { + return; + } + }; + state.set_dpms(&output, on); + if let Some(on) = state.get_dpms(&output) { + let mode = output_power_mode(on); + for (output_power, old_mode) in + state.output_power_state().output_powers.iter_mut() + { + let data = output_power.data::().unwrap(); + if let Some(o) = data.output.as_ref() { + if o == &output && mode != *old_mode { + output_power.mode(mode); + *old_mode = mode; + } + } + } + } else { + obj.failed(); + state.output_power_state().output_powers.remove(obj); + } + } else { + obj.failed(); + state.output_power_state().output_powers.remove(obj); + } + } + zwlr_output_power_v1::Request::Destroy => {} + _ => unreachable!(), + } + } + + fn destroyed( + state: &mut D, + _client: ClientId, + obj: &ZwlrOutputPowerV1, + _data: &OutputPowerData, + ) { + state.output_power_state().output_powers.remove(obj); + } +} + +fn output_power_mode(on: bool) -> zwlr_output_power_v1::Mode { + if on { + zwlr_output_power_v1::Mode::On + } else { + zwlr_output_power_v1::Mode::Off + } +} + +macro_rules! delegate_output_power { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::output_power_management::v1::server::zwlr_output_power_manager_v1::ZwlrOutputPowerManagerV1: $crate::wayland::protocols::output_power::OutputPowerManagerGlobalData + ] => $crate::wayland::protocols::output_power::OutputPowerState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::output_power_management::v1::server::zwlr_output_power_manager_v1::ZwlrOutputPowerManagerV1: () + ] => $crate::wayland::protocols::output_power::OutputPowerState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::output_power_management::v1::server::zwlr_output_power_v1::ZwlrOutputPowerV1: $crate::wayland::protocols::output_power::OutputPowerData + ] => $crate::wayland::protocols::output_power::OutputPowerState); + }; +} +pub(crate) use delegate_output_power; From 520745315753b17526862ce44b10c0c8550a2ac6 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 25 Oct 2024 10:42:38 +0200 Subject: [PATCH 15/60] kms: Don't substract for mirrored outputs --- src/backend/kms/device.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/backend/kms/device.rs b/src/backend/kms/device.rs index 5fdd6446..df3fec77 100644 --- a/src/backend/kms/device.rs +++ b/src/backend/kms/device.rs @@ -305,12 +305,10 @@ impl State { .cloned() { let surface = device.surfaces.remove(&crtc).unwrap(); - // TODO: move up later outputs? - w -= surface - .output - .current_mode() - .map(|m| m.size.w as u32) - .unwrap_or(0); + if surface.output.mirroring().is_none() { + // TODO: move up later outputs? + w = w.saturating_sub(surface.output.config().transformed_size().w as u32); + } } if !changes.added.iter().any(|(c, _)| c == &conn) { From 743e5d4360a06d88a1c25c5db7b833beb91b5485 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 24 Oct 2024 19:56:20 +0200 Subject: [PATCH 16/60] toplevel_info: Fix racy data creation --- src/wayland/protocols/toplevel_info.rs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/wayland/protocols/toplevel_info.rs b/src/wayland/protocols/toplevel_info.rs index 48b9e483..356d6ccf 100644 --- a/src/wayland/protocols/toplevel_info.rs +++ b/src/wayland/protocols/toplevel_info.rs @@ -90,7 +90,7 @@ pub struct ToplevelHandleStateInner { title: String, app_id: String, states: Vec, - pub(super) window: W, + pub(super) window: Option, } pub type ToplevelHandleState = Mutex>; @@ -104,7 +104,20 @@ impl ToplevelHandleStateInner { title: String::new(), app_id: String::new(), states: Vec::new(), - window: window.clone(), + window: Some(window.clone()), + }) + } + + fn empty() -> ToplevelHandleState { + ToplevelHandleState::new(ToplevelHandleStateInner { + outputs: Vec::new(), + geometry: None, + wl_outputs: HashSet::new(), + workspaces: Vec::new(), + title: String::new(), + app_id: String::new(), + states: Vec::new(), + window: None, }) } } @@ -187,6 +200,10 @@ where .instances .push(instance); } else { + let _ = data_init.init( + cosmic_toplevel, + ToplevelHandleStateInner::empty(), + ); error!(?foreign_toplevel, "Toplevel for foreign-toplevel-list not registered for cosmic-toplevel-info."); } } @@ -600,7 +617,7 @@ where pub fn window_from_handle(handle: ZcosmicToplevelHandleV1) -> Option { handle .data::>() - .map(|state| state.lock().unwrap().window.clone()) + .and_then(|state| state.lock().unwrap().window.clone()) } macro_rules! delegate_toplevel_info { From eb64fdaf8fe4f5e6957a35e74004b183acc905c8 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 25 Oct 2024 11:27:41 +0200 Subject: [PATCH 17/60] element/window: Input offset fixes --- src/shell/element/window.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index 97d24e55..5e995cd4 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -256,7 +256,7 @@ impl CosmicWindow { || (point_i32.y - geo.loc.y >= -RESIZE_BORDER && point_i32.y - geo.loc.y < 0) || (point_i32.x - geo.loc.x >= geo.size.w && point_i32.x - geo.loc.x < geo.size.w + RESIZE_BORDER) - || (point_i32.y - geo.loc.y >= geo.size.h + || (point_i32.y - geo.loc.y >= geo.size.h + SSD_HEIGHT && point_i32.y - geo.loc.y < geo.size.h + SSD_HEIGHT + RESIZE_BORDER) { window_ui = Some(( @@ -271,7 +271,9 @@ impl CosmicWindow { Point::from((0., 0.)), )); } + } + if p.has_ssd(false) { relative_pos.y -= SSD_HEIGHT as f64; offset.y += SSD_HEIGHT as f64; } From 90883c6ab12533da08a31da338881c38aa773634 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 21 Aug 2024 20:20:27 -0700 Subject: [PATCH 18/60] Add `cosmic_atspi_v1` protocol Used to provide a backend for `AtspiDevice` in `at-spi2-core`, so Orca keybindings can work. --- Cargo.lock | 15 +- Cargo.toml | 3 +- src/config/mod.rs | 1 + src/input/mod.rs | 67 ++++++++ src/state.rs | 10 ++ src/wayland/handlers/atspi.rs | 304 +++++++++++++++++++++++++++++++++ src/wayland/handlers/mod.rs | 1 + src/wayland/protocols/atspi.rs | 162 ++++++++++++++++++ src/wayland/protocols/mod.rs | 1 + 9 files changed, 561 insertions(+), 3 deletions(-) create mode 100644 src/wayland/handlers/atspi.rs create mode 100644 src/wayland/protocols/atspi.rs diff --git a/Cargo.lock b/Cargo.lock index c55aeee8..aebb3e94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -833,6 +833,7 @@ dependencies = [ "profiling", "rand", "regex", + "reis", "ron", "rust-embed", "rustix", @@ -854,7 +855,7 @@ dependencies = [ "xcursor", "xdg", "xdg-user", - "xkbcommon 0.7.0", + "xkbcommon 0.8.0", "zbus", ] @@ -898,7 +899,7 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols?branch=main#91aeb55052a8e6e15a7ddd53e039a9350f16fa69" +source = "git+https://github.com/pop-os/cosmic-protocols?branch=main#ec1616b90fa6b4568709cfe2c0627b1e8cc887e0" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -4250,6 +4251,16 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +[[package]] +name = "reis" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "827073dbe443c57fd72ae05491c6b94213218627ac6ac169850673b0cb7034f1" +dependencies = [ + "calloop 0.14.1", + "rustix", +] + [[package]] name = "renderdoc-sys" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 7f69bca9..6aa22761 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,12 +54,13 @@ wayland-scanner = "0.31.1" xcursor = "0.3.3" xdg = "^2.1" xdg-user = "0.2.1" -xkbcommon = "0.7" +xkbcommon = "0.8" zbus = "4.4.0" profiling = { version = "1.0" } rustix = { version = "0.38.32", features = ["process"] } smallvec = "1.13.2" rand = "0.8.5" +reis = { version = "0.4", features = ["calloop"] } drm-ffi = "0.8.0" [dependencies.id_tree] diff --git a/src/config/mod.rs b/src/config/mod.rs index 77db2951..b944a6ed 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -640,6 +640,7 @@ fn config_changed(config: cosmic_config::Config, keys: Vec, state: &mut } } } + state.common.atspi_ei.update_keymap(value.clone()); state.common.config.cosmic_conf.xkb_config = value; } "input_default" => { diff --git a/src/input/mod.rs b/src/input/mod.rs index 02f97e3b..d36edc66 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -1464,6 +1464,13 @@ impl State { .unwrap_or(false) }); + self.common.atspi_ei.input( + modifiers, + &handle, + event.state(), + event.time() as u64 * 1000, + ); + // Leave move overview mode, if any modifier was released if let Some(Trigger::KeyboardMove(action_modifiers)) = shell.overview_mode().0.active_trigger() @@ -1625,6 +1632,57 @@ impl State { ))); } + if event.state() == KeyState::Released { + let removed = self + .common + .atspi_ei + .active_virtual_mods + .remove(&event.key_code()); + // If `Caps_Lock` is a virtual modifier, and is in locked state, clear it + if removed && handle.modified_sym() == Keysym::Caps_Lock { + if (modifiers.serialized.locked & 2) != 0 { + let serial = SERIAL_COUNTER.next_serial(); + let time = self.common.clock.now().as_millis(); + keyboard.input( + self, + event.key_code(), + KeyState::Pressed, + serial, + time, + |_, _, _| FilterResult::<()>::Forward, + ); + let serial = SERIAL_COUNTER.next_serial(); + keyboard.input( + self, + event.key_code(), + KeyState::Released, + serial, + time, + |_, _, _| FilterResult::<()>::Forward, + ); + } + } + } else if event.state() == KeyState::Pressed + && self + .common + .atspi_ei + .virtual_mods + .contains(&event.key_code()) + { + self.common + .atspi_ei + .active_virtual_mods + .insert(event.key_code()); + + tracing::debug!( + "active virtual mods: {:?}", + self.common.atspi_ei.active_virtual_mods + ); + seat.supressed_keys().add(&handle, None); + + return FilterResult::Intercept(None); + } + // Skip released events for initially surpressed keys if event.state() == KeyState::Released { if let Some(tokens) = seat.supressed_keys().filter(&handle) { @@ -1649,6 +1707,15 @@ impl State { return FilterResult::Intercept(None); } + if self.common.atspi_ei.has_keyboard_grab() + || self + .common + .atspi_ei + .has_key_grab(modifiers.serialized.layout_effective, event.key_code()) + { + return FilterResult::Intercept(None); + } + // handle the rest of the global shortcuts let mut clear_queue = true; if !shortcuts_inhibited { diff --git a/src/state.rs b/src/state.rs index b3e641ef..bc89614b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -12,6 +12,7 @@ use crate::{ shell::{grabs::SeatMoveGrabState, CosmicSurface, SeatExt, Shell}, utils::prelude::OutputExt, wayland::protocols::{ + atspi::AtspiState, drm::WlDrmState, image_source::ImageSourceState, output_configuration::OutputConfigurationState, @@ -229,6 +230,9 @@ pub struct Common { pub xwayland_state: Option, pub xwayland_shell_state: XWaylandShellState, pub pointer_focus_state: Option, + + pub atspi_state: AtspiState, + pub atspi_ei: crate::wayland::handlers::atspi::AtspiEiState, } #[derive(Debug)] @@ -559,6 +563,9 @@ impl State { tracing::warn!(?err, "Failed to initialize dbus handlers"); } + // TODO: Restrict to only specific client? + let atspi_state = AtspiState::new::(dh, client_is_privileged); + State { common: Common { config, @@ -615,6 +622,9 @@ impl State { xwayland_state: None, xwayland_shell_state, pointer_focus_state: None, + + atspi_state, + atspi_ei: Default::default(), }, backend: BackendData::Unset, ready: Once::new(), diff --git a/src/wayland/handlers/atspi.rs b/src/wayland/handlers/atspi.rs new file mode 100644 index 00000000..277e0ef4 --- /dev/null +++ b/src/wayland/handlers/atspi.rs @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use cosmic_comp_config::XkbConfig; +use cosmic_protocols::atspi::v1::server::cosmic_atspi_manager_v1::CosmicAtspiManagerV1; +use reis::{ + calloop::{EisRequestSource, EisRequestSourceEvent}, + eis::{self, device::DeviceType}, + request::{Connection, Device, DeviceCapability, EisRequest, Seat}, +}; +use smithay::{ + backend::input::{KeyState, Keycode}, + input::keyboard::ModifiersState, + utils::SealedFile, +}; +use std::{ + collections::{HashMap, HashSet}, + ffi::{CStr, CString}, + mem, + os::unix::{io::AsFd, net::UnixStream}, +}; +use xkbcommon::xkb; + +use crate::{ + state::State, + wayland::protocols::atspi::{delegate_atspi, AtspiHandler}, +}; + +#[derive(PartialEq, Debug)] +pub struct AtspiKeyGrab { + pub mods: u32, + pub virtual_mods: HashSet, + pub key: Keycode, +} + +#[derive(Debug, Default)] +struct AtspiClient { + key_grabs: Vec, + has_keyboard_grab: bool, + // TODO: purge old instances + keyboards: Vec<(Connection, Device, eis::Keyboard)>, +} + +impl AtspiClient { + fn add_keyboard( + &mut self, + connection: &Connection, + seat: &Seat, + keymap: &xkb::Keymap, + modifiers: &ModifiersState, + ) { + let keymap_text = keymap.get_as_string(xkb::KEYMAP_FORMAT_TEXT_V1); + let name = CStr::from_bytes_with_nul(b"eis-keymap\0").unwrap(); + let file = SealedFile::with_content(name, &CString::new(keymap_text).unwrap()).unwrap(); + + let device = seat.add_device( + Some("keyboard"), + DeviceType::Virtual, + &[DeviceCapability::Keyboard], + |device| { + let keyboard = device.interface::().unwrap(); + keyboard.keymap( + eis::keyboard::KeymapType::Xkb, + file.size() as u32 - 1, + file.as_fd(), + ); + }, + ); + device.resumed(); + + let keyboard = device.interface::().unwrap(); + + connection.with_next_serial(|serial| { + keyboard.modifiers( + serial, + modifiers.serialized.depressed, + modifiers.serialized.locked, + modifiers.serialized.latched, + modifiers.serialized.layout_effective, + ) + }); + + device.start_emulating(0); + + self.keyboards.push((connection.clone(), device, keyboard)); + } +} + +#[derive(Debug, Default)] +pub struct AtspiEiState { + modifiers: ModifiersState, + clients: HashMap, + pub virtual_mods: HashSet, + pub active_virtual_mods: HashSet, +} + +impl AtspiEiState { + pub fn input( + &mut self, + modifiers: &smithay::input::keyboard::ModifiersState, + keysym: &smithay::input::keyboard::KeysymHandle, + state: KeyState, + time: u64, + ) { + let state = match state { + KeyState::Pressed => eis::keyboard::KeyState::Press, + KeyState::Released => eis::keyboard::KeyState::Released, + }; + if &self.modifiers != modifiers { + self.modifiers = *modifiers; + for client in self.clients.values() { + for (connection, _, keyboard) in &client.keyboards { + connection.with_next_serial(|serial| { + keyboard.modifiers( + serial, + modifiers.serialized.depressed, + modifiers.serialized.locked, + modifiers.serialized.latched, + modifiers.serialized.layout_effective, + ) + }); + } + } + } + for client in self.clients.values() { + for (connection, device, keyboard) in &client.keyboards { + keyboard.key(keysym.raw_code().raw() - 8, state); + device.frame(time); + let _ = connection.flush(); + } + } + } + + pub fn has_keyboard_grab(&self) -> bool { + self.clients.values().any(|client| client.has_keyboard_grab) + } + + /// Key grab exists for mods, key, with active virtual mods + pub fn has_key_grab(&self, mods: u32, key: Keycode) -> bool { + self.clients + .values() + .flat_map(|client| &client.key_grabs) + .any(|grab| { + grab.mods == mods + && grab.virtual_mods == self.active_virtual_mods + && grab.key == key + }) + } + + fn update_virtual_mods(&mut self) { + self.virtual_mods.clear(); + self.virtual_mods.extend( + self.clients + .values() + .flat_map(|client| &client.key_grabs) + .flat_map(|grab| &grab.virtual_mods), + ); + } + + pub fn update_keymap(&mut self, xkb_config: XkbConfig) { + let keymap = keymap_or_default(xkb_config); + for client in self.clients.values_mut() { + let old_keyboards = mem::take(&mut client.keyboards); + for (connection, device, _keyboard) in old_keyboards { + device.remove(); + client.add_keyboard(&connection, device.seat(), &keymap, &self.modifiers); + let _ = connection.flush(); + } + } + } +} + +impl AtspiHandler for State { + fn client_connected(&mut self, manager: &CosmicAtspiManagerV1, socket: UnixStream) { + self.common + .atspi_ei + .clients + .insert(manager.clone(), AtspiClient::default()); + + let context = eis::Context::new(socket).unwrap(); + let source = EisRequestSource::new(context, 0); + let manager = manager.clone(); + self.common + .event_loop_handle + .insert_source(source, move |event, connected_state, state| { + Ok(handle_event(&manager, event, connected_state, state)) + }) + .unwrap(); + } + + fn client_disconnected(&mut self, manager: &CosmicAtspiManagerV1) { + self.common.atspi_ei.clients.remove(manager); + self.common.atspi_ei.update_virtual_mods(); + } + + fn add_key_grab( + &mut self, + manager: &CosmicAtspiManagerV1, + mods: u32, + virtual_mods: Vec, + key: Keycode, + ) { + let grab = AtspiKeyGrab { + mods, + virtual_mods: virtual_mods.into_iter().collect(), + key, + }; + let client = self.common.atspi_ei.clients.get_mut(manager).unwrap(); + client.key_grabs.push(grab); + self.common.atspi_ei.update_virtual_mods(); + } + + fn remove_key_grab( + &mut self, + manager: &CosmicAtspiManagerV1, + mods: u32, + virtual_mods: Vec, + key: Keycode, + ) { + let grab = AtspiKeyGrab { + mods, + virtual_mods: virtual_mods.into_iter().collect(), + key, + }; + let client = self.common.atspi_ei.clients.get_mut(manager).unwrap(); + if let Some(idx) = client.key_grabs.iter().position(|x| *x == grab) { + client.key_grabs.remove(idx); + } + self.common.atspi_ei.update_virtual_mods(); + } + + fn grab_keyboard(&mut self, manager: &CosmicAtspiManagerV1) { + let client = self.common.atspi_ei.clients.get_mut(manager).unwrap(); + client.has_keyboard_grab = true; + } + + fn ungrab_keyboard(&mut self, manager: &CosmicAtspiManagerV1) { + let client = self.common.atspi_ei.clients.get_mut(manager).unwrap(); + client.has_keyboard_grab = false; + } +} + +fn handle_event( + manager: &CosmicAtspiManagerV1, + event: Result, + connection: &Connection, + state: &mut State, +) -> calloop::PostAction { + let Some(client) = state.common.atspi_ei.clients.get_mut(manager) else { + return calloop::PostAction::Remove; + }; + match event { + Ok(EisRequestSourceEvent::Connected) => { + if connection.context_type() != reis::ei::handshake::ContextType::Receiver { + return calloop::PostAction::Remove; + } + let _seat = connection.add_seat(Some("default"), &[DeviceCapability::Keyboard]); + } + Ok(EisRequestSourceEvent::Request(EisRequest::Disconnect)) => { + return calloop::PostAction::Remove; + } + Ok(EisRequestSourceEvent::Request(EisRequest::Bind(request))) => { + if connection.has_interface("ei_keyboard") + && request.capabilities & 2 << DeviceCapability::Keyboard as u64 != 0 + { + let keymap = keymap_or_default(state.common.config.xkb_config()); + client.add_keyboard( + connection, + &request.seat, + &keymap, + &state.common.atspi_ei.modifiers, + ); + } + } + Ok(EisRequestSourceEvent::Request(_request)) => { + // seat / keyboard / device release? + } + Ok(EisRequestSourceEvent::InvalidObject(_)) => {} + Err(_) => { + // TODO + } + } + let _ = connection.flush(); + calloop::PostAction::Continue +} + +// TODO: use keymap of seat? +fn keymap_or_default(xkb_config: XkbConfig) -> xkb::Keymap { + keymap(xkb_config).unwrap_or_else(|| keymap(XkbConfig::default()).unwrap()) +} + +fn keymap(xkb_config: XkbConfig) -> Option { + let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS); + xkb::Keymap::new_from_names( + &context, + &xkb_config.rules, + &xkb_config.model, + &xkb_config.layout, + &xkb_config.variant, + xkb_config.options.clone(), + xkb::KEYMAP_COMPILE_NO_FLAGS, + ) +} + +delegate_atspi!(State); diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index 78604722..52414103 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pub mod alpha_modifier; +pub mod atspi; pub mod buffer; pub mod compositor; pub mod data_control; diff --git a/src/wayland/protocols/atspi.rs b/src/wayland/protocols/atspi.rs new file mode 100644 index 00000000..c4f73ee7 --- /dev/null +++ b/src/wayland/protocols/atspi.rs @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use cosmic_protocols::atspi::v1::server::cosmic_atspi_manager_v1; + +use smithay::{ + backend::input::Keycode, + reexports::wayland_server::{ + backend::GlobalId, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, + }, +}; +use std::os::unix::{io::AsFd, net::UnixStream}; +use wayland_backend::server::ClientId; + +pub trait AtspiHandler { + fn client_connected( + &mut self, + manager: &cosmic_atspi_manager_v1::CosmicAtspiManagerV1, + key_event_socket: UnixStream, + ); + fn client_disconnected(&mut self, manager: &cosmic_atspi_manager_v1::CosmicAtspiManagerV1); + fn add_key_grab( + &mut self, + manager: &cosmic_atspi_manager_v1::CosmicAtspiManagerV1, + mods: u32, + virtual_mods: Vec, + key: Keycode, + ); + fn remove_key_grab( + &mut self, + manager: &cosmic_atspi_manager_v1::CosmicAtspiManagerV1, + mods: u32, + virtual_mods: Vec, + key: Keycode, + ); + fn grab_keyboard(&mut self, manager: &cosmic_atspi_manager_v1::CosmicAtspiManagerV1); + fn ungrab_keyboard(&mut self, manager: &cosmic_atspi_manager_v1::CosmicAtspiManagerV1); +} + +#[derive(Debug)] +pub struct AtspiState { + global: GlobalId, +} + +impl AtspiState { + pub fn new(dh: &DisplayHandle, client_filter: F) -> AtspiState + where + D: GlobalDispatch + 'static, + F: for<'a> Fn(&'a Client) -> bool + Send + Sync + 'static, + { + let global = dh.create_global::( + 1, + AtspiGlobalData { + filter: Box::new(client_filter), + }, + ); + AtspiState { global } + } + + pub fn global_id(&self) -> GlobalId { + self.global.clone() + } +} + +pub struct AtspiGlobalData { + filter: Box Fn(&'a Client) -> bool + Send + Sync>, +} + +impl GlobalDispatch + for AtspiState +where + D: GlobalDispatch + + Dispatch + + AtspiHandler + + 'static, +{ + fn bind( + state: &mut D, + _dh: &DisplayHandle, + _client: &Client, + resource: New, + _global_data: &AtspiGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + let instance = data_init.init(resource, ()); + let (client_socket, server_socket) = UnixStream::pair().unwrap(); + state.client_connected(&instance, server_socket); + instance.key_events_eis(client_socket.as_fd()); + } + + fn can_view(client: Client, global_data: &AtspiGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +impl Dispatch for AtspiState +where + D: Dispatch + AtspiHandler + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + manager: &cosmic_atspi_manager_v1::CosmicAtspiManagerV1, + request: cosmic_atspi_manager_v1::Request, + _data: &(), + _dh: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + cosmic_atspi_manager_v1::Request::AddKeyGrab { + mods, + virtual_mods, + key, + } => { + let virtual_mods = virtual_mods + .chunks_exact(4) + .map(|x| (u32::from_ne_bytes(<[u8; 4]>::try_from(x).unwrap()) + 8).into()) + .collect(); + state.add_key_grab(manager, mods, virtual_mods, (key + 8).into()); + } + cosmic_atspi_manager_v1::Request::RemoveKeyGrab { + mods, + virtual_mods, + key, + } => { + let virtual_mods = virtual_mods + .chunks_exact(4) + .map(|x| (u32::from_ne_bytes(<[u8; 4]>::try_from(x).unwrap()) + 8).into()) + .collect(); + state.remove_key_grab(manager, mods, virtual_mods, (key + 8).into()); + } + cosmic_atspi_manager_v1::Request::GrabKeyboard => { + state.grab_keyboard(manager); + } + cosmic_atspi_manager_v1::Request::UngrabKeyboard => { + state.ungrab_keyboard(manager); + } + cosmic_atspi_manager_v1::Request::Destroy => {} + _ => unreachable!(), + } + } + + fn destroyed( + state: &mut D, + _client: ClientId, + manager: &cosmic_atspi_manager_v1::CosmicAtspiManagerV1, + _data: &(), + ) { + state.client_disconnected(manager); + } +} + +macro_rules! delegate_atspi { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::atspi::v1::server::cosmic_atspi_manager_v1::CosmicAtspiManagerV1: $crate::wayland::protocols::atspi::AtspiGlobalData + ] => $crate::wayland::protocols::atspi::AtspiState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::atspi::v1::server::cosmic_atspi_manager_v1::CosmicAtspiManagerV1: () + ] => $crate::wayland::protocols::atspi::AtspiState); + }; +} +pub(crate) use delegate_atspi; diff --git a/src/wayland/protocols/mod.rs b/src/wayland/protocols/mod.rs index 7d18b003..3fe5285d 100644 --- a/src/wayland/protocols/mod.rs +++ b/src/wayland/protocols/mod.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only +pub mod atspi; pub mod drm; pub mod image_source; pub mod output_configuration; From 641bb75eb1a6449752d14b9776381a823999b85e Mon Sep 17 00:00:00 2001 From: Ryan Brue Date: Tue, 5 Nov 2024 20:58:49 -0600 Subject: [PATCH 19/60] design: use active_window_hint as the thickness for floating window snap outline This commit changes the snapping indicator's thickness to match the active window hint, per design recommendation by Maria. The thickness for this outline never goes under 1, also per Maria's spec. Signed-off-by: Ryan Brue --- src/shell/grabs/moving.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/shell/grabs/moving.rs b/src/shell/grabs/moving.rs index 3bbb83ab..4d302ec0 100644 --- a/src/shell/grabs/moving.rs +++ b/src/shell/grabs/moving.rs @@ -145,6 +145,7 @@ impl MoveGrabState { }; let gaps = (theme.gaps.0 as i32, theme.gaps.1 as i32); + let thickness = self.indicator_thickness.max(1); let snapping_indicator = match &self.snapping_zone { Some(t) if &self.cursor_output == output => { @@ -155,7 +156,7 @@ impl MoveGrabState { renderer, Key::Window(Usage::SnappingIndicator, self.window.key()), overlay_geometry, - 3, + thickness, theme.radius_s()[0] as u8, // TODO: Fix once shaders support 4 corner radii customization 1.0, output_scale.x, From 91bc1c00bf949037a3c0261b8175ee07421953db Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 12 Nov 2024 02:59:42 -0800 Subject: [PATCH 20/60] Recalculate tiling layout on change of display configuration Previously, changing the resolution, scale, or orientation of a display left tiling layouts that don't fill the screen and/or extend off of the screen area, until an action like opening a window makes it recalculate window positions. Now this is done immediately when the output configuration changes. Perhaps we should consider if we want a different animation for things like rotating a screen, but the current behavior isn't too bad. For floating layouts, `refresh` already remaps windows that are out of bounds for the output, so this doesn't change that. Perhaps decreasing the resolution (or moving to a lower resolution output) should try to reduce the window size. But the current behavior generally seems okay. --- src/state.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/state.rs b/src/state.rs index bc89614b..a5e68943 100644 --- a/src/state.rs +++ b/src/state.rs @@ -358,6 +358,9 @@ impl BackendData { self.schedule_render(&output); } + // Update layout for changes in resolution, scale, orientation + shell.workspaces.recalculate(); + loop_handle.insert_idle(|state| state.common.update_xwayland_scale()); Ok(()) From 8d5541b27f783a06cc51448b552432291b5e8cce Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 12 Nov 2024 23:14:50 -0500 Subject: [PATCH 21/60] fix: apply transform to damage before blitting --- src/backend/kms/surface/mod.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/backend/kms/surface/mod.rs b/src/backend/kms/surface/mod.rs index c09856c1..0353c7db 100644 --- a/src/backend/kms/surface/mod.rs +++ b/src/backend/kms/surface/mod.rs @@ -1205,13 +1205,23 @@ impl SurfaceThreadState { .into_iter() .flatten(); + // If the screen is rotated, we must convert damage to match output. + let adjusted = damage.iter().copied().map(|mut d| { + d.size = d + .size + .to_logical(1) + .to_buffer(1, output_transform) + .to_logical(1, Transform::Normal) + .to_physical(1); + d + }); match frame_result .blit_frame_result( output_size, output_transform, output_scale, &mut renderer, - damage.iter().copied(), + adjusted, filter, ) .map_err(|err| match err { From 9f354ab45f5d5efda295526bfcb65dab5a849146 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 13 Nov 2024 12:50:14 -0800 Subject: [PATCH 22/60] output_configuration: Set `enabled` to `false` in `remove_heads` `add_heads` will set it to `true` again, if used on the same `Output`. It doesn't like like cosmic-comp's usage of this does that currently though. --- src/wayland/protocols/output_configuration/mod.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/wayland/protocols/output_configuration/mod.rs b/src/wayland/protocols/output_configuration/mod.rs index e575a126..9ba707fb 100644 --- a/src/wayland/protocols/output_configuration/mod.rs +++ b/src/wayland/protocols/output_configuration/mod.rs @@ -203,12 +203,22 @@ where .collect::>(); for output in new_outputs { - output.user_data().insert_if_missing(|| { + let added = output.user_data().insert_if_missing(|| { OutputState::new(OutputStateInner { enabled: true, global: None, }) }); + if !added { + // If head was previous added, enable it again + let mut inner = output + .user_data() + .get::() + .unwrap() + .lock() + .unwrap(); + inner.enabled = true; + } self.outputs.push(output.clone()); } } @@ -219,8 +229,7 @@ where self.removed_outputs.push(output.clone()); if let Some(inner) = output.user_data().get::() { let mut inner = inner.lock().unwrap(); - // if it gets re-added it should start with being enabled and no global - inner.enabled = true; + inner.enabled = false; if let Some(global) = inner.global.take() { self.dh.remove_global::(global); } From 4db2e3ec25b598bfb0145d3031bfd0ca7a2234cd Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 13 Nov 2024 12:55:08 -0800 Subject: [PATCH 23/60] Use new primary scanout output if old output is disabled `Output` in Smithay doesn't track if the output still exists, other than based on whether or not it has strong references. Which doesn't seem to be working correctly. There may be leaked strong references to `Output`s somewhere, and maybe Smithay should track if an output is still valid, generally when it is exposed as a Wayland global (https://github.com/Smithay/smithay/issues/1584). But a check like this works for now. Addresses https://github.com/pop-os/cosmic-comp/issues/985. --- src/state.rs | 17 +++++++++++++++-- .../protocols/output_configuration/mod.rs | 7 +++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/state.rs b/src/state.rs index a5e68943..747754c0 100644 --- a/src/state.rs +++ b/src/state.rs @@ -38,7 +38,7 @@ use smithay::{ renderer::{ element::{ default_primary_scanout_output_compare, utils::select_dmabuf_feedback, - RenderElementStates, + RenderElementState, RenderElementStates, }, ImportDma, }, @@ -649,6 +649,19 @@ impl State { } } +fn primary_scanout_output_compare<'a>( + current_output: &'a Output, + current_state: &RenderElementState, + next_output: &'a Output, + next_state: &RenderElementState, +) -> &'a Output { + if !crate::wayland::protocols::output_configuration::head_is_enabled(current_output) { + return next_output; + } + + default_primary_scanout_output_compare(current_output, current_state, next_output, next_state) +} + impl Common { pub fn update_primary_output( &self, @@ -662,7 +675,7 @@ impl Common { output, states, render_element_states, - default_primary_scanout_output_compare, + primary_scanout_output_compare, ); if let Some(output) = primary_scanout_output { with_fractional_scale(states, |fraction_scale| { diff --git a/src/wayland/protocols/output_configuration/mod.rs b/src/wayland/protocols/output_configuration/mod.rs index 9ba707fb..e82b922d 100644 --- a/src/wayland/protocols/output_configuration/mod.rs +++ b/src/wayland/protocols/output_configuration/mod.rs @@ -27,6 +27,13 @@ use std::{convert::TryFrom, sync::Mutex}; mod handlers; +pub fn head_is_enabled(output: &Output) -> bool { + output + .user_data() + .get::() + .map_or(false, |inner| inner.lock().unwrap().enabled) +} + #[derive(Debug)] pub struct OutputConfigurationState { outputs: Vec, From 61692b21ad306a7c998c4097c64871faa473b116 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 14 Nov 2024 13:19:47 -0800 Subject: [PATCH 24/60] Run `cargo format` --- src/backend/kms/device.rs | 4 +- src/shell/grabs/moving.rs | 7 +-- src/shell/layout/floating/mod.rs | 66 ++++++++++++++++++-------- src/shell/layout/tiling/mod.rs | 36 +++++++++----- src/shell/mod.rs | 5 +- src/wayland/protocols/toplevel_info.rs | 5 +- 6 files changed, 80 insertions(+), 43 deletions(-) diff --git a/src/backend/kms/device.rs b/src/backend/kms/device.rs index df3fec77..687e3b89 100644 --- a/src/backend/kms/device.rs +++ b/src/backend/kms/device.rs @@ -307,7 +307,9 @@ impl State { let surface = device.surfaces.remove(&crtc).unwrap(); if surface.output.mirroring().is_none() { // TODO: move up later outputs? - w = w.saturating_sub(surface.output.config().transformed_size().w as u32); + w = w.saturating_sub( + surface.output.config().transformed_size().w as u32, + ); } } diff --git a/src/shell/grabs/moving.rs b/src/shell/grabs/moving.rs index 4d302ec0..5081ead8 100644 --- a/src/shell/grabs/moving.rs +++ b/src/shell/grabs/moving.rs @@ -864,9 +864,10 @@ impl Drop for MoveGrab { let pointer = seat.get_pointer().unwrap(); let current_location = pointer.current_location(); - if let Some((target, offset)) = - mapped.focus_under(current_location - position.as_logical().to_f64(), WindowSurfaceType::ALL) - { + if let Some((target, offset)) = mapped.focus_under( + current_location - position.as_logical().to_f64(), + WindowSurfaceType::ALL, + ) { pointer.motion( state, Some(( diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index a3f10355..4507d7ea 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -732,41 +732,60 @@ impl FloatingLayout { self.space .elements() .rev() - .map(|e| (e, self.space.element_location(e).unwrap() - e.geometry().loc)) + .map(|e| { + ( + e, + self.space.element_location(e).unwrap() - e.geometry().loc, + ) + }) .filter(|(e, render_location)| { let mut bbox = e.bbox(); bbox.loc += *render_location; bbox.to_f64().contains(location.as_logical()) }) .find_map(|(e, render_location)| { - let render_location = render_location - .as_local() - .to_f64(); + let render_location = render_location.as_local().to_f64(); let point = location - render_location; - if e.focus_under(point.as_logical(), WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE).is_some() { + if e.focus_under( + point.as_logical(), + WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, + ) + .is_some() + { Some(e.clone().into()) } else { None } }) } - - pub fn toplevel_element_under(&self, location: Point) -> Option { + + pub fn toplevel_element_under( + &self, + location: Point, + ) -> Option { self.space .elements() .rev() - .map(|e| (e, self.space.element_location(e).unwrap() - e.geometry().loc)) + .map(|e| { + ( + e, + self.space.element_location(e).unwrap() - e.geometry().loc, + ) + }) .filter(|(e, render_location)| { let mut bbox = e.bbox(); bbox.loc += *render_location; bbox.to_f64().contains(location.as_logical()) }) .find_map(|(e, render_location)| { - let render_location = render_location - .as_local() - .to_f64(); + let render_location = render_location.as_local().to_f64(); let point = location - render_location; - if e.focus_under(point.as_logical(), WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE).is_some() { + if e.focus_under( + point.as_logical(), + WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, + ) + .is_some() + { Some(e.clone().into()) } else { None @@ -781,16 +800,19 @@ impl FloatingLayout { self.space .elements() .rev() - .map(|e| (e, self.space.element_location(e).unwrap() - e.geometry().loc)) + .map(|e| { + ( + e, + self.space.element_location(e).unwrap() - e.geometry().loc, + ) + }) .filter(|(e, render_location)| { let mut bbox = e.bbox(); bbox.loc += *render_location; bbox.to_f64().contains(location.as_logical()) }) .find_map(|(e, render_location)| { - let render_location = render_location - .as_local() - .to_f64(); + let render_location = render_location.as_local().to_f64(); let point = location - render_location; e.focus_under( point.as_logical(), @@ -809,7 +831,12 @@ impl FloatingLayout { self.space .elements() .rev() - .map(|e| (e, self.space.element_location(e).unwrap() - e.geometry().loc)) + .map(|e| { + ( + e, + self.space.element_location(e).unwrap() - e.geometry().loc, + ) + }) .filter(|(e, render_location)| { let mut bbox = e.bbox(); bbox.loc += *render_location; @@ -823,10 +850,7 @@ impl FloatingLayout { WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, ) .map(|(surface, surface_offset)| { - ( - surface, - render_location + surface_offset.as_local(), - ) + (surface, render_location + surface_offset.as_local()) }) }) } diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index c814f304..395a3466 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -3104,8 +3104,11 @@ impl TilingLayout { None } - - pub fn popup_element_under(&self, location_f64: Point) -> Option { + + pub fn popup_element_under( + &self, + location_f64: Point, + ) -> Option { let location = location_f64.to_i32_round(); for (mapped, geo) in self.mapped() { @@ -3113,18 +3116,24 @@ impl TilingLayout { continue; } - if mapped.focus_under( - (location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64(), - WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, - ).is_some() { + if mapped + .focus_under( + (location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64(), + WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, + ) + .is_some() + { return Some(mapped.clone().into()); } } None } - - pub fn toplevel_element_under(&self, location_f64: Point) -> Option { + + pub fn toplevel_element_under( + &self, + location_f64: Point, + ) -> Option { let location = location_f64.to_i32_round(); for (mapped, geo) in self.mapped() { @@ -3132,10 +3141,13 @@ impl TilingLayout { continue; } - if mapped.focus_under( - (location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64(), - WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, - ).is_some() { + if mapped + .focus_under( + (location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64(), + WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, + ) + .is_some() + { return Some(mapped.clone().into()); } } diff --git a/src/shell/mod.rs b/src/shell/mod.rs index e8e422d5..8e52c73c 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -1960,13 +1960,14 @@ impl Shell { self.pending_windows.retain(|(s, _, _)| s.alive()); } - pub fn update_pointer_position(&mut self, location: Point, output: &Output) { + pub fn update_pointer_position(&mut self, location: Point, output: &Output) { for (o, set) in self.workspaces.sets.iter_mut() { if o == output { set.sticky_layer.update_pointer_position(Some(location)); for (i, workspace) in set.workspaces.iter_mut().enumerate() { if i == set.active { - workspace.update_pointer_position(Some(location), self.overview_mode.clone()); + workspace + .update_pointer_position(Some(location), self.overview_mode.clone()); } else { workspace.update_pointer_position(None, self.overview_mode.clone()); } diff --git a/src/wayland/protocols/toplevel_info.rs b/src/wayland/protocols/toplevel_info.rs index 356d6ccf..a7f3b64d 100644 --- a/src/wayland/protocols/toplevel_info.rs +++ b/src/wayland/protocols/toplevel_info.rs @@ -200,10 +200,7 @@ where .instances .push(instance); } else { - let _ = data_init.init( - cosmic_toplevel, - ToplevelHandleStateInner::empty(), - ); + let _ = data_init.init(cosmic_toplevel, ToplevelHandleStateInner::empty()); error!(?foreign_toplevel, "Toplevel for foreign-toplevel-list not registered for cosmic-toplevel-info."); } } From 17b61bbfab02da481f0d7b8c1ef630819979b350 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 14 Nov 2024 13:21:23 -0800 Subject: [PATCH 25/60] ci: Add `cargo fmt` check --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2463c111..5fe858c9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,3 +25,4 @@ jobs: - run: cargo check --no-default-features - run: cargo check --features debug - run: cargo check --features profile-with-tracy + - run: cargo fmt --all -- --check From 1b962566226c4b969858ca724794606a6a7c8c38 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 14 Nov 2024 13:28:05 -0800 Subject: [PATCH 26/60] Update `smithay` --- Cargo.lock | 20 ++++++++++---------- Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aebb3e94..14d41d8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2828,7 +2828,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -4667,7 +4667,7 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smithay" version = "0.3.0" -source = "git+https://github.com/smithay//smithay?rev=05c49f7#05c49f7a193bc89fba12a6484dbac895d5c9f853" +source = "git+https://github.com/smithay//smithay?rev=ace2e6a#ace2e6a9197f4b4173dea13d53d280d82f3a99c6" dependencies = [ "appendlist", "ash 0.38.0+1.3.281", @@ -5639,9 +5639,9 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.6" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3f45d1222915ef1fd2057220c1d9d9624b7654443ea35c3877f7a52bd0a5a2d" +checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" dependencies = [ "bitflags 2.6.0", "rustix", @@ -5683,9 +5683,9 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.32.4" +version = "0.32.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5755d77ae9040bb872a25026555ce4cb0ae75fd923e90d25fba07d81057de0" +checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -5722,9 +5722,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd993de54a40a40fbe5601d9f1fbcaef0aebcc5fda447d7dc8f6dcbaae4f8953" +checksum = "782e12f6cd923c3c316130d56205ebab53f55d6666b7faddfad36cecaeeb4022" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -5747,9 +5747,9 @@ dependencies = [ [[package]] name = "wayland-server" -version = "0.31.5" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f18d47038c0b10479e695d99ed073e400ccd9bdbb60e6e503c96f62adcb12b6" +checksum = "c89532cc712a2adb119eb4d09694b402576052254d0bb284f82ac1c47fb786ad" dependencies = [ "bitflags 2.6.0", "downcast-rs", diff --git a/Cargo.toml b/Cargo.toml index 6aa22761..b0c1a4a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -119,4 +119,4 @@ inherits = "release" lto = "fat" [patch."https://github.com/Smithay/smithay.git"] -smithay = { git = "https://github.com/smithay//smithay", rev = "05c49f7" } +smithay = { git = "https://github.com/smithay//smithay", rev = "ace2e6a" } From 8d7f491830cc1353b6b81ca6640bb627c2f51035 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 14 Nov 2024 13:30:17 -0800 Subject: [PATCH 27/60] Use `WeakOutput` instead of `Option` --- src/wayland/protocols/output_power.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/wayland/protocols/output_power.rs b/src/wayland/protocols/output_power.rs index 25a98de0..d977275a 100644 --- a/src/wayland/protocols/output_power.rs +++ b/src/wayland/protocols/output_power.rs @@ -58,7 +58,7 @@ impl OutputPowerState { let mut output_powers = mem::take(&mut state.output_power_state().output_powers); for (output_power, old_mode) in output_powers.iter_mut() { let data = output_power.data::().unwrap(); - if let Some(output) = data.output.as_ref().and_then(|o| o.upgrade()) { + if let Some(output) = data.output.upgrade() { if let Some(on) = state.get_dpms(&output) { let mode = output_power_mode(on); if mode != *old_mode { @@ -77,7 +77,7 @@ pub struct OutputPowerManagerGlobalData { } pub struct OutputPowerData { - output: Option, + output: WeakOutput, } impl GlobalDispatch @@ -126,7 +126,7 @@ where let output_power = data_init.init( id, OutputPowerData { - output: output.as_ref().map(|o| o.downgrade()), + output: output.as_ref().map(|o| o.downgrade()).unwrap_or_default(), }, ); if let Some(on) = output.as_ref().and_then(|o| state.get_dpms(o)) { @@ -161,7 +161,7 @@ where ) { match request { zwlr_output_power_v1::Request::SetMode { mode } => { - if let Some(output) = data.output.as_ref().and_then(|o| o.upgrade()) { + if let Some(output) = data.output.upgrade() { let on = match mode { WEnum::Value(zwlr_output_power_v1::Mode::On) => true, WEnum::Value(zwlr_output_power_v1::Mode::Off) => false, @@ -176,11 +176,9 @@ where state.output_power_state().output_powers.iter_mut() { let data = output_power.data::().unwrap(); - if let Some(o) = data.output.as_ref() { - if o == &output && mode != *old_mode { - output_power.mode(mode); - *old_mode = mode; - } + if data.output == output && mode != *old_mode { + output_power.mode(mode); + *old_mode = mode; } } } else { From cd9ff0b7bb9ef5559420ac2e91f1e97e837fcd34 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 14 Nov 2024 15:40:28 -0800 Subject: [PATCH 28/60] output_configuration: Use `WeakOutput` as user data instead of `Output` --- .../output_configuration/handlers/cosmic.rs | 6 +++--- .../output_configuration/handlers/wlr.rs | 20 +++++++++---------- .../protocols/output_configuration/mod.rs | 10 +++++----- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/wayland/protocols/output_configuration/handlers/cosmic.rs b/src/wayland/protocols/output_configuration/handlers/cosmic.rs index bbd98de9..61be21d6 100644 --- a/src/wayland/protocols/output_configuration/handlers/cosmic.rs +++ b/src/wayland/protocols/output_configuration/handlers/cosmic.rs @@ -54,7 +54,7 @@ impl Dispatch for OutputConfigurationState where D: GlobalDispatch + Dispatch - + Dispatch + + Dispatch + Dispatch + Dispatch + Dispatch @@ -150,7 +150,7 @@ impl Dispatch, where D: GlobalDispatch + Dispatch - + Dispatch + + Dispatch + Dispatch + Dispatch + Dispatch @@ -238,7 +238,7 @@ impl Dispatch + Dispatch - + Dispatch + + Dispatch + Dispatch + Dispatch + Dispatch diff --git a/src/wayland/protocols/output_configuration/handlers/wlr.rs b/src/wayland/protocols/output_configuration/handlers/wlr.rs index 84800f6e..56565c0c 100644 --- a/src/wayland/protocols/output_configuration/handlers/wlr.rs +++ b/src/wayland/protocols/output_configuration/handlers/wlr.rs @@ -2,7 +2,7 @@ use cosmic_protocols::output_management::v1::server::zcosmic_output_configuration_v1; use smithay::{ - output::{Mode, Output}, + output::{Mode, Output, WeakOutput}, reexports::{ wayland_protocols_wlr::output_management::v1::server::{ zwlr_output_configuration_head_v1::{self, ZwlrOutputConfigurationHeadV1}, @@ -26,7 +26,7 @@ impl GlobalDispatch for OutputC where D: GlobalDispatch + Dispatch - + Dispatch + + Dispatch + Dispatch + Dispatch + Dispatch @@ -63,7 +63,7 @@ impl Dispatch for OutputConfigurationState where D: GlobalDispatch + Dispatch - + Dispatch + + Dispatch + Dispatch + Dispatch + Dispatch @@ -116,11 +116,11 @@ where } } -impl Dispatch for OutputConfigurationState +impl Dispatch for OutputConfigurationState where D: GlobalDispatch + Dispatch - + Dispatch + + Dispatch + Dispatch + Dispatch + Dispatch @@ -132,7 +132,7 @@ where _client: &Client, obj: &ZwlrOutputHeadV1, request: zwlr_output_head_v1::Request, - _data: &Output, + _data: &WeakOutput, _dh: &DisplayHandle, _data_init: &mut DataInit<'_, D>, ) { @@ -146,7 +146,7 @@ where } } - fn destroyed(state: &mut D, _client: ClientId, obj: &ZwlrOutputHeadV1, _data: &Output) { + fn destroyed(state: &mut D, _client: ClientId, obj: &ZwlrOutputHeadV1, _data: &WeakOutput) { for instance in &mut state.output_configuration_state().instances { instance.heads.retain(|h| &h.obj != obj); } @@ -157,7 +157,7 @@ impl Dispatch for OutputConfigurationState where D: GlobalDispatch + Dispatch - + Dispatch + + Dispatch + Dispatch + Dispatch + Dispatch @@ -191,7 +191,7 @@ impl Dispatch for OutputC where D: GlobalDispatch + Dispatch - + Dispatch + + Dispatch + Dispatch + Dispatch + Dispatch @@ -382,7 +382,7 @@ impl Dispatch where D: GlobalDispatch + Dispatch - + Dispatch + + Dispatch + Dispatch + Dispatch + Dispatch diff --git a/src/wayland/protocols/output_configuration/mod.rs b/src/wayland/protocols/output_configuration/mod.rs index e82b922d..b34995c3 100644 --- a/src/wayland/protocols/output_configuration/mod.rs +++ b/src/wayland/protocols/output_configuration/mod.rs @@ -6,7 +6,7 @@ use cosmic_protocols::output_management::v1::server::{ zcosmic_output_head_v1::ZcosmicOutputHeadV1, zcosmic_output_manager_v1::ZcosmicOutputManagerV1, }; use smithay::{ - output::{Mode, Output}, + output::{Mode, Output, WeakOutput}, reexports::{ wayland_protocols_wlr::output_management::v1::server::{ zwlr_output_configuration_head_v1::{self, ZwlrOutputConfigurationHeadV1}, @@ -154,7 +154,7 @@ where D: GlobalDispatch + GlobalDispatch + Dispatch - + Dispatch + + Dispatch + Dispatch + Dispatch + Dispatch @@ -312,7 +312,7 @@ fn send_head_to_client(dh: &DisplayHandle, mngr: &mut OutputMngrInstance, out where D: GlobalDispatch + Dispatch - + Dispatch + + Dispatch + Dispatch + Dispatch + OutputConfigurationHandler @@ -330,7 +330,7 @@ where if let Ok(head) = client.create_resource::( dh, mngr.obj.version(), - output.clone(), + output.downgrade(), ) { mngr.obj.head(&head); let data = OutputHeadInstance { @@ -468,7 +468,7 @@ macro_rules! delegate_output_configuration { smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_manager_v1::ZwlrOutputManagerV1: () ] => $crate::wayland::protocols::output_configuration::OutputConfigurationState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ - smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_head_v1::ZwlrOutputHeadV1: smithay::output::Output + smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_head_v1::ZwlrOutputHeadV1: smithay::output::WeakOutput ] => $crate::wayland::protocols::output_configuration::OutputConfigurationState); smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_mode_v1::ZwlrOutputModeV1: smithay::output::Mode From 7e8cb91d2353538a561c58620204f5b4a9a5ccff Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Sat, 9 Nov 2024 15:37:59 +0100 Subject: [PATCH 29/60] Unthrottle windows in screencopy sessions If an individual window was being screencast, and that window was not visible, for example because it was minimized, that window would only be rendered every 995ms, which did not look good on the screencast. Now, non-visible windows with active screencopy sessions, as well as windows that are mapped on non-visible workspaces with active screencopy sessions, are rendered every 1/60th of a second, which matches the frame rate of the video produced by xdg-desktop-portal-cosmic. In future, it might be good to let screencopy clients suggest a redraw rate for captured windows. --- src/state.rs | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/state.rs b/src/state.rs index 747754c0..9d71fbae 100644 --- a/src/state.rs +++ b/src/state.rs @@ -11,6 +11,7 @@ use crate::{ input::{gestures::GestureState, PointerFocusState}, shell::{grabs::SeatMoveGrabState, CosmicSurface, SeatExt, Shell}, utils::prelude::OutputExt, + wayland::handlers::screencopy::SessionHolder, wayland::protocols::{ atspi::AtspiState, drm::WlDrmState, @@ -108,6 +109,7 @@ use time::UtcOffset; use std::{ cell::RefCell, + cmp::min, collections::HashSet, ffi::OsString, process::Child, @@ -951,7 +953,17 @@ impl Common { None } }; - let throttle = Some(Duration::from_millis(995)); + const THROTTLE: Option = Some(Duration::from_millis(995)); + const SCREENCOPY_THROTTLE: Option = Some(Duration::from_nanos(16_666_666)); + + fn throttle(session_holder: &impl SessionHolder) -> Option { + if session_holder.sessions().is_empty() && session_holder.cursor_sessions().is_empty() { + THROTTLE + } else { + SCREENCOPY_THROTTLE + } + } + let shell = self.shell.read().unwrap(); if let Some(session_lock) = shell.session_lock.as_ref() { @@ -998,7 +1010,7 @@ impl Common { if let Some(move_grab) = seat.user_data().get::() { if let Some(grab_state) = move_grab.lock().unwrap().as_ref() { for (window, _) in grab_state.element().windows() { - window.send_frame(output, time, throttle, should_send); + window.send_frame(output, time, throttle(&window), should_send); } } } @@ -1013,21 +1025,21 @@ impl Common { .mapped() .for_each(|mapped| { for (window, _) in mapped.windows() { - window.send_frame(output, time, throttle, should_send); + window.send_frame(output, time, throttle(&window), should_send); } }); let active = shell.active_space(output); active.mapped().for_each(|mapped| { for (window, _) in mapped.windows() { - window.send_frame(output, time, throttle, should_send); + window.send_frame(output, time, throttle(&window), should_send); } }); // other (throttled) windows active.minimized_windows.iter().for_each(|m| { for (window, _) in m.window.windows() { - window.send_frame(output, time, throttle, |_, _| None); + window.send_frame(output, time, throttle(&window), |_, _| None); } }); for space in shell @@ -1037,25 +1049,26 @@ impl Common { { space.mapped().for_each(|mapped| { for (window, _) in mapped.windows() { + let throttle = min(throttle(space), throttle(&window)); window.send_frame(output, time, throttle, |_, _| None); } }); space.minimized_windows.iter().for_each(|m| { for (window, _) in m.window.windows() { - window.send_frame(output, time, throttle, |_, _| None); + window.send_frame(output, time, throttle(&window), |_, _| None); } }) } shell.override_redirect_windows.iter().for_each(|or| { if let Some(wl_surface) = or.wl_surface() { - send_frames_surface_tree(&wl_surface, output, time, throttle, should_send); + send_frames_surface_tree(&wl_surface, output, time, THROTTLE, should_send); } }); let map = smithay::desktop::layer_map_for_output(output); for layer_surface in map.layers() { - layer_surface.send_frame(output, time, throttle, should_send); + layer_surface.send_frame(output, time, THROTTLE, should_send); } } } From dbb51e827d3f18e3c17d771ddd0f7892545b3bdf Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 18 Nov 2024 18:36:44 +0100 Subject: [PATCH 30/60] kms: Fix enabling outputs --- src/backend/kms/device.rs | 8 ++--- src/backend/kms/mod.rs | 66 +++++++++++++++++++++++++-------------- 2 files changed, 46 insertions(+), 28 deletions(-) diff --git a/src/backend/kms/device.rs b/src/backend/kms/device.rs index 687e3b89..1da09203 100644 --- a/src/backend/kms/device.rs +++ b/src/backend/kms/device.rs @@ -253,8 +253,6 @@ impl State { self.backend.kms().drm_devices.insert(drm_node, device); } - self.backend.kms().refresh_used_devices()?; - self.common .output_configuration_state .add_heads(wl_outputs.iter()); @@ -267,6 +265,8 @@ impl State { &self.common.xdg_activation_state, self.common.startup_done.clone(), ); + + self.backend.kms().refresh_used_devices()?; self.common.refresh(); Ok(()) @@ -349,8 +349,6 @@ impl State { } } - self.backend.kms().refresh_used_devices()?; - self.common .output_configuration_state .remove_heads(outputs_removed.iter()); @@ -376,6 +374,8 @@ impl State { self.common.refresh(); } + self.backend.kms().refresh_used_devices()?; + Ok(()) } diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 24327509..e01f5065 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -528,7 +528,6 @@ impl KmsState { return Ok(Vec::new()); } - let mut all_outputs = Vec::new(); for device in self.drm_devices.values_mut() { // we only want outputs exposed to wayland - not leased ones // but that is also not all surface, because that doesn't contain all detected, but unmapped outputs @@ -587,7 +586,7 @@ impl KmsState { .flat_map(|encoder_handle| device.drm.get_encoder(*encoder_handle)) { for crtc in res_handles.filter_crtcs(encoder_info.possible_crtcs()) { - if !free_crtcs.contains(&crtc) { + if free_crtcs.contains(&crtc) { new_pairings.insert(conn, crtc); break 'outer; } @@ -607,7 +606,34 @@ impl KmsState { } } - // reconfigure existing + // add new ones + let mut w = shell.read().unwrap().global_space().size.w as u32; + if !test_only { + for (conn, crtc) in new_pairings { + let (output, _) = device.connector_added( + self.primary_node.as_ref(), + conn, + Some(crtc), + (w, 0), + loop_handle, + shell.clone(), + startup_done.clone(), + )?; + if output.mirroring().is_none() { + w += output.config().transformed_size().w as u32; + } + } + } + } + + if !test_only { + self.refresh_used_devices() + .context("Failed to enable devices")?; + } + + let mut all_outputs = Vec::new(); + for device in self.drm_devices.values_mut() { + // reconfigure for (crtc, surface) in device.surfaces.iter_mut() { let output_config = surface.output.config(); @@ -673,27 +699,19 @@ impl KmsState { } } - // add new ones - let mut w = shell.read().unwrap().global_space().size.w as u32; - if !test_only { - for (conn, crtc) in new_pairings { - let (output, _) = device.connector_added( - self.primary_node.as_ref(), - conn, - Some(crtc), - (0, w), - loop_handle, - shell.clone(), - startup_done.clone(), - )?; - if output.mirroring().is_none() { - w += output.config().transformed_size().w as u32; - } - all_outputs.push(output); - } - } - - all_outputs.extend(outputs); + all_outputs.extend( + device + .outputs + .iter() + .filter(|(conn, _)| { + !device + .leased_connectors + .iter() + .any(|(leased_conn, _)| *conn == leased_conn) + }) + .map(|(_, output)| output.clone()) + .collect::>(), + ); } // we need to handle mirroring, after all outputs have been enabled From d72bf5462836b497c47aa9e607d99d4ffaaffb67 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 18 Nov 2024 19:48:17 +0100 Subject: [PATCH 31/60] deps: Update smithay --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/backend/kms/surface/mod.rs | 28 +++++++++++++++++++--------- src/backend/winit.rs | 10 +++++++--- src/backend/x11.rs | 10 +++++++--- src/wayland/handlers/data_device.rs | 3 ++- 6 files changed, 37 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 14d41d8d..d759d8ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4667,7 +4667,7 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smithay" version = "0.3.0" -source = "git+https://github.com/smithay//smithay?rev=ace2e6a#ace2e6a9197f4b4173dea13d53d280d82f3a99c6" +source = "git+https://github.com/smithay//smithay?rev=bc1d732#bc1d7320f95cdf17f9e7aa6867cccc5903548032" dependencies = [ "appendlist", "ash 0.38.0+1.3.281", diff --git a/Cargo.toml b/Cargo.toml index b0c1a4a6..6b8e669d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -119,4 +119,4 @@ inherits = "release" lto = "fat" [patch."https://github.com/Smithay/smithay.git"] -smithay = { git = "https://github.com/smithay//smithay", rev = "ace2e6a" } +smithay = { git = "https://github.com/smithay//smithay", rev = "bc1d732" } diff --git a/src/backend/kms/surface/mod.rs b/src/backend/kms/surface/mod.rs index 0353c7db..8ec9dad0 100644 --- a/src/backend/kms/surface/mod.rs +++ b/src/backend/kms/surface/mod.rs @@ -67,6 +67,7 @@ use smithay::{ utils::{Buffer as BufferCoords, Clock, Monotonic, Physical, Rectangle, Size, Transform}, wayland::{ dmabuf::{get_dmabuf, DmabufFeedbackBuilder}, + presentation::Refresh, seat::WaylandFocus, shm::{shm_format_to_fourcc, with_buffer_contents}, }, @@ -757,15 +758,24 @@ impl SurfaceThreadState { ) }; - feedback.presented( - clock, - self.output - .current_mode() - .map(|mode| Duration::from_secs_f64(1_000.0 / mode.refresh as f64)) - .unwrap_or_default(), - sequence as u64, - flags, - ); + let rate = self + .output + .current_mode() + .map(|mode| Duration::from_secs_f64(1_000.0 / mode.refresh as f64)); + let refresh = match rate { + Some(rate) + if self + .compositor + .as_ref() + .is_some_and(|comp| comp.vrr_enabled()) => + { + Refresh::Variable(rate) + } + Some(rate) => Refresh::Fixed(rate), + None => Refresh::Unknown, + }; + + feedback.presented(clock, refresh, sequence as u64, flags); self.timings.presented(clock); diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 1bf3f527..a1a2bdce 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -29,7 +29,7 @@ use smithay::{ winit::platform::pump_events::PumpStatus, }, utils::Transform, - wayland::dmabuf::DmabufFeedbackBuilder, + wayland::{dmabuf::DmabufFeedbackBuilder, presentation::Refresh}, }; use std::{borrow::BorrowMut, cell::RefCell, time::Duration}; use tracing::{error, info, warn}; @@ -84,8 +84,12 @@ impl WinitState { state.clock.now(), self.output .current_mode() - .map(|mode| Duration::from_secs_f64(1_000.0 / mode.refresh as f64)) - .unwrap_or_default(), + .map(|mode| { + Refresh::Fixed(Duration::from_secs_f64( + 1_000.0 / mode.refresh as f64, + )) + }) + .unwrap_or(Refresh::Unknown), 0, wp_presentation_feedback::Kind::Vsync, ); diff --git a/src/backend/x11.rs b/src/backend/x11.rs index 75096edd..ba33c14b 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -36,7 +36,7 @@ use smithay::{ wayland_server::DisplayHandle, }, utils::{DeviceFd, Transform}, - wayland::dmabuf::DmabufFeedbackBuilder, + wayland::{dmabuf::DmabufFeedbackBuilder, presentation::Refresh}, }; use std::{borrow::BorrowMut, cell::RefCell, os::unix::io::OwnedFd, time::Duration}; use tracing::{debug, error, info, warn}; @@ -234,8 +234,12 @@ impl Surface { state.clock.now(), self.output .current_mode() - .map(|mode| Duration::from_secs_f64(1_000.0 / mode.refresh as f64)) - .unwrap_or_default(), + .map(|mode| { + Refresh::Fixed(Duration::from_secs_f64( + 1_000.0 / mode.refresh as f64, + )) + }) + .unwrap_or(Refresh::Unknown), 0, wp_presentation_feedback::Kind::Vsync, ) diff --git a/src/wayland/handlers/data_device.rs b/src/wayland/handlers/data_device.rs index 913aef92..672a7cb7 100644 --- a/src/wayland/handlers/data_device.rs +++ b/src/wayland/handlers/data_device.rs @@ -97,7 +97,8 @@ impl ClientDndGrabHandler for State { .lock() .unwrap() = icon.map(|surface| DnDIcon { surface, offset }) } - fn dropped(&mut self, seat: Seat) { + + fn dropped(&mut self, _target: Option, _validated: bool, seat: Seat) { seat.user_data() .get::>>() .unwrap() From 80965a61b9b6b6b70233f43725347ea5c6d03b6f Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 18 Nov 2024 19:47:59 +0100 Subject: [PATCH 32/60] kms: Adaptive VRR support --- src/backend/kms/drm_helpers.rs | 34 --------- src/backend/kms/mod.rs | 48 ++++++++----- src/backend/kms/surface/mod.rs | 69 ++++++++++++++++--- src/config/mod.rs | 23 +++++-- src/utils/prelude.rs | 37 ++++++---- src/wayland/handlers/output_configuration.rs | 8 ++- .../protocols/output_configuration/mod.rs | 14 ++-- 7 files changed, 146 insertions(+), 87 deletions(-) diff --git a/src/backend/kms/drm_helpers.rs b/src/backend/kms/drm_helpers.rs index 2724a8bd..0b7cdc3b 100644 --- a/src/backend/kms/drm_helpers.rs +++ b/src/backend/kms/drm_helpers.rs @@ -349,40 +349,6 @@ pub fn calculate_refresh_rate(mode: Mode) -> u32 { refresh as u32 } -pub fn supports_vrr(dev: &impl ControlDevice, conn: connector::Handle) -> Result { - get_property_val(dev, conn, "vrr_capable").map(|(val_type, val)| { - match val_type.convert_value(val) { - property::Value::UnsignedRange(res) => res == 1, - property::Value::Boolean(res) => res, - _ => false, - } - }) -} - -pub fn set_vrr( - dev: &impl ControlDevice, - crtc: crtc::Handle, - conn: connector::Handle, - vrr: bool, -) -> Result { - if supports_vrr(dev, conn)? { - dev.set_property( - conn, - get_prop(dev, crtc, "VRR_ENABLED")?, - property::Value::UnsignedRange(if vrr { 1 } else { 0 }).into(), - ) - .map_err(Into::::into) - .and_then(|_| get_property_val(dev, crtc, "VRR_ENABLED")) - .map(|(val_type, val)| match val_type.convert_value(val) { - property::Value::UnsignedRange(vrr) => vrr == 1, - property::Value::Boolean(vrr) => vrr, - _ => false, - }) - } else { - Ok(false) - } -} - pub fn get_max_bpc( dev: &impl ControlDevice, conn: connector::Handle, diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index e01f5065..8edbd71d 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -1,6 +1,11 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::{config::OutputState, shell::Shell, state::BackendData, utils::prelude::*}; +use crate::{ + config::{AdaptiveSync, OutputState}, + shell::Shell, + state::BackendData, + utils::prelude::*, +}; use anyhow::{Context, Result}; use calloop::LoopSignal; @@ -663,10 +668,6 @@ impl KmsState { let gbm = device.gbm.clone(); let cursor_size = drm.cursor_size(); - let vrr = drm_helpers::set_vrr(drm, *crtc, conn, output_config.vrr) - .unwrap_or(false); - surface.output.set_adaptive_sync(vrr); - if let Some(bpc) = output_config.max_bpc { if let Err(err) = drm_helpers::set_max_bpc(drm, conn, bpc) { warn!( @@ -678,20 +679,35 @@ impl KmsState { } } + let vrr = output_config.vrr; std::mem::drop(output_config); - surface - .resume(drm_surface, gbm, cursor_size, vrr) - .context("Failed to create surface")?; - } else { - if output_config.vrr != surface.output.adaptive_sync() { - surface.output.set_adaptive_sync(drm_helpers::set_vrr( - drm, - surface.crtc, - surface.connector, - output_config.vrr, - )?); + + match surface.resume(drm_surface, gbm, cursor_size) { + Ok(_) => { + if surface.use_adaptive_sync(vrr)? { + surface.output.set_adaptive_sync(vrr); + } else { + surface.output.config_mut().vrr = AdaptiveSync::Disabled; + surface.output.set_adaptive_sync(AdaptiveSync::Disabled); + } + } + Err(err) => { + surface.output.config_mut().enabled = OutputState::Disabled; + return Err(err).context("Failed to create surface"); + } } + } else { + let vrr = output_config.vrr; std::mem::drop(output_config); + if vrr != surface.output.adaptive_sync() { + if surface.use_adaptive_sync(vrr)? { + surface.output.set_adaptive_sync(vrr); + } else if vrr != AdaptiveSync::Disabled { + anyhow::bail!("Requested VRR mode unsupported"); + } else { + surface.output.set_adaptive_sync(AdaptiveSync::Disabled); + } + } surface .set_mode(*mode) .context("Failed to apply new mode")?; diff --git a/src/backend/kms/surface/mod.rs b/src/backend/kms/surface/mod.rs index 8ec9dad0..ce7a449b 100644 --- a/src/backend/kms/surface/mod.rs +++ b/src/backend/kms/surface/mod.rs @@ -5,6 +5,7 @@ use crate::{ element::{CosmicElement, DamageElement}, init_shaders, workspace_elements, CursorMode, ElementFilter, GlMultiRenderer, CLEAR_COLOR, }, + config::AdaptiveSync, shell::Shell, state::SurfaceDmabufFeedback, utils::{prelude::*, quirks::workspace_overview_is_open}, @@ -27,7 +28,7 @@ use smithay::{ }, drm::{ compositor::{BlitFrameResultError, DrmCompositor, FrameError, PrimaryPlaneElement}, - DrmDeviceFd, DrmEventMetadata, DrmEventTime, DrmNode, DrmSurface, + DrmDeviceFd, DrmEventMetadata, DrmEventTime, DrmNode, DrmSurface, VrrSupport, }, egl::EGLContext, renderer::{ @@ -104,7 +105,7 @@ static NVIDIA_LOGO: &'static [u8] = include_bytes!("../../../../resources/icons/ #[derive(Debug)] pub struct Surface { pub(crate) connector: connector::Handle, - pub(super) crtc: crtc::Handle, + pub(super) _crtc: crtc::Handle, pub(crate) output: Output, known_nodes: HashSet, @@ -125,6 +126,7 @@ pub struct SurfaceThreadState { primary_node: DrmNode, target_node: DrmNode, active: Arc, + vrr_mode: AdaptiveSync, compositor: Option, state: QueueState, @@ -225,7 +227,6 @@ pub enum ThreadCommand { surface: DrmSurface, gbm: GbmDevice, cursor_size: Size, - vrr: bool, result: SyncSender>, }, NodeAdded { @@ -240,6 +241,8 @@ pub enum ThreadCommand { VBlank(Option), ScheduleRender, SetMode(Mode, SyncSender>), + AdaptiveSyncAvailable(SyncSender>), + UseAdaptiveSync(AdaptiveSync), End, DpmsOff, } @@ -345,7 +348,7 @@ impl Surface { Ok(Surface { connector, - crtc, + _crtc: crtc, output: output.clone(), known_nodes: HashSet::new(), active, @@ -402,6 +405,25 @@ impl Surface { rx.recv().context("Surface thread died")? } + pub fn use_adaptive_sync(&mut self, vrr: AdaptiveSync) -> Result { + if vrr != AdaptiveSync::Disabled { + let (tx, rx) = std::sync::mpsc::sync_channel(1); + let _ = self + .thread_command + .send(ThreadCommand::AdaptiveSyncAvailable(tx)); + match rx.recv().context("Surface thread died")?? { + VrrSupport::RequiresModeset if vrr == AdaptiveSync::Enabled => return Ok(false), + VrrSupport::NotSupported => return Ok(false), + _ => {} + }; + } + + let _ = self + .thread_command + .send(ThreadCommand::UseAdaptiveSync(vrr)); + Ok(true) + } + pub fn suspend(&mut self) { let _ = self.thread_command.send(ThreadCommand::Suspend); } @@ -411,7 +433,6 @@ impl Surface { surface: DrmSurface, gbm: GbmDevice, cursor_size: Size, - vrr: bool, ) -> Result<()> { let (tx, rx) = std::sync::mpsc::sync_channel(1); self.plane_formats = surface @@ -432,7 +453,6 @@ impl Surface { surface, gbm, cursor_size, - vrr, result: tx, }); @@ -503,6 +523,7 @@ fn surface_thread( target_node, active, compositor: None, + vrr_mode: AdaptiveSync::Disabled, state: QueueState::Idle, timings: Timings::new(None, false), @@ -529,10 +550,9 @@ fn surface_thread( surface, gbm, cursor_size, - vrr, result, }) => { - let _ = result.send(state.resume(surface, gbm, cursor_size, vrr)); + let _ = result.send(state.resume(surface, gbm, cursor_size)); } Event::Msg(ThreadCommand::NodeAdded { node, gbm, egl }) => { if let Err(err) = state.node_added(node, gbm, egl) { @@ -562,6 +582,22 @@ fn surface_thread( let _ = result.send(Err(anyhow::anyhow!("Set mode with inactive surface"))); } } + Event::Msg(ThreadCommand::AdaptiveSyncAvailable(result)) => { + if let Some(compositor) = state.compositor.as_mut() { + let _ = result.send( + compositor + .vrr_supported( + compositor.pending_connectors().into_iter().next().unwrap(), + ) + .map_err(Into::into), + ); + } else { + let _ = result.send(Err(anyhow::anyhow!("Set vrr with inactive surface"))); + } + } + Event::Msg(ThreadCommand::UseAdaptiveSync(vrr)) => { + state.vrr_mode = vrr; + } Event::Msg(ThreadCommand::DpmsOff) => { if let Some(compositor) = state.compositor.as_mut() { if let Err(err) = compositor.clear() { @@ -623,7 +659,6 @@ impl SurfaceThreadState { surface: DrmSurface, gbm: GbmDevice, cursor_size: Size, - vrr: bool, ) -> Result<()> { let driver = surface.get_driver().ok(); let mut planes = surface.planes().clone(); @@ -649,7 +684,6 @@ impl SurfaceThreadState { .set_refresh_interval(Some(Duration::from_secs_f64( 1_000.0 / drm_helpers::calculate_refresh_rate(surface.pending_mode()) as f64, ))); - self.timings.set_vrr(vrr); match DrmCompositor::new( &self.output, @@ -920,6 +954,11 @@ impl SurfaceThreadState { self.timings.start_render(&self.clock); + let mut vrr = match self.vrr_mode { + AdaptiveSync::Force => true, + _ => false, + }; + let mut elements = { let shell = self.shell.read().unwrap(); let output = self.mirroring.as_ref().unwrap_or(&self.output); @@ -929,6 +968,9 @@ impl SurfaceThreadState { let previous_workspace = previous_workspace .zip(previous_idx) .map(|((w, start), idx)| (w.handle, idx, start)); + if self.vrr_mode == AdaptiveSync::Enabled { + vrr = workspace.get_fullscreen().is_some(); + } let workspace = (workspace.handle, idx); std::mem::drop(shell); @@ -958,6 +1000,7 @@ impl SurfaceThreadState { anyhow::format_err!("Failed to accumulate elements for rendering: {:?}", err) })? }; + self.timings.set_vrr(vrr); self.timings.elements_done(&self.clock); // we can't use the elements after `compositor.render_frame`, @@ -1113,8 +1156,14 @@ impl SurfaceThreadState { .collect::>(); renderer = self.api.single_renderer(&self.target_node).unwrap(); + if let Err(err) = compositor.use_vrr(false) { + warn!("Unable to set adaptive VRR state: {}", err); + } compositor.render_frame(&mut renderer, &elements, [0.0, 0.0, 0.0, 1.0]) } else { + if let Err(err) = compositor.use_vrr(vrr) { + warn!("Unable to set adaptive VRR state: {}", err); + } compositor.render_frame( &mut renderer, &elements, diff --git a/src/config/mod.rs b/src/config/mod.rs index b944a6ed..f8bd9a2f 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -98,19 +98,34 @@ pub enum OutputState { Mirroring(String), } -fn default_enabled() -> OutputState { +fn default_state() -> OutputState { OutputState::Enabled } +#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum AdaptiveSync { + #[serde(rename = "true")] + Enabled, + #[serde(rename = "false")] + Disabled, + Force, +} + +fn default_sync() -> AdaptiveSync { + AdaptiveSync::Enabled +} + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] pub struct OutputConfig { pub mode: ((i32, i32), Option), - pub vrr: bool, + #[serde(default = "default_sync")] + pub vrr: AdaptiveSync, pub scale: f64, #[serde(with = "TransformDef")] pub transform: Transform, pub position: (u32, u32), - #[serde(default = "default_enabled")] + #[serde(default = "default_state")] pub enabled: OutputState, #[serde(default, skip_serializing_if = "Option::is_none")] pub max_bpc: Option, @@ -120,7 +135,7 @@ impl Default for OutputConfig { fn default() -> OutputConfig { OutputConfig { mode: ((0, 0), None), - vrr: false, + vrr: AdaptiveSync::Enabled, scale: 1.0, transform: Transform::Normal, position: (0, 0), diff --git a/src/utils/prelude.rs b/src/utils/prelude.rs index 39d71a7e..1984033f 100644 --- a/src/utils/prelude.rs +++ b/src/utils/prelude.rs @@ -4,7 +4,7 @@ use smithay::{ }; pub use super::geometry::*; -use crate::config::{OutputConfig, OutputState}; +use crate::config::{AdaptiveSync, OutputConfig, OutputState}; pub use crate::shell::{SeatExt, Shell, Workspace}; pub use crate::state::{Common, State}; pub use crate::wayland::handlers::xdg_shell::popup::update_reactive_popups; @@ -12,15 +12,15 @@ pub use crate::wayland::handlers::xdg_shell::popup::update_reactive_popups; use std::{ cell::{Ref, RefCell, RefMut}, sync::{ - atomic::{AtomicBool, Ordering}, + atomic::{AtomicU8, Ordering}, Mutex, }, }; pub trait OutputExt { fn geometry(&self) -> Rectangle; - fn adaptive_sync(&self) -> bool; - fn set_adaptive_sync(&self, vrr: bool); + fn adaptive_sync(&self) -> AdaptiveSync; + fn set_adaptive_sync(&self, vrr: AdaptiveSync); fn mirroring(&self) -> Option; fn set_mirroring(&self, output: Option); @@ -29,7 +29,7 @@ pub trait OutputExt { fn config_mut(&self) -> RefMut<'_, OutputConfig>; } -struct Vrr(AtomicBool); +struct Vrr(AtomicU8); struct Mirroring(Mutex>); @@ -49,20 +49,27 @@ impl OutputExt for Output { .as_global() } - fn adaptive_sync(&self) -> bool { + fn adaptive_sync(&self) -> AdaptiveSync { self.user_data() .get::() - .map(|vrr| vrr.0.load(Ordering::SeqCst)) - .unwrap_or(false) + .map(|vrr| match vrr.0.load(Ordering::SeqCst) { + 2 => AdaptiveSync::Force, + 1 => AdaptiveSync::Enabled, + _ => AdaptiveSync::Disabled, + }) + .unwrap_or(AdaptiveSync::Disabled) } - fn set_adaptive_sync(&self, vrr: bool) { + fn set_adaptive_sync(&self, vrr: AdaptiveSync) { let user_data = self.user_data(); - user_data.insert_if_missing_threadsafe(|| Vrr(AtomicBool::new(false))); - user_data - .get::() - .unwrap() - .0 - .store(vrr, Ordering::SeqCst); + user_data.insert_if_missing_threadsafe(|| Vrr(AtomicU8::new(0))); + user_data.get::().unwrap().0.store( + match vrr { + AdaptiveSync::Disabled => 0, + AdaptiveSync::Enabled => 1, + AdaptiveSync::Force => 2, + }, + Ordering::SeqCst, + ); } fn mirroring(&self) -> Option { diff --git a/src/wayland/handlers/output_configuration.rs b/src/wayland/handlers/output_configuration.rs index eb900ddc..6e8b23ce 100644 --- a/src/wayland/handlers/output_configuration.rs +++ b/src/wayland/handlers/output_configuration.rs @@ -4,7 +4,7 @@ use smithay::{output::Output, utils::Point}; use tracing::{error, warn}; use crate::{ - config::{OutputConfig, OutputState}, + config::{AdaptiveSync, OutputConfig, OutputState}, state::State, wayland::protocols::output_configuration::{ delegate_output_configuration, ModeConfiguration, OutputConfiguration, @@ -120,7 +120,11 @@ impl State { current_config.position = (position.x as u32, position.y as u32); } if let Some(vrr) = adaptive_sync { - current_config.vrr = *vrr; + current_config.vrr = if *vrr { + AdaptiveSync::Force + } else { + AdaptiveSync::Disabled + }; } if let Some(mirror) = mirroring { current_config.enabled = OutputState::Mirroring(mirror.name()); diff --git a/src/wayland/protocols/output_configuration/mod.rs b/src/wayland/protocols/output_configuration/mod.rs index b34995c3..8f32407e 100644 --- a/src/wayland/protocols/output_configuration/mod.rs +++ b/src/wayland/protocols/output_configuration/mod.rs @@ -441,11 +441,13 @@ where } if instance.obj.version() >= zwlr_output_head_v1::EVT_ADAPTIVE_SYNC_SINCE { - instance.obj.adaptive_sync(if output.adaptive_sync() { - zwlr_output_head_v1::AdaptiveSyncState::Enabled - } else { - zwlr_output_head_v1::AdaptiveSyncState::Disabled - }); + instance + .obj + .adaptive_sync(if output.adaptive_sync() == AdaptiveSync::Disabled { + zwlr_output_head_v1::AdaptiveSyncState::Disabled + } else { + zwlr_output_head_v1::AdaptiveSyncState::Enabled + }); } } @@ -498,4 +500,4 @@ macro_rules! delegate_output_configuration { } pub(crate) use delegate_output_configuration; -use crate::utils::prelude::OutputExt; +use crate::{config::AdaptiveSync, utils::prelude::OutputExt}; From 81b9fb179bd7a882e3fd7bc3c10affc293d1f42c Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 18 Nov 2024 21:30:02 +0100 Subject: [PATCH 33/60] output-configuration: Support cosmic-ext v2 --- Cargo.lock | 12 ++--- src/backend/kms/device.rs | 6 ++- src/backend/kms/mod.rs | 3 ++ src/backend/kms/surface/mod.rs | 8 +++ src/utils/prelude.rs | 31 +++++++++++- src/wayland/handlers/output_configuration.rs | 8 +-- .../output_configuration/handlers/cosmic.rs | 23 +++++++++ .../output_configuration/handlers/wlr.rs | 4 +- .../protocols/output_configuration/mod.rs | 50 +++++++++++++++---- 9 files changed, 120 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d759d8ce..4a38c8aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -899,7 +899,7 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols?branch=main#ec1616b90fa6b4568709cfe2c0627b1e8cc887e0" +source = "git+https://github.com/pop-os/cosmic-protocols?branch=main#27d70b6eb9c785a2a48341016f32a7b1ac4980ac" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -1064,7 +1064,7 @@ version = "0.19.0" source = "git+https://github.com/gfx-rs/wgpu?rev=20fda69#20fda698341efbdc870b8027d6d49f5bf3f36109" dependencies = [ "bitflags 2.6.0", - "libloading 0.8.5", + "libloading 0.7.4", "winapi", ] @@ -1199,7 +1199,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.5", + "libloading 0.7.4", ] [[package]] @@ -2162,7 +2162,7 @@ dependencies = [ "bitflags 2.6.0", "com", "libc", - "libloading 0.8.5", + "libloading 0.7.4", "thiserror", "widestring", "winapi", @@ -2828,7 +2828,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -5879,7 +5879,7 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading 0.8.5", + "libloading 0.7.4", "log", "metal", "naga", diff --git a/src/backend/kms/device.rs b/src/backend/kms/device.rs index 1da09203..be22b7f9 100644 --- a/src/backend/kms/device.rs +++ b/src/backend/kms/device.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ - config::{OutputConfig, OutputState}, + config::{AdaptiveSync, OutputConfig, OutputState}, shell::Shell, utils::prelude::*, }; @@ -664,6 +664,10 @@ fn populate_modes( max_bpc, scale, transform, + // Try opportunistic VRR by default, + // if not supported this will be turned off on `resume`, + // when we have the `Surface` to actually check for support. + vrr: AdaptiveSync::Enabled, ..std::mem::take(&mut *output_config) }; diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 8edbd71d..b1e54710 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -684,6 +684,9 @@ impl KmsState { match surface.resume(drm_surface, gbm, cursor_size) { Ok(_) => { + surface.output.set_adaptive_sync_support( + surface.adaptive_sync_support().ok(), + ); if surface.use_adaptive_sync(vrr)? { surface.output.set_adaptive_sync(vrr); } else { diff --git a/src/backend/kms/surface/mod.rs b/src/backend/kms/surface/mod.rs index ce7a449b..e7893dee 100644 --- a/src/backend/kms/surface/mod.rs +++ b/src/backend/kms/surface/mod.rs @@ -405,6 +405,14 @@ impl Surface { rx.recv().context("Surface thread died")? } + pub fn adaptive_sync_support(&self) -> Result { + let (tx, rx) = std::sync::mpsc::sync_channel(1); + let _ = self + .thread_command + .send(ThreadCommand::AdaptiveSyncAvailable(tx)); + rx.recv().context("Surface thread died")? + } + pub fn use_adaptive_sync(&mut self, vrr: AdaptiveSync) -> Result { if vrr != AdaptiveSync::Disabled { let (tx, rx) = std::sync::mpsc::sync_channel(1); diff --git a/src/utils/prelude.rs b/src/utils/prelude.rs index 1984033f..6fc68ed5 100644 --- a/src/utils/prelude.rs +++ b/src/utils/prelude.rs @@ -1,4 +1,5 @@ use smithay::{ + backend::drm::VrrSupport as Support, output::{Output, WeakOutput}, utils::{Rectangle, Transform}, }; @@ -21,6 +22,8 @@ pub trait OutputExt { fn geometry(&self) -> Rectangle; fn adaptive_sync(&self) -> AdaptiveSync; fn set_adaptive_sync(&self, vrr: AdaptiveSync); + fn adaptive_sync_support(&self) -> Option; + fn set_adaptive_sync_support(&self, vrr: Option); fn mirroring(&self) -> Option; fn set_mirroring(&self, output: Option); @@ -30,7 +33,7 @@ pub trait OutputExt { } struct Vrr(AtomicU8); - +struct VrrSupport(AtomicU8); struct Mirroring(Mutex>); impl OutputExt for Output { @@ -72,6 +75,32 @@ impl OutputExt for Output { ); } + fn adaptive_sync_support(&self) -> Option { + self.user_data() + .get::() + .map(|vrr| match vrr.0.load(Ordering::SeqCst) { + 0 => None, + 2 => Some(Support::RequiresModeset), + 3 => Some(Support::Supported), + _ => Some(Support::NotSupported), + }) + .flatten() + } + + fn set_adaptive_sync_support(&self, vrr: Option) { + let user_data = self.user_data(); + user_data.insert_if_missing_threadsafe(|| VrrSupport(AtomicU8::new(0))); + user_data.get::().unwrap().0.store( + match vrr { + None => 0, + Some(Support::NotSupported) => 1, + Some(Support::RequiresModeset) => 2, + Some(Support::Supported) => 3, + }, + Ordering::SeqCst, + ); + } + fn mirroring(&self) -> Option { self.user_data().get::().and_then(|mirroring| { mirroring diff --git a/src/wayland/handlers/output_configuration.rs b/src/wayland/handlers/output_configuration.rs index 6e8b23ce..eb900ddc 100644 --- a/src/wayland/handlers/output_configuration.rs +++ b/src/wayland/handlers/output_configuration.rs @@ -4,7 +4,7 @@ use smithay::{output::Output, utils::Point}; use tracing::{error, warn}; use crate::{ - config::{AdaptiveSync, OutputConfig, OutputState}, + config::{OutputConfig, OutputState}, state::State, wayland::protocols::output_configuration::{ delegate_output_configuration, ModeConfiguration, OutputConfiguration, @@ -120,11 +120,7 @@ impl State { current_config.position = (position.x as u32, position.y as u32); } if let Some(vrr) = adaptive_sync { - current_config.vrr = if *vrr { - AdaptiveSync::Force - } else { - AdaptiveSync::Disabled - }; + current_config.vrr = *vrr; } if let Some(mirror) = mirroring { current_config.enabled = OutputState::Mirroring(mirror.name()); diff --git a/src/wayland/protocols/output_configuration/handlers/cosmic.rs b/src/wayland/protocols/output_configuration/handlers/cosmic.rs index 61be21d6..f36d002d 100644 --- a/src/wayland/protocols/output_configuration/handlers/cosmic.rs +++ b/src/wayland/protocols/output_configuration/handlers/cosmic.rs @@ -270,6 +270,29 @@ where } } } + zcosmic_output_configuration_head_v1::Request::SetAdaptiveSyncExt { state } => { + if let Ok(obj) = obj.upgrade() { + if let Some(data) = obj.data::() { + let mut pending = data.lock().unwrap(); + if pending.adaptive_sync.is_some() { + obj.post_error( + zwlr_output_configuration_head_v1::Error::AlreadySet, + format!("{:?} already had an adaptive_sync state configured", obj), + ); + return; + } + pending.adaptive_sync = match state.into_result() { + Ok(zcosmic_output_head_v1::AdaptiveSyncStateExt::Always) => { + Some(AdaptiveSync::Force) + } + Ok(zcosmic_output_head_v1::AdaptiveSyncStateExt::Automatic) => { + Some(AdaptiveSync::Enabled) + } + _ => Some(AdaptiveSync::Disabled), + }; + } + } + } _ => {} } } diff --git a/src/wayland/protocols/output_configuration/handlers/wlr.rs b/src/wayland/protocols/output_configuration/handlers/wlr.rs index 56565c0c..1ac7ee19 100644 --- a/src/wayland/protocols/output_configuration/handlers/wlr.rs +++ b/src/wayland/protocols/output_configuration/handlers/wlr.rs @@ -481,8 +481,8 @@ where } pending.adaptive_sync = Some(match state.into_result() { Ok(state) => match state { - zwlr_output_head_v1::AdaptiveSyncState::Enabled => true, - _ => false, + zwlr_output_head_v1::AdaptiveSyncState::Enabled => AdaptiveSync::Force, + _ => AdaptiveSync::Disabled, }, Err(err) => { obj.post_error( diff --git a/src/wayland/protocols/output_configuration/mod.rs b/src/wayland/protocols/output_configuration/mod.rs index 8f32407e..a75c1d16 100644 --- a/src/wayland/protocols/output_configuration/mod.rs +++ b/src/wayland/protocols/output_configuration/mod.rs @@ -3,9 +3,11 @@ use cosmic_protocols::output_management::v1::server::{ zcosmic_output_configuration_head_v1::ZcosmicOutputConfigurationHeadV1, zcosmic_output_configuration_v1::ZcosmicOutputConfigurationV1, - zcosmic_output_head_v1::ZcosmicOutputHeadV1, zcosmic_output_manager_v1::ZcosmicOutputManagerV1, + zcosmic_output_head_v1::{self, ZcosmicOutputHeadV1}, + zcosmic_output_manager_v1::ZcosmicOutputManagerV1, }; use smithay::{ + backend::drm::VrrSupport, output::{Mode, Output, WeakOutput}, reexports::{ wayland_protocols_wlr::output_management::v1::server::{ @@ -98,7 +100,7 @@ pub struct PendingOutputConfigurationInner { position: Option>, transform: Option, scale: Option, - adaptive_sync: Option, + adaptive_sync: Option, } pub type PendingOutputConfiguration = Mutex; @@ -110,7 +112,7 @@ pub enum OutputConfiguration { position: Option>, transform: Option, scale: Option, - adaptive_sync: Option, + adaptive_sync: Option, }, Disabled, } @@ -178,7 +180,7 @@ where ); let extension_global = dh.create_global::( - 1, + 2, OutputMngrGlobalData { filter: Box::new(client_filter), }, @@ -434,11 +436,6 @@ where let scale = output.current_scale().fractional_scale(); instance.obj.scale(scale); - if let Some(extension_obj) = instance.extension_obj.as_ref() { - extension_obj.scale_1000((scale * 1000.0).round() as i32); - - extension_obj.mirroring(output.mirroring().map(|o| o.name())); - } if instance.obj.version() >= zwlr_output_head_v1::EVT_ADAPTIVE_SYNC_SINCE { instance @@ -449,6 +446,41 @@ where zwlr_output_head_v1::AdaptiveSyncState::Enabled }); } + + if let Some(extension_obj) = instance.extension_obj.as_ref() { + extension_obj.scale_1000((scale * 1000.0).round() as i32); + + extension_obj.mirroring(output.mirroring().map(|o| o.name())); + + if extension_obj.version() >= zcosmic_output_head_v1::EVT_ADAPTIVE_SYNC_EXT_SINCE { + extension_obj.adaptive_sync_ext(match output.adaptive_sync() { + AdaptiveSync::Disabled => { + zcosmic_output_head_v1::AdaptiveSyncStateExt::Disabled + } + AdaptiveSync::Enabled => { + zcosmic_output_head_v1::AdaptiveSyncStateExt::Automatic + } + AdaptiveSync::Force => zcosmic_output_head_v1::AdaptiveSyncStateExt::Always, + }); + + extension_obj.adaptive_sync_available( + match output + .adaptive_sync_support() + .unwrap_or(VrrSupport::NotSupported) + { + VrrSupport::NotSupported => { + zcosmic_output_head_v1::AdaptiveSyncAvailability::Unsupported + } + VrrSupport::RequiresModeset => { + zcosmic_output_head_v1::AdaptiveSyncAvailability::RequiresModeset + } + VrrSupport::Supported => { + zcosmic_output_head_v1::AdaptiveSyncAvailability::Supported + } + }, + ); + } + } } if instance.obj.version() >= zwlr_output_head_v1::EVT_MAKE_SINCE { From 895261c2bffac651d6f71057f91ae30800167ec5 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 25 Nov 2024 15:32:34 -0500 Subject: [PATCH 34/60] overlap-notify: Implementation fixes --- src/wayland/protocols/overlap_notify.rs | 120 +++++++++++++++--------- 1 file changed, 78 insertions(+), 42 deletions(-) diff --git a/src/wayland/protocols/overlap_notify.rs b/src/wayland/protocols/overlap_notify.rs index 2a79c268..69f3eb43 100644 --- a/src/wayland/protocols/overlap_notify.rs +++ b/src/wayland/protocols/overlap_notify.rs @@ -12,7 +12,6 @@ use cosmic_protocols::{ zcosmic_toplevel_info_v1::ZcosmicToplevelInfoV1, }, }; -use rand::distributions::{Alphanumeric, DistString}; use smithay::{ desktop::{layer_map_for_output, LayerSurface}, output::Output, @@ -29,7 +28,7 @@ use smithay::{ shell::wlr_layer::{ExclusiveZone, Layer}, }, }; -use wayland_backend::server::GlobalId; +use wayland_backend::server::{GlobalId, ObjectId}; use crate::utils::prelude::{RectExt, RectGlobalExt, RectLocalExt}; @@ -94,14 +93,18 @@ impl OverlapNotifyState { if inner.has_active_notifications() { let mut new_snapshot = OverlapSnapshot::default(); - let layer_geo = layer_surface.bbox().as_local().to_global(output); + let layer_geo = map + .layer_geometry(layer_surface) + .unwrap_or_default() + .as_local() + .to_global(&output); for window in state.toplevel_info_state().registered_toplevels() { if let Some(window_geo) = window.global_geometry() { if let Some(intersection) = layer_geo.intersection(window_geo) { - // relative to window location + // relative to layer location let region = Rectangle::from_loc_and_size( - intersection.loc - window_geo.loc, + intersection.loc - layer_geo.loc, intersection.size, ) .as_logical(); @@ -111,11 +114,18 @@ impl OverlapNotifyState { } for other_surface in map.layers().filter(|s| *s != layer_surface) { - let other_geo = other_surface.bbox().as_local().to_global(output); + if other_surface.wl_surface().id() == layer_surface.wl_surface().id() { + continue; + } + let other_geo = map + .layer_geometry(other_surface) + .unwrap_or_default() + .as_local() + .to_global(&output); if let Some(intersection) = layer_geo.intersection(other_geo) { - // relative to window location + // relative to layer location let region = Rectangle::from_loc_and_size( - intersection.loc - other_geo.loc, + intersection.loc - layer_geo.loc, intersection.size, ) .as_logical(); @@ -156,20 +166,26 @@ impl LayerOverlapNotificationDataInternal { } pub fn add_notification(&mut self, new_notification: ZcosmicOverlapNotificationV1) { - for (toplevel, overlap) in &self.last_snapshot.toplevel_overlaps { - if let Ok(toplevel) = toplevel.upgrade() { - new_notification.toplevel_enter( - &toplevel, - overlap.loc.x, - overlap.loc.y, - overlap.size.w, - overlap.size.h, - ); + if let Some(client) = new_notification.client() { + for (toplevel, overlap) in &self.last_snapshot.toplevel_overlaps { + if let Some(toplevel) = toplevel + .upgrade() + .ok() + .filter(|handle| handle.client().is_some_and(|c| c == client)) + { + new_notification.toplevel_enter( + &toplevel, + overlap.loc.x, + overlap.loc.y, + overlap.size.w, + overlap.size.h, + ); + } } } - for (layer_surface, (exclusive, layer, overlap)) in &self.last_snapshot.layer_overlaps { + for (_, (identifier, exclusive, layer, overlap)) in &self.last_snapshot.layer_overlaps { new_notification.layer_enter( - layer_surface.clone(), + identifier.clone(), if *exclusive { 1 } else { 0 }, match layer { Layer::Background => WlrLayer::Background, @@ -196,44 +212,61 @@ impl LayerOverlapNotificationDataInternal { for toplevel in self.last_snapshot.toplevel_overlaps.keys() { if !new_snapshot.toplevel_overlaps.contains_key(toplevel) { if let Ok(toplevel) = toplevel.upgrade() { - for notification in ¬ifications { - notification.toplevel_leave(&toplevel); + if let Some(client) = toplevel.client() { + for notification in notifications + .iter() + .filter(|n| n.client().is_some_and(|c| c == client)) + { + notification.toplevel_leave(&toplevel); + } } } } } for (toplevel, overlap) in &new_snapshot.toplevel_overlaps { - if !self.last_snapshot.toplevel_overlaps.contains_key(toplevel) { + if !self + .last_snapshot + .toplevel_overlaps + .get(toplevel) + .is_some_and(|old_overlap| old_overlap == overlap) + { if let Ok(toplevel) = toplevel.upgrade() { - for notification in ¬ifications { - notification.toplevel_enter( - &toplevel, - overlap.loc.x, - overlap.loc.y, - overlap.size.w, - overlap.size.h, - ); + if let Some(client) = toplevel.client() { + for notification in notifications + .iter() + .filter(|n| n.client().is_some_and(|c| c == client)) + { + notification.toplevel_enter( + &toplevel, + overlap.loc.x, + overlap.loc.y, + overlap.size.w, + overlap.size.h, + ); + } } } } } - for layer_surface in self.last_snapshot.layer_overlaps.keys() { - if new_snapshot.layer_overlaps.contains_key(layer_surface) { + for (layer_surface, (identifier, ..)) in &self.last_snapshot.layer_overlaps { + if !new_snapshot.layer_overlaps.contains_key(layer_surface) { for notification in ¬ifications { - notification.layer_leave(layer_surface.clone()); + notification.layer_leave(identifier.clone()); } } } - for (layer_surface, (exclusive, layer, overlap)) in &new_snapshot.layer_overlaps { + for (layer_surface, (identifier, exclusive, layer, overlap)) in &new_snapshot.layer_overlaps + { if !self .last_snapshot .layer_overlaps - .contains_key(layer_surface) + .get(layer_surface) + .is_some_and(|(_, _, _, old_overlap)| old_overlap == overlap) { for notification in ¬ifications { notification.layer_enter( - layer_surface.clone(), + identifier.clone(), if *exclusive { 1 } else { 0 }, match layer { Layer::Background => WlrLayer::Background, @@ -257,7 +290,7 @@ impl LayerOverlapNotificationDataInternal { #[derive(Debug, Default, Clone)] struct OverlapSnapshot { toplevel_overlaps: HashMap, Rectangle>, - layer_overlaps: HashMap)>, + layer_overlaps: HashMap)>, } impl OverlapSnapshot { @@ -283,18 +316,21 @@ impl OverlapSnapshot { ExclusiveZone::Exclusive(_) ); let layer = layer_surface.layer(); - let identifier = layer_surface.user_data().get_or_insert(Identifier::default); + let id = layer_surface.wl_surface().id(); + let identifier = layer_surface + .user_data() + .get_or_insert(|| Identifier::from(id.clone())); self.layer_overlaps - .insert(identifier.0.clone(), (exclusive, layer, overlap)); + .insert(id, (identifier.0.clone(), exclusive, layer, overlap)); } } struct Identifier(String); -impl Default for Identifier { - fn default() -> Self { - Identifier(Alphanumeric.sample_string(&mut rand::thread_rng(), 32)) +impl From for Identifier { + fn from(value: ObjectId) -> Self { + Identifier(value.to_string()) } } From 60558e18fd7a423739efac21c6b9972f89e9633d Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 22 Nov 2024 19:10:58 -0500 Subject: [PATCH 35/60] overlay-notify: Implement handler --- src/main.rs | 2 ++ src/state.rs | 5 +++ src/wayland/handlers/mod.rs | 1 + src/wayland/handlers/overlap_notify.rs | 42 +++++++++++++++++++++++++ src/wayland/protocols/overlap_notify.rs | 20 ++++++++++-- 5 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 src/wayland/handlers/overlap_notify.rs diff --git a/src/main.rs b/src/main.rs index 4c9c4860..aba0727e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ use anyhow::{Context, Result}; use state::State; use std::{env, ffi::OsString, os::unix::process::CommandExt, process, sync::Arc}; use tracing::{error, info, warn}; +use wayland::protocols::overlap_notify::OverlapNotifyState; use crate::wayland::handlers::compositor::client_compositor_state; @@ -131,6 +132,7 @@ fn main() -> Result<()> { } state.common.refresh(); state::Common::refresh_focus(state); + OverlapNotifyState::refresh(state); state.common.update_x11_stacking_order(); { diff --git a/src/state.rs b/src/state.rs index 9d71fbae..fc2ba06e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -18,6 +18,7 @@ use crate::{ image_source::ImageSourceState, output_configuration::OutputConfigurationState, output_power::OutputPowerState, + overlap_notify::OverlapNotifyState, screencopy::ScreencopyState, toplevel_info::ToplevelInfoState, toplevel_management::{ManagementCapabilities, ToplevelManagementState}, @@ -219,6 +220,7 @@ pub struct Common { pub viewporter_state: ViewporterState, pub kde_decoration_state: KdeDecorationState, pub xdg_decoration_state: XdgDecorationState, + pub overlap_notify_state: OverlapNotifyState, // shell-related wayland state pub xdg_shell_state: XdgShellState, @@ -499,6 +501,8 @@ impl State { let output_state = OutputManagerState::new_with_xdg_output::(dh); let output_configuration_state = OutputConfigurationState::new(dh, client_is_privileged); let output_power_state = OutputPowerState::new::(dh, client_is_privileged); + let overlap_notify_state = + OverlapNotifyState::new::(dh, client_has_no_security_context); let presentation_state = PresentationState::new::(dh, clock.id() as u32); let primary_selection_state = PrimarySelectionState::new::(dh); let image_source_state = ImageSourceState::new::(dh, client_is_privileged); @@ -609,6 +613,7 @@ impl State { output_state, output_configuration_state, output_power_state, + overlap_notify_state, presentation_state, primary_selection_state, data_control_state, diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index 52414103..870d74e2 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -21,6 +21,7 @@ pub mod layer_shell; pub mod output; pub mod output_configuration; pub mod output_power; +pub mod overlap_notify; pub mod pointer_constraints; pub mod pointer_gestures; pub mod presentation; diff --git a/src/wayland/handlers/overlap_notify.rs b/src/wayland/handlers/overlap_notify.rs new file mode 100644 index 00000000..7b948653 --- /dev/null +++ b/src/wayland/handlers/overlap_notify.rs @@ -0,0 +1,42 @@ +use smithay::{ + desktop::{layer_map_for_output, LayerSurface, WindowSurfaceType}, + output::Output, + reexports::wayland_protocols_wlr::layer_shell::v1::server::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, +}; + +use crate::{ + state::State, + wayland::protocols::overlap_notify::{ + delegate_overlap_notify, OverlapNotifyHandler, OverlapNotifyState, + }, +}; + +impl OverlapNotifyHandler for State { + fn overlap_notify_state(&mut self) -> &mut OverlapNotifyState { + &mut self.common.overlap_notify_state + } + + fn layer_surface_from_resource(&self, resource: ZwlrLayerSurfaceV1) -> Option { + self.common + .layer_shell_state + .layer_surfaces() + .find(|l| l.shell_surface() == &resource) + .and_then(|l| { + let shell = self.common.shell.read().unwrap(); + let outputs = shell.outputs(); + let ret = outputs.map(|o| layer_map_for_output(o)).find_map(|s| { + s.layer_for_surface(l.wl_surface(), WindowSurfaceType::ALL) + .cloned() + }); + drop(shell); + ret + }) + } + + fn outputs(&self) -> impl Iterator { + let shell = self.common.shell.read().unwrap(); + shell.outputs().cloned().collect::>().into_iter() + } +} + +delegate_overlap_notify!(State); diff --git a/src/wayland/protocols/overlap_notify.rs b/src/wayland/protocols/overlap_notify.rs index 69f3eb43..4298b6ef 100644 --- a/src/wayland/protocols/overlap_notify.rs +++ b/src/wayland/protocols/overlap_notify.rs @@ -36,6 +36,7 @@ use super::toplevel_info::{ ToplevelHandleState, ToplevelInfoGlobalData, ToplevelInfoHandler, ToplevelState, Window, }; +#[derive(Debug)] pub struct OverlapNotifyState { instances: Vec, global: GlobalId, @@ -82,7 +83,7 @@ impl OverlapNotifyState { W: Window + 'static, { for output in state.outputs() { - let map = layer_map_for_output(output); + let map = layer_map_for_output(&output); for layer_surface in map.layers() { if let Some(data) = layer_surface .user_data() @@ -144,7 +145,7 @@ impl OverlapNotifyState { pub trait OverlapNotifyHandler: ToplevelInfoHandler { fn overlap_notify_state(&mut self) -> &mut OverlapNotifyState; fn layer_surface_from_resource(&self, resource: ZwlrLayerSurfaceV1) -> Option; - fn outputs(&self) -> impl Iterator; + fn outputs(&self) -> impl Iterator; } pub struct OverlapNotifyGlobalData { @@ -428,3 +429,18 @@ where } } } + +macro_rules! delegate_overlap_notify { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::overlap_notify::v1::server::zcosmic_overlap_notify_v1::ZcosmicOverlapNotifyV1: $crate::wayland::protocols::overlap_notify::OverlapNotifyGlobalData + ] => $crate::wayland::protocols::overlap_notify::OverlapNotifyState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::overlap_notify::v1::server::zcosmic_overlap_notify_v1::ZcosmicOverlapNotifyV1: () + ] => $crate::wayland::protocols::overlap_notify::OverlapNotifyState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + cosmic_protocols::overlap_notify::v1::server::zcosmic_overlap_notification_v1::ZcosmicOverlapNotificationV1: () + ] => $crate::wayland::protocols::overlap_notify::OverlapNotifyState); + }; +} +pub(crate) use delegate_overlap_notify; From a14d075aa00e7bd1c3b6fc497a846662f18488f5 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 27 Nov 2024 10:59:41 -0500 Subject: [PATCH 36/60] refactor: add namespace --- Cargo.lock | 12 ++++++------ src/wayland/protocols/overlap_notify.rs | 25 +++++++++++++++++++------ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a38c8aa..8b27bc28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -899,7 +899,7 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols?branch=main#27d70b6eb9c785a2a48341016f32a7b1ac4980ac" +source = "git+https://github.com/pop-os/cosmic-protocols?branch=main#d218c76b58c7a3b20dd5e7943f93fc306a1b81b8" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -1064,7 +1064,7 @@ version = "0.19.0" source = "git+https://github.com/gfx-rs/wgpu?rev=20fda69#20fda698341efbdc870b8027d6d49f5bf3f36109" dependencies = [ "bitflags 2.6.0", - "libloading 0.7.4", + "libloading 0.8.5", "winapi", ] @@ -1199,7 +1199,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.7.4", + "libloading 0.8.5", ] [[package]] @@ -2162,7 +2162,7 @@ dependencies = [ "bitflags 2.6.0", "com", "libc", - "libloading 0.7.4", + "libloading 0.8.5", "thiserror", "widestring", "winapi", @@ -2828,7 +2828,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -5879,7 +5879,7 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading 0.7.4", + "libloading 0.8.5", "log", "metal", "naga", diff --git a/src/wayland/protocols/overlap_notify.rs b/src/wayland/protocols/overlap_notify.rs index 4298b6ef..d29102f3 100644 --- a/src/wayland/protocols/overlap_notify.rs +++ b/src/wayland/protocols/overlap_notify.rs @@ -184,9 +184,12 @@ impl LayerOverlapNotificationDataInternal { } } } - for (_, (identifier, exclusive, layer, overlap)) in &self.last_snapshot.layer_overlaps { + for (_, (identifier, namespace, exclusive, layer, overlap)) in + &self.last_snapshot.layer_overlaps + { new_notification.layer_enter( identifier.clone(), + namespace.clone(), if *exclusive { 1 } else { 0 }, match layer { Layer::Background => WlrLayer::Background, @@ -257,17 +260,19 @@ impl LayerOverlapNotificationDataInternal { } } } - for (layer_surface, (identifier, exclusive, layer, overlap)) in &new_snapshot.layer_overlaps + for (layer_surface, (identifier, namespace, exclusive, layer, overlap)) in + &new_snapshot.layer_overlaps { if !self .last_snapshot .layer_overlaps .get(layer_surface) - .is_some_and(|(_, _, _, old_overlap)| old_overlap == overlap) + .is_some_and(|(_, _, _, _, old_overlap)| old_overlap == overlap) { for notification in ¬ifications { notification.layer_enter( identifier.clone(), + namespace.clone(), if *exclusive { 1 } else { 0 }, match layer { Layer::Background => WlrLayer::Background, @@ -291,7 +296,7 @@ impl LayerOverlapNotificationDataInternal { #[derive(Debug, Default, Clone)] struct OverlapSnapshot { toplevel_overlaps: HashMap, Rectangle>, - layer_overlaps: HashMap)>, + layer_overlaps: HashMap)>, } impl OverlapSnapshot { @@ -322,8 +327,16 @@ impl OverlapSnapshot { .user_data() .get_or_insert(|| Identifier::from(id.clone())); - self.layer_overlaps - .insert(id, (identifier.0.clone(), exclusive, layer, overlap)); + self.layer_overlaps.insert( + id, + ( + identifier.0.clone(), + layer_surface.namespace().to_string(), + exclusive, + layer, + overlap, + ), + ); } } From 69d251fb741664c8932937a72d362f728fc3e161 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Sat, 30 Nov 2024 11:49:53 +0100 Subject: [PATCH 37/60] screencopy: don't force opaque window backgrounds This meant that, for windows that were not fully opaque, there was no way to capture their transparency. No way to put a window with transparency on top of a background in OBS, for example. --- src/wayland/handlers/screencopy/render.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/wayland/handlers/screencopy/render.rs b/src/wayland/handlers/screencopy/render.rs index 54f3070e..eda0951b 100644 --- a/src/wayland/handlers/screencopy/render.rs +++ b/src/wayland/handlers/screencopy/render.rs @@ -12,7 +12,7 @@ use smithay::{ gles::{GlesError, GlesRenderbuffer}, sync::SyncPoint, utils::with_renderer_surface_state, - Bind, Blit, BufferType, ExportMem, ImportAll, ImportMem, Offscreen, Renderer, + Bind, Blit, BufferType, Color32F, ExportMem, ImportAll, ImportMem, Offscreen, Renderer, }, }, desktop::space::SpaceElement, @@ -36,7 +36,7 @@ use crate::{ backend::render::{ cursor, element::{AsGlowRenderer, CosmicElement, DamageElement, FromGlesError}, - render_workspace, CursorMode, ElementFilter, RendererRef, CLEAR_COLOR, + render_workspace, CursorMode, ElementFilter, RendererRef, }, shell::{CosmicMappedRenderElement, CosmicSurface, WorkspaceRenderElement}, state::{Common, KmsNodes, State}, @@ -573,12 +573,7 @@ pub fn render_window_to_buffer( renderer.bind(render_buffer).map_err(DTError::Rendering)?; } - dt.render_output( - renderer, - age, - &elements, - CLEAR_COLOR, // TODO use a theme neutral color - ) + dt.render_output(renderer, age, &elements, Color32F::TRANSPARENT) } let common = &mut state.common; From eaeca9a2444aca35352808afaf0a863818ef3f7e Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 20 Nov 2024 13:13:07 -0800 Subject: [PATCH 38/60] input: Define function for transforming touch/tablet event position --- src/input/mod.rs | 43 +++++++++++++++++-------------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/src/input/mod.rs b/src/input/mod.rs index d36edc66..0c3e4ce2 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -1143,13 +1143,7 @@ impl State { return; }; - let geometry = output.geometry(); - - let position = geometry.loc.to_f64() - + event - .position_transformed(geometry.size.as_logical()) - .as_global(); - + let position = transform_output_mapped_position(&output, &event); let under = State::surface_under(position, &output, &mut *shell) .map(|(target, pos)| (target, pos.as_logical())); @@ -1180,13 +1174,7 @@ impl State { return; }; - let geometry = output.geometry(); - - let position = geometry.loc.to_f64() - + event - .position_transformed(geometry.size.as_logical()) - .as_global(); - + let position = transform_output_mapped_position(&output, &event); let under = State::surface_under(position, &output, &mut *shell) .map(|(target, pos)| (target, pos.as_logical())); @@ -1269,13 +1257,8 @@ impl State { else { return; }; - let geometry = output.geometry(); - - let position = event - .position_transformed(geometry.size.as_logical()) - .as_global() - + geometry.loc.to_f64(); + let position = transform_output_mapped_position(&output, &event); let under = State::surface_under(position, &output, &mut *shell) .map(|(target, pos)| (target, pos.as_logical())); @@ -1338,13 +1321,8 @@ impl State { else { return; }; - let geometry = output.geometry(); - - let position = event - .position_transformed(geometry.size.as_logical()) - .as_global() - + geometry.loc.to_f64(); + let position = transform_output_mapped_position(&output, &event); let under = State::surface_under(position, &output, &mut *shell) .map(|(target, pos)| (target, pos.as_logical())); @@ -2161,6 +2139,19 @@ fn cursor_sessions_for_output( .chain(output.cursor_sessions().into_iter()) } +fn transform_output_mapped_position<'a, B, E>(output: &Output, event: &E) -> Point +where + B: InputBackend, + E: AbsolutePositionEvent, + B::Device: 'static, +{ + let geometry = output.geometry(); + event + .position_transformed(geometry.size.as_logical()) + .as_global() + + geometry.loc.to_f64() +} + // TODO Is it possible to determine mapping for external touchscreen? // Support map_to_region like sway? fn mapped_output_for_device<'a, D: Device + 'static>( From e3b41c5c554cc5aadf44183866ff4df7c2957d09 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 20 Nov 2024 13:21:09 -0800 Subject: [PATCH 39/60] input: Apply output transform to touch/tablet events --- src/input/mod.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/input/mod.rs b/src/input/mod.rs index 0c3e4ce2..8b40908e 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -2146,10 +2146,14 @@ where B::Device: 'static, { let geometry = output.geometry(); - event - .position_transformed(geometry.size.as_logical()) - .as_global() - + geometry.loc.to_f64() + let transform = output.current_transform(); + let size = transform + .invert() + .transform_size(geometry.size.as_logical()); + geometry.loc.to_f64() + + transform + .transform_point_in(event.position_transformed(size), &size.to_f64()) + .as_global() } // TODO Is it possible to determine mapping for external touchscreen? From fc84fa994867118ef516b1ffc007642ba10c77e0 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 5 Dec 2024 17:30:27 +0100 Subject: [PATCH 40/60] shell: Fix windows global geometry to include ssd --- src/shell/element/stack.rs | 4 ++-- src/shell/element/surface.rs | 22 +++++++++++++++++++--- src/shell/element/window.rs | 13 ++++--------- src/shell/workspace.rs | 8 ++++---- src/wayland/handlers/toplevel_info.rs | 10 ++-------- 5 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index 3a0597f6..484caac7 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -170,7 +170,7 @@ impl CosmicStack { if let Some(mut geo) = p.geometry.lock().unwrap().clone() { geo.loc.y += TAB_HEIGHT; geo.size.h -= TAB_HEIGHT; - window.set_geometry(geo); + window.set_geometry(geo, TAB_HEIGHT as u32); } window.send_configure(); if let Some(idx) = idx { @@ -490,7 +490,7 @@ impl CosmicStack { let win_geo = Rectangle::from_loc_and_size(loc, size); for window in p.windows.lock().unwrap().iter() { - window.set_geometry(win_geo); + window.set_geometry(win_geo, TAB_HEIGHT as u32); } *p.geometry.lock().unwrap() = Some(geo); diff --git a/src/shell/element/surface.rs b/src/shell/element/surface.rs index 512f7e84..3c6ad3f2 100644 --- a/src/shell/element/surface.rs +++ b/src/shell/element/surface.rs @@ -92,7 +92,7 @@ struct Minimized(AtomicBool); struct Sticky(AtomicBool); #[derive(Default)] -pub struct GlobalGeometry(pub Mutex>>); +struct GlobalGeometry(Mutex>>); pub const SSD_HEIGHT: i32 = 36; pub const RESIZE_BORDER: i32 = 10; @@ -139,14 +139,30 @@ impl CosmicSurface { } } - pub fn set_geometry(&self, geo: Rectangle) { + pub fn global_geometry(&self) -> Option> { *self .0 .user_data() .get_or_insert_threadsafe(GlobalGeometry::default) .0 .lock() - .unwrap() = Some(geo); + .unwrap() + } + + pub fn set_geometry(&self, geo: Rectangle, ssd_height: u32) { + { + let mut geo = geo; + geo.size.h += ssd_height as i32; + geo.loc.y -= ssd_height as i32; + + *self + .0 + .user_data() + .get_or_insert_threadsafe(GlobalGeometry::default) + .0 + .lock() + .unwrap() = Some(geo); + } match self.0.underlying_surface() { WindowSurface::Wayland(toplevel) => { toplevel.with_pending_state(|state| state.size = Some(geo.size.as_logical())) diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index 5e995cd4..dd43457d 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -210,16 +210,11 @@ impl CosmicWindow { pub fn set_geometry(&self, geo: Rectangle) { self.0.with_program(|p| { - let loc = ( - geo.loc.x, - geo.loc.y + if p.has_ssd(true) { SSD_HEIGHT } else { 0 }, - ); - let size = ( - geo.size.w, - std::cmp::max(geo.size.h - if p.has_ssd(true) { SSD_HEIGHT } else { 0 }, 0), - ); + let ssd_height = if p.has_ssd(true) { SSD_HEIGHT } else { 0 }; + let loc = (geo.loc.x, geo.loc.y + ssd_height); + let size = (geo.size.w, std::cmp::max(geo.size.h - ssd_height, 0)); p.window - .set_geometry(Rectangle::from_loc_and_size(loc, size)); + .set_geometry(Rectangle::from_loc_and_size(loc, size), ssd_height as u32); }); } diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 00f30285..05e71538 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -797,7 +797,7 @@ impl Workspace { } else { None }; - fullscreen.surface.set_geometry(geo); + fullscreen.surface.set_geometry(geo, 0); fullscreen.surface.send_configure(); } @@ -835,7 +835,7 @@ impl Workspace { window.set_fullscreen(true); let geo = self.output.geometry(); - let original_geometry = window.geometry().as_global(); + let original_geometry = window.global_geometry().unwrap_or_default(); let signal = if let Some(surface) = window.wl_surface() { let signal = Arc::new(AtomicBool::new(false)); add_blocker( @@ -848,7 +848,7 @@ impl Workspace { } else { None }; - window.set_geometry(geo); + window.set_geometry(geo, 0); window.send_configure(); self.fullscreen = Some(FullscreenSurface { @@ -878,7 +878,7 @@ impl Workspace { .filter(|f| &f.surface == window && f.ended_at.is_none()) { window.set_fullscreen(false); - window.set_geometry(f.original_geometry); + window.set_geometry(f.original_geometry, 0); self.floating_layer.refresh(); self.tiling_layer.recalculate(); diff --git a/src/wayland/handlers/toplevel_info.rs b/src/wayland/handlers/toplevel_info.rs index 2b196c39..18b16a07 100644 --- a/src/wayland/handlers/toplevel_info.rs +++ b/src/wayland/handlers/toplevel_info.rs @@ -3,7 +3,7 @@ use smithay::utils::{user_data::UserDataMap, Rectangle}; use crate::{ - shell::{element::surface::GlobalGeometry, CosmicSurface}, + shell::CosmicSurface, state::State, utils::prelude::Global, wayland::protocols::toplevel_info::{ @@ -52,13 +52,7 @@ impl Window for CosmicSurface { } fn global_geometry(&self) -> Option> { - self.user_data() - .get_or_insert(GlobalGeometry::default) - .0 - .lock() - .unwrap() - .clone() - .filter(|_| !self.is_minimized()) + CosmicSurface::global_geometry(self) } fn user_data(&self) -> &UserDataMap { From 76863aaf9b07e5dc62c2772c0d8e7354a58234a4 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 5 Dec 2024 12:13:31 -0500 Subject: [PATCH 41/60] fix: filter by active workspace in overlap notify --- src/wayland/handlers/overlap_notify.rs | 13 ++++++++++ src/wayland/protocols/overlap_notify.rs | 32 +++++++++++++++++++++---- src/wayland/protocols/toplevel_info.rs | 4 ++++ 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/wayland/handlers/overlap_notify.rs b/src/wayland/handlers/overlap_notify.rs index 7b948653..5afd464f 100644 --- a/src/wayland/handlers/overlap_notify.rs +++ b/src/wayland/handlers/overlap_notify.rs @@ -37,6 +37,19 @@ impl OverlapNotifyHandler for State { let shell = self.common.shell.read().unwrap(); shell.outputs().cloned().collect::>().into_iter() } + + fn active_workspaces( + &self, + ) -> impl Iterator { + let shell = self.common.shell.read().unwrap(); + shell + .workspaces + .sets + .iter() + .map(|(_, set)| set.workspaces[set.active].handle) + .collect::>() + .into_iter() + } } delegate_overlap_notify!(State); diff --git a/src/wayland/protocols/overlap_notify.rs b/src/wayland/protocols/overlap_notify.rs index d29102f3..1c7d2864 100644 --- a/src/wayland/protocols/overlap_notify.rs +++ b/src/wayland/protocols/overlap_notify.rs @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only -use std::{collections::HashMap, sync::Mutex}; +use std::{ + collections::{HashMap, HashSet}, + sync::Mutex, +}; use cosmic_protocols::{ overlap_notify::v1::server::{ @@ -32,8 +35,11 @@ use wayland_backend::server::{GlobalId, ObjectId}; use crate::utils::prelude::{RectExt, RectGlobalExt, RectLocalExt}; -use super::toplevel_info::{ - ToplevelHandleState, ToplevelInfoGlobalData, ToplevelInfoHandler, ToplevelState, Window, +use super::{ + toplevel_info::{ + ToplevelHandleState, ToplevelInfoGlobalData, ToplevelInfoHandler, ToplevelState, Window, + }, + workspace::WorkspaceHandle, }; #[derive(Debug)] @@ -82,8 +88,10 @@ impl OverlapNotifyState { + 'static, W: Window + 'static, { + let active_workspaces: Vec<_> = state.active_workspaces().collect(); for output in state.outputs() { let map = layer_map_for_output(&output); + for layer_surface in map.layers() { if let Some(data) = layer_surface .user_data() @@ -100,7 +108,22 @@ impl OverlapNotifyState { .as_local() .to_global(&output); - for window in state.toplevel_info_state().registered_toplevels() { + for window in + state + .toplevel_info_state() + .registered_toplevels() + .filter(|w| { + let state = w + .user_data() + .get::() + .unwrap() + .lock() + .unwrap(); + active_workspaces.iter().any(|active_workspace| { + state.in_workspace(&active_workspace) + }) + }) + { if let Some(window_geo) = window.global_geometry() { if let Some(intersection) = layer_geo.intersection(window_geo) { // relative to layer location @@ -146,6 +169,7 @@ pub trait OverlapNotifyHandler: ToplevelInfoHandler { fn overlap_notify_state(&mut self) -> &mut OverlapNotifyState; fn layer_surface_from_resource(&self, resource: ZwlrLayerSurfaceV1) -> Option; fn outputs(&self) -> impl Iterator; + fn active_workspaces(&self) -> impl Iterator; } pub struct OverlapNotifyGlobalData { diff --git a/src/wayland/protocols/toplevel_info.rs b/src/wayland/protocols/toplevel_info.rs index a7f3b64d..3d4d2538 100644 --- a/src/wayland/protocols/toplevel_info.rs +++ b/src/wayland/protocols/toplevel_info.rs @@ -80,6 +80,10 @@ impl ToplevelStateInner { pub fn foreign_handle(&self) -> Option<&ForeignToplevelHandle> { self.foreign_handle.as_ref() } + + pub fn in_workspace(&self, handle: &WorkspaceHandle) -> bool { + self.workspaces.contains(handle) + } } pub struct ToplevelHandleStateInner { From fce52cb6a21a6cb7b1394eb8586d6a7fa64c2332 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Sat, 28 Sep 2024 12:28:17 +0200 Subject: [PATCH 42/60] focus: Un-activate non-active workspaces --- src/shell/focus/mod.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/shell/focus/mod.rs b/src/shell/focus/mod.rs index 9f3bc71a..6ab2e857 100644 --- a/src/shell/focus/mod.rs +++ b/src/shell/focus/mod.rs @@ -203,7 +203,7 @@ impl Shell { m.window.configure(); } - let workspace = self.workspaces.active_mut(&output); + let workspace = &mut set.workspaces[set.active]; for focused in focused_windows.iter() { raise_with_children(&mut workspace.floating_layer, focused); } @@ -215,6 +215,16 @@ impl Shell { m.window.set_activated(false); m.window.configure(); } + + for (i, workspace) in set.workspaces.iter().enumerate() { + if i == set.active { + continue; + } + for window in workspace.mapped() { + window.set_activated(false); + window.configure(); + } + } } } } From 7829e7696d276c0b51ea4834986a0a37e40b9ee4 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 5 Dec 2024 18:31:34 +0100 Subject: [PATCH 43/60] focus: Only consider focused output --- src/shell/focus/mod.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/shell/focus/mod.rs b/src/shell/focus/mod.rs index 6ab2e857..638ac4ec 100644 --- a/src/shell/focus/mod.rs +++ b/src/shell/focus/mod.rs @@ -172,7 +172,7 @@ impl Shell { let focused_windows = self .seats .iter() - .flat_map(|seat| { + .map(|seat| { if matches!( seat.get_keyboard().unwrap().current_focus(), Some(KeyboardFocusTarget::Group(_)) @@ -180,11 +180,10 @@ impl Shell { return None; } - Some(self.outputs().flat_map(|o| { - let space = self.active_space(o); - let stack = space.focus_stack.get(seat); - stack.last().cloned() - })) + let output = seat.focused_or_active_output(); + let space = self.active_space(&output); + let stack = space.focus_stack.get(seat); + stack.last().cloned() }) .flatten() .collect::>(); @@ -223,7 +222,7 @@ impl Shell { for window in workspace.mapped() { window.set_activated(false); window.configure(); - } + } } } } From 9b50d0f50606891440cf9844304ae3c8b14034db Mon Sep 17 00:00:00 2001 From: Michal Rostecki Date: Sun, 8 Dec 2024 14:40:52 +0100 Subject: [PATCH 44/60] main: Update D-Bus activation environment also on non-systemd systems On systems without systemd, use zbus to update D-Bus activation environment with `WAYLAND_DISPLAY` and `DISPLAY` variables. Fixes #1037 --- src/dbus/mod.rs | 25 ++++++++++++++++++++++++- src/main.rs | 6 +++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/dbus/mod.rs b/src/dbus/mod.rs index cea8c261..13b2bcc5 100644 --- a/src/dbus/mod.rs +++ b/src/dbus/mod.rs @@ -1,6 +1,8 @@ -use crate::state::{BackendData, State}; +use crate::state::{BackendData, Common, State}; use anyhow::{Context, Result}; use calloop::{InsertError, LoopHandle, RegistrationToken}; +use std::collections::HashMap; +use zbus::blocking::{fdo::DBusProxy, Connection}; mod power; @@ -64,3 +66,24 @@ pub fn init(evlh: &LoopHandle<'static, State>) -> Result> Ok(tokens) } + +/// Updated the D-Bus activation environment with `WAYLAND_DISPLAY` and +/// `DISPLAY` variables. +pub fn ready(common: &Common) -> Result<()> { + let conn = Connection::session()?; + let proxy = DBusProxy::new(&conn)?; + + proxy.update_activation_environment(HashMap::from([ + ("WAYLAND_DISPLAY", common.socket.to_str().unwrap()), + ( + "DISPLAY", + &common + .xwayland_state + .as_ref() + .map(|s| format!(":{}", s.display)) + .unwrap_or(String::new()), + ), + ]))?; + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index aba0727e..43b8a698 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,9 +45,13 @@ impl State { // into systemd and the session? self.ready.call_once(|| { // potentially tell systemd we are setup now - #[cfg(feature = "systemd")] if let state::BackendData::Kms(_) = &self.backend { + #[cfg(feature = "systemd")] systemd::ready(&self.common); + #[cfg(not(feature = "systemd"))] + if let Err(err) = dbus::ready(&self.common) { + error!(?err, "Failed to update the D-Bus activation environment"); + } } // potentially tell the session we are setup now From 2c01c944770a9ac085166f4ae1b3ad14e0f8b001 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 10 Dec 2024 11:25:37 -0500 Subject: [PATCH 45/60] fix: activation of an element outside the current workspace If the activated element is outside the current workspace, its workspace should be set to urgent --- src/wayland/handlers/xdg_activation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wayland/handlers/xdg_activation.rs b/src/wayland/handlers/xdg_activation.rs index 78a488e6..7decb4bf 100644 --- a/src/wayland/handlers/xdg_activation.rs +++ b/src/wayland/handlers/xdg_activation.rs @@ -152,7 +152,7 @@ impl XdgActivationHandler for State { } } - if workspace == ¤t_workspace.handle || in_current_workspace { + if in_current_workspace { let target = element.into(); std::mem::drop(shell); From fdfc5cbeb4b4909861dad045f909f54bf46479c4 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 10 Dec 2024 14:25:15 -0800 Subject: [PATCH 46/60] Use `WeakOutput` when accessing data of `ZwlrOutputHeadV1` It seems cd9ff0b7bb9ef5559420ac2e91f1e97e837fcd34 broke mirroring. Though it's also a bug that if any of these if conditions is not met, the data will not be initialized. Fixes https://github.com/pop-os/cosmic-epoch/issues/1341. --- .../protocols/output_configuration/handlers/cosmic.rs | 8 +++++--- .../protocols/output_configuration/handlers/wlr.rs | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/wayland/protocols/output_configuration/handlers/cosmic.rs b/src/wayland/protocols/output_configuration/handlers/cosmic.rs index f36d002d..d7145f58 100644 --- a/src/wayland/protocols/output_configuration/handlers/cosmic.rs +++ b/src/wayland/protocols/output_configuration/handlers/cosmic.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use smithay::{ - output::{Mode, Output}, + output::Mode, reexports::{ wayland_protocols_wlr::output_management::v1::server::{ zwlr_output_configuration_head_v1::{self, ZwlrOutputConfigurationHeadV1}, @@ -179,7 +179,9 @@ where } => { if let Ok(obj) = obj.upgrade() { if let Some(data) = obj.data::() { - if let Some(output) = mirroring.data::() { + if let Some(output) = + mirroring.data::().and_then(|x| x.upgrade()) + { let mut pending = data.lock().unwrap(); if pending.heads.iter().any(|(h, _)| *h == head) { obj.post_error( @@ -195,7 +197,7 @@ where if let Some(conf) = c.data::() { match conf.lock().unwrap().mirroring.as_ref() { Some(mirrored) => { - head.data::().is_some_and(|o| o == mirrored) // we are already a mirror target -> invalid + head.data::().is_some_and(|o| o == mirrored) // we are already a mirror target -> invalid || *h == mirroring // our target already mirrors -> invalid } None => false, diff --git a/src/wayland/protocols/output_configuration/handlers/wlr.rs b/src/wayland/protocols/output_configuration/handlers/wlr.rs index 1ac7ee19..55b5cfc0 100644 --- a/src/wayland/protocols/output_configuration/handlers/wlr.rs +++ b/src/wayland/protocols/output_configuration/handlers/wlr.rs @@ -234,7 +234,7 @@ where if pending.heads.iter().any(|(_, c)| match c { Some(conf) => { if let Some(output_conf) = conf.data::() { - if let Some(output) = head.data::() { + if let Some(output) = head.data::() { let pending_conf = output_conf.lock().unwrap(); pending_conf.mirroring.as_ref().is_some_and(|o| o == output) } else { From 16a1214207a64730583eb90ca558b5d7ceabb0d3 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 10 Dec 2024 14:53:34 -0800 Subject: [PATCH 47/60] output_configuration: Use `unwrap` in calls to `Resource::data` Having if conditions for these is unnecessary when they should never be reached. (This is commonly unwrapped in `smithay`.) Some of these else conditions fail to call `data_init.init` with a new id, so they'd result in a crash later anyway. --- .../output_configuration/handlers/cosmic.rs | 132 ++++++++---------- .../output_configuration/handlers/wlr.rs | 14 +- 2 files changed, 65 insertions(+), 81 deletions(-) diff --git a/src/wayland/protocols/output_configuration/handlers/cosmic.rs b/src/wayland/protocols/output_configuration/handlers/cosmic.rs index d7145f58..b3d23606 100644 --- a/src/wayland/protocols/output_configuration/handlers/cosmic.rs +++ b/src/wayland/protocols/output_configuration/handlers/cosmic.rs @@ -99,10 +99,9 @@ where } } zcosmic_output_manager_v1::Request::GetConfiguration { extended, config } => { - if let Some(pending) = config.data::() { - let obj = data_init.init(extended, config.downgrade()); - pending.lock().unwrap().extension_obj = Some(obj); - } + let pending = config.data::().unwrap(); + let obj = data_init.init(extended, config.downgrade()); + pending.lock().unwrap().extension_obj = Some(obj); } zcosmic_output_manager_v1::Request::GetConfigurationHead { extended, @@ -178,56 +177,49 @@ where mirroring, } => { if let Ok(obj) = obj.upgrade() { - if let Some(data) = obj.data::() { - if let Some(output) = - mirroring.data::().and_then(|x| x.upgrade()) - { - let mut pending = data.lock().unwrap(); - if pending.heads.iter().any(|(h, _)| *h == head) { - obj.post_error( - zwlr_output_configuration_v1::Error::AlreadyConfiguredHead, - format!("{:?} was already configured", head), - ); - return; - } + let data = obj.data::().unwrap(); + if let Some(output) = mirroring.data::().unwrap().upgrade() { + let mut pending = data.lock().unwrap(); + if pending.heads.iter().any(|(h, _)| *h == head) { + obj.post_error( + zwlr_output_configuration_v1::Error::AlreadyConfiguredHead, + format!("{:?} was already configured", head), + ); + return; + } - if pending.heads.iter().any(|(h, c)| { - match c.as_ref() { - Some(c) => { - if let Some(conf) = c.data::() { - match conf.lock().unwrap().mirroring.as_ref() { - Some(mirrored) => { - head.data::().is_some_and(|o| o == mirrored) // we are already a mirror target -> invalid - || *h == mirroring // our target already mirrors -> invalid - } - None => false, - } - } else { - *h == mirroring // unknown state for our mirror target -> invalid + if pending.heads.iter().any(|(h, c)| { + match c.as_ref() { + Some(c) => { + let conf = c.data::().unwrap(); + match conf.lock().unwrap().mirroring.as_ref() { + Some(mirrored) => { + head.data::().unwrap() == mirrored // we are already a mirror target -> invalid + || *h == mirroring // our target already mirrors -> invalid } + None => false, } - None => *h == mirroring, // disabled state for our mirror target -> invalid } - }) { - extension_obj.post_error( - zcosmic_output_configuration_v1::Error::MirroredHeadBusy, - format!("{:?} can't mirror, it is either a mirror target itself or {:?} is not enabled/already mirroring", head, mirroring), - ); + None => *h == mirroring, // disabled state for our mirror target -> invalid } - - let output_conf = PendingOutputConfiguration::default(); - output_conf.lock().unwrap().mirroring = Some(output.clone()); - let conf_head = data_init.init(id, output_conf); - pending.heads.push((head, Some(conf_head))); + }) { + extension_obj.post_error( + zcosmic_output_configuration_v1::Error::MirroredHeadBusy, + format!("{:?} can't mirror, it is either a mirror target itself or {:?} is not enabled/already mirroring", head, mirroring), + ); } + + let output_conf = PendingOutputConfiguration::default(); + output_conf.lock().unwrap().mirroring = Some(output.clone()); + let conf_head = data_init.init(id, output_conf); + pending.heads.push((head, Some(conf_head))); } } } zcosmic_output_configuration_v1::Request::Release => { if let Ok(obj) = obj.upgrade() { - if let Some(data) = obj.data::() { - data.lock().unwrap().extension_obj.take(); - } + let data = obj.data::().unwrap(); + data.lock().unwrap().extension_obj.take(); } } _ => {} @@ -259,40 +251,38 @@ where match request { zcosmic_output_configuration_head_v1::Request::SetScale1000 { scale_1000 } => { if let Ok(obj) = obj.upgrade() { - if let Some(data) = obj.data::() { - let mut pending = data.lock().unwrap(); - if pending.scale.is_some() { - obj.post_error( - zwlr_output_configuration_head_v1::Error::AlreadySet, - format!("{:?} already had a scale configured", obj), - ); - return; - } - pending.scale = Some((scale_1000 as f64) / 1000.0); + let data = obj.data::().unwrap(); + let mut pending = data.lock().unwrap(); + if pending.scale.is_some() { + obj.post_error( + zwlr_output_configuration_head_v1::Error::AlreadySet, + format!("{:?} already had a scale configured", obj), + ); + return; } + pending.scale = Some((scale_1000 as f64) / 1000.0); } } zcosmic_output_configuration_head_v1::Request::SetAdaptiveSyncExt { state } => { if let Ok(obj) = obj.upgrade() { - if let Some(data) = obj.data::() { - let mut pending = data.lock().unwrap(); - if pending.adaptive_sync.is_some() { - obj.post_error( - zwlr_output_configuration_head_v1::Error::AlreadySet, - format!("{:?} already had an adaptive_sync state configured", obj), - ); - return; - } - pending.adaptive_sync = match state.into_result() { - Ok(zcosmic_output_head_v1::AdaptiveSyncStateExt::Always) => { - Some(AdaptiveSync::Force) - } - Ok(zcosmic_output_head_v1::AdaptiveSyncStateExt::Automatic) => { - Some(AdaptiveSync::Enabled) - } - _ => Some(AdaptiveSync::Disabled), - }; + let data = obj.data::().unwrap(); + let mut pending = data.lock().unwrap(); + if pending.adaptive_sync.is_some() { + obj.post_error( + zwlr_output_configuration_head_v1::Error::AlreadySet, + format!("{:?} already had an adaptive_sync state configured", obj), + ); + return; } + pending.adaptive_sync = match state.into_result() { + Ok(zcosmic_output_head_v1::AdaptiveSyncStateExt::Always) => { + Some(AdaptiveSync::Force) + } + Ok(zcosmic_output_head_v1::AdaptiveSyncStateExt::Automatic) => { + Some(AdaptiveSync::Enabled) + } + _ => Some(AdaptiveSync::Disabled), + }; } } _ => {} diff --git a/src/wayland/protocols/output_configuration/handlers/wlr.rs b/src/wayland/protocols/output_configuration/handlers/wlr.rs index 55b5cfc0..4666b114 100644 --- a/src/wayland/protocols/output_configuration/handlers/wlr.rs +++ b/src/wayland/protocols/output_configuration/handlers/wlr.rs @@ -233,16 +233,10 @@ where if pending.heads.iter().any(|(_, c)| match c { Some(conf) => { - if let Some(output_conf) = conf.data::() { - if let Some(output) = head.data::() { - let pending_conf = output_conf.lock().unwrap(); - pending_conf.mirroring.as_ref().is_some_and(|o| o == output) - } else { - false - } - } else { - false - } + let output_conf = conf.data::().unwrap(); + let output = head.data::().unwrap(); + let pending_conf = output_conf.lock().unwrap(); + pending_conf.mirroring.as_ref().is_some_and(|o| o == output) } None => false, }) { From 0d0b89d53889cc1049eafc7747930b111a05b3a0 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 10 Dec 2024 14:56:57 -0800 Subject: [PATCH 48/60] Call `data_init.init` even if weak references are not alive --- .../protocols/output_configuration/handlers/cosmic.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/wayland/protocols/output_configuration/handlers/cosmic.rs b/src/wayland/protocols/output_configuration/handlers/cosmic.rs index b3d23606..b0b768df 100644 --- a/src/wayland/protocols/output_configuration/handlers/cosmic.rs +++ b/src/wayland/protocols/output_configuration/handlers/cosmic.rs @@ -213,7 +213,13 @@ where output_conf.lock().unwrap().mirroring = Some(output.clone()); let conf_head = data_init.init(id, output_conf); pending.heads.push((head, Some(conf_head))); + } else { + let output_conf = PendingOutputConfiguration::default(); + data_init.init(id, output_conf); } + } else { + let output_conf = PendingOutputConfiguration::default(); + data_init.init(id, output_conf); } } zcosmic_output_configuration_v1::Request::Release => { From 7de52054ff14aa7f918102543483b57ce9764d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= Date: Thu, 24 Oct 2024 00:15:05 +0200 Subject: [PATCH 49/60] chore: update iced/libcosmic --- Cargo.lock | 597 ++++++++++++-------------- src/shell/element/resize_indicator.rs | 49 +-- src/shell/element/stack.rs | 25 +- src/shell/element/stack/tab.rs | 39 +- src/shell/element/stack/tab_text.rs | 2 +- src/shell/element/stack/tabs.rs | 45 +- src/shell/element/stack_hover.rs | 16 +- src/shell/element/swap_indicator.rs | 16 +- src/shell/element/window.rs | 12 +- src/shell/grabs/menu/item.rs | 15 +- src/shell/grabs/menu/mod.rs | 36 +- src/utils/iced.rs | 201 ++++----- 12 files changed, 503 insertions(+), 550 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b27bc28..8c3446a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,7 +89,7 @@ dependencies = [ "log", "ndk", "ndk-context", - "ndk-sys", + "ndk-sys 0.6.0+11769913", "num_enum", "thiserror", ] @@ -168,9 +168,9 @@ checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "as-raw-xcb-connection" @@ -178,22 +178,13 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" -[[package]] -name = "ash" -version = "0.37.3+1.3.251" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" -dependencies = [ - "libloading 0.7.4", -] - [[package]] name = "ash" version = "0.38.0+1.3.281" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" dependencies = [ - "libloading 0.8.5", + "libloading", ] [[package]] @@ -391,18 +382,18 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bit-set" -version = "0.5.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.6.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" [[package]] name = "bit_field" @@ -617,7 +608,7 @@ dependencies = [ [[package]] name = "clipboard_macos" version = "0.1.0" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-8#7c59b07b9172d8e0401f7e06609e1050575309c9" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13#a83bf83784276aaa882ef13555295a2ad9edd265" dependencies = [ "objc", "objc-foundation", @@ -627,7 +618,7 @@ dependencies = [ [[package]] name = "clipboard_wayland" version = "0.2.2" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-8#7c59b07b9172d8e0401f7e06609e1050575309c9" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13#a83bf83784276aaa882ef13555295a2ad9edd265" dependencies = [ "dnd", "mime 0.1.0", @@ -637,7 +628,7 @@ dependencies = [ [[package]] name = "clipboard_x11" version = "0.4.2" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-8#7c59b07b9172d8e0401f7e06609e1050575309c9" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13#a83bf83784276aaa882ef13555295a2ad9edd265" dependencies = [ "thiserror", "x11rb", @@ -871,7 +862,7 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" dependencies = [ "atomicwrites", "calloop 0.14.1", @@ -890,7 +881,7 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" dependencies = [ "quote", "syn 1.0.109", @@ -925,15 +916,15 @@ dependencies = [ [[package]] name = "cosmic-text" version = "0.12.1" -source = "git+https://github.com/pop-os/cosmic-text.git#4fe90bb6126c22f589b46768d7754d65ae300c5e" +source = "git+https://github.com/pop-os/cosmic-text.git#1f4065c1c3399efad58841082212f7c039b58480" dependencies = [ "bitflags 2.6.0", - "fontdb", + "fontdb 0.16.2", "log", "rangemap", "rayon", - "rustc-hash", - "rustybuzz 0.14.1", + "rustc-hash 1.1.0", + "rustybuzz", "self_cell 1.0.4", "smol_str", "swash", @@ -948,7 +939,7 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" dependencies = [ "almost", "cosmic-config", @@ -1060,11 +1051,12 @@ checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" [[package]] name = "d3d12" -version = "0.19.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=20fda69#20fda698341efbdc870b8027d6d49f5bf3f36109" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdbd1f579714e3c809ebd822c81ef148b1ceaeb3d535352afc73fd0c4c6a0017" dependencies = [ "bitflags 2.6.0", - "libloading 0.8.5", + "libloading", "winapi", ] @@ -1199,7 +1191,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.5", + "libloading", ] [[package]] @@ -1214,7 +1206,7 @@ dependencies = [ [[package]] name = "dnd" version = "0.1.0" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-8#7c59b07b9172d8e0401f7e06609e1050575309c9" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13#a83bf83784276aaa882ef13555295a2ad9edd265" dependencies = [ "bitflags 2.6.0", "mime 0.1.0", @@ -1223,6 +1215,15 @@ dependencies = [ "smithay-clipboard", ] +[[package]] +name = "document-features" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" +dependencies = [ + "litrs", +] + [[package]] name = "downcast-rs" version = "1.2.1" @@ -1366,7 +1367,7 @@ dependencies = [ "enum-map", "log", "mime_guess2", - "resvg", + "resvg 0.37.0", ] [[package]] @@ -1652,7 +1653,7 @@ dependencies = [ "fluent-syntax", "intl-memoizer", "intl_pluralrules", - "rustc-hash", + "rustc-hash 1.1.0", "self_cell 0.10.3", "smallvec", "unic-langid", @@ -1723,6 +1724,20 @@ dependencies = [ "ttf-parser 0.20.0", ] +[[package]] +name = "fontdb" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e32eac81c1135c1df01d4e6d4233c47ba11f6a6d07f33e0bba09d18797077770" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2 0.9.4", + "slotmap", + "tinyvec", + "ttf-parser 0.21.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -1759,16 +1774,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fraction" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f158e3ff0a1b334408dc9fb811cd99b446986f4d8b741bb08f9df1604085ae7" -dependencies = [ - "lazy_static", - "num", -] - [[package]] name = "freedesktop-icons" version = "0.2.6" @@ -1960,16 +1965,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "gif" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" -dependencies = [ - "color_quant", - "weezl", -] - [[package]] name = "gif" version = "0.13.1" @@ -1999,9 +1994,9 @@ dependencies = [ [[package]] name = "glam" -version = "0.24.2" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945" +checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" [[package]] name = "glow" @@ -2041,24 +2036,13 @@ dependencies = [ [[package]] name = "glutin_wgl_sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" +checksum = "0a4e1951bbd9434a81aa496fe59ccc2235af3820d27b85f9314e279609211e2c" dependencies = [ "gl_generator", ] -[[package]] -name = "glyphon" -version = "0.5.0" -source = "git+https://github.com/pop-os/glyphon.git?tag=v0.5.0#1b0646ff8f74da92d3be704dfc2257d7f4d7eed8" -dependencies = [ - "cosmic-text", - "etagere", - "lru", - "wgpu", -] - [[package]] name = "gpu-alloc" version = "0.6.0" @@ -2080,9 +2064,9 @@ dependencies = [ [[package]] name = "gpu-allocator" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" +checksum = "fdd4240fc91d3433d5e5b0fc5b67672d771850dc19bbee03c1381e19322803d7" dependencies = [ "log", "presser", @@ -2093,9 +2077,9 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" +checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557" dependencies = [ "bitflags 2.6.0", "gpu-descriptor-types", @@ -2104,9 +2088,9 @@ dependencies = [ [[package]] name = "gpu-descriptor-types" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ "bitflags 2.6.0", ] @@ -2162,7 +2146,7 @@ dependencies = [ "bitflags 2.6.0", "com", "libc", - "libloading 0.8.5", + "libloading", "thiserror", "widestring", "winapi", @@ -2311,8 +2295,8 @@ dependencies = [ [[package]] name = "iced" -version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +version = "0.14.0-dev" +source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" dependencies = [ "dnd", "iced_core", @@ -2327,45 +2311,60 @@ dependencies = [ [[package]] name = "iced_core" -version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +version = "0.14.0-dev" +source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" dependencies = [ "bitflags 2.6.0", + "bytes", "dnd", + "glam", "log", "mime 0.1.0", "num-traits", + "once_cell", "palette", "raw-window-handle", + "rustc-hash 2.0.0", "serde", "smol_str", "thiserror", - "web-time 0.2.4", + "web-time", "window_clipboard", - "xxhash-rust", ] [[package]] name = "iced_futures" -version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +version = "0.14.0-dev" +source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" dependencies = [ "futures", "iced_core", "log", + "rustc-hash 2.0.0", "wasm-bindgen-futures", "wasm-timer", ] +[[package]] +name = "iced_glyphon" +version = "0.6.0" +source = "git+https://github.com/pop-os/glyphon.git?tag=iced-0.14-dev#6ef9d12a20cfd0f7bdf38136a26ded9f7459ec8b" +dependencies = [ + "cosmic-text", + "etagere", + "lru", + "rustc-hash 2.0.0", + "wgpu", +] + [[package]] name = "iced_graphics" -version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +version = "0.14.0-dev" +source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" dependencies = [ "bitflags 2.6.0", "bytemuck", "cosmic-text", - "glam", "half", "iced_core", "iced_futures", @@ -2375,16 +2374,15 @@ dependencies = [ "lyon_path", "once_cell", "raw-window-handle", - "rustc-hash", + "rustc-hash 2.0.0", "thiserror", "unicode-segmentation", - "xxhash-rust", ] [[package]] name = "iced_renderer" -version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +version = "0.14.0-dev" +source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2395,63 +2393,56 @@ dependencies = [ [[package]] name = "iced_runtime" -version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +version = "0.14.0-dev" +source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" dependencies = [ + "bytes", "dnd", "iced_core", "iced_futures", + "raw-window-handle", "thiserror", "window_clipboard", ] -[[package]] -name = "iced_style" -version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" -dependencies = [ - "iced_core", - "once_cell", - "palette", -] - [[package]] name = "iced_tiny_skia" -version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +version = "0.14.0-dev" +source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" dependencies = [ "bytemuck", "cosmic-text", "iced_graphics", - "kurbo", + "kurbo 0.10.4", "log", - "resvg", - "rustc-hash", + "resvg 0.42.0", + "rustc-hash 2.0.0", "softbuffer", "tiny-skia", - "xxhash-rust", ] [[package]] name = "iced_wgpu" -version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +version = "0.14.0-dev" +source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" dependencies = [ "as-raw-xcb-connection", "bitflags 2.6.0", "bytemuck", "futures", "glam", - "glyphon", "guillotiere", + "iced_glyphon", "iced_graphics", "log", "lyon", "once_cell", "raw-window-handle", - "resvg", + "resvg 0.42.0", + "rustc-hash 2.0.0", "rustix", "smithay-client-toolkit", + "thiserror", "tiny-xlib", "wayland-backend", "wayland-client", @@ -2463,15 +2454,16 @@ dependencies = [ [[package]] name = "iced_widget" -version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +version = "0.14.0-dev" +source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" dependencies = [ "dnd", "iced_renderer", "iced_runtime", - "iced_style", "num-traits", + "once_cell", "ouroboros", + "rustc-hash 2.0.0", "thiserror", "unicode-segmentation", "window_clipboard", @@ -2511,7 +2503,7 @@ dependencies = [ "byteorder", "color_quant", "exr", - "gif 0.13.1", + "gif", "jpeg-decoder", "num-traits", "png", @@ -2631,6 +2623,15 @@ version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a611371471e98973dbcab4e0ec66c31a10bc356eeb4d54a0e05eac8158fe38c" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -2712,7 +2713,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ "libc", - "libloading 0.8.5", + "libloading", "pkg-config", ] @@ -2760,6 +2761,26 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "kurbo" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1618d4ebd923e97d67e7cd363d80aef35fe961005cbbbb3d2dad8bdd1bc63440" +dependencies = [ + "arrayvec", + "smallvec", +] + +[[package]] +name = "kurbo" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f" +dependencies = [ + "arrayvec", + "smallvec", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -2781,7 +2802,7 @@ checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/#5306649be1cfb6c384da11e2ab25cafc4be79b14" +source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" dependencies = [ "apply", "chrono", @@ -2789,14 +2810,12 @@ dependencies = [ "cosmic-theme", "css-color", "derive_setters", - "fraction", "freedesktop-icons", "iced", "iced_core", "iced_futures", "iced_renderer", "iced_runtime", - "iced_style", "iced_tiny_skia", "iced_widget", "lazy_static", @@ -2811,16 +2830,6 @@ dependencies = [ "ustr", ] -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - [[package]] name = "libloading" version = "0.8.5" @@ -2828,7 +2837,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -2918,6 +2927,12 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0b5399f6804fbab912acbd8878ed3532d506b7c951b8f9f164ef90fef39e3f4" +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + [[package]] name = "locale_config" version = "0.3.0" @@ -2975,9 +2990,6 @@ name = "lru" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" -dependencies = [ - "hashbrown 0.14.5", -] [[package]] name = "lyon" @@ -3084,9 +3096,9 @@ dependencies = [ [[package]] name = "metal" -version = "0.27.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" +checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" dependencies = [ "bitflags 2.6.0", "block", @@ -3100,7 +3112,7 @@ dependencies = [ [[package]] name = "mime" version = "0.1.0" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-8#7c59b07b9172d8e0401f7e06609e1050575309c9" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13#a83bf83784276aaa882ef13555295a2ad9edd265" dependencies = [ "smithay-clipboard", ] @@ -3163,18 +3175,19 @@ checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b" [[package]] name = "naga" -version = "0.19.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=20fda69#20fda698341efbdc870b8027d6d49f5bf3f36109" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad" dependencies = [ "arrayvec", "bit-set", "bitflags 2.6.0", + "cfg_aliases 0.1.1", "codespan-reporting", "hexf-parse", "indexmap 2.3.0", "log", - "num-traits", - "rustc-hash", + "rustc-hash 1.1.0", "spirv", "termcolor", "thiserror", @@ -3190,7 +3203,7 @@ dependencies = [ "bitflags 2.6.0", "jni-sys", "log", - "ndk-sys", + "ndk-sys 0.6.0+11769913", "num_enum", "raw-window-handle", "thiserror", @@ -3202,6 +3215,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", +] + [[package]] name = "ndk-sys" version = "0.6.0+11769913" @@ -3281,76 +3303,12 @@ dependencies = [ "winapi", ] -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -3408,7 +3366,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", - "objc_exception", ] [[package]] @@ -3625,15 +3582,6 @@ dependencies = [ "objc2-foundation", ] -[[package]] -name = "objc_exception" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" -dependencies = [ - "cc", -] - [[package]] name = "objc_id" version = "0.1.1" @@ -3704,9 +3652,9 @@ dependencies = [ [[package]] name = "ouroboros" -version = "0.17.2" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ba07320d39dfea882faa70554b4bd342a5f273ed59ba7c1c6b4c840492c954" +checksum = "944fa20996a25aded6b4795c6d63f10014a7a83f8be9828a11860b08c5fc4a67" dependencies = [ "aliasable", "ouroboros_macro", @@ -3715,13 +3663,14 @@ dependencies = [ [[package]] name = "ouroboros_macro" -version = "0.17.2" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec4c6225c69b4ca778c0aea097321a64c421cf4577b331c61b229267edabb6f8" +checksum = "39b0deead1528fd0e5947a8546a9642a9777c25f6e1e26f34c97b204bbb465bd" dependencies = [ "heck", - "proc-macro-error", + "itertools", "proc-macro2", + "proc-macro2-diagnostics", "quote", "syn 2.0.72", ] @@ -3871,7 +3820,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ - "siphasher", + "siphasher 0.3.11", ] [[package]] @@ -4038,6 +3987,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", + "version_check", + "yansi", +] + [[package]] name = "profiling" version = "1.0.15" @@ -4273,15 +4235,28 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cadccb3d99a9efb8e5e00c16fbb732cbe400db2ec7fc004697ee7d97d86cf1f4" dependencies = [ - "gif 0.12.0", + "log", + "pico-args", + "rgb", + "svgtypes 0.13.0", + "tiny-skia", + "usvg 0.37.0", +] + +[[package]] +name = "resvg" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "944d052815156ac8fa77eaac055220e95ba0b01fa8887108ca710c03805d9051" +dependencies = [ + "gif", "jpeg-decoder", "log", "pico-args", - "png", "rgb", - "svgtypes", + "svgtypes 0.15.2", "tiny-skia", - "usvg", + "usvg 0.42.0", ] [[package]] @@ -4373,6 +4348,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustix" version = "0.38.34" @@ -4392,22 +4373,6 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" -[[package]] -name = "rustybuzz" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0ae5692c5beaad6a9e22830deeed7874eae8a4e3ba4076fb48e12c56856222c" -dependencies = [ - "bitflags 2.6.0", - "bytemuck", - "smallvec", - "ttf-parser 0.20.0", - "unicode-bidi-mirroring 0.1.0", - "unicode-ccc 0.1.2", - "unicode-properties", - "unicode-script", -] - [[package]] name = "rustybuzz" version = "0.14.1" @@ -4419,8 +4384,8 @@ dependencies = [ "libm", "smallvec", "ttf-parser 0.21.1", - "unicode-bidi-mirroring 0.2.0", - "unicode-ccc 0.2.0", + "unicode-bidi-mirroring", + "unicode-ccc", "unicode-properties", "unicode-script", ] @@ -4630,6 +4595,12 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "skrifa" version = "0.20.0" @@ -4670,7 +4641,7 @@ version = "0.3.0" source = "git+https://github.com/smithay//smithay?rev=bc1d732#bc1d7320f95cdf17f9e7aa6867cccc5903548032" dependencies = [ "appendlist", - "ash 0.38.0+1.3.281", + "ash", "bitflags 2.6.0", "calloop 0.14.1", "cc", @@ -4688,7 +4659,7 @@ dependencies = [ "indexmap 2.3.0", "input", "libc", - "libloading 0.8.5", + "libloading", "libseat", "once_cell", "pixman", @@ -4877,8 +4848,18 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e44e288cd960318917cbd540340968b90becc8bc81f171345d706e7a89d9d70" dependencies = [ - "kurbo", - "siphasher", + "kurbo 0.9.5", + "siphasher 0.3.11", +] + +[[package]] +name = "svgtypes" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "794de53cc48eaabeed0ab6a3404a65f40b3e38c067e4435883a65d2aa4ca000e" +dependencies = [ + "kurbo 0.11.1", + "siphasher 1.0.1", ] [[package]] @@ -5072,7 +5053,7 @@ checksum = "1d52f22673960ad13af14ff4025997312def1223bfa7c8e4949d099e6b3d5d1c" dependencies = [ "as-raw-xcb-connection", "ctor-lite", - "libloading 0.8.5", + "libloading", "pkg-config", "tracing", ] @@ -5272,7 +5253,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f" dependencies = [ - "rustc-hash", + "rustc-hash 1.1.0", ] [[package]] @@ -5338,24 +5319,12 @@ version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" -[[package]] -name = "unicode-bidi-mirroring" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" - [[package]] name = "unicode-bidi-mirroring" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" -[[package]] -name = "unicode-ccc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" - [[package]] name = "unicode-ccc" version = "0.2.0" @@ -5453,42 +5422,52 @@ dependencies = [ "log", "pico-args", "usvg-parser", - "usvg-text-layout", "usvg-tree", "xmlwriter", ] [[package]] -name = "usvg-parser" -version = "0.37.0" +name = "usvg" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd4e3c291f45d152929a31f0f6c819245e2921bfd01e7bd91201a9af39a2bdc" +checksum = "b84ea542ae85c715f07b082438a4231c3760539d902e11d093847a0b22963032" dependencies = [ + "base64 0.22.1", "data-url", "flate2", + "fontdb 0.18.0", "imagesize", - "kurbo", + "kurbo 0.11.1", "log", - "roxmltree 0.19.0", + "pico-args", + "roxmltree 0.20.0", + "rustybuzz", "simplecss", - "siphasher", - "svgtypes", - "usvg-tree", + "siphasher 1.0.1", + "strict-num", + "svgtypes 0.15.2", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", ] [[package]] -name = "usvg-text-layout" +name = "usvg-parser" version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d383a3965de199d7f96d4e11a44dd859f46e86de7f3dca9a39bf82605da0a37c" +checksum = "9bd4e3c291f45d152929a31f0f6c819245e2921bfd01e7bd91201a9af39a2bdc" dependencies = [ - "fontdb", - "kurbo", + "data-url", + "flate2", + "imagesize", + "kurbo 0.9.5", "log", - "rustybuzz 0.12.1", - "unicode-bidi", - "unicode-script", - "unicode-vo", + "roxmltree 0.19.0", + "simplecss", + "siphasher 0.3.11", + "svgtypes 0.13.0", "usvg-tree", ] @@ -5500,7 +5479,7 @@ checksum = "8ee3d202ebdb97a6215604b8f5b4d6ef9024efd623cf2e373a6416ba976ec7d3" dependencies = [ "rctree", "strict-num", - "svgtypes", + "svgtypes 0.13.0", "tiny-skia-path", ] @@ -5781,16 +5760,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web-time" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "web-time" version = "1.1.0" @@ -5809,12 +5778,13 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "wgpu" -version = "0.19.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=20fda69#20fda698341efbdc870b8027d6d49f5bf3f36109" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d1c4ba43f80542cf63a0a6ed3134629ae73e8ab51e4b765a67f3aa062eb433" dependencies = [ "arrayvec", - "cfg-if", "cfg_aliases 0.1.1", + "document-features", "js-sys", "log", "naga", @@ -5833,14 +5803,15 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "0.19.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=20fda69#20fda698341efbdc870b8027d6d49f5bf3f36109" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a" dependencies = [ "arrayvec", "bit-vec", "bitflags 2.6.0", "cfg_aliases 0.1.1", - "codespan-reporting", + "document-features", "indexmap 2.3.0", "log", "naga", @@ -5848,22 +5819,22 @@ dependencies = [ "parking_lot 0.12.3", "profiling", "raw-window-handle", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "thiserror", - "web-sys", "wgpu-hal", "wgpu-types", ] [[package]] name = "wgpu-hal" -version = "0.19.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=20fda69#20fda698341efbdc870b8027d6d49f5bf3f36109" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6bbf4b4de8b2a83c0401d9e5ae0080a2792055f25859a02bf9be97952bbed4f" dependencies = [ "android_system_properties", "arrayvec", - "ash 0.37.3+1.3.251", + "ash", "bit-set", "bitflags 2.6.0", "block", @@ -5879,10 +5850,11 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading 0.8.5", + "libloading", "log", "metal", "naga", + "ndk-sys 0.5.0+25.2.9519653", "objc", "once_cell", "parking_lot 0.12.3", @@ -5890,7 +5862,7 @@ dependencies = [ "range-alloc", "raw-window-handle", "renderdoc-sys", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "thiserror", "wasm-bindgen", @@ -5901,8 +5873,9 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "0.19.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=20fda69#20fda698341efbdc870b8027d6d49f5bf3f36109" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9d91f0e2c4b51434dfa6db77846f2793149d8e73f800fa2e41f52b8eac3c5d" dependencies = [ "bitflags 2.6.0", "js-sys", @@ -5949,7 +5922,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "window_clipboard" version = "0.4.1" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-8#7c59b07b9172d8e0401f7e06609e1050575309c9" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13#a83bf83784276aaa882ef13555295a2ad9edd265" dependencies = [ "clipboard-win", "clipboard_macos", @@ -6293,7 +6266,7 @@ dependencies = [ "wayland-protocols", "wayland-protocols-plasma", "web-sys", - "web-time 1.1.0", + "web-time", "windows-sys 0.52.0", "x11-dl", "x11rb", @@ -6338,7 +6311,7 @@ dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "libloading 0.8.5", + "libloading", "once_cell", "rustix", "x11rb-protocol", @@ -6439,10 +6412,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] -name = "xxhash-rust" -version = "0.8.12" +name = "yansi" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a5cbf750400958819fb6178eaa83bee5cd9c29a26a40cc241df8c70fdd46984" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yazi" diff --git a/src/shell/element/resize_indicator.rs b/src/shell/element/resize_indicator.rs index 99c975ea..21f73428 100644 --- a/src/shell/element/resize_indicator.rs +++ b/src/shell/element/resize_indicator.rs @@ -9,7 +9,10 @@ use crate::{ use calloop::LoopHandle; use cosmic::{ - iced::widget::{column, container, horizontal_space, row, vertical_space}, + iced::{ + widget::{column, container, horizontal_space, row, vertical_space}, + Alignment, + }, iced_core::{Background, Border, Color, Length}, theme, widget::{icon::from_name, text}, @@ -66,7 +69,7 @@ impl Program for ResizeIndicatorInternal { fn view(&self) -> cosmic::Element<'_, Self::Message> { let edges = self.edges.lock().unwrap(); let icon_container_style = || { - theme::Container::custom(|theme| container::Appearance { + theme::Container::custom(|theme| container::Style { icon_color: Some(Color::from(theme.cosmic().accent.on)), text_color: Some(Color::from(theme.cosmic().accent.on)), background: Some(Background::Color(theme.cosmic().accent_color().into())), @@ -90,14 +93,13 @@ impl Program for ResizeIndicatorInternal { .prefer_svg(true) .apply(container) .padding(8) - .style(icon_container_style()) + .class(icon_container_style()) .width(Length::Shrink) .apply(container) - .center_x() - .width(Length::Fill) + .center_x(Length::Fill) .into() } else { - vertical_space(36).into() + vertical_space().height(36).into() }, row(vec![ if edges.contains(ResizeEdge::LEFT) { @@ -110,35 +112,32 @@ impl Program for ResizeIndicatorInternal { .prefer_svg(true) .apply(container) .padding(8) - .style(icon_container_style()) + .class(icon_container_style()) .width(Length::Shrink) .apply(container) - .center_y() - .height(Length::Fill) + .center_y(Length::Fill) .into() } else { - horizontal_space(36).into() + horizontal_space().width(36).into() }, row(vec![ text::heading(&self.shortcut1).into(), text::body(fl!("grow-window")).into(), - horizontal_space(40).into(), + horizontal_space().width(40).into(), text::heading(&self.shortcut2).into(), text::body(fl!("shrink-window")).into(), ]) .apply(container) - .center_x() - .center_y() + .align_x(Alignment::Center) + .align_y(Alignment::Center) .padding(16) .apply(container) - .style(icon_container_style()) + .class(icon_container_style()) .width(Length::Shrink) .height(Length::Shrink) .apply(container) - .height(Length::Fill) - .width(Length::Fill) - .center_x() - .center_y() + .center_x(Length::Fill) + .center_y(Length::Fill) .into(), if edges.contains(ResizeEdge::RIGHT) { from_name(if self.direction == ResizeDirection::Outwards { @@ -150,14 +149,13 @@ impl Program for ResizeIndicatorInternal { .prefer_svg(true) .apply(container) .padding(8) - .style(icon_container_style()) + .class(icon_container_style()) .height(Length::Shrink) .apply(container) - .center_y() - .height(Length::Fill) + .center_y(Length::Fill) .into() } else { - horizontal_space(36).into() + horizontal_space().width(36).into() }, ]) .width(Length::Fill) @@ -173,14 +171,13 @@ impl Program for ResizeIndicatorInternal { .prefer_svg(true) .apply(container) .padding(8) - .style(icon_container_style()) + .class(icon_container_style()) .width(Length::Shrink) .apply(container) - .center_x() - .width(Length::Fill) + .center_x(Length::Fill) .into() } else { - vertical_space(36).into() + vertical_space().height(36).into() }, ]) .into() diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index 484caac7..90ae4d22 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -14,9 +14,9 @@ use crate::{ }; use calloop::LoopHandle; use cosmic::{ - iced::{id::Id, widget as iced_widget}, + iced::{id::Id, widget as iced_widget, Alignment}, iced_core::{border::Radius, Background, Border, Color, Length}, - iced_runtime::Command, + iced_runtime::Task, iced_widget::scrollable::AbsoluteOffset, theme, widget as cosmic_widget, Apply, Element as CosmicElement, Theme, }; @@ -712,7 +712,7 @@ impl Program for CosmicStackInternal { &mut self, message: Self::Message, loop_handle: &LoopHandle<'static, crate::state::State>, - ) -> Command { + ) -> Task { match message { Message::DragStart => { if let Some((seat, serial)) = self.last_seat.lock().unwrap().clone() { @@ -874,7 +874,7 @@ impl Program for CosmicStackInternal { } _ => unreachable!(), } - Command::none() + Task::none() } fn view(&self) -> CosmicElement<'_, Self::Message> { @@ -890,8 +890,8 @@ impl Program for CosmicStackInternal { .size(16) .prefer_svg(true) .icon() - .style(if group_focused { - theme::Svg::custom(|theme| iced_widget::svg::Appearance { + .class(if group_focused { + theme::Svg::custom(|theme| iced_widget::svg::Style { color: Some(if theme.cosmic().is_dark { Color::BLACK } else { @@ -903,7 +903,7 @@ impl Program for CosmicStackInternal { }) .apply(iced_widget::container) .padding([4, 24]) - .center_y() + .align_y(Alignment::Center) .apply(iced_widget::mouse_area) .on_press(Message::DragStart) .on_right_press(Message::Menu) @@ -935,7 +935,8 @@ impl Program for CosmicStackInternal { .height(Length::Fill) .width(Length::Fill), ), - iced_widget::horizontal_space(0) + iced_widget::horizontal_space() + .width(Length::Fixed(0.0)) .apply(iced_widget::container) .padding([64, 24]) .apply(iced_widget::mouse_area) @@ -953,10 +954,10 @@ impl Program for CosmicStackInternal { iced_widget::row(elements) .height(TAB_HEIGHT as u16) - .width(Length::Fill) //width as u16) + .width(Length::Fill) .apply(iced_widget::container) - .center_y() - .style(theme::Container::custom(move |theme| { + .align_y(Alignment::Center) + .class(theme::Container::custom(move |theme| { let background = if group_focused { Some(Background::Color(theme.cosmic().accent_color().into())) } else { @@ -965,7 +966,7 @@ impl Program for CosmicStackInternal { ))) }; - iced_widget::container::Appearance { + iced_widget::container::Style { icon_color: Some(Color::from(theme.cosmic().background.on)), text_color: Some(Color::from(theme.cosmic().background.on)), background, diff --git a/src/shell/element/stack/tab.rs b/src/shell/element/stack/tab.rs index 9d6dfd6c..f3532a72 100644 --- a/src/shell/element/stack/tab.rs +++ b/src/shell/element/stack/tab.rs @@ -5,11 +5,7 @@ use cosmic::{ alignment, event, layout::{Layout, Limits, Node}, mouse, overlay, renderer, - widget::{ - operation::{Operation, OperationOutputWrapper}, - tree::Tree, - Id, Widget, - }, + widget::{operation::Operation, tree::Tree, Id, Widget}, Border, Clipboard, Color, Length, Rectangle, Shell, Size, }, iced_widget::scrollable::AbsoluteOffset, @@ -54,19 +50,19 @@ pub(super) enum TabRuleTheme { impl From for theme::Rule { fn from(theme: TabRuleTheme) -> Self { match theme { - TabRuleTheme::ActiveActivated => Self::custom(|theme| widget::rule::Appearance { + TabRuleTheme::ActiveActivated => Self::custom(|theme| widget::rule::Style { color: theme.cosmic().accent_color().into(), width: 4, radius: 0.0.into(), fill_mode: FillMode::Full, }), - TabRuleTheme::ActiveDeactivated => Self::custom(|theme| widget::rule::Appearance { + TabRuleTheme::ActiveDeactivated => Self::custom(|theme| widget::rule::Style { color: theme.cosmic().palette.neutral_5.into(), width: 4, radius: 0.0.into(), fill_mode: FillMode::Full, }), - TabRuleTheme::Default => Self::custom(|theme| widget::rule::Appearance { + TabRuleTheme::Default => Self::custom(|theme| widget::rule::Style { color: theme.cosmic().palette.neutral_5.into(), width: 4, radius: 8.0.into(), @@ -96,11 +92,11 @@ impl TabBackgroundTheme { } } -impl From for theme::Container { +impl From for theme::Container<'_> { fn from(background_theme: TabBackgroundTheme) -> Self { match background_theme { TabBackgroundTheme::ActiveActivated => { - Self::custom(move |theme| widget::container::Appearance { + Self::custom(move |theme| widget::container::Style { icon_color: Some(Color::from(theme.cosmic().accent_text_color())), text_color: Some(Color::from(theme.cosmic().accent_text_color())), background: Some(background_theme.background_color(theme).into()), @@ -113,7 +109,7 @@ impl From for theme::Container { }) } TabBackgroundTheme::ActiveDeactivated => { - Self::custom(move |theme| widget::container::Appearance { + Self::custom(move |theme| widget::container::Style { icon_color: None, text_color: None, background: Some(background_theme.background_color(theme).into()), @@ -215,20 +211,19 @@ impl Tab { .icon() .apply(widget::button) .padding(0) - .style(theme::iced::Button::Text); + .class(theme::iced::Button::Text); if let Some(close_message) = self.close_message { close_button = close_button.on_press(close_message); } let items = vec![ - widget::vertical_rule(4).style(self.rule_theme).into(), + widget::vertical_rule(4).class(self.rule_theme).into(), self.app_icon .clone() .apply(widget::container) - .height(Length::Fill) .width(Length::Shrink) .padding([2, 4]) - .center_y() + .center_y(Length::Fill) .into(), tab_text(self.title, self.active) .font(self.font) @@ -238,10 +233,9 @@ impl Tab { .into(), close_button .apply(widget::container) - .height(Length::Fill) .width(Length::Shrink) .padding([2, 4]) - .center_y() + .center_y(Length::Fill) .align_x(alignment::Horizontal::Right) .into(), ]; @@ -269,7 +263,7 @@ pub(super) struct TabInternal<'a, Message: TabMessage> { id: Id, idx: usize, active: bool, - background: theme::Container, + background: theme::Container<'a>, elements: Vec>, press_message: Option, right_click_message: Option, @@ -347,7 +341,7 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &cosmic::Renderer, - operation: &mut dyn Operation>, + operation: &mut dyn Operation<()>, ) { operation.container(None, layout.bounds(), &mut |operation| { self.elements @@ -454,8 +448,8 @@ where cursor: mouse::Cursor, viewport: &Rectangle, ) { - use cosmic::widget::container::StyleSheet; - let style = theme.appearance(&self.background); + use cosmic::widget::container::Catalog; + let style = theme.style(&self.background); draw_background(renderer, &style, layout.bounds()); @@ -486,7 +480,8 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &cosmic::Renderer, + translation: cosmic::iced::Vector, ) -> Option> { - overlay::from_children(&mut self.elements, tree, layout, renderer) + overlay::from_children(&mut self.elements, tree, layout, renderer, translation) } } diff --git a/src/shell/element/stack/tab_text.rs b/src/shell/element/stack/tab_text.rs index 185edf59..d3a136a5 100644 --- a/src/shell/element/stack/tab_text.rs +++ b/src/shell/element/stack/tab_text.rs @@ -84,7 +84,7 @@ impl TabText { vertical_alignment: alignment::Vertical::Center, shaping: Shaping::Advanced, line_height: LineHeight::default(), - wrap: cosmic::iced::advanced::text::Wrap::None, + wrapping: cosmic::iced::advanced::text::Wrapping::None, }) } } diff --git a/src/shell/element/stack/tabs.rs b/src/shell/element/stack/tabs.rs index 08aa329b..bf4a096d 100644 --- a/src/shell/element/stack/tabs.rs +++ b/src/shell/element/stack/tabs.rs @@ -8,7 +8,7 @@ use cosmic::{ widget::{ operation::{ scrollable::{AbsoluteOffset, RelativeOffset}, - Operation, OperationOutputWrapper, Scrollable, + Operation, Scrollable, }, tree::{self, Tree}, Widget, @@ -16,10 +16,9 @@ use cosmic::{ Background, Border, Clipboard, Color, Length, Point, Rectangle, Renderer, Shell, Size, Vector, }, - iced_style::container::StyleSheet as ContainerStyleSheet, iced_widget::container::draw_background, theme, - widget::icon::from_name, + widget::{container::Catalog, icon::from_name}, Apply, }; use keyframe::{ @@ -45,6 +44,7 @@ struct ScrollAnimationState { start_time: Instant, start: Offset, end: Offset, + extra: Offset, } #[derive(Debug, Clone)] @@ -75,9 +75,24 @@ impl Scrollable for State { start_time: Instant::now(), start: self.offset_x, end: new_offset, + extra: Offset::Absolute(0.), }); self.offset_x = new_offset; } + + fn scroll_by( + &mut self, + offset: AbsoluteOffset, + _bounds: Rectangle, + _content_bounds: Rectangle, + ) { + self.scroll_animation = Some(ScrollAnimationState { + start_time: Instant::now(), + start: self.offset_x, + end: self.offset_x, + extra: Offset::Absolute(offset.x.max(0.0)), + }); + } } impl Default for State { @@ -145,7 +160,7 @@ where Element::new(tab.internal(i)) }); - let tabs_rule = widget::vertical_rule(4).style(if tabs.len() - 1 == active { + let tabs_rule = widget::vertical_rule(4).class(if tabs.len() - 1 == active { if activated { TabRuleTheme::ActiveActivated } else { @@ -166,7 +181,7 @@ where .prefer_svg(true) .icon() .apply(widget::button) - .style(theme::iced::Button::Text) + .class(theme::iced::Button::Text) .on_press(Message::scroll_back()); let next_button = from_name("go-next-symbolic") @@ -174,17 +189,17 @@ where .prefer_svg(true) .icon() .apply(widget::button) - .style(theme::iced::Button::Text) + .class(theme::iced::Button::Text) .on_press(Message::scroll_further()); let mut elements = Vec::with_capacity(tabs.len() + 5); - elements.push(widget::vertical_rule(4).style(rule_style).into()); + elements.push(widget::vertical_rule(4).class(rule_style).into()); elements.push(prev_button.into()); elements.extend(tabs); elements.push(tabs_rule.into()); elements.push(next_button.into()); - elements.push(widget::vertical_rule(4).style(rule_style).into()); + elements.push(widget::vertical_rule(4).class(rule_style).into()); Tabs { elements, @@ -239,6 +254,7 @@ impl State { Vector::new( animation.start.absolute(bounds.width, content_bounds.width) + + animation.extra.absolute(bounds.width, content_bounds.width) * percentage + (animation.end.absolute(bounds.width, content_bounds.width) - animation.start.absolute(bounds.width, content_bounds.width)) * percentage, @@ -490,9 +506,9 @@ where height: b.bounds().height, }); - let background_style = ContainerStyleSheet::appearance( + let background_style = Catalog::style( theme, - &theme::Container::custom(|theme| widget::container::Appearance { + &theme::Container::custom(|theme| widget::container::Style { icon_color: None, text_color: None, background: Some(Background::Color(super::tab::primary_container_color( @@ -666,10 +682,12 @@ where tree: &mut Tree, layout: Layout<'_>, renderer: &cosmic::Renderer, - operation: &mut dyn Operation>, + operation: &mut dyn Operation<()>, ) { let state = tree.state.downcast_mut::(); let bounds = layout.bounds(); + let content_layout = layout.children().next().unwrap(); + let content_bounds = content_layout.bounds(); state.cleanup_old_animations(); @@ -677,6 +695,7 @@ where state, self.id.as_ref(), bounds, + content_bounds, Vector { x: 0.0, y: 0.0 }, /* seemingly unused */ ); @@ -819,6 +838,7 @@ where start_time: Instant::now(), start: Offset::Absolute(offset.x), end: Offset::Absolute(new_offset.x), + extra: Offset::Absolute(0.), }); state.offset_x = Offset::Absolute(new_offset.x); } @@ -993,7 +1013,8 @@ where tree: &'b mut Tree, layout: Layout<'_>, renderer: &cosmic::Renderer, + translation: cosmic::iced::Vector, ) -> Option> { - overlay::from_children(&mut self.elements, tree, layout, renderer) + overlay::from_children(&mut self.elements, tree, layout, renderer, translation) } } diff --git a/src/shell/element/stack_hover.rs b/src/shell/element/stack_hover.rs index 26177bfd..a260f9ea 100644 --- a/src/shell/element/stack_hover.rs +++ b/src/shell/element/stack_hover.rs @@ -38,16 +38,16 @@ impl Program for StackHoverInternal { .prefer_svg(true) .icon() .into(), - horizontal_space(16).into(), + horizontal_space().width(16).into(), text::title3(fl!("stack-windows")).into(), ]) - .align_items(Alignment::Center) + .align_y(Alignment::Center) .apply(container) - .center_x() - .center_y() + .align_x(Alignment::Center) + .align_y(Alignment::Center) .padding(16) .apply(container) - .style(theme::Container::custom(|theme| container::Appearance { + .class(theme::Container::custom(|theme| container::Style { icon_color: Some(Color::from(theme.cosmic().accent.on)), text_color: Some(Color::from(theme.cosmic().accent.on)), background: Some(Background::Color(theme.cosmic().accent_color().into())), @@ -61,10 +61,8 @@ impl Program for StackHoverInternal { .width(Length::Shrink) .height(Length::Shrink) .apply(container) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() + .center_x(Length::Fill) + .center_y(Length::Fill) .into() } } diff --git a/src/shell/element/swap_indicator.rs b/src/shell/element/swap_indicator.rs index 713af6a2..13033b00 100644 --- a/src/shell/element/swap_indicator.rs +++ b/src/shell/element/swap_indicator.rs @@ -34,16 +34,16 @@ impl Program for SwapIndicatorInternal { .prefer_svg(true) .icon() .into(), - horizontal_space(16).into(), + horizontal_space().width(16).into(), text::title3(fl!("swap-windows")).into(), ]) - .align_items(Alignment::Center) + .align_y(Alignment::Center) .apply(container) - .center_x() - .center_y() + .align_x(Alignment::Center) + .align_y(Alignment::Center) .padding(16) .apply(container) - .style(theme::Container::custom(|theme| container::Appearance { + .class(theme::Container::custom(|theme| container::Style { icon_color: Some(Color::from(theme.cosmic().accent.on)), text_color: Some(Color::from(theme.cosmic().accent.on)), background: Some(Background::Color(theme.cosmic().accent_color().into())), @@ -57,10 +57,8 @@ impl Program for SwapIndicatorInternal { .width(Length::Shrink) .height(Length::Shrink) .apply(container) - .height(Length::Fill) - .width(Length::Fill) - .center_x() - .center_y() + .center_x(Length::Fill) + .center_y(Length::Fill) .into() } } diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index dd43457d..c88c6121 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -11,7 +11,7 @@ use crate::{ }, }; use calloop::LoopHandle; -use cosmic::iced::{Color, Command}; +use cosmic::iced::{Color, Task}; use smithay::{ backend::{ input::KeyState, @@ -404,7 +404,7 @@ impl Program for CosmicWindowInternal { &mut self, message: Self::Message, loop_handle: &LoopHandle<'static, crate::state::State>, - ) -> Command { + ) -> Task { match message { Message::DragStart => { if let Some((seat, serial)) = self.last_seat.lock().unwrap().clone() { @@ -504,7 +504,7 @@ impl Program for CosmicWindowInternal { } } } - Command::none() + Task::none() } fn background_color(&self, theme: &cosmic::Theme) -> Color { @@ -517,7 +517,7 @@ impl Program for CosmicWindowInternal { fn view(&self) -> cosmic::Element<'_, Self::Message> { let mut header = cosmic::widget::header_bar() - .start(cosmic::widget::horizontal_space(32)) + .start(cosmic::widget::horizontal_space().width(32)) .title(self.last_title.lock().unwrap().clone()) .on_drag(Message::DragStart) .on_close(Message::Close) @@ -528,12 +528,12 @@ impl Program for CosmicWindowInternal { if cosmic::config::show_minimize() { header = header .on_minimize(Message::Minimize) - .start(cosmic::widget::horizontal_space(40)); // 32 + 8 spacing + .start(cosmic::widget::horizontal_space().width(40)); // 32 + 8 spacing } if cosmic::config::show_maximize() { header = header .on_maximize(Message::Maximize) - .start(cosmic::widget::horizontal_space(40)); // 32 + 8 spacing + .start(cosmic::widget::horizontal_space().width(40)); // 32 + 8 spacing } header.into() diff --git a/src/shell/grabs/menu/item.rs b/src/shell/grabs/menu/item.rs index b6aa0227..bf69e3ab 100644 --- a/src/shell/grabs/menu/item.rs +++ b/src/shell/grabs/menu/item.rs @@ -3,17 +3,17 @@ use cosmic::{ iced_core::{ event, layout, mouse, overlay, renderer::{Quad, Style}, - widget::{tree, Id, OperationOutputWrapper, Tree, Widget}, + widget::{tree, Id, Tree, Widget}, Background, Border, Clipboard, Color, Event, Layout, Length, Rectangle, Renderer as IcedRenderer, Shell, Size, }, - widget::button::StyleSheet, + widget::button::Catalog, }; pub struct SubmenuItem<'a, Message> { elem: cosmic::Element<'a, Message>, idx: usize, - styling: ::Style, + styling: ::Class, } impl<'a, Message> SubmenuItem<'a, Message> { @@ -25,7 +25,7 @@ impl<'a, Message> SubmenuItem<'a, Message> { } } - pub fn style(mut self, style: ::Style) -> Self { + pub fn style(mut self, style: ::Class) -> Self { self.styling = style; self } @@ -133,7 +133,7 @@ where state: &mut Tree, layout: Layout<'_>, renderer: &cosmic::Renderer, - operation: &mut dyn cosmic::widget::Operation>, + operation: &mut dyn cosmic::widget::Operation<()>, ) { let state = &mut state.children[0]; let layout = layout.children().next().unwrap(); @@ -206,10 +206,13 @@ where state: &'b mut Tree, layout: Layout<'_>, renderer: &cosmic::Renderer, + translation: cosmic::iced::Vector, ) -> Option> { let state = &mut state.children[0]; let layout = layout.children().next().unwrap(); - self.elem.as_widget_mut().overlay(state, layout, renderer) + self.elem + .as_widget_mut() + .overlay(state, layout, renderer, translation) } } diff --git a/src/shell/grabs/menu/mod.rs b/src/shell/grabs/menu/mod.rs index 83cc70f1..62fcaa7e 100644 --- a/src/shell/grabs/menu/mod.rs +++ b/src/shell/grabs/menu/mod.rs @@ -7,10 +7,10 @@ use calloop::LoopHandle; use cosmic::{ iced::{Alignment, Background}, iced_core::{alignment::Horizontal, Border, Length, Rectangle as IcedRectangle}, - iced_widget::{self, text::Appearance as TextAppearance, Column, Row}, + iced_widget::{self, text::Style as TextStyle, Column, Row}, theme, widget::{button, divider, horizontal_space, icon::from_name, text}, - Apply as _, Command, + Apply as _, Task, }; use smithay::{ backend::{ @@ -202,7 +202,7 @@ impl Program for ContextMenu { &mut self, message: Self::Message, loop_handle: &LoopHandle<'static, crate::state::State>, - ) -> Command { + ) -> Task { match message { Message::ItemPressed(idx) => { if let Some(Item::Entry { on_press, .. }) = self.items.get_mut(idx) { @@ -331,7 +331,7 @@ impl Program for ContextMenu { } }; - Command::none() + Task::none() } fn view(&self) -> cosmic::Element<'_, Self::Message> { @@ -350,7 +350,7 @@ impl Program for ContextMenu { match item { Item::Separator => divider::horizontal::light().into(), Item::Submenu { title, .. } => Row::with_children(vec![ - horizontal_space(16).into(), + horizontal_space().width(16).into(), text::body(title).width(mode).into(), from_name("go-next-symbolic") .size(16) @@ -361,7 +361,7 @@ impl Program for ContextMenu { .spacing(8) .width(width) .padding([8, 16]) - .align_items(Alignment::Center) + .align_y(Alignment::Center) .apply(|row| item::SubmenuItem::new(row, idx)) .style(theme::Button::MenuItem) .into(), @@ -378,20 +378,20 @@ impl Program for ContextMenu { .size(16) .prefer_svg(true) .icon() - .style(theme::Svg::custom(|theme| iced_widget::svg::Appearance { + .class(theme::Svg::custom(|theme| iced_widget::svg::Style { color: Some(theme.cosmic().accent.base.into()), })) .into() } else { - horizontal_space(16).into() + horizontal_space().width(16).into() }, text::body(title) .width(mode) - .style(if *disabled { + .class(if *disabled { theme::Text::Custom(|theme| { let mut color = theme.cosmic().background.component.on; color.alpha *= 0.5; - TextAppearance { + TextStyle { color: Some(color.into()), } }) @@ -399,17 +399,17 @@ impl Program for ContextMenu { theme::Text::Default }) .into(), - horizontal_space(16).into(), + horizontal_space().width(16).into(), ]; if let Some(shortcut) = shortcut.as_ref() { components.push( text::body(shortcut) - .horizontal_alignment(Horizontal::Right) + .align_x(Horizontal::Right) .width(Length::Shrink) - .style(theme::Text::Custom(|theme| { + .class(theme::Text::Custom(|theme| { let mut color = theme.cosmic().background.component.on; color.alpha *= 0.75; - TextAppearance { + TextStyle { color: Some(color.into()), } })) @@ -420,12 +420,12 @@ impl Program for ContextMenu { Row::with_children(components) .spacing(8) .width(mode) - .align_items(Alignment::Center) + .align_y(Alignment::Center) .apply(button::custom) .width(width) .padding([8, 16]) .on_press_maybe((!disabled).then_some(Message::ItemPressed(idx))) - .style(theme::Button::MenuItem) + .class(theme::Button::MenuItem) .into() } } @@ -433,10 +433,10 @@ impl Program for ContextMenu { .width(Length::Shrink) .apply(iced_widget::container) .padding(1) - .style(theme::Container::custom(|theme| { + .class(theme::Container::custom(|theme| { let cosmic = theme.cosmic(); let component = &cosmic.background.component; - iced_widget::container::Appearance { + iced_widget::container::Style { icon_color: Some(cosmic.accent.base.into()), text_color: Some(component.on.into()), background: Some(Background::Color(component.base.into())), diff --git a/src/utils/iced.rs b/src/utils/iced.rs index 7ab7a14f..18513644 100644 --- a/src/utils/iced.rs +++ b/src/utils/iced.rs @@ -10,26 +10,24 @@ use cosmic::{ iced::{ advanced::widget::Tree, event::Event, + futures::{FutureExt, StreamExt}, keyboard::{Event as KeyboardEvent, Modifiers as IcedModifiers}, mouse::{Button as MouseButton, Cursor, Event as MouseEvent, ScrollDelta}, touch::{Event as TouchEvent, Finger}, - window::{Event as WindowEvent, Id}, - Command, Limits, Point as IcedPoint, Rectangle as IcedRectangle, Size as IcedSize, + window::Event as WindowEvent, + Limits, Point as IcedPoint, Size as IcedSize, Task, }, - iced_core::{clipboard::Null as NullClipboard, renderer::Style, Color, Length, Pixels}, - iced_renderer::graphics::Renderer as IcedGraphicsRenderer, + iced_core::{clipboard::Null as NullClipboard, id::Id, renderer::Style, Color, Length, Pixels}, iced_runtime::{ - command::Action, program::{Program as IcedProgram, State}, - Debug, + task::into_stream, + Action, Debug, }, Theme, }; -use iced_tiny_skia::{ - graphics::{damage, Viewport}, - Backend, Primitive, -}; +use iced_tiny_skia::{graphics::Viewport, Primitive}; +use once_cell::sync::Lazy; use ordered_float::OrderedFloat; use smithay::{ backend::{ @@ -67,6 +65,8 @@ use smithay::{ }, }; +static ID: Lazy = Lazy::new(|| Id::new("Program")); + pub struct IcedElement(pub(crate) Arc>>); impl fmt::Debug for IcedElement

{ @@ -104,9 +104,9 @@ pub trait Program { &mut self, message: Self::Message, loop_handle: &LoopHandle<'static, crate::state::State>, - ) -> Command { + ) -> Task { let _ = (message, loop_handle); - Command::none() + Task::none() } fn view(&self) -> cosmic::Element<'_, Self::Message>; @@ -131,7 +131,7 @@ impl IcedProgram for ProgramWrapper

{ type Renderer = cosmic::Renderer; type Theme = cosmic::Theme; - fn update(&mut self, message: Self::Message) -> Command { + fn update(&mut self, message: Self::Message) -> Task { self.0.update(message, &self.1) } @@ -159,9 +159,9 @@ pub(crate) struct IcedElementInternal { // futures handle: LoopHandle<'static, crate::state::State>, - scheduler: Scheduler<

::Message>, + scheduler: Scheduler::Message>>, executor_token: Option, - rx: Receiver<

::Message>, + rx: Receiver::Message>>, } impl Clone for IcedElementInternal

{ @@ -178,14 +178,10 @@ impl Clone for IcedElementInternal

{ if !self.state.is_queue_empty() { tracing::warn!("Missing force_update call"); } - let mut renderer = cosmic::Renderer::TinySkia(IcedGraphicsRenderer::new( - Backend::new(), - cosmic::font::default(), - Pixels(16.0), - )); + let mut renderer = cosmic::Renderer::new(cosmic::font::default(), Pixels(16.0)); let mut debug = Debug::new(); let state = State::new( - Id::MAIN, + ID.clone(), ProgramWrapper(self.state.program().0.clone(), handle.clone()), IcedSize::new(self.size.w as f32, self.size.h as f32), &mut renderer, @@ -244,15 +240,11 @@ impl IcedElement

{ theme: cosmic::Theme, ) -> IcedElement

{ let size = size.into(); - let mut renderer = cosmic::Renderer::TinySkia(IcedGraphicsRenderer::new( - Backend::new(), - cosmic::font::default(), - Pixels(16.0), - )); + let mut renderer = cosmic::Renderer::new(cosmic::font::default(), Pixels(16.0)); let mut debug = Debug::new(); let state = State::new( - Id::MAIN, + ID.clone(), ProgramWrapper(program, handle.clone()), IcedSize::new(size.w as f32, size.h as f32), &mut renderer, @@ -368,8 +360,8 @@ impl IcedElement

{ impl IcedElementInternal

{ #[profiling::function] - fn update(&mut self, mut force: bool) -> Vec::Message>> { - while let Ok(message) = self.rx.try_recv() { + fn update(&mut self, mut force: bool) -> Vec::Message>> { + while let Ok(Some(message)) = self.rx.try_recv() { self.state.queue_message(message); force = true; } @@ -383,17 +375,16 @@ impl IcedElementInternal

{ .map(|p| IcedPoint::new(p.x as f32, p.y as f32)) .map(Cursor::Available) .unwrap_or(Cursor::Unavailable); - let actions = self .state .update( - Id::MAIN, + ID.clone(), IcedSize::new(self.size.w as f32, self.size.h as f32), cursor, &mut self.renderer, &self.theme, &Style { - scale_factor: 1.0, //TODO: why is this + scale_factor: 1.0, // TODO: why is this icon_color: self.theme.cosmic().on_bg_color().into(), text_color: self.theme.cosmic().on_bg_color().into(), }, @@ -402,17 +393,15 @@ impl IcedElementInternal

{ ) .1; - actions - .into_iter() - .filter_map(|action| { - if let Action::Future(future) = action { - let _ = self.scheduler.schedule(future); - None - } else { - Some(action) - } - }) - .collect::>() + if let Some(action) = actions { + if let Some(t) = into_stream(action) { + let _ = self.scheduler.schedule(t.into_future().map(|f| match f.0 { + Some(Action::Output(msg)) => Some(msg), + _ => None, + })); + } + } + Vec::new() } } @@ -751,14 +740,11 @@ impl SpaceElement for IcedElement

{ fn set_activate(&self, activated: bool) { let mut internal = self.0.lock().unwrap(); - internal.state.queue_event(Event::Window( - Id::MAIN, - if activated { - WindowEvent::Focused - } else { - WindowEvent::Unfocused - }, - )); + internal.state.queue_event(Event::Window(if activated { + WindowEvent::Focused + } else { + WindowEvent::Unfocused + })); let _ = internal.update(true); // TODO } @@ -860,83 +846,62 @@ where } else { false }; + if force { + internal_ref.pending_update = None; + } let _ = internal_ref.update(force); - - if let Some((buffer, ref mut old_primitives)) = - internal_ref.buffers.get_mut(&OrderedFloat(scale.x)) - { + if let Some((buffer, _)) = internal_ref.buffers.get_mut(&OrderedFloat(scale.x)) { let size: Size = internal_ref .size .to_f64() .to_buffer(scale.x, Transform::Normal) .to_i32_round(); - if size.w > 0 && size.h > 0 { - let cosmic::Renderer::TinySkia(renderer) = &mut internal_ref.renderer; let state_ref = &internal_ref.state; let mut clip_mask = tiny_skia::Mask::new(size.w as u32, size.h as u32).unwrap(); let overlay = internal_ref.debug.overlay(); let theme = &internal_ref.theme; - buffer - .render() - .draw(move |buf| { - let mut pixels = - tiny_skia::PixmapMut::from_bytes(buf, size.w as u32, size.h as u32) - .expect("Failed to create pixel map"); - - renderer.with_primitives(|backend, primitives| { - let background_color = state_ref.program().0.background_color(theme); - let bounds = IcedSize::new(size.w as u32, size.h as u32); - let viewport = Viewport::with_physical_size(bounds, scale.x); - - let mut damage = old_primitives - .as_ref() - .and_then(|(last_primitives, last_color)| { - (last_color == &background_color) - .then(|| damage::list(last_primitives, primitives)) - }) - .unwrap_or_else(|| { - vec![IcedRectangle::with_size(viewport.logical_size())] - }); - damage = damage::group(damage, scale.x as f32, bounds); - - if !damage.is_empty() { - backend.draw( - &mut pixels, - &mut clip_mask, - primitives, - &viewport, - &damage, - background_color, - &overlay, - ); - - *old_primitives = Some((primitives.to_vec(), background_color)); - } - - let damage = damage - .into_iter() - .map(|x| x.snap()) - .map(|damage_rect| { - Rectangle::from_loc_and_size( - (damage_rect.x as i32, damage_rect.y as i32), - (damage_rect.width as i32, damage_rect.height as i32), - ) - }) - .collect::>(); - - state_ref.program().0.foreground( - &mut pixels, - &damage, - scale.x as f32, - theme, - ); - - Result::<_, ()>::Ok(damage) + _ = buffer.render().draw(|buf| { + let mut pixels = + tiny_skia::PixmapMut::from_bytes(buf, size.w as u32, size.h as u32) + .expect("Failed to create pixel map"); + + let background_color = state_ref.program().0.background_color(theme); + let bounds = IcedSize::new(size.w as u32, size.h as u32); + let viewport = Viewport::with_physical_size(bounds, scale.x); + + let damage = vec![cosmic::iced::Rectangle::new( + cosmic::iced::Point::default(), + viewport.logical_size(), + )]; + + internal_ref.renderer.draw( + &mut pixels, + &mut clip_mask, + &viewport, + &damage, + background_color, + &overlay, + ); + + let damage = damage + .into_iter() + .filter_map(|x| x.snap()) + .map(|damage_rect| { + Rectangle::from_loc_and_size( + (damage_rect.x as i32, damage_rect.y as i32), + (damage_rect.width as i32, damage_rect.height as i32), + ) }) - }) - .unwrap(); + .collect::>(); + state_ref + .program() + .0 + .foreground(&mut pixels, &damage, scale.x as f32, theme); + + Result::<_, ()>::Ok(damage) + }); } if let Ok(buffer) = MemoryRenderBufferRenderElement::from_buffer( @@ -946,9 +911,11 @@ where Some(alpha), Some(Rectangle::from_loc_and_size( (0., 0.), - size.to_f64().to_logical(1.0, Transform::Normal), + size.to_f64() + .to_logical(1., Transform::Normal) + .to_i32_round(), )), - Some(internal_ref.size), + Some(size.to_logical(1, Transform::Normal)), Kind::Unspecified, ) { return vec![C::from(buffer)]; From 5f650e7d088bc5f56584c3a78612a88b5a4dfc49 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 6 Dec 2024 12:32:24 -0500 Subject: [PATCH 50/60] feat: alt+shift+tab --- data/keybindings.ron | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/keybindings.ron b/data/keybindings.ron index 986e827c..127f5887 100644 --- a/data/keybindings.ron +++ b/data/keybindings.ron @@ -78,7 +78,9 @@ (modifiers: [Super], key: "slash"): System(Launcher), (modifiers: [Super]): System(Launcher), (modifiers: [Alt], key: "Tab"): System(WindowSwitcher), + (modifiers: [Alt, Shift], key: "Tab"): System(WindowSwitcherPrevious), (modifiers: [Super], key: "Tab"): System(WindowSwitcher), + (modifiers: [Super, Shift], key: "Tab"): System(WindowSwitcherPrevious), (modifiers: [], key: "Print"): System(Screenshot), (modifiers: [], key: "XF86AudioRaiseVolume"): System(VolumeRaise), From 6c8325856fe340e6313cad5f4485de78f9d9d833 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 11 Dec 2024 12:07:46 -0500 Subject: [PATCH 51/60] chore: update deps --- Cargo.lock | 108 ++++++++++++++++++++++++++--------------------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c3446a5..7fcf9c27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -293,7 +293,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -328,7 +328,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -482,7 +482,7 @@ checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -862,7 +862,7 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" +source = "git+https://github.com/pop-os/libcosmic/#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "atomicwrites", "calloop 0.14.1", @@ -881,7 +881,7 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" +source = "git+https://github.com/pop-os/libcosmic/#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "quote", "syn 1.0.109", @@ -903,7 +903,7 @@ dependencies = [ [[package]] name = "cosmic-settings-config" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-settings-daemon#1ed68808e85ce681da882446ec572d44c68a6866" +source = "git+https://github.com/pop-os/cosmic-settings-daemon#747e482ca197497ee3bc5f6e9dcd23c73e592e47" dependencies = [ "cosmic-config", "serde", @@ -1081,7 +1081,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -1092,7 +1092,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -1133,7 +1133,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -1182,7 +1182,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -1445,7 +1445,7 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -1466,7 +1466,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -1756,7 +1756,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -1866,7 +1866,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -2253,7 +2253,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.10.0", - "syn 2.0.72", + "syn 2.0.90", "unic-langid", ] @@ -2267,7 +2267,7 @@ dependencies = [ "i18n-config", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -2312,7 +2312,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" +source = "git+https://github.com/pop-os/libcosmic/#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "bitflags 2.6.0", "bytes", @@ -2335,7 +2335,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic/#a6db807c1bbffc90b68513171348cad0b4469eac" +source = "git+https://github.com/pop-os/libcosmic/#5422ab3130a0f943c71fda558d61c815086e6f40" dependencies = [ "futures", "iced_core", @@ -3347,7 +3347,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -3672,7 +3672,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -3712,7 +3712,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -3811,7 +3811,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -3846,7 +3846,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -3980,9 +3980,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -3995,7 +3995,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", "version_check", "yansi", ] @@ -4017,7 +4017,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" dependencies = [ "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -4312,7 +4312,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.72", + "syn 2.0.90", "walkdir", ] @@ -4468,7 +4468,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -4492,7 +4492,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -4531,7 +4531,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -4886,9 +4886,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.72" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -4938,22 +4938,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -5138,9 +5138,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -5150,20 +5150,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -5542,7 +5542,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", "wasm-bindgen-shared", ] @@ -5576,7 +5576,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5984,7 +5984,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -5995,7 +5995,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -6470,7 +6470,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", "zvariant_utils", ] @@ -6518,7 +6518,7 @@ checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -6529,7 +6529,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] [[package]] @@ -6563,7 +6563,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", "zvariant_utils", ] @@ -6575,5 +6575,5 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.90", ] From 892c05f4fca31af5cbe6960ffbd20051d9032096 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 11 Dec 2024 21:36:50 -0500 Subject: [PATCH 52/60] fix(iced): use internal_ref size --- src/utils/iced.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/iced.rs b/src/utils/iced.rs index 18513644..55552034 100644 --- a/src/utils/iced.rs +++ b/src/utils/iced.rs @@ -915,7 +915,7 @@ where .to_logical(1., Transform::Normal) .to_i32_round(), )), - Some(size.to_logical(1, Transform::Normal)), + Some(internal_ref.size), Kind::Unspecified, ) { return vec![C::from(buffer)]; From 3132767a6014d11da4c4bc0a408bedf01d3f77a2 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 12 Dec 2024 01:31:33 -0500 Subject: [PATCH 53/60] fix: improve damage tracking --- src/utils/iced.rs | 67 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/src/utils/iced.rs b/src/utils/iced.rs index 55552034..b5d930a4 100644 --- a/src/utils/iced.rs +++ b/src/utils/iced.rs @@ -25,7 +25,10 @@ use cosmic::{ }, Theme, }; -use iced_tiny_skia::{graphics::Viewport, Primitive}; +use iced_tiny_skia::{ + graphics::{damage, Viewport}, + Layer, +}; use once_cell::sync::Lazy; use ordered_float::OrderedFloat; @@ -143,7 +146,7 @@ impl IcedProgram for ProgramWrapper

{ pub(crate) struct IcedElementInternal { // draw buffer outputs: HashSet, - buffers: HashMap, (MemoryRenderBuffer, Option<(Vec, Color)>)>, + buffers: HashMap, (MemoryRenderBuffer, Option<(Vec, Color)>)>, pending_update: Option, // state @@ -850,7 +853,9 @@ where internal_ref.pending_update = None; } let _ = internal_ref.update(force); - if let Some((buffer, _)) = internal_ref.buffers.get_mut(&OrderedFloat(scale.x)) { + if let Some((buffer, ref mut old_layers)) = + internal_ref.buffers.get_mut(&OrderedFloat(scale.x)) + { let size: Size = internal_ref .size .to_f64() @@ -871,20 +876,52 @@ where let bounds = IcedSize::new(size.w as u32, size.h as u32); let viewport = Viewport::with_physical_size(bounds, scale.x); - let damage = vec![cosmic::iced::Rectangle::new( - cosmic::iced::Point::default(), - viewport.logical_size(), - )]; - - internal_ref.renderer.draw( - &mut pixels, - &mut clip_mask, - &viewport, - &damage, - background_color, - &overlay, + let current_layers = internal_ref.renderer.layers(); + let mut damage: Vec<_> = old_layers + .as_ref() + .and_then(|(last_primitives, last_color)| { + (last_color == &background_color).then(|| { + damage::diff( + &last_primitives, + current_layers, + |_| { + vec![cosmic::iced::Rectangle::new( + cosmic::iced::Point::default(), + viewport.logical_size(), + )] + }, + Layer::damage, + ) + .into_iter() + .filter(|d| { + let width = d.width as u32; + let height = d.height as u32; + + width > 1 && height > 1 + }) + .collect() + }) + }) + .unwrap_or_else(|| { + vec![cosmic::iced::Rectangle::with_size(viewport.logical_size())] + }); + damage = damage::group( + damage, + cosmic::iced::Rectangle::with_size(viewport.logical_size()), ); + if !damage.is_empty() { + *old_layers = Some((current_layers.to_vec(), background_color)); + + internal_ref.renderer.draw( + &mut pixels, + &mut clip_mask, + &viewport, + &damage, + background_color, + &overlay, + ); + } let damage = damage .into_iter() .filter_map(|x| x.snap()) From 5b89ad27fb56dcbd926ef53bd08313255ba8e449 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 12 Dec 2024 01:48:38 -0500 Subject: [PATCH 54/60] fix: manually scale the damage --- src/utils/iced.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/iced.rs b/src/utils/iced.rs index b5d930a4..90f5bab9 100644 --- a/src/utils/iced.rs +++ b/src/utils/iced.rs @@ -875,7 +875,7 @@ where let background_color = state_ref.program().0.background_color(theme); let bounds = IcedSize::new(size.w as u32, size.h as u32); let viewport = Viewport::with_physical_size(bounds, scale.x); - + let scale_x = scale.x as f32; let current_layers = internal_ref.renderer.layers(); let mut damage: Vec<_> = old_layers .as_ref() @@ -922,8 +922,10 @@ where &overlay, ); } + let damage = damage .into_iter() + .map(|d| d * scale_x) .filter_map(|x| x.snap()) .map(|damage_rect| { Rectangle::from_loc_and_size( From 9b4127555e32060083745c381393df742c69ca60 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 12 Dec 2024 14:13:42 +0100 Subject: [PATCH 55/60] kms/surface: Fix racy output disable --- src/backend/kms/surface/mod.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/backend/kms/surface/mod.rs b/src/backend/kms/surface/mod.rs index e7893dee..2fe8ef05 100644 --- a/src/backend/kms/surface/mod.rs +++ b/src/backend/kms/surface/mod.rs @@ -222,7 +222,7 @@ impl Default for QueueState { #[derive(Debug)] pub enum ThreadCommand { - Suspend, + Suspend(SyncSender<()>), Resume { surface: DrmSurface, gbm: GbmDevice, @@ -433,7 +433,9 @@ impl Surface { } pub fn suspend(&mut self) { - let _ = self.thread_command.send(ThreadCommand::Suspend); + let (tx, rx) = std::sync::mpsc::sync_channel(1); + let _ = self.thread_command.send(ThreadCommand::Suspend(tx)); + let _ = rx.recv(); } pub fn resume( @@ -553,7 +555,7 @@ fn surface_thread( event_loop .handle() .insert_source(thread_receiver, move |command, _, state| match command { - Event::Msg(ThreadCommand::Suspend) => state.suspend(), + Event::Msg(ThreadCommand::Suspend(tx)) => state.suspend(tx), Event::Msg(ThreadCommand::Resume { surface, gbm, @@ -642,7 +644,7 @@ fn surface_thread( } impl SurfaceThreadState { - fn suspend(&mut self) { + fn suspend(&mut self, tx: SyncSender<()>) { self.active.store(false, Ordering::SeqCst); let _ = self.compositor.take(); @@ -660,6 +662,8 @@ impl SurfaceThreadState { self.loop_handle.remove(queued_render); } }; + + let _ = tx.send(()); } fn resume( From 2e1f6a474603081c8c859bd50e8dc911ddc94fea Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 13 Dec 2024 16:23:09 +0100 Subject: [PATCH 56/60] shell: Unset focused outputs on `remove_output` --- src/shell/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 8e52c73c..28b87e61 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -722,6 +722,9 @@ impl Workspaces { if &seat.active_output() == output { seat.set_active_output(&new_output); } + if seat.focused_output().as_ref() == Some(output) { + seat.set_focused_output(None); + } } let new_set = self.sets.get_mut(&new_output).unwrap(); From 6707c9252214a10e48a10091efacc301160f6d17 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 13 Dec 2024 17:50:03 -0800 Subject: [PATCH 57/60] toplevel-info: Fix behavior with multiple instances of global Instead of looking for toplevel handles for the client, look for toplevel handles for the specific global instance. --- src/wayland/protocols/toplevel_info.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/wayland/protocols/toplevel_info.rs b/src/wayland/protocols/toplevel_info.rs index 3d4d2538..44db2031 100644 --- a/src/wayland/protocols/toplevel_info.rs +++ b/src/wayland/protocols/toplevel_info.rs @@ -62,7 +62,7 @@ pub struct ToplevelInfoGlobalData { #[derive(Default)] pub(super) struct ToplevelStateInner { foreign_handle: Option, - instances: Vec, + instances: Vec<(Weak, ZcosmicToplevelHandleV1)>, outputs: Vec, workspaces: Vec, pub(super) rectangles: Vec<(Weak, Rectangle)>, @@ -202,7 +202,7 @@ where .lock() .unwrap() .instances - .push(instance); + .push((obj.downgrade(), instance)); } else { let _ = data_init.init(cosmic_toplevel, ToplevelHandleStateInner::empty()); error!(?foreign_toplevel, "Toplevel for foreign-toplevel-list not registered for cosmic-toplevel-info."); @@ -259,7 +259,11 @@ where ) { for toplevel in &state.toplevel_info_state_mut().toplevels { if let Some(state) = toplevel.user_data().get::() { - state.lock().unwrap().instances.retain(|i| i != resource); + state + .lock() + .unwrap() + .instances + .retain(|(_, i)| i != resource); } } } @@ -340,7 +344,7 @@ where pub fn remove_toplevel(&mut self, toplevel: &W) { if let Some(state) = toplevel.user_data().get::() { let mut state_inner = state.lock().unwrap(); - for handle in &state_inner.instances { + for (_info, handle) in &state_inner.instances { // don't send events to stopped instances if handle.version() < zcosmic_toplevel_info_v1::REQ_GET_COSMIC_TOPLEVEL_SINCE && self @@ -386,7 +390,7 @@ where } true } else { - for handle in &state.instances { + for (_info, handle) in &state.instances { // don't send events to stopped instances if handle.version() < zcosmic_toplevel_info_v1::REQ_GET_COSMIC_TOPLEVEL_SINCE && self @@ -442,11 +446,7 @@ where .unwrap() .lock() .unwrap(); - let instance = match state - .instances - .iter() - .find(|i| i.id().same_client_as(&info.id())) - { + let (_info, instance) = match state.instances.iter().find(|(i, _)| i == info) { Some(i) => i, None => { if info.version() < zcosmic_toplevel_info_v1::REQ_GET_COSMIC_TOPLEVEL_SINCE { @@ -459,7 +459,7 @@ where ) { info.toplevel(&toplevel_handle); - state.instances.push(toplevel_handle); + state.instances.push((info.downgrade(), toplevel_handle)); state.instances.last().unwrap() } else { return false; From f2e53f09c97b83f923470ea2cdc818b216881bb5 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 13 Dec 2024 17:48:42 +0100 Subject: [PATCH 58/60] shell: Fix stacking maximized windows --- src/shell/mod.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 28b87e61..e9022c67 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -3501,7 +3501,12 @@ impl Shell { let workspace = &mut set.workspaces[set.active]; let maybe_window = workspace.focus_stack.get(seat).iter().next().cloned(); if let Some(window) = maybe_window { - if set.sticky_layer.mapped().any(|m| m == &window) { + let was_maximized = window.is_maximized(false); + if was_maximized { + workspace.unmaximize_request(&window); + } + + let res = if set.sticky_layer.mapped().any(|m| m == &window) { set.sticky_layer .toggle_stacking_focused(seat, workspace.focus_stack.get_mut(seat)) } else if workspace.tiling_layer.mapped().any(|(m, _)| m == &window) { @@ -3514,7 +3519,15 @@ impl Shell { .toggle_stacking_focused(seat, workspace.focus_stack.get_mut(seat)) } else { None + }; + + if was_maximized { + if let Some(KeyboardFocusTarget::Element(mapped)) = res.as_ref() { + self.maximize_request(mapped, seat); + } } + + res } else { None } From 7ac204ee799224de389ed988ee96a79411adc695 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 16 Dec 2024 20:14:37 +0100 Subject: [PATCH 59/60] focus: Fix active/focused output on `refresh_focus` Previously removing the last output could have left seats with an invalid active output. We already have logic to check this in `refresh_focus` but failed to apply it before `update_pointer_focus`. Let's fix that. --- src/shell/focus/mod.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/shell/focus/mod.rs b/src/shell/focus/mod.rs index 638ac4ec..f4a6ca17 100644 --- a/src/shell/focus/mod.rs +++ b/src/shell/focus/mod.rs @@ -344,19 +344,27 @@ impl Common { .cloned() .collect::>(); for seat in &seats { - update_pointer_focus(state, &seat); - - let mut shell = state.common.shell.write().unwrap(); - let output = seat.focused_or_active_output(); + { + let shell = state.common.shell.read().unwrap(); + let focused_output = seat.focused_output(); + let active_output = seat.active_output(); - // If the focused or active output is not in the list of outputs, switch to the first output - if !shell.outputs().any(|o| o == &output) { - if let Some(other) = shell.outputs().next() { - seat.set_active_output(other); + // If the focused or active output is not in the list of outputs, switch to the first output + if focused_output.is_some_and(|f| !shell.outputs().any(|o| &f == o)) { + seat.set_focused_output(None); + } + if !shell.outputs().any(|o| o == &active_output) { + if let Some(other) = shell.outputs().next() { + seat.set_active_output(other); + } + continue; } - continue; } + update_pointer_focus(state, &seat); + + let output = seat.focused_or_active_output(); + let mut shell = state.common.shell.write().unwrap(); let last_known_focus = ActiveFocus::get(&seat); if let Some(target) = last_known_focus { From 1118aa287712b0540df25aade9d3ad95408d3da3 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 18 Dec 2024 13:40:31 -0800 Subject: [PATCH 60/60] When removing output global, use `disable_global`, remove with timer This should fix an issue where output hotplug can sometimes cause clients (including XWayland) to crash with a protocol error trying to bind the output. Using a timer doesn't seem ideal, but seems to be the correct way to do this at present. Wlroots `wlr_global_destroy_safe` is basically the same as this. Adding a `LoopHandle` argument to `OutputConfigurationState::new` seems awkward, but maybe better than a handler method for removing globals. (`IdleNotifierState::new` also takes a `LoopHandle`). Perhaps Smithay could provide some kind of helper for this. --- src/state.rs | 3 +- .../protocols/output_configuration/mod.rs | 42 +++++++++++++++++-- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/state.rs b/src/state.rs index fc2ba06e..ce3b456f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -499,7 +499,8 @@ impl State { let fractional_scale_state = FractionalScaleManagerState::new::(dh); let keyboard_shortcuts_inhibit_state = KeyboardShortcutsInhibitState::new::(dh); let output_state = OutputManagerState::new_with_xdg_output::(dh); - let output_configuration_state = OutputConfigurationState::new(dh, client_is_privileged); + let output_configuration_state = + OutputConfigurationState::new(dh, handle.clone(), client_is_privileged); let output_power_state = OutputPowerState::new::(dh, client_is_privileged); let overlap_notify_state = OverlapNotifyState::new::(dh, client_has_no_security_context); diff --git a/src/wayland/protocols/output_configuration/mod.rs b/src/wayland/protocols/output_configuration/mod.rs index a75c1d16..9cffb102 100644 --- a/src/wayland/protocols/output_configuration/mod.rs +++ b/src/wayland/protocols/output_configuration/mod.rs @@ -1,5 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only +use calloop::{ + timer::{TimeoutAction, Timer}, + LoopHandle, +}; use cosmic_protocols::output_management::v1::server::{ zcosmic_output_configuration_head_v1::ZcosmicOutputConfigurationHeadV1, zcosmic_output_configuration_v1::ZcosmicOutputConfigurationV1, @@ -25,7 +29,7 @@ use smithay::{ utils::{Logical, Physical, Point, Size, Transform}, wayland::output::WlOutputData, }; -use std::{convert::TryFrom, sync::Mutex}; +use std::{convert::TryFrom, sync::Mutex, time::Duration}; mod handlers; @@ -45,6 +49,7 @@ pub struct OutputConfigurationState { global: GlobalId, extension_global: GlobalId, dh: DisplayHandle, + event_loop_handle: LoopHandle<'static, D>, _dispatch: std::marker::PhantomData, } @@ -168,7 +173,11 @@ where + OutputConfigurationHandler + 'static, { - pub fn new(dh: &DisplayHandle, client_filter: F) -> OutputConfigurationState + pub fn new( + dh: &DisplayHandle, + event_loop_handle: LoopHandle<'static, D>, + client_filter: F, + ) -> OutputConfigurationState where F: for<'a> Fn(&'a Client) -> bool + Clone + Send + Sync + 'static, { @@ -194,6 +203,7 @@ where global, extension_global, dh: dh.clone(), + event_loop_handle: event_loop_handle.clone(), _dispatch: std::marker::PhantomData, } } @@ -240,7 +250,7 @@ where let mut inner = inner.lock().unwrap(); inner.enabled = false; if let Some(global) = inner.global.take() { - self.dh.remove_global::(global); + remove_global_with_timer(&self.dh, &self.event_loop_handle, global); } } } @@ -292,7 +302,11 @@ where inner.global = Some(output.create_global::(&self.dh)); } if !inner.enabled && inner.global.is_some() { - self.dh.remove_global::(inner.global.take().unwrap()); + remove_global_with_timer( + &self.dh, + &self.event_loop_handle, + inner.global.take().unwrap(), + ); } } for manager in self.instances.iter_mut() { @@ -493,6 +507,26 @@ where } } +fn remove_global_with_timer( + dh: &DisplayHandle, + event_loop_handle: &LoopHandle, + id: GlobalId, +) { + dh.disable_global::(id.clone()); + let source = Timer::from_duration(Duration::from_secs(5)); + let dh = dh.clone(); + let res = event_loop_handle.insert_source(source, move |_, _, _state| { + dh.remove_global::(id.clone()); + TimeoutAction::Drop + }); + if let Err(err) = res { + tracing::error!( + "failed to insert timer source to destroy output global: {}", + err + ); + } +} + macro_rules! delegate_output_configuration { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [