Skip to content

Commit 180efb0

Browse files
bbb651Supreeeme
authored andcommitted
Support XDG Activation
Test XDG Activation
1 parent b34b08f commit 180efb0

File tree

10 files changed

+413
-27
lines changed

10 files changed

+413
-27
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ wayland-client.workspace = true
2828
wayland-protocols = { workspace = true, features = ["client", "server", "staging", "unstable"] }
2929
wayland-scanner.workspace = true
3030
wayland-server.workspace = true
31-
xcb = { version = "1.3.0", features = ["composite", "randr"] }
31+
xcb = { version = "1.3.0", features = ["composite", "randr", "res"] }
3232
wl_drm = { path = "wl_drm" }
3333
libc = "0.2.153"
3434
log = "0.4.21"

src/clientside/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod data_device;
2+
pub mod xdg_activation;
23

34
use crate::server::{ObjectEvent, ObjectKey};
45
use std::os::unix::net::UnixStream;
@@ -46,6 +47,7 @@ use wayland_protocols::{
4647
viewporter::client::{wp_viewport::WpViewport, wp_viewporter::WpViewporter},
4748
},
4849
xdg::{
50+
activation::v1::client::xdg_activation_v1::XdgActivationV1,
4951
shell::client::{
5052
xdg_popup::XdgPopup, xdg_positioner::XdgPositioner, xdg_surface::XdgSurface,
5153
xdg_toplevel::XdgToplevel, xdg_wm_base::XdgWmBase,
@@ -69,6 +71,7 @@ pub struct Globals {
6971
smithay_client_toolkit::data_device_manager::WritePipe,
7072
)>,
7173
pub cancelled: bool,
74+
pub pending_activations: Vec<(xcb::x::Window, String)>,
7275
}
7376

7477
pub type ClientQueueHandle = QueueHandle<Globals>;
@@ -136,6 +139,7 @@ delegate_noop!(Globals: WpViewport);
136139
delegate_noop!(Globals: ZxdgOutputManagerV1);
137140
delegate_noop!(Globals: ZwpPointerConstraintsV1);
138141
delegate_noop!(Globals: ZwpTabletManagerV2);
142+
delegate_noop!(Globals: XdgActivationV1);
139143

140144
impl Dispatch<WlRegistry, GlobalListContents> for Globals {
141145
fn event(

src/clientside/xdg_activation.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use smithay_client_toolkit::{
2+
activation::{ActivationHandler, RequestData, RequestDataExt},
3+
delegate_activation,
4+
};
5+
use xcb::x;
6+
7+
use crate::clientside::Globals;
8+
9+
delegate_activation!(Globals, ActivationData);
10+
11+
pub struct ActivationData {
12+
window: x::Window,
13+
data: RequestData,
14+
}
15+
16+
impl ActivationData {
17+
pub fn new(window: x::Window, data: RequestData) -> Self {
18+
Self { window, data }
19+
}
20+
}
21+
22+
impl RequestDataExt for ActivationData {
23+
fn app_id(&self) -> Option<&str> {
24+
self.data.app_id()
25+
}
26+
27+
fn seat_and_serial(&self) -> Option<(&wayland_client::protocol::wl_seat::WlSeat, u32)> {
28+
self.data.seat_and_serial()
29+
}
30+
31+
fn surface(&self) -> Option<&wayland_client::protocol::wl_surface::WlSurface> {
32+
self.data.surface()
33+
}
34+
}
35+
36+
impl ActivationHandler for Globals {
37+
type RequestData = ActivationData;
38+
39+
fn new_token(&mut self, token: String, data: &Self::RequestData) {
40+
self.pending_activations.push((data.window, token));
41+
}
42+
}

src/server/dispatch.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -445,10 +445,18 @@ impl<C: XConnection> Dispatch<WlSeat, ObjectKey> for ServerState<C> {
445445
state
446446
.objects
447447
.insert_from_other_objects([*key], |[seat_obj], key| {
448-
let Seat { client, .. }: &Seat = seat_obj.try_into().unwrap();
449-
let client = client.get_keyboard(&state.qh, key);
448+
let Seat {
449+
client: client_seat,
450+
..
451+
}: &Seat = seat_obj.try_into().unwrap();
452+
let client = client_seat.get_keyboard(&state.qh, key);
450453
let server = data_init.init(id, key);
451-
Keyboard { client, server }.into()
454+
Keyboard {
455+
client,
456+
server,
457+
seat: client_seat.clone(),
458+
}
459+
.into()
452460
});
453461
}
454462
Request::<WlSeat>::GetTouch { id } => {

src/server/event.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,12 @@ impl HandleEvent for Pointer {
540540
}
541541
}
542542

543-
pub type Keyboard = GenericObject<WlKeyboard, client::wl_keyboard::WlKeyboard>;
543+
pub struct Keyboard {
544+
pub server: WlKeyboard,
545+
pub client: client::wl_keyboard::WlKeyboard,
546+
pub seat: client::wl_seat::WlSeat,
547+
}
548+
544549
impl HandleEvent for Keyboard {
545550
type Event = client::wl_keyboard::Event;
546551

@@ -557,7 +562,14 @@ impl HandleEvent for Keyboard {
557562
.get(key)
558563
.map(<_ as AsRef<SurfaceData>>::as_ref)
559564
}) {
560-
state.last_kb_serial = Some(serial);
565+
state.last_kb_serial = Some((
566+
state
567+
.last_kb_serial
568+
.take()
569+
.and_then(|(seat, _)| (seat == self.seat).then_some(seat))
570+
.unwrap_or_else(|| self.seat.clone()),
571+
serial,
572+
));
561573
let output_name = data.get_output_name(state);
562574
state.to_focus = Some(FocusData {
563575
window: data.window.unwrap(),
@@ -584,19 +596,29 @@ impl HandleEvent for Keyboard {
584596
self.server.leave(serial, &data.server);
585597
}
586598
}
599+
client::wl_keyboard::Event::Key {
600+
serial,
601+
time,
602+
key,
603+
state: key_state,
604+
} => {
605+
state.last_kb_serial = Some((
606+
state
607+
.last_kb_serial
608+
.take()
609+
.and_then(|(seat, _)| (seat == self.seat).then_some(seat))
610+
.unwrap_or_else(|| self.seat.clone()),
611+
serial,
612+
));
613+
self.server.key(serial, time, key, convert_wenum(key_state));
614+
}
587615
_ => simple_event_shunt! {
588616
self.server, event: client::wl_keyboard::Event => [
589617
Keymap {
590618
|format| convert_wenum(format),
591619
|fd| fd.as_fd(),
592620
size
593621
},
594-
Key {
595-
serial,
596-
time,
597-
key,
598-
|state| convert_wenum(state)
599-
},
600622
Modifiers {
601623
serial,
602624
mods_depressed,

src/server/mod.rs

Lines changed: 101 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ use crate::{X11Selection, XConnection};
1111
use log::{debug, warn};
1212
use rustix::event::{poll, PollFd, PollFlags};
1313
use slotmap::{new_key_type, HopSlotMap, SparseSecondaryMap};
14+
use smithay_client_toolkit::activation::ActivationState;
1415
use smithay_client_toolkit::data_device_manager::{
1516
data_device::DataDevice, data_offer::SelectionOffer, data_source::CopyPasteSource,
1617
DataDeviceManagerState,
1718
};
18-
use std::collections::HashMap;
19+
use std::collections::{HashMap, HashSet};
1920
use std::io::Read;
2021
use std::os::fd::{AsFd, BorrowedFd};
2122
use std::os::unix::net::UnixStream;
@@ -43,10 +44,11 @@ use wayland_protocols::{
4344
xwayland_shell_v1::XwaylandShellV1, xwayland_surface_v1::XwaylandSurfaceV1,
4445
},
4546
};
47+
use wayland_server::protocol::wl_seat::WlSeat;
4648
use wayland_server::{
4749
protocol::{
48-
wl_callback::WlCallback, wl_compositor::WlCompositor, wl_output::WlOutput, wl_seat::WlSeat,
49-
wl_shm::WlShm, wl_surface::WlSurface,
50+
wl_callback::WlCallback, wl_compositor::WlCompositor, wl_output::WlOutput, wl_shm::WlShm,
51+
wl_surface::WlSurface,
5052
},
5153
Client, DisplayHandle, Resource, WEnum,
5254
};
@@ -102,6 +104,7 @@ struct WindowData {
102104
attrs: WindowAttributes,
103105
output_offset: WindowOutputOffset,
104106
output_key: Option<ObjectKey>,
107+
activation_token: Option<String>,
105108
}
106109

107110
impl WindowData {
@@ -110,6 +113,7 @@ impl WindowData {
110113
override_redirect: bool,
111114
dims: WindowDims,
112115
parent: Option<x::Window>,
116+
activation_token: Option<String>,
113117
) -> Self {
114118
Self {
115119
window,
@@ -124,6 +128,7 @@ impl WindowData {
124128
},
125129
output_offset: WindowOutputOffset::default(),
126130
output_key: None,
131+
activation_token,
127132
}
128133
}
129134

@@ -488,6 +493,7 @@ pub struct ServerState<C: XConnection> {
488493
associated_windows: SparseSecondaryMap<ObjectKey, x::Window>,
489494
output_keys: SparseSecondaryMap<ObjectKey, ()>,
490495
windows: HashMap<x::Window, WindowData>,
496+
pids: HashSet<u32>,
491497

492498
qh: ClientQueueHandle,
493499
client: Option<Client>,
@@ -499,7 +505,8 @@ pub struct ServerState<C: XConnection> {
499505

500506
xdg_wm_base: XdgWmBase,
501507
clipboard_data: Option<ClipboardData<C::X11Selection>>,
502-
last_kb_serial: Option<u32>,
508+
last_kb_serial: Option<(client::wl_seat::WlSeat, u32)>,
509+
activation_state: Option<ActivationState>,
503510
global_output_offset: GlobalOutputOffset,
504511
global_offset_updated: bool,
505512
}
@@ -529,6 +536,12 @@ impl<C: XConnection> ServerState<C> {
529536
source: None::<CopyPasteData<C::X11Selection>>,
530537
});
531538

539+
let activation_state = ActivationState::bind(&clientside.global_list, &qh)
540+
.inspect_err(|e| {
541+
warn!("Could not bind xdg activation ({e:?}). Windows might not recive focus depending on compositor focus stealing policy.")
542+
})
543+
.ok();
544+
532545
dh.create_global::<Self, XwaylandShellV1, _>(1, ());
533546
clientside
534547
.global_list
@@ -537,6 +550,7 @@ impl<C: XConnection> ServerState<C> {
537550

538551
Self {
539552
windows: HashMap::new(),
553+
pids: HashSet::new(),
540554
clientside,
541555
client: None,
542556
qh,
@@ -552,6 +566,7 @@ impl<C: XConnection> ServerState<C> {
552566
xdg_wm_base,
553567
clipboard_data,
554568
last_kb_serial: None,
569+
activation_state,
555570
global_output_offset: GlobalOutputOffset {
556571
x: GlobalOutputOffsetDimension {
557572
owner: None,
@@ -593,10 +608,23 @@ impl<C: XConnection> ServerState<C> {
593608
override_redirect: bool,
594609
dims: WindowDims,
595610
parent: Option<x::Window>,
611+
pid: Option<u32>,
596612
) {
613+
let activation_token = pid
614+
.filter(|pid| self.pids.insert(*pid))
615+
.and_then(|pid| std::fs::read(format!("/proc/{pid}/environ")).ok())
616+
.and_then(|environ| {
617+
environ
618+
.split(|byte| *byte == 0)
619+
.find_map(|line| line.strip_prefix(b"XDG_ACTIVATION_TOKEN="))
620+
.and_then(|token| String::from_utf8(token.to_vec()).ok())
621+
});
622+
if activation_token.is_none() {
623+
self.activate_window(window);
624+
}
597625
self.windows.insert(
598626
window,
599-
WindowData::new(window, override_redirect, dims, parent),
627+
WindowData::new(window, override_redirect, dims, parent, activation_token),
600628
);
601629
}
602630

@@ -823,6 +851,41 @@ impl<C: XConnection> ServerState<C> {
823851
}
824852
}
825853

854+
pub fn activate_window(&mut self, window: x::Window) {
855+
let Some(activation_state) = self.activation_state.as_ref() else {
856+
return;
857+
};
858+
859+
let Some(last_focused_toplevel) = self.last_focused_toplevel else {
860+
warn!("No last focused toplevel, cannot focus window {window:?}");
861+
return;
862+
};
863+
let Some(win) = self.windows.get(&last_focused_toplevel) else {
864+
warn!("Unknown last focused toplevel, cannot focus window {window:?}");
865+
return;
866+
};
867+
let Some(key) = win.surface_key else {
868+
warn!("Last focused toplevel has no surface, cannot focus window {window:?}");
869+
return;
870+
};
871+
let Some(object) = self.objects.get_mut(key) else {
872+
warn!("Last focused toplevel has stale reference, cannot focus window {window:?}");
873+
return;
874+
};
875+
let surface: &mut SurfaceData = object.as_mut();
876+
activation_state.request_token_with_data(
877+
&self.qh,
878+
xdg_activation::ActivationData::new(
879+
window,
880+
smithay_client_toolkit::activation::RequestData {
881+
app_id: win.attrs.class.clone(),
882+
seat_and_serial: self.last_kb_serial.clone(),
883+
surface: Some(surface.client.clone()),
884+
},
885+
),
886+
);
887+
}
888+
826889
pub fn destroy_window(&mut self, window: x::Window) {
827890
let _ = self.windows.remove(&window);
828891
}
@@ -839,7 +902,12 @@ impl<C: XConnection> ServerState<C> {
839902
let CopyPasteData::X11 { inner, .. } = d.source.insert(data) else {
840903
unreachable!();
841904
};
842-
if let Some(serial) = self.last_kb_serial.as_ref().copied() {
905+
if let Some(serial) = self
906+
.last_kb_serial
907+
.as_ref()
908+
.map(|(_seat, serial)| serial)
909+
.copied()
910+
{
843911
inner.set_selection(d.device.as_ref().unwrap(), serial);
844912
}
845913
}
@@ -920,6 +988,7 @@ impl<C: XConnection> ServerState<C> {
920988
}
921989

922990
self.handle_clipboard_events();
991+
self.handle_activations();
923992
self.clientside
924993
.queue
925994
.flush()
@@ -967,6 +1036,24 @@ impl<C: XConnection> ServerState<C> {
9671036
}
9681037
}
9691038

1039+
fn handle_activations(&mut self) {
1040+
let Some(activation_state) = self.activation_state.as_ref() else {
1041+
return;
1042+
};
1043+
let globals = &mut self.clientside.globals;
1044+
1045+
globals.pending_activations.retain(|(window, token)| {
1046+
if let Some(window) = self.windows.get(window) {
1047+
if let Some(key) = window.surface_key {
1048+
let surface: &SurfaceData = self.objects[key].as_ref();
1049+
activation_state.activate::<Self>(&surface.client, token.clone());
1050+
return false;
1051+
}
1052+
}
1053+
true
1054+
});
1055+
}
1056+
9701057
fn calc_global_output_offset(&mut self) {
9711058
for (key, _) in &self.output_keys {
9721059
let Some(object) = &self.objects.get(key) else {
@@ -1134,6 +1221,14 @@ impl<C: XConnection> ServerState<C> {
11341221
toplevel.set_fullscreen(None);
11351222
}
11361223

1224+
let surface: &SurfaceData = self.objects[surface_key].as_ref();
1225+
if let (Some(activation_state), Some(token)) = (
1226+
self.activation_state.as_ref(),
1227+
window.activation_token.clone(),
1228+
) {
1229+
activation_state.activate::<Self>(&surface.client, token);
1230+
}
1231+
11371232
ToplevelData {
11381233
xdg: XdgSurfaceData {
11391234
surface: xdg,

0 commit comments

Comments
 (0)