From 86696d88cf9a4367f2a67bc2086b5e944eb7412e Mon Sep 17 00:00:00 2001 From: apricotbucket28 <71973804+apricotbucket28@users.noreply.github.com> Date: Fri, 3 May 2024 18:02:39 -0300 Subject: [PATCH] wayland: Implement xdg-activation when opening urls (#11368) Since Wayland doesn't have a way for windows to activate themselves, currently, when you click on a link in Zed, the browser window opens in the background. This PR implements the `xdg-activation` protocol to get an activation token, which the browser can use to raise its window. https://github.com/zed-industries/zed/assets/71973804/8b3456c0-89f8-4201-b1cb-633a149796b7 Release Notes: - N/A --- .../src/platform/linux/headless/client.rs | 3 +- crates/gpui/src/platform/linux/platform.rs | 17 ++++++- .../gpui/src/platform/linux/wayland/client.rs | 51 ++++++++++++++++++- .../gpui/src/platform/linux/wayland/window.rs | 4 ++ crates/gpui/src/platform/linux/x11/client.rs | 9 +++- 5 files changed, 79 insertions(+), 5 deletions(-) diff --git a/crates/gpui/src/platform/linux/headless/client.rs b/crates/gpui/src/platform/linux/headless/client.rs index 578c4a6c3b..c02646ae46 100644 --- a/crates/gpui/src/platform/linux/headless/client.rs +++ b/crates/gpui/src/platform/linux/headless/client.rs @@ -76,9 +76,10 @@ impl LinuxClient for HeadlessClient { unimplemented!() } - //todo(linux) fn set_cursor_style(&self, _style: CursorStyle) {} + fn open_uri(&self, _uri: &str) {} + fn write_to_primary(&self, item: crate::ClipboardItem) {} fn write_to_clipboard(&self, item: crate::ClipboardItem) {} diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 221674683e..99d77c76a1 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -61,6 +61,7 @@ pub trait LinuxClient { options: WindowParams, ) -> Box; fn set_cursor_style(&self, style: CursorStyle); + fn open_uri(&self, uri: &str); fn write_to_primary(&self, item: ClipboardItem); fn write_to_clipboard(&self, item: ClipboardItem); fn read_from_primary(&self) -> Option; @@ -216,7 +217,7 @@ impl Platform for P { } fn open_url(&self, url: &str) { - open::that(url); + self.open_uri(url); } fn on_open_urls(&self, callback: Box)>) { @@ -484,6 +485,20 @@ impl Platform for P { } } +pub(super) fn open_uri_internal(uri: &str, activation_token: Option<&str>) { + let mut last_err = None; + for mut command in open::commands(uri) { + if let Some(token) = activation_token { + command.env("XDG_ACTIVATION_TOKEN", token); + } + match command.status() { + Ok(_) => return, + Err(err) => last_err = Some(err), + } + } + log::error!("failed to open uri: {uri:?}, last error: {last_err:?}"); +} + pub(super) fn is_within_click_distance(a: Point, b: Point) -> bool { let diff = a - b; diff.x.abs() <= DOUBLE_CLICK_DISTANCE && diff.y.abs() <= DOUBLE_CLICK_DISTANCE diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index f2ab74bfff..50c2935a7b 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -42,6 +42,7 @@ use wayland_protocols::wp::fractional_scale::v1::client::{ wp_fractional_scale_manager_v1, wp_fractional_scale_v1, }; use wayland_protocols::wp::viewporter::client::{wp_viewport, wp_viewporter}; +use wayland_protocols::xdg::activation::v1::client::{xdg_activation_token_v1, xdg_activation_v1}; use wayland_protocols::xdg::decoration::zv1::client::{ zxdg_decoration_manager_v1, zxdg_toplevel_decoration_v1, }; @@ -49,7 +50,7 @@ use wayland_protocols::xdg::shell::client::{xdg_surface, xdg_toplevel, xdg_wm_ba use xkbcommon::xkb::ffi::XKB_KEYMAP_FORMAT_TEXT_V1; use xkbcommon::xkb::{self, Keycode, KEYMAP_COMPILE_NO_FLAGS}; -use super::super::{read_fd, DOUBLE_CLICK_INTERVAL}; +use super::super::{open_uri_internal, read_fd, DOUBLE_CLICK_INTERVAL}; use super::window::{WaylandWindowState, WaylandWindowStatePtr}; use crate::platform::linux::is_within_click_distance; use crate::platform::linux::wayland::cursor::Cursor; @@ -71,6 +72,7 @@ const MIN_KEYCODE: u32 = 8; #[derive(Clone)] pub struct Globals { pub qh: QueueHandle, + pub activation: Option, pub compositor: wl_compositor::WlCompositor, pub cursor_shape_manager: Option, pub data_device_manager: Option, @@ -90,6 +92,7 @@ impl Globals { qh: QueueHandle, ) -> Self { Globals { + activation: globals.bind(&qh, 1..=1, ()).ok(), compositor: globals .bind( &qh, @@ -121,6 +124,7 @@ pub(crate) struct WaylandClientState { serial: u32, // todo(linux): storing a general serial is wrong pointer_serial: u32, globals: Globals, + wl_seat: wl_seat::WlSeat, // todo(linux): multi-seat support wl_pointer: Option, cursor_shape_device: Option, data_device: Option, @@ -151,6 +155,8 @@ pub(crate) struct WaylandClientState { primary: Option, event_loop: Option>, common: LinuxCommon, + + pending_open_uri: Option, } pub struct DragState { @@ -259,7 +265,6 @@ impl WaylandClient { for global in list { match &global.interface[..] { "wl_seat" => { - // TODO: multi-seat support seat = Some(globals.registry().bind::( global.name, wl_seat_version(global.version), @@ -310,6 +315,7 @@ impl WaylandClient { serial: 0, pointer_serial: 0, globals, + wl_seat: seat, wl_pointer: None, cursor_shape_device: None, data_device, @@ -357,6 +363,8 @@ impl WaylandClient { clipboard: Some(clipboard), primary: Some(primary), event_loop: Some(event_loop), + + pending_open_uri: None, })); WaylandSource::new(conn, event_queue).insert(handle); @@ -421,6 +429,22 @@ impl LinuxClient for WaylandClient { } } + fn open_uri(&self, uri: &str) { + let mut state = self.0.borrow_mut(); + if let (Some(activation), Some(window)) = ( + state.globals.activation.clone(), + state.mouse_focused_window.clone(), + ) { + state.pending_open_uri = Some(uri.to_owned()); + let token = activation.get_activation_token(&state.globals.qh, ()); + token.set_serial(state.serial, &state.wl_seat); + token.set_surface(&window.surface()); + token.commit(); + } else { + open_uri_internal(uri, None); + } + } + fn with_common(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R { f(&mut self.0.borrow_mut().common) } @@ -525,6 +549,7 @@ impl Dispatch for WaylandClientStat } } +delegate_noop!(WaylandClientStatePtr: ignore xdg_activation_v1::XdgActivationV1); delegate_noop!(WaylandClientStatePtr: ignore wl_compositor::WlCompositor); delegate_noop!(WaylandClientStatePtr: ignore wp_cursor_shape_device_v1::WpCursorShapeDeviceV1); delegate_noop!(WaylandClientStatePtr: ignore wp_cursor_shape_manager_v1::WpCursorShapeManagerV1); @@ -677,6 +702,28 @@ impl Dispatch for WaylandClientStatePtr { } } +impl Dispatch for WaylandClientStatePtr { + fn event( + this: &mut Self, + token: &xdg_activation_token_v1::XdgActivationTokenV1, + event: ::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + let client = this.get_client(); + let mut state = client.borrow_mut(); + if let xdg_activation_token_v1::Event::Done { token } = event { + if let Some(uri) = state.pending_open_uri.take() { + open_uri_internal(&uri, Some(&token)); + } else { + log::error!("called while pending_open_uri is None"); + } + } + token.destroy(); + } +} + impl Dispatch for WaylandClientStatePtr { fn event( state: &mut Self, diff --git a/crates/gpui/src/platform/linux/wayland/window.rs b/crates/gpui/src/platform/linux/wayland/window.rs index af1b226fe5..da312459a5 100644 --- a/crates/gpui/src/platform/linux/wayland/window.rs +++ b/crates/gpui/src/platform/linux/wayland/window.rs @@ -251,6 +251,10 @@ impl WaylandWindow { } impl WaylandWindowStatePtr { + pub fn surface(&self) -> wl_surface::WlSurface { + self.state.borrow().surface.clone() + } + pub fn ptr_eq(&self, other: &Self) -> bool { Rc::ptr_eq(&self.state, &other.state) } diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index 90fc094a49..4d8c2b0520 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -29,7 +29,10 @@ use crate::{ Size, TouchPhase, WindowParams, X11Window, }; -use super::{super::SCROLL_LINES, X11Display, X11WindowStatePtr, XcbAtoms}; +use super::{ + super::{open_uri_internal, SCROLL_LINES}, + X11Display, X11WindowStatePtr, XcbAtoms, +}; use super::{button_of_key, modifiers_from_state}; use crate::platform::linux::is_within_click_distance; use crate::platform::linux::platform::DOUBLE_CLICK_INTERVAL; @@ -672,6 +675,10 @@ impl LinuxClient for X11Client { //todo(linux) fn set_cursor_style(&self, _style: CursorStyle) {} + fn open_uri(&self, uri: &str) { + open_uri_internal(uri, None); + } + fn write_to_primary(&self, item: crate::ClipboardItem) { self.0.borrow_mut().primary.set_contents(item.text); }