1
0
Fork 0

Compare commits

..

4 commits

25 changed files with 780 additions and 1404 deletions

View file

@ -1,37 +1,29 @@
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@v4
- uses: actions/checkout@v2
- 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: dtolnay/rust-toolchain@stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
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
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
- name: Run tests
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
run: cargo test --examples --workspace --all-features --verbose

View file

@ -23,8 +23,9 @@ keyboard-types = { version = "0.6.1", default-features = false }
raw-window-handle = "0.6"
[target.'cfg(target_os="linux")'.dependencies]
x11rb = { version = "0.13.0", features = ["cursor", "resource_manager", "allow-unsafe-code"] }
x11 = { version = "2.21", features = ["xlib", "xlib_xcb"] }
xcb = { version = "0.9", features = ["thread", "xlib_xcb", "dri2"] }
x11 = { version = "2.18", features = ["xlib", "xcursor"] }
xcb-util = { version = "0.3", features = ["icccm"] }
nix = "0.22.0"
[target.'cfg(target_os="windows")'.dependencies]
@ -52,20 +53,3 @@ 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 libxcb1-dev libx11-xcb-dev libgl1-mesa-dev
sudo apt-get install libx11-dev libxcursor-dev libxcb-dri2-0-dev libxcb-icccm4-dev libx11-xcb-dev
```
## License

View file

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

View file

@ -1,141 +0,0 @@
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,13 +1,10 @@
use std::num::NonZeroU32;
use std::time::Duration;
use rtrb::{Consumer, RingBuffer};
#[cfg(target_os = "macos")]
use baseview::{copy_to_clipboard, MouseEvent};
use baseview::{
Event, EventStatus, PhySize, Window, WindowEvent, WindowHandler, WindowScalePolicy,
};
use baseview::copy_to_clipboard;
use baseview::{Event, EventStatus, MouseEvent, Window, WindowHandler, WindowScalePolicy};
#[derive(Debug, Clone)]
enum Message {
@ -16,48 +13,32 @@ 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 {
match event {
Event::Mouse(e) => {
println!("Mouse event: {:?}", e);
#[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;
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;
match e {
MouseEvent::ButtonPressed { .. } => {
copy_to_clipboard(&"This is a test!")
}
_ => (),
}
}
_ => {}
Event::Keyboard(e) => println!("Keyboard event: {:?}", e),
Event::Window(e) => println!("Window event: {:?}", e),
}
log_event(&event);
EventStatus::Captured
}
}
@ -75,33 +56,13 @@ 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 tx.push(Message::Hello).is_err() {
if let Err(_) = tx.push(Message::Hello) {
println!("Failed sending message");
}
});
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),
}
Window::open_blocking(window_open_options, |_| OpenWindowExample { rx });
}

View file

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

View file

@ -1,115 +0,0 @@
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

@ -117,8 +117,10 @@ impl GlContext {
let framework_name = CFString::from_str("com.apple.opengl").unwrap();
let framework =
unsafe { CFBundleGetBundleWithIdentifier(framework_name.as_concrete_TypeRef()) };
unsafe { CFBundleGetFunctionPointerForName(framework, symbol_name.as_concrete_TypeRef()) }
let addr = unsafe {
CFBundleGetFunctionPointerForName(framework, symbol_name.as_concrete_TypeRef())
};
addr as *const c_void
}
pub fn swap_buffers(&self) {

View file

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

View file

@ -1,27 +1,25 @@
use std::ffi::CStr;
use std::fmt::{Debug, Display, Formatter};
use std::fmt::{Debug, 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 afterward.
static CURRENT_X11_ERROR: RefCell<Option<XLibError>> = const { RefCell::new(None) };
/// error can then be converted to a regular Rust Result value afterwards.
static CURRENT_X11_ERROR: RefCell<Option<xlib::XErrorEvent>> = RefCell::new(None);
}
/// A helper struct for safe X11 error handling
pub struct XErrorHandler<'a> {
display: *mut xlib::Display,
error: &'a RefCell<Option<XLibError>>,
error: &'a RefCell<Option<xlib::XErrorEvent>>,
}
impl<'a> XErrorHandler<'a> {
/// Syncs and checks if any previous X11 calls from the given display returned an error
/// Syncs and checks if any previous X11 calls returned an error
pub fn check(&mut self) -> Result<(), XLibError> {
// Flush all possible previous errors
unsafe {
@ -31,27 +29,20 @@ impl<'a> XErrorHandler<'a> {
match error {
None => Ok(()),
Some(inner) => Err(inner),
Some(inner) => Err(XLibError { 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.
///
/// # 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>(
/// that closure to check on the latest X11 error at any time
pub 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 access
let err = &*err;
// SAFETY: the error pointer should be safe to copy
let err = *err;
CURRENT_X11_ERROR.with(|error| {
let mut error = error.borrow_mut();
@ -60,7 +51,7 @@ impl<'a> XErrorHandler<'a> {
// cause of the other errors
Some(_) => 1,
None => {
*error = Some(XLibError::from_event(err));
*error = Some(err);
0
}
}
@ -74,9 +65,7 @@ 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;
}
let old_handler = unsafe { xlib::XSetErrorHandler(Some(error_handler)) };
let panic_result = std::panic::catch_unwind(AssertUnwindSafe(|| {
@ -95,41 +84,15 @@ impl<'a> XErrorHandler<'a> {
}
pub struct XLibError {
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>,
inner: xlib::XErrorEvent,
}
impl XLibError {
/// # 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];
pub fn get_display_name(&self, buf: &mut [u8]) -> &CStr {
unsafe {
xlib::XGetErrorText(
error.display,
error.error_code.into(),
self.inner.display,
self.inner.error_code.into(),
buf.as_mut_ptr().cast(),
(buf.len() - 1) as i32,
);
@ -137,30 +100,23 @@ 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
let cstr = unsafe { CStr::from_ptr(buf.as_mut_ptr().cast()) };
cstr.to_string_lossy().into()
unsafe { CStr::from_ptr(buf.as_mut_ptr().cast()) }
}
}
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.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)
.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)
.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,12 +29,6 @@ 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)]
@ -100,18 +94,6 @@ 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();
@ -121,9 +103,6 @@ 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])
@ -145,14 +124,6 @@ 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),
@ -206,10 +177,6 @@ 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);
@ -241,25 +208,6 @@ 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) };
@ -523,30 +471,3 @@ 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,14 +7,15 @@ use cocoa::appkit::{
NSApp, NSApplication, NSApplicationActivationPolicyRegular, NSBackingStoreBuffered,
NSPasteboard, NSView, NSWindow, NSWindowStyleMask,
};
use cocoa::base::{id, nil, BOOL, NO, YES};
use cocoa::base::{id, nil, 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,
@ -82,11 +83,6 @@ 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
@ -281,30 +277,6 @@ 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

View file

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

View file

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

View file

@ -1,20 +1,19 @@
use winapi::shared::guiddef::GUID;
use winapi::shared::minwindef::{ATOM, FALSE, LOWORD, LPARAM, LRESULT, UINT, WPARAM};
use winapi::shared::minwindef::{ATOM, FALSE, 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, 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,
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,
WS_CLIPSIBLINGS, WS_MAXIMIZEBOX, WS_MINIMIZEBOX, WS_POPUPWINDOW, WS_SIZEBOX, WS_VISIBLE,
XBUTTON1, XBUTTON2,
};
@ -39,7 +38,6 @@ use crate::{
WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy,
};
use super::cursor::cursor_to_lpcwstr;
use super::drop_target::DropTarget;
use super::keyboard::KeyboardState;
@ -170,52 +168,21 @@ 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 move_event = Event::Mouse(MouseEvent::CursorMoved {
let 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 => {
@ -428,24 +395,6 @@ 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 => {
@ -497,8 +446,6 @@ 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>>>,
@ -533,14 +480,17 @@ 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) => {
// `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);
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
};
// If the window is a standalone window then the size needs to include the window
// decorations
@ -705,8 +655,6 @@ 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),
@ -794,17 +742,6 @@ 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
@ -812,12 +749,8 @@ impl Window<'_> {
self.state.deferred_tasks.borrow_mut().push_back(task);
}
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);
}
pub fn set_mouse_cursor(&mut self, _mouse_cursor: MouseCursor) {
todo!()
}
#[cfg(feature = "opengl")]

View file

@ -24,7 +24,7 @@ pub struct WindowHandle {
impl WindowHandle {
fn new(window_handle: platform::WindowHandle) -> Self {
Self { window_handle, phantom: PhantomData }
Self { window_handle, phantom: PhantomData::default() }
}
/// Close the window
@ -103,14 +103,6 @@ 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")]

View file

@ -1,100 +1,105 @@
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 std::os::raw::c_char;
use crate::MouseCursor;
fn create_empty_cursor(conn: &XCBConnection, screen: usize) -> Result<Cursor, Box<dyn Error>> {
let cursor_id = conn.generate_id()?;
let pixmap_id = conn.generate_id()?;
let root_window = conn.setup().roots[screen].root;
conn.create_pixmap(1, pixmap_id, root_window, 1, 1)?;
conn.create_cursor(cursor_id, pixmap_id, pixmap_id, 0, 0, 0, 0, 0, 0, 0, 0)?;
conn.free_pixmap(pixmap_id)?;
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)
};
Ok(cursor_id)
if pixmap == 0 {
return 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))
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)
}
}
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
} else {
Ok(None)
Some(xcursor as u32)
}
}
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);
}
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)
}
Ok(None)
}
pub(super) fn get_xcursor(
conn: &XCBConnection, screen: usize, cursor_handle: &CursorHandle, cursor: MouseCursor,
) -> Result<Cursor, Box<dyn Error>> {
let load = |name: &str| load_cursor(conn, cursor_handle, name);
let loadn = |names: &[&str]| load_first_existing_cursor(conn, cursor_handle, names);
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);
let cursor = match cursor {
MouseCursor::Default => None, // catch this in the fallback case below
MouseCursor::Hand => loadn(&["hand2", "hand1"])?,
MouseCursor::HandGrabbing => loadn(&["closedhand", "grabbing"])?,
MouseCursor::Help => load("question_arrow")?,
MouseCursor::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::Hidden => Some(create_empty_cursor(conn, screen)?),
MouseCursor::Hidden => create_empty_cursor(display),
MouseCursor::Text => loadn(&["text", "xterm"])?,
MouseCursor::VerticalText => load("vertical-text")?,
MouseCursor::Text => loadn(&[b"text\0", b"xterm\0"]),
MouseCursor::VerticalText => load(b"vertical-text\0"),
MouseCursor::Working => load("watch")?,
MouseCursor::PtrWorking => load("left_ptr_watch")?,
MouseCursor::Working => load(b"watch\0"),
MouseCursor::PtrWorking => load(b"left_ptr_watch\0"),
MouseCursor::NotAllowed => load("crossed_circle")?,
MouseCursor::PtrNotAllowed => loadn(&["no-drop", "crossed_circle"])?,
MouseCursor::NotAllowed => load(b"crossed_circle\0"),
MouseCursor::PtrNotAllowed => loadn(&[b"no-drop\0", b"crossed_circle\0"]),
MouseCursor::ZoomIn => load("zoom-in")?,
MouseCursor::ZoomOut => load("zoom-out")?,
MouseCursor::ZoomIn => load(b"zoom-in\0"),
MouseCursor::ZoomOut => load(b"zoom-out\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::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::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"])?,
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"]),
};
if let Some(cursor) = cursor {
Ok(cursor)
} else {
Ok(load("left_ptr")?.unwrap_or(x11rb::NONE))
}
cursor.or_else(|| load(b"left_ptr\0")).unwrap_or(0)
}

View file

@ -1,303 +0,0 @@
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 x11rb::protocol::xproto::{KeyButMask, KeyPressEvent, KeyReleaseEvent};
use xcb::xproto;
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
// `x11rb::protocol::xproto::ButtonPressEvent`
pub(super) fn key_mods(mods: KeyButMask) -> Modifiers {
// `xcb::xproto::ButtonPressEvent`
pub(super) fn key_mods(mods: u16) -> Modifiers {
let mut ret = Modifiers::default();
let key_masks = [
(KeyButMask::SHIFT, Modifiers::SHIFT),
(KeyButMask::CONTROL, Modifiers::CONTROL),
let mut key_masks = [
(xproto::MOD_MASK_SHIFT, Modifiers::SHIFT),
(xproto::MOD_MASK_CONTROL, Modifiers::CONTROL),
// X11's mod keys are configurable, but this seems
// like a reasonable default for US keyboards, at least,
// where the "windows" key seems to be MOD_MASK_4.
(KeyButMask::BUTTON1, Modifiers::ALT),
(KeyButMask::BUTTON2, Modifiers::NUM_LOCK),
(KeyButMask::BUTTON4, Modifiers::META),
(KeyButMask::LOCK, Modifiers::CAPS_LOCK),
(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),
];
for (mask, modifiers) in &key_masks {
if mods.contains(*mask) {
for (mask, modifiers) in &mut key_masks {
if mods & (*mask as u16) != 0 {
ret |= *modifiers;
}
}
ret
}
pub(super) fn convert_key_press_event(key_press: &KeyPressEvent) -> KeyboardEvent {
let hw_keycode = key_press.detail;
pub(super) fn convert_key_press_event(key_press: &xcb::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: &KeyPressEvent) -> KeyboardEven
KeyboardEvent { code, key, modifiers, location, state, repeat: false, is_composing: false }
}
pub(super) fn convert_key_release_event(key_release: &KeyReleaseEvent) -> KeyboardEvent {
let hw_keycode = key_release.detail;
pub(super) fn convert_key_release_event(key_release: &xcb::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,6 +5,4 @@ mod window;
pub use window::*;
mod cursor;
mod event_loop;
mod keyboard;
mod visual_info;

View file

@ -1,94 +0,0 @@
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,33 +1,27 @@
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 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 xcb::ffi::xcb_screen_t;
use xcb::StructPtr;
use super::XcbConnection;
use crate::{
Event, MouseCursor, Size, WindowEvent, WindowHandler, WindowInfo, WindowOpenOptions,
WindowScalePolicy,
Event, MouseButton, MouseCursor, MouseEvent, PhyPoint, PhySize, ScrollDelta, 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>,
@ -52,18 +46,17 @@ impl WindowHandle {
}
unsafe impl HasRawWindowHandle for WindowHandle {
fn raw_window_handle(&self) -> RawWindowHandle {
fn raw_window_handle(&self) -> Result<RawWindowHandle, raw_window_handle::HandleError> {
if let Some(raw_window_handle) = self.raw_window_handle {
if self.is_open.load(Ordering::Relaxed) {
return raw_window_handle;
return Ok(raw_window_handle);
}
}
Err(raw_window_handle::HandleError::Unavailable)
}
}
RawWindowHandle::Xlib(XlibWindowHandle::empty())
}
}
pub(crate) struct ParentHandle {
struct ParentHandle {
close_requested: Arc<AtomicBool>,
is_open: Arc<AtomicBool>,
}
@ -93,21 +86,26 @@ impl Drop for ParentHandle {
}
}
pub(crate) struct WindowInner {
pub(crate) xcb_connection: XcbConnection,
window_id: XWindow,
pub(crate) window_info: WindowInfo,
visual_id: Visualid,
mouse_cursor: Cell<MouseCursor>,
struct WindowInner {
xcb_connection: XcbConnection,
window_id: u32,
window_info: WindowInfo,
visual_id: u32,
mouse_cursor: MouseCursor,
pub(crate) close_requested: Cell<bool>,
frame_interval: Duration,
event_loop_running: bool,
close_requested: bool,
new_physical_size: Option<PhySize>,
parent_handle: Option<ParentHandle>,
#[cfg(feature = "opengl")]
gl_context: Option<GlContext>,
}
pub struct Window<'a> {
pub(crate) inner: &'a WindowInner,
inner: &'a mut WindowInner,
}
// Hack to allow sending a RawWindowHandle between threads. Do not make public
@ -127,8 +125,8 @@ impl<'a> Window<'a> {
{
// Convert parent into something that X understands
let parent_id = match parent.raw_window_handle() {
RawWindowHandle::Xlib(h) => h.window as u32,
RawWindowHandle::Xcb(h) => h.window,
Ok(RawWindowHandle::Xlib(h)) => h.window as u32,
Ok(RawWindowHandle::Xcb(h)) => h.window.get(),
h => panic!("unsupported parent handle type {:?}", h),
};
@ -137,8 +135,7 @@ 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))
.unwrap();
Self::window_thread(Some(parent_id), options, build, tx.clone(), Some(parent_handle));
});
let raw_window_handle = rx.recv().unwrap().unwrap();
@ -156,7 +153,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).unwrap();
Self::window_thread(None, options, build, tx, None);
});
let _ = rx.recv().unwrap().unwrap();
@ -169,26 +166,29 @@ impl<'a> Window<'a> {
fn window_thread<H, B>(
parent: Option<u32>, options: WindowOpenOptions, build: B,
tx: mpsc::SyncSender<WindowOpenResult>, parent_handle: Option<ParentHandle>,
) -> Result<(), Box<dyn Error>>
where
) 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()?;
let xcb_connection = XcbConnection::new().unwrap();
// Get screen information
let screen = xcb_connection.screen();
let parent_id = parent.unwrap_or(screen.root);
// Get screen information (?)
let setup = xcb_connection.conn.get_setup();
let screen = setup.roots().nth(xcb_connection.xlib_display as usize).unwrap();
let gc_id = xcb_connection.conn.generate_id()?;
xcb_connection.conn.create_gc(
gc_id,
let foreground = xcb_connection.conn.generate_id();
let parent_id = parent.unwrap_or_else(|| screen.root());
xcb::create_gc(
&xcb_connection.conn,
foreground,
parent_id,
&CreateGCAux::new().foreground(screen.black_pixel).graphics_exposures(0),
)?;
&[(xcb::GC_FOREGROUND, screen.black_pixel()), (xcb::GC_GRAPHICS_EXPOSURES, 0)],
);
let scaling = match options.scale {
WindowScalePolicy::SystemScaleFactor => xcb_connection.get_scaling().unwrap_or(1.0),
@ -197,16 +197,49 @@ 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 visual_info =
WindowVisualConfig::find_best_visual_config_for_gl(&xcb_connection, options.gl_config)?;
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()),
};
#[cfg(not(feature = "opengl"))]
let visual_info = WindowVisualConfig::find_best_visual_config(&xcb_connection)?;
let (depth, visual) = create_default_config();
let window_id = xcb_connection.conn.generate_id()?;
xcb_connection.conn.create_window(
visual_info.visual_depth,
// 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,
window_id,
parent_id,
0, // x coordinate of the new window
@ -214,56 +247,66 @@ 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
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,
)
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,
),
// As mentioned above, these two values are needed to be able to create a window
// with a depth of 32-bits when the parent window has a different depth
.colormap(visual_info.color_map)
.border_pixel(0),
)?;
xcb_connection.conn.map_window(window_id)?;
(xcb::CW_COLORMAP, colormap),
(xcb::CW_BORDER_PIXEL, 0),
],
)
.request_check()
.unwrap();
xcb::map_window(&xcb_connection.conn, window_id);
// Change window title
let title = options.title;
xcb_connection.conn.change_property8(
PropMode::REPLACE,
xcb::change_property(
&xcb_connection.conn,
xcb::PROP_MODE_REPLACE as u8,
window_id,
AtomEnum::WM_NAME,
AtomEnum::STRING,
xcb::ATOM_WM_NAME,
xcb::ATOM_STRING,
8, // view data as 8-bit
title.as_bytes(),
)?;
);
xcb_connection.conn.change_property32(
PropMode::REPLACE,
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,
xcb_connection.atoms.WM_PROTOCOLS,
AtomEnum::ATOM,
&[xcb_connection.atoms.WM_DELETE_WINDOW],
)?;
wm_protocols,
&[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 = visual_info.fb_config.map(|fb_config| {
let gl_context = fb_config.map(|fb_config| {
use std::ffi::c_ulong;
let window = window_id as c_ulong;
let display = xcb_connection.dpy;
let display = xcb_connection.conn.get_raw_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) }
@ -275,10 +318,15 @@ impl<'a> Window<'a> {
xcb_connection,
window_id,
window_info,
visual_id: visual_info.visual_id,
mouse_cursor: Cell::new(MouseCursor::default()),
visual_id: visual,
mouse_cursor: MouseCursor::default(),
close_requested: Cell::new(false),
frame_interval: Duration::from_millis(15),
event_loop_running: false,
close_requested: false,
new_physical_size: None,
parent_handle,
#[cfg(feature = "opengl")]
gl_context,
@ -292,54 +340,50 @@ impl<'a> Window<'a> {
// the correct dpi scaling.
handler.on_event(&mut window, Event::Window(WindowEvent::Resized(window_info)));
let _ = tx.send(Ok(SendableRwh(window.raw_window_handle())));
EventLoop::new(inner, handler, parent_handle).run()?;
Ok(())
if let Ok(rwh) = window.raw_window_handle() {
let _ = tx.send(Ok(SendableRwh(rwh)));
}
pub fn set_mouse_cursor(&self, mouse_cursor: MouseCursor) {
if self.inner.mouse_cursor.get() == mouse_cursor {
inner.run_event_loop(&mut handler);
}
pub fn set_mouse_cursor(&mut self, mouse_cursor: MouseCursor) {
if self.inner.mouse_cursor == mouse_cursor {
return;
}
let xid = self.inner.xcb_connection.get_cursor(mouse_cursor).unwrap();
let xid = self.inner.xcb_connection.get_cursor_xid(mouse_cursor);
if xid != 0 {
let _ = self.inner.xcb_connection.conn.change_window_attributes(
xcb::change_window_attributes(
&self.inner.xcb_connection.conn,
self.inner.window_id,
&ChangeWindowAttributesAux::new().cursor(xid),
&[(xcb::CW_CURSOR, xid)],
);
let _ = self.inner.xcb_connection.conn.flush();
self.inner.xcb_connection.conn.flush();
}
self.inner.mouse_cursor.set(mouse_cursor);
self.inner.mouse_cursor = mouse_cursor;
}
pub fn close(&mut self) {
self.inner.close_requested.set(true);
}
pub fn has_focus(&mut self) -> bool {
unimplemented!()
}
pub fn focus(&mut self) {
unimplemented!()
self.inner.close_requested = true;
}
pub fn resize(&mut self, size: Size) {
let scaling = self.inner.window_info.scale();
let new_window_info = WindowInfo::from_logical_size(size, scaling);
let _ = self.inner.xcb_connection.conn.configure_window(
xcb::configure_window(
&self.inner.xcb_connection.conn,
self.inner.window_id,
&ConfigureWindowAux::new()
.width(new_window_info.physical_size().width)
.height(new_window_info.physical_size().height),
&[
(xcb::CONFIG_WINDOW_WIDTH as u16, new_window_info.physical_size().width),
(xcb::CONFIG_WINDOW_HEIGHT as u16, new_window_info.physical_size().height),
],
);
let _ = self.inner.xcb_connection.conn.flush();
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
@ -349,28 +393,343 @@ 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> {
fn raw_window_handle(&self) -> RawWindowHandle {
let mut handle = XlibWindowHandle::empty();
fn raw_window_handle(&self) -> Result<RawWindowHandle, raw_window_handle::HandleError> {
let mut handle = XlibWindowHandle::new(self.inner.window_id.into());
handle.window = self.inner.window_id.into();
handle.visual_id = self.inner.visual_id.into();
RawWindowHandle::Xlib(handle)
Ok(RawWindowHandle::Xlib(handle))
}
}
unsafe impl<'a> HasRawDisplayHandle for Window<'a> {
fn raw_display_handle(&self) -> RawDisplayHandle {
let display = self.inner.xcb_connection.dpy;
let mut handle = XlibDisplayHandle::empty();
fn raw_display_handle(&self) -> Result<RawDisplayHandle, raw_window_handle::HandleError> {
let display = self.inner.xcb_connection.conn.get_raw_dpy();
let handle =
XlibDisplayHandle::new(std::ptr::NonNull::new(display as *mut c_void), unsafe {
x11::xlib::XDefaultScreen(display)
});
handle.display = display as *mut c_void;
handle.screen = unsafe { x11::xlib::XDefaultScreen(display) };
Ok(RawDisplayHandle::Xlib(handle))
}
}
RawDisplayHandle::Xlib(handle)
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

@ -1,83 +1,117 @@
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 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 crate::MouseCursor;
use super::cursor;
x11rb::atom_manager! {
pub Atoms: AtomsCookie {
WM_PROTOCOLS,
WM_DELETE_WINDOW,
}
pub(crate) struct Atoms {
pub wm_protocols: Option<u32>,
pub wm_delete_window: Option<u32>,
}
/// A very light abstraction around the XCB connection.
///
/// Keeps track of the xcb connection itself and the xlib display ID that was used to connect.
pub struct XcbConnection {
pub(crate) dpy: *mut Display,
pub(crate) conn: XCBConnection,
pub(crate) screen: usize,
pub conn: xcb::Connection,
pub xlib_display: i32,
pub(crate) atoms: Atoms,
pub(crate) resources: resource_manager::Database,
pub(crate) cursor_handle: CursorHandle,
pub(super) cursor_cache: RefCell<HashMap<MouseCursor, u32>>,
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()),+
)
}};
}
impl XcbConnection {
pub fn new() -> Result<Self, Box<dyn Error>> {
let dpy = unsafe { xlib::XOpenDisplay(std::ptr::null()) };
assert!(!dpy.is_null());
let xcb_connection = unsafe { xlib_xcb::XGetXCBConnection(dpy) };
assert!(!xcb_connection.is_null());
let screen = unsafe { xlib::XDefaultScreen(dpy) } as usize;
let conn = unsafe { XCBConnection::from_raw_xcb_connection(xcb_connection, false)? };
unsafe {
xlib_xcb::XSetEventQueueOwner(dpy, xlib_xcb::XEventQueueOwner::XCBOwnsEventQueue)
};
pub fn new() -> Result<Self, xcb::base::ConnError> {
let (conn, xlib_display) = xcb::Connection::connect_with_xlib_display()?;
let atoms = Atoms::new(&conn)?.reply()?;
let resources = resource_manager::new_from_default(&conn)?;
let cursor_handle = CursorHandle::new(&conn, screen, &resources)?.reply()?;
conn.set_event_queue_owner(xcb::base::EventQueueOwner::Xcb);
let (wm_protocols, wm_delete_window) = intern_atoms!(&conn, WM_PROTOCOLS, WM_DELETE_WINDOW);
Ok(Self {
dpy,
conn,
screen,
atoms,
resources,
cursor_handle,
cursor_cache: RefCell::new(HashMap::new()),
xlib_display,
atoms: Atoms { wm_protocols, wm_delete_window },
cursor_cache: HashMap::new(),
})
}
// Try to get the scaling with this function first.
// If this gives you `None`, fall back to `get_scaling_screen_dimensions`.
// If neither work, I guess just assume 96.0 and don't do any scaling.
fn get_scaling_xft(&self) -> Result<Option<f64>, Box<dyn Error>> {
if let Some(dpi) = self.resources.get_value::<u32>("Xft.dpi", "")? {
Ok(Some(dpi as f64 / 96.0))
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 {
Ok(None)
None
};
XrmDestroyDatabase(db);
return dpi;
}
}
}
None
}
// Try to get the scaling with `get_scaling_xft` first.
// Only use this function as a fallback.
// If neither work, I guess just assume 96.0 and don't do any scaling.
fn get_scaling_screen_dimensions(&self) -> f64 {
fn get_scaling_screen_dimensions(&self) -> Option<f64> {
// Figure out screen information
let screen = self.screen();
let setup = self.conn.get_setup();
let screen = setup.roots().nth(self.xlib_display as usize).unwrap();
// Get the DPI from the screen struct
//
@ -85,48 +119,28 @@ 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?)
yres / 96.0
Some(yscale)
}
#[inline]
pub fn get_scaling(&self) -> Result<f64, Box<dyn Error>> {
Ok(self.get_scaling_xft()?.unwrap_or(self.get_scaling_screen_dimensions()))
pub fn get_scaling(&self) -> Option<f64> {
self.get_scaling_xft().or_else(|| self.get_scaling_screen_dimensions())
}
#[inline]
pub fn get_cursor(&self, cursor: MouseCursor) -> Result<Cursor, Box<dyn Error>> {
// PANIC: this function is the only point where we access the cache, and we never call
// external functions that may make a reentrant call to this function
let mut cursor_cache = self.cursor_cache.borrow_mut();
pub fn get_cursor_xid(&mut self, cursor: MouseCursor) -> u32 {
let dpy = self.conn.get_raw_dpy();
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);
}
*self.cursor_cache.entry(cursor).or_insert_with(|| cursor::get_xcursor(dpy, cursor))
}
}