Support XDG Activation

Test XDG Activation
This commit is contained in:
bbb651 2025-03-03 00:18:27 +02:00 committed by Shawn Wallace
parent b34b08f004
commit 180efb0ba9
10 changed files with 413 additions and 27 deletions

View file

@ -23,6 +23,10 @@ use wayland_protocols::{
viewporter::server::wp_viewporter::WpViewporter,
},
xdg::{
activation::v1::server::{
xdg_activation_token_v1::{self, XdgActivationTokenV1},
xdg_activation_v1::{self, XdgActivationV1},
},
shell::server::{
xdg_popup::{self, XdgPopup},
xdg_positioner::{self, XdgPositioner},
@ -176,6 +180,14 @@ struct KeyboardState {
current_focus: Option<SurfaceId>,
}
#[derive(Default)]
struct ActivationTokenData {
serial: Option<(u32, WlSeat)>,
app_id: Option<String>,
surface: Option<WlSurface>,
constructed: bool,
}
struct State {
surfaces: HashMap<SurfaceId, SurfaceData>,
outputs: HashMap<WlOutput, Output>,
@ -185,12 +197,16 @@ struct State {
last_surface_id: Option<SurfaceId>,
last_output: Option<WlOutput>,
callbacks: Vec<WlCallback>,
seat: Option<WlSeat>,
pointer: Option<WlPointer>,
keyboard: Option<KeyboardState>,
configure_serial: u32,
selection: Option<WlDataSource>,
data_device_man: Option<WlDataDeviceManager>,
data_device: Option<WlDataDevice>,
xdg_activation: Option<XdgActivationV1>,
valid_tokens: HashSet<String>,
token_counter: u32,
}
impl Default for State {
@ -204,12 +220,16 @@ impl Default for State {
last_surface_id: None,
last_output: None,
callbacks: Vec::new(),
seat: None,
pointer: None,
keyboard: None,
configure_serial: 0,
selection: None,
data_device_man: None,
data_device: None,
xdg_activation: None,
valid_tokens: HashSet::new(),
token_counter: 0,
}
}
}
@ -246,11 +266,9 @@ impl State {
keyboard.leave(self.configure_serial, &self.surfaces[id].surface);
}
keyboard.enter(
self.configure_serial,
&self.surfaces[&surface_id].surface,
Vec::default(),
);
let surface = self.surfaces.get_mut(&surface_id).unwrap();
keyboard.enter(self.configure_serial, &surface.surface, Vec::default());
surface.last_enter_serial = Some(self.configure_serial);
*current_focus = Some(surface_id);
}
@ -267,6 +285,10 @@ impl State {
}
}
fn get_focused(&self) -> Option<SurfaceId> {
self.keyboard.as_ref()?.current_focus
}
#[track_caller]
fn configure_popup(&mut self, surface_id: SurfaceId) {
let surface = self.surfaces.get_mut(&surface_id).unwrap();
@ -360,6 +382,7 @@ impl Server {
dh.create_global::<State, WlSeat, _>(5, ());
dh.create_global::<State, WlDataDeviceManager, _>(3, ());
dh.create_global::<State, ZwpTabletManagerV2, _>(1, ());
dh.create_global::<State, XdgActivationV1, _>(1, ());
global_noop!(ZwpLinuxDmabufV1);
global_noop!(ZwpRelativePointerManagerV1);
global_noop!(WpViewporter);
@ -495,6 +518,11 @@ impl Server {
self.display.flush_clients().unwrap();
}
#[track_caller]
pub fn get_focused(&self) -> Option<SurfaceId> {
self.state.get_focused()
}
#[track_caller]
pub fn configure_popup(&mut self, surface_id: SurfaceId) {
self.state.configure_popup(surface_id);
@ -971,7 +999,7 @@ impl Dispatch<WlDataDeviceManager, ()> for State {
impl GlobalDispatch<WlSeat, ()> for State {
fn bind(
_: &mut Self,
state: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: wayland_server::New<WlSeat>,
@ -980,6 +1008,7 @@ impl GlobalDispatch<WlSeat, ()> for State {
) {
let seat = data_init.init(resource, ());
seat.capabilities(wl_seat::Capability::Pointer | wl_seat::Capability::Keyboard);
state.seat = Some(seat);
}
}
@ -1524,3 +1553,126 @@ impl Dispatch<WlCallback, ()> for State {
unreachable!()
}
}
impl GlobalDispatch<XdgActivationV1, ()> for State {
fn bind(
state: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: wayland_server::New<XdgActivationV1>,
_: &(),
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
state.xdg_activation = Some(data_init.init(resource, ()));
}
}
impl Dispatch<XdgActivationV1, ()> for State {
fn request(
state: &mut Self,
_: &Client,
_: &XdgActivationV1,
request: <XdgActivationV1 as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut wayland_server::DataInit<'_, Self>,
) {
match request {
xdg_activation_v1::Request::Destroy => {}
xdg_activation_v1::Request::GetActivationToken { id } => {
data_init.init(id, Mutex::new(ActivationTokenData::default()));
}
xdg_activation_v1::Request::Activate { token, surface } => {
if state.valid_tokens.remove(&token) {
let surface_id = SurfaceId(surface.id().protocol_id());
state.focus_toplevel(surface_id);
}
}
_ => unreachable!(),
}
}
}
impl Dispatch<XdgActivationTokenV1, Mutex<ActivationTokenData>> for State {
fn request(
state: &mut Self,
_: &Client,
token: &XdgActivationTokenV1,
request: <XdgActivationTokenV1 as Resource>::Request,
data: &Mutex<ActivationTokenData>,
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
let mut data = data.lock().unwrap();
match request {
xdg_activation_token_v1::Request::SetSerial { serial, seat } => {
if data.constructed {
token.post_error(
xdg_activation_token_v1::Error::AlreadyUsed,
"The activation token has already been constructed",
);
return;
}
data.serial = Some((serial, seat));
}
xdg_activation_token_v1::Request::SetAppId { app_id } => {
if data.constructed {
token.post_error(
xdg_activation_token_v1::Error::AlreadyUsed,
"The activation token has already been constructed",
);
return;
}
data.app_id = Some(app_id);
}
xdg_activation_token_v1::Request::SetSurface { surface } => {
if data.constructed {
token.post_error(
xdg_activation_token_v1::Error::AlreadyUsed,
"The activation token has already been constructed",
);
return;
}
data.surface = Some(surface);
}
xdg_activation_token_v1::Request::Commit => {
if data.constructed {
token.post_error(
xdg_activation_token_v1::Error::AlreadyUsed,
"The activation token has already been constructed",
);
return;
}
data.constructed = true;
// Require a valid serial, otherwise ignore the activation.
// This matches niri's behavior: https://github.com/YaLTeR/niri/blob/5e549e13238a853f8860e29621ab6b31ee1b9ee4/src/handlers/mod.rs#L712-L723
let valid = if let (Some((serial, seat)), Some(surface_data)) = (
data.serial.take(),
data.surface.take().and_then(|surface| {
state.surfaces.get(&SurfaceId(surface.id().protocol_id()))
}),
) {
state.seat == Some(seat)
&& surface_data
.last_enter_serial
.is_some_and(|last_enter| serial >= last_enter)
} else {
false
};
let activation_token = state.token_counter.to_string();
state.token_counter += 1;
if valid {
state.valid_tokens.insert(activation_token.clone());
}
token.done(activation_token);
}
xdg_activation_token_v1::Request::Destroy => {}
_ => unreachable!(),
}
}
}