Compare commits
24 commits
7df00dba07
...
4e6e9737f1
Author | SHA1 | Date | |
---|---|---|---|
Alex Janka | 4e6e9737f1 | ||
Alex Janka | e51b4dd832 | ||
Alex Janka | 3d186ce51f | ||
579130ecb4 | |||
45465c5f46 | |||
be3d72d524 | |||
905854d026 | |||
119fc25cd6 | |||
bcbdb8921f | |||
f5b0c6d460 | |||
70e7af6c61 | |||
f5ae585758 | |||
e8b1236fda | |||
f7d83a561e | |||
ea0cd5367d | |||
65d970495f | |||
bad50d886a | |||
085ae2a27e | |||
fdc5d282fc | |||
998ced845c | |||
26b019d6a2 | |||
d8cedc8a77 | |||
475bd5f88a | |||
1274b1c08f |
34
.github/workflows/rust.yml
vendored
34
.github/workflows/rust.yml
vendored
|
@ -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
|
||||
|
|
41
Cargo.toml
41
Cargo.toml
|
@ -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
|
||||
|
|
|
@ -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
2
clippy.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
msrv = '1.59'
|
||||
check-private-items = true
|
141
examples/open_parented.rs
Normal file
141
examples/open_parented.rs
Normal 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);
|
||||
}
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
9
examples/render_femtovg/Cargo.toml
Normal file
9
examples/render_femtovg/Cargo.toml
Normal 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"
|
115
examples/render_femtovg/src/main.rs
Normal file
115
examples/render_femtovg/src/main.rs
Normal 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),
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
54
src/win/cursor.rs
Normal 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,
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
mod cursor;
|
||||
mod drop_target;
|
||||
mod keyboard;
|
||||
mod window;
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()?) })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
303
src/x11/event_loop.rs
Normal 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),
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
94
src/x11/visual_info.rs
Normal file
|
@ -0,0 +1,94 @@
|
|||
use crate::x11::xcb_connection::XcbConnection;
|
||||
use std::error::Error;
|
||||
use x11rb::connection::Connection;
|
||||
use x11rb::protocol::xproto::{
|
||||
Colormap, ColormapAlloc, ConnectionExt, Screen, VisualClass, Visualid,
|
||||
};
|
||||
use x11rb::COPY_FROM_PARENT;
|
||||
|
||||
pub(super) struct WindowVisualConfig {
|
||||
#[cfg(feature = "opengl")]
|
||||
pub fb_config: Option<crate::gl::x11::FbConfig>,
|
||||
|
||||
pub visual_depth: u8,
|
||||
pub visual_id: Visualid,
|
||||
pub color_map: Option<Colormap>,
|
||||
}
|
||||
|
||||
// TODO: make visual negotiation actually check all of a visual's parameters
|
||||
impl WindowVisualConfig {
|
||||
#[cfg(feature = "opengl")]
|
||||
pub fn find_best_visual_config_for_gl(
|
||||
connection: &XcbConnection, gl_config: Option<crate::gl::GlConfig>,
|
||||
) -> Result<Self, Box<dyn Error>> {
|
||||
let Some(gl_config) = gl_config else { return Self::find_best_visual_config(connection) };
|
||||
|
||||
// SAFETY: TODO
|
||||
let (fb_config, window_config) = unsafe {
|
||||
crate::gl::platform::GlContext::get_fb_config_and_visual(connection.dpy, gl_config)
|
||||
}
|
||||
.expect("Could not fetch framebuffer config");
|
||||
|
||||
Ok(Self {
|
||||
fb_config: Some(fb_config),
|
||||
visual_depth: window_config.depth,
|
||||
visual_id: window_config.visual,
|
||||
color_map: Some(create_color_map(connection, window_config.visual)?),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn find_best_visual_config(connection: &XcbConnection) -> Result<Self, Box<dyn Error>> {
|
||||
match find_visual_for_depth(connection.screen(), 32) {
|
||||
None => Ok(Self::copy_from_parent()),
|
||||
Some(visual_id) => Ok(Self {
|
||||
#[cfg(feature = "opengl")]
|
||||
fb_config: None,
|
||||
visual_id,
|
||||
visual_depth: 32,
|
||||
color_map: Some(create_color_map(connection, visual_id)?),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
const fn copy_from_parent() -> Self {
|
||||
Self {
|
||||
#[cfg(feature = "opengl")]
|
||||
fb_config: None,
|
||||
visual_depth: COPY_FROM_PARENT as u8,
|
||||
visual_id: COPY_FROM_PARENT,
|
||||
color_map: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For this 32-bit depth to work, you also need to define a color map and set a border
|
||||
// pixel: https://cgit.freedesktop.org/xorg/xserver/tree/dix/window.c#n818
|
||||
fn create_color_map(
|
||||
connection: &XcbConnection, visual_id: Visualid,
|
||||
) -> Result<Colormap, Box<dyn Error>> {
|
||||
let colormap = connection.conn.generate_id()?;
|
||||
connection.conn.create_colormap(
|
||||
ColormapAlloc::NONE,
|
||||
colormap,
|
||||
connection.screen().root,
|
||||
visual_id,
|
||||
)?;
|
||||
|
||||
Ok(colormap)
|
||||
}
|
||||
|
||||
fn find_visual_for_depth(screen: &Screen, depth: u8) -> Option<Visualid> {
|
||||
for candidate_depth in &screen.allowed_depths {
|
||||
if candidate_depth.depth != depth {
|
||||
continue;
|
||||
}
|
||||
|
||||
for candidate_visual in &candidate_depth.visuals {
|
||||
if candidate_visual.class == VisualClass::TRUE_COLOR {
|
||||
return Some(candidate_visual.visual_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
|
@ -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!()
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue