Trying to clena the mess opened by plugins
Some checks failed
Deploy Website / deploy (push) Failing after 4m53s
Some checks failed
Deploy Website / deploy (push) Failing after 4m53s
This commit is contained in:
100
plugins/baseview/src/x11/cursor.rs
Normal file
100
plugins/baseview/src/x11/cursor.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
use std::error::Error;
|
||||
|
||||
use x11rb::connection::Connection;
|
||||
use x11rb::cursor::Handle as CursorHandle;
|
||||
use x11rb::protocol::xproto::{ConnectionExt as _, Cursor};
|
||||
use x11rb::xcb_ffi::XCBConnection;
|
||||
|
||||
use crate::MouseCursor;
|
||||
|
||||
fn create_empty_cursor(conn: &XCBConnection, screen: usize) -> Result<Cursor, Box<dyn Error>> {
|
||||
let cursor_id = conn.generate_id()?;
|
||||
let pixmap_id = conn.generate_id()?;
|
||||
let root_window = conn.setup().roots[screen].root;
|
||||
conn.create_pixmap(1, pixmap_id, root_window, 1, 1)?;
|
||||
conn.create_cursor(cursor_id, pixmap_id, pixmap_id, 0, 0, 0, 0, 0, 0, 0, 0)?;
|
||||
conn.free_pixmap(pixmap_id)?;
|
||||
|
||||
Ok(cursor_id)
|
||||
}
|
||||
|
||||
fn load_cursor(
|
||||
conn: &XCBConnection, cursor_handle: &CursorHandle, name: &str,
|
||||
) -> Result<Option<Cursor>, Box<dyn Error>> {
|
||||
let cursor = cursor_handle.load_cursor(conn, name)?;
|
||||
if cursor != x11rb::NONE {
|
||||
Ok(Some(cursor))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn load_first_existing_cursor(
|
||||
conn: &XCBConnection, cursor_handle: &CursorHandle, names: &[&str],
|
||||
) -> Result<Option<Cursor>, Box<dyn Error>> {
|
||||
for name in names {
|
||||
let cursor = load_cursor(conn, cursor_handle, name)?;
|
||||
if cursor.is_some() {
|
||||
return Ok(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub(super) fn get_xcursor(
|
||||
conn: &XCBConnection, screen: usize, cursor_handle: &CursorHandle, cursor: MouseCursor,
|
||||
) -> Result<Cursor, Box<dyn Error>> {
|
||||
let load = |name: &str| load_cursor(conn, cursor_handle, name);
|
||||
let loadn = |names: &[&str]| load_first_existing_cursor(conn, cursor_handle, names);
|
||||
|
||||
let cursor = match cursor {
|
||||
MouseCursor::Default => None, // catch this in the fallback case below
|
||||
|
||||
MouseCursor::Hand => loadn(&["hand2", "hand1"])?,
|
||||
MouseCursor::HandGrabbing => loadn(&["closedhand", "grabbing"])?,
|
||||
MouseCursor::Help => load("question_arrow")?,
|
||||
|
||||
MouseCursor::Hidden => Some(create_empty_cursor(conn, screen)?),
|
||||
|
||||
MouseCursor::Text => loadn(&["text", "xterm"])?,
|
||||
MouseCursor::VerticalText => load("vertical-text")?,
|
||||
|
||||
MouseCursor::Working => load("watch")?,
|
||||
MouseCursor::PtrWorking => load("left_ptr_watch")?,
|
||||
|
||||
MouseCursor::NotAllowed => load("crossed_circle")?,
|
||||
MouseCursor::PtrNotAllowed => loadn(&["no-drop", "crossed_circle"])?,
|
||||
|
||||
MouseCursor::ZoomIn => load("zoom-in")?,
|
||||
MouseCursor::ZoomOut => load("zoom-out")?,
|
||||
|
||||
MouseCursor::Alias => load("link")?,
|
||||
MouseCursor::Copy => load("copy")?,
|
||||
MouseCursor::Move => load("move")?,
|
||||
MouseCursor::AllScroll => load("all-scroll")?,
|
||||
MouseCursor::Cell => load("plus")?,
|
||||
MouseCursor::Crosshair => load("crosshair")?,
|
||||
|
||||
MouseCursor::EResize => load("right_side")?,
|
||||
MouseCursor::NResize => load("top_side")?,
|
||||
MouseCursor::NeResize => load("top_right_corner")?,
|
||||
MouseCursor::NwResize => load("top_left_corner")?,
|
||||
MouseCursor::SResize => load("bottom_side")?,
|
||||
MouseCursor::SeResize => load("bottom_right_corner")?,
|
||||
MouseCursor::SwResize => load("bottom_left_corner")?,
|
||||
MouseCursor::WResize => load("left_side")?,
|
||||
MouseCursor::EwResize => load("h_double_arrow")?,
|
||||
MouseCursor::NsResize => load("v_double_arrow")?,
|
||||
MouseCursor::NwseResize => loadn(&["bd_double_arrow", "size_bdiag"])?,
|
||||
MouseCursor::NeswResize => loadn(&["fd_double_arrow", "size_fdiag"])?,
|
||||
MouseCursor::ColResize => loadn(&["split_h", "h_double_arrow"])?,
|
||||
MouseCursor::RowResize => loadn(&["split_v", "v_double_arrow"])?,
|
||||
};
|
||||
|
||||
if let Some(cursor) = cursor {
|
||||
Ok(cursor)
|
||||
} else {
|
||||
Ok(load("left_ptr")?.unwrap_or(x11rb::NONE))
|
||||
}
|
||||
}
|
||||
305
plugins/baseview/src/x11/event_loop.rs
Normal file
305
plugins/baseview/src/x11/event_loop.rs
Normal file
@@ -0,0 +1,305 @@
|
||||
use crate::x11::keyboard::{convert_key_press_event, convert_key_release_event, key_mods};
|
||||
use crate::x11::{ParentHandle, Window, WindowInner};
|
||||
use crate::{
|
||||
Event, MouseButton, MouseEvent, PhyPoint, PhySize, ScrollDelta, WindowEvent, WindowHandler,
|
||||
WindowInfo,
|
||||
};
|
||||
use std::error::Error;
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::time::{Duration, Instant};
|
||||
use x11rb::connection::Connection;
|
||||
use x11rb::protocol::Event as XEvent;
|
||||
|
||||
pub(super) struct EventLoop {
|
||||
handler: Box<dyn WindowHandler>,
|
||||
window: WindowInner,
|
||||
parent_handle: Option<ParentHandle>,
|
||||
|
||||
new_physical_size: Option<PhySize>,
|
||||
frame_interval: Duration,
|
||||
event_loop_running: bool,
|
||||
}
|
||||
|
||||
impl EventLoop {
|
||||
pub fn new(
|
||||
window: WindowInner, handler: impl WindowHandler + 'static,
|
||||
parent_handle: Option<ParentHandle>,
|
||||
) -> Self {
|
||||
Self {
|
||||
window,
|
||||
handler: Box::new(handler),
|
||||
parent_handle,
|
||||
frame_interval: Duration::from_millis(15),
|
||||
event_loop_running: false,
|
||||
new_physical_size: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn drain_xcb_events(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
// the X server has a tendency to send spurious/extraneous configure notify events when a
|
||||
// window is resized, and we need to batch those together and just send one resize event
|
||||
// when they've all been coalesced.
|
||||
self.new_physical_size = None;
|
||||
|
||||
while let Some(event) = self.window.xcb_connection.conn.poll_for_event()? {
|
||||
self.handle_xcb_event(event);
|
||||
}
|
||||
|
||||
if let Some(size) = self.new_physical_size.take() {
|
||||
self.window.window_info =
|
||||
WindowInfo::from_physical_size(size, self.window.window_info.scale());
|
||||
|
||||
let window_info = self.window.window_info;
|
||||
|
||||
self.handler.on_event(
|
||||
&mut crate::Window::new(Window { inner: &self.window }),
|
||||
Event::Window(WindowEvent::Resized(window_info)),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Event loop
|
||||
// FIXME: poll() acts fine on linux, sometimes funky on *BSD. XCB upstream uses a define to
|
||||
// switch between poll() and select() (the latter of which is fine on *BSD), and we should do
|
||||
// the same.
|
||||
pub fn run(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
use nix::poll::*;
|
||||
|
||||
let xcb_fd = self.window.xcb_connection.conn.as_raw_fd();
|
||||
|
||||
let mut last_frame = Instant::now();
|
||||
self.event_loop_running = true;
|
||||
|
||||
while self.event_loop_running {
|
||||
// We'll try to keep a consistent frame pace. If the last frame couldn't be processed in
|
||||
// the expected frame time, this will throttle down to prevent multiple frames from
|
||||
// being queued up. The conditional here is needed because event handling and frame
|
||||
// drawing is interleaved. The `poll()` function below will wait until the next frame
|
||||
// can be drawn, or until the window receives an event. We thus need to manually check
|
||||
// if it's already time to draw a new frame.
|
||||
let next_frame = last_frame + self.frame_interval;
|
||||
if Instant::now() >= next_frame {
|
||||
self.handler.on_frame(&mut crate::Window::new(Window { inner: &self.window }));
|
||||
last_frame = Instant::max(next_frame, Instant::now() - self.frame_interval);
|
||||
}
|
||||
|
||||
let mut fds = [PollFd::new(xcb_fd, PollFlags::POLLIN)];
|
||||
|
||||
// Check for any events in the internal buffers
|
||||
// before going to sleep:
|
||||
self.drain_xcb_events()?;
|
||||
|
||||
// FIXME: handle errors
|
||||
poll(&mut fds, next_frame.duration_since(Instant::now()).subsec_millis() as i32)
|
||||
.unwrap();
|
||||
|
||||
if let Some(revents) = fds[0].revents() {
|
||||
if revents.contains(PollFlags::POLLERR) {
|
||||
panic!("xcb connection poll error");
|
||||
}
|
||||
|
||||
if revents.contains(PollFlags::POLLIN) {
|
||||
self.drain_xcb_events()?;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the parents's handle was dropped (such as when the host
|
||||
// requested the window to close)
|
||||
if let Some(parent_handle) = &self.parent_handle {
|
||||
if parent_handle.parent_did_drop() {
|
||||
self.handle_must_close();
|
||||
self.window.close_requested.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the user has requested the window to close
|
||||
if self.window.close_requested.get() {
|
||||
self.handle_must_close();
|
||||
self.window.close_requested.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_xcb_event(&mut self, event: XEvent) {
|
||||
// For all the keyboard and mouse events, you can fetch
|
||||
// `x`, `y`, `detail`, and `state`.
|
||||
// - `x` and `y` are the position inside the window where the cursor currently is
|
||||
// when the event happened.
|
||||
// - `detail` will tell you which keycode was pressed/released (for keyboard events)
|
||||
// or which mouse button was pressed/released (for mouse events).
|
||||
// For mouse events, here's what the value means (at least on my current mouse):
|
||||
// 1 = left mouse button
|
||||
// 2 = middle mouse button (scroll wheel)
|
||||
// 3 = right mouse button
|
||||
// 4 = scroll wheel up
|
||||
// 5 = scroll wheel down
|
||||
// 8 = lower side button ("back" button)
|
||||
// 9 = upper side button ("forward" button)
|
||||
// Note that you *will* get a "button released" event for even the scroll wheel
|
||||
// events, which you can probably ignore.
|
||||
// - `state` will tell you the state of the main three mouse buttons and some of
|
||||
// the keyboard modifier keys at the time of the event.
|
||||
// http://rtbo.github.io/rust-xcb/src/xcb/ffi/xproto.rs.html#445
|
||||
|
||||
match event {
|
||||
////
|
||||
// window
|
||||
////
|
||||
XEvent::ClientMessage(event) => {
|
||||
if event.format == 32
|
||||
&& event.data.as_data32()[0]
|
||||
== self.window.xcb_connection.atoms.WM_DELETE_WINDOW
|
||||
{
|
||||
self.handle_close_requested();
|
||||
}
|
||||
}
|
||||
|
||||
XEvent::ConfigureNotify(event) => {
|
||||
let new_physical_size = PhySize::new(event.width as u32, event.height as u32);
|
||||
|
||||
if self.new_physical_size.is_some()
|
||||
|| new_physical_size != self.window.window_info.physical_size()
|
||||
{
|
||||
self.new_physical_size = Some(new_physical_size);
|
||||
}
|
||||
}
|
||||
|
||||
////
|
||||
// mouse
|
||||
////
|
||||
XEvent::MotionNotify(event) => {
|
||||
let physical_pos = PhyPoint::new(event.event_x as i32, event.event_y as i32);
|
||||
let logical_pos = physical_pos.to_logical(&self.window.window_info);
|
||||
let screen_physical = PhyPoint::new(event.root_x as i32, event.root_y as i32);
|
||||
let screen_position = screen_physical.to_logical(&self.window.window_info);
|
||||
|
||||
self.handler.on_event(
|
||||
&mut crate::Window::new(Window { inner: &self.window }),
|
||||
Event::Mouse(MouseEvent::CursorMoved {
|
||||
position: logical_pos,
|
||||
screen_position,
|
||||
modifiers: key_mods(event.state),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
XEvent::EnterNotify(event) => {
|
||||
self.handler.on_event(
|
||||
&mut crate::Window::new(Window { inner: &self.window }),
|
||||
Event::Mouse(MouseEvent::CursorEntered),
|
||||
);
|
||||
// since no `MOTION_NOTIFY` event is generated when `ENTER_NOTIFY` is generated,
|
||||
// we generate a CursorMoved as well, so the mouse position from here isn't lost
|
||||
let physical_pos = PhyPoint::new(event.event_x as i32, event.event_y as i32);
|
||||
let logical_pos = physical_pos.to_logical(&self.window.window_info);
|
||||
let screen_physical = PhyPoint::new(event.root_x as i32, event.root_y as i32);
|
||||
let screen_position = screen_physical.to_logical(&self.window.window_info);
|
||||
self.handler.on_event(
|
||||
&mut crate::Window::new(Window { inner: &self.window }),
|
||||
Event::Mouse(MouseEvent::CursorMoved {
|
||||
position: logical_pos,
|
||||
screen_position,
|
||||
modifiers: key_mods(event.state),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
XEvent::LeaveNotify(_) => {
|
||||
self.handler.on_event(
|
||||
&mut crate::Window::new(Window { inner: &self.window }),
|
||||
Event::Mouse(MouseEvent::CursorLeft),
|
||||
);
|
||||
}
|
||||
|
||||
XEvent::ButtonPress(event) => match event.detail {
|
||||
4..=7 => {
|
||||
self.handler.on_event(
|
||||
&mut crate::Window::new(Window { inner: &self.window }),
|
||||
Event::Mouse(MouseEvent::WheelScrolled {
|
||||
delta: match event.detail {
|
||||
4 => ScrollDelta::Lines { x: 0.0, y: 1.0 },
|
||||
5 => ScrollDelta::Lines { x: 0.0, y: -1.0 },
|
||||
6 => ScrollDelta::Lines { x: -1.0, y: 0.0 },
|
||||
7 => ScrollDelta::Lines { x: 1.0, y: 0.0 },
|
||||
_ => unreachable!(),
|
||||
},
|
||||
modifiers: key_mods(event.state),
|
||||
}),
|
||||
);
|
||||
}
|
||||
detail => {
|
||||
let button_id = mouse_id(detail);
|
||||
self.handler.on_event(
|
||||
&mut crate::Window::new(Window { inner: &self.window }),
|
||||
Event::Mouse(MouseEvent::ButtonPressed {
|
||||
button: button_id,
|
||||
modifiers: key_mods(event.state),
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
XEvent::ButtonRelease(event) => {
|
||||
if !(4..=7).contains(&event.detail) {
|
||||
let button_id = mouse_id(event.detail);
|
||||
self.handler.on_event(
|
||||
&mut crate::Window::new(Window { inner: &self.window }),
|
||||
Event::Mouse(MouseEvent::ButtonReleased {
|
||||
button: button_id,
|
||||
modifiers: key_mods(event.state),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
////
|
||||
// keys
|
||||
////
|
||||
XEvent::KeyPress(event) => {
|
||||
self.handler.on_event(
|
||||
&mut crate::Window::new(Window { inner: &self.window }),
|
||||
Event::Keyboard(convert_key_press_event(&event)),
|
||||
);
|
||||
}
|
||||
|
||||
XEvent::KeyRelease(event) => {
|
||||
self.handler.on_event(
|
||||
&mut crate::Window::new(Window { inner: &self.window }),
|
||||
Event::Keyboard(convert_key_release_event(&event)),
|
||||
);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_close_requested(&mut self) {
|
||||
// FIXME: handler should decide whether window stays open or not
|
||||
self.handle_must_close();
|
||||
}
|
||||
|
||||
fn handle_must_close(&mut self) {
|
||||
self.handler.on_event(
|
||||
&mut crate::Window::new(Window { inner: &self.window }),
|
||||
Event::Window(WindowEvent::WillClose),
|
||||
);
|
||||
|
||||
self.event_loop_running = false;
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_id(id: u8) -> MouseButton {
|
||||
match id {
|
||||
1 => MouseButton::Left,
|
||||
2 => MouseButton::Middle,
|
||||
3 => MouseButton::Right,
|
||||
8 => MouseButton::Back,
|
||||
9 => MouseButton::Forward,
|
||||
id => MouseButton::Other(id),
|
||||
}
|
||||
}
|
||||
406
plugins/baseview/src/x11/keyboard.rs
Normal file
406
plugins/baseview/src/x11/keyboard.rs
Normal file
@@ -0,0 +1,406 @@
|
||||
// Copyright 2020 The Druid Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Baseview modifications to druid code:
|
||||
// - collect functions from various files
|
||||
// - update imports, paths etc
|
||||
|
||||
//! X11 keyboard handling
|
||||
|
||||
use x11rb::protocol::xproto::{KeyButMask, KeyPressEvent, KeyReleaseEvent};
|
||||
|
||||
use keyboard_types::*;
|
||||
|
||||
use crate::keyboard::code_to_location;
|
||||
|
||||
/// Convert a hardware scan code to a key.
|
||||
///
|
||||
/// Note: this is a hardcoded layout. We need to detect the user's
|
||||
/// layout from the system and apply it.
|
||||
fn code_to_key(code: Code, m: Modifiers) -> Key {
|
||||
fn a(s: &str) -> Key {
|
||||
Key::Character(s.into())
|
||||
}
|
||||
fn s(mods: Modifiers, base: &str, shifted: &str) -> Key {
|
||||
if mods.contains(Modifiers::SHIFT) {
|
||||
Key::Character(shifted.into())
|
||||
} else {
|
||||
Key::Character(base.into())
|
||||
}
|
||||
}
|
||||
fn n(mods: Modifiers, base: Key, num: &str) -> Key {
|
||||
if mods.contains(Modifiers::NUM_LOCK) != mods.contains(Modifiers::SHIFT) {
|
||||
Key::Character(num.into())
|
||||
} else {
|
||||
base
|
||||
}
|
||||
}
|
||||
match code {
|
||||
Code::KeyA => s(m, "a", "A"),
|
||||
Code::KeyB => s(m, "b", "B"),
|
||||
Code::KeyC => s(m, "c", "C"),
|
||||
Code::KeyD => s(m, "d", "D"),
|
||||
Code::KeyE => s(m, "e", "E"),
|
||||
Code::KeyF => s(m, "f", "F"),
|
||||
Code::KeyG => s(m, "g", "G"),
|
||||
Code::KeyH => s(m, "h", "H"),
|
||||
Code::KeyI => s(m, "i", "I"),
|
||||
Code::KeyJ => s(m, "j", "J"),
|
||||
Code::KeyK => s(m, "k", "K"),
|
||||
Code::KeyL => s(m, "l", "L"),
|
||||
Code::KeyM => s(m, "m", "M"),
|
||||
Code::KeyN => s(m, "n", "N"),
|
||||
Code::KeyO => s(m, "o", "O"),
|
||||
Code::KeyP => s(m, "p", "P"),
|
||||
Code::KeyQ => s(m, "q", "Q"),
|
||||
Code::KeyR => s(m, "r", "R"),
|
||||
Code::KeyS => s(m, "s", "S"),
|
||||
Code::KeyT => s(m, "t", "T"),
|
||||
Code::KeyU => s(m, "u", "U"),
|
||||
Code::KeyV => s(m, "v", "V"),
|
||||
Code::KeyW => s(m, "w", "W"),
|
||||
Code::KeyX => s(m, "x", "X"),
|
||||
Code::KeyY => s(m, "y", "Y"),
|
||||
Code::KeyZ => s(m, "z", "Z"),
|
||||
|
||||
Code::Digit0 => s(m, "0", ")"),
|
||||
Code::Digit1 => s(m, "1", "!"),
|
||||
Code::Digit2 => s(m, "2", "@"),
|
||||
Code::Digit3 => s(m, "3", "#"),
|
||||
Code::Digit4 => s(m, "4", "$"),
|
||||
Code::Digit5 => s(m, "5", "%"),
|
||||
Code::Digit6 => s(m, "6", "^"),
|
||||
Code::Digit7 => s(m, "7", "&"),
|
||||
Code::Digit8 => s(m, "8", "*"),
|
||||
Code::Digit9 => s(m, "9", "("),
|
||||
|
||||
Code::Backquote => s(m, "`", "~"),
|
||||
Code::Minus => s(m, "-", "_"),
|
||||
Code::Equal => s(m, "=", "+"),
|
||||
Code::BracketLeft => s(m, "[", "{"),
|
||||
Code::BracketRight => s(m, "]", "}"),
|
||||
Code::Backslash => s(m, "\\", "|"),
|
||||
Code::Semicolon => s(m, ";", ":"),
|
||||
Code::Quote => s(m, "'", "\""),
|
||||
Code::Comma => s(m, ",", "<"),
|
||||
Code::Period => s(m, ".", ">"),
|
||||
Code::Slash => s(m, "/", "?"),
|
||||
|
||||
Code::Space => a(" "),
|
||||
|
||||
Code::Escape => Key::Escape,
|
||||
Code::Backspace => Key::Backspace,
|
||||
Code::Tab => Key::Tab,
|
||||
Code::Enter => Key::Enter,
|
||||
Code::ControlLeft => Key::Control,
|
||||
Code::ShiftLeft => Key::Shift,
|
||||
Code::ShiftRight => Key::Shift,
|
||||
Code::NumpadMultiply => a("*"),
|
||||
Code::AltLeft => Key::Alt,
|
||||
Code::CapsLock => Key::CapsLock,
|
||||
Code::F1 => Key::F1,
|
||||
Code::F2 => Key::F2,
|
||||
Code::F3 => Key::F3,
|
||||
Code::F4 => Key::F4,
|
||||
Code::F5 => Key::F5,
|
||||
Code::F6 => Key::F6,
|
||||
Code::F7 => Key::F7,
|
||||
Code::F8 => Key::F8,
|
||||
Code::F9 => Key::F9,
|
||||
Code::F10 => Key::F10,
|
||||
Code::NumLock => Key::NumLock,
|
||||
Code::ScrollLock => Key::ScrollLock,
|
||||
Code::Numpad0 => n(m, Key::Insert, "0"),
|
||||
Code::Numpad1 => n(m, Key::End, "1"),
|
||||
Code::Numpad2 => n(m, Key::ArrowDown, "2"),
|
||||
Code::Numpad3 => n(m, Key::PageDown, "3"),
|
||||
Code::Numpad4 => n(m, Key::ArrowLeft, "4"),
|
||||
Code::Numpad5 => n(m, Key::Clear, "5"),
|
||||
Code::Numpad6 => n(m, Key::ArrowRight, "6"),
|
||||
Code::Numpad7 => n(m, Key::Home, "7"),
|
||||
Code::Numpad8 => n(m, Key::ArrowUp, "8"),
|
||||
Code::Numpad9 => n(m, Key::PageUp, "9"),
|
||||
Code::NumpadSubtract => a("-"),
|
||||
Code::NumpadAdd => a("+"),
|
||||
Code::NumpadDecimal => n(m, Key::Delete, "."),
|
||||
Code::IntlBackslash => s(m, "\\", "|"),
|
||||
Code::F11 => Key::F11,
|
||||
Code::F12 => Key::F12,
|
||||
// This mapping is based on the picture in the w3c spec.
|
||||
Code::IntlRo => a("\\"),
|
||||
Code::Convert => Key::Convert,
|
||||
Code::KanaMode => Key::KanaMode,
|
||||
Code::NonConvert => Key::NonConvert,
|
||||
Code::NumpadEnter => Key::Enter,
|
||||
Code::ControlRight => Key::Control,
|
||||
Code::NumpadDivide => a("/"),
|
||||
Code::PrintScreen => Key::PrintScreen,
|
||||
Code::AltRight => Key::Alt,
|
||||
Code::Home => Key::Home,
|
||||
Code::ArrowUp => Key::ArrowUp,
|
||||
Code::PageUp => Key::PageUp,
|
||||
Code::ArrowLeft => Key::ArrowLeft,
|
||||
Code::ArrowRight => Key::ArrowRight,
|
||||
Code::End => Key::End,
|
||||
Code::ArrowDown => Key::ArrowDown,
|
||||
Code::PageDown => Key::PageDown,
|
||||
Code::Insert => Key::Insert,
|
||||
Code::Delete => Key::Delete,
|
||||
Code::AudioVolumeMute => Key::AudioVolumeMute,
|
||||
Code::AudioVolumeDown => Key::AudioVolumeDown,
|
||||
Code::AudioVolumeUp => Key::AudioVolumeUp,
|
||||
Code::NumpadEqual => a("="),
|
||||
Code::Pause => Key::Pause,
|
||||
Code::NumpadComma => a(","),
|
||||
Code::Lang1 => Key::HangulMode,
|
||||
Code::Lang2 => Key::HanjaMode,
|
||||
Code::IntlYen => a("¥"),
|
||||
Code::MetaLeft => Key::Meta,
|
||||
Code::MetaRight => Key::Meta,
|
||||
Code::ContextMenu => Key::ContextMenu,
|
||||
Code::BrowserStop => Key::BrowserStop,
|
||||
Code::Again => Key::Again,
|
||||
Code::Props => Key::Props,
|
||||
Code::Undo => Key::Undo,
|
||||
Code::Select => Key::Select,
|
||||
Code::Copy => Key::Copy,
|
||||
Code::Open => Key::Open,
|
||||
Code::Paste => Key::Paste,
|
||||
Code::Find => Key::Find,
|
||||
Code::Cut => Key::Cut,
|
||||
Code::Help => Key::Help,
|
||||
Code::LaunchApp2 => Key::LaunchApplication2,
|
||||
Code::WakeUp => Key::WakeUp,
|
||||
Code::LaunchApp1 => Key::LaunchApplication1,
|
||||
Code::LaunchMail => Key::LaunchMail,
|
||||
Code::BrowserFavorites => Key::BrowserFavorites,
|
||||
Code::BrowserBack => Key::BrowserBack,
|
||||
Code::BrowserForward => Key::BrowserForward,
|
||||
Code::Eject => Key::Eject,
|
||||
Code::MediaTrackNext => Key::MediaTrackNext,
|
||||
Code::MediaPlayPause => Key::MediaPlayPause,
|
||||
Code::MediaTrackPrevious => Key::MediaTrackPrevious,
|
||||
Code::MediaStop => Key::MediaStop,
|
||||
Code::MediaSelect => Key::LaunchMediaPlayer,
|
||||
Code::BrowserHome => Key::BrowserHome,
|
||||
Code::BrowserRefresh => Key::BrowserRefresh,
|
||||
Code::BrowserSearch => Key::BrowserSearch,
|
||||
|
||||
_ => Key::Unidentified,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
/// Map hardware keycode to code.
|
||||
///
|
||||
/// In theory, the hardware keycode is device dependent, but in
|
||||
/// practice it's probably pretty reliable.
|
||||
///
|
||||
/// The logic is based on NativeKeyToDOMCodeName.h in Mozilla.
|
||||
fn hardware_keycode_to_code(hw_keycode: u16) -> Code {
|
||||
match hw_keycode {
|
||||
0x0009 => Code::Escape,
|
||||
0x000A => Code::Digit1,
|
||||
0x000B => Code::Digit2,
|
||||
0x000C => Code::Digit3,
|
||||
0x000D => Code::Digit4,
|
||||
0x000E => Code::Digit5,
|
||||
0x000F => Code::Digit6,
|
||||
0x0010 => Code::Digit7,
|
||||
0x0011 => Code::Digit8,
|
||||
0x0012 => Code::Digit9,
|
||||
0x0013 => Code::Digit0,
|
||||
0x0014 => Code::Minus,
|
||||
0x0015 => Code::Equal,
|
||||
0x0016 => Code::Backspace,
|
||||
0x0017 => Code::Tab,
|
||||
0x0018 => Code::KeyQ,
|
||||
0x0019 => Code::KeyW,
|
||||
0x001A => Code::KeyE,
|
||||
0x001B => Code::KeyR,
|
||||
0x001C => Code::KeyT,
|
||||
0x001D => Code::KeyY,
|
||||
0x001E => Code::KeyU,
|
||||
0x001F => Code::KeyI,
|
||||
0x0020 => Code::KeyO,
|
||||
0x0021 => Code::KeyP,
|
||||
0x0022 => Code::BracketLeft,
|
||||
0x0023 => Code::BracketRight,
|
||||
0x0024 => Code::Enter,
|
||||
0x0025 => Code::ControlLeft,
|
||||
0x0026 => Code::KeyA,
|
||||
0x0027 => Code::KeyS,
|
||||
0x0028 => Code::KeyD,
|
||||
0x0029 => Code::KeyF,
|
||||
0x002A => Code::KeyG,
|
||||
0x002B => Code::KeyH,
|
||||
0x002C => Code::KeyJ,
|
||||
0x002D => Code::KeyK,
|
||||
0x002E => Code::KeyL,
|
||||
0x002F => Code::Semicolon,
|
||||
0x0030 => Code::Quote,
|
||||
0x0031 => Code::Backquote,
|
||||
0x0032 => Code::ShiftLeft,
|
||||
0x0033 => Code::Backslash,
|
||||
0x0034 => Code::KeyZ,
|
||||
0x0035 => Code::KeyX,
|
||||
0x0036 => Code::KeyC,
|
||||
0x0037 => Code::KeyV,
|
||||
0x0038 => Code::KeyB,
|
||||
0x0039 => Code::KeyN,
|
||||
0x003A => Code::KeyM,
|
||||
0x003B => Code::Comma,
|
||||
0x003C => Code::Period,
|
||||
0x003D => Code::Slash,
|
||||
0x003E => Code::ShiftRight,
|
||||
0x003F => Code::NumpadMultiply,
|
||||
0x0040 => Code::AltLeft,
|
||||
0x0041 => Code::Space,
|
||||
0x0042 => Code::CapsLock,
|
||||
0x0043 => Code::F1,
|
||||
0x0044 => Code::F2,
|
||||
0x0045 => Code::F3,
|
||||
0x0046 => Code::F4,
|
||||
0x0047 => Code::F5,
|
||||
0x0048 => Code::F6,
|
||||
0x0049 => Code::F7,
|
||||
0x004A => Code::F8,
|
||||
0x004B => Code::F9,
|
||||
0x004C => Code::F10,
|
||||
0x004D => Code::NumLock,
|
||||
0x004E => Code::ScrollLock,
|
||||
0x004F => Code::Numpad7,
|
||||
0x0050 => Code::Numpad8,
|
||||
0x0051 => Code::Numpad9,
|
||||
0x0052 => Code::NumpadSubtract,
|
||||
0x0053 => Code::Numpad4,
|
||||
0x0054 => Code::Numpad5,
|
||||
0x0055 => Code::Numpad6,
|
||||
0x0056 => Code::NumpadAdd,
|
||||
0x0057 => Code::Numpad1,
|
||||
0x0058 => Code::Numpad2,
|
||||
0x0059 => Code::Numpad3,
|
||||
0x005A => Code::Numpad0,
|
||||
0x005B => Code::NumpadDecimal,
|
||||
0x005E => Code::IntlBackslash,
|
||||
0x005F => Code::F11,
|
||||
0x0060 => Code::F12,
|
||||
0x0061 => Code::IntlRo,
|
||||
0x0064 => Code::Convert,
|
||||
0x0065 => Code::KanaMode,
|
||||
0x0066 => Code::NonConvert,
|
||||
0x0068 => Code::NumpadEnter,
|
||||
0x0069 => Code::ControlRight,
|
||||
0x006A => Code::NumpadDivide,
|
||||
0x006B => Code::PrintScreen,
|
||||
0x006C => Code::AltRight,
|
||||
0x006E => Code::Home,
|
||||
0x006F => Code::ArrowUp,
|
||||
0x0070 => Code::PageUp,
|
||||
0x0071 => Code::ArrowLeft,
|
||||
0x0072 => Code::ArrowRight,
|
||||
0x0073 => Code::End,
|
||||
0x0074 => Code::ArrowDown,
|
||||
0x0075 => Code::PageDown,
|
||||
0x0076 => Code::Insert,
|
||||
0x0077 => Code::Delete,
|
||||
0x0079 => Code::AudioVolumeMute,
|
||||
0x007A => Code::AudioVolumeDown,
|
||||
0x007B => Code::AudioVolumeUp,
|
||||
0x007D => Code::NumpadEqual,
|
||||
0x007F => Code::Pause,
|
||||
0x0081 => Code::NumpadComma,
|
||||
0x0082 => Code::Lang1,
|
||||
0x0083 => Code::Lang2,
|
||||
0x0084 => Code::IntlYen,
|
||||
0x0085 => Code::MetaLeft,
|
||||
0x0086 => Code::MetaRight,
|
||||
0x0087 => Code::ContextMenu,
|
||||
0x0088 => Code::BrowserStop,
|
||||
0x0089 => Code::Again,
|
||||
0x008A => Code::Props,
|
||||
0x008B => Code::Undo,
|
||||
0x008C => Code::Select,
|
||||
0x008D => Code::Copy,
|
||||
0x008E => Code::Open,
|
||||
0x008F => Code::Paste,
|
||||
0x0090 => Code::Find,
|
||||
0x0091 => Code::Cut,
|
||||
0x0092 => Code::Help,
|
||||
0x0094 => Code::LaunchApp2,
|
||||
0x0097 => Code::WakeUp,
|
||||
0x0098 => Code::LaunchApp1,
|
||||
// key to right of volume controls on T430s produces 0x9C
|
||||
// but no documentation of what it should map to :/
|
||||
0x00A3 => Code::LaunchMail,
|
||||
0x00A4 => Code::BrowserFavorites,
|
||||
0x00A6 => Code::BrowserBack,
|
||||
0x00A7 => Code::BrowserForward,
|
||||
0x00A9 => Code::Eject,
|
||||
0x00AB => Code::MediaTrackNext,
|
||||
0x00AC => Code::MediaPlayPause,
|
||||
0x00AD => Code::MediaTrackPrevious,
|
||||
0x00AE => Code::MediaStop,
|
||||
0x00B3 => Code::MediaSelect,
|
||||
0x00B4 => Code::BrowserHome,
|
||||
0x00B5 => Code::BrowserRefresh,
|
||||
0x00E1 => Code::BrowserSearch,
|
||||
_ => Code::Unidentified,
|
||||
}
|
||||
}
|
||||
|
||||
// Extracts the keyboard modifiers from, e.g., the `state` field of
|
||||
// `x11rb::protocol::xproto::ButtonPressEvent`
|
||||
pub(super) fn key_mods(mods: KeyButMask) -> Modifiers {
|
||||
let mut ret = Modifiers::default();
|
||||
let key_masks = [
|
||||
(KeyButMask::SHIFT, Modifiers::SHIFT),
|
||||
(KeyButMask::CONTROL, Modifiers::CONTROL),
|
||||
// X11's mod keys are configurable, but this seems
|
||||
// like a reasonable default for US keyboards, at least,
|
||||
// where the "windows" key seems to be MOD_MASK_4.
|
||||
(KeyButMask::MOD1, Modifiers::ALT),
|
||||
(KeyButMask::MOD2, Modifiers::NUM_LOCK),
|
||||
(KeyButMask::MOD4, Modifiers::META),
|
||||
(KeyButMask::LOCK, Modifiers::CAPS_LOCK),
|
||||
];
|
||||
for (mask, modifiers) in &key_masks {
|
||||
if mods.contains(*mask) {
|
||||
ret |= *modifiers;
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
pub(super) fn convert_key_press_event(key_press: &KeyPressEvent) -> KeyboardEvent {
|
||||
let hw_keycode = key_press.detail;
|
||||
let code = hardware_keycode_to_code(hw_keycode.into());
|
||||
let modifiers = key_mods(key_press.state);
|
||||
let key = code_to_key(code, modifiers);
|
||||
let location = code_to_location(code);
|
||||
let state = KeyState::Down;
|
||||
|
||||
KeyboardEvent { code, key, modifiers, location, state, repeat: false, is_composing: false }
|
||||
}
|
||||
|
||||
pub(super) fn convert_key_release_event(key_release: &KeyReleaseEvent) -> KeyboardEvent {
|
||||
let hw_keycode = key_release.detail;
|
||||
let code = hardware_keycode_to_code(hw_keycode.into());
|
||||
let modifiers = key_mods(key_release.state);
|
||||
let key = code_to_key(code, modifiers);
|
||||
let location = code_to_location(code);
|
||||
let state = KeyState::Up;
|
||||
|
||||
KeyboardEvent { code, key, modifiers, location, state, repeat: false, is_composing: false }
|
||||
}
|
||||
10
plugins/baseview/src/x11/mod.rs
Normal file
10
plugins/baseview/src/x11/mod.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
mod xcb_connection;
|
||||
use xcb_connection::XcbConnection;
|
||||
|
||||
mod window;
|
||||
pub use window::*;
|
||||
|
||||
mod cursor;
|
||||
mod event_loop;
|
||||
mod keyboard;
|
||||
mod visual_info;
|
||||
94
plugins/baseview/src/x11/visual_info.rs
Normal file
94
plugins/baseview/src/x11/visual_info.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
use crate::x11::xcb_connection::XcbConnection;
|
||||
use std::error::Error;
|
||||
use x11rb::connection::Connection;
|
||||
use x11rb::protocol::xproto::{
|
||||
Colormap, ColormapAlloc, ConnectionExt, Screen, VisualClass, Visualid,
|
||||
};
|
||||
use x11rb::COPY_FROM_PARENT;
|
||||
|
||||
pub(super) struct WindowVisualConfig {
|
||||
#[cfg(feature = "opengl")]
|
||||
pub fb_config: Option<crate::gl::x11::FbConfig>,
|
||||
|
||||
pub visual_depth: u8,
|
||||
pub visual_id: Visualid,
|
||||
pub color_map: Option<Colormap>,
|
||||
}
|
||||
|
||||
// TODO: make visual negotiation actually check all of a visual's parameters
|
||||
impl WindowVisualConfig {
|
||||
#[cfg(feature = "opengl")]
|
||||
pub fn find_best_visual_config_for_gl(
|
||||
connection: &XcbConnection, gl_config: Option<crate::gl::GlConfig>,
|
||||
) -> Result<Self, Box<dyn Error>> {
|
||||
let Some(gl_config) = gl_config else { return Self::find_best_visual_config(connection) };
|
||||
|
||||
// SAFETY: TODO
|
||||
let (fb_config, window_config) = unsafe {
|
||||
crate::gl::platform::GlContext::get_fb_config_and_visual(connection.dpy, gl_config)
|
||||
}
|
||||
.expect("Could not fetch framebuffer config");
|
||||
|
||||
Ok(Self {
|
||||
fb_config: Some(fb_config),
|
||||
visual_depth: window_config.depth,
|
||||
visual_id: window_config.visual,
|
||||
color_map: Some(create_color_map(connection, window_config.visual)?),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn find_best_visual_config(connection: &XcbConnection) -> Result<Self, Box<dyn Error>> {
|
||||
match find_visual_for_depth(connection.screen(), 32) {
|
||||
None => Ok(Self::copy_from_parent()),
|
||||
Some(visual_id) => Ok(Self {
|
||||
#[cfg(feature = "opengl")]
|
||||
fb_config: None,
|
||||
visual_id,
|
||||
visual_depth: 32,
|
||||
color_map: Some(create_color_map(connection, visual_id)?),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
const fn copy_from_parent() -> Self {
|
||||
Self {
|
||||
#[cfg(feature = "opengl")]
|
||||
fb_config: None,
|
||||
visual_depth: COPY_FROM_PARENT as u8,
|
||||
visual_id: COPY_FROM_PARENT,
|
||||
color_map: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For this 32-bit depth to work, you also need to define a color map and set a border
|
||||
// pixel: https://cgit.freedesktop.org/xorg/xserver/tree/dix/window.c#n818
|
||||
fn create_color_map(
|
||||
connection: &XcbConnection, visual_id: Visualid,
|
||||
) -> Result<Colormap, Box<dyn Error>> {
|
||||
let colormap = connection.conn.generate_id()?;
|
||||
connection.conn.create_colormap(
|
||||
ColormapAlloc::NONE,
|
||||
colormap,
|
||||
connection.screen().root,
|
||||
visual_id,
|
||||
)?;
|
||||
|
||||
Ok(colormap)
|
||||
}
|
||||
|
||||
fn find_visual_for_depth(screen: &Screen, depth: u8) -> Option<Visualid> {
|
||||
for candidate_depth in &screen.allowed_depths {
|
||||
if candidate_depth.depth != depth {
|
||||
continue;
|
||||
}
|
||||
|
||||
for candidate_visual in &candidate_depth.visuals {
|
||||
if candidate_visual.class == VisualClass::TRUE_COLOR {
|
||||
return Some(candidate_visual.visual_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
380
plugins/baseview/src/x11/window.rs
Normal file
380
plugins/baseview/src/x11/window.rs
Normal file
@@ -0,0 +1,380 @@
|
||||
use std::cell::Cell;
|
||||
use std::error::Error;
|
||||
use std::ffi::c_void;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc;
|
||||
use std::sync::Arc;
|
||||
use std::thread::{self, JoinHandle};
|
||||
|
||||
use raw_window_handle::{
|
||||
HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, XlibDisplayHandle,
|
||||
XlibWindowHandle,
|
||||
};
|
||||
|
||||
use x11rb::connection::Connection;
|
||||
use x11rb::protocol::xproto::{
|
||||
AtomEnum, ChangeWindowAttributesAux, ConfigureWindowAux, ConnectionExt as _, CreateGCAux,
|
||||
CreateWindowAux, EventMask, PropMode, Visualid, Window as XWindow, WindowClass,
|
||||
};
|
||||
use x11rb::wrapper::ConnectionExt as _;
|
||||
|
||||
use super::XcbConnection;
|
||||
use crate::{
|
||||
Event, MouseCursor, Size, WindowEvent, WindowHandler, WindowInfo, WindowOpenOptions,
|
||||
WindowScalePolicy,
|
||||
};
|
||||
|
||||
#[cfg(feature = "opengl")]
|
||||
use crate::gl::{platform, GlContext};
|
||||
use crate::x11::event_loop::EventLoop;
|
||||
use crate::x11::visual_info::WindowVisualConfig;
|
||||
|
||||
pub struct WindowHandle {
|
||||
raw_window_handle: Option<RawWindowHandle>,
|
||||
event_loop_handle: Option<JoinHandle<()>>,
|
||||
close_requested: Arc<AtomicBool>,
|
||||
is_open: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl WindowHandle {
|
||||
pub fn close(&mut self) {
|
||||
self.close_requested.store(true, Ordering::Relaxed);
|
||||
if let Some(event_loop) = self.event_loop_handle.take() {
|
||||
let _ = event_loop.join();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_open(&self) -> bool {
|
||||
self.is_open.load(Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl HasRawWindowHandle for WindowHandle {
|
||||
fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
if let Some(raw_window_handle) = self.raw_window_handle {
|
||||
if self.is_open.load(Ordering::Relaxed) {
|
||||
return raw_window_handle;
|
||||
}
|
||||
}
|
||||
|
||||
RawWindowHandle::Xlib(XlibWindowHandle::empty())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ParentHandle {
|
||||
close_requested: Arc<AtomicBool>,
|
||||
is_open: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl ParentHandle {
|
||||
pub fn new() -> (Self, WindowHandle) {
|
||||
let close_requested = Arc::new(AtomicBool::new(false));
|
||||
let is_open = Arc::new(AtomicBool::new(true));
|
||||
let handle = WindowHandle {
|
||||
raw_window_handle: None,
|
||||
event_loop_handle: None,
|
||||
close_requested: Arc::clone(&close_requested),
|
||||
is_open: Arc::clone(&is_open),
|
||||
};
|
||||
|
||||
(Self { close_requested, is_open }, handle)
|
||||
}
|
||||
|
||||
pub fn parent_did_drop(&self) -> bool {
|
||||
self.close_requested.load(Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ParentHandle {
|
||||
fn drop(&mut self) {
|
||||
self.is_open.store(false, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WindowInner {
|
||||
// GlContext should be dropped **before** XcbConnection is dropped
|
||||
#[cfg(feature = "opengl")]
|
||||
gl_context: Option<GlContext>,
|
||||
|
||||
pub(crate) xcb_connection: XcbConnection,
|
||||
window_id: XWindow,
|
||||
pub(crate) window_info: WindowInfo,
|
||||
visual_id: Visualid,
|
||||
mouse_cursor: Cell<MouseCursor>,
|
||||
|
||||
pub(crate) close_requested: Cell<bool>,
|
||||
}
|
||||
|
||||
pub struct Window<'a> {
|
||||
pub(crate) inner: &'a WindowInner,
|
||||
}
|
||||
|
||||
// Hack to allow sending a RawWindowHandle between threads. Do not make public
|
||||
struct SendableRwh(RawWindowHandle);
|
||||
|
||||
unsafe impl Send for SendableRwh {}
|
||||
|
||||
type WindowOpenResult = Result<SendableRwh, ()>;
|
||||
|
||||
impl<'a> Window<'a> {
|
||||
pub fn open_parented<P, H, B>(parent: &P, options: WindowOpenOptions, build: B) -> WindowHandle
|
||||
where
|
||||
P: HasRawWindowHandle,
|
||||
H: WindowHandler + 'static,
|
||||
B: FnOnce(&mut crate::Window) -> H,
|
||||
B: Send + 'static,
|
||||
{
|
||||
// Convert parent into something that X understands
|
||||
let parent_id = match parent.raw_window_handle() {
|
||||
RawWindowHandle::Xlib(h) => h.window as u32,
|
||||
RawWindowHandle::Xcb(h) => h.window,
|
||||
h => panic!("unsupported parent handle type {:?}", h),
|
||||
};
|
||||
|
||||
let (tx, rx) = mpsc::sync_channel::<WindowOpenResult>(1);
|
||||
let (parent_handle, mut window_handle) = ParentHandle::new();
|
||||
let join_handle = thread::spawn(move || {
|
||||
Self::window_thread(Some(parent_id), options, build, tx.clone(), Some(parent_handle))
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
let raw_window_handle = rx.recv().unwrap().unwrap();
|
||||
window_handle.raw_window_handle = Some(raw_window_handle.0);
|
||||
window_handle.event_loop_handle = Some(join_handle);
|
||||
window_handle
|
||||
}
|
||||
|
||||
pub fn open_blocking<H, B>(options: WindowOpenOptions, build: B)
|
||||
where
|
||||
H: WindowHandler + 'static,
|
||||
B: FnOnce(&mut crate::Window) -> H,
|
||||
B: Send + 'static,
|
||||
{
|
||||
let (tx, rx) = mpsc::sync_channel::<WindowOpenResult>(1);
|
||||
|
||||
let thread = thread::spawn(move || {
|
||||
Self::window_thread(None, options, build, tx, None).unwrap();
|
||||
});
|
||||
|
||||
let _ = rx.recv().unwrap().unwrap();
|
||||
|
||||
thread.join().unwrap_or_else(|err| {
|
||||
eprintln!("Window thread panicked: {:#?}", err);
|
||||
});
|
||||
}
|
||||
|
||||
fn window_thread<H, B>(
|
||||
parent: Option<u32>, options: WindowOpenOptions, build: B,
|
||||
tx: mpsc::SyncSender<WindowOpenResult>, parent_handle: Option<ParentHandle>,
|
||||
) -> Result<(), Box<dyn Error>>
|
||||
where
|
||||
H: WindowHandler + 'static,
|
||||
B: FnOnce(&mut crate::Window) -> H,
|
||||
B: Send + 'static,
|
||||
{
|
||||
// Connect to the X server
|
||||
// FIXME: baseview error type instead of unwrap()
|
||||
let xcb_connection = XcbConnection::new()?;
|
||||
|
||||
// Get screen information
|
||||
let screen = xcb_connection.screen();
|
||||
let parent_id = parent.unwrap_or(screen.root);
|
||||
|
||||
let gc_id = xcb_connection.conn.generate_id()?;
|
||||
xcb_connection.conn.create_gc(
|
||||
gc_id,
|
||||
parent_id,
|
||||
&CreateGCAux::new().foreground(screen.black_pixel).graphics_exposures(0),
|
||||
)?;
|
||||
|
||||
let scaling = match options.scale {
|
||||
WindowScalePolicy::SystemScaleFactor => xcb_connection.get_scaling().unwrap_or(1.0),
|
||||
WindowScalePolicy::ScaleFactor(scale) => scale,
|
||||
};
|
||||
|
||||
let window_info = WindowInfo::from_logical_size(options.size, scaling);
|
||||
|
||||
#[cfg(feature = "opengl")]
|
||||
let visual_info =
|
||||
WindowVisualConfig::find_best_visual_config_for_gl(&xcb_connection, options.gl_config)?;
|
||||
|
||||
#[cfg(not(feature = "opengl"))]
|
||||
let visual_info = WindowVisualConfig::find_best_visual_config(&xcb_connection)?;
|
||||
|
||||
let window_id = xcb_connection.conn.generate_id()?;
|
||||
xcb_connection.conn.create_window(
|
||||
visual_info.visual_depth,
|
||||
window_id,
|
||||
parent_id,
|
||||
0, // x coordinate of the new window
|
||||
0, // y coordinate of the new window
|
||||
window_info.physical_size().width as u16, // window width
|
||||
window_info.physical_size().height as u16, // window height
|
||||
0, // window border
|
||||
WindowClass::INPUT_OUTPUT,
|
||||
visual_info.visual_id,
|
||||
&CreateWindowAux::new()
|
||||
.event_mask(
|
||||
EventMask::EXPOSURE
|
||||
| EventMask::POINTER_MOTION
|
||||
| EventMask::BUTTON_PRESS
|
||||
| EventMask::BUTTON_RELEASE
|
||||
| EventMask::KEY_PRESS
|
||||
| EventMask::KEY_RELEASE
|
||||
| EventMask::STRUCTURE_NOTIFY
|
||||
| EventMask::ENTER_WINDOW
|
||||
| EventMask::LEAVE_WINDOW,
|
||||
)
|
||||
// As mentioned above, these two values are needed to be able to create a window
|
||||
// with a depth of 32-bits when the parent window has a different depth
|
||||
.colormap(visual_info.color_map)
|
||||
.border_pixel(0),
|
||||
)?;
|
||||
xcb_connection.conn.map_window(window_id)?;
|
||||
|
||||
// Change window title
|
||||
let title = options.title;
|
||||
xcb_connection.conn.change_property8(
|
||||
PropMode::REPLACE,
|
||||
window_id,
|
||||
AtomEnum::WM_NAME,
|
||||
AtomEnum::STRING,
|
||||
title.as_bytes(),
|
||||
)?;
|
||||
|
||||
xcb_connection.conn.change_property32(
|
||||
PropMode::REPLACE,
|
||||
window_id,
|
||||
xcb_connection.atoms.WM_PROTOCOLS,
|
||||
AtomEnum::ATOM,
|
||||
&[xcb_connection.atoms.WM_DELETE_WINDOW],
|
||||
)?;
|
||||
|
||||
xcb_connection.conn.flush()?;
|
||||
|
||||
// TODO: These APIs could use a couple tweaks now that everything is internal and there is
|
||||
// no error handling anymore at this point. Everything is more or less unchanged
|
||||
// compared to when raw-gl-context was a separate crate.
|
||||
#[cfg(feature = "opengl")]
|
||||
let gl_context = visual_info.fb_config.map(|fb_config| {
|
||||
use std::ffi::c_ulong;
|
||||
|
||||
let window = window_id as c_ulong;
|
||||
let display = xcb_connection.dpy;
|
||||
|
||||
// Because of the visual negotation we had to take some extra steps to create this context
|
||||
let context = unsafe { platform::GlContext::create(window, display, fb_config) }
|
||||
.expect("Could not create OpenGL context");
|
||||
GlContext::new(context)
|
||||
});
|
||||
|
||||
let mut inner = WindowInner {
|
||||
xcb_connection,
|
||||
window_id,
|
||||
window_info,
|
||||
visual_id: visual_info.visual_id,
|
||||
mouse_cursor: Cell::new(MouseCursor::default()),
|
||||
|
||||
close_requested: Cell::new(false),
|
||||
|
||||
#[cfg(feature = "opengl")]
|
||||
gl_context,
|
||||
};
|
||||
|
||||
let mut window = crate::Window::new(Window { inner: &mut inner });
|
||||
|
||||
let mut handler = build(&mut window);
|
||||
|
||||
// Send an initial window resized event so the user is alerted of
|
||||
// the correct dpi scaling.
|
||||
handler.on_event(&mut window, Event::Window(WindowEvent::Resized(window_info)));
|
||||
|
||||
let _ = tx.send(Ok(SendableRwh(window.raw_window_handle())));
|
||||
|
||||
EventLoop::new(inner, handler, parent_handle).run()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn physical_size(&self) -> crate::PhySize {
|
||||
self.inner.window_info.physical_size()
|
||||
}
|
||||
|
||||
pub fn set_mouse_cursor(&self, mouse_cursor: MouseCursor) {
|
||||
if self.inner.mouse_cursor.get() == mouse_cursor {
|
||||
return;
|
||||
}
|
||||
|
||||
let xid = self.inner.xcb_connection.get_cursor(mouse_cursor).unwrap();
|
||||
|
||||
if xid != 0 {
|
||||
let _ = self.inner.xcb_connection.conn.change_window_attributes(
|
||||
self.inner.window_id,
|
||||
&ChangeWindowAttributesAux::new().cursor(xid),
|
||||
);
|
||||
let _ = self.inner.xcb_connection.conn.flush();
|
||||
}
|
||||
|
||||
self.inner.mouse_cursor.set(mouse_cursor);
|
||||
}
|
||||
|
||||
pub fn close(&mut self) {
|
||||
self.inner.close_requested.set(true);
|
||||
}
|
||||
|
||||
pub fn has_focus(&mut self) -> bool {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn focus(&mut self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, size: Size) {
|
||||
let scaling = self.inner.window_info.scale();
|
||||
let new_window_info = WindowInfo::from_logical_size(size, scaling);
|
||||
|
||||
let _ = self.inner.xcb_connection.conn.configure_window(
|
||||
self.inner.window_id,
|
||||
&ConfigureWindowAux::new()
|
||||
.width(new_window_info.physical_size().width)
|
||||
.height(new_window_info.physical_size().height),
|
||||
);
|
||||
let _ = self.inner.xcb_connection.conn.flush();
|
||||
|
||||
// This will trigger a `ConfigureNotify` event which will in turn change `self.window_info`
|
||||
// and notify the window handler about it
|
||||
}
|
||||
|
||||
#[cfg(feature = "opengl")]
|
||||
pub fn gl_context(&self) -> Option<&crate::gl::GlContext> {
|
||||
self.inner.gl_context.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'a> HasRawWindowHandle for Window<'a> {
|
||||
fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
let mut handle = XlibWindowHandle::empty();
|
||||
|
||||
handle.window = self.inner.window_id.into();
|
||||
handle.visual_id = self.inner.visual_id.into();
|
||||
|
||||
RawWindowHandle::Xlib(handle)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'a> HasRawDisplayHandle for Window<'a> {
|
||||
fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
let display = self.inner.xcb_connection.dpy;
|
||||
let mut handle = XlibDisplayHandle::empty();
|
||||
|
||||
handle.display = display as *mut c_void;
|
||||
handle.screen = unsafe { x11::xlib::XDefaultScreen(display) };
|
||||
|
||||
RawDisplayHandle::Xlib(handle)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_to_clipboard(_data: &str) {
|
||||
todo!()
|
||||
}
|
||||
132
plugins/baseview/src/x11/xcb_connection.rs
Normal file
132
plugins/baseview/src/x11/xcb_connection.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::hash_map::{Entry, HashMap};
|
||||
use std::error::Error;
|
||||
|
||||
use x11::{xlib, xlib::Display, xlib_xcb};
|
||||
|
||||
use x11rb::connection::Connection;
|
||||
use x11rb::cursor::Handle as CursorHandle;
|
||||
use x11rb::protocol::xproto::{Cursor, Screen};
|
||||
use x11rb::resource_manager;
|
||||
use x11rb::xcb_ffi::XCBConnection;
|
||||
|
||||
use crate::MouseCursor;
|
||||
|
||||
use super::cursor;
|
||||
|
||||
x11rb::atom_manager! {
|
||||
pub Atoms: AtomsCookie {
|
||||
WM_PROTOCOLS,
|
||||
WM_DELETE_WINDOW,
|
||||
}
|
||||
}
|
||||
|
||||
/// A very light abstraction around the XCB connection.
|
||||
///
|
||||
/// Keeps track of the xcb connection itself and the xlib display ID that was used to connect.
|
||||
pub struct XcbConnection {
|
||||
pub(crate) dpy: *mut Display,
|
||||
pub(crate) conn: XCBConnection,
|
||||
pub(crate) screen: usize,
|
||||
pub(crate) atoms: Atoms,
|
||||
pub(crate) resources: resource_manager::Database,
|
||||
pub(crate) cursor_handle: CursorHandle,
|
||||
pub(super) cursor_cache: RefCell<HashMap<MouseCursor, u32>>,
|
||||
}
|
||||
|
||||
impl XcbConnection {
|
||||
pub fn new() -> Result<Self, Box<dyn Error>> {
|
||||
let dpy = unsafe { xlib::XOpenDisplay(std::ptr::null()) };
|
||||
assert!(!dpy.is_null());
|
||||
let xcb_connection = unsafe { xlib_xcb::XGetXCBConnection(dpy) };
|
||||
assert!(!xcb_connection.is_null());
|
||||
let screen = unsafe { xlib::XDefaultScreen(dpy) } as usize;
|
||||
let conn = unsafe { XCBConnection::from_raw_xcb_connection(xcb_connection, false)? };
|
||||
unsafe {
|
||||
xlib_xcb::XSetEventQueueOwner(dpy, xlib_xcb::XEventQueueOwner::XCBOwnsEventQueue)
|
||||
};
|
||||
|
||||
let atoms = Atoms::new(&conn)?.reply()?;
|
||||
let resources = resource_manager::new_from_default(&conn)?;
|
||||
let cursor_handle = CursorHandle::new(&conn, screen, &resources)?.reply()?;
|
||||
|
||||
Ok(Self {
|
||||
dpy,
|
||||
conn,
|
||||
screen,
|
||||
atoms,
|
||||
resources,
|
||||
cursor_handle,
|
||||
cursor_cache: RefCell::new(HashMap::new()),
|
||||
})
|
||||
}
|
||||
|
||||
// Try to get the scaling with this function first.
|
||||
// If this gives you `None`, fall back to `get_scaling_screen_dimensions`.
|
||||
// If neither work, I guess just assume 96.0 and don't do any scaling.
|
||||
fn get_scaling_xft(&self) -> Result<Option<f64>, Box<dyn Error>> {
|
||||
if let Some(dpi) = self.resources.get_value::<u32>("Xft.dpi", "")? {
|
||||
Ok(Some(dpi as f64 / 96.0))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
// Try to get the scaling with `get_scaling_xft` first.
|
||||
// Only use this function as a fallback.
|
||||
// If neither work, I guess just assume 96.0 and don't do any scaling.
|
||||
fn get_scaling_screen_dimensions(&self) -> f64 {
|
||||
// Figure out screen information
|
||||
let screen = self.screen();
|
||||
|
||||
// Get the DPI from the screen struct
|
||||
//
|
||||
// there are 2.54 centimeters to an inch; so there are 25.4 millimeters.
|
||||
// dpi = N pixels / (M millimeters / (25.4 millimeters / 1 inch))
|
||||
// = N pixels / (M inch / 25.4)
|
||||
// = N * 25.4 pixels / M inch
|
||||
let width_px = screen.width_in_pixels as f64;
|
||||
let width_mm = screen.width_in_millimeters as f64;
|
||||
let height_px = screen.height_in_pixels as f64;
|
||||
let height_mm = screen.height_in_millimeters as f64;
|
||||
let _xres = width_px * 25.4 / width_mm;
|
||||
let yres = height_px * 25.4 / height_mm;
|
||||
|
||||
// TODO: choose between `xres` and `yres`? (probably both are the same?)
|
||||
yres / 96.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_scaling(&self) -> Result<f64, Box<dyn Error>> {
|
||||
Ok(self.get_scaling_xft()?.unwrap_or(self.get_scaling_screen_dimensions()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_cursor(&self, cursor: MouseCursor) -> Result<Cursor, Box<dyn Error>> {
|
||||
// PANIC: this function is the only point where we access the cache, and we never call
|
||||
// external functions that may make a reentrant call to this function
|
||||
let mut cursor_cache = self.cursor_cache.borrow_mut();
|
||||
|
||||
match cursor_cache.entry(cursor) {
|
||||
Entry::Occupied(entry) => Ok(*entry.get()),
|
||||
Entry::Vacant(entry) => {
|
||||
let cursor =
|
||||
cursor::get_xcursor(&self.conn, self.screen, &self.cursor_handle, cursor)?;
|
||||
entry.insert(cursor);
|
||||
Ok(cursor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn screen(&self) -> &Screen {
|
||||
&self.conn.setup().roots[self.screen]
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for XcbConnection {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
xlib::XCloseDisplay(self.dpy);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user