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 Dec 19, 2022
1 parent 7279a0f commit 6447075
Show file tree
Hide file tree
Showing 8 changed files with 386 additions and 9 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ gdk3x11 = {package = "gdkx11", version = "0.16", optional = true}
gdk3wayland = {package = "gdkwayland", version = "0.16", optional = true}
gtk3 = {package = "gtk", version = "0.16", optional = true}

gdk4wayland = {package = "gdk4-wayland", version = "0.5", optional = true}
gdk4wayland = {package = "gdk4-wayland", version = "0.5", optional = true, features = ["wayland_crate"]}
gdk4x11 = {package = "gdk4-x11", version = "0.5", optional = true}
gtk4 = {version = "0.5", optional = true}

Expand All @@ -44,7 +44,7 @@ tracing = {version = "0.1", optional = true}
libc = {version = "0.2", optional = true}
raw-window-handle = {version = "0.5", optional = true}
wayland-client = {version = "0.30.0-beta.10", optional = true}
wayland-protocols = {version = "0.30.0-beta.10", optional = true, features = ["unstable", "client"]}
wayland-protocols = {version = "0.30.0-beta.10", optional = true, features = ["unstable", "client", "staging"]}
wayland-backend = {version = "0.1.0-beta.10", optional = true, features = ["client_system"]}
async-std = {version = "1.12", optional = true}
tokio = {version = "1.21", features = ["fs", "io-util"], optional = true, default-features = false}
Expand Down
17 changes: 17 additions & 0 deletions src/activation_token/gtk3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use gtk3::prelude::*;
use gtk3::{gdk, glib};

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

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 => todo!(),
_ => None,
}
}
}
40 changes: 40 additions & 0 deletions src/activation_token/gtk4.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#[cfg(feature = "wayland")]
use super::wayland::WaylandActivationToken;
use gdk4wayland::prelude::WaylandSurfaceExtManual;
use gtk4::{gdk, glib, prelude::*};

#[derive(Debug)]
pub struct Gtk4ActivationToken {
pub(crate) wl_token: WaylandActivationToken,
}

#[cfg(all(feature = "gtk4_wayland", feature = "wayland"))]
impl Gtk4ActivationToken {
pub async fn from_native<N: glib::IsA<gtk4::Native>>(app_id: &str, native: &N) -> Option<Self> {
let surface = native.surface();
match surface.display().backend() {
gdk::Backend::Wayland => {
let surface = surface
.downcast_ref::<gdk4wayland::WaylandSurface>()
.unwrap();
if let Some(wl_surface) = surface.wl_surface() {
let wl_token = WaylandActivationToken::from_surface(app_id, &wl_surface)
.await
.unwrap();

Some(Self { wl_token })
} else {
None
}
}
_ => None,
}
}
}

#[cfg(feature = "wayland")]
impl From<WaylandActivationToken> for Gtk4ActivationToken {
fn from(wl_token: WaylandActivationToken) -> Self {
Self { wl_token }
}
}
119 changes: 119 additions & 0 deletions src/activation_token/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#[cfg(all(feature = "gtk3", feature = "wayland"))]
mod gtk3;
#[cfg(all(feature = "gtk3", feature = "wayland"))]
pub use self::gtk3::Gtk3ActivationToken;

#[cfg(feature = "gtk4_wayland")]
mod gtk4;
#[cfg(feature = "gtk4_wayland")]
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_wayland")]
#[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: &str,
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: &str,
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_wayland")]
// TODO Maybe name from_display.
/// Creates a [`ActivationToken`] from a [`gtk4::Native`](https://docs.gtk.org/gtk4/class.Native.html).
pub async fn from_native<N: ::gtk4::glib::IsA<::gtk4::Native>>(
app_id: &str,
native: &N,
) -> Option<Self> {
let token = Gtk4ActivationToken::from_native(app_id, native).await?;

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::Window>) -> 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_wayland")]
Self::Gtk4(activation_token) => activation_token.wl_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 => "",
}
}
}
175 changes: 175 additions & 0 deletions src/activation_token/wayland.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
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: &str, 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: &str,
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: &str,
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();
let app_id = app_id.to_owned();
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,
_data: &(),
_connhandle: &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,
(),
);
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,
_data: &(),
_connhandle: &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);
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 6447075

Please sign in to comment.