Compare commits

...

2 Commits

Author SHA1 Message Date
Mikayla Maki
aa0d0af8c6 WIP: Shred linux platform 2024-04-03 17:39:52 -07:00
Mikayla Maki
6387859874 WIP: Rewrite linux platform to reduce abstraction and smart pointers.
Checkpoint: Commented out wayland, converted general linux platform and x11 client
2024-04-03 13:29:46 -07:00
9 changed files with 679 additions and 657 deletions

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

View File

@@ -1,12 +1,11 @@
mod client;
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

@@ -1,5 +1,6 @@
#![allow(unused)]
use std::any::Any;
use std::cell::RefCell;
use std::env;
use std::{
@@ -13,33 +14,57 @@ 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 trait LinuxClient {
fn common(&self, f: &dyn FnOnce(&mut LinuxCommon));
fn common_background_executor(&self) -> BackgroundExecutor;
fn common_foreground_executor(&self) -> ForegroundExecutor;
fn common_text_system(&self) -> Arc<dyn PlatformTextSystem>;
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 Callbacks {
pub(crate) struct PlatformHandlers {
open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
become_active: Option<Box<dyn FnMut()>>,
resign_active: Option<Box<dyn FnMut()>>,
@@ -51,102 +76,61 @@ pub(crate) struct Callbacks {
validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
}
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) struct LinuxCommon {
pub(crate) background_executor: BackgroundExecutor,
pub(crate) foreground_executor: ForegroundExecutor,
pub(crate) text_system: Arc<LinuxTextSystem>,
pub(crate) callbacks: RefCell<Callbacks>,
pub(crate) callbacks: PlatformHandlers,
pub(crate) signal: LoopSignal,
}
pub(crate) struct LinuxPlatform {
client: Rc<dyn Client>,
inner: Rc<LinuxPlatformInner>,
}
impl Default for LinuxPlatform {
fn default() -> Self {
Self::new()
}
}
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 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.common_background_executor()
}
fn foreground_executor(&self) -> ForegroundExecutor {
self.inner.foreground_executor.clone()
self.common_foreground_executor()
}
fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
self.inner.text_system.clone()
self.common_text_system()
}
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");
self.run();
if let Some(mut fun) = self.inner.callbacks.borrow_mut().quit.take() {
fun();
}
self.common(&|common| {
if let Some(mut fun) = common.callbacks.quit.take() {
fun();
}
});
}
fn quit(&self) {
self.inner.loop_signal.stop();
self.common(&|common| common.signal.stop());
}
fn restart(&self) {
@@ -201,15 +185,15 @@ impl Platform for LinuxPlatform {
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 +206,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 +214,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.common(&move |common| common.callbacks.open_urls = Some(callback))
}
fn prompt_for_paths(
@@ -238,8 +222,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 +265,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 +285,7 @@ impl Platform for LinuxPlatform {
done_tx.send(result);
})
.detach();
done_rx
}
@@ -316,38 +299,6 @@ impl Platform for LinuxPlatform {
open::that(dir);
}
fn on_become_active(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.borrow_mut().become_active = Some(callback);
}
fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.borrow_mut().resign_active = Some(callback);
}
fn on_quit(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.borrow_mut().quit = Some(callback);
}
fn on_reopen(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.borrow_mut().reopen = Some(callback);
}
fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
self.inner.callbacks.borrow_mut().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);
}
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.borrow_mut().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);
}
fn os_name(&self) -> &'static str {
"Linux"
}
@@ -381,7 +332,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 +340,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 +413,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::*;

View File

