Support XDG Activation
Test XDG Activation
This commit is contained in:
parent
b34b08f004
commit
180efb0ba9
10 changed files with 413 additions and 27 deletions
|
|
@ -445,10 +445,18 @@ impl<C: XConnection> Dispatch<WlSeat, ObjectKey> for ServerState<C> {
|
|||
state
|
||||
.objects
|
||||
.insert_from_other_objects([*key], |[seat_obj], key| {
|
||||
let Seat { client, .. }: &Seat = seat_obj.try_into().unwrap();
|
||||
let client = client.get_keyboard(&state.qh, key);
|
||||
let Seat {
|
||||
client: client_seat,
|
||||
..
|
||||
}: &Seat = seat_obj.try_into().unwrap();
|
||||
let client = client_seat.get_keyboard(&state.qh, key);
|
||||
let server = data_init.init(id, key);
|
||||
Keyboard { client, server }.into()
|
||||
Keyboard {
|
||||
client,
|
||||
server,
|
||||
seat: client_seat.clone(),
|
||||
}
|
||||
.into()
|
||||
});
|
||||
}
|
||||
Request::<WlSeat>::GetTouch { id } => {
|
||||
|
|
|
|||
|
|
@ -540,7 +540,12 @@ impl HandleEvent for Pointer {
|
|||
}
|
||||
}
|
||||
|
||||
pub type Keyboard = GenericObject<WlKeyboard, client::wl_keyboard::WlKeyboard>;
|
||||
pub struct Keyboard {
|
||||
pub server: WlKeyboard,
|
||||
pub client: client::wl_keyboard::WlKeyboard,
|
||||
pub seat: client::wl_seat::WlSeat,
|
||||
}
|
||||
|
||||
impl HandleEvent for Keyboard {
|
||||
type Event = client::wl_keyboard::Event;
|
||||
|
||||
|
|
@ -557,7 +562,14 @@ impl HandleEvent for Keyboard {
|
|||
.get(key)
|
||||
.map(<_ as AsRef<SurfaceData>>::as_ref)
|
||||
}) {
|
||||
state.last_kb_serial = Some(serial);
|
||||
state.last_kb_serial = Some((
|
||||
state
|
||||
.last_kb_serial
|
||||
.take()
|
||||
.and_then(|(seat, _)| (seat == self.seat).then_some(seat))
|
||||
.unwrap_or_else(|| self.seat.clone()),
|
||||
serial,
|
||||
));
|
||||
let output_name = data.get_output_name(state);
|
||||
state.to_focus = Some(FocusData {
|
||||
window: data.window.unwrap(),
|
||||
|
|
@ -584,6 +596,22 @@ impl HandleEvent for Keyboard {
|
|||
self.server.leave(serial, &data.server);
|
||||
}
|
||||
}
|
||||
client::wl_keyboard::Event::Key {
|
||||
serial,
|
||||
time,
|
||||
key,
|
||||
state: key_state,
|
||||
} => {
|
||||
state.last_kb_serial = Some((
|
||||
state
|
||||
.last_kb_serial
|
||||
.take()
|
||||
.and_then(|(seat, _)| (seat == self.seat).then_some(seat))
|
||||
.unwrap_or_else(|| self.seat.clone()),
|
||||
serial,
|
||||
));
|
||||
self.server.key(serial, time, key, convert_wenum(key_state));
|
||||
}
|
||||
_ => simple_event_shunt! {
|
||||
self.server, event: client::wl_keyboard::Event => [
|
||||
Keymap {
|
||||
|
|
@ -591,12 +619,6 @@ impl HandleEvent for Keyboard {
|
|||
|fd| fd.as_fd(),
|
||||
size
|
||||
},
|
||||
Key {
|
||||
serial,
|
||||
time,
|
||||
key,
|
||||
|state| convert_wenum(state)
|
||||
},
|
||||
Modifiers {
|
||||
serial,
|
||||
mods_depressed,
|
||||
|
|
|
|||
|
|
@ -11,11 +11,12 @@ use crate::{X11Selection, XConnection};
|
|||
use log::{debug, warn};
|
||||
use rustix::event::{poll, PollFd, PollFlags};
|
||||
use slotmap::{new_key_type, HopSlotMap, SparseSecondaryMap};
|
||||
use smithay_client_toolkit::activation::ActivationState;
|
||||
use smithay_client_toolkit::data_device_manager::{
|
||||
data_device::DataDevice, data_offer::SelectionOffer, data_source::CopyPasteSource,
|
||||
DataDeviceManagerState,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::io::Read;
|
||||
use std::os::fd::{AsFd, BorrowedFd};
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
|
@ -43,10 +44,11 @@ use wayland_protocols::{
|
|||
xwayland_shell_v1::XwaylandShellV1, xwayland_surface_v1::XwaylandSurfaceV1,
|
||||
},
|
||||
};
|
||||
use wayland_server::protocol::wl_seat::WlSeat;
|
||||
use wayland_server::{
|
||||
protocol::{
|
||||
wl_callback::WlCallback, wl_compositor::WlCompositor, wl_output::WlOutput, wl_seat::WlSeat,
|
||||
wl_shm::WlShm, wl_surface::WlSurface,
|
||||
wl_callback::WlCallback, wl_compositor::WlCompositor, wl_output::WlOutput, wl_shm::WlShm,
|
||||
wl_surface::WlSurface,
|
||||
},
|
||||
Client, DisplayHandle, Resource, WEnum,
|
||||
};
|
||||
|
|
@ -102,6 +104,7 @@ struct WindowData {
|
|||
attrs: WindowAttributes,
|
||||
output_offset: WindowOutputOffset,
|
||||
output_key: Option<ObjectKey>,
|
||||
activation_token: Option<String>,
|
||||
}
|
||||
|
||||
impl WindowData {
|
||||
|
|
@ -110,6 +113,7 @@ impl WindowData {
|
|||
override_redirect: bool,
|
||||
dims: WindowDims,
|
||||
parent: Option<x::Window>,
|
||||
activation_token: Option<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
window,
|
||||
|
|
@ -124,6 +128,7 @@ impl WindowData {
|
|||
},
|
||||
output_offset: WindowOutputOffset::default(),
|
||||
output_key: None,
|
||||
activation_token,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -488,6 +493,7 @@ pub struct ServerState<C: XConnection> {
|
|||
associated_windows: SparseSecondaryMap<ObjectKey, x::Window>,
|
||||
output_keys: SparseSecondaryMap<ObjectKey, ()>,
|
||||
windows: HashMap<x::Window, WindowData>,
|
||||
pids: HashSet<u32>,
|
||||
|
||||
qh: ClientQueueHandle,
|
||||
client: Option<Client>,
|
||||
|
|
@ -499,7 +505,8 @@ pub struct ServerState<C: XConnection> {
|
|||
|
||||
xdg_wm_base: XdgWmBase,
|
||||
clipboard_data: Option<ClipboardData<C::X11Selection>>,
|
||||
last_kb_serial: Option<u32>,
|
||||
last_kb_serial: Option<(client::wl_seat::WlSeat, u32)>,
|
||||
activation_state: Option<ActivationState>,
|
||||
global_output_offset: GlobalOutputOffset,
|
||||
global_offset_updated: bool,
|
||||
}
|
||||
|
|
@ -529,6 +536,12 @@ impl<C: XConnection> ServerState<C> {
|
|||
source: None::<CopyPasteData<C::X11Selection>>,
|
||||
});
|
||||
|
||||
let activation_state = ActivationState::bind(&clientside.global_list, &qh)
|
||||
.inspect_err(|e| {
|
||||
warn!("Could not bind xdg activation ({e:?}). Windows might not recive focus depending on compositor focus stealing policy.")
|
||||
})
|
||||
.ok();
|
||||
|
||||
dh.create_global::<Self, XwaylandShellV1, _>(1, ());
|
||||
clientside
|
||||
.global_list
|
||||
|
|
@ -537,6 +550,7 @@ impl<C: XConnection> ServerState<C> {
|
|||
|
||||
Self {
|
||||
windows: HashMap::new(),
|
||||
pids: HashSet::new(),
|
||||
clientside,
|
||||
client: None,
|
||||
qh,
|
||||
|
|
@ -552,6 +566,7 @@ impl<C: XConnection> ServerState<C> {
|
|||
xdg_wm_base,
|
||||
clipboard_data,
|
||||
last_kb_serial: None,
|
||||
activation_state,
|
||||
global_output_offset: GlobalOutputOffset {
|
||||
x: GlobalOutputOffsetDimension {
|
||||
owner: None,
|
||||
|
|
@ -593,10 +608,23 @@ impl<C: XConnection> ServerState<C> {
|
|||
override_redirect: bool,
|
||||
dims: WindowDims,
|
||||
parent: Option<x::Window>,
|
||||
pid: Option<u32>,
|
||||
) {
|
||||
let activation_token = pid
|
||||
.filter(|pid| self.pids.insert(*pid))
|
||||
.and_then(|pid| std::fs::read(format!("/proc/{pid}/environ")).ok())
|
||||
.and_then(|environ| {
|
||||
environ
|
||||
.split(|byte| *byte == 0)
|
||||
.find_map(|line| line.strip_prefix(b"XDG_ACTIVATION_TOKEN="))
|
||||
.and_then(|token| String::from_utf8(token.to_vec()).ok())
|
||||
});
|
||||
if activation_token.is_none() {
|
||||
self.activate_window(window);
|
||||
}
|
||||
self.windows.insert(
|
||||
window,
|
||||
WindowData::new(window, override_redirect, dims, parent),
|
||||
WindowData::new(window, override_redirect, dims, parent, activation_token),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -823,6 +851,41 @@ impl<C: XConnection> ServerState<C> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn activate_window(&mut self, window: x::Window) {
|
||||
let Some(activation_state) = self.activation_state.as_ref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(last_focused_toplevel) = self.last_focused_toplevel else {
|
||||
warn!("No last focused toplevel, cannot focus window {window:?}");
|
||||
return;
|
||||
};
|
||||
let Some(win) = self.windows.get(&last_focused_toplevel) else {
|
||||
warn!("Unknown last focused toplevel, cannot focus window {window:?}");
|
||||
return;
|
||||
};
|
||||
let Some(key) = win.surface_key else {
|
||||
warn!("Last focused toplevel has no surface, cannot focus window {window:?}");
|
||||
return;
|
||||
};
|
||||
let Some(object) = self.objects.get_mut(key) else {
|
||||
warn!("Last focused toplevel has stale reference, cannot focus window {window:?}");
|
||||
return;
|
||||
};
|
||||
let surface: &mut SurfaceData = object.as_mut();
|
||||
activation_state.request_token_with_data(
|
||||
&self.qh,
|
||||
xdg_activation::ActivationData::new(
|
||||
window,
|
||||
smithay_client_toolkit::activation::RequestData {
|
||||
app_id: win.attrs.class.clone(),
|
||||
seat_and_serial: self.last_kb_serial.clone(),
|
||||
surface: Some(surface.client.clone()),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn destroy_window(&mut self, window: x::Window) {
|
||||
let _ = self.windows.remove(&window);
|
||||
}
|
||||
|
|
@ -839,7 +902,12 @@ impl<C: XConnection> ServerState<C> {
|
|||
let CopyPasteData::X11 { inner, .. } = d.source.insert(data) else {
|
||||
unreachable!();
|
||||
};
|
||||
if let Some(serial) = self.last_kb_serial.as_ref().copied() {
|
||||
if let Some(serial) = self
|
||||
.last_kb_serial
|
||||
.as_ref()
|
||||
.map(|(_seat, serial)| serial)
|
||||
.copied()
|
||||
{
|
||||
inner.set_selection(d.device.as_ref().unwrap(), serial);
|
||||
}
|
||||
}
|
||||
|
|
@ -920,6 +988,7 @@ impl<C: XConnection> ServerState<C> {
|
|||
}
|
||||
|
||||
self.handle_clipboard_events();
|
||||
self.handle_activations();
|
||||
self.clientside
|
||||
.queue
|
||||
.flush()
|
||||
|
|
@ -967,6 +1036,24 @@ impl<C: XConnection> ServerState<C> {
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_activations(&mut self) {
|
||||
let Some(activation_state) = self.activation_state.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let globals = &mut self.clientside.globals;
|
||||
|
||||
globals.pending_activations.retain(|(window, token)| {
|
||||
if let Some(window) = self.windows.get(window) {
|
||||
if let Some(key) = window.surface_key {
|
||||
let surface: &SurfaceData = self.objects[key].as_ref();
|
||||
activation_state.activate::<Self>(&surface.client, token.clone());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
});
|
||||
}
|
||||
|
||||
fn calc_global_output_offset(&mut self) {
|
||||
for (key, _) in &self.output_keys {
|
||||
let Some(object) = &self.objects.get(key) else {
|
||||
|
|
@ -1134,6 +1221,14 @@ impl<C: XConnection> ServerState<C> {
|
|||
toplevel.set_fullscreen(None);
|
||||
}
|
||||
|
||||
let surface: &SurfaceData = self.objects[surface_key].as_ref();
|
||||
if let (Some(activation_state), Some(token)) = (
|
||||
self.activation_state.as_ref(),
|
||||
window.activation_token.clone(),
|
||||
) {
|
||||
activation_state.activate::<Self>(&surface.client, token);
|
||||
}
|
||||
|
||||
ToplevelData {
|
||||
xdg: XdgSurfaceData {
|
||||
surface: xdg,
|
||||
|
|
|
|||
|
|
@ -515,7 +515,7 @@ impl TestFixture {
|
|||
let dims = data.dims;
|
||||
self.register_window(window, data);
|
||||
self.satellite
|
||||
.new_window(window, override_redirect, dims, parent);
|
||||
.new_window(window, override_redirect, dims, parent, None);
|
||||
}
|
||||
|
||||
fn map_window(
|
||||
|
|
@ -1118,6 +1118,7 @@ fn window_group_properties() {
|
|||
..Default::default()
|
||||
},
|
||||
None,
|
||||
None,
|
||||
);
|
||||
f.satellite
|
||||
.set_win_title(prop_win, WmName::WmName("window".into()));
|
||||
|
|
@ -1137,7 +1138,7 @@ fn window_group_properties() {
|
|||
let (_, surface) = comp.create_surface();
|
||||
let dims = data.dims;
|
||||
f.register_window(win, data);
|
||||
f.satellite.new_window(win, false, dims, None);
|
||||
f.satellite.new_window(win, false, dims, None, None);
|
||||
f.satellite.set_win_hints(
|
||||
win,
|
||||
super::WmHints {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue