Skip to content

Commit

Permalink
Add activation_token
Browse files Browse the repository at this point in the history
Fixes: #55
  • Loading branch information
A6GibKm committed Jul 26, 2022
1 parent d0aaa90 commit 8f08328
Show file tree
Hide file tree
Showing 8 changed files with 424 additions and 22 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ tracing = {version = "0.1", optional = true}
libc = {version = "0.2.94", optional = true}
raw-window-handle = {version = "0.4", optional = true}
wayland-client = {version = "0.30.0-beta.8", optional = true}
wayland-protocols = {version = "0.30.0-beta.8", optional = true, features = ["unstable", "client"]}
wayland-protocols = {version = "0.30.0-beta.8", optional = true, features = ["unstable", "client", "staging"]}
wayland-backend = {version = "0.1.0-beta.8", optional = true, features = ["client_system"]}
async-std = {version = "1.11", optional = true}
tokio = {version = "1.17", features = ["fs", "io-util"], optional = true, default-features = false}
Expand Down
56 changes: 56 additions & 0 deletions src/activation_token/gtk3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use super::wayland::WaylandActivationToken;
use gtk3::glib::translate::ToGlibPtr;
use gtk3::prelude::*;
use gtk3::{gdk, glib};
use wayland_client::{backend::ObjectId, protocol::wl_surface::WlSurface, Proxy};
use wayland_protocols::xdg::activation::v1::client::xdg_activation_token_v1::XdgActivationTokenV1;

#[derive(Debug)]
pub struct Gtk3ActivationToken {
pub token: String,
inner: XdgActivationTokenV1,
}

impl Gtk3ActivationToken {
pub fn from_window(window: &impl glib::IsA<gdk::Window>) -> Option<Self> {
let display = window.as_ref().display();
match display.backend() {
gdk::Backend::Wayland => {
let cnx = wayland_client::Connection::connect_to_env().unwrap();
let wayland_window = window
.as_ref()
.downcast_ref::<gdk3wayland::WaylandWindow>()
.unwrap();
let surface_id = unsafe {
let ptr = gdk3wayland::ffi::gdk_wayland_window_get_wl_surface(
wayland_window.to_glib_none().0,
);

ObjectId::from_ptr(&WlSurface::interface(), ptr as *mut _).unwrap()
};
let wl_surface = WlSurface::from_id(&cnx, surface_id).unwrap();
let wl = WaylandActivationToken::from_surface(&wl_surface).ok()?;

Some(Self::from(wl))
}
// TODO Can this be implemented for X11?
_ => None,
}
}
}

impl From<WaylandActivationToken> for Gtk3ActivationToken {
fn from(mut wl_token: WaylandActivationToken) -> Self {
let token = std::mem::take(&mut wl_token.token);
// NOTE Safe unwrap, WlActivationToken has a inner set at construction.
let inner = wl_token.inner.take().unwrap();

Self { inner, token }
}
}

impl Drop for Gtk3ActivationToken {
fn drop(&mut self) {
self.inner.destroy();
}
}
18 changes: 18 additions & 0 deletions src/activation_token/gtk4.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use gtk4::{gdk, glib, prelude::*};

#[derive(Debug)]
pub struct Gtk4ActivationToken {
pub(crate) token: String,
}

impl Gtk4ActivationToken {
pub fn from_native<N: glib::IsA<gdk::Display>>(native: &N) -> Option<Self> {
match native.backend() {
gdk::Backend::Wayland => native.startup_notification_id().map(|token| Self {
token: token.to_string(),
}),
gdk::Backend::X11 => todo!(),
_ => None,
}
}
}
115 changes: 115 additions & 0 deletions src/activation_token/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#[cfg(all(feature = "gtk3", feature = "wayland"))]
mod gtk3;
#[cfg(all(feature = "gtk3", feature = "wayland"))]
pub use self::gtk3::Gtk3ActivationToken;

#[cfg(feature = "gtk4")]
mod gtk4;
#[cfg(feature = "gtk4")]
pub use self::gtk4::Gtk4ActivationToken;

#[cfg(any(feature = "wayland"))]
mod wayland;
#[cfg(feature = "wayland")]
pub use wayland::WaylandActivationToken;

use serde::{ser::Serializer, Serialize};
use zbus::zvariant::Type;

// TODO
/// See https://wayland.app/protocols/xdg-activation-v1
#[derive(Type)]
#[zvariant(signature = "s")]
#[derive(Debug)]
pub enum ActivationToken {
#[cfg(feature = "wayland")]
#[doc(hidden)]
Wayland(WaylandActivationToken),
#[cfg(feature = "gtk4")]
#[doc(hidden)]
Gtk4(Gtk4ActivationToken),
#[cfg(all(feature = "gtk3", feature = "wayland"))]
#[doc(hidden)]
Gtk3(Gtk3ActivationToken),
#[doc(hidden)]
Raw(String),
#[doc(hidden)]
None,
}

impl Serialize for ActivationToken {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}

impl Default for ActivationToken {
fn default() -> Self {
Self::None
}
}

impl ActivationToken {
#[cfg(feature = "wayland")]
/// Create an instance of [`ActivationToken`] from a Wayland surface and the
/// application's id.
pub async fn from_wayland_surface(
app_id: String,
surface: &wayland_client::protocol::wl_surface::WlSurface,
) -> Option<Self> {
let token = WaylandActivationToken::from_surface(app_id, surface).await?;

Some(Self::Wayland(token))
}

#[cfg(feature = "wayland")]
/// Create an instance of [`ActivationToken`] from a raw Wayland surface and
/// the application's id.
///
/// # Safety
///
/// Both pointers have to be valid surface and display pointers. You must
/// ensure the `display_ptr` lives longer than the returned
/// `ActivationToken`.
pub async unsafe fn from_wayland_raw(
app_id: String,
surface_ptr: *mut std::ffi::c_void,
display_ptr: *mut std::ffi::c_void,
) -> Option<Self> {
let token = WaylandActivationToken::from_raw(app_id, surface_ptr, display_ptr).await?;

Some(Self::Wayland(token))
}

#[cfg(feature = "gtk4")]
/// Creates a [`ActivationToken`] from a [`gtk4::Native`](https://docs.gtk.org/gtk4/class.Native.html).
pub fn from_native<N: ::gtk4::glib::IsA<::gtk4::gdk::Display>>(native: &N) -> Option<Self> {
let token = Gtk4ActivationToken::from_native(native)?;

Some(Self::Gtk4(token))
}

#[cfg(all(feature = "gtk3", feature = "wayland"))]
/// Creates a [`ActivationToken`] from a [`IsA<gdk3::Window>`](https://gtk-rs.org/gtk3-rs/stable/latest/docs/gdk/struct.Window.html).
pub fn from_window(window: &impl ::gtk3::glib::IsA<::gtk3::gdk::Root>) -> Option<Self> {
let token = Gtk3ActivationToken::from_window(window)?;

Some(Self::Gtk3(token))
}

pub(crate) fn as_str(&self) -> &str {
match self {
#[cfg(feature = "wayland")]
Self::Wayland(activation_token) => activation_token.token.as_str(),
#[cfg(feature = "gtk4")]
Self::Gtk4(activation_token) => activation_token.token.as_str(),
#[cfg(all(feature = "gtk3", feature = "wayland"))]
Self::Gtk3(activation_token) => activation_token.token.as_str(),
Self::Raw(string) => string.as_str(),
Self::None => "",
}
}
}
177 changes: 177 additions & 0 deletions src/activation_token/wayland.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
use wayland_backend::sys::client::Backend;
use wayland_client::{
protocol::{wl_registry, wl_surface::WlSurface},
Proxy, QueueHandle,
};
use wayland_protocols::xdg::activation::v1::client::{
xdg_activation_token_v1::{Event, XdgActivationTokenV1},
xdg_activation_v1::XdgActivationV1,
};

// Supported versions.
const XDG_ACTIVATION_V1_VERSION: u32 = 1;

#[derive(Debug, Default)]
pub struct WaylandActivationToken {
pub(crate) token: String,
wl_activation: Option<XdgActivationV1>,
wl_token: Option<XdgActivationTokenV1>,
}

impl Drop for WaylandActivationToken {
fn drop(&mut self) {
if let Some(wl_token) = self.wl_token.take() {
wl_token.destroy();
}

if let Some(wl_activation) = self.wl_activation.take() {
wl_activation.destroy();
}
}
}

