crates/gpui/src/platform/linux.rs 🔗
@@ -6,6 +6,7 @@ mod headless;
mod platform;
mod wayland;
mod x11;
+mod xdg_desktop_portal;
pub(crate) use dispatcher::*;
pub(crate) use headless::*;
citorva created
The method has been tested on:
- Gnome 46 (Working)
- Gnome 40 (Not supported)
Tasks
- [x] Implements a draft which get and provides the user theme to
components which needs it
- [x] Implements a way to call the callback function when the theme is
updated
- [X] Cleans the code
Release notes:
- N/A
crates/gpui/src/platform/linux.rs | 1
crates/gpui/src/platform/linux/platform.rs | 18 +
crates/gpui/src/platform/linux/wayland/client.rs | 24 ++
crates/gpui/src/platform/linux/wayland/window.rs | 22 +
crates/gpui/src/platform/linux/x11/client.rs | 21 +
crates/gpui/src/platform/linux/x11/window.rs | 19 +
crates/gpui/src/platform/linux/xdg_desktop_portal.rs | 133 ++++++++++++++
7 files changed, 224 insertions(+), 14 deletions(-)
@@ -6,6 +6,7 @@ mod headless;
mod platform;
mod wayland;
mod x11;
+mod xdg_desktop_portal;
pub(crate) use dispatcher::*;
pub(crate) use headless::*;
@@ -8,6 +8,7 @@ use std::io::Read;
use std::ops::{Deref, DerefMut};
use std::os::fd::{AsRawFd, FromRawFd};
use std::panic::Location;
+use std::rc::Weak;
use std::{
path::{Path, PathBuf},
process::Command,
@@ -27,11 +28,13 @@ use flume::{Receiver, Sender};
use futures::channel::oneshot;
use parking_lot::Mutex;
use time::UtcOffset;
+use util::ResultExt;
use wayland_client::Connection;
use wayland_protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::Shape;
use xkbcommon::xkb::{self, Keycode, Keysym, State};
use crate::platform::linux::wayland::WaylandClient;
+use crate::platform::linux::xdg_desktop_portal::window_appearance;
use crate::{
px, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CosmicTextSystem, CursorStyle,
DisplayId, ForegroundExecutor, Keymap, Keystroke, LinuxDispatcher, Menu, MenuItem, Modifiers,
@@ -86,6 +89,7 @@ pub(crate) struct LinuxCommon {
pub(crate) background_executor: BackgroundExecutor,
pub(crate) foreground_executor: ForegroundExecutor,
pub(crate) text_system: Arc<CosmicTextSystem>,
+ pub(crate) appearance: WindowAppearance,
pub(crate) callbacks: PlatformHandlers,
pub(crate) signal: LoopSignal,
}
@@ -96,12 +100,18 @@ impl LinuxCommon {
let text_system = Arc::new(CosmicTextSystem::new());
let callbacks = PlatformHandlers::default();
- let dispatcher = Arc::new(LinuxDispatcher::new(main_sender));
+ let dispatcher = Arc::new(LinuxDispatcher::new(main_sender.clone()));
+
+ let background_executor = BackgroundExecutor::new(dispatcher.clone());
+ let appearance = window_appearance(&background_executor)
+ .log_err()
+ .unwrap_or(WindowAppearance::Light);
let common = LinuxCommon {
- background_executor: BackgroundExecutor::new(dispatcher.clone()),
+ background_executor,
foreground_executor: ForegroundExecutor::new(dispatcher.clone()),
text_system,
+ appearance,
callbacks,
signal,
};
@@ -462,8 +472,8 @@ impl<P: LinuxClient + 'static> Platform for P {
})
}
- fn window_appearance(&self) -> crate::WindowAppearance {
- crate::WindowAppearance::Light
+ fn window_appearance(&self) -> WindowAppearance {
+ self.with_common(|common| common.appearance)
}
fn register_url_scheme(&self, _: &str) -> Task<anyhow::Result<()>> {
@@ -1,6 +1,7 @@
use core::hash;
use std::cell::{RefCell, RefMut};
use std::ffi::OsString;
+use std::ops::{Deref, DerefMut};
use std::os::fd::{AsRawFd, BorrowedFd};
use std::path::PathBuf;
use std::rc::{Rc, Weak};
@@ -15,6 +16,7 @@ use collections::HashMap;
use copypasta::wayland_clipboard::{create_clipboards_from_external, Clipboard, Primary};
use copypasta::ClipboardProvider;
use filedescriptor::Pipe;
+use parking_lot::Mutex;
use smallvec::SmallVec;
use util::ResultExt;
use wayland_backend::client::ObjectId;
@@ -65,9 +67,12 @@ use crate::platform::linux::is_within_click_distance;
use crate::platform::linux::wayland::cursor::Cursor;
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::PlatformWindow;
-use crate::{point, px, FileDropEvent, ForegroundExecutor, MouseExitEvent, SCROLL_LINES};
+use crate::{
+ point, px, FileDropEvent, ForegroundExecutor, MouseExitEvent, WindowAppearance, SCROLL_LINES,
+};
use crate::{
AnyWindowHandle, CursorStyle, DisplayId, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
@@ -342,6 +347,22 @@ impl WaylandClient {
let cursor = Cursor::new(&conn, &globals, 24);
+ handle.insert_source(XDPEventSource::new(&common.background_executor), {
+ move |event, _, client| match event {
+ XDPEvent::WindowAppearance(appearance) => {
+ if let Some(client) = client.0.upgrade() {
+ let mut client = client.borrow_mut();
+
+ client.common.appearance = appearance;
+
+ for (_, window) in &mut client.windows {
+ window.set_appearance(appearance);
+ }
+ }
+ }
+ }
+ });
+
let mut state = Rc::new(RefCell::new(WaylandClientState {
serial_tracker: SerialTracker::new(),
globals,
@@ -430,6 +451,7 @@ impl LinuxClient for WaylandClient {
state.globals.clone(),
WaylandClientStatePtr(Rc::downgrade(&self.0)),
params,
+ state.common.appearance,
);
state.windows.insert(surface_id, window.0.clone());
@@ -2,7 +2,7 @@ use std::any::Any;
use std::cell::{Ref, RefCell, RefMut};
use std::ffi::c_void;
use std::num::NonZeroU32;
-use std::ops::Range;
+use std::ops::{Deref, Range};
use std::ptr::NonNull;
use std::rc::{Rc, Weak};
use std::sync::Arc;
@@ -10,6 +10,7 @@ use std::sync::Arc;
use blade_graphics as gpu;
use collections::{HashMap, HashSet};
use futures::channel::oneshot::Receiver;
+use parking_lot::Mutex;
use raw_window_handle as rwh;
use wayland_backend::client::ObjectId;
use wayland_client::protocol::wl_region::WlRegion;
@@ -70,6 +71,7 @@ pub struct WaylandWindowState {
acknowledged_first_configure: bool,
pub surface: wl_surface::WlSurface,
decoration: Option<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1>,
+ appearance: WindowAppearance,
blur: Option<org_kde_kwin_blur::OrgKdeKwinBlur>,
toplevel: xdg_toplevel::XdgToplevel,
viewport: Option<wp_viewport::WpViewport>,
@@ -100,6 +102,7 @@ impl WaylandWindowState {
xdg_surface: xdg_surface::XdgSurface,
toplevel: xdg_toplevel::XdgToplevel,
decoration: Option<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1>,
+ appearance: WindowAppearance,
viewport: Option<wp_viewport::WpViewport>,
client: WaylandClientStatePtr,
globals: Globals,
@@ -158,6 +161,7 @@ impl WaylandWindowState {
maximized: false,
callbacks: Callbacks::default(),
client,
+ appearance,
}
}
}
@@ -215,6 +219,7 @@ impl WaylandWindow {
globals: Globals,
client: WaylandClientStatePtr,
params: WindowParams,
+ appearance: WindowAppearance,
) -> (Self, ObjectId) {
let surface = globals.compositor.create_surface(&globals.qh, ());
let xdg_surface = globals
@@ -251,6 +256,7 @@ impl WaylandWindow {
xdg_surface,
toplevel,
decoration,
+ appearance,
viewport,
client,
globals,
@@ -571,6 +577,15 @@ impl WaylandWindowStatePtr {
fun(focus);
}
}
+
+ pub fn set_appearance(&mut self, appearance: WindowAppearance) {
+ self.state.borrow_mut().appearance = appearance;
+
+ let mut callbacks = self.callbacks.borrow_mut();
+ if let Some(ref mut fun) = callbacks.appearance_changed {
+ (fun)()
+ }
+ }
}
impl rwh::HasWindowHandle for WaylandWindow {
@@ -618,9 +633,8 @@ impl PlatformWindow for WaylandWindow {
self.borrow().scale
}
- // todo(linux)
fn appearance(&self) -> WindowAppearance {
- WindowAppearance::Light
+ self.borrow().appearance
}
// todo(linux)
@@ -777,7 +791,7 @@ impl PlatformWindow for WaylandWindow {
}
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
- // todo(linux)
+ self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
}
fn draw(&self, scene: &Scene) {
@@ -2,6 +2,7 @@ use std::cell::RefCell;
use std::ffi::OsString;
use std::ops::Deref;
use std::rc::{Rc, Weak};
+use std::sync::OnceLock;
use std::time::{Duration, Instant};
use calloop::generic::{FdWrapper, Generic};
@@ -10,6 +11,7 @@ use calloop::{channel, EventLoop, LoopHandle, RegistrationToken};
use collections::HashMap;
use copypasta::x11_clipboard::{Clipboard, Primary, X11ClipboardContext};
use copypasta::ClipboardProvider;
+use parking_lot::Mutex;
use util::ResultExt;
use x11rb::connection::{Connection, RequestConnection};
@@ -27,11 +29,11 @@ use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSIO
use xkbcommon::xkb as xkbc;
use crate::platform::linux::LinuxClient;
-use crate::platform::{LinuxCommon, PlatformWindow};
+use crate::platform::{LinuxCommon, PlatformWindow, WaylandClientState};
use crate::{
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, CursorStyle, DisplayId,
- Keystroke, Modifiers, ModifiersChangedEvent, Pixels, PlatformDisplay, PlatformInput, Point,
- ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
+ ForegroundExecutor, Keystroke, Modifiers, ModifiersChangedEvent, Pixels, PlatformDisplay,
+ PlatformInput, Point, ScrollDelta, Size, TouchPhase, WindowAppearance, WindowParams, X11Window,
};
use super::{
@@ -42,6 +44,7 @@ use super::{button_of_key, modifiers_from_state, pressed_button_from_mask};
use super::{XimCallbackEvent, XimHandler};
use crate::platform::linux::is_within_click_distance;
use crate::platform::linux::platform::DOUBLE_CLICK_INTERVAL;
+use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource};
pub(super) const XINPUT_MASTER_DEVICE: u16 = 1;
@@ -358,6 +361,17 @@ impl X11Client {
}
})
.expect("Failed to initialize XIM event source");
+ handle.insert_source(XDPEventSource::new(&common.background_executor), {
+ move |event, _, client| match event {
+ XDPEvent::WindowAppearance(appearance) => {
+ client.with_common(|common| common.appearance = appearance);
+ for (_, window) in &mut client.0.borrow_mut().windows {
+ window.window.set_appearance(appearance);
+ }
+ }
+ }
+ });
+
X11Client(Rc::new(RefCell::new(X11ClientState {
event_loop: Some(event_loop),
loop_handle: handle,
@@ -824,6 +838,7 @@ impl LinuxClient for X11Client {
x_window,
&state.atoms,
state.scale_factor,
+ state.common.appearance,
);
let screen_resources = state
@@ -28,6 +28,8 @@ use x11rb::{
xcb_ffi::XCBConnection,
};
+use std::ops::Deref;
+use std::rc::Weak;
use std::{
cell::{Ref, RefCell, RefMut},
collections::HashMap,
@@ -174,6 +176,7 @@ pub(crate) struct X11WindowState {
renderer: BladeRenderer,
display: Rc<dyn PlatformDisplay>,
input_handler: Option<PlatformInputHandler>,
+ appearance: WindowAppearance,
}
#[derive(Clone)]
@@ -223,6 +226,7 @@ impl X11WindowState {
x_window: xproto::Window,
atoms: &XcbAtoms,
scale_factor: f32,
+ appearance: WindowAppearance,
) -> Self {
let x_screen_index = params
.display_id
@@ -375,6 +379,7 @@ impl X11WindowState {
renderer: BladeRenderer::new(gpu, config),
atoms: *atoms,
input_handler: None,
+ appearance,
}
}
@@ -431,6 +436,7 @@ impl X11Window {
x_window: xproto::Window,
atoms: &XcbAtoms,
scale_factor: f32,
+ appearance: WindowAppearance,
) -> Self {
Self(X11WindowStatePtr {
state: Rc::new(RefCell::new(X11WindowState::new(
@@ -442,6 +448,7 @@ impl X11Window {
x_window,
atoms,
scale_factor,
+ appearance,
))),
callbacks: Rc::new(RefCell::new(Callbacks::default())),
xcb_connection: xcb_connection.clone(),
@@ -622,6 +629,15 @@ impl X11WindowStatePtr {
fun(focus);
}
}
+
+ pub fn set_appearance(&mut self, appearance: WindowAppearance) {
+ self.state.borrow_mut().appearance = appearance;
+
+ let mut callbacks = self.callbacks.borrow_mut();
+ if let Some(ref mut fun) = callbacks.appearance_changed {
+ (fun)()
+ }
+ }
}
impl PlatformWindow for X11Window {
@@ -656,9 +672,8 @@ impl PlatformWindow for X11Window {
self.0.state.borrow().scale_factor
}
- // todo(linux)
fn appearance(&self) -> WindowAppearance {
- WindowAppearance::Light
+ self.0.state.borrow().appearance
}
fn display(&self) -> Rc<dyn PlatformDisplay> {
@@ -0,0 +1,133 @@
+//! Provides a [calloop] event source from [XDG Desktop Portal] events
+//!
+//! This module uses the [ashpd] crate and handles many async loop
+use std::future::Future;
+
+use ashpd::desktop::settings::{ColorScheme, Settings};
+use calloop::channel::{Channel, Sender};
+use calloop::{EventSource, Poll, PostAction, Readiness, Token, TokenFactory};
+use parking_lot::Mutex;
+use smol::stream::StreamExt;
+use util::ResultExt;
+
+use crate::{BackgroundExecutor, WindowAppearance};
+
+pub enum Event {
+ WindowAppearance(WindowAppearance),
+}
+
+pub struct XDPEventSource {
+ channel: Channel<Event>,
+}
+
+impl XDPEventSource {
+ pub fn new(executor: &BackgroundExecutor) -> Self {
+ let (sender, channel) = calloop::channel::channel();
+
+ Self::spawn_observer(executor, Self::appearance_observer(sender.clone()));
+
+ Self { channel }
+ }
+
+ fn spawn_observer(
+ executor: &BackgroundExecutor,
+ to_spawn: impl Future<Output = Result<(), anyhow::Error>> + Send + 'static,
+ ) {
+ executor
+ .spawn(async move {
+ to_spawn.await.log_err();
+ })
+ .detach()
+ }
+
+ async fn appearance_observer(sender: Sender<Event>) -> Result<(), anyhow::Error> {
+ let settings = Settings::new().await?;
+
+ // We observe the color change during the execution of the application
+ let mut stream = settings.receive_color_scheme_changed().await?;
+ while let Some(scheme) = stream.next().await {
+ sender.send(Event::WindowAppearance(WindowAppearance::from_native(
+ scheme,
+ )))?;
+ }
+
+ Ok(())
+ }
+}
+
+impl EventSource for XDPEventSource {
+ type Event = Event;
+ type Metadata = ();
+ type Ret = ();
+ type Error = anyhow::Error;
+
+ fn process_events<F>(
+ &mut self,
+ readiness: Readiness,
+ token: Token,
+ mut callback: F,
+ ) -> Result<PostAction, Self::Error>
+ where
+ F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
+ {
+ self.channel.process_events(readiness, token, |evt, _| {
+ if let calloop::channel::Event::Msg(msg) = evt {
+ (callback)(msg, &mut ())
+ }
+ })?;
+
+ Ok(PostAction::Continue)
+ }
+
+ fn register(
+ &mut self,
+ poll: &mut Poll,
+ token_factory: &mut TokenFactory,
+ ) -> calloop::Result<()> {
+ self.channel.register(poll, token_factory)?;
+
+ Ok(())
+ }
+
+ fn reregister(
+ &mut self,
+ poll: &mut Poll,
+ token_factory: &mut TokenFactory,
+ ) -> calloop::Result<()> {
+ self.channel.reregister(poll, token_factory)?;
+
+ Ok(())
+ }
+
+ fn unregister(&mut self, poll: &mut Poll) -> calloop::Result<()> {
+ self.channel.unregister(poll)?;
+
+ Ok(())
+ }
+}
+
+impl WindowAppearance {
+ fn from_native(cs: ColorScheme) -> WindowAppearance {
+ match cs {
+ ColorScheme::PreferDark => WindowAppearance::Dark,
+ ColorScheme::PreferLight => WindowAppearance::Light,
+ ColorScheme::NoPreference => WindowAppearance::Light,
+ }
+ }
+
+ fn set_native(&mut self, cs: ColorScheme) {
+ *self = Self::from_native(cs);
+ }
+}
+
+pub fn window_appearance(executor: &BackgroundExecutor) -> Result<WindowAppearance, anyhow::Error> {
+ executor.block(async {
+ let settings = Settings::new().await?;
+
+ let scheme = settings.color_scheme().await?;
+
+ let appearance = WindowAppearance::from_native(scheme);
+
+ Ok(appearance)
+ })
+}