Detailed changes
@@ -341,9 +341,8 @@ dependencies = [
[[package]]
name = "ashpd"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd884d7c72877a94102c3715f3b1cd09ff4fac28221add3e57cfbe25c236d093"
+version = "0.9.0"
+source = "git+https://github.com/bilelmoussaoui/ashpd?rev=29f2e1a#29f2e1a6f4b0911f504658f5f4630c02e01b13f2"
dependencies = [
"async-fs 2.1.1",
"async-net 2.0.0",
@@ -4893,7 +4892,6 @@ dependencies = [
"num_cpus",
"objc",
"oo7",
- "open",
"parking",
"parking_lot",
"pathfinder_geometry",
@@ -5705,25 +5703,6 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
-[[package]]
-name = "is-docker"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
-dependencies = [
- "once_cell",
-]
-
-[[package]]
-name = "is-wsl"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
-dependencies = [
- "is-docker",
- "once_cell",
-]
-
[[package]]
name = "isahc"
version = "1.7.2"
@@ -7225,17 +7204,6 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
-[[package]]
-name = "open"
-version = "5.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "449f0ff855d85ddbf1edd5b646d65249ead3f5e422aaa86b7d2d0b049b103e32"
-dependencies = [
- "is-wsl",
- "libc",
- "pathdiff",
-]
-
[[package]]
name = "open_ai"
version = "0.1.0"
@@ -272,9 +272,9 @@ zed_actions = { path = "crates/zed_actions" }
alacritty_terminal = "0.23"
any_vec = "0.13"
anyhow = "1.0.57"
-ashpd = "0.8.0"
+ashpd = { git = "https://github.com/bilelmoussaoui/ashpd", rev = "29f2e1a" }
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
-async-dispatcher = { version = "0.1"}
+async-dispatcher = { version = "0.1" }
async-fs = "1.6"
async-recursion = "1.0.0"
async-tar = "0.4.2"
@@ -315,7 +315,9 @@ image = "0.25.1"
indexmap = { version = "1.6.2", features = ["serde"] }
indoc = "1"
# We explicitly disable http2 support in isahc.
-isahc = { version = "1.7.2", default-features = false, features = [ "text-decoding" ] }
+isahc = { version = "1.7.2", default-features = false, features = [
+ "text-decoding",
+] }
itertools = "0.11.0"
lazy_static = "1.4.0"
libc = "0.2"
@@ -341,7 +343,9 @@ rand = "0.8.5"
refineable = { path = "./crates/refineable" }
regex = "1.5"
repair_json = "0.1.0"
-runtimelib = { version="0.12", default-features = false, features = ["async-dispatcher-runtime"] }
+runtimelib = { version = "0.12", default-features = false, features = [
+ "async-dispatcher-runtime",
+] }
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
rust-embed = { version = "8.4", features = ["include-exclude"] }
schemars = "0.8"
@@ -124,7 +124,6 @@ wayland-protocols = { version = "0.31.2", features = [
] }
wayland-protocols-plasma = { version = "0.2.0", features = ["client"] }
oo7 = "0.3.0"
-open = "5.1.2"
filedescriptor = "0.8.2"
x11rb = { version = "0.13.0", features = [
"allow-unsafe-code",
@@ -81,6 +81,8 @@ impl LinuxClient for HeadlessClient {
fn open_uri(&self, _uri: &str) {}
+ fn reveal_path(&self, _path: std::path::PathBuf) {}
+
fn write_to_primary(&self, _item: crate::ClipboardItem) {}
fn write_to_clipboard(&self, _item: crate::ClipboardItem) {}
@@ -7,7 +7,7 @@ use std::ffi::OsString;
use std::fs::File;
use std::io::Read;
use std::ops::{Deref, DerefMut};
-use std::os::fd::{AsRawFd, FromRawFd};
+use std::os::fd::{AsFd, AsRawFd, FromRawFd};
use std::panic::Location;
use std::rc::Weak;
use std::{
@@ -20,6 +20,8 @@ use std::{
use anyhow::anyhow;
use ashpd::desktop::file_chooser::{OpenFileRequest, SaveFileRequest};
+use ashpd::desktop::open_uri::{OpenDirectoryRequest, OpenFileRequest as OpenUriRequest};
+use ashpd::{url, ActivationToken};
use async_task::Runnable;
use calloop::channel::Channel;
use calloop::{EventLoop, LoopHandle, LoopSignal};
@@ -67,6 +69,7 @@ pub trait LinuxClient {
) -> anyhow::Result<Box<dyn PlatformWindow>>;
fn set_cursor_style(&self, style: CursorStyle);
fn open_uri(&self, uri: &str);
+ fn reveal_path(&self, path: PathBuf);
fn write_to_primary(&self, item: ClipboardItem);
fn write_to_clipboard(&self, item: ClipboardItem);
fn read_from_primary(&self) -> Option<ClipboardItem>;
@@ -344,13 +347,7 @@ impl<P: LinuxClient + 'static> Platform for P {
}
fn reveal_path(&self, path: &Path) {
- if path.is_dir() {
- open::that_detached(path);
- return;
- }
- // If `path` is a file, the system may try to open it in a text editor
- let dir = path.parent().unwrap_or(Path::new(""));
- open::that_detached(dir);
+ self.reveal_path(path.to_owned());
}
fn on_quit(&self, callback: Box<dyn FnMut()>) {
@@ -511,18 +508,40 @@ impl<P: LinuxClient + 'static> Platform for P {
fn add_recent_document(&self, _path: &Path) {}
}
-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.spawn() {
- Ok(_) => return,
- Err(err) => last_err = Some(err),
- }
+pub(super) fn open_uri_internal(
+ executor: BackgroundExecutor,
+ uri: &str,
+ activation_token: Option<String>,
+) {
+ if let Some(uri) = url::Url::parse(uri).log_err() {
+ executor
+ .spawn(async move {
+ OpenUriRequest::default()
+ .activation_token(activation_token.map(ActivationToken::from))
+ .send_uri(&uri)
+ .await
+ .log_err();
+ })
+ .detach();
}
- log::error!("failed to open uri: {uri:?}, last error: {last_err:?}");
+}
+
+pub(super) fn reveal_path_internal(
+ executor: BackgroundExecutor,
+ path: PathBuf,
+ activation_token: Option<String>,
+) {
+ executor
+ .spawn(async move {
+ if let Some(dir) = File::open(path).log_err() {
+ OpenDirectoryRequest::default()
+ .activation_token(activation_token.map(ActivationToken::from))
+ .send(&dir.as_fd())
+ .await
+ .log_err();
+ }
+ })
+ .detach();
}
pub(super) fn is_within_click_distance(a: Point<Pixels>, b: Point<Pixels>) -> bool {
@@ -61,7 +61,6 @@ use wayland_protocols_plasma::blur::client::{org_kde_kwin_blur, org_kde_kwin_blu
use xkbcommon::xkb::ffi::XKB_KEYMAP_FORMAT_TEXT_V1;
use xkbcommon::xkb::{self, Keycode, KEYMAP_COMPILE_NO_FLAGS};
-use super::super::{open_uri_internal, read_fd, DOUBLE_CLICK_INTERVAL};
use super::display::WaylandDisplay;
use super::window::{ImeInput, WaylandWindowStatePtr};
use crate::platform::linux::wayland::clipboard::{
@@ -72,11 +71,14 @@ use crate::platform::linux::wayland::serial::{SerialKind, SerialTracker};
use crate::platform::linux::wayland::window::WaylandWindow;
use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource};
use crate::platform::linux::LinuxClient;
-use crate::platform::linux::{get_xkb_compose_state, is_within_click_distance};
+use crate::platform::linux::{
+ get_xkb_compose_state, is_within_click_distance, open_uri_internal, read_fd,
+ reveal_path_internal,
+};
use crate::platform::PlatformWindow;
use crate::{
point, px, size, Bounds, DevicePixels, FileDropEvent, ForegroundExecutor, MouseExitEvent, Size,
- SCROLL_LINES,
+ DOUBLE_CLICK_INTERVAL, SCROLL_LINES,
};
use crate::{
AnyWindowHandle, CursorStyle, DisplayId, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers,
@@ -220,7 +222,7 @@ pub(crate) struct WaylandClientState {
data_offers: Vec<DataOffer<WlDataOffer>>,
primary_data_offer: Option<DataOffer<ZwpPrimarySelectionOfferV1>>,
cursor: Cursor,
- pending_open_uri: Option<String>,
+ pending_activation: Option<PendingActivation>,
event_loop: Option<EventLoop<'static, WaylandClientStatePtr>>,
common: LinuxCommon,
}
@@ -244,6 +246,15 @@ pub(crate) struct KeyRepeat {
current_keycode: Option<xkb::Keycode>,
}
+pub(crate) enum PendingActivation {
+ /// URI to open in the web browser.
+ Uri(String),
+ /// Path to open in the file explorer.
+ Path(PathBuf),
+ /// A window from ourselves to raise.
+ Window(ObjectId),
+}
+
/// This struct is required to conform to Rust's orphan rules, so we can dispatch on the state but hand the
/// window to GPUI.
#[derive(Clone)]
@@ -260,6 +271,11 @@ impl WaylandClientStatePtr {
self.0.upgrade().unwrap().borrow().serial_tracker.get(kind)
}
+ pub fn set_pending_activation(&self, window: ObjectId) {
+ self.0.upgrade().unwrap().borrow_mut().pending_activation =
+ Some(PendingActivation::Window(window));
+ }
+
pub fn enable_ime(&self) {
let client = self.get_client();
let mut state = client.borrow_mut();
@@ -530,7 +546,7 @@ impl WaylandClient {
data_offers: Vec::new(),
primary_data_offer: None,
cursor,
- pending_open_uri: None,
+ pending_activation: None,
event_loop: Some(event_loop),
}));
@@ -629,14 +645,33 @@ impl LinuxClient for WaylandClient {
state.globals.activation.clone(),
state.mouse_focused_window.clone(),
) {
- state.pending_open_uri = Some(uri.to_owned());
+ state.pending_activation = Some(PendingActivation::Uri(uri.to_string()));
let token = activation.get_activation_token(&state.globals.qh, ());
let serial = state.serial_tracker.get(SerialKind::MousePress);
token.set_serial(serial, &state.wl_seat);
token.set_surface(&window.surface());
token.commit();
} else {
- open_uri_internal(uri, None);
+ let executor = state.common.background_executor.clone();
+ open_uri_internal(executor, uri, None);
+ }
+ }
+
+ fn reveal_path(&self, path: PathBuf) {
+ let mut state = self.0.borrow_mut();
+ if let (Some(activation), Some(window)) = (
+ state.globals.activation.clone(),
+ state.mouse_focused_window.clone(),
+ ) {
+ state.pending_activation = Some(PendingActivation::Path(path));
+ let token = activation.get_activation_token(&state.globals.qh, ());
+ let serial = state.serial_tracker.get(SerialKind::MousePress);
+ token.set_serial(serial, &state.wl_seat);
+ token.set_surface(&window.surface());
+ token.commit();
+ } else {
+ let executor = state.common.background_executor.clone();
+ reveal_path_internal(executor, path, None);
}
}
@@ -954,13 +989,25 @@ impl Dispatch<xdg_activation_token_v1::XdgActivationTokenV1, ()> for WaylandClie
) {
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");
+ let executor = state.common.background_executor.clone();
+ match state.pending_activation.take() {
+ Some(PendingActivation::Uri(uri)) => open_uri_internal(executor, &uri, Some(token)),
+ Some(PendingActivation::Path(path)) => {
+ reveal_path_internal(executor, path, Some(token))
+ }
+ Some(PendingActivation::Window(window)) => {
+ let Some(window) = get_window(&mut state, &window) else {
+ return;
+ };
+ let activation = state.globals.activation.as_ref().unwrap();
+ activation.activate(token, &window.surface());
+ }
+ None => log::error!("activation token received with no pending activation"),
}
}
+
token.destroy();
}
}
@@ -76,6 +76,7 @@ pub struct WaylandWindowState {
acknowledged_first_configure: bool,
pub surface: wl_surface::WlSurface,
decoration: Option<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1>,
+ app_id: Option<String>,
appearance: WindowAppearance,
blur: Option<org_kde_kwin_blur::OrgKdeKwinBlur>,
toplevel: xdg_toplevel::XdgToplevel,
@@ -158,6 +159,7 @@ impl WaylandWindowState {
acknowledged_first_configure: false,
surface,
decoration,
+ app_id: None,
blur: None,
toplevel,
viewport,
@@ -823,7 +825,20 @@ impl PlatformWindow for WaylandWindow {
}
fn activate(&self) {
- log::info!("Wayland does not support this API");
+ // Try to request an activation token. Even though the activation is likely going to be rejected,
+ // KWin and Mutter can use the app_id to visually indicate we're requesting attention.
+ let state = self.borrow();
+ if let (Some(activation), Some(app_id)) = (&state.globals.activation, state.app_id.clone())
+ {
+ state.client.set_pending_activation(state.surface.id());
+ let token = activation.get_activation_token(&state.globals.qh, ());
+ // The serial isn't exactly important here, since the activation is probably going to be rejected anyway.
+ let serial = state.client.get_serial(SerialKind::MousePress);
+ token.set_app_id(app_id);
+ token.set_serial(serial, &state.globals.seat);
+ token.set_surface(&state.surface);
+ token.commit();
+ }
}
fn is_active(&self) -> bool {
@@ -835,7 +850,9 @@ impl PlatformWindow for WaylandWindow {
}
fn set_app_id(&mut self, app_id: &str) {
- self.borrow().toplevel.set_app_id(app_id.to_owned());
+ let mut state = self.borrow_mut();
+ state.toplevel.set_app_id(app_id.to_owned());
+ state.app_id = Some(app_id.to_owned());
}
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
@@ -2,6 +2,7 @@ use std::cell::RefCell;
use std::collections::HashSet;
use std::ops::Deref;
use std::os::fd::AsRawFd;
+use std::path::PathBuf;
use std::rc::{Rc, Weak};
use std::sync::Arc;
use std::time::{Duration, Instant};
@@ -33,19 +34,18 @@ use crate::platform::linux::LinuxClient;
use crate::platform::{LinuxCommon, PlatformWindow};
use crate::{
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, ClipboardItem, CursorStyle,
- DisplayId, Keystroke, Modifiers, ModifiersChangedEvent, Pixels, PlatformDisplay, PlatformInput,
- Point, QuitSignal, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
+ DisplayId, Keystroke, Modifiers, ModifiersChangedEvent, Pixels, Platform, PlatformDisplay,
+ PlatformInput, Point, QuitSignal, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
};
-use super::{
- super::{get_xkb_compose_state, open_uri_internal, SCROLL_LINES},
- X11Display, X11WindowStatePtr, XcbAtoms,
-};
use super::{button_of_key, modifiers_from_state, pressed_button_from_mask};
+use super::{X11Display, X11WindowStatePtr, XcbAtoms};
use super::{XimCallbackEvent, XimHandler};
-use crate::platform::linux::is_within_click_distance;
-use crate::platform::linux::platform::DOUBLE_CLICK_INTERVAL;
+use crate::platform::linux::platform::{DOUBLE_CLICK_INTERVAL, SCROLL_LINES};
use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource};
+use crate::platform::linux::{
+ get_xkb_compose_state, is_within_click_distance, open_uri_internal, reveal_path_internal,
+};
pub(super) const XINPUT_MASTER_DEVICE: u16 = 1;
@@ -1100,7 +1100,11 @@ impl LinuxClient for X11Client {
}
fn open_uri(&self, uri: &str) {
- open_uri_internal(uri, None);
+ open_uri_internal(self.background_executor(), uri, None);
+ }
+
+ fn reveal_path(&self, path: PathBuf) {
+ reveal_path_internal(self.background_executor(), path, None);
}
fn write_to_primary(&self, item: crate::ClipboardItem) {