@@ -37,12 +37,11 @@ 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::LinuxClient;
use crate::platform::PlatformWindow;
use crate::{
platform::linux::wayland::window::WaylandWindowState, AnyWindowHandle, CursorStyle, DisplayId,
KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
@@ -50,6 +49,7 @@ use crate::{
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;
@@ -63,7 +63,7 @@ pub(crate) struct WaylandClientStateInner {
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>,
platform_inner: LinuxCommon,
keymap_state: Option<xkb::State>,
click_state: ClickState,
repeat: KeyRepeat,
@@ -103,10 +103,10 @@ pub(crate) struct KeyRepeat {
current_keysym: Option<xkb::Keysym>,
}
pub(crate) struct WaylandClient {
platform_inner: Rc<LinuxPlatformInner>,
state: WaylandClientState,
qh: Arc<QueueHandle<WaylandClientState>>,
pub struct WaylandClient {
// platform_inner: Rc<LinuxPlatformInner>,
// state: WaylandClientState,
// qh: Arc<QueueHandle<WaylandClientState>>,
}
const WL_SEAT_MIN_VERSION: u32 = 4;
@@ -126,111 +126,111 @@ fn wl_seat_version(version: u32) -> u32 {
}
impl WaylandClient {
pub(crate) fn new(linux_platform_inner: Rc<LinuxPlatformInner>) -> Self {
let conn = Connection::connect_to_env().unwrap();
pub(crate) fn new() -> Self {
// let conn = Connection::connect_to_env().unwrap();
let (globals, mut event_queue) = registry_queue_init::<WaylandClientState>(&conn).unwrap();
let qh = event_queue.handle();
let mut outputs = Vec::new();
// let (globals, mut event_queue) = registry_queue_init::<WaylandClientState>(&conn).unwrap();
// let qh = event_queue.handle();
// let mut outputs = Vec::new();
globals.contents().with_list(|list| {
for global in list {
match &global.interface[..] {
"wl_seat" => {
globals.registry().bind::<wl_seat::WlSeat, _, _>(
global.name,
wl_seat_version(global.version),
&qh,
(),
);
}
"wl_output" => outputs.push((
globals.registry().bind::<wl_output::WlOutput, _, _>(
global.name,
WL_OUTPUT_VERSION,
&qh,
(),
),
Rc::new(RefCell::new(OutputState::default())),
)),
_ => {}
}
}
});
// globals.contents().with_list(|list| {
// for global in list {
// match &global.interface[..] {
// "wl_seat" => {
// globals.registry().bind::<wl_seat::WlSeat, _, _>(
// global.name,
// wl_seat_version(global.version),
// &qh,
// (),
// );
// }
// "wl_output" => outputs.push((
// globals.registry().bind::<wl_output::WlOutput, _, _>(
// global.name,
// WL_OUTPUT_VERSION,
// &qh,
// (),
// ),
// Rc::new(RefCell::new(OutputState::default())),
// )),
// _ => {}
// }
// }
// });
let display = conn.backend().display_ptr() as *mut std::ffi::c_void;
let (primary, clipboard) = unsafe { create_clipboards_from_external(display) };
// 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),
keymap_state: None,
click_state: ClickState {
last_click: Instant::now(),
last_location: Point::new(px(0.0), px(0.0)),
current_count: 0,
},
repeat: KeyRepeat {
characters_per_second: 16,
delay: Duration::from_millis(500),
current_id: 0,
current_keysym: None,
},
modifiers: Modifiers {
shift: false,
control: false,
alt: false,
function: false,
platform: false,
},
scroll_direction: -1.0,
axis_source: AxisSource::Wheel,
mouse_location: point(px(0.0), px(0.0)),
button_pressed: None,
mouse_focused_window: None,
keyboard_focused_window: None,
loop_handle: Rc::clone(&linux_platform_inner.loop_handle),
}));
// 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),
// keymap_state: None,
// click_state: ClickState {
// last_click: Instant::now(),
// last_location: Point::new(px(0.0), px(0.0)),
// current_count: 0,
// },
// repeat: KeyRepeat {
// characters_per_second: 16,
// delay: Duration::from_millis(500),
// current_id: 0,
// current_keysym: None,
// },
// modifiers: Modifiers {
// shift: false,
// control: false,
// alt: false,
// function: false,
// platform: false,
// },
// scroll_direction: -1.0,
// axis_source: AxisSource::Wheel,
// mouse_location: point(px(0.0), px(0.0)),
// 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 {
cursor_icon_name: "arrow".to_string(),
cursor: None,
}));
// let mut cursor_state = Rc::new(RefCell::new(CursorState {
// cursor_icon_name: "arrow".to_string(),
// cursor: None,
// }));
let source = WaylandSource::new(conn, event_queue);
// 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();
// 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();
Self {
platform_inner: linux_platform_inner,
state,
qh: Arc::new(qh),
// platform_inner: linux_platform_inner,
// state,
// qh: Arc::new(qh),
}
}
}
impl Client for WaylandClient {
impl LinuxClient for WaylandClient {
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
Vec::new()
}
@@ -248,59 +248,60 @@ impl Client for WaylandClient {
handle: AnyWindowHandle,
options: WindowParams,
) -> Box<dyn PlatformWindow> {
let mut state = self.state.client_state_inner.borrow_mut();
todo!()
// 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);
// 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.
// // 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.
// 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());
// 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);
}
// // 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 viewport = state
// .viewporter
// .as_ref()
// .map(|viewporter| viewporter.get_viewport(&wl_surface, &self.qh, ()));
wl_surface.frame(&self.qh, wl_surface.clone());
wl_surface.commit();
// 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,
));
// 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());
}
// 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());
// }
state.windows.push((xdg_surface, Rc::clone(&window_state)));
Box::new(WaylandWindow(window_state))
// state.windows.push((xdg_surface, Rc::clone(&window_state)));
// Box::new(WaylandWindow(window_state))
}
fn set_cursor_style(&self, style: CursorStyle) {
@@ -329,15 +330,34 @@ impl Client for WaylandClient {
}
.to_string();
self.state.cursor_state.borrow_mut().cursor_icon_name = cursor_icon_name;
todo!()
// self.state.cursor_state.borrow_mut().cursor_icon_name = cursor_icon_name;
}
fn get_clipboard(&self) -> Rc<RefCell<dyn ClipboardProvider>> {
self.state.clipboard.clone()
fn common(&self, f: &dyn Fn(&mut LinuxCommon)) {
todo!()
}
fn get_primary(&self) -> Rc<RefCell<dyn ClipboardProvider>> {
self.state.primary.clone()
fn run(&self) {
todo!()
}
fn write_to_clipboard(&self, item: crate::ClipboardItem) {}
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
todo!()
}
fn common_background_executor(&self) -> crate::BackgroundExecutor {
todo!()
}
fn common_foreground_executor(&self) -> crate::ForegroundExecutor {
todo!()
}
fn common_text_system(&self) -> Arc<dyn crate::PlatformTextSystem> {
todo!()
}
}
@@ -579,7 +599,8 @@ impl Dispatch<xdg_toplevel::XdgToplevel, ()> for WaylandClientState {
true
}
});
state.platform_inner.loop_signal.stop();
todo!()
// state.platform_inner.loop_signal.stop();
}
}
}

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>,
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 common(&self, f: &dyn Fn(&mut LinuxCommon)) {
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();
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,54 @@ 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()
.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();
}
fn common_background_executor(&self) -> crate::BackgroundExecutor {
self.0.borrow().common.background_executor.clone()
}
fn common_foreground_executor(&self) -> crate::ForegroundExecutor {
self.0.borrow().common.foreground_executor.clone()
}
fn common_text_system(&self) -> std::sync::Arc<dyn crate::PlatformTextSystem> {
self.0.borrow().common.text_system.clone()
}
}

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