Commit 02d5a5d5b17e
tarball6 files changed+167-3
@@ -682,6 +682,24 @@ impl Layout {
682682
}683683
}684684
685
+ /// Moves a window to `tag` on `o`, marking the tag urgent when the window686
+ /// ends up out of sight.687
+ pub fn relocate(&mut self, w: WindowId, o: Option<OutputId>, tag: TagId) {688
+ if let Some(o) = o {689
+ self.send_to_output(w, o);690
+ }691
+ self.set_tag(w, tag);692
+ self.mark_urgent(w);693
+ }694
+695
+ pub fn mark_urgent(&mut self, w: WindowId) {696
+ if !self.visible(w)697
+ && let Some(tag) = self.windows.get(w).map(|win| win.tag)698
+ {699
+ self.tags[tag].urgent = true;700
+ }701
+ }702
+685703
pub fn send_to_output(&mut self, w: WindowId, o: OutputId) {686704
let Some(win) = self.windows.get(w) else {687705
return;@@ -0,0 +1,88 @@
1
+//! xdg-activation. Tokens carry launch context, so a program started2
+//! through a launcher or D-Bus lands where the user was when it began, and3
+//! they decide whether an activation may take focus.4
+5
+use crate::state::State;6
+use monolith_layout::{OutputId, TagId};7
+use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;8
+use smithay::wayland::xdg_activation::{9
+ XdgActivationHandler, XdgActivationState, XdgActivationToken, XdgActivationTokenData,10
+};11
+use std::time::Duration;12
+13
+/// Tokens older than this may still place a window but not focus it.14
+const FRESH: Duration = Duration::from_secs(10);15
+16
+struct Context {17
+ output: Option<OutputId>,18
+ tag: TagId,19
+ /// Created by monolith or by a client while it held the keyboard.20
+ may_focus: bool,21
+}22
+23
+impl XdgActivationHandler for State {24
+ fn activation_state(&mut self) -> &mut XdgActivationState {25
+ &mut self.activation_state26
+ }27
+28
+ fn token_created(&mut self, _token: XdgActivationToken, data: XdgActivationTokenData) -> bool {29
+ let keyboard = self.seat.get_keyboard();30
+ let focused = keyboard.as_ref().and_then(|k| k.current_focus());31
+ let may_focus = data.surface.is_some() && data.surface == focused;32
+ let (output, tag) = self.launch_context();33
+ data.user_data.insert_if_missing(|| Context {34
+ output,35
+ tag,36
+ may_focus,37
+ });38
+ true39
+ }40
+41
+ fn request_activation(42
+ &mut self,43
+ _token: XdgActivationToken,44
+ data: XdgActivationTokenData,45
+ surface: WlSurface,46
+ ) {47
+ let Some(id) = self.window_id(&surface) else {48
+ return;49
+ };50
+ let ctx = data.user_data.get::<Context>();51
+ if self.awaiting.contains(&id)52
+ && let Some(ctx) = ctx53
+ && !self.placed_by_rule.contains(&id)54
+ {55
+ self.layout.relocate(id, ctx.output, ctx.tag);56
+ }57
+ let may_focus = ctx.is_some_and(|c| c.may_focus) && data.timestamp.elapsed() < FRESH;58
+ if may_focus && self.layout.visible(id) {59
+ self.layout.focus(id);60
+ } else {61
+ self.layout.mark_urgent(id);62
+ }63
+ self.apply_layout();64
+ }65
+}66
+67
+impl State {68
+ /// Where a launch starting now should put its window.69
+ pub fn launch_context(&self) -> (Option<OutputId>, TagId) {70
+ let output = self.layout.active_output();71
+ let tag = output.map_or(self.layout.homeless(), |o| self.layout.launch_tag(o));72
+ (output, tag)73
+ }74
+75
+ /// A token for a program monolith starts, carrying the launch context76
+ /// in case its window comes from a process that is not its descendant.77
+ pub fn launch_token(&mut self) -> String {78
+ let (output, tag) = self.launch_context();79
+ let data = XdgActivationTokenData::default();80
+ data.user_data.insert_if_missing(|| Context {81
+ output,82
+ tag,83
+ may_focus: true,84
+ });85
+ let (token, _) = self.activation_state.create_external_token(data);86
+ token.to_string()87
+ }88
+}@@ -173,10 +173,12 @@ impl State {
173173
if let Some(sock) = &self.ipc.path {174174
cmd.env("MONOLITH_SOCKET", sock);175175
}176
+ let token = self.launch_token();177
+ cmd.env("XDG_ACTIVATION_TOKEN", &token)178
+ .env("DESKTOP_STARTUP_ID", &token);176179
let mut child = cmd.spawn().map_err(|e| format!("{program}: {e}"))?;177180
tracing::info!(pid = child.id(), ?argv, "spawned");178
- let output = self.layout.active_output();179
- let tag = output.map_or(self.layout.homeless(), |o| self.layout.launch_tag(o));181
+ let (output, tag) = self.launch_context();180182
self.launches.record(child.id(), output, tag);181183
std::thread::spawn(move || child.wait());182184
Ok(())@@ -26,6 +26,9 @@ use smithay::wayland::dmabuf::{
2626
};2727
use smithay::wayland::output::OutputHandler;2828
use smithay::wayland::selection::SelectionHandler;29
+use smithay::wayland::selection::primary_selection::{30
+ PrimarySelectionHandler, PrimarySelectionState, set_primary_focus,31
+};2932
use smithay::wayland::selection::data_device::{3033
DataDeviceHandler, DataDeviceState, WaylandDndGrabHandler, set_data_device_focus,3134
};@@ -186,7 +189,8 @@ impl SeatHandler for State {
186189
187190
fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&WlSurface>) {188191
let client = focused.and_then(|s| self.display.get_client(s.id()).ok());189
- set_data_device_focus(&self.display, seat, client);192
+ set_data_device_focus(&self.display, seat, client.clone());193
+ set_primary_focus(&self.display, seat, client);190194
}191195
}192196
@@ -200,6 +204,28 @@ impl DataDeviceHandler for State {
200204
}201205
}202206
207
+impl PrimarySelectionHandler for State {208
+ fn primary_selection_state(&mut self) -> &mut PrimarySelectionState {209
+ &mut self.primary_selection_state210
+ }211
+}212
+213
+impl smithay::wayland::selection::wlr_data_control::DataControlHandler for State {214
+ fn data_control_state(215
+ &mut self,216
+ ) -> &mut smithay::wayland::selection::wlr_data_control::DataControlState {217
+ &mut self.wlr_data_control_state218
+ }219
+}220
+221
+impl smithay::wayland::selection::ext_data_control::DataControlHandler for State {222
+ fn data_control_state(223
+ &mut self,224
+ ) -> &mut smithay::wayland::selection::ext_data_control::DataControlState {225
+ &mut self.ext_data_control_state226
+ }227
+}228
+203229
impl DndGrabHandler for State {}204230
205231
impl WaylandDndGrabHandler for State {@@ -1,3 +1,4 @@
1
+mod activation;12
mod anim;23
mod commands;34
mod cursor;@@ -28,12 +28,16 @@ use smithay::wayland::idle_inhibit::IdleInhibitManagerState;
2828
use smithay::wayland::idle_notify::IdleNotifierState;2929
use smithay::wayland::output::OutputManagerState;3030
use smithay::wayland::selection::data_device::DataDeviceState;31
+use smithay::wayland::selection::ext_data_control::DataControlState as ExtDataControlState;32
+use smithay::wayland::selection::primary_selection::PrimarySelectionState;33
+use smithay::wayland::selection::wlr_data_control::DataControlState as WlrDataControlState;3134
use smithay::wayland::session_lock::SessionLockManagerState;3235
use smithay::wayland::shell::wlr_layer::{Layer, WlrLayerShellState};3336
use smithay::wayland::shell::xdg::decoration::XdgDecorationState;3437
use smithay::wayland::shell::xdg::{XdgShellState, XdgToplevelSurfaceData};3538
use smithay::wayland::shm::ShmState;3639
use smithay::wayland::socket::ListeningSocketSource;40
+use smithay::wayland::xdg_activation::XdgActivationState;3741
use std::collections::{HashMap, HashSet};3842
use std::ffi::OsString;3943
use std::path::PathBuf;@@ -80,6 +84,8 @@ pub struct State {
8084
/// switches between tiled, floating and fullscreen.8185
pub last_kind: HashMap<WindowId, (Kind, Rect, OutputId)>,8286
pub awaiting: HashSet<WindowId>,87
+ /// Windows a rule sent somewhere, which activation tokens leave alone.88
+ pub placed_by_rule: HashSet<WindowId>,8389
/// A layer surface given the keyboard by a click, like a launcher.8490
pub layer_focus: Option<WlSurface>,8591
pub lock: crate::lock::Lock,@@ -90,6 +96,10 @@ pub struct State {
9096
pub compositor_state: CompositorState,9197
pub xdg_shell_state: XdgShellState,9298
pub layer_shell_state: WlrLayerShellState,99
+ pub activation_state: XdgActivationState,100
+ pub primary_selection_state: PrimarySelectionState,101
+ pub wlr_data_control_state: WlrDataControlState,102
+ pub ext_data_control_state: ExtDataControlState,93103
pub session_lock_state: SessionLockManagerState,94104
pub idle_notifier: IdleNotifierState<State>,95105
pub _idle_inhibit: IdleInhibitManagerState,@@ -152,6 +162,7 @@ impl State {
152162
layout.gaps = gaps(&config);153163
let binds = Binds::new(&config);154164
let animation = config.animation.clone();165
+ let primary_selection_state = PrimarySelectionState::new::<State>(&dh);155166
Ok(Self {156167
start: Instant::now(),157168
socket_name,@@ -184,6 +195,7 @@ impl State {
184195
borders: Default::default(),185196
last_kind: HashMap::new(),186197
awaiting: HashSet::new(),198
+ placed_by_rule: HashSet::new(),187199
layer_focus: None,188200
lock: Default::default(),189201
inhibitors: HashSet::new(),@@ -192,6 +204,18 @@ impl State {
192204
compositor_state: CompositorState::new::<State>(&dh),193205
xdg_shell_state: XdgShellState::new::<State>(&dh),194206
layer_shell_state: WlrLayerShellState::new::<State>(&dh),207
+ activation_state: XdgActivationState::new::<State>(&dh),208
+ wlr_data_control_state: WlrDataControlState::new::<State, _>(209
+ &dh,210
+ Some(&primary_selection_state),211
+ |_| true,212
+ ),213
+ ext_data_control_state: ExtDataControlState::new::<State, _>(214
+ &dh,215
+ Some(&primary_selection_state),216
+ |_| true,217
+ ),218
+ primary_selection_state,195219
session_lock_state: SessionLockManagerState::new::<State, _>(&dh, |_| true),196220
idle_notifier: IdleNotifierState::new(&dh, event_loop.handle()),197221
_idle_inhibit: IdleInhibitManagerState::new::<State>(&dh),@@ -262,6 +286,9 @@ impl State {
262286
None => launch.map(|l| l.1),263287
};264288
let id = self.layout.add_window(output, tag, None);289
+ if rule.tag.is_some() || rule.output.is_some() {290
+ self.placed_by_rule.insert(id);291
+ }265292
if rule.floating == Some(true) {266293
self.layout.toggle_floating(id);267294
}@@ -304,6 +331,7 @@ impl State {
304331
}305332
306333
fn first_buffer(&mut self, id: WindowId) {334
+ self.placed_by_rule.remove(&id);307335
if self.awaiting.remove(&id) {308336
self.anim.spawned(id, now_ns());309337
self.apply_layout();@@ -319,6 +347,7 @@ impl State {
319347
self.leave_ghost(id, &window);320348
}321349
self.layout.remove_window(id);350
+ self.placed_by_rule.remove(&id);322351
self.apply_layout();323352
}324353
}