Merge pull request #207 from pop-os/xdg-activation_jammy
xdg-activation: Initial implementation
This commit is contained in:
commit
9a04fa2abd
13 changed files with 422 additions and 73 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -88,4 +88,4 @@ debug = true
|
|||
lto = "fat"
|
||||
|
||||
[patch."https://github.com/Smithay/smithay.git"]
|
||||
smithay = { git = "https://github.com/smithay//smithay", rev = "a8f3c46" }
|
||||
smithay = { git = "https://github.com/smithay//smithay", rev = "d5b352b" }
|
||||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<State>,
|
||||
serial: Option<Serial>,
|
||||
) {
|
||||
// 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<State>,
|
||||
serial: Option<Serial>,
|
||||
) {
|
||||
Self::append_focus_stack(state, target, active_seat);
|
||||
|
||||
// update keyboard focus
|
||||
if let Some(keyboard) = active_seat.get_keyboard() {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ pub use self::grabs::*;
|
|||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FloatingLayout {
|
||||
pub(in crate::shell) space: Space<CosmicMapped>,
|
||||
pub(crate) space: Space<CosmicMapped>,
|
||||
}
|
||||
|
||||
impl FloatingLayout {
|
||||
|
|
|
|||
192
src/shell/mod.rs
192
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<State>, Option<Output>)>,
|
||||
pub pending_layers: Vec<(LayerSurface, Output, Seat<State>)>,
|
||||
pub pending_activations: HashMap<ActivationKey, ActivationContext>,
|
||||
pub override_redirect_windows: Vec<X11Surface>,
|
||||
|
||||
// wayland_state
|
||||
|
|
@ -160,6 +181,7 @@ pub struct Shell {
|
|||
pub toplevel_info_state: ToplevelInfoState<State, CosmicSurface>,
|
||||
pub toplevel_management_state: ToplevelManagementState,
|
||||
pub xdg_shell_state: XdgShellState,
|
||||
pub xdg_activation_state: XdgActivationState,
|
||||
pub workspace_state: WorkspaceState<State>,
|
||||
|
||||
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<State>,
|
||||
toplevel_info: &mut ToplevelInfoState<State, CosmicSurface>,
|
||||
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<Item = Seat<State>>,
|
||||
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
|
||||
toplevel_info_state: &mut ToplevelInfoState<State, CosmicSurface>,
|
||||
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<State, CosmicSurface>,
|
||||
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<State, CosmicSurface>,
|
||||
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::<State>(dh);
|
||||
let xdg_activation_state = XdgActivationState::new::<State>(dh);
|
||||
let toplevel_info_state =
|
||||
ToplevelInfoState::new(dh, client_should_see_privileged_protocols);
|
||||
let toplevel_management_state = ToplevelManagementState::new::<State, _>(
|
||||
|
|
@ -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,
|
||||
|
|
@ -1006,10 +1059,7 @@ impl Shell {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn workspaces_for_surface(
|
||||
&self,
|
||||
surface: &WlSurface,
|
||||
) -> impl Iterator<Item = (WorkspaceHandle, Output)> {
|
||||
pub fn workspace_for_surface(&self, surface: &WlSurface) -> Option<(WorkspaceHandle, Output)> {
|
||||
match self.outputs().find(|o| {
|
||||
let map = layer_map_for_output(o);
|
||||
map.layer_for_surface(surface, WindowSurfaceType::ALL)
|
||||
|
|
@ -1029,7 +1079,6 @@ impl Shell {
|
|||
})
|
||||
.map(|w| (w.handle.clone(), w.output().clone())),
|
||||
}
|
||||
.into_iter()
|
||||
}
|
||||
|
||||
pub fn element_for_surface(&self, surface: &CosmicSurface) -> Option<&CosmicMapped> {
|
||||
|
|
@ -1194,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() {
|
||||
|
|
@ -1262,10 +1315,42 @@ impl Shell {
|
|||
.unwrap();
|
||||
let (window, seat, output) = state.common.shell.pending_windows.remove(pos);
|
||||
|
||||
let should_be_fullscreen = output.is_some();
|
||||
let output = output.unwrap_or_else(|| seat.active_output());
|
||||
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 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
|
||||
|
|
@ -1284,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
|
||||
|
|
@ -1306,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 {
|
||||
|
|
@ -1328,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() {
|
||||
|
|
@ -1670,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<DropableSession>,
|
||||
pub output_stack: VecDeque<String>,
|
||||
pub pending_tokens: HashSet<XdgActivationToken>,
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ impl<P: Program + Send + 'static> fmt::Debug for IcedElementInternal<P> {
|
|||
.field("size", &self.size)
|
||||
.field("pending_update", &self.pending_update)
|
||||
.field("cursor_pos", &self.cursor_pos)
|
||||
.field("theme", &self.theme)
|
||||
.field("theme", &"...")
|
||||
.field("renderer", &"...")
|
||||
.field("state", &"...")
|
||||
.field("debug", &self.debug)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1246,7 +1246,7 @@ impl State {
|
|||
.outputs()
|
||||
.map(|o| (o.clone(), self.common.shell.active_space(o).handle.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
for (handle, output) in self.common.shell.workspaces_for_surface(surface) {
|
||||
if let Some((handle, output)) = self.common.shell.workspace_for_surface(surface) {
|
||||
let workspace = self.common.shell.space_for_handle_mut(&handle).unwrap();
|
||||
if !workspace.pending_buffers.is_empty() {
|
||||
// TODO: replace with drain_filter....
|
||||
|
|
|
|||
150
src/wayland/handlers/xdg_activation.rs
Normal file
150
src/wayland/handlers/xdg_activation.rs
Normal file
|
|
@ -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::<ClientState>()
|
||||
.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::<ActivationContext>() {
|
||||
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);
|
||||
|
|
@ -312,7 +312,7 @@ impl XdgShellHandler for State {
|
|||
.visible_outputs_for_surface(surface.wl_surface())
|
||||
.collect::<Vec<_>>();
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
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,
|
||||
},
|
||||
primary_selection::{
|
||||
clear_primary_selection, current_primary_selection_userdata,
|
||||
request_primary_client_selection, set_primary_selection,
|
||||
},
|
||||
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::<ActivationContext>())
|
||||
{
|
||||
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::<ActivationContext>())
|
||||
{
|
||||
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::<Vec<_>>()
|
||||
};
|
||||
for output in outputs.iter() {
|
||||
self.common.shell.active_space_mut(output).refresh();
|
||||
self.common.shell.refresh_active_space(output);
|
||||
}
|
||||
|
||||
// screencopy
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue