diff --git a/Cargo.lock b/Cargo.lock index c44fc02d..10627f9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3964,7 +3964,7 @@ checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "smithay" version = "0.3.0" -source = "git+https://github.com/smithay//smithay?rev=a8f3c46#a8f3c46f0bd0153160a3ba117502ba47c38ab0dc" +source = "git+https://github.com/smithay//smithay?rev=d5b352b#d5b352b33525d21b38ad8d7ebd54c99d39246464" dependencies = [ "appendlist", "ash", diff --git a/Cargo.toml b/Cargo.toml index 89357a99..ad54e88d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,4 +88,4 @@ debug = true lto = "fat" [patch."https://github.com/Smithay/smithay.git"] -smithay = { git = "https://github.com/smithay//smithay", rev = "a8f3c46" } \ No newline at end of file +smithay = { git = "https://github.com/smithay//smithay", rev = "d5b352b" } \ No newline at end of file diff --git a/src/input/mod.rs b/src/input/mod.rs index 1a8aea65..af3feedc 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -15,7 +15,10 @@ use crate::{ }, state::{Common, SessionLock}, utils::prelude::*, - wayland::{handlers::screencopy::ScreencopySessions, protocols::screencopy::Session}, + wayland::{ + handlers::{screencopy::ScreencopySessions, xdg_activation::ActivationContext}, + protocols::screencopy::Session, + }, }; use calloop::{timer::Timer, RegistrationToken}; use cosmic_comp_config::workspace::WorkspaceLayout; @@ -1688,8 +1691,22 @@ impl State { workspace.toggle_floating_window(seat); } Action::Spawn(command) => { - let wayland_display = self.common.socket.clone(); + let (token, data) = self + .common + .shell + .xdg_activation_state + .create_external_token(None); + let (token, data) = (token.clone(), data.clone()); + + let seat = self.common.last_active_seat(); + let output = seat.active_output(); + let workspace = self.common.shell.active_space_mut(&output); + workspace.pending_tokens.insert(token.clone()); + let handle = workspace.handle; + data.user_data + .insert_if_missing(move || ActivationContext::Workspace(handle)); + let wayland_display = self.common.socket.clone(); let display = self .common .xwayland_state @@ -1697,22 +1714,22 @@ impl State { .map(|s| format!(":{}", s.display)) .unwrap_or_default(); - std::thread::spawn(move || { - let mut cmd = std::process::Command::new("/bin/sh"); + let mut cmd = std::process::Command::new("/bin/sh"); - cmd.arg("-c") - .arg(command.clone()) - .env("WAYLAND_DISPLAY", &wayland_display) - .env("DISPLAY", &display) - .env_remove("COSMIC_SESSION_SOCK"); + cmd.arg("-c") + .arg(command.clone()) + .env("WAYLAND_DISPLAY", &wayland_display) + .env("DISPLAY", &display) + .env("XDG_ACTIVATION_TOKEN", &*token) + .env("DESKTOP_STARTUP_ID", &*token) + .env_remove("COSMIC_SESSION_SOCK"); - match cmd.spawn() { - Ok(mut child) => { - let _res = child.wait(); - } - Err(err) => { - tracing::warn!(?err, "Failed to spawn \"{}\"", command); - } + std::thread::spawn(move || match cmd.spawn() { + Ok(mut child) => { + let _res = child.wait(); + } + Err(err) => { + tracing::warn!(?err, "Failed to spawn \"{}\"", command); } }); } diff --git a/src/shell/focus/mod.rs b/src/shell/focus/mod.rs index f6e30920..fc244f5e 100644 --- a/src/shell/focus/mod.rs +++ b/src/shell/focus/mod.rs @@ -94,11 +94,10 @@ impl ActiveFocus { } impl Shell { - pub fn set_focus<'a>( + pub fn append_focus_stack( state: &mut State, target: Option<&KeyboardFocusTarget>, active_seat: &Seat, - serial: Option, ) { // update FocusStack and notify layouts about new focus (if any window) let element = match target { @@ -128,6 +127,15 @@ impl Shell { } } } + } + + pub fn set_focus( + state: &mut State, + target: Option<&KeyboardFocusTarget>, + active_seat: &Seat, + serial: Option, + ) { + Self::append_focus_stack(state, target, active_seat); // update keyboard focus if let Some(keyboard) = active_seat.get_keyboard() { diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 75d847d7..0676ae2a 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -35,7 +35,7 @@ pub use self::grabs::*; #[derive(Debug, Default)] pub struct FloatingLayout { - pub(in crate::shell) space: Space, + pub(crate) space: Space, } impl FloatingLayout { diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 6a5ba211..d315adbb 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -30,6 +30,7 @@ use smithay::{ }, xdg::XdgShellState, }, + xdg_activation::XdgActivationState, }, xwayland::X11Surface, }; @@ -38,12 +39,15 @@ use crate::{ config::{Config, KeyModifiers, KeyPattern}, state::client_should_see_privileged_protocols, utils::prelude::*, - wayland::protocols::{ - toplevel_info::ToplevelInfoState, - toplevel_management::{ManagementCapabilities, ToplevelManagementState}, - workspace::{ - WorkspaceCapabilities, WorkspaceGroupHandle, WorkspaceHandle, WorkspaceState, - WorkspaceUpdateGuard, + wayland::{ + handlers::xdg_activation::ActivationContext, + protocols::{ + toplevel_info::ToplevelInfoState, + toplevel_management::{ManagementCapabilities, ToplevelManagementState}, + workspace::{ + WorkspaceCapabilities, WorkspaceGroupHandle, WorkspaceHandle, WorkspaceState, + WorkspaceUpdateGuard, + }, }, }, }; @@ -145,6 +149,22 @@ pub enum MaximizeMode { OnTop, } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum ActivationKey { + Wayland(WlSurface), + X11(u32), +} + +impl From<&CosmicSurface> for ActivationKey { + fn from(value: &CosmicSurface) -> Self { + match value { + CosmicSurface::Wayland(w) => ActivationKey::Wayland(w.toplevel().wl_surface().clone()), + CosmicSurface::X11(s) => ActivationKey::X11(s.window_id()), + _ => unreachable!(), + } + } +} + #[derive(Debug)] pub struct Shell { pub workspaces: Workspaces, @@ -153,6 +173,7 @@ pub struct Shell { pub maximize_mode: MaximizeMode, pub pending_windows: Vec<(CosmicSurface, Seat, Option)>, pub pending_layers: Vec<(LayerSurface, Output, Seat)>, + pub pending_activations: HashMap, pub override_redirect_windows: Vec, // wayland_state @@ -160,6 +181,7 @@ pub struct Shell { pub toplevel_info_state: ToplevelInfoState, pub toplevel_management_state: ToplevelManagementState, pub xdg_shell_state: XdgShellState, + pub xdg_activation_state: XdgActivationState, pub workspace_state: WorkspaceState, theme: cosmic::Theme, @@ -279,6 +301,8 @@ impl WorkspaceSet { if self.active != idx { let old_active = self.active; state.remove_workspace_state(&self.workspaces[old_active].handle, WState::Active); + state.remove_workspace_state(&self.workspaces[old_active].handle, WState::Urgent); + state.remove_workspace_state(&self.workspaces[idx].handle, WState::Urgent); state.add_workspace_state(&self.workspaces[idx].handle, WState::Active); self.previously_active = Some((old_active, Instant::now())); self.active = idx; @@ -299,13 +323,13 @@ impl WorkspaceSet { self.output = new_output.clone(); } - fn refresh<'a>(&mut self) { + fn refresh<'a>(&mut self, xdg_activation_state: &XdgActivationState) { if let Some((_, start)) = self.previously_active { if Instant::now().duration_since(start).as_millis() >= ANIMATION_DURATION.as_millis() { self.previously_active = None; } } else { - self.workspaces[self.active].refresh(); + self.workspaces[self.active].refresh(xdg_activation_state); } } @@ -332,7 +356,7 @@ impl WorkspaceSet { if self .workspaces .last() - .map(|last| last.windows().next().is_some()) + .map(|last| !last.pending_tokens.is_empty() || last.windows().next().is_some()) .unwrap_or(true) { self.add_empty_workspace(state); @@ -342,7 +366,8 @@ impl WorkspaceSet { let len = self.workspaces.len(); let mut keep = vec![true; len]; for (i, workspace) in self.workspaces.iter().enumerate() { - let has_windows = workspace.windows().next().is_some(); + let has_windows = + !workspace.pending_tokens.is_empty() || workspace.windows().next().is_some(); if !has_windows && i != self.active && i != len - 1 { state.remove_workspace(workspace.handle); @@ -370,6 +395,7 @@ impl WorkspaceSet { amount: usize, state: &mut WorkspaceUpdateGuard, toplevel_info: &mut ToplevelInfoState, + xdg_activation_state: &XdgActivationState, ) { if amount < self.workspaces.len() { // merge last ones @@ -401,7 +427,7 @@ impl WorkspaceSet { state.remove_workspace(workspace.handle); } - last_space.refresh(); + last_space.refresh(xdg_activation_state); } else if amount > self.workspaces.len() { // add empty ones while amount > self.workspaces.len() { @@ -508,6 +534,7 @@ impl Workspaces { seats: impl Iterator>, workspace_state: &mut WorkspaceUpdateGuard<'_, State>, toplevel_info_state: &mut ToplevelInfoState, + xdg_activation_state: &XdgActivationState, ) { if !self.sets.contains_key(output) { return; @@ -557,7 +584,7 @@ impl Workspaces { // update mapping workspace.set_output(&new_output, toplevel_info_state); - workspace.refresh(); + workspace.refresh(xdg_activation_state); // TODO: merge if mode = static new_set.workspaces.push(workspace); @@ -576,7 +603,7 @@ impl Workspaces { self.backup_set = Some(set); } - self.refresh(workspace_state, toplevel_info_state) + self.refresh(workspace_state, toplevel_info_state, xdg_activation_state) } } @@ -585,6 +612,7 @@ impl Workspaces { config: &Config, workspace_state: &mut WorkspaceUpdateGuard<'_, State>, toplevel_info_state: &mut ToplevelInfoState, + xdg_activation_state: &XdgActivationState, ) { let old_mode = self.mode; @@ -650,13 +678,14 @@ impl Workspaces { _ => {} }; - self.refresh(workspace_state, toplevel_info_state) + self.refresh(workspace_state, toplevel_info_state, xdg_activation_state) } pub fn refresh( &mut self, workspace_state: &mut WorkspaceUpdateGuard<'_, State>, toplevel_info_state: &mut ToplevelInfoState, + xdg_activation_state: &XdgActivationState, ) { match self.mode { WorkspaceMode::Global => { @@ -696,10 +725,10 @@ impl Workspaces { let mut active = self.sets[0].active; let mut keep = vec![true; len]; for i in 0..len { - let has_windows = self - .sets - .values() - .any(|s| s.workspaces[i].windows().next().is_some()); + let has_windows = self.sets.values().any(|s| { + !s.workspaces[i].pending_tokens.is_empty() + || s.workspaces[i].windows().next().is_some() + }); if !has_windows && i != active && i != len - 1 { for workspace in self.sets.values().map(|s| &s.workspaces[i]) { @@ -733,7 +762,12 @@ impl Workspaces { } WorkspaceAmount::Static(amount) => { for set in self.sets.values_mut() { - set.ensure_static(amount as usize, workspace_state, toplevel_info_state) + set.ensure_static( + amount as usize, + workspace_state, + toplevel_info_state, + xdg_activation_state, + ) } } } @@ -746,14 +780,19 @@ impl Workspaces { } WorkspaceAmount::Static(amount) => { for set in self.sets.values_mut() { - set.ensure_static(amount as usize, workspace_state, toplevel_info_state) + set.ensure_static( + amount as usize, + workspace_state, + toplevel_info_state, + xdg_activation_state, + ) } } }, } for set in self.sets.values_mut() { - set.refresh() + set.refresh(xdg_activation_state) } } @@ -826,7 +865,7 @@ impl Workspaces { } } - pub fn set_theme(&mut self, theme: cosmic::Theme) { + pub fn set_theme(&mut self, theme: cosmic::Theme, xdg_activation_state: &XdgActivationState) { for (_, s) in &mut self.sets { s.theme = theme.clone(); for mut w in &mut s.workspaces { @@ -837,7 +876,7 @@ impl Workspaces { m.force_redraw(); }); - w.refresh(); + w.refresh(xdg_activation_state); w.dirty.store(true, Ordering::Relaxed); w.recalculate(); } @@ -854,6 +893,7 @@ impl Shell { client_should_see_privileged_protocols, ); let xdg_shell_state = XdgShellState::new::(dh); + let xdg_activation_state = XdgActivationState::new::(dh); let toplevel_info_state = ToplevelInfoState::new(dh, client_should_see_privileged_protocols); let toplevel_management_state = ToplevelManagementState::new::( @@ -874,12 +914,14 @@ impl Shell { pending_windows: Vec::new(), pending_layers: Vec::new(), + pending_activations: HashMap::new(), override_redirect_windows: Vec::new(), layer_shell_state, toplevel_info_state, toplevel_management_state, xdg_shell_state, + xdg_activation_state, workspace_state, theme, @@ -905,6 +947,7 @@ impl Shell { seats, &mut self.workspace_state.update(), &mut self.toplevel_info_state, + &self.xdg_activation_state, ); self.refresh(); // cleans up excess of workspaces and empty workspaces } @@ -912,8 +955,12 @@ impl Shell { pub fn update_config(&mut self, config: &Config) { let mut workspace_state = self.workspace_state.update(); let toplevel_info_state = &mut self.toplevel_info_state; - self.workspaces - .update_config(config, &mut workspace_state, toplevel_info_state); + self.workspaces.update_config( + config, + &mut workspace_state, + toplevel_info_state, + &self.xdg_activation_state, + ); } pub fn activate( @@ -958,6 +1005,12 @@ impl Shell { self.workspaces.active_mut(output) } + pub fn refresh_active_space(&mut self, output: &Output) { + self.workspaces + .active_mut(output) + .refresh(&self.xdg_activation_state) + } + pub fn visible_outputs_for_surface<'a>( &'a self, surface: &'a WlSurface, @@ -1190,9 +1243,13 @@ impl Shell { self.popups.cleanup(); + self.xdg_activation_state.retain_tokens(|_, data| { + Instant::now().duration_since(data.timestamp) < Duration::from_secs(5) + }); self.workspaces.refresh( &mut self.workspace_state.update(), &mut self.toplevel_info_state, + &self.xdg_activation_state, ); for output in self.outputs() { @@ -1258,10 +1315,42 @@ impl Shell { .unwrap(); let (window, seat, output) = state.common.shell.pending_windows.remove(pos); + let pending_activation = state + .common + .shell + .pending_activations + .remove(&(&window).into()); + let workspace_handle = match pending_activation { + Some(ActivationContext::Workspace(handle)) => Some(handle), + _ => None, + }; + let should_be_fullscreen = output.is_some(); - let output = output.unwrap_or_else(|| seat.active_output()); + let mut output = output.unwrap_or_else(|| seat.active_output()); + + // this is beyond stupid, just to make the borrow checker happy + let workspace = if let Some(handle) = workspace_handle.filter(|handle| { + state + .common + .shell + .workspaces + .spaces() + .any(|space| &space.handle == handle) + }) { + state + .common + .shell + .workspaces + .spaces_mut() + .find(|space| space.handle == handle) + .unwrap() + } else { + state.common.shell.workspaces.active_mut(&output) + }; + if output != workspace.output { + output = workspace.output.clone(); + } - let workspace = state.common.shell.workspaces.active_mut(&output); if let Some((mapped, layer, previous_workspace)) = workspace.remove_fullscreen() { let old_handle = workspace.handle.clone(); let new_workspace_handle = state @@ -1280,7 +1369,26 @@ impl Shell { ); }; - let workspace = state.common.shell.workspaces.active_mut(&output); + let active_handle = state.common.shell.workspaces.active(&output).1.handle; + let workspace = if let Some(handle) = workspace_handle.filter(|handle| { + state + .common + .shell + .workspaces + .spaces() + .any(|space| &space.handle == handle) + }) { + state + .common + .shell + .workspaces + .spaces_mut() + .find(|space| space.handle == handle) + .unwrap() + } else { + state.common.shell.workspaces.active_mut(&output) + }; + state.common.shell.toplevel_info_state.new_toplevel(&window); state .common @@ -1302,6 +1410,9 @@ impl Shell { { mapped.set_debug(state.common.egui.active); } + + let workspace_empty = workspace.mapped().next().is_none(); + if layout::should_be_floating(&window) || !workspace.tiling_enabled { workspace.floating_layer.map(mapped.clone(), None); } else { @@ -1324,7 +1435,14 @@ impl Shell { workspace.fullscreen_request(&mapped.active_window(), None); } - Shell::set_focus(state, Some(&KeyboardFocusTarget::from(mapped)), &seat, None); + if workspace.output == seat.active_output() && active_handle == workspace.handle { + // TODO: enforce focus stealing prevention by also checking the same rules as for the else case. + Shell::set_focus(state, Some(&KeyboardFocusTarget::from(mapped)), &seat, None); + } else if workspace_empty || workspace_handle.is_some() || should_be_fullscreen { + let handle = workspace.handle; + Shell::append_focus_stack(state, Some(&KeyboardFocusTarget::from(mapped)), &seat); + state.common.shell.set_urgent(&handle); + } let active_space = state.common.shell.active_space(&output); for mapped in active_space.mapped() { @@ -1666,7 +1784,13 @@ impl Shell { pub(crate) fn set_theme(&mut self, theme: cosmic::Theme) { self.theme = theme.clone(); self.refresh(); - self.workspaces.set_theme(theme.clone()); + self.workspaces + .set_theme(theme.clone(), &self.xdg_activation_state); + } + + pub fn set_urgent(&mut self, workspace: &WorkspaceHandle) { + let mut workspace_guard = self.workspace_state.update(); + workspace_guard.add_workspace_state(workspace, WState::Urgent); } } diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 3f1de379..916d274f 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -43,11 +43,12 @@ use smithay::{ wayland::{ compositor::{add_blocker, Blocker, BlockerState}, seat::WaylandFocus, + xdg_activation::{XdgActivationState, XdgActivationToken}, }, xwayland::X11Surface, }; use std::{ - collections::{HashMap, VecDeque}, + collections::{HashMap, HashSet, VecDeque}, sync::{ atomic::{AtomicBool, Ordering}, Arc, @@ -87,6 +88,7 @@ pub struct Workspace { pub pending_buffers: Vec<(ScreencopySession, BufferParams)>, pub screencopy_sessions: Vec, pub output_stack: VecDeque, + pub pending_tokens: HashSet, pub(super) backdrop_id: Id, pub dirty: AtomicBool, } @@ -227,12 +229,13 @@ impl Workspace { pending_buffers: Vec::new(), screencopy_sessions: Vec::new(), output_stack: VecDeque::new(), + pending_tokens: HashSet::new(), backdrop_id: Id::new(), dirty: AtomicBool::new(false), } } - pub fn refresh(&mut self) { + pub fn refresh(&mut self, xdg_activation_state: &XdgActivationState) { #[cfg(feature = "debug")] puffin::profile_function!(); @@ -243,6 +246,9 @@ impl Workspace { self.floating_layer.refresh(); self.tiling_layer.refresh(); + + self.pending_tokens + .retain(|token| xdg_activation_state.data_for_token(token).is_some()); } pub fn refresh_focus_stack(&mut self) { diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index 2edb9aff..92159c75 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -30,5 +30,6 @@ pub mod viewporter; pub mod virtual_keyboard; pub mod wl_drm; pub mod workspace; +pub mod xdg_activation; pub mod xdg_shell; pub mod xwayland_keyboard_grab; diff --git a/src/wayland/handlers/xdg_activation.rs b/src/wayland/handlers/xdg_activation.rs new file mode 100644 index 00000000..e76c5856 --- /dev/null +++ b/src/wayland/handlers/xdg_activation.rs @@ -0,0 +1,150 @@ +use smithay::{ + delegate_xdg_activation, + input::Seat, + reexports::wayland_server::protocol::wl_surface::WlSurface, + wayland::xdg_activation::{ + XdgActivationHandler, XdgActivationState, XdgActivationToken, XdgActivationTokenData, + }, +}; +use tracing::debug; + +use crate::{shell::ActivationKey, state::ClientState, utils::prelude::*}; +use crate::{state::State, wayland::protocols::workspace::WorkspaceHandle}; + +#[derive(Debug, Clone, Copy)] +pub enum ActivationContext { + UrgentOnly, + Workspace(WorkspaceHandle), +} + +impl XdgActivationHandler for State { + fn activation_state(&mut self) -> &mut XdgActivationState { + &mut self.common.shell.xdg_activation_state + } + + fn token_created(&mut self, token: XdgActivationToken, data: XdgActivationTokenData) -> bool { + // Privileged clients always get valid tokens + if data + .client_id + .and_then(|client_id| { + self.common + .display_handle + .backend_handle() + .get_client_data(client_id) + .ok() + }) + .and_then(|data| { + data.downcast_ref::() + .map(|data| data.privileged) + }) + .unwrap_or(false) + { + if let Some(seat) = data.serial.and_then(|(_, seat)| Seat::from_resource(&seat)) { + let output = seat.active_output(); + let workspace = self.common.shell.active_space_mut(&output); + workspace.pending_tokens.insert(token.clone()); + let handle = workspace.handle; + data.user_data + .insert_if_missing(move || ActivationContext::Workspace(handle)); + debug!(?token, "created workspace token for privileged client"); + } else { + data.user_data + .insert_if_missing(|| ActivationContext::UrgentOnly); + debug!( + ?token, + "created urgent-only token for privileged client without seat" + ); + } + + return true; + }; + + // Tokens without validation aren't allowed to steal focus + let Some((serial, seat)) = data.serial else { + data.user_data.insert_if_missing(|| ActivationContext::UrgentOnly); + debug!(?token, "created urgent-only token for missing seat/serial"); + return true + }; + let Some(seat) = Seat::from_resource(&seat) else { + data.user_data.insert_if_missing(|| ActivationContext::UrgentOnly); + debug!(?token, "created urgent-only token for unknown seat"); + return true + }; + + // At this point we don't bother with urgent-only tokens. + // If the client provides a bad serial, it should be fixed. + + let keyboard = seat.get_keyboard().unwrap(); + let valid = keyboard + .last_enter() + .map(|last_enter| serial.is_no_older_than(&last_enter)) + .unwrap_or(false); + + if valid { + let output = seat.active_output(); + let workspace = self.common.shell.active_space_mut(&output); + workspace.pending_tokens.insert(token.clone()); + let handle = workspace.handle; + data.user_data + .insert_if_missing(move || ActivationContext::Workspace(handle)); + + debug!(?token, "created workspace token"); + } else { + debug!(?token, "created urgent-only token for invalid serial"); + } + + valid + } + + fn request_activation( + &mut self, + _token: XdgActivationToken, + token_data: XdgActivationTokenData, + surface: WlSurface, + ) { + if let Some(context) = token_data.user_data.get::() { + if let Some(element) = self.common.shell.element_for_wl_surface(&surface).cloned() { + match context { + ActivationContext::UrgentOnly => { + if let Some((workspace, _output)) = + self.common.shell.workspace_for_surface(&surface) + { + self.common.shell.set_urgent(&workspace); + } + } + ActivationContext::Workspace(workspace) => { + let seat = self.common.last_active_seat().clone(); + let current_output = seat.active_output(); + let current_workspace = self.common.shell.active_space_mut(¤t_output); + + if current_workspace + .floating_layer + .mapped() + .any(|m| m == &element) + { + current_workspace + .floating_layer + .space + .raise_element(&element, true); + } + + let target = element.into(); + if workspace == ¤t_workspace.handle { + Shell::set_focus(self, Some(&target), &seat, None); + } else { + Shell::append_focus_stack(self, Some(&target), &seat); + self.common.shell.set_urgent(workspace); + } + } + } + } else { + self.common + .shell + .pending_activations + .insert(ActivationKey::Wayland(surface), context.clone()); + } + } + } +} + +delegate_xdg_activation!(State); diff --git a/src/wayland/handlers/xdg_shell/mod.rs b/src/wayland/handlers/xdg_shell/mod.rs index bc8b2390..ca15441c 100644 --- a/src/wayland/handlers/xdg_shell/mod.rs +++ b/src/wayland/handlers/xdg_shell/mod.rs @@ -312,7 +312,7 @@ impl XdgShellHandler for State { .visible_outputs_for_surface(surface.wl_surface()) .collect::>(); for output in outputs.iter() { - self.common.shell.active_space_mut(output).refresh(); + self.common.shell.refresh_active_space(output); } // animations might be unblocked now diff --git a/src/xwayland.rs b/src/xwayland.rs index a344178a..600632e5 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -5,23 +5,29 @@ use crate::{ shell::{focus::target::KeyboardFocusTarget, CosmicSurface, Shell}, state::State, utils::prelude::*, - wayland::{handlers::screencopy::PendingScreencopyBuffers, protocols::screencopy::SessionType}, + wayland::{ + handlers::{screencopy::PendingScreencopyBuffers, xdg_activation::ActivationContext}, + protocols::screencopy::SessionType, + }, }; use smithay::{ backend::drm::DrmNode, desktop::space::SpaceElement, reexports::x11rb::protocol::xproto::Window as X11Window, utils::{Logical, Point, Rectangle, Size}, - wayland::selection::{ - data_device::{ - clear_data_device_selection, current_data_device_selection_userdata, - request_data_device_client_selection, set_data_device_selection, - }, - primary_selection::{ - clear_primary_selection, current_primary_selection_userdata, - request_primary_client_selection, set_primary_selection, + wayland::{ + selection::{ + data_device::{ + clear_data_device_selection, current_data_device_selection_userdata, + request_data_device_client_selection, set_data_device_selection, + }, + primary_selection::{ + clear_primary_selection, current_primary_selection_userdata, + request_primary_client_selection, set_primary_selection, + }, + SelectionTarget, }, - SelectionTarget, + xdg_activation::XdgActivationToken, }, xwayland::{ xwm::{Reorder, XwmId}, @@ -145,12 +151,29 @@ impl XwmHandler for State { warn!(?window, ?err, "Failed to send Xwayland Mapped-Event",); } + let startup_id = window.startup_id(); let surface = CosmicSurface::X11(window.clone()); if self.common.shell.element_for_surface(&surface).is_some() { return; } let seat = self.common.last_active_seat().clone(); + if let Some(context) = startup_id + .map(XdgActivationToken::from) + .and_then(|token| { + self.common + .shell + .xdg_activation_state + .data_for_token(&token) + }) + .and_then(|data| data.user_data.get::()) + { + self.common.shell.pending_activations.insert( + crate::shell::ActivationKey::X11(window.window_id()), + context.clone(), + ); + } + self.common .shell .pending_windows @@ -172,6 +195,30 @@ impl XwmHandler for State { }) .cloned() { + if !self + .common + .shell + .pending_activations + .contains_key(&crate::shell::ActivationKey::X11(surface.window_id())) + { + if let Some(startup_id) = match &window { + CosmicSurface::X11(x11) => x11.startup_id(), + _ => None, + } { + if let Some(context) = self + .common + .shell + .xdg_activation_state + .data_for_token(&XdgActivationToken::from(startup_id)) + .and_then(|data| data.user_data.get::()) + { + self.common.shell.pending_activations.insert( + crate::shell::ActivationKey::X11(surface.window_id()), + context.clone(), + ); + } + } + } Shell::map_window(self, &window); } } @@ -224,7 +271,7 @@ impl XwmHandler for State { self.common.shell.outputs().cloned().collect::>() }; for output in outputs.iter() { - self.common.shell.active_space_mut(output).refresh(); + self.common.shell.refresh_active_space(output); } // screencopy