WIP: Refactor Linux platform implementation (#10227)

This puts the Linux platform implementation at a similar code style and
quality to the macOS platform. The largest change is that I collapsed
the `LinuxPlatform` -> `[Backend]` -> `[Backend]State` ->
`[Backend]StateInner` to just `[Backend]` and `[Backend]State`, and in
the process removed most of the `Rc`s and `RefCell`s.

TODO:
- [x] Make sure that this is on-par with the existing implementation
- [x] Review in detail, now that the large changes are done.
- [ ] Update the roadmap

Release Notes:

- N/A
This commit is contained in:
Mikayla Maki 2024-04-08 16:40:35 -07:00 committed by GitHub
parent ee1642a50f
commit def87a8d76
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 1256 additions and 1095 deletions

4
Cargo.lock generated
View file

@ -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",

View file

@ -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"] }

View file

@ -784,7 +784,6 @@ impl Client {
}
Status::UpgradeRequired => return Err(EstablishConnectionError::UpgradeRequired)?,
};
if was_disconnected {
self.set_status(Status::Authenticating, cx);
} else {

View file

@ -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.

View file

@ -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")]

View file

@ -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::*;

View file

@ -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>>;
}

View file

@ -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 {

View file

@ -1,7 +1,10 @@
#![allow(unused)]
use std::cell::RefCell;
use std::any::{type_name, Any};
use std::cell::{self, RefCell};
use std::env;
use std::ops::{Deref, DerefMut};
use std::panic::Location;
use std::{
path::{Path, PathBuf},
process::Command,
@ -13,140 +16,176 @@ use std::{
use anyhow::anyhow;
use ashpd::desktop::file_chooser::{OpenFileRequest, SaveFileRequest};
use async_task::Runnable;
use calloop::channel::Channel;
use calloop::{EventLoop, LoopHandle, LoopSignal};
use copypasta::ClipboardProvider;
use flume::{Receiver, Sender};
use futures::channel::oneshot;
use parking_lot::Mutex;
use time::UtcOffset;
use wayland_client::Connection;
use xkbcommon::xkb::{self, Keycode, Keysym, State};
use crate::platform::linux::client::Client;
use crate::platform::linux::wayland::WaylandClient;
use crate::{
px, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
ForegroundExecutor, Keymap, LinuxDispatcher, LinuxTextSystem, Menu, PathPromptOptions, Pixels,
Platform, PlatformDisplay, PlatformInput, PlatformTextSystem, PlatformWindow, Result,
SemanticVersion, Task, WindowOptions, WindowParams,
ForegroundExecutor, Keymap, Keystroke, LinuxDispatcher, LinuxTextSystem, Menu, Modifiers,
PathPromptOptions, Pixels, Platform, PlatformDisplay, PlatformInput, PlatformInputHandler,
PlatformTextSystem, PlatformWindow, Point, PromptLevel, Result, SemanticVersion, Size, Task,
WindowAppearance, WindowOptions, WindowParams,
};
use super::x11::X11Client;
pub(super) const SCROLL_LINES: f64 = 3.0;
pub(crate) const SCROLL_LINES: f64 = 3.0;
// Values match the defaults on GTK.
// Taken from https://github.com/GNOME/gtk/blob/main/gtk/gtksettings.c#L320
pub(super) const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(400);
pub(super) const DOUBLE_CLICK_DISTANCE: Pixels = px(5.0);
pub(crate) const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(400);
pub(crate) const DOUBLE_CLICK_DISTANCE: Pixels = px(5.0);
pub(crate) const KEYRING_LABEL: &str = "zed-github-account";
#[derive(Default)]
pub(crate) struct Callbacks {
open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
become_active: Option<Box<dyn FnMut()>>,
resign_active: Option<Box<dyn FnMut()>>,
quit: Option<Box<dyn FnMut()>>,
reopen: Option<Box<dyn FnMut()>>,
event: Option<Box<dyn FnMut(PlatformInput) -> bool>>,
app_menu_action: Option<Box<dyn FnMut(&dyn Action)>>,
will_open_app_menu: Option<Box<dyn FnMut()>>,
validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
}
pub struct RcRefCell<T>(Rc<RefCell<T>>);
pub(crate) struct LinuxPlatformInner {
pub(crate) event_loop: RefCell<EventLoop<'static, ()>>,
pub(crate) loop_handle: Rc<LoopHandle<'static, ()>>,
pub(crate) loop_signal: LoopSignal,
pub(crate) background_executor: BackgroundExecutor,
pub(crate) foreground_executor: ForegroundExecutor,
pub(crate) text_system: Arc<LinuxTextSystem>,
pub(crate) callbacks: RefCell<Callbacks>,
}
impl<T> RcRefCell<T> {
pub fn new(value: T) -> Self {
RcRefCell(Rc::new(RefCell::new(value)))
}
pub(crate) struct LinuxPlatform {
client: Rc<dyn Client>,
inner: Rc<LinuxPlatformInner>,
}
#[inline]
#[track_caller]
pub fn borrow_mut(&self) -> std::cell::RefMut<'_, T> {
#[cfg(debug_assertions)]
{
if option_env!("TRACK_BORROW_MUT").is_some() {
eprintln!(
"borrow_mut-ing {} at {}",
type_name::<T>(),
Location::caller()
);
}
}
impl Default for LinuxPlatform {
fn default() -> Self {
Self::new()
self.0.borrow_mut()
}
#[inline]
#[track_caller]
pub fn borrow(&self) -> std::cell::Ref<'_, T> {
#[cfg(debug_assertions)]
{
if option_env!("TRACK_BORROW_MUT").is_some() {
eprintln!("borrow-ing {} at {}", type_name::<T>(), Location::caller());
}
}
self.0.borrow()
}
}
impl LinuxPlatform {
pub(crate) fn new() -> Self {
let wayland_display = env::var_os("WAYLAND_DISPLAY");
let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
impl<T> Deref for RcRefCell<T> {
type Target = Rc<RefCell<T>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for RcRefCell<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T> Clone for RcRefCell<T> {
fn clone(&self) -> Self {
RcRefCell(self.0.clone())
}
}
pub trait LinuxClient {
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R;
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
fn open_window(
&self,
handle: AnyWindowHandle,
options: WindowParams,
) -> Box<dyn PlatformWindow>;
fn set_cursor_style(&self, style: CursorStyle);
fn write_to_clipboard(&self, item: ClipboardItem);
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
fn run(&self);
}
#[derive(Default)]
pub(crate) struct PlatformHandlers {
pub(crate) open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
pub(crate) become_active: Option<Box<dyn FnMut()>>,
pub(crate) resign_active: Option<Box<dyn FnMut()>>,
pub(crate) quit: Option<Box<dyn FnMut()>>,
pub(crate) reopen: Option<Box<dyn FnMut()>>,
pub(crate) event: Option<Box<dyn FnMut(PlatformInput) -> bool>>,
pub(crate) app_menu_action: Option<Box<dyn FnMut(&dyn Action)>>,
pub(crate) will_open_app_menu: Option<Box<dyn FnMut()>>,
pub(crate) validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
}
pub(crate) struct LinuxCommon {
pub(crate) background_executor: BackgroundExecutor,
pub(crate) foreground_executor: ForegroundExecutor,
pub(crate) text_system: Arc<LinuxTextSystem>,
pub(crate) callbacks: PlatformHandlers,
pub(crate) signal: LoopSignal,
}
impl LinuxCommon {
pub fn new(signal: LoopSignal) -> (Self, Channel<Runnable>) {
let (main_sender, main_receiver) = calloop::channel::channel::<Runnable>();
let text_system = Arc::new(LinuxTextSystem::new());
let callbacks = RefCell::new(Callbacks::default());
let event_loop = EventLoop::try_new().unwrap();
event_loop
.handle()
.insert_source(main_receiver, |event, _, _| {
if let calloop::channel::Event::Msg(runnable) = event {
runnable.run();
}
});
let callbacks = PlatformHandlers::default();
let dispatcher = Arc::new(LinuxDispatcher::new(main_sender));
let inner = Rc::new(LinuxPlatformInner {
loop_handle: Rc::new(event_loop.handle()),
loop_signal: event_loop.get_signal(),
event_loop: RefCell::new(event_loop),
let common = LinuxCommon {
background_executor: BackgroundExecutor::new(dispatcher.clone()),
foreground_executor: ForegroundExecutor::new(dispatcher.clone()),
text_system,
callbacks,
});
signal,
};
if use_wayland {
Self {
client: Rc::new(WaylandClient::new(Rc::clone(&inner))),
inner,
}
} else {
Self {
client: X11Client::new(Rc::clone(&inner)),
inner,
}
}
(common, main_receiver)
}
}
const KEYRING_LABEL: &str = "zed-github-account";
impl Platform for LinuxPlatform {
impl<P: LinuxClient + 'static> Platform for P {
fn background_executor(&self) -> BackgroundExecutor {
self.inner.background_executor.clone()
self.with_common(|common| common.background_executor.clone())
}
fn foreground_executor(&self) -> ForegroundExecutor {
self.inner.foreground_executor.clone()
self.with_common(|common| common.foreground_executor.clone())
}
fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
self.inner.text_system.clone()
self.with_common(|common| common.text_system.clone())
}
fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
on_finish_launching();
self.inner
.event_loop
.borrow_mut()
.run(None, &mut (), |&mut ()| {})
.expect("Run loop failed");
LinuxClient::run(self);
if let Some(mut fun) = self.inner.callbacks.borrow_mut().quit.take() {
fun();
}
self.with_common(|common| {
if let Some(mut fun) = common.callbacks.quit.take() {
fun();
}
});
}
fn quit(&self) {
self.inner.loop_signal.stop();
self.with_common(|common| common.signal.stop());
}
fn restart(&self) {
@ -194,22 +233,23 @@ impl Platform for LinuxPlatform {
// todo(linux)
fn hide(&self) {}
// todo(linux)
fn hide_other_apps(&self) {}
fn hide_other_apps(&self) {
log::warn!("hide_other_apps is not implemented on Linux, ignoring the call")
}
// todo(linux)
fn unhide_other_apps(&self) {}
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
self.client.primary_display()
self.primary_display()
}
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
self.client.displays()
self.displays()
}
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
self.client.display(id)
self.display(id)
}
// todo(linux)
@ -222,7 +262,7 @@ impl Platform for LinuxPlatform {
handle: AnyWindowHandle,
options: WindowParams,
) -> Box<dyn PlatformWindow> {
self.client.open_window(handle, options)
self.open_window(handle, options)
}
fn open_url(&self, url: &str) {
@ -230,7 +270,7 @@ impl Platform for LinuxPlatform {
}
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
self.inner.callbacks.borrow_mut().open_urls = Some(callback);
self.with_common(|common| common.callbacks.open_urls = Some(callback));
}
fn prompt_for_paths(
@ -238,8 +278,7 @@ impl Platform for LinuxPlatform {
options: PathPromptOptions,
) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
let (done_tx, done_rx) = oneshot::channel();
self.inner
.foreground_executor
self.foreground_executor()
.spawn(async move {
let title = if options.multiple {
if !options.files {
@ -282,8 +321,7 @@ impl Platform for LinuxPlatform {
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
let (done_tx, done_rx) = oneshot::channel();
let directory = directory.to_owned();
self.inner
.foreground_executor
self.foreground_executor()
.spawn(async move {
let result = SaveFileRequest::default()
.modal(true)
@ -303,6 +341,7 @@ impl Platform for LinuxPlatform {
done_tx.send(result);
})
.detach();
done_rx
}
@ -317,35 +356,51 @@ impl Platform for LinuxPlatform {
}
fn on_become_active(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.borrow_mut().become_active = Some(callback);
self.with_common(|common| {
common.callbacks.become_active = Some(callback);
});
}
fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.borrow_mut().resign_active = Some(callback);
self.with_common(|common| {
common.callbacks.resign_active = Some(callback);
});
}
fn on_quit(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.borrow_mut().quit = Some(callback);
self.with_common(|common| {
common.callbacks.quit = Some(callback);
});
}
fn on_reopen(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.borrow_mut().reopen = Some(callback);
self.with_common(|common| {
common.callbacks.reopen = Some(callback);
});
}
fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
self.inner.callbacks.borrow_mut().event = Some(callback);
self.with_common(|common| {
common.callbacks.event = Some(callback);
});
}
fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
self.inner.callbacks.borrow_mut().app_menu_action = Some(callback);
self.with_common(|common| {
common.callbacks.app_menu_action = Some(callback);
});
}
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.borrow_mut().will_open_app_menu = Some(callback);
self.with_common(|common| {
common.callbacks.will_open_app_menu = Some(callback);
});
}
fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
self.inner.callbacks.borrow_mut().validate_app_menu_command = Some(callback);
self.with_common(|common| {
common.callbacks.validate_app_menu_command = Some(callback);
});
}
fn os_name(&self) -> &'static str {
@ -381,7 +436,7 @@ impl Platform for LinuxPlatform {
}
fn set_cursor_style(&self, style: CursorStyle) {
self.client.set_cursor_style(style)
self.set_cursor_style(style)
}
// todo(linux)
@ -389,23 +444,6 @@ impl Platform for LinuxPlatform {
false
}
fn write_to_clipboard(&self, item: ClipboardItem) {
let clipboard = self.client.get_clipboard();
clipboard.borrow_mut().set_contents(item.text);
}
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
let clipboard = self.client.get_clipboard();
let contents = clipboard.borrow_mut().get_contents();
match contents {
Ok(text) => Some(ClipboardItem {
metadata: None,
text,
}),
_ => None,
}
}
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
let url = url.to_string();
let username = username.to_string();
@ -479,14 +517,136 @@ impl Platform for LinuxPlatform {
fn register_url_scheme(&self, _: &str) -> Task<anyhow::Result<()>> {
Task::ready(Err(anyhow!("register_url_scheme unimplemented")))
}
fn write_to_clipboard(&self, item: ClipboardItem) {
self.write_to_clipboard(item)
}
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
self.read_from_clipboard()
}
}
pub(super) fn is_within_click_distance(a: Point<Pixels>, b: Point<Pixels>) -> bool {
let diff = a - b;
diff.x.abs() <= DOUBLE_CLICK_DISTANCE && diff.y.abs() <= DOUBLE_CLICK_DISTANCE
}
impl Keystroke {
pub(super) fn from_xkb(state: &State, modifiers: Modifiers, keycode: Keycode) -> Self {
let mut modifiers = modifiers;
let key_utf32 = state.key_get_utf32(keycode);
let key_utf8 = state.key_get_utf8(keycode);
let key_sym = state.key_get_one_sym(keycode);
// The logic here tries to replicate the logic in `../mac/events.rs`
// "Consumed" modifiers are modifiers that have been used to translate a key, for example
// pressing "shift" and "1" on US layout produces the key `!` but "consumes" the shift.
// Notes:
// - macOS gets the key character directly ("."), xkb gives us the key name ("period")
// - macOS logic removes consumed shift modifier for symbols: "{", not "shift-{"
// - macOS logic keeps consumed shift modifiers for letters: "shift-a", not "a" or "A"
let mut handle_consumed_modifiers = true;
let key = match key_sym {
Keysym::Return => "enter".to_owned(),
Keysym::Prior => "pageup".to_owned(),
Keysym::Next => "pagedown".to_owned(),
Keysym::comma => ",".to_owned(),
Keysym::period => ".".to_owned(),
Keysym::less => "<".to_owned(),
Keysym::greater => ">".to_owned(),
Keysym::slash => "/".to_owned(),
Keysym::question => "?".to_owned(),
Keysym::semicolon => ";".to_owned(),
Keysym::colon => ":".to_owned(),
Keysym::apostrophe => "'".to_owned(),
Keysym::quotedbl => "\"".to_owned(),
Keysym::bracketleft => "[".to_owned(),
Keysym::braceleft => "{".to_owned(),
Keysym::bracketright => "]".to_owned(),
Keysym::braceright => "}".to_owned(),
Keysym::backslash => "\\".to_owned(),
Keysym::bar => "|".to_owned(),
Keysym::grave => "`".to_owned(),
Keysym::asciitilde => "~".to_owned(),
Keysym::exclam => "!".to_owned(),
Keysym::at => "@".to_owned(),
Keysym::numbersign => "#".to_owned(),
Keysym::dollar => "$".to_owned(),
Keysym::percent => "%".to_owned(),
Keysym::asciicircum => "^".to_owned(),
Keysym::ampersand => "&".to_owned(),
Keysym::asterisk => "*".to_owned(),
Keysym::parenleft => "(".to_owned(),
Keysym::parenright => ")".to_owned(),
Keysym::minus => "-".to_owned(),
Keysym::underscore => "_".to_owned(),
Keysym::equal => "=".to_owned(),
Keysym::plus => "+".to_owned(),
Keysym::ISO_Left_Tab => {
handle_consumed_modifiers = false;
"tab".to_owned()
}
_ => {
handle_consumed_modifiers = false;
xkb::keysym_get_name(key_sym).to_lowercase()
}
};
// Ignore control characters (and DEL) for the purposes of ime_key,
// but if key_utf32 is 0 then assume it isn't one
let ime_key = ((key_utf32 == 0 || (key_utf32 >= 32 && key_utf32 != 127))
&& !key_utf8.is_empty())
.then_some(key_utf8);
if handle_consumed_modifiers {
let mod_shift_index = state.get_keymap().mod_get_index(xkb::MOD_NAME_SHIFT);
let is_shift_consumed = state.mod_index_is_consumed(keycode, mod_shift_index);
if modifiers.shift && is_shift_consumed {
modifiers.shift = false;
}
}
Keystroke {
modifiers,
key,
ime_key,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{px, Point};
fn build_platform() -> LinuxPlatform {
let platform = LinuxPlatform::new();
platform
#[test]
fn test_is_within_click_distance() {
let zero = Point::new(px(0.0), px(0.0));
assert_eq!(
is_within_click_distance(zero, Point::new(px(5.0), px(5.0))),
true
);
assert_eq!(
is_within_click_distance(zero, Point::new(px(-4.9), px(5.0))),
true
);
assert_eq!(
is_within_click_distance(Point::new(px(3.0), px(2.0)), Point::new(px(-2.0), px(-2.0))),
true
);
assert_eq!(
is_within_click_distance(zero, Point::new(px(5.0), px(5.1))),
false
);
}
}

View file

@ -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
);
}
}

View file

@ -1,9 +1,6 @@
// todo(linux): remove this once the relevant functionality has been implemented
#![allow(unused_variables)]
pub(crate) use client::*;
mod client;
mod cursor;
mod display;
mod window;
pub(crate) use client::*;

File diff suppressed because it is too large Load diff

View file

@ -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");

View file

@ -7,23 +7,28 @@ use std::sync::Arc;
use blade_graphics as gpu;
use blade_rwh::{HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle};
use collections::HashSet;
use collections::{HashMap, HashSet};
use futures::channel::oneshot::Receiver;
use raw_window_handle::{
DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, WindowHandle,
};
use wayland_backend::client::ObjectId;
use wayland_client::WEnum;
use wayland_client::{protocol::wl_surface, Proxy};
use wayland_protocols::wp::fractional_scale::v1::client::wp_fractional_scale_v1;
use wayland_protocols::wp::viewporter::client::wp_viewport;
use wayland_protocols::xdg::shell::client::xdg_toplevel;
use wayland_protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1;
use wayland_protocols::xdg::shell::client::xdg_surface;
use wayland_protocols::xdg::shell::client::xdg_toplevel::{self, WmCapabilities};
use crate::platform::blade::BladeRenderer;
use crate::platform::linux::wayland::display::WaylandDisplay;
use crate::platform::{PlatformAtlas, PlatformInputHandler, PlatformWindow};
use crate::scene::Scene;
use crate::{
px, size, Bounds, DevicePixels, Modifiers, Pixels, PlatformDisplay, PlatformInput, Point,
PromptLevel, Size, WindowAppearance, WindowBackgroundAppearance, WindowParams,
px, size, Bounds, DevicePixels, Globals, Modifiers, Pixels, PlatformDisplay, PlatformInput,
Point, PromptLevel, RcRefCell, Size, WindowAppearance, WindowBackgroundAppearance,
WindowParams,
};
#[derive(Default)]
@ -39,14 +44,6 @@ pub(crate) struct Callbacks {
appearance_changed: Option<Box<dyn FnMut()>>,
}
struct WaylandWindowInner {
renderer: BladeRenderer,
bounds: Bounds<u32>,
scale: f32,
input_handler: Option<PlatformInputHandler>,
decoration_state: WaylandDecorationState,
}
struct RawWindow {
window: *mut c_void,
display: *mut c_void,
@ -68,11 +65,36 @@ unsafe impl HasRawDisplayHandle for RawWindow {
}
}
impl WaylandWindowInner {
fn new(wl_surf: &Arc<wl_surface::WlSurface>, bounds: Bounds<u32>) -> Self {
pub struct WaylandWindowState {
xdg_surface: xdg_surface::XdgSurface,
surface: wl_surface::WlSurface,
toplevel: xdg_toplevel::XdgToplevel,
viewport: Option<wp_viewport::WpViewport>,
outputs: HashSet<ObjectId>,
globals: Globals,
renderer: BladeRenderer,
bounds: Bounds<u32>,
scale: f32,
input_handler: Option<PlatformInputHandler>,
decoration_state: WaylandDecorationState,
fullscreen: bool,
maximized: bool,
}
impl WaylandWindowState {
pub(crate) fn new(
surface: wl_surface::WlSurface,
xdg_surface: xdg_surface::XdgSurface,
viewport: Option<wp_viewport::WpViewport>,
toplevel: xdg_toplevel::XdgToplevel,
globals: Globals,
options: WindowParams,
) -> Self {
let bounds = options.bounds.map(|p| p.0 as u32);
let raw = RawWindow {
window: wl_surf.id().as_ptr().cast::<c_void>(),
display: wl_surf
window: surface.id().as_ptr().cast::<c_void>(),
display: surface
.backend()
.upgrade()
.unwrap()
@ -97,54 +119,213 @@ impl WaylandWindowInner {
height: bounds.size.height,
depth: 1,
};
Self {
xdg_surface,
surface,
toplevel,
viewport,
globals,
outputs: HashSet::default(),
renderer: BladeRenderer::new(gpu, extent),
bounds,
scale: 1.0,
input_handler: None,
// On wayland, decorations are by default provided by the client
decoration_state: WaylandDecorationState::Client,
fullscreen: false,
maximized: false,
}
}
}
pub(crate) struct WaylandWindowState {
inner: RefCell<WaylandWindowInner>,
pub(crate) callbacks: RefCell<Callbacks>,
pub(crate) surface: Arc<wl_surface::WlSurface>,
pub(crate) toplevel: Arc<xdg_toplevel::XdgToplevel>,
pub(crate) outputs: RefCell<HashSet<ObjectId>>,
viewport: Option<wp_viewport::WpViewport>,
fullscreen: RefCell<bool>,
#[derive(Clone)]
pub(crate) struct WaylandWindow {
pub(crate) state: RcRefCell<WaylandWindowState>,
pub(crate) callbacks: Rc<RefCell<Callbacks>>,
}
impl WaylandWindowState {
pub(crate) fn new(
wl_surf: Arc<wl_surface::WlSurface>,
viewport: Option<wp_viewport::WpViewport>,
toplevel: Arc<xdg_toplevel::XdgToplevel>,
options: WindowParams,
) -> Self {
let bounds = options.bounds.map(|p| p.0 as u32);
impl WaylandWindow {
pub fn ptr_eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.state, &other.state)
}
Self {
surface: Arc::clone(&wl_surf),
inner: RefCell::new(WaylandWindowInner::new(&wl_surf, bounds)),
callbacks: RefCell::new(Callbacks::default()),
outputs: RefCell::new(HashSet::default()),
toplevel,
pub fn new(globals: Globals, params: WindowParams) -> (Self, ObjectId) {
let surface = globals.compositor.create_surface(&globals.qh, ());
let xdg_surface = globals
.wm_base
.get_xdg_surface(&surface, &globals.qh, surface.id());
let toplevel = xdg_surface.get_toplevel(&globals.qh, surface.id());
if let Some(fractional_scale_manager) = globals.fractional_scale_manager.as_ref() {
fractional_scale_manager.get_fractional_scale(&surface, &globals.qh, surface.id());
}
// Attempt to set up window decorations based on the requested configuration
if let Some(decoration_manager) = globals.decoration_manager.as_ref() {
let decoration =
decoration_manager.get_toplevel_decoration(&toplevel, &globals.qh, surface.id());
// Request client side decorations if possible
decoration.set_mode(zxdg_toplevel_decoration_v1::Mode::ClientSide);
}
let viewport = globals
.viewporter
.as_ref()
.map(|viewporter| viewporter.get_viewport(&surface, &globals.qh, ()));
surface.frame(&globals.qh, surface.id());
let window_state = RcRefCell::new(WaylandWindowState::new(
surface.clone(),
xdg_surface,
viewport,
fullscreen: RefCell::new(false),
toplevel,
globals,
params,
));
let this = Self {
state: window_state,
callbacks: Rc::new(RefCell::new(Callbacks::default())),
};
// Kick things off
surface.commit();
(this, surface.id())
}
pub fn frame(&self) {
let state = self.state.borrow_mut();
state.surface.frame(&state.globals.qh, state.surface.id());
drop(state);
let mut cb = self.callbacks.borrow_mut();
if let Some(fun) = cb.request_frame.as_mut() {
fun();
}
}
pub fn update(&self) {
let mut cb = self.callbacks.borrow_mut();
if let Some(mut fun) = cb.request_frame.take() {
drop(cb);
fun();
self.callbacks.borrow_mut().request_frame = Some(fun);
pub fn handle_toplevel_decoration_event(&self, event: zxdg_toplevel_decoration_v1::Event) {
match event {
zxdg_toplevel_decoration_v1::Event::Configure { mode } => match mode {
WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ServerSide) => {
self.set_decoration_state(WaylandDecorationState::Server)
}
WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ClientSide) => {
self.set_decoration_state(WaylandDecorationState::Server)
}
WEnum::Value(_) => {
log::warn!("Unknown decoration mode");
}
WEnum::Unknown(v) => {
log::warn!("Unknown decoration mode: {}", v);
}
},
_ => {}
}
}
pub fn handle_fractional_scale_event(&self, event: wp_fractional_scale_v1::Event) {
match event {
wp_fractional_scale_v1::Event::PreferredScale { scale } => {
self.rescale(scale as f32 / 120.0);
}
_ => {}
}
}
pub fn handle_toplevel_event(&self, event: xdg_toplevel::Event) -> bool {
match event {
xdg_toplevel::Event::Configure {
width,
height,
states,
} => {
let width = NonZeroU32::new(width as u32);
let height = NonZeroU32::new(height as u32);
let fullscreen = states.contains(&(xdg_toplevel::State::Fullscreen as u8));
let maximized = states.contains(&(xdg_toplevel::State::Maximized as u8));
self.resize(width, height);
self.set_fullscreen(fullscreen);
let mut state = self.state.borrow_mut();
state.maximized = true;
false
}
xdg_toplevel::Event::Close => {
let mut cb = self.callbacks.borrow_mut();
if let Some(mut should_close) = cb.should_close.take() {
let result = (should_close)();
cb.should_close = Some(should_close);
result
} else {
false
}
}
_ => false,
}
}
pub fn handle_surface_event(
&self,
event: wl_surface::Event,
output_scales: HashMap<ObjectId, i32>,
) {
let mut state = self.state.borrow_mut();
// We use `WpFractionalScale` instead to set the scale if it's available
if state.globals.fractional_scale_manager.is_some() {
return;
}
match event {
wl_surface::Event::Enter { output } => {
// We use `PreferredBufferScale` instead to set the scale if it's available
if state.surface.version() >= wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE {
return;
}
state.outputs.insert(output.id());
let mut scale = 1;
for output in state.outputs.iter() {
if let Some(s) = output_scales.get(output) {
scale = scale.max(*s)
}
}
state.surface.set_buffer_scale(scale);
drop(state);
self.rescale(scale as f32);
}
wl_surface::Event::Leave { output } => {
// We use `PreferredBufferScale` instead to set the scale if it's available
if state.surface.version() >= wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE {
return;
}
state.outputs.remove(&output.id());
let mut scale = 1;
for output in state.outputs.iter() {
if let Some(s) = output_scales.get(output) {
scale = scale.max(*s)
}
}
state.surface.set_buffer_scale(scale);
drop(state);
self.rescale(scale as f32);
}
wl_surface::Event::PreferredBufferScale { factor } => {
state.surface.set_buffer_scale(factor);
drop(state);
self.rescale(factor as f32);
}
_ => {}
}
}
@ -155,26 +336,26 @@ impl WaylandWindowState {
scale: Option<f32>,
) {
let (width, height, scale) = {
let mut inner = self.inner.borrow_mut();
if width.map_or(true, |width| width.get() == inner.bounds.size.width)
&& height.map_or(true, |height| height.get() == inner.bounds.size.height)
&& scale.map_or(true, |scale| scale == inner.scale)
let mut state = self.state.borrow_mut();
if width.map_or(true, |width| width.get() == state.bounds.size.width)
&& height.map_or(true, |height| height.get() == state.bounds.size.height)
&& scale.map_or(true, |scale| scale == state.scale)
{
return;
}
if let Some(width) = width {
inner.bounds.size.width = width.get();
state.bounds.size.width = width.get();
}
if let Some(height) = height {
inner.bounds.size.height = height.get();
state.bounds.size.height = height.get();
}
if let Some(scale) = scale {
inner.scale = scale;
state.scale = scale;
}
let width = inner.bounds.size.width;
let height = inner.bounds.size.height;
let scale = inner.scale;
inner.renderer.update_drawable_size(size(
let width = state.bounds.size.width;
let height = state.bounds.size.height;
let scale = state.scale;
state.renderer.update_drawable_size(size(
width as f64 * scale as f64,
height as f64 * scale as f64,
));
@ -191,8 +372,11 @@ impl WaylandWindowState {
);
}
if let Some(viewport) = &self.viewport {
viewport.set_destination(width as i32, height as i32);
{
let state = self.state.borrow();
if let Some(viewport) = &state.viewport {
viewport.set_destination(width as i32, height as i32);
}
}
}
@ -205,11 +389,13 @@ impl WaylandWindowState {
}
pub fn set_fullscreen(&self, fullscreen: bool) {
let mut state = self.state.borrow_mut();
state.fullscreen = fullscreen;
let mut callbacks = self.callbacks.borrow_mut();
if let Some(ref mut fun) = callbacks.fullscreen {
fun(fullscreen)
}
self.fullscreen.replace(fullscreen);
}
/// Notifies the window of the state of the decorations.
@ -221,9 +407,7 @@ impl WaylandWindowState {
/// of the decorations. This is because the state of the decorations
/// is managed by the compositor and not the client.
pub fn set_decoration_state(&self, state: WaylandDecorationState) {
self.inner.borrow_mut().decoration_state = state;
log::trace!("Window decorations are now handled by {:?}", state);
// todo(linux) - Handle this properly
self.state.borrow_mut().decoration_state = state;
}
pub fn close(&self) {
@ -231,7 +415,7 @@ impl WaylandWindowState {
if let Some(fun) = callbacks.close.take() {
fun()
}
self.toplevel.destroy();
self.state.borrow_mut().toplevel.destroy();
}
pub fn handle_input(&self, input: PlatformInput) {
@ -241,10 +425,13 @@ impl WaylandWindowState {
}
}
if let PlatformInput::KeyDown(event) = input {
let mut inner = self.inner.borrow_mut();
if let Some(ref mut input_handler) = inner.input_handler {
let mut state = self.state.borrow_mut();
if let Some(mut input_handler) = state.input_handler.take() {
if let Some(ime_key) = &event.keystroke.ime_key {
drop(state);
input_handler.replace_text_in_range(None, ime_key);
let mut state = self.state.borrow_mut();
state.input_handler = Some(input_handler);
}
}
}
@ -257,9 +444,6 @@ impl WaylandWindowState {
}
}
#[derive(Clone)]
pub(crate) struct WaylandWindow(pub(crate) Rc<WaylandWindowState>);
impl HasWindowHandle for WaylandWindow {
fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
unimplemented!()
@ -273,31 +457,29 @@ impl HasDisplayHandle for WaylandWindow {
}
impl PlatformWindow for WaylandWindow {
// todo(linux)
fn bounds(&self) -> Bounds<DevicePixels> {
unimplemented!()
self.state.borrow().bounds.map(|p| DevicePixels(p as i32))
}
// todo(linux)
fn is_maximized(&self) -> bool {
false
self.state.borrow().maximized
}
// todo(linux)
fn is_minimized(&self) -> bool {
// This cannot be determined by the client
false
}
fn content_size(&self) -> Size<Pixels> {
let inner = self.0.inner.borrow();
let state = self.state.borrow();
Size {
width: Pixels(inner.bounds.size.width as f32),
height: Pixels(inner.bounds.size.height as f32),
width: Pixels(state.bounds.size.width as f32),
height: Pixels(state.bounds.size.height as f32),
}
}
fn scale_factor(&self) -> f32 {
self.0.inner.borrow().scale
self.state.borrow().scale
}
// todo(linux)
@ -325,11 +507,11 @@ impl PlatformWindow for WaylandWindow {
}
fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
self.0.inner.borrow_mut().input_handler = Some(input_handler);
self.state.borrow_mut().input_handler = Some(input_handler);
}
fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
self.0.inner.borrow_mut().input_handler.take()
self.state.borrow_mut().input_handler.take()
}
fn prompt(
@ -352,7 +534,10 @@ impl PlatformWindow for WaylandWindow {
}
fn set_title(&mut self, title: &str) {
self.0.toplevel.set_title(title.to_string());
self.state
.borrow_mut()
.toplevel
.set_title(title.to_string());
}
fn set_background_appearance(&mut self, _background_appearance: WindowBackgroundAppearance) {
@ -368,7 +553,7 @@ impl PlatformWindow for WaylandWindow {
}
fn minimize(&self) {
self.0.toplevel.set_minimized();
self.state.borrow_mut().toplevel.set_minimized();
}
fn zoom(&self) {
@ -376,47 +561,48 @@ impl PlatformWindow for WaylandWindow {
}
fn toggle_fullscreen(&self) {
if !(*self.0.fullscreen.borrow()) {
self.0.toplevel.set_fullscreen(None);
let state = self.state.borrow_mut();
if !state.fullscreen {
state.toplevel.set_fullscreen(None);
} else {
self.0.toplevel.unset_fullscreen();
state.toplevel.unset_fullscreen();
}
}
fn is_fullscreen(&self) -> bool {
*self.0.fullscreen.borrow()
self.state.borrow().fullscreen
}
fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
self.0.callbacks.borrow_mut().request_frame = Some(callback);
self.callbacks.borrow_mut().request_frame = Some(callback);
}
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
self.0.callbacks.borrow_mut().input = Some(callback);
self.callbacks.borrow_mut().input = Some(callback);
}
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
self.0.callbacks.borrow_mut().active_status_change = Some(callback);
self.callbacks.borrow_mut().active_status_change = Some(callback);
}
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
self.0.callbacks.borrow_mut().resize = Some(callback);
self.callbacks.borrow_mut().resize = Some(callback);
}
fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
self.0.callbacks.borrow_mut().fullscreen = Some(callback);
self.callbacks.borrow_mut().fullscreen = Some(callback);
}
fn on_moved(&self, callback: Box<dyn FnMut()>) {
self.0.callbacks.borrow_mut().moved = Some(callback);
self.callbacks.borrow_mut().moved = Some(callback);
}
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
self.0.callbacks.borrow_mut().should_close = Some(callback);
self.callbacks.borrow_mut().should_close = Some(callback);
}
fn on_close(&self, callback: Box<dyn FnOnce()>) {
self.0.callbacks.borrow_mut().close = Some(callback);
self.callbacks.borrow_mut().close = Some(callback);
}
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
@ -429,12 +615,18 @@ impl PlatformWindow for WaylandWindow {
}
fn draw(&self, scene: &Scene) {
self.0.inner.borrow_mut().renderer.draw(scene);
let mut state = self.state.borrow_mut();
state.renderer.draw(scene);
}
fn completed_frame(&self) {
let mut state = self.state.borrow_mut();
state.surface.commit();
}
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
let inner = self.0.inner.borrow();
inner.renderer.sprite_atlas().clone()
let state = self.state.borrow();
state.renderer.sprite_atlas().clone()
}
}

View file

@ -1,11 +1,14 @@
use std::cell::RefCell;
use std::ops::Deref;
use std::rc::Rc;
use std::time::{Duration, Instant};
use calloop::{EventLoop, LoopHandle};
use collections::HashMap;
use copypasta::x11_clipboard::{Clipboard, Primary, X11ClipboardContext};
use copypasta::ClipboardProvider;
use util::ResultExt;
use x11rb::connection::{Connection, RequestConnection};
use x11rb::errors::ConnectionError;
use x11rb::protocol::randr::ConnectionExt as _;
@ -16,50 +19,71 @@ use x11rb::xcb_ffi::XCBConnection;
use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION};
use xkbcommon::xkb as xkbc;
use crate::platform::linux::client::Client;
use crate::platform::{LinuxPlatformInner, PlatformWindow};
use crate::platform::linux::LinuxClient;
use crate::platform::{LinuxCommon, PlatformWindow};
use crate::{
px, AnyWindowHandle, Bounds, CursorStyle, DisplayId, Pixels, PlatformDisplay, PlatformInput,
Point, ScrollDelta, Size, TouchPhase, WindowParams,
};
use super::{super::SCROLL_LINES, X11Display, X11Window, X11WindowState, XcbAtoms};
use super::{super::SCROLL_LINES, X11Display, X11Window, XcbAtoms};
use super::{button_of_key, modifiers_from_state};
use crate::platform::linux::is_within_click_distance;
use crate::platform::linux::platform::DOUBLE_CLICK_INTERVAL;
use crate::platform::linux::util::is_within_click_distance;
use calloop::{
generic::{FdWrapper, Generic},
RegistrationToken,
};
struct WindowRef {
state: Rc<X11WindowState>,
pub(crate) struct WindowRef {
window: X11Window,
refresh_event_token: RegistrationToken,
}
struct X11ClientState {
windows: HashMap<xproto::Window, WindowRef>,
xkb: xkbc::State,
clipboard: Rc<RefCell<X11ClipboardContext<Clipboard>>>,
primary: Rc<RefCell<X11ClipboardContext<Primary>>>,
click_state: ClickState,
impl Deref for WindowRef {
type Target = X11Window;
fn deref(&self) -> &Self::Target {
&self.window
}
}
struct ClickState {
last_click: Instant,
last_location: Point<Pixels>,
current_count: usize,
pub struct X11ClientState {
pub(crate) loop_handle: LoopHandle<'static, X11Client>,
pub(crate) event_loop: Option<calloop::EventLoop<'static, X11Client>>,
pub(crate) last_click: Instant,
pub(crate) last_location: Point<Pixels>,
pub(crate) current_count: usize,
pub(crate) xcb_connection: Rc<XCBConnection>,
pub(crate) x_root_index: usize,
pub(crate) atoms: XcbAtoms,
pub(crate) windows: HashMap<xproto::Window, WindowRef>,
pub(crate) xkb: xkbc::State,
pub(crate) common: LinuxCommon,
pub(crate) clipboard: X11ClipboardContext<Clipboard>,
pub(crate) primary: X11ClipboardContext<Primary>,
}
pub(crate) struct X11Client {
platform_inner: Rc<LinuxPlatformInner>,
xcb_connection: Rc<XCBConnection>,
x_root_index: usize,
atoms: XcbAtoms,
state: RefCell<X11ClientState>,
}
#[derive(Clone)]
pub(crate) struct X11Client(Rc<RefCell<X11ClientState>>);
impl X11Client {
pub(crate) fn new(inner: Rc<LinuxPlatformInner>) -> Rc<Self> {
pub(crate) fn new() -> Self {
let event_loop = EventLoop::try_new().unwrap();
let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
let handle = event_loop.handle();
handle.insert_source(main_receiver, |event, _, _: &mut X11Client| {
if let calloop::channel::Event::Msg(runnable) = event {
runnable.run();
}
});
let (xcb_connection, x_root_index) = XCBConnection::connect(None).unwrap();
xcb_connection
.prefetch_extension_information(xkb::X11_EXTENSION_NAME)
@ -94,30 +118,10 @@ impl X11Client {
let xcb_connection = Rc::new(xcb_connection);
let click_state = ClickState {
last_click: Instant::now(),
last_location: Point::new(px(0.0), px(0.0)),
current_count: 0,
};
let client: Rc<X11Client> = Rc::new(Self {
platform_inner: inner.clone(),
xcb_connection: Rc::clone(&xcb_connection),
x_root_index,
atoms,
state: RefCell::new(X11ClientState {
windows: HashMap::default(),
xkb: xkb_state,
clipboard: Rc::new(RefCell::new(clipboard)),
primary: Rc::new(RefCell::new(primary)),
click_state,
}),
});
// Safety: Safe if xcb::Connection always returns a valid fd
let fd = unsafe { FdWrapper::new(Rc::clone(&xcb_connection)) };
inner
.loop_handle
handle
.insert_source(
Generic::new_with_error::<ConnectionError>(
fd,
@ -125,8 +129,8 @@ impl X11Client {
calloop::Mode::Level,
),
{
let client = Rc::clone(&client);
move |_readiness, _, _| {
let xcb_connection = xcb_connection.clone();
move |_readiness, _, client| {
while let Some(event) = xcb_connection.poll_for_event()? {
client.handle_event(event);
}
@ -136,34 +140,47 @@ impl X11Client {
)
.expect("Failed to initialize x11 event source");
client
X11Client(Rc::new(RefCell::new(X11ClientState {
event_loop: Some(event_loop),
loop_handle: handle,
common,
last_click: Instant::now(),
last_location: Point::new(px(0.0), px(0.0)),
current_count: 0,
xcb_connection,
x_root_index,
atoms,
windows: HashMap::default(),
xkb: xkb_state,
clipboard,
primary,
})))
}
fn get_window(&self, win: xproto::Window) -> Option<Rc<X11WindowState>> {
let state = self.state.borrow();
state.windows.get(&win).map(|wr| Rc::clone(&wr.state))
fn get_window(&self, win: xproto::Window) -> Option<X11Window> {
let state = self.0.borrow();
state
.windows
.get(&win)
.map(|window_reference| window_reference.window.clone())
}
fn handle_event(&self, event: Event) -> Option<()> {
match event {
Event::ClientMessage(event) => {
let [atom, ..] = event.data.as_data32();
if atom == self.atoms.WM_DELETE_WINDOW {
let mut state = self.0.borrow_mut();
if atom == state.atoms.WM_DELETE_WINDOW {
// window "x" button clicked by user, we gracefully exit
let window_ref = self
.state
.borrow_mut()
.windows
.remove(&event.window)
.unwrap();
let window_ref = state.windows.remove(&event.window)?;
self.platform_inner
.loop_handle
.remove(window_ref.refresh_event_token);
window_ref.state.destroy();
state.loop_handle.remove(window_ref.refresh_event_token);
window_ref.window.destroy();
if self.state.borrow().windows.is_empty() {
self.platform_inner.loop_signal.stop();
if state.windows.is_empty() {
state.common.signal.stop();
}
}
}
@ -195,15 +212,17 @@ impl X11Client {
}
Event::KeyPress(event) => {
let window = self.get_window(event.event)?;
let modifiers = super::modifiers_from_state(event.state);
let mut state = self.0.borrow_mut();
let modifiers = modifiers_from_state(event.state);
let keystroke = {
let code = event.detail.into();
let mut state = self.state.borrow_mut();
let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
state.xkb.update_key(code, xkbc::KeyDirection::Down);
keystroke
};
drop(state);
window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
keystroke,
is_held: false,
@ -211,48 +230,54 @@ impl X11Client {
}
Event::KeyRelease(event) => {
let window = self.get_window(event.event)?;
let modifiers = super::modifiers_from_state(event.state);
let mut state = self.0.borrow_mut();
let modifiers = modifiers_from_state(event.state);
let keystroke = {
let code = event.detail.into();
let mut state = self.state.borrow_mut();
let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
state.xkb.update_key(code, xkbc::KeyDirection::Up);
keystroke
};
drop(state);
window.handle_input(PlatformInput::KeyUp(crate::KeyUpEvent { keystroke }));
}
Event::ButtonPress(event) => {
let window = self.get_window(event.event)?;
let modifiers = super::modifiers_from_state(event.state);
let mut state = self.0.borrow_mut();
let modifiers = modifiers_from_state(event.state);
let position =
Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
if let Some(button) = super::button_of_key(event.detail) {
let mut state = self.state.borrow_mut();
let click_elapsed = state.click_state.last_click.elapsed();
if let Some(button) = button_of_key(event.detail) {
let click_elapsed = state.last_click.elapsed();
if click_elapsed < DOUBLE_CLICK_INTERVAL
&& is_within_click_distance(state.click_state.last_location, position)
&& is_within_click_distance(state.last_location, position)
{
state.click_state.current_count += 1;
state.current_count += 1;
} else {
state.click_state.current_count = 1;
state.current_count = 1;
}
state.click_state.last_click = Instant::now();
state.click_state.last_location = position;
state.last_click = Instant::now();
state.last_location = position;
let current_count = state.current_count;
drop(state);
window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent {
button,
position,
modifiers,
click_count: state.click_state.current_count,
click_count: current_count,
first_mouse: false,
}));
} else if event.detail >= 4 && event.detail <= 5 {
// https://stackoverflow.com/questions/15510472/scrollwheel-event-in-x11
let scroll_direction = if event.detail == 4 { 1.0 } else { -1.0 };
let scroll_y = SCROLL_LINES * scroll_direction;
drop(state);
window.handle_input(PlatformInput::ScrollWheel(crate::ScrollWheelEvent {
position,
delta: ScrollDelta::Lines(Point::new(0.0, scroll_y as f32)),
@ -265,16 +290,18 @@ impl X11Client {
}
Event::ButtonRelease(event) => {
let window = self.get_window(event.event)?;
let modifiers = super::modifiers_from_state(event.state);
let state = self.0.borrow();
let modifiers = modifiers_from_state(event.state);
let position =
Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
let state = self.state.borrow();
if let Some(button) = super::button_of_key(event.detail) {
if let Some(button) = button_of_key(event.detail) {
let click_count = state.current_count;
drop(state);
window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent {
button,
position,
modifiers,
click_count: state.click_state.current_count,
click_count,
}));
}
}
@ -283,7 +310,7 @@ impl X11Client {
let pressed_button = super::button_from_state(event.state);
let position =
Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
let modifiers = super::modifiers_from_state(event.state);
let modifiers = modifiers_from_state(event.state);
window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent {
pressed_button,
position,
@ -295,7 +322,7 @@ impl X11Client {
let pressed_button = super::button_from_state(event.state);
let position =
Point::new((event.event_x as f32).into(), (event.event_y as f32).into());
let modifiers = super::modifiers_from_state(event.state);
let modifiers = modifiers_from_state(event.state);
window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
pressed_button,
position,
@ -309,61 +336,71 @@ impl X11Client {
}
}
impl Client for X11Client {
impl LinuxClient for X11Client {
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R {
f(&mut self.0.borrow_mut().common)
}
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
let setup = self.xcb_connection.setup();
let state = self.0.borrow();
let setup = state.xcb_connection.setup();
setup
.roots
.iter()
.enumerate()
.filter_map(|(root_id, _)| {
Some(Rc::new(X11Display::new(&self.xcb_connection, root_id)?)
Some(Rc::new(X11Display::new(&state.xcb_connection, root_id)?)
as Rc<dyn PlatformDisplay>)
})
.collect()
}
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
Some(Rc::new(X11Display::new(
&self.xcb_connection,
id.0 as usize,
)?))
}
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
let state = self.0.borrow();
Some(Rc::new(
X11Display::new(&self.xcb_connection, self.x_root_index)
X11Display::new(&state.xcb_connection, state.x_root_index)
.expect("There should always be a root index"),
))
}
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
let state = self.0.borrow();
Some(Rc::new(X11Display::new(
&state.xcb_connection,
id.0 as usize,
)?))
}
fn open_window(
&self,
_handle: AnyWindowHandle,
options: WindowParams,
params: WindowParams,
) -> Box<dyn PlatformWindow> {
let x_window = self.xcb_connection.generate_id().unwrap();
let mut state = self.0.borrow_mut();
let x_window = state.xcb_connection.generate_id().unwrap();
let window_ptr = Rc::new(X11WindowState::new(
options,
&self.xcb_connection,
self.x_root_index,
let window = X11Window::new(
params,
&state.xcb_connection,
state.x_root_index,
x_window,
&self.atoms,
));
&state.atoms,
);
let screen_resources = self
let screen_resources = state
.xcb_connection
.randr_get_screen_resources(x_window)
.unwrap()
.reply()
.expect("TODO");
.expect("Could not find available screens");
let mode = screen_resources
.crtcs
.iter()
.find_map(|crtc| {
let crtc_info = self
let crtc_info = state
.xcb_connection
.randr_get_crtc_info(*crtc, x11rb::CURRENT_TIME)
.ok()?
@ -377,16 +414,14 @@ impl Client for X11Client {
})
.expect("Unable to find screen refresh rate");
// .expect("Missing screen mode for crtc specified mode id");
let refresh_event_token = self
.platform_inner
let refresh_event_token = state
.loop_handle
.insert_source(calloop::timer::Timer::immediate(), {
let refresh_duration = mode_refresh_rate(mode);
let xcb_connection = Rc::clone(&self.xcb_connection);
move |mut instant, (), _| {
xcb_connection
move |mut instant, (), client| {
let state = client.0.borrow_mut();
state
.xcb_connection
.send_event(
false,
x_window,
@ -403,7 +438,7 @@ impl Client for X11Client {
},
)
.unwrap();
let _ = xcb_connection.flush().unwrap();
let _ = state.xcb_connection.flush().unwrap();
// Take into account that some frames have been skipped
let now = time::Instant::now();
while instant < now {
@ -415,22 +450,42 @@ impl Client for X11Client {
.expect("Failed to initialize refresh timer");
let window_ref = WindowRef {
state: Rc::clone(&window_ptr),
window: window.clone(),
refresh_event_token,
};
self.state.borrow_mut().windows.insert(x_window, window_ref);
Box::new(X11Window(window_ptr))
state.windows.insert(x_window, window_ref);
Box::new(window)
}
//todo(linux)
fn set_cursor_style(&self, _style: CursorStyle) {}
fn get_clipboard(&self) -> Rc<RefCell<dyn ClipboardProvider>> {
self.state.borrow().clipboard.clone()
fn write_to_clipboard(&self, item: crate::ClipboardItem) {
self.0.borrow_mut().clipboard.set_contents(item.text);
}
fn get_primary(&self) -> Rc<RefCell<dyn ClipboardProvider>> {
self.state.borrow().primary.clone()
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
self.0
.borrow_mut()
.clipboard
.get_contents()
.ok()
.map(|text| crate::ClipboardItem {
text,
metadata: None,
})
}
fn run(&self) {
let mut event_loop = self
.0
.borrow_mut()
.event_loop
.take()
.expect("App is already running");
event_loop.run(None, &mut self.clone(), |_| {}).log_err();
}
}

View file

@ -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()
}
}

View file

@ -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]

View file

@ -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 != ?