impl WaylandActivationToken {
// Can be changed to display.
pub async fn from_surface(app_id: String, surface: &WlSurface) -> Option<Self> {
let backend = surface.backend().upgrade()?;
let conn = wayland_client::Connection::from_backend(backend);

Self::new_inner(app_id, conn, surface).await
}

pub async unsafe fn from_raw(
app_id: String,
surface_ptr: *mut std::ffi::c_void,
display_ptr: *mut std::ffi::c_void,
) -> Option<Self> {
if surface_ptr.is_null() || display_ptr.is_null() {
return None;
}

let backend = Backend::from_foreign_display(display_ptr as *mut _);
let conn = wayland_client::Connection::from_backend(backend);
let obj_id = wayland_backend::sys::client::ObjectId::from_ptr(
WlSurface::interface(),
surface_ptr as *mut _,
)
.ok()?;

let surface = WlSurface::from_id(&conn, obj_id).ok()?;

Self::new_inner(app_id, conn, &surface).await
}

async fn new_inner(
app_id: String,
conn: wayland_client::Connection,
surface: &WlSurface,
) -> Option<Self> {
let (sender, receiver) = futures::channel::oneshot::channel::<Option<Self>>();

// Cheap clone, protocol objects are essentially smart pointers
let surface = surface.clone();
std::thread::spawn(move || match wayland_export_token(app_id, conn, &surface) {
Ok(window_handle) => sender.send(Some(window_handle)).unwrap(),
Err(_err) => {
#[cfg(feature = "tracing")]
tracing::info!("Could not get wayland window identifier: {_err}");
sender.send(None).unwrap();
}
});

receiver.await.unwrap()
}
}

impl wayland_client::Dispatch<XdgActivationTokenV1, ()> for WaylandActivationToken {
fn event(
state: &mut Self,
_proxy: &XdgActivationTokenV1,
event: <XdgActivationTokenV1 as Proxy>::Event,
_data: &(),
_connhandle: &wayland_client::Connection,
_qhandle: &QueueHandle<Self>,
) {
if let Event::Done { token } = event {
state.token = token;
}
}
}

impl wayland_client::Dispatch<wl_registry::WlRegistry, ()> for WaylandActivationToken {
fn event(
state: &mut Self,
registry: &wl_registry::WlRegistry,
event: wl_registry::Event,
_: &(),
_: &wayland_client::Connection,
qhandle: &QueueHandle<Self>,
) {
if let wl_registry::Event::Global {
name,
interface,
version,
} = event
{
if &interface == "xdg_activation_v1" {
#[cfg(feature = "tracing")]
tracing::info!("Found wayland interface {interface} v{version}");
let activation = registry
.bind::<XdgActivationV1, (), Self>(
name,
version.min(XDG_ACTIVATION_V1_VERSION),
qhandle,
(),
)
.unwrap();
state.wl_activation = Some(activation);
}
}
}
}

impl wayland_client::Dispatch<XdgActivationV1, ()> for WaylandActivationToken {
fn event(
_state: &mut Self,
_activation: &XdgActivationV1,
_event: wayland_protocols::xdg::activation::v1::client::xdg_activation_v1::Event,
_: &(),
_: &wayland_client::Connection,
_qhandle: &QueueHandle<Self>,
) {
}
}

fn wayland_export_token(
app_id: String,
conn: wayland_client::Connection,
surface: &WlSurface,
) -> Result<WaylandActivationToken, Box<dyn std::error::Error>> {
let display = conn.display();
let mut event_queue = conn.new_event_queue();
let mut state = WaylandActivationToken::default();
let qhandle = event_queue.handle();
display.get_registry(&qhandle, ())?;
event_queue.roundtrip(&mut state)?;

if let Some(ref activation) = state.wl_activation {
let wl_token = activation.get_activation_token(&qhandle, ())?;
// TODO is this an APP ID in the traditional sense?
wl_token.set_app_id(app_id);
wl_token.set_surface(surface);
// TODO wl_token.set_serial(serial, &seat);
// Requests a token.
wl_token.commit();

event_queue.roundtrip(&mut state)?;
state.wl_token = Some(wl_token);
};

if !state.token.is_empty() {
Ok(state)
} else {
#[cfg(feature = "tracing")]
tracing::error!("Failed to get a response from the wayland server");
Err(Box::new(crate::Error::NoResponse))
}
}
Loading

0 comments on commit 8f08328

Please sign in to comment.