mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-14 22:14:23 +00:00
linux: Get the color scheme through xdg-desktop-portal (#11926)
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
This commit is contained in:
parent
b1cfd46d37
commit
a84344a82e
7 changed files with 224 additions and 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> {
|
||||
|
|
133
crates/gpui/src/platform/linux/xdg_desktop_portal.rs
Normal file
133
crates/gpui/src/platform/linux/xdg_desktop_portal.rs
Normal file
|
@ -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)
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue