Compare commits
2 Commits
another
...
fix-more-l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa0d0af8c6 | ||
|
|
6387859874 |
@@ -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")]
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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>>;
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
use xkbcommon::xkb::{self, Keycode, Keysym, State};
|
||||
|
||||
use super::DOUBLE_CLICK_DISTANCE;
|
||||
use crate::{Keystroke, Modifiers, Pixels, Point};
|
||||
|
||||
impl Keystroke {
|
||||
pub(super) fn from_xkb(state: &State, modifiers: Modifiers, keycode: Keycode) -> Self {
|
||||
let mut modifiers = modifiers;
|
||||
|
||||
let key_utf32 = state.key_get_utf32(keycode);
|
||||
let key_utf8 = state.key_get_utf8(keycode);
|
||||
let key_sym = state.key_get_one_sym(keycode);
|
||||
|
||||
// The logic here tries to replicate the logic in `../mac/events.rs`
|
||||
// "Consumed" modifiers are modifiers that have been used to translate a key, for example
|
||||
// pressing "shift" and "1" on US layout produces the key `!` but "consumes" the shift.
|
||||
// Notes:
|
||||
// - macOS gets the key character directly ("."), xkb gives us the key name ("period")
|
||||
// - macOS logic removes consumed shift modifier for symbols: "{", not "shift-{"
|
||||
// - macOS logic keeps consumed shift modifiers for letters: "shift-a", not "a" or "A"
|
||||
|
||||
let mut handle_consumed_modifiers = true;
|
||||
let key = match key_sym {
|
||||
Keysym::Return => "enter".to_owned(),
|
||||
Keysym::Prior => "pageup".to_owned(),
|
||||
Keysym::Next => "pagedown".to_owned(),
|
||||
|
||||
Keysym::comma => ",".to_owned(),
|
||||
Keysym::period => ".".to_owned(),
|
||||
Keysym::less => "<".to_owned(),
|
||||
Keysym::greater => ">".to_owned(),
|
||||
Keysym::slash => "/".to_owned(),
|
||||
Keysym::question => "?".to_owned(),
|
||||
|
||||
Keysym::semicolon => ";".to_owned(),
|
||||
Keysym::colon => ":".to_owned(),
|
||||
Keysym::apostrophe => "'".to_owned(),
|
||||
Keysym::quotedbl => "\"".to_owned(),
|
||||
|
||||
Keysym::bracketleft => "[".to_owned(),
|
||||
Keysym::braceleft => "{".to_owned(),
|
||||
Keysym::bracketright => "]".to_owned(),
|
||||
Keysym::braceright => "}".to_owned(),
|
||||
Keysym::backslash => "\\".to_owned(),
|
||||
Keysym::bar => "|".to_owned(),
|
||||
|
||||
Keysym::grave => "`".to_owned(),
|
||||
Keysym::asciitilde => "~".to_owned(),
|
||||
Keysym::exclam => "!".to_owned(),
|
||||
Keysym::at => "@".to_owned(),
|
||||
Keysym::numbersign => "#".to_owned(),
|
||||
Keysym::dollar => "$".to_owned(),
|
||||
Keysym::percent => "%".to_owned(),
|
||||
Keysym::asciicircum => "^".to_owned(),
|
||||
Keysym::ampersand => "&".to_owned(),
|
||||
Keysym::asterisk => "*".to_owned(),
|
||||
Keysym::parenleft => "(".to_owned(),
|
||||
Keysym::parenright => ")".to_owned(),
|
||||
Keysym::minus => "-".to_owned(),
|
||||
Keysym::underscore => "_".to_owned(),
|
||||
Keysym::equal => "=".to_owned(),
|
||||
Keysym::plus => "+".to_owned(),
|
||||
|
||||
Keysym::ISO_Left_Tab => {
|
||||
handle_consumed_modifiers = false;
|
||||
"tab".to_owned()
|
||||
}
|
||||
|
||||
_ => {
|
||||
handle_consumed_modifiers = false;
|
||||
xkb::keysym_get_name(key_sym).to_lowercase()
|
||||
}
|
||||
};
|
||||
|
||||
// Ignore control characters (and DEL) for the purposes of ime_key,
|
||||
// but if key_utf32 is 0 then assume it isn't one
|
||||
let ime_key = ((key_utf32 == 0 || (key_utf32 >= 32 && key_utf32 != 127))
|
||||
&& !key_utf8.is_empty())
|
||||
.then_some(key_utf8);
|
||||
|
||||
if handle_consumed_modifiers {
|
||||
let mod_shift_index = state.get_keymap().mod_get_index(xkb::MOD_NAME_SHIFT);
|
||||
let is_shift_consumed = state.mod_index_is_consumed(keycode, mod_shift_index);
|
||||
|
||||
if modifiers.shift && is_shift_consumed {
|
||||
modifiers.shift = false;
|
||||
}
|
||||
}
|
||||
|
||||
Keystroke {
|
||||
modifiers,
|
||||
key,
|
||||
ime_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_within_click_distance(a: Point<Pixels>, b: Point<Pixels>) -> bool {
|
||||
let diff = a - b;
|
||||
diff.x.abs() <= DOUBLE_CLICK_DISTANCE && diff.y.abs() <= DOUBLE_CLICK_DISTANCE
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{px, Point};
|
||||
|
||||
#[test]
|
||||
fn test_is_within_click_distance() {
|
||||
let zero = Point::new(px(0.0), px(0.0));
|
||||
assert_eq!(
|
||||
is_within_click_distance(zero, Point::new(px(5.0), px(5.0))),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
is_within_click_distance(zero, Point::new(px(-4.9), px(5.0))),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
is_within_click_distance(Point::new(px(3.0), px(2.0)), Point::new(px(-2.0), px(-2.0))),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
is_within_click_distance(zero, Point::new(px(5.0), px(5.1))),
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
// todo(linux): remove this once the relevant functionality has been implemented
|
||||
#![allow(unused_variables)]
|
||||
|
||||
pub(crate) use client::*;
|
||||
|
||||
mod client;
|
||||
mod cursor;
|
||||
mod display;
|
||||
mod window;
|
||||
|
||||
pub(crate) use client::*;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user