mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-28 09:54:33 +00:00
WIP: Refactor Linux platform implementation (#10227)
This puts the Linux platform implementation at a similar code style and quality to the macOS platform. The largest change is that I collapsed the `LinuxPlatform` -> `[Backend]` -> `[Backend]State` -> `[Backend]StateInner` to just `[Backend]` and `[Backend]State`, and in the process removed most of the `Rc`s and `RefCell`s. TODO: - [x] Make sure that this is on-par with the existing implementation - [x] Review in detail, now that the large changes are done. - [ ] Update the roadmap Release Notes: - N/A
This commit is contained in:
parent
ee1642a50f
commit
def87a8d76
18 changed files with 1256 additions and 1095 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -1434,7 +1434,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "blade-graphics"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/kvark/blade?rev=61cbd6b2c224791d52b150fe535cee665cc91bb2#61cbd6b2c224791d52b150fe535cee665cc91bb2"
|
||||
source = "git+https://github.com/zed-industries/blade?rev=85981c0f4890a5fcd08da2a53cc4a0459247af44#85981c0f4890a5fcd08da2a53cc4a0459247af44"
|
||||
dependencies = [
|
||||
"ash",
|
||||
"ash-window",
|
||||
|
@ -1464,7 +1464,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "blade-macros"
|
||||
version = "0.2.1"
|
||||
source = "git+https://github.com/kvark/blade?rev=61cbd6b2c224791d52b150fe535cee665cc91bb2#61cbd6b2c224791d52b150fe535cee665cc91bb2"
|
||||
source = "git+https://github.com/zed-industries/blade?rev=85981c0f4890a5fcd08da2a53cc4a0459247af44#85981c0f4890a5fcd08da2a53cc4a0459247af44"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
@ -230,8 +230,9 @@ async-recursion = "1.0.0"
|
|||
async-tar = "0.4.2"
|
||||
async-trait = "0.1"
|
||||
bitflags = "2.4.2"
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "61cbd6b2c224791d52b150fe535cee665cc91bb2" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "61cbd6b2c224791d52b150fe535cee665cc91bb2" }
|
||||
# todo(linux): Remove these once https://github.com/kvark/blade/pull/107 is merged and we've upgraded our renderer
|
||||
blade-graphics = { git = "https://github.com/zed-industries/blade", rev = "85981c0f4890a5fcd08da2a53cc4a0459247af44" }
|
||||
blade-macros = { git = "https://github.com/zed-industries/blade", rev = "85981c0f4890a5fcd08da2a53cc4a0459247af44" }
|
||||
blade-rwh = { package = "raw-window-handle", version = "0.5" }
|
||||
cap-std = "3.0"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
|
|
|
@ -784,7 +784,6 @@ impl Client {
|
|||
}
|
||||
Status::UpgradeRequired => return Err(EstablishConnectionError::UpgradeRequired)?,
|
||||
};
|
||||
|
||||
if was_disconnected {
|
||||
self.set_status(Status::Authenticating, cx);
|
||||
} else {
|
||||
|
|
|
@ -307,7 +307,6 @@ impl ScrollDelta {
|
|||
}
|
||||
|
||||
/// A mouse exit event from the platform, generated when the mouse leaves the window.
|
||||
/// The position generated should be just outside of the window's bounds.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MouseExitEvent {
|
||||
/// The position of the mouse relative to the window.
|
||||
|
|
|
@ -66,7 +66,14 @@ pub(crate) fn current_platform() -> Rc<dyn Platform> {
|
|||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
pub(crate) fn current_platform() -> Rc<dyn Platform> {
|
||||
Rc::new(LinuxPlatform::new())
|
||||
let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
|
||||
let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
|
||||
|
||||
if use_wayland {
|
||||
Rc::new(WaylandClient::new())
|
||||
} else {
|
||||
Rc::new(X11Client::new())
|
||||
}
|
||||
}
|
||||
// todo("windows")
|
||||
#[cfg(target_os = "windows")]
|
||||
|
@ -207,6 +214,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
|||
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
|
||||
fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
|
||||
fn draw(&self, scene: &Scene);
|
||||
fn completed_frame(&self) {}
|
||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
mod client;
|
||||
// todo(linux): remove
|
||||
#![allow(unused)]
|
||||
|
||||
mod dispatcher;
|
||||
mod platform;
|
||||
mod text_system;
|
||||
mod util;
|
||||
mod wayland;
|
||||
mod x11;
|
||||
|
||||
pub(crate) use dispatcher::*;
|
||||
pub(crate) use platform::*;
|
||||
pub(crate) use text_system::*;
|
||||
// pub(crate) use x11::*;
|
||||
pub(crate) use wayland::*;
|
||||
pub(crate) use x11::*;
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use copypasta::ClipboardProvider;
|
||||
|
||||
use crate::platform::PlatformWindow;
|
||||
use crate::{AnyWindowHandle, CursorStyle, DisplayId, PlatformDisplay, WindowParams};
|
||||
|
||||
pub trait Client {
|
||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
|
||||
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
|
||||
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
|
||||
fn open_window(
|
||||
&self,
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowParams,
|
||||
) -> Box<dyn PlatformWindow>;
|
||||
fn set_cursor_style(&self, style: CursorStyle);
|
||||
fn get_clipboard(&self) -> Rc<RefCell<dyn ClipboardProvider>>;
|
||||
fn get_primary(&self) -> Rc<RefCell<dyn ClipboardProvider>>;
|
||||
}
|
|
@ -101,15 +101,13 @@ impl PlatformDispatcher for LinuxDispatcher {
|
|||
}
|
||||
|
||||
fn dispatch_on_main_thread(&self, runnable: Runnable) {
|
||||
self.main_sender
|
||||
.send(runnable)
|
||||
.expect("Main thread is gone");
|
||||
self.main_sender.send(runnable).ok();
|
||||
}
|
||||
|
||||
fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
|
||||
self.timer_sender
|
||||
.send(TimerAfter { duration, runnable })
|
||||
.expect("Timer thread has died");
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn tick(&self, background_only: bool) -> bool {
|
||||
|
@ -117,7 +115,7 @@ impl PlatformDispatcher for LinuxDispatcher {
|
|||
}
|
||||
|
||||
fn park(&self) {
|
||||
self.parker.lock().park()
|
||||
self.parker.lock().park();
|
||||
}
|
||||
|
||||
fn unparker(&self) -> Unparker {
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
#![allow(unused)]
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::any::{type_name, Any};
|
||||
use std::cell::{self, RefCell};
|
||||
use std::env;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::panic::Location;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
|
@ -13,140 +16,176 @@ use std::{
|
|||
use anyhow::anyhow;
|
||||
use ashpd::desktop::file_chooser::{OpenFileRequest, SaveFileRequest};
|
||||
use async_task::Runnable;
|
||||
use calloop::channel::Channel;
|
||||
use calloop::{EventLoop, LoopHandle, LoopSignal};
|
||||
use copypasta::ClipboardProvider;
|
||||
use flume::{Receiver, Sender};
|
||||
use futures::channel::oneshot;
|
||||
use parking_lot::Mutex;
|
||||
use time::UtcOffset;
|
||||
use wayland_client::Connection;
|
||||
use xkbcommon::xkb::{self, Keycode, Keysym, State};
|
||||
|
||||
use crate::platform::linux::client::Client;
|
||||
use crate::platform::linux::wayland::WaylandClient;
|
||||
use crate::{
|
||||
px, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
|
||||
ForegroundExecutor, Keymap, LinuxDispatcher, LinuxTextSystem, Menu, PathPromptOptions, Pixels,
|
||||
Platform, PlatformDisplay, PlatformInput, PlatformTextSystem, PlatformWindow, Result,
|
||||
SemanticVersion, Task, WindowOptions, WindowParams,
|
||||
ForegroundExecutor, Keymap, Keystroke, LinuxDispatcher, LinuxTextSystem, Menu, Modifiers,
|
||||
PathPromptOptions, Pixels, Platform, PlatformDisplay, PlatformInput, PlatformInputHandler,
|
||||
PlatformTextSystem, PlatformWindow, Point, PromptLevel, Result, SemanticVersion, Size, Task,
|
||||
WindowAppearance, WindowOptions, WindowParams,
|
||||
};
|
||||
|
||||
use super::x11::X11Client;
|
||||
|
||||
pub(super) const SCROLL_LINES: f64 = 3.0;
|
||||
pub(crate) const SCROLL_LINES: f64 = 3.0;
|
||||
|
||||
// Values match the defaults on GTK.
|
||||
// Taken from https://github.com/GNOME/gtk/blob/main/gtk/gtksettings.c#L320
|
||||
pub(super) const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(400);
|
||||
pub(super) const DOUBLE_CLICK_DISTANCE: Pixels = px(5.0);
|
||||
pub(crate) const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(400);
|
||||
pub(crate) const DOUBLE_CLICK_DISTANCE: Pixels = px(5.0);
|
||||
pub(crate) const KEYRING_LABEL: &str = "zed-github-account";
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Callbacks {
|
||||
open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
|
||||
become_active: Option<Box<dyn FnMut()>>,
|
||||
resign_active: Option<Box<dyn FnMut()>>,
|
||||
quit: Option<Box<dyn FnMut()>>,
|
||||
reopen: Option<Box<dyn FnMut()>>,
|
||||
event: Option<Box<dyn FnMut(PlatformInput) -> bool>>,
|
||||
app_menu_action: Option<Box<dyn FnMut(&dyn Action)>>,
|
||||
will_open_app_menu: Option<Box<dyn FnMut()>>,
|
||||
validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
|
||||
}
|
||||
pub struct RcRefCell<T>(Rc<RefCell<T>>);
|
||||
|
||||
pub(crate) struct LinuxPlatformInner {
|
||||
pub(crate) event_loop: RefCell<EventLoop<'static, ()>>,
|
||||
pub(crate) loop_handle: Rc<LoopHandle<'static, ()>>,
|
||||
pub(crate) loop_signal: LoopSignal,
|
||||
pub(crate) background_executor: BackgroundExecutor,
|
||||
pub(crate) foreground_executor: ForegroundExecutor,
|
||||
pub(crate) text_system: Arc<LinuxTextSystem>,
|
||||
pub(crate) callbacks: RefCell<Callbacks>,
|
||||
}
|
||||
impl<T> RcRefCell<T> {
|
||||
pub fn new(value: T) -> Self {
|
||||
RcRefCell(Rc::new(RefCell::new(value)))
|
||||
}
|
||||
|
||||
pub(crate) struct LinuxPlatform {
|
||||
client: Rc<dyn Client>,
|
||||
inner: Rc<LinuxPlatformInner>,
|
||||
}
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn borrow_mut(&self) -> std::cell::RefMut<'_, T> {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
if option_env!("TRACK_BORROW_MUT").is_some() {
|
||||
eprintln!(
|
||||
"borrow_mut-ing {} at {}",
|
||||
type_name::<T>(),
|
||||
Location::caller()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LinuxPlatform {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
self.0.borrow_mut()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn borrow(&self) -> std::cell::Ref<'_, T> {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
if option_env!("TRACK_BORROW_MUT").is_some() {
|
||||
eprintln!("borrow-ing {} at {}", type_name::<T>(), Location::caller());
|
||||
}
|
||||
}
|
||||
|
||||
self.0.borrow()
|
||||
}
|
||||
}
|
||||
|
||||
impl LinuxPlatform {
|
||||
pub(crate) fn new() -> Self {
|
||||
let wayland_display = env::var_os("WAYLAND_DISPLAY");
|
||||
let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
|
||||
impl<T> Deref for RcRefCell<T> {
|
||||
type Target = Rc<RefCell<T>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl<T> DerefMut for RcRefCell<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for RcRefCell<T> {
|
||||
fn clone(&self) -> Self {
|
||||
RcRefCell(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LinuxClient {
|
||||
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R;
|
||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
|
||||
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
|
||||
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
|
||||
fn open_window(
|
||||
&self,
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowParams,
|
||||
) -> Box<dyn PlatformWindow>;
|
||||
fn set_cursor_style(&self, style: CursorStyle);
|
||||
fn write_to_clipboard(&self, item: ClipboardItem);
|
||||
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
|
||||
fn run(&self);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct PlatformHandlers {
|
||||
pub(crate) open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
|
||||
pub(crate) become_active: Option<Box<dyn FnMut()>>,
|
||||
pub(crate) resign_active: Option<Box<dyn FnMut()>>,
|
||||
pub(crate) quit: Option<Box<dyn FnMut()>>,
|
||||
pub(crate) reopen: Option<Box<dyn FnMut()>>,
|
||||
pub(crate) event: Option<Box<dyn FnMut(PlatformInput) -> bool>>,
|
||||
pub(crate) app_menu_action: Option<Box<dyn FnMut(&dyn Action)>>,
|
||||
pub(crate) will_open_app_menu: Option<Box<dyn FnMut()>>,
|
||||
pub(crate) validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
|
||||
}
|
||||
|
||||
pub(crate) struct LinuxCommon {
|
||||
pub(crate) background_executor: BackgroundExecutor,
|
||||
pub(crate) foreground_executor: ForegroundExecutor,
|
||||
pub(crate) text_system: Arc<LinuxTextSystem>,
|
||||
pub(crate) callbacks: PlatformHandlers,
|
||||
pub(crate) signal: LoopSignal,
|
||||
}
|
||||
|
||||
impl LinuxCommon {
|
||||
pub fn new(signal: LoopSignal) -> (Self, Channel<Runnable>) {
|
||||
let (main_sender, main_receiver) = calloop::channel::channel::<Runnable>();
|
||||
let text_system = Arc::new(LinuxTextSystem::new());
|
||||
let callbacks = RefCell::new(Callbacks::default());
|
||||
|
||||
let event_loop = EventLoop::try_new().unwrap();
|
||||
event_loop
|
||||
.handle()
|
||||
.insert_source(main_receiver, |event, _, _| {
|
||||
if let calloop::channel::Event::Msg(runnable) = event {
|
||||
runnable.run();
|
||||
}
|
||||
});
|
||||
let callbacks = PlatformHandlers::default();
|
||||
|
||||
let dispatcher = Arc::new(LinuxDispatcher::new(main_sender));
|
||||
|
||||
let inner = Rc::new(LinuxPlatformInner {
|
||||
loop_handle: Rc::new(event_loop.handle()),
|
||||
loop_signal: event_loop.get_signal(),
|
||||
event_loop: RefCell::new(event_loop),
|
||||
let common = LinuxCommon {
|
||||
background_executor: BackgroundExecutor::new(dispatcher.clone()),
|
||||
foreground_executor: ForegroundExecutor::new(dispatcher.clone()),
|
||||
text_system,
|
||||
callbacks,
|
||||
});
|
||||
signal,
|
||||
};
|
||||
|
||||
if use_wayland {
|
||||
Self {
|
||||
client: Rc::new(WaylandClient::new(Rc::clone(&inner))),
|
||||
inner,
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
client: X11Client::new(Rc::clone(&inner)),
|
||||
inner,
|
||||
}
|
||||
}
|
||||
(common, main_receiver)
|
||||
}
|
||||
}
|
||||
|
||||
const KEYRING_LABEL: &str = "zed-github-account";
|
||||
|
||||
impl Platform for LinuxPlatform {
|
||||
impl<P: LinuxClient + 'static> Platform for P {
|
||||
fn background_executor(&self) -> BackgroundExecutor {
|
||||
self.inner.background_executor.clone()
|
||||
self.with_common(|common| common.background_executor.clone())
|
||||
}
|
||||
|
||||
fn foreground_executor(&self) -> ForegroundExecutor {
|
||||
self.inner.foreground_executor.clone()
|
||||
self.with_common(|common| common.foreground_executor.clone())
|
||||
}
|
||||
|
||||
fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
|
||||
self.inner.text_system.clone()
|
||||
self.with_common(|common| common.text_system.clone())
|
||||
}
|
||||
|
||||
fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
|
||||
on_finish_launching();
|
||||
|
||||
self.inner
|
||||
.event_loop
|
||||
.borrow_mut()
|
||||
.run(None, &mut (), |&mut ()| {})
|
||||
.expect("Run loop failed");
|
||||
LinuxClient::run(self);
|
||||
|
||||
if let Some(mut fun) = self.inner.callbacks.borrow_mut().quit.take() {
|
||||
fun();
|
||||
}
|
||||
self.with_common(|common| {
|
||||
if let Some(mut fun) = common.callbacks.quit.take() {
|
||||
fun();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn quit(&self) {
|
||||
self.inner.loop_signal.stop();
|
||||
self.with_common(|common| common.signal.stop());
|
||||
}
|
||||
|
||||
fn restart(&self) {
|
||||
|
@ -194,22 +233,23 @@ impl Platform for LinuxPlatform {
|
|||
// todo(linux)
|
||||
fn hide(&self) {}
|
||||
|
||||
// todo(linux)
|
||||
fn hide_other_apps(&self) {}
|
||||
fn hide_other_apps(&self) {
|
||||
log::warn!("hide_other_apps is not implemented on Linux, ignoring the call")
|
||||
}
|
||||
|
||||
// todo(linux)
|
||||
fn unhide_other_apps(&self) {}
|
||||
|
||||
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
|
||||
self.client.primary_display()
|
||||
self.primary_display()
|
||||
}
|
||||
|
||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
|
||||
self.client.displays()
|
||||
self.displays()
|
||||
}
|
||||
|
||||
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
|
||||
self.client.display(id)
|
||||
self.display(id)
|
||||
}
|
||||
|
||||
// todo(linux)
|
||||
|
@ -222,7 +262,7 @@ impl Platform for LinuxPlatform {
|
|||
handle: AnyWindowHandle,
|
||||
options: WindowParams,
|
||||
) -> Box<dyn PlatformWindow> {
|
||||
self.client.open_window(handle, options)
|
||||
self.open_window(handle, options)
|
||||
}
|
||||
|
||||
fn open_url(&self, url: &str) {
|
||||
|
@ -230,7 +270,7 @@ impl Platform for LinuxPlatform {
|
|||
}
|
||||
|
||||
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
|
||||
self.inner.callbacks.borrow_mut().open_urls = Some(callback);
|
||||
self.with_common(|common| common.callbacks.open_urls = Some(callback));
|
||||
}
|
||||
|
||||
fn prompt_for_paths(
|
||||
|
@ -238,8 +278,7 @@ impl Platform for LinuxPlatform {
|
|||
options: PathPromptOptions,
|
||||
) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
|
||||
let (done_tx, done_rx) = oneshot::channel();
|
||||
self.inner
|
||||
.foreground_executor
|
||||
self.foreground_executor()
|
||||
.spawn(async move {
|
||||
let title = if options.multiple {
|
||||
if !options.files {
|
||||
|
@ -282,8 +321,7 @@ impl Platform for LinuxPlatform {
|
|||
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
|
||||
let (done_tx, done_rx) = oneshot::channel();
|
||||
let directory = directory.to_owned();
|
||||
self.inner
|
||||
.foreground_executor
|
||||
self.foreground_executor()
|
||||
.spawn(async move {
|
||||
let result = SaveFileRequest::default()
|
||||
.modal(true)
|
||||
|
@ -303,6 +341,7 @@ impl Platform for LinuxPlatform {
|
|||
done_tx.send(result);
|
||||
})
|
||||
.detach();
|
||||
|
||||
done_rx
|
||||
}
|
||||
|
||||
|
@ -317,35 +356,51 @@ impl Platform for LinuxPlatform {
|
|||
}
|
||||
|
||||
fn on_become_active(&self, callback: Box<dyn FnMut()>) {
|
||||
self.inner.callbacks.borrow_mut().become_active = Some(callback);
|
||||
self.with_common(|common| {
|
||||
common.callbacks.become_active = Some(callback);
|
||||
});
|
||||
}
|
||||
|
||||
fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
|
||||
self.inner.callbacks.borrow_mut().resign_active = Some(callback);
|
||||
self.with_common(|common| {
|
||||
common.callbacks.resign_active = Some(callback);
|
||||
});
|
||||
}
|
||||
|
||||
fn on_quit(&self, callback: Box<dyn FnMut()>) {
|
||||
self.inner.callbacks.borrow_mut().quit = Some(callback);
|
||||
self.with_common(|common| {
|
||||
common.callbacks.quit = Some(callback);
|
||||
});
|
||||
}
|
||||
|
||||
fn on_reopen(&self, callback: Box<dyn FnMut()>) {
|
||||
self.inner.callbacks.borrow_mut().reopen = Some(callback);
|
||||
self.with_common(|common| {
|
||||
common.callbacks.reopen = Some(callback);
|
||||
});
|
||||
}
|
||||
|
||||
fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
|
||||
self.inner.callbacks.borrow_mut().event = Some(callback);
|
||||
self.with_common(|common| {
|
||||
common.callbacks.event = Some(callback);
|
||||
});
|
||||
}
|
||||
|
||||
fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
|
||||
self.inner.callbacks.borrow_mut().app_menu_action = Some(callback);
|
||||
self.with_common(|common| {
|
||||
common.callbacks.app_menu_action = Some(callback);
|
||||
});
|
||||
}
|
||||
|
||||
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
|
||||
self.inner.callbacks.borrow_mut().will_open_app_menu = Some(callback);
|
||||
self.with_common(|common| {
|
||||
common.callbacks.will_open_app_menu = Some(callback);
|
||||
});
|
||||
}
|
||||
|
||||
fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
|
||||
self.inner.callbacks.borrow_mut().validate_app_menu_command = Some(callback);
|
||||
self.with_common(|common| {
|
||||
common.callbacks.validate_app_menu_command = Some(callback);
|
||||
});
|
||||
}
|
||||
|
||||
fn os_name(&self) -> &'static str {
|
||||
|
@ -381,7 +436,7 @@ impl Platform for LinuxPlatform {
|
|||
}
|
||||
|
||||
fn set_cursor_style(&self, style: CursorStyle) {
|
||||
self.client.set_cursor_style(style)
|
||||
self.set_cursor_style(style)
|
||||
}
|
||||
|
||||
// todo(linux)
|
||||
|
@ -389,23 +444,6 @@ impl Platform for LinuxPlatform {
|
|||
false
|
||||
}
|
||||
|
||||
fn write_to_clipboard(&self, item: ClipboardItem) {
|
||||
let clipboard = self.client.get_clipboard();
|
||||
clipboard.borrow_mut().set_contents(item.text);
|
||||
}
|
||||
|
||||
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
||||
let clipboard = self.client.get_clipboard();
|
||||
let contents = clipboard.borrow_mut().get_contents();
|
||||
match contents {
|
||||
Ok(text) => Some(ClipboardItem {
|
||||
metadata: None,
|
||||
text,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
|
||||
let url = url.to_string();
|
||||
let username = username.to_string();
|
||||
|
@ -479,14 +517,136 @@ impl Platform for LinuxPlatform {
|
|||
fn register_url_scheme(&self, _: &str) -> Task<anyhow::Result<()>> {
|
||||
Task::ready(Err(anyhow!("register_url_scheme unimplemented")))
|
||||
}
|
||||
|
||||
fn write_to_clipboard(&self, item: ClipboardItem) {
|
||||
self.write_to_clipboard(item)
|
||||
}
|
||||
|
||||
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
||||
self.read_from_clipboard()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_within_click_distance(a: Point<Pixels>, b: Point<Pixels>) -> bool {
|
||||
let diff = a - b;
|
||||
diff.x.abs() <= DOUBLE_CLICK_DISTANCE && diff.y.abs() <= DOUBLE_CLICK_DISTANCE
|
||||
}
|
||||
|
||||
impl Keystroke {
|
||||
pub(super) fn from_xkb(state: &State, modifiers: Modifiers, keycode: Keycode) -> Self {
|
||||
let mut modifiers = modifiers;
|
||||
|
||||
let key_utf32 = state.key_get_utf32(keycode);
|
||||
let key_utf8 = state.key_get_utf8(keycode);
|
||||
let key_sym = state.key_get_one_sym(keycode);
|
||||
|
||||
// The logic here tries to replicate the logic in `../mac/events.rs`
|
||||
// "Consumed" modifiers are modifiers that have been used to translate a key, for example
|
||||
// pressing "shift" and "1" on US layout produces the key `!` but "consumes" the shift.
|
||||
// Notes:
|
||||
// - macOS gets the key character directly ("."), xkb gives us the key name ("period")
|
||||
// - macOS logic removes consumed shift modifier for symbols: "{", not "shift-{"
|
||||
// - macOS logic keeps consumed shift modifiers for letters: "shift-a", not "a" or "A"
|
||||
|
||||
let mut handle_consumed_modifiers = true;
|
||||
let key = match key_sym {
|
||||
Keysym::Return => "enter".to_owned(),
|
||||
Keysym::Prior => "pageup".to_owned(),
|
||||
Keysym::Next => "pagedown".to_owned(),
|
||||
|
||||
Keysym::comma => ",".to_owned(),
|
||||
Keysym::period => ".".to_owned(),
|
||||
Keysym::less => "<".to_owned(),
|
||||
Keysym::greater => ">".to_owned(),
|
||||
Keysym::slash => "/".to_owned(),
|
||||
Keysym::question => "?".to_owned(),
|
||||
|
||||
Keysym::semicolon => ";".to_owned(),
|
||||
Keysym::colon => ":".to_owned(),
|
||||
Keysym::apostrophe => "'".to_owned(),
|
||||
Keysym::quotedbl => "\"".to_owned(),
|
||||
|
||||
Keysym::bracketleft => "[".to_owned(),
|
||||
Keysym::braceleft => "{".to_owned(),
|
||||
Keysym::bracketright => "]".to_owned(),
|
||||
Keysym::braceright => "}".to_owned(),
|
||||
Keysym::backslash => "\\".to_owned(),
|
||||
Keysym::bar => "|".to_owned(),
|
||||
|
||||
Keysym::grave => "`".to_owned(),
|
||||
Keysym::asciitilde => "~".to_owned(),
|
||||
Keysym::exclam => "!".to_owned(),
|
||||
Keysym::at => "@".to_owned(),
|
||||
Keysym::numbersign => "#".to_owned(),
|
||||
Keysym::dollar => "$".to_owned(),
|
||||
Keysym::percent => "%".to_owned(),
|
||||
Keysym::asciicircum => "^".to_owned(),
|
||||
Keysym::ampersand => "&".to_owned(),
|
||||
Keysym::asterisk => "*".to_owned(),
|
||||
Keysym::parenleft => "(".to_owned(),
|
||||
Keysym::parenright => ")".to_owned(),
|
||||
Keysym::minus => "-".to_owned(),
|
||||
Keysym::underscore => "_".to_owned(),
|
||||
Keysym::equal => "=".to_owned(),
|
||||
Keysym::plus => "+".to_owned(),
|
||||
|
||||
Keysym::ISO_Left_Tab => {
|
||||
handle_consumed_modifiers = false;
|
||||
"tab".to_owned()
|
||||
}
|
||||
|
||||
_ => {
|
||||
handle_consumed_modifiers = false;
|
||||
xkb::keysym_get_name(key_sym).to_lowercase()
|
||||
}
|
||||
};
|
||||
|
||||
// Ignore control characters (and DEL) for the purposes of ime_key,
|
||||
// but if key_utf32 is 0 then assume it isn't one
|
||||
let ime_key = ((key_utf32 == 0 || (key_utf32 >= 32 && key_utf32 != 127))
|
||||
&& !key_utf8.is_empty())
|
||||
.then_some(key_utf8);
|
||||
|
||||
if handle_consumed_modifiers {
|
||||
let mod_shift_index = state.get_keymap().mod_get_index(xkb::MOD_NAME_SHIFT);
|
||||
let is_shift_consumed = state.mod_index_is_consumed(keycode, mod_shift_index);
|
||||
|
||||
if modifiers.shift && is_shift_consumed {
|
||||
modifiers.shift = false;
|
||||
}
|
||||
}
|
||||
|
||||
Keystroke {
|
||||
modifiers,
|
||||
key,
|
||||
ime_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{px, Point};
|
||||
|
||||
fn build_platform() -> LinuxPlatform {
|
||||
let platform = LinuxPlatform::new();
|
||||
platform
|
||||
#[test]
|
||||
fn test_is_within_click_distance() {
|
||||
let zero = Point::new(px(0.0), px(0.0));
|
||||
assert_eq!(
|
||||
is_within_click_distance(zero, Point::new(px(5.0), px(5.0))),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
is_within_click_distance(zero, Point::new(px(-4.9), px(5.0))),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
is_within_click_distance(Point::new(px(3.0), px(2.0)), Point::new(px(-2.0), px(-2.0))),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
is_within_click_distance(zero, Point::new(px(5.0), px(5.1))),
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,128 +0,0 @@
|
|||
use xkbcommon::xkb::{self, Keycode, Keysym, State};
|
||||
|
||||
use super::DOUBLE_CLICK_DISTANCE;
|
||||
use crate::{Keystroke, Modifiers, Pixels, Point};
|
||||
|
||||
impl Keystroke {
|
||||
pub(super) fn from_xkb(state: &State, modifiers: Modifiers, keycode: Keycode) -> Self {
|
||||
let mut modifiers = modifiers;
|
||||
|
||||
let key_utf32 = state.key_get_utf32(keycode);
|
||||
let key_utf8 = state.key_get_utf8(keycode);
|
||||
let key_sym = state.key_get_one_sym(keycode);
|
||||
|
||||
// The logic here tries to replicate the logic in `../mac/events.rs`
|
||||
// "Consumed" modifiers are modifiers that have been used to translate a key, for example
|
||||
// pressing "shift" and "1" on US layout produces the key `!` but "consumes" the shift.
|
||||
// Notes:
|
||||
// - macOS gets the key character directly ("."), xkb gives us the key name ("period")
|
||||
// - macOS logic removes consumed shift modifier for symbols: "{", not "shift-{"
|
||||
// - macOS logic keeps consumed shift modifiers for letters: "shift-a", not "a" or "A"
|
||||
|
||||
let mut handle_consumed_modifiers = true;
|
||||
let key = match key_sym {
|
||||
Keysym::Return => "enter".to_owned(),
|
||||
Keysym::Prior => "pageup".to_owned(),
|
||||
Keysym::Next => "pagedown".to_owned(),
|
||||
|
||||
Keysym::comma => ",".to_owned(),
|
||||
Keysym::period => ".".to_owned(),
|
||||
Keysym::less => "<".to_owned(),
|
||||
Keysym::greater => ">".to_owned(),
|
||||
Keysym::slash => "/".to_owned(),
|
||||
Keysym::question => "?".to_owned(),
|
||||
|
||||
Keysym::semicolon => ";".to_owned(),
|
||||
Keysym::colon => ":".to_owned(),
|
||||
Keysym::apostrophe => "'".to_owned(),
|
||||
Keysym::quotedbl => "\"".to_owned(),
|
||||
|
||||
Keysym::bracketleft => "[".to_owned(),
|
||||
Keysym::braceleft => "{".to_owned(),
|
||||
Keysym::bracketright => "]".to_owned(),
|
||||
Keysym::braceright => "}".to_owned(),
|
||||
Keysym::backslash => "\\".to_owned(),
|
||||
Keysym::bar => "|".to_owned(),
|
||||
|
||||
Keysym::grave => "`".to_owned(),
|
||||
Keysym::asciitilde => "~".to_owned(),
|
||||
Keysym::exclam => "!".to_owned(),
|
||||
Keysym::at => "@".to_owned(),
|
||||
Keysym::numbersign => "#".to_owned(),
|
||||
Keysym::dollar => "$".to_owned(),
|
||||
Keysym::percent => "%".to_owned(),
|
||||
Keysym::asciicircum => "^".to_owned(),
|
||||
Keysym::ampersand => "&".to_owned(),
|
||||
Keysym::asterisk => "*".to_owned(),
|
||||
Keysym::parenleft => "(".to_owned(),
|
||||
Keysym::parenright => ")".to_owned(),
|
||||
Keysym::minus => "-".to_owned(),
|
||||
Keysym::underscore => "_".to_owned(),
|
||||
Keysym::equal => "=".to_owned(),
|
||||
Keysym::plus => "+".to_owned(),
|
||||
|
||||
Keysym::ISO_Left_Tab => {
|
||||
handle_consumed_modifiers = false;
|
||||
"tab".to_owned()
|
||||
}
|
||||
|
||||
_ => {
|
||||
handle_consumed_modifiers = false;
|
||||
xkb::keysym_get_name(key_sym).to_lowercase()
|
||||
}
|
||||
};
|
||||
|
||||
// Ignore control characters (and DEL) for the purposes of ime_key,
|
||||
// but if key_utf32 is 0 then assume it isn't one
|
||||
let ime_key = ((key_utf32 == 0 || (key_utf32 >= 32 && key_utf32 != 127))
|
||||
&& !key_utf8.is_empty())
|
||||
.then_some(key_utf8);
|
||||
|
||||
if handle_consumed_modifiers {
|
||||
let mod_shift_index = state.get_keymap().mod_get_index(xkb::MOD_NAME_SHIFT);
|
||||
let is_shift_consumed = state.mod_index_is_consumed(keycode, mod_shift_index);
|
||||
|
||||
if modifiers.shift && is_shift_consumed {
|
||||
modifiers.shift = false;
|
||||
}
|
||||
}
|
||||
|
||||
Keystroke {
|
||||
modifiers,
|
||||
key,
|
||||
ime_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_within_click_distance(a: Point<Pixels>, b: Point<Pixels>) -> bool {
|
||||
let diff = a - b;
|
||||
diff.x.abs() <= DOUBLE_CLICK_DISTANCE && diff.y.abs() <= DOUBLE_CLICK_DISTANCE
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{px, Point};
|
||||
|
||||
#[test]
|
||||
fn test_is_within_click_distance() {
|
||||
let zero = Point::new(px(0.0), px(0.0));
|
||||
assert_eq!(
|
||||
is_within_click_distance(zero, Point::new(px(5.0), px(5.0))),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
is_within_click_distance(zero, Point::new(px(-4.9), px(5.0))),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
is_within_click_distance(Point::new(px(3.0), px(2.0)), Point::new(px(-2.0), px(-2.0))),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
is_within_click_distance(zero, Point::new(px(5.0), px(5.1))),
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,9 +1,6 @@
|
|||
// todo(linux): remove this once the relevant functionality has been implemented
|
||||
#![allow(unused_variables)]
|
||||
|
||||
pub(crate) use client::*;
|
||||
|
||||
mod client;
|
||||
mod cursor;
|
||||
mod display;
|
||||
mod window;
|
||||
|
||||
pub(crate) use client::*;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,31 +1,31 @@
|
|||
use crate::platform::linux::wayland::WaylandClientState;
|
||||
use wayland_backend::client::InvalidId;
|
||||
use wayland_client::protocol::wl_compositor::WlCompositor;
|
||||
use crate::Globals;
|
||||
use util::ResultExt;
|
||||
|
||||
use wayland_client::protocol::wl_pointer::WlPointer;
|
||||
use wayland_client::protocol::wl_shm::WlShm;
|
||||
use wayland_client::protocol::wl_surface::WlSurface;
|
||||
use wayland_client::{Connection, QueueHandle};
|
||||
use wayland_client::Connection;
|
||||
use wayland_cursor::{CursorImageBuffer, CursorTheme};
|
||||
|
||||
pub(crate) struct Cursor {
|
||||
theme: Result<CursorTheme, InvalidId>,
|
||||
theme: Option<CursorTheme>,
|
||||
current_icon_name: String,
|
||||
surface: WlSurface,
|
||||
serial_id: u32,
|
||||
}
|
||||
|
||||
impl Drop for Cursor {
|
||||
fn drop(&mut self) {
|
||||
self.theme.take();
|
||||
self.surface.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
impl Cursor {
|
||||
pub fn new(
|
||||
connection: &Connection,
|
||||
compositor: &WlCompositor,
|
||||
qh: &QueueHandle<WaylandClientState>,
|
||||
shm: &WlShm,
|
||||
size: u32,
|
||||
) -> Self {
|
||||
pub fn new(connection: &Connection, globals: &Globals, size: u32) -> Self {
|
||||
Self {
|
||||
theme: CursorTheme::load(&connection, shm.clone(), size),
|
||||
current_icon_name: "".to_string(),
|
||||
surface: compositor.create_surface(qh, ()),
|
||||
theme: CursorTheme::load(&connection, globals.shm.clone(), size).log_err(),
|
||||
current_icon_name: "default".to_string(),
|
||||
surface: globals.compositor.create_surface(&globals.qh, ()),
|
||||
serial_id: 0,
|
||||
}
|
||||
}
|
||||
|
@ -34,17 +34,17 @@ impl Cursor {
|
|||
self.serial_id = serial_id;
|
||||
}
|
||||
|
||||
pub fn set_icon(&mut self, wl_pointer: &WlPointer, cursor_icon_name: String) {
|
||||
let mut cursor_icon_name = cursor_icon_name.clone();
|
||||
pub fn set_icon(&mut self, wl_pointer: &WlPointer, mut cursor_icon_name: Option<&str>) {
|
||||
let mut cursor_icon_name = cursor_icon_name.unwrap_or("default");
|
||||
if self.current_icon_name != cursor_icon_name {
|
||||
if let Ok(theme) = &mut self.theme {
|
||||
if let Some(theme) = &mut self.theme {
|
||||
let mut buffer: Option<&CursorImageBuffer>;
|
||||
|
||||
if let Some(cursor) = theme.get_cursor(&cursor_icon_name) {
|
||||
buffer = Some(&cursor[0]);
|
||||
} else if let Some(cursor) = theme.get_cursor("default") {
|
||||
buffer = Some(&cursor[0]);
|
||||
cursor_icon_name = "default".to_string();
|
||||
cursor_icon_name = "default";
|
||||
log::warn!(
|
||||
"Linux: Wayland: Unable to get cursor icon: {}. Using default cursor icon",
|
||||
cursor_icon_name
|
||||
|
@ -68,7 +68,7 @@ impl Cursor {
|
|||
self.surface.damage(0, 0, width as i32, height as i32);
|
||||
self.surface.commit();
|
||||
|
||||
self.current_icon_name = cursor_icon_name;
|
||||
self.current_icon_name = cursor_icon_name.to_string();
|
||||
}
|
||||
} else {
|
||||
log::warn!("Linux: Wayland: Unable to load cursor themes");
|
||||
|
|
|
@ -7,23 +7,28 @@ use std::sync::Arc;
|
|||
|
||||
use blade_graphics as gpu;
|
||||
use blade_rwh::{HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle};
|
||||
use collections::HashSet;
|
||||
use collections::{HashMap, HashSet};
|
||||
use futures::channel::oneshot::Receiver;
|
||||
use raw_window_handle::{
|
||||
DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, WindowHandle,
|
||||
};
|
||||
use wayland_backend::client::ObjectId;
|
||||
use wayland_client::WEnum;
|
||||
use wayland_client::{protocol::wl_surface, Proxy};
|
||||
use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1;
|
||||
use wayland_protocols::wp::viewporter::client::wp_viewport;
|
||||
use wayland_protocols::xdg::shell::client::xdg_toplevel;
|
||||
use wayland_protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1;
|
||||
use wayland_protocols::xdg::shell::client::xdg_surface;
|
||||
use wayland_protocols::xdg::shell::client::xdg_toplevel::{self, WmCapabilities};
|
||||
|
||||
use crate::platform::blade::BladeRenderer;
|
||||
use crate::platform::linux::wayland::display::WaylandDisplay;
|
||||
use crate::platform::{PlatformAtlas, PlatformInputHandler, PlatformWindow};
|
||||
use crate::scene::Scene;
|
||||
use crate::{
|
||||
px, size, Bounds, DevicePixels, Modifiers, Pixels, PlatformDisplay, PlatformInput, Point,
|
||||
PromptLevel, Size, WindowAppearance, WindowBackgroundAppearance, WindowParams,
|
||||
px, size, Bounds, DevicePixels, Globals, Modifiers, Pixels, PlatformDisplay, PlatformInput,
|
||||
Point, PromptLevel, RcRefCell, Size, WindowAppearance, WindowBackgroundAppearance,
|
||||
WindowParams,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -39,14 +44,6 @@ pub(crate) struct Callbacks {
|
|||
appearance_changed: Option<Box<dyn FnMut()>>,
|
||||
}
|
||||
|
||||
struct WaylandWindowInner {
|
||||
renderer: BladeRenderer,
|
||||
bounds: Bounds<u32>,
|
||||
scale: f32,
|
||||
input_handler: Option<PlatformInputHandler>,
|
||||
decoration_state: WaylandDecorationState,
|
||||
}
|
||||
|
||||
struct RawWindow {
|
||||
window: *mut c_void,
|
||||
display: *mut c_void,
|
||||
|
@ -68,11 +65,36 @@ unsafe impl HasRawDisplayHandle for RawWindow {
|
|||
}
|
||||
}
|
||||
|
||||
impl WaylandWindowInner {
|
||||
fn new(wl_surf: &Arc<wl_surface::WlSurface>, bounds: Bounds<u32>) -> Self {
|
||||
pub struct WaylandWindowState {
|
||||
xdg_surface: xdg_surface::XdgSurface,
|
||||
surface: wl_surface::WlSurface,
|
||||
toplevel: xdg_toplevel::XdgToplevel,
|
||||
viewport: Option<wp_viewport::WpViewport>,
|
||||
outputs: HashSet<ObjectId>,
|
||||
globals: Globals,
|
||||
renderer: BladeRenderer,
|
||||
bounds: Bounds<u32>,
|
||||
scale: f32,
|
||||
input_handler: Option<PlatformInputHandler>,
|
||||
decoration_state: WaylandDecorationState,
|
||||
fullscreen: bool,
|
||||
maximized: bool,
|
||||
}
|
||||
|
||||
impl WaylandWindowState {
|
||||
pub(crate) fn new(
|
||||
surface: wl_surface::WlSurface,
|
||||
xdg_surface: xdg_surface::XdgSurface,
|
||||
viewport: Option<wp_viewport::WpViewport>,
|
||||
toplevel: xdg_toplevel::XdgToplevel,
|
||||
globals: Globals,
|
||||
options: WindowParams,
|
||||
) -> Self {
|
||||
let bounds = options.bounds.map(|p| p.0 as u32);
|
||||
|
||||
let raw = RawWindow {
|
||||
window: wl_surf.id().as_ptr().cast::<c_void>(),
|
||||
display: wl_surf
|
||||
window: surface.id().as_ptr().cast::<c_void>(),
|
||||
display: surface
|
||||
.backend()
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
|
@ -97,54 +119,213 @@ impl WaylandWindowInner {
|
|||
height: bounds.size.height,
|
||||
depth: 1,
|
||||
};
|
||||
|
||||
Self {
|
||||
xdg_surface,
|
||||
surface,
|
||||
toplevel,
|
||||
viewport,
|
||||
globals,
|
||||
|
||||
outputs: HashSet::default(),
|
||||
|
||||
renderer: BladeRenderer::new(gpu, extent),
|
||||
bounds,
|
||||
scale: 1.0,
|
||||
input_handler: None,
|
||||
|
||||
// On wayland, decorations are by default provided by the client
|
||||
decoration_state: WaylandDecorationState::Client,
|
||||
fullscreen: false,
|
||||
maximized: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WaylandWindowState {
|
||||
inner: RefCell<WaylandWindowInner>,
|
||||
pub(crate) callbacks: RefCell<Callbacks>,
|
||||
pub(crate) surface: Arc<wl_surface::WlSurface>,
|
||||
pub(crate) toplevel: Arc<xdg_toplevel::XdgToplevel>,
|
||||
pub(crate) outputs: RefCell<HashSet<ObjectId>>,
|
||||
viewport: Option<wp_viewport::WpViewport>,
|
||||
fullscreen: RefCell<bool>,
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct WaylandWindow {
|
||||
pub(crate) state: RcRefCell<WaylandWindowState>,
|
||||
pub(crate) callbacks: Rc<RefCell<Callbacks>>,
|
||||
}
|
||||
|
||||
impl WaylandWindowState {
|
||||
pub(crate) fn new(
|
||||
wl_surf: Arc<wl_surface::WlSurface>,
|
||||
viewport: Option<wp_viewport::WpViewport>,
|
||||
toplevel: Arc<xdg_toplevel::XdgToplevel>,
|
||||
options: WindowParams,
|
||||
) -> Self {
|
||||
let bounds = options.bounds.map(|p| p.0 as u32);
|
||||
impl WaylandWindow {
|
||||
pub fn ptr_eq(&self, other: &Self) -> bool {
|
||||
Rc::ptr_eq(&self.state, &other.state)
|
||||
}
|
||||
|
||||
Self {
|
||||
surface: Arc::clone(&wl_surf),
|
||||
inner: RefCell::new(WaylandWindowInner::new(&wl_surf, bounds)),
|
||||
callbacks: RefCell::new(Callbacks::default()),
|
||||
outputs: RefCell::new(HashSet::default()),
|
||||
toplevel,
|
||||
pub fn new(globals: Globals, params: WindowParams) -> (Self, ObjectId) {
|
||||
let surface = globals.compositor.create_surface(&globals.qh, ());
|
||||
let xdg_surface = globals
|
||||
.wm_base
|
||||
.get_xdg_surface(&surface, &globals.qh, surface.id());
|
||||
let toplevel = xdg_surface.get_toplevel(&globals.qh, surface.id());
|
||||
|
||||
if let Some(fractional_scale_manager) = globals.fractional_scale_manager.as_ref() {
|
||||
fractional_scale_manager.get_fractional_scale(&surface, &globals.qh, surface.id());
|
||||
}
|
||||
|
||||
// Attempt to set up window decorations based on the requested configuration
|
||||
if let Some(decoration_manager) = globals.decoration_manager.as_ref() {
|
||||
let decoration =
|
||||
decoration_manager.get_toplevel_decoration(&toplevel, &globals.qh, surface.id());
|
||||
|
||||
// Request client side decorations if possible
|
||||
decoration.set_mode(zxdg_toplevel_decoration_v1::Mode::ClientSide);
|
||||
}
|
||||
|
||||
let viewport = globals
|
||||
.viewporter
|
||||
.as_ref()
|
||||
.map(|viewporter| viewporter.get_viewport(&surface, &globals.qh, ()));
|
||||
|
||||
surface.frame(&globals.qh, surface.id());
|
||||
|
||||
let window_state = RcRefCell::new(WaylandWindowState::new(
|
||||
surface.clone(),
|
||||
xdg_surface,
|
||||
viewport,
|
||||
fullscreen: RefCell::new(false),
|
||||
toplevel,
|
||||
globals,
|
||||
params,
|
||||
));
|
||||
|
||||
let this = Self {
|
||||
state: window_state,
|
||||
callbacks: Rc::new(RefCell::new(Callbacks::default())),
|
||||
};
|
||||
|
||||
// Kick things off
|
||||
surface.commit();
|
||||
|
||||
(this, surface.id())
|
||||
}
|
||||
|
||||
pub fn frame(&self) {
|
||||
let state = self.state.borrow_mut();
|
||||
state.surface.frame(&state.globals.qh, state.surface.id());
|
||||
drop(state);
|
||||
|
||||
let mut cb = self.callbacks.borrow_mut();
|
||||
if let Some(fun) = cb.request_frame.as_mut() {
|
||||
fun();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&self) {
|
||||
let mut cb = self.callbacks.borrow_mut();
|
||||
if let Some(mut fun) = cb.request_frame.take() {
|
||||
drop(cb);
|
||||
fun();
|
||||
self.callbacks.borrow_mut().request_frame = Some(fun);
|
||||
pub fn handle_toplevel_decoration_event(&self, event: zxdg_toplevel_decoration_v1::Event) {
|
||||
match event {
|
||||
zxdg_toplevel_decoration_v1::Event::Configure { mode } => match mode {
|
||||
WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ServerSide) => {
|
||||
self.set_decoration_state(WaylandDecorationState::Server)
|
||||
}
|
||||
WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ClientSide) => {
|
||||
self.set_decoration_state(WaylandDecorationState::Server)
|
||||
}
|
||||
WEnum::Value(_) => {
|
||||
log::warn!("Unknown decoration mode");
|
||||
}
|
||||
WEnum::Unknown(v) => {
|
||||
log::warn!("Unknown decoration mode: {}", v);
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_fractional_scale_event(&self, event: wp_fractional_scale_v1::Event) {
|
||||
match event {
|
||||
wp_fractional_scale_v1::Event::PreferredScale { scale } => {
|
||||
self.rescale(scale as f32 / 120.0);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_toplevel_event(&self, event: xdg_toplevel::Event) -> bool {
|
||||
match event {
|
||||
xdg_toplevel::Event::Configure {
|
||||
width,
|
||||
height,
|
||||
states,
|
||||
} => {
|
||||
let width = NonZeroU32::new(width as u32);
|
||||
let height = NonZeroU32::new(height as u32);
|
||||
let fullscreen = states.contains(&(xdg_toplevel::State::Fullscreen as u8));
|
||||
let maximized = states.contains(&(xdg_toplevel::State::Maximized as u8));
|
||||
self.resize(width, height);
|
||||
self.set_fullscreen(fullscreen);
|
||||
let mut state = self.state.borrow_mut();
|
||||
state.maximized = true;
|
||||
false
|
||||
}
|
||||
xdg_toplevel::Event::Close => {
|
||||
let mut cb = self.callbacks.borrow_mut();
|
||||
if let Some(mut should_close) = cb.should_close.take() {
|
||||
let result = (should_close)();
|
||||
cb.should_close = Some(should_close);
|
||||
result
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_surface_event(
|
||||
&self,
|
||||
event: wl_surface::Event,
|
||||
output_scales: HashMap<ObjectId, i32>,
|
||||
) {
|
||||
let mut state = self.state.borrow_mut();
|
||||
|
||||
// We use `WpFractionalScale` instead to set the scale if it's available
|
||||
if state.globals.fractional_scale_manager.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
match event {
|
||||
wl_surface::Event::Enter { output } => {
|
||||
// We use `PreferredBufferScale` instead to set the scale if it's available
|
||||
if state.surface.version() >= wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE {
|
||||
return;
|
||||
}
|
||||
|
||||
state.outputs.insert(output.id());
|
||||
|
||||
let mut scale = 1;
|
||||
for output in state.outputs.iter() {
|
||||
if let Some(s) = output_scales.get(output) {
|
||||
scale = scale.max(*s)
|
||||
}
|
||||
}
|
||||
|
||||
state.surface.set_buffer_scale(scale);
|
||||
drop(state);
|
||||
self.rescale(scale as f32);
|
||||
}
|
||||
wl_surface::Event::Leave { output } => {
|
||||
// We use `PreferredBufferScale` instead to set the scale if it's available
|
||||
if state.surface.version() >= wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE {
|
||||
return;
|
||||
}
|
||||
|
||||
state.outputs.remove(&output.id());
|
||||
|
||||
let mut scale = 1;
|
||||
for output in state.outputs.iter() {
|
||||
if let Some(s) = output_scales.get(output) {
|
||||
scale = scale.max(*s)
|
||||
}
|
||||
}
|
||||
|
||||
state.surface.set_buffer_scale(scale);
|
||||
drop(state);
|
||||
self.rescale(scale as f32);
|
||||
}
|
||||
wl_surface::Event::PreferredBufferScale { factor } => {
|
||||
state.surface.set_buffer_scale(factor);
|
||||
drop(state);
|
||||
self.rescale(factor as f32);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,26 +336,26 @@ impl WaylandWindowState {
|
|||
scale: Option<f32>,
|
||||
) {
|
||||
let (width, height, scale) = {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
if width.map_or(true, |width| width.get() == inner.bounds.size.width)
|
||||
&& height.map_or(true, |height| height.get() == inner.bounds.size.height)
|
||||
&& scale.map_or(true, |scale| scale == inner.scale)
|
||||
let mut state = self.state.borrow_mut();
|
||||
if width.map_or(true, |width| width.get() == state.bounds.size.width)
|
||||
&& height.map_or(true, |height| height.get() == state.bounds.size.height)
|
||||
&& scale.map_or(true, |scale| scale == state.scale)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if let Some(width) = width {
|
||||
inner.bounds.size.width = width.get();
|
||||
state.bounds.size.width = width.get();
|
||||
}
|
||||
if let Some(height) = height {
|
||||
inner.bounds.size.height = height.get();
|
||||
state.bounds.size.height = height.get();
|
||||
}
|
||||
if let Some(scale) = scale {
|
||||
inner.scale = scale;
|
||||
state.scale = scale;
|
||||
}
|
||||
let width = inner.bounds.size.width;
|
||||
let height = inner.bounds.size.height;
|
||||
let scale = inner.scale;
|
||||
inner.renderer.update_drawable_size(size(
|
||||
let width = state.bounds.size.width;
|
||||
let height = state.bounds.size.height;
|
||||
let scale = state.scale;
|
||||
state.renderer.update_drawable_size(size(
|
||||
width as f64 * scale as f64,
|
||||
height as f64 * scale as f64,
|
||||
));
|
||||
|
@ -191,8 +372,11 @@ impl WaylandWindowState {
|
|||
);
|
||||
}
|
||||
|
||||
if let Some(viewport) = &self.viewport {
|
||||
viewport.set_destination(width as i32, height as i32);
|
||||
{
|
||||
let state = self.state.borrow();
|
||||
if let Some(viewport) = &state.viewport {
|
||||
viewport.set_destination(width as i32, height as i32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,11 +389,13 @@ impl WaylandWindowState {
|
|||
}
|
||||
|
||||
pub fn set_fullscreen(&self, fullscreen: bool) {
|
||||
let mut state = self.state.borrow_mut();
|
||||
state.fullscreen = fullscreen;
|
||||
|
||||
let mut callbacks = self.callbacks.borrow_mut();
|
||||
if let Some(ref mut fun) = callbacks.fullscreen {
|
||||
fun(fullscreen)
|
||||
}
|
||||
self.fullscreen.replace(fullscreen);
|
||||
}
|
||||
|
||||
/// Notifies the window of the state of the decorations.
|
||||
|
@ -221,9 +407,7 @@ impl WaylandWindowState {
|
|||
/// of the decorations. This is because the state of the decorations
|
||||
/// is managed by the compositor and not the client.
|
||||
pub fn set_decoration_state(&self, state: WaylandDecorationState) {
|
||||
self.inner.borrow_mut().decoration_state = state;
|
||||
log::trace!("Window decorations are now handled by {:?}", state);
|
||||
// todo(linux) - Handle this properly
|
||||
self.state.borrow_mut().decoration_state = state;
|
||||
}
|
||||
|
||||
pub fn close(&self) {
|
||||
|
@ -231,7 +415,7 @@ impl WaylandWindowState {
|
|||
if let Some(fun) = callbacks.close.take() {
|
||||
fun()
|
||||
}
|
||||
self.toplevel.destroy();
|
||||
self.state.borrow_mut().toplevel.destroy();
|
||||
}
|
||||
|
||||
pub fn handle_input(&self, input: PlatformInput) {
|
||||
|
@ -241,10 +425,13 @@ impl WaylandWindowState {
|
|||
}
|
||||
}
|
||||
if let PlatformInput::KeyDown(event) = input {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
if let Some(ref mut input_handler) = inner.input_handler {
|
||||
let mut state = self.state.borrow_mut();
|
||||
if let Some(mut input_handler) = state.input_handler.take() {
|
||||
if let Some(ime_key) = &event.keystroke.ime_key {
|
||||
drop(state);
|
||||
input_handler.replace_text_in_range(None, ime_key);
|
||||
let mut state = self.state.borrow_mut();
|
||||
state.input_handler = Some(input_handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -257,9 +444,6 @@ impl WaylandWindowState {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct WaylandWindow(pub(crate) Rc<WaylandWindowState>);
|
||||
|
||||
impl HasWindowHandle for WaylandWindow {
|
||||
fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
|
||||
unimplemented!()
|
||||
|
@ -273,31 +457,29 @@ impl HasDisplayHandle for WaylandWindow {
|
|||
}
|
||||
|
||||
impl PlatformWindow for WaylandWindow {
|
||||
// todo(linux)
|
||||
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||
unimplemented!()
|
||||
self.state.borrow().bounds.map(|p| DevicePixels(p as i32))
|
||||
}
|
||||
|
||||
// todo(linux)
|
||||
fn is_maximized(&self) -> bool {
|
||||
false
|
||||
self.state.borrow().maximized
|
||||
}
|
||||
|
||||
// todo(linux)
|
||||
fn is_minimized(&self) -> bool {
|
||||
// This cannot be determined by the client
|
||||
false
|
||||
}
|
||||
|
||||
fn content_size(&self) -> Size<Pixels> {
|
||||
let inner = self.0.inner.borrow();
|
||||
let state = self.state.borrow();
|
||||
Size {
|
||||
width: Pixels(inner.bounds.size.width as f32),
|
||||
height: Pixels(inner.bounds.size.height as f32),
|
||||
width: Pixels(state.bounds.size.width as f32),
|
||||
height: Pixels(state.bounds.size.height as f32),
|
||||
}
|
||||
}
|
||||
|
||||
fn scale_factor(&self) -> f32 {
|
||||
self.0.inner.borrow().scale
|
||||
self.state.borrow().scale
|
||||
}
|
||||
|
||||
// todo(linux)
|
||||
|
@ -325,11 +507,11 @@ impl PlatformWindow for WaylandWindow {
|
|||
}
|
||||
|
||||
fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
|
||||
self.0.inner.borrow_mut().input_handler = Some(input_handler);
|
||||
self.state.borrow_mut().input_handler = Some(input_handler);
|
||||
}
|
||||
|
||||
fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
|
||||
self.0.inner.borrow_mut().input_handler.take()
|
||||
self.state.borrow_mut().input_handler.take()
|
||||
}
|
||||
|
||||
fn prompt(
|
||||
|
@ -352,7 +534,10 @@ impl PlatformWindow for WaylandWindow {
|
|||
}
|
||||
|
||||
fn set_title(&mut self, title: &str) {
|
||||
self.0.toplevel.set_title(title.to_string());
|
||||
self.state
|
||||
.borrow_mut()
|
||||
.toplevel
|
||||
.set_title(title.to_string());
|
||||
}
|
||||
|
||||
fn set_background_appearance(&mut self, _background_appearance: WindowBackgroundAppearance) {
|
||||
|
@ -368,7 +553,7 @@ impl PlatformWindow for WaylandWindow {
|
|||
}
|
||||
|
||||
fn minimize(&self) {
|
||||
self.0.toplevel.set_minimized();
|
||||
self.state.borrow_mut().toplevel.set_minimized();
|
||||
}
|
||||
|
||||
fn zoom(&self) {
|
||||
|
@ -376,47 +561,48 @@ impl PlatformWindow for WaylandWindow {
|
|||
}
|
||||
|
||||
fn toggle_fullscreen(&self) {
|
||||
if !(*self.0.fullscreen.borrow()) {
|
||||
self.0.toplevel.set_fullscreen(None);
|
||||
let state = self.state.borrow_mut();
|
||||
if !state.fullscreen {
|
||||
state.toplevel.set_fullscreen(None);
|
||||
} else {
|
||||
self.0.toplevel.unset_fullscreen();
|
||||
state.toplevel.unset_fullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
fn is_fullscreen(&self) -> bool {
|
||||
*self.0.fullscreen.borrow()
|
||||
self.state.borrow().fullscreen
|
||||
}
|
||||
|
||||
fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
|
||||
self.0.callbacks.borrow_mut().request_frame = Some(callback);
|
||||
self.callbacks.borrow_mut().request_frame = Some(callback);
|
||||
}
|
||||
|
||||
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
|
||||
self.0.callbacks.borrow_mut().input = Some(callback);
|
||||
self.callbacks.borrow_mut().input = Some(callback);
|
||||
}
|
||||
|
||||
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
|
||||
self.0.callbacks.borrow_mut().active_status_change = Some(callback);
|
||||
self.callbacks.borrow_mut().active_status_change = Some(callback);
|
||||
}
|
||||
|
||||
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
|
||||
self.0.callbacks.borrow_mut().resize = Some(callback);
|
||||
self.callbacks.borrow_mut().resize = Some(callback);
|
||||
}
|
||||
|
||||
fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
|
||||
self.0.callbacks.borrow_mut().fullscreen = Some(callback);
|
||||
self.callbacks.borrow_mut().fullscreen = Some(callback);
|
||||
}
|
||||
|
||||
fn on_moved(&self, callback: Box<dyn FnMut()>) {
|
||||
self.0.callbacks.borrow_mut().moved = Some(callback);
|
||||
self.callbacks.borrow_mut().moved = Some(callback);
|
||||
}
|
||||
|
||||
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
|
||||
self.0.callbacks.borrow_mut().should_close = Some(callback);
|
||||
self.callbacks.borrow_mut().should_close = Some(callback);
|
||||
}
|
||||
|
||||
fn on_close(&self, callback: Box<dyn FnOnce()>) {
|
||||
self.0.callbacks.borrow_mut().close = Some(callback);
|
||||
self.callbacks.borrow_mut().close = Some(callback);
|
||||
}
|
||||
|
||||
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
|
||||
|
@ -429,12 +615,18 @@ impl PlatformWindow for WaylandWindow {
|
|||
}
|
||||
|
||||
fn draw(&self, scene: &Scene) {
|
||||
self.0.inner.borrow_mut().renderer.draw(scene);
|
||||
let mut state = self.state.borrow_mut();
|
||||
state.renderer.draw(scene);
|
||||
}
|
||||
|
||||
fn completed_frame(&self) {
|
||||
let mut state = self.state.borrow_mut();
|
||||
state.surface.commit();
|
||||
}
|
||||
|
||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
||||
let inner = self.0.inner.borrow();
|
||||
inner.renderer.sprite_atlas().clone()
|
||||
let state = self.state.borrow();
|
||||
state.renderer.sprite_atlas().clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
use std::cell::RefCell;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use calloop::{EventLoop, LoopHandle};
|
||||
use collections::HashMap;
|
||||
use copypasta::x11_clipboard::{Clipboard, Primary, X11ClipboardContext};
|
||||
use copypasta::ClipboardProvider;
|
||||
|
||||
use util::ResultExt;
|
||||
use x11rb::connection::{Connection, RequestConnection};
|
||||
use x11rb::errors::ConnectionError;
|
||||
use x11rb::protocol::randr::ConnectionExt as _;
|
||||
|
@ -16,50 +19,71 @@ use x11rb::xcb_ffi::XCBConnection;
|
|||
use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION};
|
||||
use xkbcommon::xkb as xkbc;
|
||||
|
||||
use crate::platform::linux::client::Client;
|
||||
use crate::platform::{LinuxPlatformInner, PlatformWindow};
|
||||
use crate::platform::linux::LinuxClient;
|
||||
use crate::platform::{LinuxCommon, PlatformWindow};
|
||||
use crate::{
|
||||
px, AnyWindowHandle, Bounds, CursorStyle, DisplayId, Pixels, PlatformDisplay, PlatformInput,
|
||||
Point, ScrollDelta, Size, TouchPhase, WindowParams,
|
||||
};
|
||||
|
||||
use super::{super::SCROLL_LINES, X11Display, X11Window, X11WindowState, XcbAtoms};
|
||||
use super::{super::SCROLL_LINES, X11Display, X11Window, 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;
|
||||
use crate::platform::linux::util::is_within_click_distance;
|
||||
use calloop::{
|
||||
generic::{FdWrapper, Generic},
|
||||
RegistrationToken,
|
||||
};
|
||||
|
||||
struct WindowRef {
|
||||
state: Rc<X11WindowState>,
|
||||
pub(crate) struct WindowRef {
|
||||
window: X11Window,
|
||||
refresh_event_token: RegistrationToken,
|
||||
}
|
||||
|
||||
struct X11ClientState {
|
||||
windows: HashMap<xproto::Window, WindowRef>,
|
||||
xkb: xkbc::State,
|
||||
clipboard: Rc<RefCell<X11ClipboardContext<Clipboard>>>,
|
||||
primary: Rc<RefCell<X11ClipboardContext<Primary>>>,
|
||||
click_state: ClickState,
|
||||
impl Deref for WindowRef {
|
||||
type Target = X11Window;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.window
|
||||
}
|
||||
}
|
||||
|
||||
struct ClickState {
|
||||
last_click: Instant,
|
||||
last_location: Point<Pixels>,
|
||||
current_count: usize,
|
||||
pub struct X11ClientState {
|
||||
pub(crate) loop_handle: LoopHandle<'static, X11Client>,
|
||||
pub(crate) event_loop: Option<calloop::EventLoop<'static, X11Client>>,
|
||||
|
||||
pub(crate) last_click: Instant,
|
||||
pub(crate) last_location: Point<Pixels>,
|
||||
pub(crate) current_count: usize,
|
||||
|
||||
pub(crate) xcb_connection: Rc<XCBConnection>,
|
||||
pub(crate) x_root_index: usize,
|
||||
pub(crate) atoms: XcbAtoms,
|
||||
pub(crate) windows: HashMap<xproto::Window, WindowRef>,
|
||||
pub(crate) xkb: xkbc::State,
|
||||
|
||||
pub(crate) common: LinuxCommon,
|
||||
pub(crate) clipboard: X11ClipboardContext<Clipboard>,
|
||||
pub(crate) primary: X11ClipboardContext<Primary>,
|
||||
}
|
||||
|
||||
pub(crate) struct X11Client {
|
||||
platform_inner: Rc<LinuxPlatformInner>,
|
||||
xcb_connection: Rc<XCBConnection>,
|
||||
x_root_index: usize,
|
||||
atoms: XcbAtoms,
|
||||
state: RefCell<X11ClientState>,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct X11Client(Rc<RefCell<X11ClientState>>);
|
||||
|
||||
impl X11Client {
|
||||
pub(crate) fn new(inner: Rc<LinuxPlatformInner>) -> Rc<Self> {
|
||||
pub(crate) fn new() -> Self {
|
||||
let event_loop = EventLoop::try_new().unwrap();
|
||||
|
||||
let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
|
||||
|
||||
let handle = event_loop.handle();
|
||||
|
||||
handle.insert_source(main_receiver, |event, _, _: &mut X11Client| {
|
||||
if let calloop::channel::Event::Msg(runnable) = event {
|
||||
runnable.run();
|
||||
}
|
||||
});
|
||||
|
||||
let (xcb_connection, x_root_index) = XCBConnection::connect(None).unwrap();
|
||||
xcb_connection
|
||||
.prefetch_extension_information(xkb::X11_EXTENSION_NAME)
|
||||
|
@ -94,30 +118,10 @@ impl X11Client {
|
|||
|
||||
let xcb_connection = Rc::new(xcb_connection);
|
||||
|
||||
let click_state = ClickState {
|
||||
last_click: Instant::now(),
|
||||
last_location: Point::new(px(0.0), px(0.0)),
|
||||
current_count: 0,
|
||||
};
|
||||
let client: Rc<X11Client> = Rc::new(Self {
|
||||
platform_inner: inner.clone(),
|
||||
xcb_connection: Rc::clone(&xcb_connection),
|
||||
x_root_index,
|
||||
atoms,
|
||||
state: RefCell::new(X11ClientState {
|
||||
windows: HashMap::default(),
|
||||
xkb: xkb_state,
|
||||
clipboard: Rc::new(RefCell::new(clipboard)),
|
||||
primary: Rc::new(RefCell::new(primary)),
|
||||
click_state,
|
||||
}),
|
||||
});
|
||||
|
||||
// Safety: Safe if xcb::Connection always returns a valid fd
|
||||
let fd = unsafe { FdWrapper::new(Rc::clone(&xcb_connection)) };
|
||||
|
||||
inner
|
||||
.loop_handle
|
||||
handle
|
||||
.insert_source(
|
||||
Generic::new_with_error::<ConnectionError>(
|
||||
fd,
|
||||
|
@ -125,8 +129,8 @@ impl X11Client {
|
|||
calloop::Mode::Level,
|
||||
),
|
||||
{
|
||||
let client = Rc::clone(&client);
|
||||
move |_readiness, _, _| {
|
||||
let xcb_connection = xcb_connection.clone();
|
||||
move |_readiness, _, client| {
|
||||
while let Some(event) = xcb_connection.poll_for_event()? {
|
||||
client.handle_event(event);
|
||||
}
|
||||
|
@ -136,34 +140,47 @@ impl X11Client {
|
|||
)
|
||||
.expect("Failed to initialize x11 event source");
|
||||
|
||||
client
|
||||
X11Client(Rc::new(RefCell::new(X11ClientState {
|
||||
event_loop: Some(event_loop),
|
||||
loop_handle: handle,
|
||||
common,
|
||||
last_click: Instant::now(),
|
||||
last_location: Point::new(px(0.0), px(0.0)),
|
||||
current_count: 0,
|
||||
|
||||
xcb_connection,
|
||||
x_root_index,
|
||||
atoms,
|
||||
windows: HashMap::default(),
|
||||
xkb: xkb_state,
|
||||
clipboard,
|
||||
primary,
|
||||
})))
|
||||
}
|
||||
|
||||
fn get_window(&self, win: xproto::Window) -> Option<Rc<X11WindowState>> {
|
||||
let state = self.state.borrow();
|
||||
state.windows.get(&win).map(|wr| Rc::clone(&wr.state))
|
||||
fn get_window(&self, win: xproto::Window) -> Option<X11Window> {
|
||||
let state = self.0.borrow();
|
||||
state
|
||||
.windows
|
||||
.get(&win)
|
||||
.map(|window_reference| window_reference.window.clone())
|
||||
}
|
||||
|
||||
fn handle_event(&self, event: Event) -> Option<()> {
|
||||
match event {
|
||||
Event::ClientMessage(event) => {
|
||||
let [atom, ..] = event.data.as_data32();
|
||||
if atom == self.atoms.WM_DELETE_WINDOW {
|
||||
let mut state = self.0.borrow_mut();
|
||||
|
||||
if atom == state.atoms.WM_DELETE_WINDOW {
|
||||
// window "x" button clicked by user, we gracefully exit
|
||||
let window_ref = self
|
||||
.state
|
||||
.borrow_mut()
|
||||
.windows
|
||||
.remove(&event.window)
|
||||
.unwrap();
|
||||
let window_ref = state.windows.remove(&event.window)?;
|
||||
|
||||
self.platform_inner
|
||||
.loop_handle
|
||||
.remove(window_ref.refresh_event_token);
|
||||
window_ref.state.destroy();
|
||||
state.loop_handle.remove(window_ref.refresh_event_token);
|
||||
window_ref.window.destroy();
|
||||
|
||||
if self.state.borrow().windows.is_empty() {
|
||||
self.platform_inner.loop_signal.stop();
|
||||
if state.windows.is_empty() {
|
||||
state.common.signal.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -195,15 +212,17 @@ impl X11Client {
|
|||
}
|
||||
Event::KeyPress(event) => {
|
||||
let window = self.get_window(event.event)?;
|
||||
let modifiers = super::modifiers_from_state(event.state);
|
||||
let mut state = self.0.borrow_mut();
|
||||
|
||||
let modifiers = modifiers_from_state(event.state);
|
||||
let keystroke = {
|
||||
let code = event.detail.into();
|
||||
let mut state = self.state.borrow_mut();
|
||||
let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
|
||||
state.xkb.update_key(code, xkbc::KeyDirection::Down);
|
||||
keystroke
|
||||
};
|
||||
|
||||
drop(state);
|
||||
window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
|
||||
keystroke,
|
||||
is_held: false,
|
||||
|
@ -211,48 +230,54 @@ impl X11Client {
|
|||
}
|
||||
Event::KeyRelease(event) => {
|
||||
let window = self.get_window(event.event)?;
|
||||
let modifiers = super::modifiers_from_state(event.state);
|
||||
let mut state = self.0.borrow_mut();
|
||||
|
||||
let modifiers = modifiers_from_state(event.state);
|
||||
let keystroke = {
|
||||
let code = event.detail.into();
|
||||
let mut state = self.state.borrow_mut();
|
||||
let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
|
||||
state.xkb.update_key(code, xkbc::KeyDirection::Up);
|
||||
keystroke
|
||||
};
|
||||
|
||||
drop(state);
|
||||
window.handle_input(PlatformInput::KeyUp(crate::KeyUpEvent { keystroke }));
|
||||
}
|
||||
Event::ButtonPress(event) => {
|
||||
let window = self.get_window(event.event)?;
|
||||
let modifiers = super::modifiers_from_state(event.state);
|
||||
let mut state = self.0.borrow_mut();
|
||||
|
||||
let modifiers = modifiers_from_state(event.state);
|
||||
let position =
|
||||
Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
|
||||
if let Some(button) = super::button_of_key(event.detail) {
|
||||
let mut state = self.state.borrow_mut();
|
||||
let click_elapsed = state.click_state.last_click.elapsed();
|
||||
if let Some(button) = button_of_key(event.detail) {
|
||||
let click_elapsed = state.last_click.elapsed();
|
||||
|
||||
if click_elapsed < DOUBLE_CLICK_INTERVAL
|
||||
&& is_within_click_distance(state.click_state.last_location, position)
|
||||
&& is_within_click_distance(state.last_location, position)
|
||||
{
|
||||
state.click_state.current_count += 1;
|
||||
state.current_count += 1;
|
||||
} else {
|
||||
state.click_state.current_count = 1;
|
||||
state.current_count = 1;
|
||||
}
|
||||
|
||||
state.click_state.last_click = Instant::now();
|
||||
state.click_state.last_location = position;
|
||||
state.last_click = Instant::now();
|
||||
state.last_location = position;
|
||||
let current_count = state.current_count;
|
||||
|
||||
drop(state);
|
||||
window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent {
|
||||
button,
|
||||
position,
|
||||
modifiers,
|
||||
click_count: state.click_state.current_count,
|
||||
click_count: current_count,
|
||||
first_mouse: false,
|
||||
}));
|
||||
} else if event.detail >= 4 && event.detail <= 5 {
|
||||
// https://stackoverflow.com/questions/15510472/scrollwheel-event-in-x11
|
||||
let scroll_direction = if event.detail == 4 { 1.0 } else { -1.0 };
|
||||
let scroll_y = SCROLL_LINES * scroll_direction;
|
||||
|
||||
drop(state);
|
||||
window.handle_input(PlatformInput::ScrollWheel(crate::ScrollWheelEvent {
|
||||
position,
|
||||
delta: ScrollDelta::Lines(Point::new(0.0, scroll_y as f32)),
|
||||
|
@ -265,16 +290,18 @@ impl X11Client {
|
|||
}
|
||||
Event::ButtonRelease(event) => {
|
||||
let window = self.get_window(event.event)?;
|
||||
let modifiers = super::modifiers_from_state(event.state);
|
||||
let state = self.0.borrow();
|
||||
let modifiers = modifiers_from_state(event.state);
|
||||
let position =
|
||||
Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
|
||||
let state = self.state.borrow();
|
||||
if let Some(button) = super::button_of_key(event.detail) {
|
||||
if let Some(button) = button_of_key(event.detail) {
|
||||
let click_count = state.current_count;
|
||||
drop(state);
|
||||
window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent {
|
||||
button,
|
||||
position,
|
||||
modifiers,
|
||||
click_count: state.click_state.current_count,
|
||||
click_count,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -283,7 +310,7 @@ impl X11Client {
|
|||
let pressed_button = super::button_from_state(event.state);
|
||||
let position =
|
||||
Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
|
||||
let modifiers = super::modifiers_from_state(event.state);
|
||||
let modifiers = modifiers_from_state(event.state);
|
||||
window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent {
|
||||
pressed_button,
|
||||
position,
|
||||
|
@ -295,7 +322,7 @@ impl X11Client {
|
|||
let pressed_button = super::button_from_state(event.state);
|
||||
let position =
|
||||
Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
|
||||
let modifiers = super::modifiers_from_state(event.state);
|
||||
let modifiers = modifiers_from_state(event.state);
|
||||
window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
|
||||
pressed_button,
|
||||
position,
|
||||
|
@ -309,61 +336,71 @@ impl X11Client {
|
|||
}
|
||||
}
|
||||
|
||||
impl Client for X11Client {
|
||||
impl LinuxClient for X11Client {
|
||||
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R {
|
||||
f(&mut self.0.borrow_mut().common)
|
||||
}
|
||||
|
||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
|
||||
let setup = self.xcb_connection.setup();
|
||||
let state = self.0.borrow();
|
||||
let setup = state.xcb_connection.setup();
|
||||
setup
|
||||
.roots
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(root_id, _)| {
|
||||
Some(Rc::new(X11Display::new(&self.xcb_connection, root_id)?)
|
||||
Some(Rc::new(X11Display::new(&state.xcb_connection, root_id)?)
|
||||
as Rc<dyn PlatformDisplay>)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
|
||||
Some(Rc::new(X11Display::new(
|
||||
&self.xcb_connection,
|
||||
id.0 as usize,
|
||||
)?))
|
||||
}
|
||||
|
||||
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
|
||||
let state = self.0.borrow();
|
||||
|
||||
Some(Rc::new(
|
||||
X11Display::new(&self.xcb_connection, self.x_root_index)
|
||||
X11Display::new(&state.xcb_connection, state.x_root_index)
|
||||
.expect("There should always be a root index"),
|
||||
))
|
||||
}
|
||||
|
||||
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
|
||||
let state = self.0.borrow();
|
||||
|
||||
Some(Rc::new(X11Display::new(
|
||||
&state.xcb_connection,
|
||||
id.0 as usize,
|
||||
)?))
|
||||
}
|
||||
|
||||
fn open_window(
|
||||
&self,
|
||||
_handle: AnyWindowHandle,
|
||||
options: WindowParams,
|
||||
params: WindowParams,
|
||||
) -> Box<dyn PlatformWindow> {
|
||||
let x_window = self.xcb_connection.generate_id().unwrap();
|
||||
let mut state = self.0.borrow_mut();
|
||||
let x_window = state.xcb_connection.generate_id().unwrap();
|
||||
|
||||
let window_ptr = Rc::new(X11WindowState::new(
|
||||
options,
|
||||
&self.xcb_connection,
|
||||
self.x_root_index,
|
||||
let window = X11Window::new(
|
||||
params,
|
||||
&state.xcb_connection,
|
||||
state.x_root_index,
|
||||
x_window,
|
||||
&self.atoms,
|
||||
));
|
||||
&state.atoms,
|
||||
);
|
||||
|
||||
let screen_resources = self
|
||||
let screen_resources = state
|
||||
.xcb_connection
|
||||
.randr_get_screen_resources(x_window)
|
||||
.unwrap()
|
||||
.reply()
|
||||
.expect("TODO");
|
||||
.expect("Could not find available screens");
|
||||
|
||||
let mode = screen_resources
|
||||
.crtcs
|
||||
.iter()
|
||||
.find_map(|crtc| {
|
||||
let crtc_info = self
|
||||
let crtc_info = state
|
||||
.xcb_connection
|
||||
.randr_get_crtc_info(*crtc, x11rb::CURRENT_TIME)
|
||||
.ok()?
|
||||
|
@ -377,16 +414,14 @@ impl Client for X11Client {
|
|||
})
|
||||
.expect("Unable to find screen refresh rate");
|
||||
|
||||
// .expect("Missing screen mode for crtc specified mode id");
|
||||
|
||||
let refresh_event_token = self
|
||||
.platform_inner
|
||||
let refresh_event_token = state
|
||||
.loop_handle
|
||||
.insert_source(calloop::timer::Timer::immediate(), {
|
||||
let refresh_duration = mode_refresh_rate(mode);
|
||||
let xcb_connection = Rc::clone(&self.xcb_connection);
|
||||
move |mut instant, (), _| {
|
||||
xcb_connection
|
||||
move |mut instant, (), client| {
|
||||
let state = client.0.borrow_mut();
|
||||
state
|
||||
.xcb_connection
|
||||
.send_event(
|
||||
false,
|
||||
x_window,
|
||||
|
@ -403,7 +438,7 @@ impl Client for X11Client {
|
|||
},
|
||||
)
|
||||
.unwrap();
|
||||
let _ = xcb_connection.flush().unwrap();
|
||||
let _ = state.xcb_connection.flush().unwrap();
|
||||
// Take into account that some frames have been skipped
|
||||
let now = time::Instant::now();
|
||||
while instant < now {
|
||||
|
@ -415,22 +450,42 @@ impl Client for X11Client {
|
|||
.expect("Failed to initialize refresh timer");
|
||||
|
||||
let window_ref = WindowRef {
|
||||
state: Rc::clone(&window_ptr),
|
||||
window: window.clone(),
|
||||
refresh_event_token,
|
||||
};
|
||||
self.state.borrow_mut().windows.insert(x_window, window_ref);
|
||||
Box::new(X11Window(window_ptr))
|
||||
|
||||
state.windows.insert(x_window, window_ref);
|
||||
Box::new(window)
|
||||
}
|
||||
|
||||
//todo(linux)
|
||||
fn set_cursor_style(&self, _style: CursorStyle) {}
|
||||
|
||||
fn get_clipboard(&self) -> Rc<RefCell<dyn ClipboardProvider>> {
|
||||
self.state.borrow().clipboard.clone()
|
||||
fn write_to_clipboard(&self, item: crate::ClipboardItem) {
|
||||
self.0.borrow_mut().clipboard.set_contents(item.text);
|
||||
}
|
||||
|
||||
fn get_primary(&self) -> Rc<RefCell<dyn ClipboardProvider>> {
|
||||
self.state.borrow().primary.clone()
|
||||
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
|
||||
self.0
|
||||
.borrow_mut()
|
||||
.clipboard
|
||||
.get_contents()
|
||||
.ok()
|
||||
.map(|text| crate::ClipboardItem {
|
||||
text,
|
||||
metadata: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn run(&self) {
|
||||
let mut event_loop = self
|
||||
.0
|
||||
.borrow_mut()
|
||||
.event_loop
|
||||
.take()
|
||||
.expect("App is already running");
|
||||
|
||||
event_loop.run(None, &mut self.clone(), |_| {}).log_err();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,10 +5,12 @@ use crate::{
|
|||
platform::blade::BladeRenderer, size, Bounds, DevicePixels, Modifiers, Pixels, PlatformAtlas,
|
||||
PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptLevel,
|
||||
Scene, Size, WindowAppearance, WindowBackgroundAppearance, WindowOptions, WindowParams,
|
||||
X11Client, X11ClientState,
|
||||
};
|
||||
use blade_graphics as gpu;
|
||||
use parking_lot::Mutex;
|
||||
use raw_window_handle as rwh;
|
||||
use util::ResultExt;
|
||||
use x11rb::{
|
||||
connection::Connection,
|
||||
protocol::xproto::{self, ConnectionExt as _, CreateWindowAux},
|
||||
|
@ -17,8 +19,9 @@ use x11rb::{
|
|||
};
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
ffi::c_void,
|
||||
iter::Zip,
|
||||
mem,
|
||||
num::NonZeroU32,
|
||||
ptr::NonNull,
|
||||
|
@ -28,19 +31,6 @@ use std::{
|
|||
|
||||
use super::X11Display;
|
||||
|
||||
#[derive(Default)]
|
||||
struct Callbacks {
|
||||
request_frame: Option<Box<dyn FnMut()>>,
|
||||
input: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
|
||||
active_status_change: Option<Box<dyn FnMut(bool)>>,
|
||||
resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
|
||||
fullscreen: Option<Box<dyn FnMut(bool)>>,
|
||||
moved: Option<Box<dyn FnMut()>>,
|
||||
should_close: Option<Box<dyn FnMut() -> bool>>,
|
||||
close: Option<Box<dyn FnOnce()>>,
|
||||
appearance_changed: Option<Box<dyn FnMut()>>,
|
||||
}
|
||||
|
||||
x11rb::atom_manager! {
|
||||
pub XcbAtoms: AtomsCookie {
|
||||
WM_PROTOCOLS,
|
||||
|
@ -51,23 +41,6 @@ x11rb::atom_manager! {
|
|||
}
|
||||
}
|
||||
|
||||
struct LinuxWindowInner {
|
||||
bounds: Bounds<i32>,
|
||||
scale_factor: f32,
|
||||
renderer: BladeRenderer,
|
||||
input_handler: Option<PlatformInputHandler>,
|
||||
}
|
||||
|
||||
impl LinuxWindowInner {
|
||||
fn content_size(&self) -> Size<Pixels> {
|
||||
let size = self.renderer.viewport_size();
|
||||
Size {
|
||||
width: size.width.into(),
|
||||
height: size.height.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn query_render_extent(xcb_connection: &XCBConnection, x_window: xproto::Window) -> gpu::Extent {
|
||||
let reply = xcb_connection
|
||||
.get_geometry(x_window)
|
||||
|
@ -88,17 +61,37 @@ struct RawWindow {
|
|||
visual_id: u32,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Callbacks {
|
||||
request_frame: Option<Box<dyn FnMut()>>,
|
||||
input: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
|
||||
active_status_change: Option<Box<dyn FnMut(bool)>>,
|
||||
resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
|
||||
fullscreen: Option<Box<dyn FnMut(bool)>>,
|
||||
moved: Option<Box<dyn FnMut()>>,
|
||||
should_close: Option<Box<dyn FnMut() -> bool>>,
|
||||
close: Option<Box<dyn FnOnce()>>,
|
||||
appearance_changed: Option<Box<dyn FnMut()>>,
|
||||
}
|
||||
|
||||
pub(crate) struct X11WindowState {
|
||||
xcb_connection: Rc<XCBConnection>,
|
||||
display: Rc<dyn PlatformDisplay>,
|
||||
raw: RawWindow,
|
||||
x_window: xproto::Window,
|
||||
callbacks: RefCell<Callbacks>,
|
||||
inner: RefCell<LinuxWindowInner>,
|
||||
|
||||
bounds: Bounds<i32>,
|
||||
scale_factor: f32,
|
||||
renderer: BladeRenderer,
|
||||
display: Rc<dyn PlatformDisplay>,
|
||||
|
||||
input_handler: Option<PlatformInputHandler>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct X11Window(pub(crate) Rc<X11WindowState>);
|
||||
pub(crate) struct X11Window {
|
||||
pub(crate) state: Rc<RefCell<X11WindowState>>,
|
||||
pub(crate) callbacks: Rc<RefCell<Callbacks>>,
|
||||
xcb_connection: Rc<XCBConnection>,
|
||||
x_window: xproto::Window,
|
||||
}
|
||||
|
||||
// todo(linux): Remove other RawWindowHandle implementation
|
||||
unsafe impl blade_rwh::HasRawWindowHandle for RawWindow {
|
||||
|
@ -121,7 +114,7 @@ unsafe impl blade_rwh::HasRawDisplayHandle for RawWindow {
|
|||
impl rwh::HasWindowHandle for X11Window {
|
||||
fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
|
||||
Ok(unsafe {
|
||||
let non_zero = NonZeroU32::new(self.0.raw.window_id).unwrap();
|
||||
let non_zero = NonZeroU32::new(self.state.borrow().raw.window_id).unwrap();
|
||||
let handle = rwh::XcbWindowHandle::new(non_zero);
|
||||
rwh::WindowHandle::borrow_raw(handle.into())
|
||||
})
|
||||
|
@ -130,8 +123,9 @@ impl rwh::HasWindowHandle for X11Window {
|
|||
impl rwh::HasDisplayHandle for X11Window {
|
||||
fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
|
||||
Ok(unsafe {
|
||||
let non_zero = NonNull::new(self.0.raw.connection).unwrap();
|
||||
let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.0.raw.screen_id as i32);
|
||||
let this = self.state.borrow();
|
||||
let non_zero = NonNull::new(this.raw.connection).unwrap();
|
||||
let handle = rwh::XcbDisplayHandle::new(Some(non_zero), this.raw.screen_id as i32);
|
||||
rwh::DisplayHandle::borrow_raw(handle.into())
|
||||
})
|
||||
}
|
||||
|
@ -239,22 +233,52 @@ impl X11WindowState {
|
|||
let gpu_extent = query_render_extent(xcb_connection, x_window);
|
||||
|
||||
Self {
|
||||
xcb_connection: xcb_connection.clone(),
|
||||
display: Rc::new(X11Display::new(xcb_connection, x_screen_index).unwrap()),
|
||||
raw,
|
||||
bounds: params.bounds.map(|v| v.0),
|
||||
scale_factor: 1.0,
|
||||
renderer: BladeRenderer::new(gpu, gpu_extent),
|
||||
|
||||
input_handler: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn content_size(&self) -> Size<Pixels> {
|
||||
let size = self.renderer.viewport_size();
|
||||
Size {
|
||||
width: size.width.into(),
|
||||
height: size.height.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl X11Window {
|
||||
pub fn new(
|
||||
params: WindowParams,
|
||||
xcb_connection: &Rc<XCBConnection>,
|
||||
x_main_screen_index: usize,
|
||||
x_window: xproto::Window,
|
||||
atoms: &XcbAtoms,
|
||||
) -> Self {
|
||||
X11Window {
|
||||
state: Rc::new(RefCell::new(X11WindowState::new(
|
||||
params,
|
||||
xcb_connection,
|
||||
x_main_screen_index,
|
||||
x_window,
|
||||
atoms,
|
||||
))),
|
||||
callbacks: Rc::new(RefCell::new(Callbacks::default())),
|
||||
xcb_connection: xcb_connection.clone(),
|
||||
x_window,
|
||||
callbacks: RefCell::new(Callbacks::default()),
|
||||
inner: RefCell::new(LinuxWindowInner {
|
||||
bounds: params.bounds.map(|v| v.0),
|
||||
scale_factor: 1.0,
|
||||
renderer: BladeRenderer::new(gpu, gpu_extent),
|
||||
input_handler: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn destroy(&self) {
|
||||
self.inner.borrow_mut().renderer.destroy();
|
||||
let mut state = self.state.borrow_mut();
|
||||
state.renderer.destroy();
|
||||
drop(state);
|
||||
|
||||
self.xcb_connection.unmap_window(self.x_window).unwrap();
|
||||
self.xcb_connection.destroy_window(self.x_window).unwrap();
|
||||
if let Some(fun) = self.callbacks.borrow_mut().close.take() {
|
||||
|
@ -270,21 +294,40 @@ impl X11WindowState {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn handle_input(&self, input: PlatformInput) {
|
||||
if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
|
||||
if !fun(input.clone()).propagate {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let PlatformInput::KeyDown(event) = input {
|
||||
let mut state = self.state.borrow_mut();
|
||||
if let Some(mut input_handler) = state.input_handler.take() {
|
||||
if let Some(ime_key) = &event.keystroke.ime_key {
|
||||
drop(state);
|
||||
input_handler.replace_text_in_range(None, ime_key);
|
||||
state = self.state.borrow_mut();
|
||||
}
|
||||
state.input_handler = Some(input_handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn configure(&self, bounds: Bounds<i32>) {
|
||||
let mut resize_args = None;
|
||||
let do_move;
|
||||
{
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
let old_bounds = mem::replace(&mut inner.bounds, bounds);
|
||||
let mut state = self.state.borrow_mut();
|
||||
let old_bounds = mem::replace(&mut state.bounds, bounds);
|
||||
do_move = old_bounds.origin != bounds.origin;
|
||||
// todo(linux): use normal GPUI types here, refactor out the double
|
||||
// viewport check and extra casts ( )
|
||||
let gpu_size = query_render_extent(&self.xcb_connection, self.x_window);
|
||||
if inner.renderer.viewport_size() != gpu_size {
|
||||
inner
|
||||
if state.renderer.viewport_size() != gpu_size {
|
||||
state
|
||||
.renderer
|
||||
.update_drawable_size(size(gpu_size.width as f64, gpu_size.height as f64));
|
||||
resize_args = Some((inner.content_size(), inner.scale_factor));
|
||||
resize_args = Some((state.content_size(), state.scale_factor));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -301,22 +344,6 @@ impl X11WindowState {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn handle_input(&self, input: PlatformInput) {
|
||||
if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
|
||||
if !fun(input.clone()).propagate {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let PlatformInput::KeyDown(event) = input {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
if let Some(ref mut input_handler) = inner.input_handler {
|
||||
if let Some(ime_key) = &event.keystroke.ime_key {
|
||||
input_handler.replace_text_in_range(None, ime_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_focused(&self, focus: bool) {
|
||||
if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
|
||||
fun(focus);
|
||||
|
@ -326,7 +353,7 @@ impl X11WindowState {
|
|||
|
||||
impl PlatformWindow for X11Window {
|
||||
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||
self.0.inner.borrow_mut().bounds.map(|v| v.into())
|
||||
self.state.borrow_mut().bounds.map(|v| v.into())
|
||||
}
|
||||
|
||||
// todo(linux)
|
||||
|
@ -340,11 +367,11 @@ impl PlatformWindow for X11Window {
|
|||
}
|
||||
|
||||
fn content_size(&self) -> Size<Pixels> {
|
||||
self.0.inner.borrow_mut().content_size()
|
||||
self.state.borrow_mut().content_size()
|
||||
}
|
||||
|
||||
fn scale_factor(&self) -> f32 {
|
||||
self.0.inner.borrow_mut().scale_factor
|
||||
self.state.borrow_mut().scale_factor
|
||||
}
|
||||
|
||||
// todo(linux)
|
||||
|
@ -353,14 +380,13 @@ impl PlatformWindow for X11Window {
|
|||
}
|
||||
|
||||
fn display(&self) -> Rc<dyn PlatformDisplay> {
|
||||
Rc::clone(&self.0.display)
|
||||
self.state.borrow().display.clone()
|
||||
}
|
||||
|
||||
fn mouse_position(&self) -> Point<Pixels> {
|
||||
let reply = self
|
||||
.0
|
||||
.xcb_connection
|
||||
.query_pointer(self.0.x_window)
|
||||
.query_pointer(self.x_window)
|
||||
.unwrap()
|
||||
.reply()
|
||||
.unwrap();
|
||||
|
@ -377,11 +403,11 @@ impl PlatformWindow for X11Window {
|
|||
}
|
||||
|
||||
fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
|
||||
self.0.inner.borrow_mut().input_handler = Some(input_handler);
|
||||
self.state.borrow_mut().input_handler = Some(input_handler);
|
||||
}
|
||||
|
||||
fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
|
||||
self.0.inner.borrow_mut().input_handler.take()
|
||||
self.state.borrow_mut().input_handler.take()
|
||||
}
|
||||
|
||||
fn prompt(
|
||||
|
@ -396,10 +422,9 @@ impl PlatformWindow for X11Window {
|
|||
|
||||
fn activate(&self) {
|
||||
let win_aux = xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE);
|
||||
self.0
|
||||
.xcb_connection
|
||||
.configure_window(self.0.x_window, &win_aux)
|
||||
.unwrap();
|
||||
self.xcb_connection
|
||||
.configure_window(self.x_window, &win_aux)
|
||||
.log_err();
|
||||
}
|
||||
|
||||
// todo(linux)
|
||||
|
@ -408,11 +433,10 @@ impl PlatformWindow for X11Window {
|
|||
}
|
||||
|
||||
fn set_title(&mut self, title: &str) {
|
||||
self.0
|
||||
.xcb_connection
|
||||
self.xcb_connection
|
||||
.change_property8(
|
||||
xproto::PropMode::REPLACE,
|
||||
self.0.x_window,
|
||||
self.x_window,
|
||||
xproto::AtomEnum::WM_NAME,
|
||||
xproto::AtomEnum::STRING,
|
||||
title.as_bytes(),
|
||||
|
@ -458,39 +482,39 @@ impl PlatformWindow for X11Window {
|
|||
}
|
||||
|
||||
fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
|
||||
self.0.callbacks.borrow_mut().request_frame = Some(callback);
|
||||
self.callbacks.borrow_mut().request_frame = Some(callback);
|
||||
}
|
||||
|
||||
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
|
||||
self.0.callbacks.borrow_mut().input = Some(callback);
|
||||
self.callbacks.borrow_mut().input = Some(callback);
|
||||
}
|
||||
|
||||
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
|
||||
self.0.callbacks.borrow_mut().active_status_change = Some(callback);
|
||||
self.callbacks.borrow_mut().active_status_change = Some(callback);
|
||||
}
|
||||
|
||||
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
|
||||
self.0.callbacks.borrow_mut().resize = Some(callback);
|
||||
self.callbacks.borrow_mut().resize = Some(callback);
|
||||
}
|
||||
|
||||
fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
|
||||
self.0.callbacks.borrow_mut().fullscreen = Some(callback);
|
||||
self.callbacks.borrow_mut().fullscreen = Some(callback);
|
||||
}
|
||||
|
||||
fn on_moved(&self, callback: Box<dyn FnMut()>) {
|
||||
self.0.callbacks.borrow_mut().moved = Some(callback);
|
||||
self.callbacks.borrow_mut().moved = Some(callback);
|
||||
}
|
||||
|
||||
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
|
||||
self.0.callbacks.borrow_mut().should_close = Some(callback);
|
||||
self.callbacks.borrow_mut().should_close = Some(callback);
|
||||
}
|
||||
|
||||
fn on_close(&self, callback: Box<dyn FnOnce()>) {
|
||||
self.0.callbacks.borrow_mut().close = Some(callback);
|
||||
self.callbacks.borrow_mut().close = Some(callback);
|
||||
}
|
||||
|
||||
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
|
||||
self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
|
||||
self.callbacks.borrow_mut().appearance_changed = Some(callback);
|
||||
}
|
||||
|
||||
// todo(linux)
|
||||
|
@ -499,12 +523,12 @@ impl PlatformWindow for X11Window {
|
|||
}
|
||||
|
||||
fn draw(&self, scene: &Scene) {
|
||||
let mut inner = self.0.inner.borrow_mut();
|
||||
let mut inner = self.state.borrow_mut();
|
||||
inner.renderer.draw(scene);
|
||||
}
|
||||
|
||||
fn sprite_atlas(&self) -> sync::Arc<dyn PlatformAtlas> {
|
||||
let inner = self.0.inner.borrow_mut();
|
||||
let inner = self.state.borrow();
|
||||
inner.renderer.sprite_atlas().clone()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -476,6 +476,12 @@ impl Window {
|
|||
} else if needs_present {
|
||||
handle.update(&mut cx, |_, cx| cx.present()).log_err();
|
||||
}
|
||||
|
||||
handle
|
||||
.update(&mut cx, |_, cx| {
|
||||
cx.complete_frame();
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}));
|
||||
platform_window.on_resize(Box::new({
|
||||
|
@ -1004,6 +1010,10 @@ impl<'a> WindowContext<'a> {
|
|||
self.window.modifiers
|
||||
}
|
||||
|
||||
fn complete_frame(&self) {
|
||||
self.window.platform_window.completed_frame();
|
||||
}
|
||||
|
||||
/// Produces a new frame and assigns it to `rendered_frame`. To actually show
|
||||
/// the contents of the new [Scene], use [present].
|
||||
#[profiling::function]
|
||||
|
|
|
@ -352,7 +352,8 @@ impl WorkspaceDb {
|
|||
// Clear out panes and pane_groups
|
||||
conn.exec_bound(sql!(
|
||||
DELETE FROM pane_groups WHERE workspace_id = ?1;
|
||||
DELETE FROM panes WHERE workspace_id = ?1;))?(workspace.id)?;
|
||||
DELETE FROM panes WHERE workspace_id = ?1;))?(workspace.id)
|
||||
.context("Clearing old panes")?;
|
||||
|
||||
conn.exec_bound(sql!(
|
||||
DELETE FROM workspaces WHERE workspace_location = ? AND workspace_id != ?
|
||||
|
|
Loading…
Reference in a new issue