1
0
Fork 0

Compare commits

...

24 commits

Author SHA1 Message Date
Alex Janka 4e6e9737f1 windows rwh 0.6 (untested) 2024-05-26 10:45:32 +10:00
Alex Janka e51b4dd832 raw-window-handle 0.6.0 (windows only!!!!) 2024-05-26 10:45:32 +10:00
Alex Janka 3d186ce51f don't disable mouse coalescing on macos 2024-05-26 10:45:32 +10:00
Adrien Prokopowicz 579130ecb4
Fix usage of spare_capacity_mut which isn't available in MSRV 1.59 (#190) 2024-05-23 01:54:39 +02:00
Adrien Prokopowicz 45465c5f46
X11: Split off the event loop into a separate module (#183)
This PR splits off the X11 event loop logic into a separate module. It also changes the X11 implementation of the `Window` type to take only a shared reference to the inner type (`&WindowInner` instead of `&mut WindowInner`), bringing it in line with the other backends.

This does not change any of the logic however, it only separates some of the window state from the event loop state, to make sure they don't step on each other's toes in the future (particularly around the WindowHandler).

This is part of the effort to split up #174 into smaller pieces.
2024-04-05 14:18:49 -05:00
Fredemus be3d72d524
Handle set_mouse_cursor method on windows (#186)
This adds the ability to change the cursor on Windows platforms. For some reason there are very few default cursors included in Windows so a lot of cursors available on other platforms aren't available yet, which is why many of the `MouseCursor` options just maps to `IDC_ARROW`. I'll look into adding custom cursors next.

`LoadCursorW` is supposedly superseded by [LoadImageW](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-loadcursorw) but I couldn't find a way to do it with that one that doesn't crash. 

Tested with Vizia's cursor icon example [here](https://github.com/Fredemus/vizia/tree/baseview-window-events) and everything seems to work as it should.
Also tested by hovering over some buttons on a plugin.
2024-04-02 16:14:13 -05:00
Micah Johnston 905854d026
Remove unnecessary dependences from CI pipeline (#185) 2024-03-30 06:29:18 +01:00
Adrien Prokopowicz 119fc25cd6
Add additional CI checks (#182) 2024-03-30 00:55:44 +01:00
Adrien Prokopowicz bcbdb8921f
Fix GL X11 error handling (#181)
This PR fixes a soundness issue inside the `XErrorHandler` utility function, which could use an xlib `Display` pointer that had no guarantee to be still valid.

This could happen because the underlying `XErrorEvent` was stored directly inside the returned error type, and the `Display` pointer it contained was only called to extract the error message during in `Debug` implementation, which could happen long after the associated `Display` had been destroyed.

This PR fixes this by extracting the error message upfront and storing it as a string as soon as the error happens.
This PR also fixes the `handle` method that was not properly marked `unsafe`.
2024-03-28 22:41:40 -05:00
Micah Johnston f5b0c6d460
Call XCloseDisplay instead of xcb_disconnect (#184) 2024-03-29 04:09:23 +01:00
Adrien Prokopowicz 70e7af6c61
Add a new example for OpenGL setup using femtovg (#176) 2024-03-27 06:39:21 +01:00
Adrien Prokopowicz f5ae585758
X11: split off Visual Info negotiation into a separate module (#177) 2024-03-27 06:34:41 +01:00
Micah Johnston e8b1236fda
Fix warnings in examples (#180) 2024-03-27 04:46:47 +01:00
Micah Johnston f7d83a561e
Fix temporary names left over from x11rb migration (#179) 2024-03-27 04:43:27 +01:00
Robbert van der Helm ea0cd5367d
Fix resizing on Windows not triggering an event (#166)
Resizing should trigger a baseview resize event, but because we already
set the new size here the `WM_SIZE` event was ignored.

Co-authored-by: Micah Johnston <micah@photophore.systems>
2024-03-26 11:05:24 -05:00
Adrien Prokopowicz 65d970495f
Render a background for the open_window example (#175)
This PR adds code to render a basic gray background to the opened window in the `open_window` example.

This also helps making the example a bit more useful, since most users will want to render to their window.

And also it looks nicer. 🙂 

This is done using the `softbuffer` crate, in the same manner of the `open_parented` introduced in #172.
2024-03-25 18:41:39 -05:00
Dionysis Athinaios bad50d886a
Implement input focus for Mac & Windows (#170)
* Implement input focus for Mac

* Add stubs for Windows and Linux

* Remove unnecessary call to window

* Implement input focus for Windows

* Add check for key window status

* Rename to `has_focus` & `focus`

* Use GetFocus

* Fix incorrectly named var

---------

Co-authored-by: Micah Johnston <micah@photophore.systems>
2024-03-25 17:05:16 -05:00
Max 085ae2a27e
add support for Window::Focused/Unfocused events on macOS (#171)
trigger `WindowEvent::Focused` and `WindowEvent::Unfocused` events when the plugin window gains/loses focus. implemented by adding observers to `NSNotificationCenter::defaultCenter()` that listen to `NSWindowDidBecomeKeyNotification` and `NSWindowDidResignKeyNotification` notifications on the `NSViews`' window.

tested and confirmed to work in Live, Bitwig, FL Studio, Reaper and AudioPluginHost.
2024-03-25 11:34:10 -05:00
Micah Johnston fdc5d282fc
Switch from xcb crate to x11rb (#173)
Replace the `xcb` and `xcb-util` crates with `x11rb`. We were using an old version of the `xcb` crate which had some soundness issue. `x11rb` doesn't have these issues and generally provides a safer and nicer to use API.

It's possible to use `x11rb` without linking to xcb at all, using the `RustConnection` API, but unfortunately we have to use the `XCBConnection` API (which uses xcb under the hood) due to our use of the xlib GLX API for creating OpenGL contexts. In the future, it might be possible to avoid linking to xlib and xcb by replacing GLX with EGL.

Getting the xlib-xcb integration to work also necessitated upgrading the version of the `x11` crate, since the version we were using was missing some necessary functionality that was previously being provided by the `xcb` crate.
2024-03-25 11:20:28 -05:00
Adrien Prokopowicz 998ced845c
Added functional open_parented example (#172)
This PR adds a simple example that allows to test and showcase the `Window::open_parented` method.

That example first creates a parent window using `Window::open_blocking`, and then creates a smaller child window using `Window::open_parented`.

Both window's handlers log all of their events to the console, in a similar fashion to the `open_window` example.

Both windows actually do rendering (unlike the `open_window` example for now): the parent fills its window with a grey backround, and the child fills its window with a red background.

This example also uses the `softbuffer` crate to perform the rendering, which allows testing it in a more portable manner and in the simplest use case possible, without having to involve OpenGL or any 3D rendering pipeline at all.
2024-03-24 17:16:16 -05:00
Robbert van der Helm 26b019d6a2
Merge pull request #150 from Fredemus/feature/windows-cursor-enter-leave
Add logic for CursorEntered/CursorLeft on Windows
2024-03-18 16:46:27 +01:00
Fredemus d8cedc8a77 Merge branch 'master' into feature/windows-cursor-enter-leave 2024-01-09 01:38:14 +01:00
Fredemus 475bd5f88a remove unneeded allocation 2023-10-28 17:35:26 +02:00
Fredemus 1274b1c08f Add logic for CursorEntered/CursorLeft on Windows 2023-10-02 02:58:07 +02:00
26 changed files with 1453 additions and 827 deletions

View file

@ -1,29 +1,37 @@
name: Rust
on: [push, pull_request]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
runs-on: ${{ matrix.os }}
env:
RUSTFLAGS: -D warnings
RUSTDOCFLAGS: -D warnings
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install XCB and GL dependencies
run: |
sudo apt update
sudo apt install libx11-xcb-dev libxcb-dri2-0-dev libgl1-mesa-dev libxcb-icccm4-dev libxcursor-dev
if: contains(matrix.os, 'ubuntu')
run: sudo apt-get install libx11-dev libxcb1-dev libx11-xcb-dev libgl1-mesa-dev
- name: Install rust stable
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
override: true
- name: Build with default features
run: cargo build --examples --workspace --verbose
- name: Build again with all features
run: cargo build --examples --workspace --all-features --verbose
components: rustfmt, clippy
- name: Build Default
run: cargo build --workspace --all-targets --verbose
- name: Build All Features
run: cargo build --workspace --all-targets --all-features --verbose
- name: Run tests
run: cargo test --examples --workspace --all-features --verbose
run: cargo test --workspace --all-targets --all-features --verbose
- name: Check docs
run: cargo doc --examples --all-features --no-deps
- name: Clippy
run: cargo clippy --workspace --all-targets --all-features -- -D warnings
- name: Check Formatting (rustfmt)
run: cargo fmt --all -- --check

View file

@ -15,21 +15,33 @@ edition = "2018"
license = "MIT OR Apache-2.0"
[features]
default = []
default = ["opengl"]
opengl = ["uuid", "x11/glx"]
[dependencies]
keyboard-types = { version = "0.6.1", default-features = false }
raw-window-handle = "0.5"
raw-window-handle = "0.6"
[target.'cfg(target_os="linux")'.dependencies]
xcb = { version = "0.9", features = ["thread", "xlib_xcb", "dri2"] }
x11 = { version = "2.18", features = ["xlib", "xcursor"] }
xcb-util = { version = "0.3", features = ["icccm"] }
x11rb = { version = "0.13.0", features = ["cursor", "resource_manager", "allow-unsafe-code"] }
x11 = { version = "2.21", features = ["xlib", "xlib_xcb"] }
nix = "0.22.0"
[target.'cfg(target_os="windows")'.dependencies]
winapi = { version = "0.3.8", features = ["libloaderapi", "winuser", "windef", "minwindef", "guiddef", "combaseapi", "wingdi", "errhandlingapi", "ole2", "oleidl", "shellapi", "winerror"] }
winapi = { version = "0.3.8", features = [
"libloaderapi",
"winuser",
"windef",
"minwindef",
"guiddef",
"combaseapi",
"wingdi",
"errhandlingapi",
"ole2",
"oleidl",
"shellapi",
"winerror",
] }
uuid = { version = "0.8", features = ["v4"], optional = true }
[target.'cfg(target_os="macos")'.dependencies]
@ -40,3 +52,20 @@ uuid = { version = "0.8", features = ["v4"] }
[dev-dependencies]
rtrb = "0.2"
softbuffer = "0.3.4"
[workspace]
members = ["examples/render_femtovg"]
[lints.clippy]
missing-safety-doc = "allow"
[[example]]
name = "open_window"
test = true
doctest = true
[[example]]
name = "open_parented"
test = true
doctest = true

View file

@ -23,10 +23,10 @@ Below is a proposed list of milestones (roughly in-order) and their status. Subj
### Linux
Install dependencies, e.g.,
Install dependencies, e.g.:
```sh
sudo apt-get install libx11-dev libxcursor-dev libxcb-dri2-0-dev libxcb-icccm4-dev libx11-xcb-dev
sudo apt-get install libx11-dev libxcb1-dev libx11-xcb-dev libgl1-mesa-dev
```
## License

2
clippy.toml Normal file
View file

@ -0,0 +1,2 @@
msrv = '1.59'
check-private-items = true

141
examples/open_parented.rs Normal file
View file

@ -0,0 +1,141 @@
use baseview::{
Event, EventStatus, PhySize, Window, WindowEvent, WindowHandle, WindowHandler,
WindowScalePolicy,
};
use std::num::NonZeroU32;
struct ParentWindowHandler {
_ctx: softbuffer::Context,
surface: softbuffer::Surface,
current_size: PhySize,
damaged: bool,
_child_window: Option<WindowHandle>,
}
impl ParentWindowHandler {
pub fn new(window: &mut Window) -> Self {
let ctx = unsafe { softbuffer::Context::new(window) }.unwrap();
let mut surface = unsafe { softbuffer::Surface::new(&ctx, window) }.unwrap();
surface.resize(NonZeroU32::new(512).unwrap(), NonZeroU32::new(512).unwrap()).unwrap();
let window_open_options = baseview::WindowOpenOptions {
title: "baseview child".into(),
size: baseview::Size::new(256.0, 256.0),
scale: WindowScalePolicy::SystemScaleFactor,
// TODO: Add an example that uses the OpenGL context
#[cfg(feature = "opengl")]
gl_config: None,
};
let child_window =
Window::open_parented(window, window_open_options, ChildWindowHandler::new);
// TODO: no way to query physical size initially?
Self {
_ctx: ctx,
surface,
current_size: PhySize::new(512, 512),
damaged: true,
_child_window: Some(child_window),
}
}
}
impl WindowHandler for ParentWindowHandler {
fn on_frame(&mut self, _window: &mut Window) {
let mut buf = self.surface.buffer_mut().unwrap();
if self.damaged {
buf.fill(0xFFAAAAAA);
self.damaged = false;
}
buf.present().unwrap();
}
fn on_event(&mut self, _window: &mut Window, event: Event) -> EventStatus {
match event {
Event::Window(WindowEvent::Resized(info)) => {
println!("Parent Resized: {:?}", info);
let new_size = info.physical_size();
self.current_size = new_size;
if let (Some(width), Some(height)) =
(NonZeroU32::new(new_size.width), NonZeroU32::new(new_size.height))
{
self.surface.resize(width, height).unwrap();
self.damaged = true;
}
}
Event::Mouse(e) => println!("Parent Mouse event: {:?}", e),
Event::Keyboard(e) => println!("Parent Keyboard event: {:?}", e),
Event::Window(e) => println!("Parent Window event: {:?}", e),
}
EventStatus::Captured
}
}
struct ChildWindowHandler {
_ctx: softbuffer::Context,
surface: softbuffer::Surface,
current_size: PhySize,
damaged: bool,
}
impl ChildWindowHandler {
pub fn new(window: &mut Window) -> Self {
let ctx = unsafe { softbuffer::Context::new(window) }.unwrap();
let mut surface = unsafe { softbuffer::Surface::new(&ctx, window) }.unwrap();
surface.resize(NonZeroU32::new(512).unwrap(), NonZeroU32::new(512).unwrap()).unwrap();
// TODO: no way to query physical size initially?
Self { _ctx: ctx, surface, current_size: PhySize::new(256, 256), damaged: true }
}
}
impl WindowHandler for ChildWindowHandler {
fn on_frame(&mut self, _window: &mut Window) {
let mut buf = self.surface.buffer_mut().unwrap();
if self.damaged {
buf.fill(0xFFAA0000);
self.damaged = false;
}
buf.present().unwrap();
}
fn on_event(&mut self, _window: &mut Window, event: Event) -> EventStatus {
match event {
Event::Window(WindowEvent::Resized(info)) => {
println!("Child Resized: {:?}", info);
let new_size = info.physical_size();
self.current_size = new_size;
if let (Some(width), Some(height)) =
(NonZeroU32::new(new_size.width), NonZeroU32::new(new_size.height))
{
self.surface.resize(width, height).unwrap();
self.damaged = true;
}
}
Event::Mouse(e) => println!("Child Mouse event: {:?}", e),
Event::Keyboard(e) => println!("Child Keyboard event: {:?}", e),
Event::Window(e) => println!("Child Window event: {:?}", e),
}
EventStatus::Captured
}
}
fn main() {
let window_open_options = baseview::WindowOpenOptions {
title: "baseview".into(),
size: baseview::Size::new(512.0, 512.0),
scale: WindowScalePolicy::SystemScaleFactor,
// TODO: Add an example that uses the OpenGL context
#[cfg(feature = "opengl")]
gl_config: None,
};
Window::open_blocking(window_open_options, ParentWindowHandler::new);
}

View file

@ -1,10 +1,13 @@
use std::num::NonZeroU32;
use std::time::Duration;
use rtrb::{Consumer, RingBuffer};
#[cfg(target_os = "macos")]
use baseview::copy_to_clipboard;
use baseview::{Event, EventStatus, MouseEvent, Window, WindowHandler, WindowScalePolicy};
use baseview::{copy_to_clipboard, MouseEvent};
use baseview::{
Event, EventStatus, PhySize, Window, WindowEvent, WindowHandler, WindowScalePolicy,
};
#[derive(Debug, Clone)]
enum Message {
@ -13,32 +16,48 @@ enum Message {
struct OpenWindowExample {
rx: Consumer<Message>,
_ctx: softbuffer::Context,
surface: softbuffer::Surface,
current_size: PhySize,
damaged: bool,
}
impl WindowHandler for OpenWindowExample {
fn on_frame(&mut self, _window: &mut Window) {
let mut buf = self.surface.buffer_mut().unwrap();
if self.damaged {
buf.fill(0xFFAAAAAA);
self.damaged = false;
}
buf.present().unwrap();
while let Ok(message) = self.rx.pop() {
println!("Message: {:?}", message);
}
}
fn on_event(&mut self, _window: &mut Window, event: Event) -> EventStatus {
match event {
Event::Mouse(e) => {
println!("Mouse event: {:?}", e);
match &event {
#[cfg(target_os = "macos")]
Event::Mouse(MouseEvent::ButtonPressed { .. }) => copy_to_clipboard("This is a test!"),
Event::Window(WindowEvent::Resized(info)) => {
println!("Resized: {:?}", info);
let new_size = info.physical_size();
self.current_size = new_size;
#[cfg(target_os = "macos")]
match e {
MouseEvent::ButtonPressed { .. } => {
copy_to_clipboard(&"This is a test!")
}
_ => (),
if let (Some(width), Some(height)) =
(NonZeroU32::new(new_size.width), NonZeroU32::new(new_size.height))
{
self.surface.resize(width, height).unwrap();
self.damaged = true;
}
}
Event::Keyboard(e) => println!("Keyboard event: {:?}", e),
Event::Window(e) => println!("Window event: {:?}", e),
_ => {}
}
log_event(&event);
EventStatus::Captured
}
}
@ -56,13 +75,33 @@ fn main() {
let (mut tx, rx) = RingBuffer::new(128);
::std::thread::spawn(move || loop {
::std::thread::sleep(Duration::from_secs(5));
std::thread::spawn(move || loop {
std::thread::sleep(Duration::from_secs(5));
if let Err(_) = tx.push(Message::Hello) {
if tx.push(Message::Hello).is_err() {
println!("Failed sending message");
}
});
Window::open_blocking(window_open_options, |_| OpenWindowExample { rx });
Window::open_blocking(window_open_options, |window| {
let ctx = unsafe { softbuffer::Context::new(window) }.unwrap();
let mut surface = unsafe { softbuffer::Surface::new(&ctx, window) }.unwrap();
surface.resize(NonZeroU32::new(512).unwrap(), NonZeroU32::new(512).unwrap()).unwrap();
OpenWindowExample {
_ctx: ctx,
surface,
rx,
current_size: PhySize::new(512, 512),
damaged: true,
}
});
}
fn log_event(event: &Event) {
match event {
Event::Mouse(e) => println!("Mouse event: {:?}", e),
Event::Keyboard(e) => println!("Keyboard event: {:?}", e),
Event::Window(e) => println!("Window event: {:?}", e),
}
}

View file

@ -0,0 +1,9 @@
[package]
name = "render_femtovg"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
baseview = { path = "../..", features = ["opengl"] }
femtovg = "0.9.0"

View file

@ -0,0 +1,115 @@
use baseview::gl::GlConfig;
use baseview::{
Event, EventStatus, MouseEvent, PhyPoint, Size, Window, WindowEvent, WindowHandler, WindowInfo,
WindowOpenOptions, WindowScalePolicy,
};
use femtovg::renderer::OpenGl;
use femtovg::{Canvas, Color};
struct FemtovgExample {
canvas: Canvas<OpenGl>,
current_size: WindowInfo,
current_mouse_position: PhyPoint,
damaged: bool,
}
impl FemtovgExample {
fn new(window: &mut Window) -> Self {
let context = window.gl_context().unwrap();
unsafe { context.make_current() };
let renderer =
unsafe { OpenGl::new_from_function(|s| context.get_proc_address(s)) }.unwrap();
let mut canvas = Canvas::new(renderer).unwrap();
// TODO: get actual window width
canvas.set_size(512, 512, 1.0);
unsafe { context.make_not_current() };
Self {
canvas,
current_size: WindowInfo::from_logical_size(Size { width: 512.0, height: 512.0 }, 1.0),
current_mouse_position: PhyPoint { x: 256, y: 256 },
damaged: true,
}
}
}
impl WindowHandler for FemtovgExample {
fn on_frame(&mut self, window: &mut Window) {
if !self.damaged {
return;
}
let context = window.gl_context().unwrap();
unsafe { context.make_current() };
let screen_height = self.canvas.height();
let screen_width = self.canvas.width();
// Clear
self.canvas.clear_rect(0, 0, screen_width, screen_height, Color::rgb(0xAA, 0xAA, 0xAA));
// Make big blue rectangle
self.canvas.clear_rect(
(screen_width as f32 * 0.1).floor() as u32,
(screen_height as f32 * 0.1).floor() as u32,
(screen_width as f32 * 0.8).floor() as u32,
(screen_height as f32 * 0.8).floor() as u32,
Color::rgbf(0., 0.3, 0.9),
);
// Make smol orange rectangle
self.canvas.clear_rect(
(self.current_mouse_position.x - 15).clamp(0, screen_width as i32 - 30) as u32,
(self.current_mouse_position.y - 15).clamp(0, screen_height as i32 - 30) as u32,
30,
30,
Color::rgbf(0.9, 0.3, 0.),
);
// Tell renderer to execute all drawing commands
self.canvas.flush();
context.swap_buffers();
unsafe { context.make_not_current() };
self.damaged = false;
}
fn on_event(&mut self, _window: &mut Window, event: Event) -> EventStatus {
match event {
Event::Window(WindowEvent::Resized(size)) => {
let phy_size = size.physical_size();
self.current_size = size;
self.canvas.set_size(phy_size.width, phy_size.height, size.scale() as f32);
self.damaged = true;
}
Event::Mouse(MouseEvent::CursorMoved { position, .. }) => {
self.current_mouse_position = position.to_physical(&self.current_size);
self.damaged = true;
}
_ => {}
};
log_event(&event);
EventStatus::Captured
}
}
fn main() {
let window_open_options = WindowOpenOptions {
title: "Femtovg on Baseview".into(),
size: Size::new(512.0, 512.0),
scale: WindowScalePolicy::SystemScaleFactor,
gl_config: Some(GlConfig { alpha_bits: 8, ..GlConfig::default() }),
};
Window::open_blocking(window_open_options, FemtovgExample::new);
}
fn log_event(event: &Event) {
match event {
Event::Mouse(e) => println!("Mouse event: {:?}", e),
Event::Keyboard(e) => println!("Keyboard event: {:?}", e),
Event::Window(e) => println!("Window event: {:?}", e),
}
}

View file

@ -35,11 +35,7 @@ impl GlContext {
return Err(GlError::InvalidWindowHandle);
};
if handle.ns_view.is_null() {
return Err(GlError::InvalidWindowHandle);
}
let parent_view = handle.ns_view as id;
let parent_view = handle.ns_view.as_ptr() as id;
let version = if config.version < (3, 2) && config.profile == Profile::Compatibility {
NSOpenGLProfileVersionLegacy
@ -121,10 +117,8 @@ impl GlContext {
let framework_name = CFString::from_str("com.apple.opengl").unwrap();
let framework =
unsafe { CFBundleGetBundleWithIdentifier(framework_name.as_concrete_TypeRef()) };
let addr = unsafe {
CFBundleGetFunctionPointerForName(framework, symbol_name.as_concrete_TypeRef())
};
addr as *const c_void
unsafe { CFBundleGetFunctionPointerForName(framework, symbol_name.as_concrete_TypeRef()) }
}
pub fn swap_buffers(&self) {

View file

@ -84,10 +84,6 @@ impl GlContext {
return Err(GlError::InvalidWindowHandle);
};
if handle.hwnd.is_null() {
return Err(GlError::InvalidWindowHandle);
}
// Create temporary window and context to load function pointers
let class_name_str = format!("raw-gl-context-window-{}", uuid::Uuid::new_v4().to_simple());
@ -195,7 +191,7 @@ impl GlContext {
// Create actual context
let hwnd = handle.hwnd as HWND;
let hwnd = handle.hwnd.get() as HWND;
let hdc = GetDC(hwnd);

View file

@ -229,12 +229,12 @@ impl GlContext {
}
pub fn swap_buffers(&self) {
errors::XErrorHandler::handle(self.display, |error_handler| {
unsafe {
unsafe {
errors::XErrorHandler::handle(self.display, |error_handler| {
glx::glXSwapBuffers(self.display, self.window);
}
error_handler.check().unwrap();
})
error_handler.check().unwrap();
})
}
}
}

View file

@ -1,25 +1,27 @@
use std::ffi::CStr;
use std::fmt::{Debug, Formatter};
use std::fmt::{Debug, Display, Formatter};
use x11::xlib;
use std::cell::RefCell;
use std::error::Error;
use std::os::raw::{c_int, c_uchar, c_ulong};
use std::panic::AssertUnwindSafe;
thread_local! {
/// Used as part of [`XerrorHandler::handle()`]. When an X11 error occurs during this function,
/// Used as part of [`XErrorHandler::handle()`]. When an X11 error occurs during this function,
/// the error gets copied to this RefCell after which the program is allowed to resume. The
/// error can then be converted to a regular Rust Result value afterwards.
static CURRENT_X11_ERROR: RefCell<Option<xlib::XErrorEvent>> = RefCell::new(None);
/// error can then be converted to a regular Rust Result value afterward.
static CURRENT_X11_ERROR: RefCell<Option<XLibError>> = const { RefCell::new(None) };
}
/// A helper struct for safe X11 error handling
pub struct XErrorHandler<'a> {
display: *mut xlib::Display,
error: &'a RefCell<Option<xlib::XErrorEvent>>,
error: &'a RefCell<Option<XLibError>>,
}
impl<'a> XErrorHandler<'a> {
/// Syncs and checks if any previous X11 calls returned an error
/// Syncs and checks if any previous X11 calls from the given display returned an error
pub fn check(&mut self) -> Result<(), XLibError> {
// Flush all possible previous errors
unsafe {
@ -29,20 +31,27 @@ impl<'a> XErrorHandler<'a> {
match error {
None => Ok(()),
Some(inner) => Err(XLibError { inner }),
Some(inner) => Err(inner),
}
}
/// Sets up a temporary X11 error handler for the duration of the given closure, and allows
/// that closure to check on the latest X11 error at any time
pub fn handle<T, F: FnOnce(&mut XErrorHandler) -> T>(
/// that closure to check on the latest X11 error at any time.
///
/// # Safety
///
/// The given display pointer *must* be and remain valid for the duration of this function, as
/// well as for the duration of the given `handler` closure.
pub unsafe fn handle<T, F: FnOnce(&mut XErrorHandler) -> T>(
display: *mut xlib::Display, handler: F,
) -> T {
/// # Safety
/// The given display and error pointers *must* be valid for the duration of this function.
unsafe extern "C" fn error_handler(
_dpy: *mut xlib::Display, err: *mut xlib::XErrorEvent,
) -> i32 {
// SAFETY: the error pointer should be safe to copy
let err = *err;
// SAFETY: the error pointer should be safe to access
let err = &*err;
CURRENT_X11_ERROR.with(|error| {
let mut error = error.borrow_mut();
@ -51,7 +60,7 @@ impl<'a> XErrorHandler<'a> {
// cause of the other errors
Some(_) => 1,
None => {
*error = Some(err);
*error = Some(XLibError::from_event(err));
0
}
}
@ -65,7 +74,9 @@ impl<'a> XErrorHandler<'a> {
CURRENT_X11_ERROR.with(|error| {
// Make sure to clear any errors from the last call to this function
*error.borrow_mut() = None;
{
*error.borrow_mut() = None;
}
let old_handler = unsafe { xlib::XSetErrorHandler(Some(error_handler)) };
let panic_result = std::panic::catch_unwind(AssertUnwindSafe(|| {
@ -84,15 +95,41 @@ impl<'a> XErrorHandler<'a> {
}
pub struct XLibError {
inner: xlib::XErrorEvent,
type_: c_int,
resourceid: xlib::XID,
serial: c_ulong,
error_code: c_uchar,
request_code: c_uchar,
minor_code: c_uchar,
display_name: Box<str>,
}
impl XLibError {
pub fn get_display_name(&self, buf: &mut [u8]) -> &CStr {
/// # Safety
/// The display pointer inside error must be valid for the duration of this call
unsafe fn from_event(error: &xlib::XErrorEvent) -> Self {
Self {
type_: error.type_,
resourceid: error.resourceid,
serial: error.serial,
error_code: error.error_code,
request_code: error.request_code,
minor_code: error.minor_code,
display_name: Self::get_display_name(error),
}
}
/// # Safety
/// The display pointer inside error must be valid for the duration of this call
unsafe fn get_display_name(error: &xlib::XErrorEvent) -> Box<str> {
let mut buf = [0; 255];
unsafe {
xlib::XGetErrorText(
self.inner.display,
self.inner.error_code.into(),
error.display,
error.error_code.into(),
buf.as_mut_ptr().cast(),
(buf.len() - 1) as i32,
);
@ -100,23 +137,30 @@ impl XLibError {
*buf.last_mut().unwrap() = 0;
// SAFETY: whatever XGetErrorText did or not, we guaranteed there is a nul byte at the end of the buffer
unsafe { CStr::from_ptr(buf.as_mut_ptr().cast()) }
let cstr = unsafe { CStr::from_ptr(buf.as_mut_ptr().cast()) };
cstr.to_string_lossy().into()
}
}
impl Debug for XLibError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut buf = [0; 255];
let display_name = self.get_display_name(&mut buf).to_string_lossy();
f.debug_struct("XLibError")
.field("error_code", &self.inner.error_code)
.field("error_message", &display_name)
.field("minor_code", &self.inner.minor_code)
.field("request_code", &self.inner.request_code)
.field("type", &self.inner.type_)
.field("resource_id", &self.inner.resourceid)
.field("serial", &self.inner.serial)
.field("error_code", &self.error_code)
.field("error_message", &self.display_name)
.field("minor_code", &self.minor_code)
.field("request_code", &self.request_code)
.field("type", &self.type_)
.field("resource_id", &self.resourceid)
.field("serial", &self.serial)
.finish()
}
}
impl Display for XLibError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "XLib error: {} (error code {})", &self.display_name, self.error_code)
}
}
impl Error for XLibError {}

View file

@ -29,6 +29,12 @@ use super::{
/// Name of the field used to store the `WindowState` pointer.
pub(super) const BASEVIEW_STATE_IVAR: &str = "baseview_state";
#[link(name = "AppKit", kind = "framework")]
extern "C" {
static NSWindowDidBecomeKeyNotification: id;
static NSWindowDidResignKeyNotification: id;
}
macro_rules! add_simple_mouse_class_method {
($class:ident, $sel:ident, $event:expr) => {
#[allow(non_snake_case)]
@ -94,6 +100,18 @@ macro_rules! add_simple_keyboard_class_method {
};
}
unsafe fn register_notification(observer: id, notification_name: id, object: id) {
let notification_center: id = msg_send![class!(NSNotificationCenter), defaultCenter];
let _: () = msg_send![
notification_center,
addObserver:observer
selector:sel!(handleNotification:)
name:notification_name
object:object
];
}
pub(super) unsafe fn create_view(window_options: &WindowOpenOptions) -> id {
let class = create_view_class();
@ -103,6 +121,9 @@ pub(super) unsafe fn create_view(window_options: &WindowOpenOptions) -> id {
view.initWithFrame_(NSRect::new(NSPoint::new(0., 0.), NSSize::new(size.width, size.height)));
register_notification(view, NSWindowDidBecomeKeyNotification, nil);
register_notification(view, NSWindowDidResignKeyNotification, nil);
let _: id = msg_send![
view,
registerForDraggedTypes: NSArray::arrayWithObjects(nil, &[NSFilenamesPboardType])
@ -124,6 +145,14 @@ unsafe fn create_view_class() -> &'static Class {
sel!(acceptsFirstResponder),
property_yes as extern "C" fn(&Object, Sel) -> BOOL,
);
class.add_method(
sel!(becomeFirstResponder),
become_first_responder as extern "C" fn(&Object, Sel) -> BOOL,
);
class.add_method(
sel!(resignFirstResponder),
resign_first_responder as extern "C" fn(&Object, Sel) -> BOOL,
);
class.add_method(sel!(isFlipped), property_yes as extern "C" fn(&Object, Sel) -> BOOL);
class.add_method(
sel!(preservesContentInLiveResize),
@ -177,6 +206,10 @@ unsafe fn create_view_class() -> &'static Class {
dragging_updated as extern "C" fn(&Object, Sel, id) -> NSUInteger,
);
class.add_method(sel!(draggingExited:), dragging_exited as extern "C" fn(&Object, Sel, id));
class.add_method(
sel!(handleNotification:),
handle_notification as extern "C" fn(&Object, Sel, id),
);
add_mouse_button_class_method!(class, mouseDown, ButtonPressed, MouseButton::Left);
add_mouse_button_class_method!(class, mouseUp, ButtonReleased, MouseButton::Left);
@ -208,6 +241,25 @@ extern "C" fn accepts_first_mouse(_this: &Object, _sel: Sel, _event: id) -> BOOL
YES
}
extern "C" fn become_first_responder(this: &Object, _sel: Sel) -> BOOL {
let state = unsafe { WindowState::from_view(this) };
let is_key_window = unsafe {
let window: id = msg_send![this, window];
let is_key_window: BOOL = msg_send![window, isKeyWindow];
is_key_window == YES
};
if is_key_window {
state.trigger_event(Event::Window(WindowEvent::Focused));
}
YES
}
extern "C" fn resign_first_responder(this: &Object, _sel: Sel) -> BOOL {
let state = unsafe { WindowState::from_view(this) };
state.trigger_event(Event::Window(WindowEvent::Unfocused));
YES
}
extern "C" fn window_should_close(this: &Object, _: Sel, _sender: id) -> BOOL {
let state = unsafe { WindowState::from_view(this) };
@ -295,8 +347,6 @@ extern "C" fn view_will_move_to_window(this: &Object, _self: Sel, new_window: id
let tracking_areas: *mut Object = msg_send![this, trackingAreas];
let tracking_area_count = NSArray::count(tracking_areas);
let _: () = msg_send![class!(NSEvent), setMouseCoalescingEnabled: NO];
if new_window == nil {
if tracking_area_count != 0 {
let tracking_area = NSArray::objectAtIndex(tracking_areas, 0);
@ -473,3 +523,30 @@ extern "C" fn dragging_exited(this: &Object, _sel: Sel, _sender: id) {
on_event(&state, MouseEvent::DragLeft);
}
extern "C" fn handle_notification(this: &Object, _cmd: Sel, notification: id) {
unsafe {
let state = WindowState::from_view(this);
// The subject of the notication, in this case an NSWindow object.
let notification_object: id = msg_send![notification, object];
// The NSWindow object associated with our NSView.
let window: id = msg_send![this, window];
let first_responder: id = msg_send![window, firstResponder];
// Only trigger focus events if the NSWindow that's being notified about is our window,
// and if the window's first responder is our NSView.
// If the first responder isn't our NSView, the focus events will instead be triggered
// by the becomeFirstResponder and resignFirstResponder methods on the NSView itself.
if notification_object == window && first_responder == this as *const Object as id {
let is_key_window: BOOL = msg_send![window, isKeyWindow];
state.trigger_event(Event::Window(if is_key_window == YES {
WindowEvent::Focused
} else {
WindowEvent::Unfocused
}));
}
}
}

View file

@ -7,15 +7,14 @@ use cocoa::appkit::{
NSApp, NSApplication, NSApplicationActivationPolicyRegular, NSBackingStoreBuffered,
NSPasteboard, NSView, NSWindow, NSWindowStyleMask,
};
use cocoa::base::{id, nil, NO, YES};
use cocoa::base::{id, nil, BOOL, NO, YES};
use cocoa::foundation::{NSAutoreleasePool, NSPoint, NSRect, NSSize, NSString};
use core_foundation::runloop::{
CFRunLoop, CFRunLoopTimer, CFRunLoopTimerContext, __CFRunLoopTimer, kCFRunLoopDefaultMode,
};
use keyboard_types::KeyboardEvent;
use objc::class;
use objc::{msg_send, runtime::Object, sel, sel_impl};
use raw_window_handle::{
AppKitDisplayHandle, AppKitWindowHandle, HasRawDisplayHandle, HasRawWindowHandle,
RawDisplayHandle, RawWindowHandle,
@ -47,7 +46,7 @@ impl WindowHandle {
}
unsafe impl HasRawWindowHandle for WindowHandle {
fn raw_window_handle(&self) -> RawWindowHandle {
fn raw_window_handle(&self) -> Result<RawWindowHandle, raw_window_handle::HandleError> {
self.state.window_inner.raw_window_handle()
}
}
@ -83,6 +82,11 @@ impl WindowInner {
CFRunLoop::get_current().remove_timer(&frame_timer, kCFRunLoopDefaultMode);
}
// Deregister NSView from NotificationCenter.
let notification_center: id =
msg_send![class!(NSNotificationCenter), defaultCenter];
let () = msg_send![notification_center, removeObserver:self.ns_view];
drop(window_state);
// Close the window if in non-parented mode
@ -103,18 +107,15 @@ impl WindowInner {
}
}
fn raw_window_handle(&self) -> RawWindowHandle {
fn raw_window_handle(&self) -> Result<RawWindowHandle, raw_window_handle::HandleError> {
if self.open.get() {
let ns_window = self.ns_window.get().unwrap_or(ptr::null_mut()) as *mut c_void;
let mut handle = AppKitWindowHandle::empty();
handle.ns_window = ns_window;
handle.ns_view = self.ns_view as *mut c_void;
return RawWindowHandle::AppKit(handle);
Ok(RawWindowHandle::AppKit(AppKitWindowHandle::new(
std::ptr::NonNull::new(self.ns_view as *mut c_void)
.ok_or(raw_window_handle::HandleError::Unavailable)?,
)))
} else {
Err(raw_window_handle::HandleError::Unavailable)
}
RawWindowHandle::AppKit(AppKitWindowHandle::empty())
}
}
@ -139,7 +140,7 @@ impl<'a> Window<'a> {
let window_info = WindowInfo::from_logical_size(options.size, scaling);
let handle = if let RawWindowHandle::AppKit(handle) = parent.raw_window_handle() {
let handle = if let Ok(RawWindowHandle::AppKit(handle)) = parent.raw_window_handle() {
handle
} else {
panic!("Not a macOS window");
@ -162,7 +163,7 @@ impl<'a> Window<'a> {
let window_handle = Self::init(window_inner, window_info, build);
unsafe {
let _: id = msg_send![handle.ns_view as *mut Object, addSubview: ns_view];
let _: id = msg_send![handle.ns_view.as_ptr() as *mut Object, addSubview: ns_view];
let () = msg_send![pool, drain];
}
@ -280,6 +281,30 @@ impl<'a> Window<'a> {
self.inner.close();
}
pub fn has_focus(&mut self) -> bool {
unsafe {
let view = self.inner.ns_view.as_mut().unwrap();
let window: id = msg_send![view, window];
if window == nil {
return false;
};
let first_responder: id = msg_send![window, firstResponder];
let is_key_window: BOOL = msg_send![window, isKeyWindow];
let is_focused: BOOL = msg_send![view, isEqual: first_responder];
is_key_window == YES && is_focused == YES
}
}
pub fn focus(&mut self) {
unsafe {
let view = self.inner.ns_view.as_mut().unwrap();
let window: id = msg_send![view, window];
if window != nil {
msg_send![window, makeFirstResponder:view]
}
}
}
pub fn resize(&mut self, size: Size) {
if self.inner.open.get() {
// NOTE: macOS gives you a personal rave if you pass in fractional pixels here. Even
@ -316,10 +341,9 @@ impl<'a> Window<'a> {
#[cfg(feature = "opengl")]
fn create_gl_context(ns_window: Option<id>, ns_view: id, config: GlConfig) -> GlContext {
let mut handle = AppKitWindowHandle::empty();
handle.ns_window = ns_window.unwrap_or(ptr::null_mut()) as *mut c_void;
handle.ns_view = ns_view as *mut c_void;
let handle = RawWindowHandle::AppKit(handle);
let handle = RawWindowHandle::AppKit(AppKitWindowHandle::new(
std::ptr::NonNull::new(ns_view as *mut c_void).expect("Could not create window handle"),
));
unsafe { GlContext::create(&handle, config).expect("Could not create OpenGL context") }
}
@ -394,14 +418,14 @@ impl WindowState {
}
unsafe impl<'a> HasRawWindowHandle for Window<'a> {
fn raw_window_handle(&self) -> RawWindowHandle {
fn raw_window_handle(&self) -> Result<RawWindowHandle, raw_window_handle::HandleError> {
self.inner.raw_window_handle()
}
}
unsafe impl<'a> HasRawDisplayHandle for Window<'a> {
fn raw_display_handle(&self) -> RawDisplayHandle {
RawDisplayHandle::AppKit(AppKitDisplayHandle::empty())
fn raw_display_handle(&self) -> Result<RawDisplayHandle, raw_window_handle::HandleError> {
Ok(RawDisplayHandle::AppKit(AppKitDisplayHandle::new()))
}
}

54
src/win/cursor.rs Normal file
View file

@ -0,0 +1,54 @@
use crate::MouseCursor;
use winapi::{
shared::ntdef::LPCWSTR,
um::winuser::{
IDC_APPSTARTING, IDC_ARROW, IDC_CROSS, IDC_HAND, IDC_HELP, IDC_IBEAM, IDC_NO, IDC_SIZEALL,
IDC_SIZENESW, IDC_SIZENS, IDC_SIZENWSE, IDC_SIZEWE, IDC_WAIT,
},
};
pub fn cursor_to_lpcwstr(cursor: MouseCursor) -> LPCWSTR {
match cursor {
MouseCursor::Default => IDC_ARROW,
MouseCursor::Hand => IDC_HAND,
MouseCursor::HandGrabbing => IDC_SIZEALL,
MouseCursor::Help => IDC_HELP,
// an empty LPCWSTR results in the cursor being hidden
MouseCursor::Hidden => std::ptr::null(),
MouseCursor::Text => IDC_IBEAM,
MouseCursor::VerticalText => IDC_IBEAM,
MouseCursor::Working => IDC_WAIT,
MouseCursor::PtrWorking => IDC_APPSTARTING,
MouseCursor::NotAllowed => IDC_NO,
MouseCursor::PtrNotAllowed => IDC_NO,
MouseCursor::ZoomIn => IDC_ARROW,
MouseCursor::ZoomOut => IDC_ARROW,
MouseCursor::Alias => IDC_ARROW,
MouseCursor::Copy => IDC_ARROW,
MouseCursor::Move => IDC_SIZEALL,
MouseCursor::AllScroll => IDC_SIZEALL,
MouseCursor::Cell => IDC_CROSS,
MouseCursor::Crosshair => IDC_CROSS,
MouseCursor::EResize => IDC_SIZEWE,
MouseCursor::NResize => IDC_SIZENS,
MouseCursor::NeResize => IDC_SIZENESW,
MouseCursor::NwResize => IDC_SIZENWSE,
MouseCursor::SResize => IDC_SIZENS,
MouseCursor::SeResize => IDC_SIZENWSE,
MouseCursor::SwResize => IDC_SIZENESW,
MouseCursor::WResize => IDC_SIZEWE,
MouseCursor::EwResize => IDC_SIZEWE,
MouseCursor::NsResize => IDC_SIZENS,
MouseCursor::NwseResize => IDC_SIZENWSE,
MouseCursor::NeswResize => IDC_SIZENESW,
MouseCursor::ColResize => IDC_SIZEWE,
MouseCursor::RowResize => IDC_SIZENS,
}
}

View file

@ -15,7 +15,7 @@ use winapi::um::oleidl::{
IDropTarget, IDropTargetVtbl, DROPEFFECT_COPY, DROPEFFECT_LINK, DROPEFFECT_MOVE,
DROPEFFECT_NONE, DROPEFFECT_SCROLL,
};
use winapi::um::shellapi::DragQueryFileW;
use winapi::um::shellapi::{DragQueryFileW, HDROP};
use winapi::um::unknwnbase::{IUnknown, IUnknownVtbl};
use winapi::um::winuser::CF_HDROP;
use winapi::Interface;
@ -135,7 +135,7 @@ impl DropTarget {
return;
}
let hdrop = transmute((*medium.u).hGlobal());
let hdrop = *(*medium.u).hGlobal() as HDROP;
let item_count = DragQueryFileW(hdrop, 0xFFFFFFFF, null_mut(), 0);
if item_count == 0 {
@ -148,15 +148,9 @@ impl DropTarget {
for i in 0..item_count {
let characters = DragQueryFileW(hdrop, i, null_mut(), 0);
let buffer_size = characters as usize + 1;
let mut buffer = Vec::<u16>::with_capacity(buffer_size);
let mut buffer = vec![0u16; buffer_size];
DragQueryFileW(
hdrop,
i,
transmute(buffer.spare_capacity_mut().as_mut_ptr()),
buffer_size as u32,
);
buffer.set_len(buffer_size);
DragQueryFileW(hdrop, i, buffer.as_mut_ptr().cast(), buffer_size as u32);
paths.push(OsString::from_wide(&buffer[..characters as usize]).into())
}
@ -175,7 +169,7 @@ impl DropTarget {
return S_OK;
}
return E_NOINTERFACE;
E_NOINTERFACE
}
unsafe extern "system" fn add_ref(this: *mut IUnknown) -> ULONG {

View file

@ -1,3 +1,4 @@
mod cursor;
mod drop_target;
mod keyboard;
mod window;

View file

@ -1,19 +1,20 @@
use winapi::shared::guiddef::GUID;
use winapi::shared::minwindef::{ATOM, FALSE, LPARAM, LRESULT, UINT, WPARAM};
use winapi::shared::minwindef::{ATOM, FALSE, LOWORD, LPARAM, LRESULT, UINT, WPARAM};
use winapi::shared::windef::{HWND, RECT};
use winapi::um::combaseapi::CoCreateGuid;
use winapi::um::ole2::{OleInitialize, RegisterDragDrop, RevokeDragDrop};
use winapi::um::oleidl::LPDROPTARGET;
use winapi::um::winuser::{
AdjustWindowRectEx, CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW,
GetDpiForWindow, GetMessageW, GetWindowLongPtrW, LoadCursorW, PostMessageW, RegisterClassW,
ReleaseCapture, SetCapture, SetProcessDpiAwarenessContext, SetTimer, SetWindowLongPtrW,
SetWindowPos, TranslateMessage, UnregisterClassW, CS_OWNDC, GET_XBUTTON_WPARAM, GWLP_USERDATA,
IDC_ARROW, MSG, SWP_NOMOVE, SWP_NOZORDER, WHEEL_DELTA, WM_CHAR, WM_CLOSE, WM_CREATE,
WM_DPICHANGED, WM_INPUTLANGCHANGE, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP,
WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCDESTROY,
WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SHOWWINDOW, WM_SIZE, WM_SYSCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP,
WM_TIMER, WM_USER, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW, WS_CAPTION, WS_CHILD,
GetDpiForWindow, GetFocus, GetMessageW, GetWindowLongPtrW, LoadCursorW, PostMessageW,
RegisterClassW, ReleaseCapture, SetCapture, SetCursor, SetFocus, SetProcessDpiAwarenessContext,
SetTimer, SetWindowLongPtrW, SetWindowPos, TrackMouseEvent, TranslateMessage, UnregisterClassW,
CS_OWNDC, GET_XBUTTON_WPARAM, GWLP_USERDATA, HTCLIENT, IDC_ARROW, MSG, SWP_NOMOVE,
SWP_NOZORDER, TRACKMOUSEEVENT, WHEEL_DELTA, WM_CHAR, WM_CLOSE, WM_CREATE, WM_DPICHANGED,
WM_INPUTLANGCHANGE, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN,
WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSELEAVE, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCDESTROY,
WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SHOWWINDOW, WM_SIZE, WM_SYSCHAR, WM_SYSKEYDOWN,
WM_SYSKEYUP, WM_TIMER, WM_USER, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW, WS_CAPTION, WS_CHILD,
WS_CLIPSIBLINGS, WS_MAXIMIZEBOX, WS_MINIMIZEBOX, WS_POPUPWINDOW, WS_SIZEBOX, WS_VISIBLE,
XBUTTON1, XBUTTON2,
};
@ -21,6 +22,7 @@ use winapi::um::winuser::{
use std::cell::{Cell, Ref, RefCell, RefMut};
use std::collections::VecDeque;
use std::ffi::{c_void, OsStr};
use std::num::NonZeroIsize;
use std::os::windows::ffi::OsStrExt;
use std::ptr::null_mut;
use std::rc::Rc;
@ -37,6 +39,7 @@ use crate::{
WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy,
};
use super::cursor::cursor_to_lpcwstr;
use super::drop_target::DropTarget;
use super::keyboard::KeyboardState;
@ -84,15 +87,12 @@ impl WindowHandle {
}
unsafe impl HasRawWindowHandle for WindowHandle {
fn raw_window_handle(&self) -> RawWindowHandle {
if let Some(hwnd) = self.hwnd {
let mut handle = Win32WindowHandle::empty();
handle.hwnd = hwnd as *mut c_void;
RawWindowHandle::Win32(handle)
} else {
RawWindowHandle::Win32(Win32WindowHandle::empty())
}
fn raw_window_handle(&self) -> Result<RawWindowHandle, raw_window_handle::HandleError> {
self.hwnd
.and_then(|hwnd| NonZeroIsize::new(hwnd as isize))
.ok_or(raw_window_handle::HandleError::Unavailable)
.map(|v| Win32WindowHandle::new(v))
.map(|v| RawWindowHandle::Win32(v))
}
}
@ -170,21 +170,52 @@ unsafe fn wnd_proc_inner(
WM_MOUSEMOVE => {
let mut window = crate::Window::new(window_state.create_window());
let mut mouse_was_outside_window = window_state.mouse_was_outside_window.borrow_mut();
if *mouse_was_outside_window {
// this makes Windows track whether the mouse leaves the window.
// When the mouse leaves it results in a `WM_MOUSELEAVE` event.
let mut track_mouse = TRACKMOUSEEVENT {
cbSize: std::mem::size_of::<TRACKMOUSEEVENT>() as u32,
dwFlags: winapi::um::winuser::TME_LEAVE,
hwndTrack: hwnd,
dwHoverTime: winapi::um::winuser::HOVER_DEFAULT,
};
// Couldn't find a good way to track whether the mouse enters,
// but if `WM_MOUSEMOVE` happens, the mouse must have entered.
TrackMouseEvent(&mut track_mouse);
*mouse_was_outside_window = false;
let enter_event = Event::Mouse(MouseEvent::CursorEntered);
window_state
.handler
.borrow_mut()
.as_mut()
.unwrap()
.on_event(&mut window, enter_event);
}
let x = (lparam & 0xFFFF) as i16 as i32;
let y = ((lparam >> 16) & 0xFFFF) as i16 as i32;
let physical_pos = PhyPoint { x, y };
let logical_pos = physical_pos.to_logical(&window_state.window_info.borrow());
let event = Event::Mouse(MouseEvent::CursorMoved {
let move_event = Event::Mouse(MouseEvent::CursorMoved {
position: logical_pos,
modifiers: window_state
.keyboard_state
.borrow()
.get_modifiers_from_mouse_wparam(wparam),
});
window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, move_event);
Some(0)
}
WM_MOUSELEAVE => {
let mut window = crate::Window::new(window_state.create_window());
let event = Event::Mouse(MouseEvent::CursorLeft);
window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, event);
*window_state.mouse_was_outside_window.borrow_mut() = true;
Some(0)
}
WM_MOUSEWHEEL | WM_MOUSEHWHEEL => {
@ -397,6 +428,24 @@ unsafe fn wnd_proc_inner(
None
}
// If WM_SETCURSOR returns `None`, WM_SETCURSOR continues to get handled by the outer window(s),
// If it returns `Some(1)`, the current window decides what the cursor is
WM_SETCURSOR => {
let low_word = LOWORD(lparam as u32) as isize;
let mouse_in_window = low_word == HTCLIENT;
if mouse_in_window {
// Here we need to set the cursor back to what the state says, since it can have changed when outside the window
let cursor =
LoadCursorW(null_mut(), cursor_to_lpcwstr(window_state.cursor_icon.get()));
unsafe {
SetCursor(cursor);
}
Some(1)
} else {
// Cursor is being changed by some other window, e.g. when having mouse on the borders to resize it
None
}
}
// NOTE: `WM_NCDESTROY` is handled in the outer function because this deallocates the window
// state
BV_WINDOW_MUST_CLOSE => {
@ -448,6 +497,8 @@ pub(super) struct WindowState {
_parent_handle: Option<ParentHandle>,
keyboard_state: RefCell<KeyboardState>,
mouse_button_counter: Cell<usize>,
mouse_was_outside_window: RefCell<bool>,
cursor_icon: Cell<MouseCursor>,
// Initialized late so the `Window` can hold a reference to this `WindowState`
handler: RefCell<Option<Box<dyn WindowHandler>>>,
_drop_target: RefCell<Option<Rc<DropTarget>>>,
@ -482,17 +533,14 @@ impl WindowState {
self.handler.borrow_mut()
}
/// Handle a deferred task as described in [`Self::deferred_tasks
/// Handle a deferred task as described in [`Self::deferred_tasks`].
pub(self) fn handle_deferred_task(&self, task: WindowTask) {
match task {
WindowTask::Resize(size) => {
let window_info = {
let mut window_info = self.window_info.borrow_mut();
let scaling = window_info.scale();
*window_info = WindowInfo::from_logical_size(size, scaling);
*window_info
};
// `self.window_info` will be modified in response to the `WM_SIZE` event that
// follows the `SetWindowPos()` call
let scaling = self.window_info.borrow().scale();
let window_info = WindowInfo::from_logical_size(size, scaling);
// If the window is a standalone window then the size needs to include the window
// decorations
@ -541,7 +589,7 @@ impl Window<'_> {
B: Send + 'static,
{
let parent = match parent.raw_window_handle() {
RawWindowHandle::Win32(h) => h.hwnd as HWND,
Ok(RawWindowHandle::Win32(h)) => h.hwnd.get() as HWND,
h => panic!("unsupported parent handle {:?}", h),
};
@ -638,9 +686,11 @@ impl Window<'_> {
#[cfg(feature = "opengl")]
let gl_context: Option<GlContext> = options.gl_config.map(|gl_config| {
let mut handle = Win32WindowHandle::empty();
handle.hwnd = hwnd as *mut c_void;
let handle = RawWindowHandle::Win32(handle);
let handle = NonZeroIsize::new(hwnd as isize)
.ok_or(raw_window_handle::HandleError::Unavailable)
.map(|v| Win32WindowHandle::new(v))
.map(|v| RawWindowHandle::Win32(v))
.unwrap();
GlContext::create(&handle, gl_config).expect("Could not create OpenGL context")
});
@ -655,6 +705,8 @@ impl Window<'_> {
_parent_handle: parent_handle,
keyboard_state: RefCell::new(KeyboardState::new()),
mouse_button_counter: Cell::new(0),
mouse_was_outside_window: RefCell::new(true),
cursor_icon: Cell::new(MouseCursor::Default),
// The Window refers to this `WindowState`, so this `handler` needs to be
// initialized later
handler: RefCell::new(None),
@ -742,6 +794,17 @@ impl Window<'_> {
}
}
pub fn has_focus(&mut self) -> bool {
let focused_window = unsafe { GetFocus() };
focused_window == self.state.hwnd
}
pub fn focus(&mut self) {
unsafe {
SetFocus(self.state.hwnd);
}
}
pub fn resize(&mut self, size: Size) {
// To avoid reentrant event handler calls we'll defer the actual resizing until after the
// event has been handled
@ -749,8 +812,12 @@ impl Window<'_> {
self.state.deferred_tasks.borrow_mut().push_back(task);
}
pub fn set_mouse_cursor(&mut self, _mouse_cursor: MouseCursor) {
todo!()
pub fn set_mouse_cursor(&mut self, mouse_cursor: MouseCursor) {
self.state.cursor_icon.set(mouse_cursor);
unsafe {
let cursor = LoadCursorW(null_mut(), cursor_to_lpcwstr(mouse_cursor));
SetCursor(cursor);
}
}
#[cfg(feature = "opengl")]
@ -760,17 +827,17 @@ impl Window<'_> {
}
unsafe impl HasRawWindowHandle for Window<'_> {
fn raw_window_handle(&self) -> RawWindowHandle {
let mut handle = Win32WindowHandle::empty();
handle.hwnd = self.state.hwnd as *mut c_void;
RawWindowHandle::Win32(handle)
fn raw_window_handle(&self) -> Result<RawWindowHandle, raw_window_handle::HandleError> {
NonZeroIsize::new(self.state.hwnd as isize)
.ok_or(raw_window_handle::HandleError::Unavailable)
.map(|v| Win32WindowHandle::new(v))
.map(|v| RawWindowHandle::Win32(v))
}
}
unsafe impl HasRawDisplayHandle for Window<'_> {
fn raw_display_handle(&self) -> RawDisplayHandle {
RawDisplayHandle::Windows(WindowsDisplayHandle::empty())
fn raw_display_handle(&self) -> Result<RawDisplayHandle, raw_window_handle::HandleError> {
Ok(RawDisplayHandle::Windows(WindowsDisplayHandle::new()))
}
}

View file

@ -1,7 +1,8 @@
use std::marker::PhantomData;
use raw_window_handle::{
HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle,
DisplayHandle, HasDisplayHandle, HasRawDisplayHandle, HasRawWindowHandle, HasWindowHandle,
RawDisplayHandle, RawWindowHandle,
};
use crate::event::{Event, EventStatus};
@ -23,7 +24,7 @@ pub struct WindowHandle {
impl WindowHandle {
fn new(window_handle: platform::WindowHandle) -> Self {
Self { window_handle, phantom: PhantomData::default() }
Self { window_handle, phantom: PhantomData }
}
/// Close the window
@ -39,7 +40,7 @@ impl WindowHandle {
}
unsafe impl HasRawWindowHandle for WindowHandle {
fn raw_window_handle(&self) -> RawWindowHandle {
fn raw_window_handle(&self) -> Result<RawWindowHandle, raw_window_handle::HandleError> {
self.window_handle.raw_window_handle()
}
}
@ -102,6 +103,14 @@ impl<'a> Window<'a> {
self.window.set_mouse_cursor(cursor);
}
pub fn has_focus(&mut self) -> bool {
self.window.has_focus()
}
pub fn focus(&mut self) {
self.window.focus()
}
/// If provided, then an OpenGL context will be created for this window. You'll be able to
/// access this context through [crate::Window::gl_context].
#[cfg(feature = "opengl")]
@ -110,14 +119,16 @@ impl<'a> Window<'a> {
}
}
unsafe impl<'a> HasRawWindowHandle for Window<'a> {
fn raw_window_handle(&self) -> RawWindowHandle {
self.window.raw_window_handle()
impl<'a> HasWindowHandle for Window<'a> {
fn window_handle(
&self,
) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
Ok(unsafe { raw_window_handle::WindowHandle::borrow_raw(self.window.raw_window_handle()?) })
}
}
unsafe impl<'a> HasRawDisplayHandle for Window<'a> {
fn raw_display_handle(&self) -> RawDisplayHandle {
self.window.raw_display_handle()
impl<'a> HasDisplayHandle for Window<'a> {
fn display_handle(&self) -> Result<DisplayHandle<'_>, raw_window_handle::HandleError> {
Ok(unsafe { DisplayHandle::borrow_raw(self.window.raw_display_handle()?) })
}
}

View file

@ -1,105 +1,100 @@
use std::os::raw::c_char;
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(display: *mut x11::xlib::Display) -> Option<u32> {
let data = 0;
let pixmap = unsafe {
let screen = x11::xlib::XDefaultScreen(display);
let window = x11::xlib::XRootWindow(display, screen);
x11::xlib::XCreateBitmapFromData(display, window, &data, 1, 1)
};
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)?;
if pixmap == 0 {
return None;
}
unsafe {
// We don't care about this color, since it only fills bytes
// in the pixmap which are not 0 in the mask.
let mut color: x11::xlib::XColor = std::mem::zeroed();
let cursor = x11::xlib::XCreatePixmapCursor(
display,
pixmap,
pixmap,
&mut color as *mut _,
&mut color as *mut _,
0,
0,
);
x11::xlib::XFreePixmap(display, pixmap);
Some(cursor as u32)
}
Ok(cursor_id)
}
fn load_cursor(display: *mut x11::xlib::Display, name: &[u8]) -> Option<u32> {
let xcursor =
unsafe { x11::xcursor::XcursorLibraryLoadCursor(display, name.as_ptr() as *const c_char) };
if xcursor == 0 {
None
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 {
Some(xcursor as u32)
Ok(None)
}
}
fn load_first_existing_cursor(display: *mut x11::xlib::Display, names: &[&[u8]]) -> Option<u32> {
names
.iter()
.map(|name| load_cursor(display, name))
.find(|xcursor| xcursor.is_some())
.unwrap_or(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(display: *mut x11::xlib::Display, cursor: MouseCursor) -> u32 {
let load = |name: &[u8]| load_cursor(display, name);
let loadn = |names: &[&[u8]]| load_first_existing_cursor(display, names);
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(&[b"hand2\0", b"hand1\0"]),
MouseCursor::HandGrabbing => loadn(&[b"closedhand\0", b"grabbing\0"]),
MouseCursor::Help => load(b"question_arrow\0"),
MouseCursor::Hand => loadn(&["hand2", "hand1"])?,
MouseCursor::HandGrabbing => loadn(&["closedhand", "grabbing"])?,
MouseCursor::Help => load("question_arrow")?,
MouseCursor::Hidden => create_empty_cursor(display),
MouseCursor::Hidden => Some(create_empty_cursor(conn, screen)?),
MouseCursor::Text => loadn(&[b"text\0", b"xterm\0"]),
MouseCursor::VerticalText => load(b"vertical-text\0"),
MouseCursor::Text => loadn(&["text", "xterm"])?,
MouseCursor::VerticalText => load("vertical-text")?,
MouseCursor::Working => load(b"watch\0"),
MouseCursor::PtrWorking => load(b"left_ptr_watch\0"),
MouseCursor::Working => load("watch")?,
MouseCursor::PtrWorking => load("left_ptr_watch")?,
MouseCursor::NotAllowed => load(b"crossed_circle\0"),
MouseCursor::PtrNotAllowed => loadn(&[b"no-drop\0", b"crossed_circle\0"]),
MouseCursor::NotAllowed => load("crossed_circle")?,
MouseCursor::PtrNotAllowed => loadn(&["no-drop", "crossed_circle"])?,
MouseCursor::ZoomIn => load(b"zoom-in\0"),
MouseCursor::ZoomOut => load(b"zoom-out\0"),
MouseCursor::ZoomIn => load("zoom-in")?,
MouseCursor::ZoomOut => load("zoom-out")?,
MouseCursor::Alias => load(b"link\0"),
MouseCursor::Copy => load(b"copy\0"),
MouseCursor::Move => load(b"move\0"),
MouseCursor::AllScroll => load(b"all-scroll\0"),
MouseCursor::Cell => load(b"plus\0"),
MouseCursor::Crosshair => load(b"crosshair\0"),
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(b"right_side\0"),
MouseCursor::NResize => load(b"top_side\0"),
MouseCursor::NeResize => load(b"top_right_corner\0"),
MouseCursor::NwResize => load(b"top_left_corner\0"),
MouseCursor::SResize => load(b"bottom_side\0"),
MouseCursor::SeResize => load(b"bottom_right_corner\0"),
MouseCursor::SwResize => load(b"bottom_left_corner\0"),
MouseCursor::WResize => load(b"left_side\0"),
MouseCursor::EwResize => load(b"h_double_arrow\0"),
MouseCursor::NsResize => load(b"v_double_arrow\0"),
MouseCursor::NwseResize => loadn(&[b"bd_double_arrow\0", b"size_bdiag\0"]),
MouseCursor::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_fdiag\0"]),
MouseCursor::ColResize => loadn(&[b"split_h\0", b"h_double_arrow\0"]),
MouseCursor::RowResize => loadn(&[b"split_v\0", b"v_double_arrow\0"]),
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"])?,
};
cursor.or_else(|| load(b"left_ptr\0")).unwrap_or(0)
if let Some(cursor) = cursor {
Ok(cursor)
} else {
Ok(load("left_ptr")?.unwrap_or(x11rb::NONE))
}
}

303
src/x11/event_loop.rs Normal file
View file

@ -0,0 +1,303 @@
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)
//
// FIXME: This will need to be changed from just setting an atomic to somehow
// synchronizing with the window being closed (using a synchronous channel, or
// by joining on the event loop thread).
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);
self.handler.on_event(
&mut crate::Window::new(Window { inner: &self.window }),
Event::Mouse(MouseEvent::CursorMoved {
position: logical_pos,
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);
self.handler.on_event(
&mut crate::Window::new(Window { inner: &self.window }),
Event::Mouse(MouseEvent::CursorMoved {
position: logical_pos,
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),
}
}

View file

@ -18,7 +18,7 @@
//! X11 keyboard handling
use xcb::xproto;
use x11rb::protocol::xproto::{KeyButMask, KeyPressEvent, KeyReleaseEvent};
use keyboard_types::*;
@ -361,32 +361,32 @@ fn hardware_keycode_to_code(hw_keycode: u16) -> Code {
}
// Extracts the keyboard modifiers from, e.g., the `state` field of
// `xcb::xproto::ButtonPressEvent`
pub(super) fn key_mods(mods: u16) -> Modifiers {
// `x11rb::protocol::xproto::ButtonPressEvent`
pub(super) fn key_mods(mods: KeyButMask) -> Modifiers {
let mut ret = Modifiers::default();
let mut key_masks = [
(xproto::MOD_MASK_SHIFT, Modifiers::SHIFT),
(xproto::MOD_MASK_CONTROL, Modifiers::CONTROL),
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.
(xproto::MOD_MASK_1, Modifiers::ALT),
(xproto::MOD_MASK_2, Modifiers::NUM_LOCK),
(xproto::MOD_MASK_4, Modifiers::META),
(xproto::MOD_MASK_LOCK, Modifiers::CAPS_LOCK),
(KeyButMask::BUTTON1, Modifiers::ALT),
(KeyButMask::BUTTON2, Modifiers::NUM_LOCK),
(KeyButMask::BUTTON4, Modifiers::META),
(KeyButMask::LOCK, Modifiers::CAPS_LOCK),
];
for (mask, modifiers) in &mut key_masks {
if mods & (*mask as u16) != 0 {
for (mask, modifiers) in &key_masks {
if mods.contains(*mask) {
ret |= *modifiers;
}
}
ret
}
pub(super) fn convert_key_press_event(key_press: &xcb::KeyPressEvent) -> KeyboardEvent {
let hw_keycode = key_press.detail();
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 modifiers = key_mods(key_press.state);
let key = code_to_key(code, modifiers);
let location = code_to_location(code);
let state = KeyState::Down;
@ -394,10 +394,10 @@ pub(super) fn convert_key_press_event(key_press: &xcb::KeyPressEvent) -> Keyboar
KeyboardEvent { code, key, modifiers, location, state, repeat: false, is_composing: false }
}
pub(super) fn convert_key_release_event(key_release: &xcb::KeyReleaseEvent) -> KeyboardEvent {
let hw_keycode = key_release.detail();
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 modifiers = key_mods(key_release.state);
let key = code_to_key(code, modifiers);
let location = code_to_location(code);
let state = KeyState::Up;

View file

@ -5,4 +5,6 @@ mod window;
pub use window::*;
mod cursor;
mod event_loop;
mod keyboard;
mod visual_info;

94
src/x11/visual_info.rs Normal file
View 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
}

View file

@ -1,27 +1,33 @@
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;
use std::time::*;
use raw_window_handle::{
HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, XlibDisplayHandle,
XlibWindowHandle,
};
use xcb::ffi::xcb_screen_t;
use xcb::StructPtr;
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, MouseButton, MouseCursor, MouseEvent, PhyPoint, PhySize, ScrollDelta, Size, WindowEvent,
WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy,
Event, MouseCursor, Size, WindowEvent, WindowHandler, WindowInfo, WindowOpenOptions,
WindowScalePolicy,
};
use super::keyboard::{convert_key_press_event, convert_key_release_event, key_mods};
#[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>,
@ -57,7 +63,7 @@ unsafe impl HasRawWindowHandle for WindowHandle {
}
}
struct ParentHandle {
pub(crate) struct ParentHandle {
close_requested: Arc<AtomicBool>,
is_open: Arc<AtomicBool>,
}
@ -87,26 +93,21 @@ impl Drop for ParentHandle {
}
}
struct WindowInner {
xcb_connection: XcbConnection,
window_id: u32,
window_info: WindowInfo,
visual_id: u32,
mouse_cursor: MouseCursor,
pub(crate) struct WindowInner {
pub(crate) xcb_connection: XcbConnection,
window_id: XWindow,
pub(crate) window_info: WindowInfo,
visual_id: Visualid,
mouse_cursor: Cell<MouseCursor>,
frame_interval: Duration,
event_loop_running: bool,
close_requested: bool,
new_physical_size: Option<PhySize>,
parent_handle: Option<ParentHandle>,
pub(crate) close_requested: Cell<bool>,
#[cfg(feature = "opengl")]
gl_context: Option<GlContext>,
}
pub struct Window<'a> {
inner: &'a mut WindowInner,
pub(crate) inner: &'a WindowInner,
}
// Hack to allow sending a RawWindowHandle between threads. Do not make public
@ -136,7 +137,8 @@ impl<'a> Window<'a> {
let (parent_handle, mut window_handle) = ParentHandle::new();
thread::spawn(move || {
Self::window_thread(Some(parent_id), options, build, tx.clone(), Some(parent_handle));
Self::window_thread(Some(parent_id), options, build, tx.clone(), Some(parent_handle))
.unwrap();
});
let raw_window_handle = rx.recv().unwrap().unwrap();
@ -154,7 +156,7 @@ impl<'a> Window<'a> {
let (tx, rx) = mpsc::sync_channel::<WindowOpenResult>(1);
let thread = thread::spawn(move || {
Self::window_thread(None, options, build, tx, None);
Self::window_thread(None, options, build, tx, None).unwrap();
});
let _ = rx.recv().unwrap().unwrap();
@ -167,29 +169,26 @@ impl<'a> Window<'a> {
fn window_thread<H, B>(
parent: Option<u32>, options: WindowOpenOptions, build: B,
tx: mpsc::SyncSender<WindowOpenResult>, parent_handle: Option<ParentHandle>,
) where
) -> 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().unwrap();
let xcb_connection = XcbConnection::new()?;
// Get screen information (?)
let setup = xcb_connection.conn.get_setup();
let screen = setup.roots().nth(xcb_connection.xlib_display as usize).unwrap();
// Get screen information
let screen = xcb_connection.screen();
let parent_id = parent.unwrap_or(screen.root);
let foreground = xcb_connection.conn.generate_id();
let parent_id = parent.unwrap_or_else(|| screen.root());
xcb::create_gc(
&xcb_connection.conn,
foreground,
let gc_id = xcb_connection.conn.generate_id()?;
xcb_connection.conn.create_gc(
gc_id,
parent_id,
&[(xcb::GC_FOREGROUND, screen.black_pixel()), (xcb::GC_GRAPHICS_EXPOSURES, 0)],
);
&CreateGCAux::new().foreground(screen.black_pixel).graphics_exposures(0),
)?;
let scaling = match options.scale {
WindowScalePolicy::SystemScaleFactor => xcb_connection.get_scaling().unwrap_or(1.0),
@ -198,49 +197,16 @@ impl<'a> Window<'a> {
let window_info = WindowInfo::from_logical_size(options.size, scaling);
// Now it starts becoming fun. If we're creating an OpenGL context, then we need to create
// the window with a visual that matches the framebuffer used for the OpenGL context. So the
// idea is that we first retrieve a framebuffer config that matches our wanted OpenGL
// configuration, find the visual that matches that framebuffer config, create the window
// with that visual, and then finally create an OpenGL context for the window. If we don't
// use OpenGL, then we'll just take a random visual with a 32-bit depth.
let create_default_config = || {
Self::find_visual_for_depth(&screen, 32)
.map(|visual| (32, visual))
.unwrap_or((xcb::COPY_FROM_PARENT as u8, xcb::COPY_FROM_PARENT as u32))
};
#[cfg(feature = "opengl")]
let (fb_config, (depth, visual)) = match options.gl_config {
Some(gl_config) => unsafe {
platform::GlContext::get_fb_config_and_visual(
xcb_connection.conn.get_raw_dpy(),
gl_config,
)
.map(|(fb_config, window_config)| {
(Some(fb_config), (window_config.depth, window_config.visual))
})
.expect("Could not fetch framebuffer config")
},
None => (None, create_default_config()),
};
let visual_info =
WindowVisualConfig::find_best_visual_config_for_gl(&xcb_connection, options.gl_config)?;
#[cfg(not(feature = "opengl"))]
let (depth, visual) = create_default_config();
let visual_info = WindowVisualConfig::find_best_visual_config(&xcb_connection)?;
// For this 32-bith 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
let colormap = xcb_connection.conn.generate_id();
xcb::create_colormap(
&xcb_connection.conn,
xcb::COLORMAP_ALLOC_NONE as u8,
colormap,
screen.root(),
visual,
);
let window_id = xcb_connection.conn.generate_id();
xcb::create_window_checked(
&xcb_connection.conn,
depth,
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
@ -248,66 +214,56 @@ impl<'a> Window<'a> {
window_info.physical_size().width as u16, // window width
window_info.physical_size().height as u16, // window height
0, // window border
xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
visual,
&[
(
xcb::CW_EVENT_MASK,
xcb::EVENT_MASK_EXPOSURE
| xcb::EVENT_MASK_POINTER_MOTION
| xcb::EVENT_MASK_BUTTON_PRESS
| xcb::EVENT_MASK_BUTTON_RELEASE
| xcb::EVENT_MASK_KEY_PRESS
| xcb::EVENT_MASK_KEY_RELEASE
| xcb::EVENT_MASK_STRUCTURE_NOTIFY
| xcb::EVENT_MASK_ENTER_WINDOW
| xcb::EVENT_MASK_LEAVE_WINDOW,
),
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
(xcb::CW_COLORMAP, colormap),
(xcb::CW_BORDER_PIXEL, 0),
],
)
.request_check()
.unwrap();
xcb::map_window(&xcb_connection.conn, window_id);
.colormap(visual_info.color_map)
.border_pixel(0),
)?;
xcb_connection.conn.map_window(window_id)?;
// Change window title
let title = options.title;
xcb::change_property(
&xcb_connection.conn,
xcb::PROP_MODE_REPLACE as u8,
xcb_connection.conn.change_property8(
PropMode::REPLACE,
window_id,
xcb::ATOM_WM_NAME,
xcb::ATOM_STRING,
8, // view data as 8-bit
AtomEnum::WM_NAME,
AtomEnum::STRING,
title.as_bytes(),
);
)?;
if let Some((wm_protocols, wm_delete_window)) =
xcb_connection.atoms.wm_protocols.zip(xcb_connection.atoms.wm_delete_window)
{
xcb_util::icccm::set_wm_protocols(
&xcb_connection.conn,
window_id,
wm_protocols,
&[wm_delete_window],
);
}
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();
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 = fb_config.map(|fb_config| {
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.conn.get_raw_dpy();
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) }
@ -319,15 +275,10 @@ impl<'a> Window<'a> {
xcb_connection,
window_id,
window_info,
visual_id: visual,
mouse_cursor: MouseCursor::default(),
visual_id: visual_info.visual_id,
mouse_cursor: Cell::new(MouseCursor::default()),
frame_interval: Duration::from_millis(15),
event_loop_running: false,
close_requested: false,
new_physical_size: None,
parent_handle,
close_requested: Cell::new(false),
#[cfg(feature = "opengl")]
gl_context,
@ -343,46 +294,52 @@ impl<'a> Window<'a> {
let _ = tx.send(Ok(SendableRwh(window.raw_window_handle())));
inner.run_event_loop(&mut handler);
EventLoop::new(inner, handler, parent_handle).run()?;
Ok(())
}
pub fn set_mouse_cursor(&mut self, mouse_cursor: MouseCursor) {
if self.inner.mouse_cursor == mouse_cursor {
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_xid(mouse_cursor);
let xid = self.inner.xcb_connection.get_cursor(mouse_cursor).unwrap();
if xid != 0 {
xcb::change_window_attributes(
&self.inner.xcb_connection.conn,
let _ = self.inner.xcb_connection.conn.change_window_attributes(
self.inner.window_id,
&[(xcb::CW_CURSOR, xid)],
&ChangeWindowAttributesAux::new().cursor(xid),
);
self.inner.xcb_connection.conn.flush();
let _ = self.inner.xcb_connection.conn.flush();
}
self.inner.mouse_cursor = mouse_cursor;
self.inner.mouse_cursor.set(mouse_cursor);
}
pub fn close(&mut self) {
self.inner.close_requested = true;
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);
xcb::configure_window(
&self.inner.xcb_connection.conn,
let _ = self.inner.xcb_connection.conn.configure_window(
self.inner.window_id,
&[
(xcb::CONFIG_WINDOW_WIDTH as u16, new_window_info.physical_size().width),
(xcb::CONFIG_WINDOW_HEIGHT as u16, new_window_info.physical_size().height),
],
&ConfigureWindowAux::new()
.width(new_window_info.physical_size().width)
.height(new_window_info.physical_size().height),
);
self.inner.xcb_connection.conn.flush();
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
@ -392,311 +349,6 @@ impl<'a> Window<'a> {
pub fn gl_context(&self) -> Option<&crate::gl::GlContext> {
self.inner.gl_context.as_ref()
}
fn find_visual_for_depth(screen: &StructPtr<xcb_screen_t>, depth: u8) -> Option<u32> {
for candidate_depth in screen.allowed_depths() {
if candidate_depth.depth() != depth {
continue;
}
for candidate_visual in candidate_depth.visuals() {
if candidate_visual.class() == xcb::VISUAL_CLASS_TRUE_COLOR as u8 {
return Some(candidate_visual.visual_id());
}
}
}
None
}
}
impl WindowInner {
#[inline]
fn drain_xcb_events(&mut self, handler: &mut dyn WindowHandler) {
// 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.xcb_connection.conn.poll_for_event() {
self.handle_xcb_event(handler, event);
}
if let Some(size) = self.new_physical_size.take() {
self.window_info = WindowInfo::from_physical_size(size, self.window_info.scale());
let window_info = self.window_info;
handler.on_event(
&mut crate::Window::new(Window { inner: self }),
Event::Window(WindowEvent::Resized(window_info)),
);
}
}
// 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.
fn run_event_loop(&mut self, handler: &mut dyn WindowHandler) {
use nix::poll::*;
let xcb_fd = unsafe {
let raw_conn = self.xcb_connection.conn.get_raw_conn();
xcb::ffi::xcb_get_file_descriptor(raw_conn)
};
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 {
handler.on_frame(&mut crate::Window::new(Window { inner: self }));
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(handler);
// 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(handler);
}
}
// Check if the parents's handle was dropped (such as when the host
// requested the window to close)
//
// FIXME: This will need to be changed from just setting an atomic to somehow
// synchronizing with the window being closed (using a synchronous channel, or
// by joining on the event loop thread).
if let Some(parent_handle) = &self.parent_handle {
if parent_handle.parent_did_drop() {
self.handle_must_close(handler);
self.close_requested = false;
}
}
// Check if the user has requested the window to close
if self.close_requested {
self.handle_must_close(handler);
self.close_requested = false;
}
}
}
fn handle_close_requested(&mut self, handler: &mut dyn WindowHandler) {
handler.on_event(
&mut crate::Window::new(Window { inner: self }),
Event::Window(WindowEvent::WillClose),
);
// FIXME: handler should decide whether window stays open or not
self.event_loop_running = false;
}
fn handle_must_close(&mut self, handler: &mut dyn WindowHandler) {
handler.on_event(
&mut crate::Window::new(Window { inner: self }),
Event::Window(WindowEvent::WillClose),
);
self.event_loop_running = false;
}
fn handle_xcb_event(&mut self, handler: &mut dyn WindowHandler, event: xcb::GenericEvent) {
let event_type = event.response_type() & !0x80;
// For all of 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_type {
////
// window
////
xcb::CLIENT_MESSAGE => {
let event = unsafe { xcb::cast_event::<xcb::ClientMessageEvent>(&event) };
// what an absolute tragedy this all is
let data = event.data().data;
let (_, data32, _) = unsafe { data.align_to::<u32>() };
let wm_delete_window =
self.xcb_connection.atoms.wm_delete_window.unwrap_or(xcb::NONE);
if wm_delete_window == data32[0] {
self.handle_close_requested(handler);
}
}
xcb::CONFIGURE_NOTIFY => {
let event = unsafe { xcb::cast_event::<xcb::ConfigureNotifyEvent>(&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_info.physical_size()
{
self.new_physical_size = Some(new_physical_size);
}
}
////
// mouse
////
xcb::MOTION_NOTIFY => {
let event = unsafe { xcb::cast_event::<xcb::MotionNotifyEvent>(&event) };
let detail = event.detail();
if detail != 4 && detail != 5 {
let physical_pos =
PhyPoint::new(event.event_x() as i32, event.event_y() as i32);
let logical_pos = physical_pos.to_logical(&self.window_info);
handler.on_event(
&mut crate::Window::new(Window { inner: self }),
Event::Mouse(MouseEvent::CursorMoved {
position: logical_pos,
modifiers: key_mods(event.state()),
}),
);
}
}
xcb::ENTER_NOTIFY => {
handler.on_event(
&mut crate::Window::new(Window { inner: self }),
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 event = unsafe { xcb::cast_event::<xcb::EnterNotifyEvent>(&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_info);
handler.on_event(
&mut crate::Window::new(Window { inner: self }),
Event::Mouse(MouseEvent::CursorMoved {
position: logical_pos,
modifiers: key_mods(event.state()),
}),
);
}
xcb::LEAVE_NOTIFY => {
handler.on_event(
&mut crate::Window::new(Window { inner: self }),
Event::Mouse(MouseEvent::CursorLeft),
);
}
xcb::BUTTON_PRESS => {
let event = unsafe { xcb::cast_event::<xcb::ButtonPressEvent>(&event) };
let detail = event.detail();
match detail {
4..=7 => {
handler.on_event(
&mut crate::Window::new(Window { inner: self }),
Event::Mouse(MouseEvent::WheelScrolled {
delta: match 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);
handler.on_event(
&mut crate::Window::new(Window { inner: self }),
Event::Mouse(MouseEvent::ButtonPressed {
button: button_id,
modifiers: key_mods(event.state()),
}),
);
}
}
}
xcb::BUTTON_RELEASE => {
let event = unsafe { xcb::cast_event::<xcb::ButtonPressEvent>(&event) };
let detail = event.detail();
if !(4..=7).contains(&detail) {
let button_id = mouse_id(detail);
handler.on_event(
&mut crate::Window::new(Window { inner: self }),
Event::Mouse(MouseEvent::ButtonReleased {
button: button_id,
modifiers: key_mods(event.state()),
}),
);
}
}
////
// keys
////
xcb::KEY_PRESS => {
let event = unsafe { xcb::cast_event::<xcb::KeyPressEvent>(&event) };
handler.on_event(
&mut crate::Window::new(Window { inner: self }),
Event::Keyboard(convert_key_press_event(event)),
);
}
xcb::KEY_RELEASE => {
let event = unsafe { xcb::cast_event::<xcb::KeyReleaseEvent>(&event) };
handler.on_event(
&mut crate::Window::new(Window { inner: self }),
Event::Keyboard(convert_key_release_event(event)),
);
}
_ => {}
}
}
}
unsafe impl<'a> HasRawWindowHandle for Window<'a> {
@ -712,7 +364,7 @@ unsafe impl<'a> HasRawWindowHandle for Window<'a> {
unsafe impl<'a> HasRawDisplayHandle for Window<'a> {
fn raw_display_handle(&self) -> RawDisplayHandle {
let display = self.inner.xcb_connection.conn.get_raw_dpy();
let display = self.inner.xcb_connection.dpy;
let mut handle = XlibDisplayHandle::empty();
handle.display = display as *mut c_void;
@ -722,17 +374,6 @@ unsafe impl<'a> HasRawDisplayHandle for Window<'a> {
}
}
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),
}
}
pub fn copy_to_clipboard(_data: &str) {
todo!()
}

View file

@ -1,117 +1,83 @@
use std::collections::HashMap;
/// 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.
use std::ffi::{CStr, CString};
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;
pub(crate) struct Atoms {
pub wm_protocols: Option<u32>,
pub wm_delete_window: Option<u32>,
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 conn: xcb::Connection,
pub xlib_display: i32,
pub(crate) dpy: *mut Display,
pub(crate) conn: XCBConnection,
pub(crate) screen: usize,
pub(crate) atoms: Atoms,
pub(super) cursor_cache: HashMap<MouseCursor, u32>,
}
macro_rules! intern_atoms {
($conn:expr, $( $name:ident ),+ ) => {{
$(
#[allow(non_snake_case)]
let $name = xcb::intern_atom($conn, true, stringify!($name));
)+
// splitting request and reply to improve throughput
(
$( $name.get_reply()
.map(|r| r.atom())
.ok()),+
)
}};
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, xcb::base::ConnError> {
let (conn, xlib_display) = xcb::Connection::connect_with_xlib_display()?;
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)
};
conn.set_event_queue_owner(xcb::base::EventQueueOwner::Xcb);
let (wm_protocols, wm_delete_window) = intern_atoms!(&conn, WM_PROTOCOLS, WM_DELETE_WINDOW);
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,
xlib_display,
atoms: Atoms { wm_protocols, wm_delete_window },
cursor_cache: HashMap::new(),
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) -> Option<f64> {
use x11::xlib::{
XResourceManagerString, XrmDestroyDatabase, XrmGetResource, XrmGetStringDatabase,
XrmValue,
};
let display = self.conn.get_raw_dpy();
unsafe {
let rms = XResourceManagerString(display);
if !rms.is_null() {
let db = XrmGetStringDatabase(rms);
if !db.is_null() {
let mut value = XrmValue { size: 0, addr: std::ptr::null_mut() };
let mut value_type: *mut std::os::raw::c_char = std::ptr::null_mut();
let name_c_str = CString::new("Xft.dpi").unwrap();
let c_str = CString::new("Xft.Dpi").unwrap();
let dpi = if XrmGetResource(
db,
name_c_str.as_ptr(),
c_str.as_ptr(),
&mut value_type,
&mut value,
) != 0
&& !value.addr.is_null()
{
let value_addr: &CStr = CStr::from_ptr(value.addr);
value_addr.to_str().ok();
let value_str = value_addr.to_str().ok()?;
let value_f64: f64 = value_str.parse().ok()?;
let dpi_to_scale = value_f64 / 96.0;
Some(dpi_to_scale)
} else {
None
};
XrmDestroyDatabase(db);
return dpi;
}
}
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)
}
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) -> Option<f64> {
fn get_scaling_screen_dimensions(&self) -> f64 {
// Figure out screen information
let setup = self.conn.get_setup();
let screen = setup.roots().nth(self.xlib_display as usize).unwrap();
let screen = self.screen();
// Get the DPI from the screen struct
//
@ -119,28 +85,48 @@ impl XcbConnection {
// 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 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;
let yscale = yres / 96.0;
// TODO: choose between `xres` and `yres`? (probably both are the same?)
Some(yscale)
yres / 96.0
}
#[inline]
pub fn get_scaling(&self) -> Option<f64> {
self.get_scaling_xft().or_else(|| self.get_scaling_screen_dimensions())
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_xid(&mut self, cursor: MouseCursor) -> u32 {
let dpy = self.conn.get_raw_dpy();
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();
*self.cursor_cache.entry(cursor).or_insert_with(|| cursor::get_xcursor(dpy, cursor))
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);
}
}
}