Detailed changes
@@ -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";
+
+pub struct RcRefCell<T>(Rc<RefCell<T>>);
+
+impl<T> RcRefCell<T> {
+ pub fn new(value: T) -> Self {
+ RcRefCell(Rc::new(RefCell::new(value)))
+ }
+
+ #[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()
+ );
+ }
+ }
-#[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>>,
-}
+ self.0.borrow_mut()
+ }
-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>,
+ #[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()
+ }
}
-pub(crate) struct LinuxPlatform {
- client: Rc<dyn Client>,
- inner: Rc<LinuxPlatformInner>,
+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 Default for LinuxPlatform {
- fn default() -> Self {
- Self::new()
+impl<T> Clone for RcRefCell<T> {
+ fn clone(&self) -> Self {
+ RcRefCell(self.0.clone())
}
}
-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());
+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::*;
-
- fn build_platform() -> LinuxPlatform {
- let platform = LinuxPlatform::new();
- platform
+ 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,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::*;
@@ -1,27 +1,25 @@
use std::cell::RefCell;
-use std::mem;
-use std::num::NonZeroU32;
use std::rc::Rc;
-use std::sync::Arc;
use std::time::{Duration, Instant};
use calloop::timer::{TimeoutAction, Timer};
-use calloop::LoopHandle;
+use calloop::{EventLoop, LoopHandle};
use calloop_wayland_source::WaylandSource;
+use collections::HashMap;
use copypasta::wayland_clipboard::{create_clipboards_from_external, Clipboard, Primary};
use copypasta::ClipboardProvider;
+use util::ResultExt;
use wayland_backend::client::ObjectId;
use wayland_backend::protocol::WEnum;
-use wayland_client::globals::{registry_queue_init, GlobalListContents};
-use wayland_client::protocol::wl_callback::WlCallback;
+use wayland_client::globals::{registry_queue_init, GlobalList, GlobalListContents};
+use wayland_client::protocol::wl_callback::{self, WlCallback};
use wayland_client::protocol::wl_output;
use wayland_client::protocol::wl_pointer::{AxisRelativeDirection, AxisSource};
use wayland_client::{
delegate_noop,
protocol::{
- wl_buffer, wl_callback, wl_compositor, wl_keyboard, wl_pointer, wl_registry, wl_seat,
- wl_shm, wl_shm_pool,
- wl_surface::{self, WlSurface},
+ wl_buffer, wl_compositor, wl_keyboard, wl_pointer, wl_registry, wl_seat, wl_shm,
+ wl_shm_pool, wl_surface,
},
Connection, Dispatch, Proxy, QueueHandle,
};
@@ -37,60 +35,83 @@ use xkbcommon::xkb::ffi::XKB_KEYMAP_FORMAT_TEXT_V1;
use xkbcommon::xkb::{self, Keycode, KEYMAP_COMPILE_NO_FLAGS};
use super::super::DOUBLE_CLICK_INTERVAL;
-use crate::platform::linux::client::Client;
-use crate::platform::linux::util::is_within_click_distance;
+use crate::platform::linux::is_within_click_distance;
use crate::platform::linux::wayland::cursor::Cursor;
-use crate::platform::linux::wayland::window::{WaylandDecorationState, WaylandWindow};
-use crate::platform::{LinuxPlatformInner, PlatformWindow};
-use crate::WindowParams;
+use crate::platform::linux::wayland::window::WaylandWindow;
+use crate::platform::linux::LinuxClient;
+use crate::platform::PlatformWindow;
+use crate::{point, px, MouseExitEvent};
use crate::{
- platform::linux::wayland::window::WaylandWindowState, AnyWindowHandle, CursorStyle, DisplayId,
- KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
- MouseDownEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay,
- PlatformInput, Point, ScrollDelta, ScrollWheelEvent, TouchPhase,
+ AnyWindowHandle, CursorStyle, DisplayId, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers,
+ ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
+ NavigationDirection, Pixels, PlatformDisplay, PlatformInput, Point, ScrollDelta,
+ ScrollWheelEvent, TouchPhase,
};
-use crate::{point, px};
+use crate::{LinuxCommon, WindowParams};
/// Used to convert evdev scancode to xkb scancode
const MIN_KEYCODE: u32 = 8;
-pub(crate) struct WaylandClientStateInner {
- compositor: wl_compositor::WlCompositor,
- wm_base: xdg_wm_base::XdgWmBase,
- shm: wl_shm::WlShm,
- viewporter: Option<wp_viewporter::WpViewporter>,
- fractional_scale_manager: Option<wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1>,
- decoration_manager: Option<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1>,
- windows: Vec<(xdg_surface::XdgSurface, Rc<WaylandWindowState>)>,
- outputs: Vec<(wl_output::WlOutput, Rc<RefCell<OutputState>>)>,
- platform_inner: Rc<LinuxPlatformInner>,
+#[derive(Debug, Clone)]
+pub struct Globals {
+ pub qh: QueueHandle<WaylandClient>,
+ pub compositor: wl_compositor::WlCompositor,
+ pub wm_base: xdg_wm_base::XdgWmBase,
+ pub shm: wl_shm::WlShm,
+ pub viewporter: Option<wp_viewporter::WpViewporter>,
+ pub fractional_scale_manager:
+ Option<wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1>,
+ pub decoration_manager: Option<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1>,
+}
+
+impl Globals {
+ fn new(globals: GlobalList, qh: QueueHandle<WaylandClient>) -> Self {
+ Globals {
+ compositor: globals
+ .bind(
+ &qh,
+ wl_surface::REQ_SET_BUFFER_SCALE_SINCE
+ ..=wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE,
+ (),
+ )
+ .unwrap(),
+ shm: globals.bind(&qh, 1..=1, ()).unwrap(),
+ wm_base: globals.bind(&qh, 1..=1, ()).unwrap(),
+ viewporter: globals.bind(&qh, 1..=1, ()).ok(),
+ fractional_scale_manager: globals.bind(&qh, 1..=1, ()).ok(),
+ decoration_manager: globals.bind(&qh, 1..=1, ()).ok(),
+ qh,
+ }
+ }
+}
+
+pub(crate) struct WaylandClientState {
+ globals: Globals,
+ // Surface to Window mapping
+ windows: HashMap<ObjectId, WaylandWindow>,
+ // Output to scale mapping
+ output_scales: HashMap<ObjectId, i32>,
keymap_state: Option<xkb::State>,
- click_state: ClickState,
+ click: ClickState,
repeat: KeyRepeat,
modifiers: Modifiers,
scroll_direction: f64,
axis_source: AxisSource,
- mouse_location: Point<Pixels>,
+ mouse_location: Option<Point<Pixels>>,
+ enter_token: Option<()>,
button_pressed: Option<MouseButton>,
- mouse_focused_window: Option<Rc<WaylandWindowState>>,
- keyboard_focused_window: Option<Rc<WaylandWindowState>>,
- loop_handle: Rc<LoopHandle<'static, ()>>,
-}
-
-pub(crate) struct CursorState {
+ mouse_focused_window: Option<WaylandWindow>,
+ keyboard_focused_window: Option<WaylandWindow>,
+ loop_handle: LoopHandle<'static, WaylandClient>,
cursor_icon_name: String,
- cursor: Option<Cursor>,
+ cursor: Cursor,
+ clipboard: Clipboard,
+ primary: Primary,
+ event_loop: Option<EventLoop<'static, WaylandClient>>,
+ common: LinuxCommon,
}
-#[derive(Clone)]
-pub(crate) struct WaylandClientState {
- client_state_inner: Rc<RefCell<WaylandClientStateInner>>,
- cursor_state: Rc<RefCell<CursorState>>,
- clipboard: Rc<RefCell<Clipboard>>,
- primary: Rc<RefCell<Primary>>,
-}
-
-struct ClickState {
+pub struct ClickState {
last_click: Instant,
last_location: Point<Pixels>,
current_count: usize,
@@ -103,11 +124,8 @@ pub(crate) struct KeyRepeat {
current_keysym: Option<xkb::Keysym>,
}
-pub(crate) struct WaylandClient {
- platform_inner: Rc<LinuxPlatformInner>,
- state: WaylandClientState,
- qh: Arc<QueueHandle<WaylandClientState>>,
-}
+#[derive(Clone)]
+pub struct WaylandClient(Rc<RefCell<WaylandClientState>>);
const WL_SEAT_MIN_VERSION: u32 = 4;
const WL_OUTPUT_VERSION: u32 = 2;
@@ -126,12 +144,12 @@ fn wl_seat_version(version: u32) -> u32 {
}
impl WaylandClient {
- pub(crate) fn new(linux_platform_inner: Rc<LinuxPlatformInner>) -> Self {
+ pub(crate) fn new() -> Self {
let conn = Connection::connect_to_env().unwrap();
- let (globals, mut event_queue) = registry_queue_init::<WaylandClientState>(&conn).unwrap();
+ let (globals, mut event_queue) = registry_queue_init::<WaylandClient>(&conn).unwrap();
let qh = event_queue.handle();
- let mut outputs = Vec::new();
+ let mut outputs = HashMap::default();
globals.contents().with_list(|list| {
for global in list {
@@ -144,15 +162,15 @@ impl WaylandClient {
(),
);
}
- "wl_output" => outputs.push((
- globals.registry().bind::<wl_output::WlOutput, _, _>(
+ "wl_output" => {
+ let output = globals.registry().bind::<wl_output::WlOutput, _, _>(
global.name,
WL_OUTPUT_VERSION,
&qh,
(),
- ),
- Rc::new(RefCell::new(OutputState::default())),
- )),
+ );
+ outputs.insert(output.id(), 1);
+ }
_ => {}
}
}
@@ -161,20 +179,29 @@ impl WaylandClient {
let display = conn.backend().display_ptr() as *mut std::ffi::c_void;
let (primary, clipboard) = unsafe { create_clipboards_from_external(display) };
- let mut state_inner = Rc::new(RefCell::new(WaylandClientStateInner {
- compositor: globals
- .bind(&qh, 1..=wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE, ())
- .unwrap(),
- wm_base: globals.bind(&qh, 1..=1, ()).unwrap(),
- shm: globals.bind(&qh, 1..=1, ()).unwrap(),
- outputs,
- viewporter: globals.bind(&qh, 1..=1, ()).ok(),
- fractional_scale_manager: globals.bind(&qh, 1..=1, ()).ok(),
- decoration_manager: globals.bind(&qh, 1..=1, ()).ok(),
- windows: Vec::new(),
- platform_inner: Rc::clone(&linux_platform_inner),
+ 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 WaylandClient| {
+ if let calloop::channel::Event::Msg(runnable) = event {
+ runnable.run();
+ }
+ });
+
+ let globals = Globals::new(globals, qh);
+
+ let cursor = Cursor::new(&conn, &globals, 24);
+
+ let mut state = Rc::new(RefCell::new(WaylandClientState {
+ globals,
+ output_scales: outputs,
+ windows: HashMap::default(),
+ common,
keymap_state: None,
- click_state: ClickState {
+ click: ClickState {
last_click: Instant::now(),
last_location: Point::new(px(0.0), px(0.0)),
current_count: 0,
@@ -194,43 +221,26 @@ impl WaylandClient {
},
scroll_direction: -1.0,
axis_source: AxisSource::Wheel,
- mouse_location: point(px(0.0), px(0.0)),
+ mouse_location: None,
button_pressed: None,
mouse_focused_window: None,
keyboard_focused_window: None,
- loop_handle: Rc::clone(&linux_platform_inner.loop_handle),
- }));
-
- let mut cursor_state = Rc::new(RefCell::new(CursorState {
+ loop_handle: handle.clone(),
cursor_icon_name: "arrow".to_string(),
- cursor: None,
+ enter_token: None,
+ cursor,
+ clipboard,
+ primary,
+ event_loop: Some(event_loop),
}));
- let source = WaylandSource::new(conn, event_queue);
-
- let mut state = WaylandClientState {
- client_state_inner: Rc::clone(&state_inner),
- cursor_state: Rc::clone(&cursor_state),
- clipboard: Rc::new(RefCell::new(clipboard)),
- primary: Rc::new(RefCell::new(primary)),
- };
- let mut state_loop = state.clone();
- linux_platform_inner
- .loop_handle
- .insert_source(source, move |_, queue, _| {
- queue.dispatch_pending(&mut state_loop)
- })
- .unwrap();
+ WaylandSource::new(conn, event_queue).insert(handle);
- Self {
- platform_inner: linux_platform_inner,
- state,
- qh: Arc::new(qh),
- }
+ Self(state)
}
}
-impl Client for WaylandClient {
+impl LinuxClient for WaylandClient {
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
Vec::new()
}
@@ -246,61 +256,14 @@ impl Client for WaylandClient {
fn open_window(
&self,
handle: AnyWindowHandle,
- options: WindowParams,
+ params: WindowParams,
) -> Box<dyn PlatformWindow> {
- let mut state = self.state.client_state_inner.borrow_mut();
-
- let wl_surface = state.compositor.create_surface(&self.qh, ());
- let xdg_surface = state.wm_base.get_xdg_surface(&wl_surface, &self.qh, ());
- let toplevel = xdg_surface.get_toplevel(&self.qh, ());
- let wl_surface = Arc::new(wl_surface);
-
- // Attempt to set up window decorations based on the requested configuration
- //
- // Note that wayland compositors may either not support decorations at all, or may
- // support them but not allow clients to choose whether they are enabled or not.
- // We attempt to account for these cases here.
-
- if let Some(decoration_manager) = state.decoration_manager.as_ref() {
- // The protocol for managing decorations is present at least, but that doesn't
- // mean that the compositor will allow us to use it.
-
- let decoration =
- decoration_manager.get_toplevel_decoration(&toplevel, &self.qh, xdg_surface.id());
-
- // todo(linux) - options.titlebar is lacking information required for wayland.
- // Especially, whether a titlebar is wanted in itself.
- //
- // Removing the titlebar also removes the entire window frame (ie. the ability to
- // close, move and resize the window [snapping still works]). This needs additional
- // handling in Zed, in order to implement drag handlers on a titlebar element.
- //
- // Since all of this handling is not present, we request server-side decorations
- // for now as a stopgap solution.
- decoration.set_mode(zxdg_toplevel_decoration_v1::Mode::ServerSide);
- }
-
- let viewport = state
- .viewporter
- .as_ref()
- .map(|viewporter| viewporter.get_viewport(&wl_surface, &self.qh, ()));
+ let mut state = self.0.borrow_mut();
- wl_surface.frame(&self.qh, wl_surface.clone());
- wl_surface.commit();
-
- let window_state: Rc<WaylandWindowState> = Rc::new(WaylandWindowState::new(
- wl_surface.clone(),
- viewport,
- Arc::new(toplevel),
- options,
- ));
-
- if let Some(fractional_scale_manager) = state.fractional_scale_manager.as_ref() {
- fractional_scale_manager.get_fractional_scale(&wl_surface, &self.qh, xdg_surface.id());
- }
+ let (window, surface_id) = WaylandWindow::new(state.globals.clone(), params);
+ state.windows.insert(surface_id, window.clone());
- state.windows.push((xdg_surface, Rc::clone(&window_state)));
- Box::new(WaylandWindow(window_state))
+ Box::new(window)
}
fn set_cursor_style(&self, style: CursorStyle) {
@@ -329,19 +292,42 @@ impl Client for WaylandClient {
}
.to_string();
- self.state.cursor_state.borrow_mut().cursor_icon_name = cursor_icon_name;
+ self.0.borrow_mut().cursor_icon_name = cursor_icon_name;
}
- fn get_clipboard(&self) -> Rc<RefCell<dyn ClipboardProvider>> {
- self.state.clipboard.clone()
+ fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R {
+ f(&mut self.0.borrow_mut().common)
}
- fn get_primary(&self) -> Rc<RefCell<dyn ClipboardProvider>> {
- self.state.primary.clone()
+ 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();
+ }
+
+ fn write_to_clipboard(&self, item: crate::ClipboardItem) {
+ self.0.borrow_mut().clipboard.set_contents(item.text);
+ }
+
+ fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
+ self.0
+ .borrow_mut()
+ .clipboard
+ .get_contents()
+ .ok()
+ .map(|s| crate::ClipboardItem {
+ text: s,
+ metadata: None,
+ })
}
}
-impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientState {
+impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClient {
fn event(
state: &mut Self,
registry: &wl_registry::WlRegistry,
@@ -350,7 +336,7 @@ impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStat
_: &Connection,
qh: &QueueHandle<Self>,
) {
- let mut state = state.client_state_inner.borrow_mut();
+ let mut state = state.0.borrow_mut();
match event {
wl_registry::Event::Global {
name,
@@ -361,10 +347,10 @@ impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStat
registry.bind::<wl_seat::WlSeat, _, _>(name, wl_seat_version(version), qh, ());
}
"wl_output" => {
- state.outputs.push((
- registry.bind::<wl_output::WlOutput, _, _>(name, WL_OUTPUT_VERSION, qh, ()),
- Rc::new(RefCell::new(OutputState::default())),
- ));
+ let output =
+ registry.bind::<wl_output::WlOutput, _, _>(name, WL_OUTPUT_VERSION, qh, ());
+
+ state.output_scales.insert(output.id(), 1);
}
_ => {}
},
@@ -374,38 +360,40 @@ impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStat
}
}
-delegate_noop!(WaylandClientState: ignore wl_compositor::WlCompositor);
-delegate_noop!(WaylandClientState: ignore wl_shm::WlShm);
-delegate_noop!(WaylandClientState: ignore wl_shm_pool::WlShmPool);
-delegate_noop!(WaylandClientState: ignore wl_buffer::WlBuffer);
-delegate_noop!(WaylandClientState: ignore wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1);
-delegate_noop!(WaylandClientState: ignore zxdg_decoration_manager_v1::ZxdgDecorationManagerV1);
-delegate_noop!(WaylandClientState: ignore wp_viewporter::WpViewporter);
-delegate_noop!(WaylandClientState: ignore wp_viewport::WpViewport);
+delegate_noop!(WaylandClient: ignore wl_compositor::WlCompositor);
+delegate_noop!(WaylandClient: ignore wl_shm::WlShm);
+delegate_noop!(WaylandClient: ignore wl_shm_pool::WlShmPool);
+delegate_noop!(WaylandClient: ignore wl_buffer::WlBuffer);
+delegate_noop!(WaylandClient: ignore wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1);
+delegate_noop!(WaylandClient: ignore zxdg_decoration_manager_v1::ZxdgDecorationManagerV1);
+delegate_noop!(WaylandClient: ignore wp_viewporter::WpViewporter);
+delegate_noop!(WaylandClient: ignore wp_viewport::WpViewport);
-impl Dispatch<WlCallback, Arc<WlSurface>> for WaylandClientState {
+impl Dispatch<WlCallback, ObjectId> for WaylandClient {
fn event(
- state: &mut Self,
- _: &WlCallback,
+ this: &mut WaylandClient,
+ _: &wl_callback::WlCallback,
event: wl_callback::Event,
- surf: &Arc<WlSurface>,
+ surface_id: &ObjectId,
_: &Connection,
qh: &QueueHandle<Self>,
) {
- let mut state = state.client_state_inner.borrow_mut();
- if let wl_callback::Event::Done { .. } = event {
- for window in &state.windows {
- if window.1.surface.id() == surf.id() {
- window.1.surface.frame(qh, surf.clone());
- window.1.update();
- window.1.surface.commit();
- }
+ let state = this.0.borrow_mut();
+ let Some(window) = state.windows.get(surface_id).cloned() else {
+ return;
+ };
+
+ drop(state);
+ match event {
+ wl_callback::Event::Done { callback_data } => {
+ window.frame();
}
+ _ => {}
}
}
}
-impl Dispatch<wl_surface::WlSurface, ()> for WaylandClientState {
+impl Dispatch<wl_surface::WlSurface, ()> for WaylandClient {
fn event(
state: &mut Self,
surface: &wl_surface::WlSurface,
@@ -414,85 +402,18 @@ impl Dispatch<wl_surface::WlSurface, ()> for WaylandClientState {
_: &Connection,
_: &QueueHandle<Self>,
) {
- let mut state = state.client_state_inner.borrow_mut();
-
- // We use `WpFractionalScale` instead to set the scale if it's available
- // or give up on scaling if `WlSurface::set_buffer_scale` isn't available
- if state.fractional_scale_manager.is_some()
- || state.compositor.version() < wl_surface::REQ_SET_BUFFER_SCALE_SINCE
- {
- return;
- }
-
- let Some(window) = state
- .windows
- .iter()
- .map(|(_, state)| state)
- .find(|state| &*state.surface == surface)
- else {
+ let mut state = state.0.borrow_mut();
+ let Some(window) = state.windows.get(&surface.id()).cloned() else {
return;
};
+ let scales = state.output_scales.clone();
+ drop(state);
- let mut outputs = window.outputs.borrow_mut();
-
- match event {
- wl_surface::Event::Enter { output } => {
- // We use `PreferredBufferScale` instead to set the scale if it's available
- if surface.version() >= wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE {
- return;
- }
- let mut scale = 1;
- for global_output in &state.outputs {
- if output == global_output.0 {
- outputs.insert(output.id());
- scale = scale.max(global_output.1.borrow().scale.get());
- } else if outputs.contains(&global_output.0.id()) {
- scale = scale.max(global_output.1.borrow().scale.get());
- }
- }
- window.rescale(scale as f32);
- window.surface.set_buffer_scale(scale as i32);
- }
- wl_surface::Event::Leave { output } => {
- // We use `PreferredBufferScale` instead to set the scale if it's available
- if surface.version() >= wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE {
- return;
- }
-
- outputs.remove(&output.id());
-
- let mut scale = 1;
- for global_output in &state.outputs {
- if outputs.contains(&global_output.0.id()) {
- scale = scale.max(global_output.1.borrow().scale.get());
- }
- }
- window.rescale(scale as f32);
- window.surface.set_buffer_scale(scale as i32);
- }
- wl_surface::Event::PreferredBufferScale { factor } => {
- window.rescale(factor as f32);
- surface.set_buffer_scale(factor);
- }
- _ => {}
- }
+ window.handle_surface_event(event, scales);
}
}
-#[derive(Debug, Clone)]
-struct OutputState {
- scale: NonZeroU32,
-}
-
-impl Default for OutputState {
- fn default() -> Self {
- Self {
- scale: NonZeroU32::new(1).unwrap(),
- }
- }
-}
-
-impl Dispatch<wl_output::WlOutput, ()> for WaylandClientState {
+impl Dispatch<wl_output::WlOutput, ()> for WaylandClient {
fn event(
state: &mut Self,
output: &wl_output::WlOutput,
@@ -501,90 +422,64 @@ impl Dispatch<wl_output::WlOutput, ()> for WaylandClientState {
_: &Connection,
_: &QueueHandle<Self>,
) {
- let mut state = state.client_state_inner.borrow_mut();
- let mut output_state = state
- .outputs
- .iter_mut()
- .find(|(o, _)| o == output)
- .map(|(_, state)| state)
- .unwrap()
- .borrow_mut();
+ let mut state = state.0.borrow_mut();
+ let Some(mut output_scale) = state.output_scales.get_mut(&output.id()) else {
+ return;
+ };
+
match event {
wl_output::Event::Scale { factor } => {
- if factor > 0 {
- output_state.scale = NonZeroU32::new(factor as u32).unwrap();
- }
+ *output_scale = factor;
}
_ => {}
}
}
}
-impl Dispatch<xdg_surface::XdgSurface, ()> for WaylandClientState {
+impl Dispatch<xdg_surface::XdgSurface, ObjectId> for WaylandClient {
fn event(
state: &mut Self,
xdg_surface: &xdg_surface::XdgSurface,
event: xdg_surface::Event,
- _: &(),
+ surface_id: &ObjectId,
_: &Connection,
_: &QueueHandle<Self>,
) {
- let mut state = state.client_state_inner.borrow_mut();
+ let mut state = state.0.borrow_mut();
+
+ // todo(linux): Apply the configuration changes as we go
if let xdg_surface::Event::Configure { serial, .. } = event {
xdg_surface.ack_configure(serial);
- for window in &state.windows {
- if &window.0 == xdg_surface {
- window.1.update();
- window.1.surface.commit();
- return;
- }
- }
}
}
}
-impl Dispatch<xdg_toplevel::XdgToplevel, ()> for WaylandClientState {
+impl Dispatch<xdg_toplevel::XdgToplevel, ObjectId> for WaylandClient {
fn event(
- state: &mut Self,
+ this: &mut Self,
xdg_toplevel: &xdg_toplevel::XdgToplevel,
event: <xdg_toplevel::XdgToplevel as Proxy>::Event,
- _: &(),
+ surface_id: &ObjectId,
_: &Connection,
_: &QueueHandle<Self>,
) {
- let mut state = state.client_state_inner.borrow_mut();
- if let xdg_toplevel::Event::Configure {
- width,
- height,
- states,
- } = event
- {
- let width = NonZeroU32::new(width as u32);
- let height = NonZeroU32::new(height as u32);
- let fullscreen = states.contains(&(xdg_toplevel::State::Fullscreen as u8));
- for window in &state.windows {
- if window.1.toplevel.id() == xdg_toplevel.id() {
- window.1.resize(width, height);
- window.1.set_fullscreen(fullscreen);
- window.1.surface.commit();
- return;
- }
- }
- } else if let xdg_toplevel::Event::Close = event {
- state.windows.retain(|(_, window)| {
- if window.toplevel.id() == xdg_toplevel.id() {
- window.toplevel.destroy();
- false
- } else {
- true
- }
- });
- state.platform_inner.loop_signal.stop();
+ let mut state = this.0.borrow_mut();
+
+ let Some(window) = state.windows.get(surface_id).cloned() else {
+ return;
+ };
+
+ drop(state);
+ let should_close = window.handle_toplevel_event(event);
+
+ if should_close {
+ let mut state = this.0.borrow_mut();
+ state.windows.remove(surface_id);
}
}
}
-impl Dispatch<xdg_wm_base::XdgWmBase, ()> for WaylandClientState {
+impl Dispatch<xdg_wm_base::XdgWmBase, ()> for WaylandClient {
fn event(
_: &mut Self,
wm_base: &xdg_wm_base::XdgWmBase,
@@ -599,7 +494,7 @@ impl Dispatch<xdg_wm_base::XdgWmBase, ()> for WaylandClientState {
}
}
-impl Dispatch<wl_seat::WlSeat, ()> for WaylandClientState {
+impl Dispatch<wl_seat::WlSeat, ()> for WaylandClient {
fn event(
state: &mut Self,
seat: &wl_seat::WlSeat,
@@ -622,7 +517,7 @@ impl Dispatch<wl_seat::WlSeat, ()> for WaylandClientState {
}
}
-impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientState {
+impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClient {
fn event(
this: &mut Self,
keyboard: &wl_keyboard::WlKeyboard,
@@ -631,7 +526,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientState {
conn: &Connection,
qh: &QueueHandle<Self>,
) {
- let mut state = this.client_state_inner.borrow_mut();
+ let mut state = this.0.borrow_mut();
match event {
wl_keyboard::Event::RepeatInfo { rate, delay } => {
state.repeat.characters_per_second = rate as u32;
@@ -656,34 +551,28 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientState {
XKB_KEYMAP_FORMAT_TEXT_V1,
KEYMAP_COMPILE_NO_FLAGS,
)
- .unwrap()
- }
- .unwrap();
+ .log_err()
+ .flatten()
+ .expect("Failed to create keymap")
+ };
state.keymap_state = Some(xkb::State::new(&keymap));
}
wl_keyboard::Event::Enter { surface, .. } => {
- state.keyboard_focused_window = state
- .windows
- .iter()
- .find(|&w| w.1.surface.id() == surface.id())
- .map(|w| w.1.clone());
+ state.keyboard_focused_window = state.windows.get(&surface.id()).cloned();
- if let Some(window) = &state.keyboard_focused_window {
+ if let Some(window) = state.keyboard_focused_window.clone() {
+ drop(state);
window.set_focused(true);
}
}
wl_keyboard::Event::Leave { surface, .. } => {
- let keyboard_focused_window = state
- .windows
- .iter()
- .find(|&w| w.1.surface.id() == surface.id())
- .map(|w| w.1.clone());
+ let keyboard_focused_window = state.windows.get(&surface.id()).cloned();
+ state.keyboard_focused_window = None;
if let Some(window) = keyboard_focused_window {
+ drop(state);
window.set_focused(false);
}
-
- state.keyboard_focused_window = None;
}
wl_keyboard::Event::Modifiers {
mods_depressed,
@@ -719,7 +608,6 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientState {
});
drop(state);
-
focused_window.handle_input(input);
}
wl_keyboard::Event::Key {
@@ -748,38 +636,33 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientState {
state.repeat.current_keysym = Some(keysym);
let rate = state.repeat.characters_per_second;
- let delay = state.repeat.delay;
let id = state.repeat.current_id;
- let this = this.clone();
-
- let timer = Timer::from_duration(delay);
- let state_ = Rc::clone(&this.client_state_inner);
- let input_ = input.clone();
state
.loop_handle
- .insert_source(timer, move |event, _metadata, shared_data| {
- let state_ = state_.borrow_mut();
- let is_repeating = id == state_.repeat.current_id
- && state_.repeat.current_keysym.is_some()
- && state_.keyboard_focused_window.is_some();
-
- if !is_repeating {
- return TimeoutAction::Drop;
- }
+ .insert_source(Timer::from_duration(state.repeat.delay), {
+ let input = input.clone();
+ move |event, _metadata, client| {
+ let state = client.0.borrow_mut();
+ let is_repeating = id == state.repeat.current_id
+ && state.repeat.current_keysym.is_some()
+ && state.keyboard_focused_window.is_some();
- let focused_window =
- state_.keyboard_focused_window.as_ref().unwrap().clone();
+ if !is_repeating {
+ return TimeoutAction::Drop;
+ }
- drop(state_);
+ let focused_window =
+ state.keyboard_focused_window.as_ref().unwrap().clone();
- focused_window.handle_input(input_.clone());
+ drop(state);
+ focused_window.handle_input(input.clone());
- TimeoutAction::ToDuration(Duration::from_secs(1) / rate)
+ TimeoutAction::ToDuration(Duration::from_secs(1) / rate)
+ }
})
.unwrap();
drop(state);
-
focused_window.handle_input(input);
}
wl_keyboard::KeyState::Released if !keysym.is_modifier_key() => {
@@ -790,7 +673,6 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientState {
state.repeat.current_keysym = None;
drop(state);
-
focused_window.handle_input(input);
}
_ => {}
@@ -821,23 +703,16 @@ fn linux_button_to_gpui(button: u32) -> Option<MouseButton> {
})
}
-impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientState {
+impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClient {
fn event(
- self_state: &mut Self,
+ client: &mut Self,
wl_pointer: &wl_pointer::WlPointer,
event: wl_pointer::Event,
data: &(),
conn: &Connection,
qh: &QueueHandle<Self>,
) {
- let mut state = self_state.client_state_inner.borrow_mut();
- {
- let mut cursor_state = self_state.cursor_state.borrow_mut();
- if cursor_state.cursor.is_none() {
- cursor_state.cursor =
- Some(Cursor::new(&conn, &state.compositor, &qh, &state.shm, 24));
- }
- }
+ let mut state = client.0.borrow_mut();
match event {
wl_pointer::Event::Enter {
@@ -847,21 +722,32 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientState {
surface_y,
..
} => {
- let windows = mem::take(&mut state.windows);
- for window in windows.iter() {
- if window.1.surface.id() == surface.id() {
- window.1.set_focused(true);
- state.mouse_focused_window = Some(window.1.clone());
- let mut cursor_state = self_state.cursor_state.borrow_mut();
- let cursor_icon_name = cursor_state.cursor_icon_name.clone();
- if let Some(mut cursor) = cursor_state.cursor.as_mut() {
- cursor.set_serial_id(serial);
- cursor.set_icon(&wl_pointer, cursor_icon_name);
- }
- }
+ state.mouse_location = Some(point(px(surface_x as f32), px(surface_y as f32)));
+
+ if let Some(window) = state.windows.get(&surface.id()).cloned() {
+ state.enter_token = Some(());
+ state.mouse_focused_window = Some(window.clone());
+ state.cursor.set_serial_id(serial);
+ state.cursor.set_icon(&wl_pointer, None);
+ drop(state);
+ window.set_focused(true);
+ }
+ }
+ wl_pointer::Event::Leave { surface, .. } => {
+ if let Some(focused_window) = state.mouse_focused_window.clone() {
+ state.enter_token.take();
+ let input = PlatformInput::MouseExited(MouseExitEvent {
+ position: state.mouse_location.unwrap(),
+ pressed_button: state.button_pressed,
+ modifiers: state.modifiers,
+ });
+ state.mouse_focused_window = None;
+ state.mouse_location = None;
+
+ drop(state);
+ focused_window.handle_input(input);
+ focused_window.set_focused(false);
}
- state.windows = windows;
- state.mouse_location = point(px(surface_x as f32), px(surface_y as f32));
}
wl_pointer::Event::Motion {
time,
@@ -872,19 +758,17 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientState {
if state.mouse_focused_window.is_none() {
return;
}
- state.mouse_location = point(px(surface_x as f32), px(surface_y as f32));
+ state.mouse_location = Some(point(px(surface_x as f32), px(surface_y as f32)));
+ state.cursor.set_icon(&wl_pointer, None);
- state.mouse_focused_window.as_ref().unwrap().handle_input(
- PlatformInput::MouseMove(MouseMoveEvent {
- position: state.mouse_location,
+ if let Some(window) = state.mouse_focused_window.clone() {
+ let input = PlatformInput::MouseMove(MouseMoveEvent {
+ position: state.mouse_location.unwrap(),
pressed_button: state.button_pressed,
modifiers: state.modifiers,
- }),
- );
- let mut cursor_state = self_state.cursor_state.borrow_mut();
- let cursor_icon_name = cursor_state.cursor_icon_name.clone();
- if let Some(mut cursor) = cursor_state.cursor.as_mut() {
- cursor.set_icon(&wl_pointer, cursor_icon_name);
+ });
+ drop(state);
+ window.handle_input(input);
}
}
wl_pointer::Event::Button {
@@ -899,43 +783,49 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientState {
}
match button_state {
wl_pointer::ButtonState::Pressed => {
- let click_elapsed = state.click_state.last_click.elapsed();
+ let click_elapsed = state.click.last_click.elapsed();
if click_elapsed < DOUBLE_CLICK_INTERVAL
&& is_within_click_distance(
- state.click_state.last_location,
- state.mouse_location,
+ state.click.last_location,
+ state.mouse_location.unwrap(),
)
{
- state.click_state.current_count += 1;
+ state.click.current_count += 1;
} else {
- state.click_state.current_count = 1;
+ state.click.current_count = 1;
}
- state.click_state.last_click = Instant::now();
- state.click_state.last_location = state.mouse_location;
+ state.click.last_click = Instant::now();
+ state.click.last_location = state.mouse_location.unwrap();
state.button_pressed = Some(button);
- state.mouse_focused_window.as_ref().unwrap().handle_input(
- PlatformInput::MouseDown(MouseDownEvent {
+
+ if let Some(window) = state.mouse_focused_window.clone() {
+ let input = PlatformInput::MouseDown(MouseDownEvent {
button,
- position: state.mouse_location,
+ position: state.mouse_location.unwrap(),
modifiers: state.modifiers,
- click_count: state.click_state.current_count,
- first_mouse: false,
- }),
- );
+ click_count: state.click.current_count,
+ first_mouse: state.enter_token.take().is_some(),
+ });
+ drop(state);
+ window.handle_input(input);
+ }
}
wl_pointer::ButtonState::Released => {
state.button_pressed = None;
- state.mouse_focused_window.as_ref().unwrap().handle_input(
- PlatformInput::MouseUp(MouseUpEvent {
+
+ if let Some(window) = state.mouse_focused_window.clone() {
+ let input = PlatformInput::MouseUp(MouseUpEvent {
button,
- position: state.mouse_location,
+ position: state.mouse_location.unwrap(),
modifiers: state.modifiers,
- click_count: state.click_state.current_count,
- }),
- );
+ click_count: state.click.current_count,
+ });
+ drop(state);
+ window.handle_input(input);
+ }
}
_ => {}
}
@@ -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,
- viewport,
- fullscreen: RefCell::new(false),
+ 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,
+ 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 update(&self) {
+ 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(mut fun) = cb.request_frame.take() {
- drop(cb);
+ if let Some(fun) = cb.request_frame.as_mut() {
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;
-struct ClickState {
- last_click: Instant,
- last_location: Point<Pixels>,
- current_count: usize,
+ fn deref(&self) -> &Self::Target {
+ &self.window
+ }
}
-pub(crate) struct X11Client {
- platform_inner: Rc<LinuxPlatformInner>,
- xcb_connection: Rc<XCBConnection>,
- x_root_index: usize,
- atoms: XcbAtoms,
- state: RefCell<X11ClientState>,
+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>,
}
+#[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 != ?