xdg-activation: Initial implementation
This commit is contained in:
parent
9ced8c346d
commit
f7cc1985e3
11 changed files with 419 additions and 66 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
186
src/shell/mod.rs
186
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,
|
||||
|
|
@ -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 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
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue