Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add activation token support #70

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"]}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is correct, the feature should be enabled only if gtk4_wayland is enabled.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what you mean here?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that looks correct to me

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", optional = true}
wayland-protocols = {version = "0.30", optional = true, features = ["unstable", "client"]}
wayland-protocols = {version = "0.30", optional = true, features = ["unstable", "client", "staging"]}
wayland-backend = {version = "0.1", 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
9 changes: 7 additions & 2 deletions ashpd-demo/src/portals/desktop/open_uri.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use ashpd::{desktop::open_uri, WindowIdentifier};
use ashpd::{desktop::open_uri, ActivationToken, WindowIdentifier};
use gtk::{glib, prelude::*, subclass::prelude::*};

use crate::widgets::{NotificationKind, PortalPage, PortalPageExt, PortalPageImpl};
use crate::{
config::APP_ID,
widgets::{NotificationKind, PortalPage, PortalPageExt, PortalPageImpl},
};

mod imp {
use adw::subclass::prelude::*;
Expand Down Expand Up @@ -59,11 +62,13 @@ impl OpenUriPage {
let ask = imp.ask_switch.is_active();
let root = self.native().unwrap();
let identifier = WindowIdentifier::from_native(&root).await;
let activation_token = ActivationToken::from_native(APP_ID, &root).await;
match url::Url::parse(&imp.uri_entry.text()) {
Ok(uri) => {
let request = open_uri::OpenFileRequest::default()
.ask(ask)
.writeable(writeable)
.activation_token(activation_token)
.identifier(identifier);
match request.build_uri(&uri).await {
Ok(_) => {
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 }
}
}
149 changes: 149 additions & 0 deletions src/activation_token/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#[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(Debug, Type)]
#[zvariant(signature = "s")]
pub enum ActivationToken {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add a TODO here to implement a X11 only thing.

#[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,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have plenty of places where we use the app_id so maybe we should have a type for it that wraps a String or something like that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its way more convenient for the user to just write their app_id in a &str, ideally it would be preferable to somehow get it from the wayland toplevel or gtkwindow..

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, you could make those functions take a impl Into<AppID> and impl From<&str> for AppID but as it is something that can be done in a separate PR, I will handle it then you can just rebase.

surface: &wayland_client::protocol::wl_surface::WlSurface,
) -> Self {
if let Some(token) = WaylandActivationToken::from_surface(app_id, surface).await {
Self::Wayland(token)
} else {
Self::default()
}
}

#[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,
) -> Self {
if let Some(token) =
WaylandActivationToken::from_raw(app_id, surface_ptr, display_ptr).await
{
Self::Wayland(token)
} else {
Self::default()
}
}

#[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,
) -> Self {
if let Some(token) = Gtk4ActivationToken::from_native(app_id, native).await {
Self::Gtk4(token)
} else {
Self::default()
}
}

#[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>) -> Self {
if let Some(token) = Gtk3ActivationToken::from_window(window) {
Self::Gtk3(token)
} else {
Self::default()
}
}

pub fn is_some(&self) -> bool {
!self.is_none()
}

pub fn is_none(&self) -> bool {
matches!(self, Self::None)
}

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 => "",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between None and Raw? You can make the default impl use Raw("")? :)

}
}

pub(crate) fn into_string(self) -> String {
match self {
#[cfg(feature = "wayland")]
Self::Wayland(activation_token) => activation_token.token,
#[cfg(feature = "gtk4_wayland")]
Self::Gtk4(activation_token) => activation_token.wl_token.token,
#[cfg(all(feature = "gtk3", feature = "wayland"))]
Self::Gtk3(activation_token) => activation_token.token,
Self::Raw(string) => string,
Self::None => "".into(),
}
}
}
Loading