Merge branch 'master' of https://github.com/RustAudio/baseview into master
This commit is contained in:
commit
ddbc95a2ed
7 changed files with 356 additions and 223 deletions
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
||||||
- name: Install XCB and GL dependencies
|
- name: Install XCB and GL dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install libx11-xcb-dev libxcb-dri2-0-dev libgl1-mesa-dev
|
sudo apt install libx11-xcb-dev libxcb-dri2-0-dev libgl1-mesa-dev libxcb-icccm4-dev
|
||||||
if: contains(matrix.os, 'ubuntu')
|
if: contains(matrix.os, 'ubuntu')
|
||||||
- name: Install rust stable
|
- name: Install rust stable
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
|
|
|
@ -17,7 +17,9 @@ raw-window-handle = "0.3.3"
|
||||||
[target.'cfg(target_os="linux")'.dependencies]
|
[target.'cfg(target_os="linux")'.dependencies]
|
||||||
xcb = { version = "0.9", features = ["thread", "xlib_xcb", "dri2"] }
|
xcb = { version = "0.9", features = ["thread", "xlib_xcb", "dri2"] }
|
||||||
x11 = { version = "2.18", features = ["xlib"] }
|
x11 = { version = "2.18", features = ["xlib"] }
|
||||||
|
xcb-util = { version = "0.3", features = ["icccm"] }
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
nix = "0.18"
|
||||||
|
|
||||||
[target.'cfg(target_os="windows")'.dependencies]
|
[target.'cfg(target_os="windows")'.dependencies]
|
||||||
winapi = { version = "0.3.8", features = ["libloaderapi", "winuser", "windef", "minwindef", "guiddef", "combaseapi", "wingdi", "errhandlingapi"] }
|
winapi = { version = "0.3.8", features = ["libloaderapi", "winuser", "windef", "minwindef", "guiddef", "combaseapi", "wingdi", "errhandlingapi"] }
|
||||||
|
|
|
@ -4,7 +4,7 @@ A low-level windowing system geared towards making audio plugin UIs.
|
||||||
|
|
||||||
`baseview` abstracts the platform-specific windowing APIs (winapi, cocoa, xcb) into a platform-independent API, but otherwise gets out of your way so you can write plugin UIs.
|
`baseview` abstracts the platform-specific windowing APIs (winapi, cocoa, xcb) into a platform-independent API, but otherwise gets out of your way so you can write plugin UIs.
|
||||||
|
|
||||||
Interested in learning more about the project? Join us on [discord](https://discord.gg/b3hjnGw), channel `#vst2-gui`.
|
Interested in learning more about the project? Join us on [discord](https://discord.gg/b3hjnGw), channel `#plugin-gui`.
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
|
|
|
@ -16,11 +16,11 @@ struct MyProgram {}
|
||||||
impl WindowHandler for MyProgram {
|
impl WindowHandler for MyProgram {
|
||||||
type Message = ();
|
type Message = ();
|
||||||
|
|
||||||
fn build(window: &mut Window) -> Self {
|
fn build(_window: &mut Window) -> Self {
|
||||||
Self {}
|
Self {}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&mut self, window: &mut Window) {}
|
fn on_frame(&mut self) {}
|
||||||
|
|
||||||
fn on_event(&mut self, window: &mut Window, event: Event) {
|
fn on_event(&mut self, window: &mut Window, event: Event) {
|
||||||
match event {
|
match event {
|
||||||
|
@ -32,5 +32,5 @@ impl WindowHandler for MyProgram {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_message(&mut self, window: &mut Window, _message: Self::Message) {}
|
fn on_message(&mut self, _window: &mut Window, _message: Self::Message) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ pub trait WindowHandler {
|
||||||
|
|
||||||
fn build(window: &mut Window) -> Self;
|
fn build(window: &mut Window) -> Self;
|
||||||
|
|
||||||
fn draw(&mut self, window: &mut Window);
|
fn on_frame(&mut self);
|
||||||
fn on_event(&mut self, window: &mut Window, event: Event);
|
fn on_event(&mut self, window: &mut Window, event: Event);
|
||||||
fn on_message(&mut self, window: &mut Window, message: Self::Message);
|
fn on_message(&mut self, window: &mut Window, message: Self::Message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use std::ffi::CStr;
|
|
||||||
use std::os::raw::{c_ulong, c_void};
|
use std::os::raw::{c_ulong, c_void};
|
||||||
|
use std::time::*;
|
||||||
|
|
||||||
|
use raw_window_handle::{unix::XlibHandle, HasRawWindowHandle, RawWindowHandle};
|
||||||
|
|
||||||
use super::XcbConnection;
|
use super::XcbConnection;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -7,25 +9,25 @@ use crate::{
|
||||||
WindowHandler, WindowOpenOptions,
|
WindowHandler, WindowOpenOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
use raw_window_handle::{unix::XlibHandle, HasRawWindowHandle, RawWindowHandle};
|
|
||||||
|
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
xcb_connection: XcbConnection,
|
xcb_connection: XcbConnection,
|
||||||
window_id: u32,
|
window_id: u32,
|
||||||
scaling: f64,
|
scaling: f64,
|
||||||
|
|
||||||
|
frame_interval: Duration,
|
||||||
|
event_loop_running: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: move to outer crate context
|
||||||
|
pub struct WindowHandle;
|
||||||
|
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
pub fn open<H: WindowHandler>(options: WindowOpenOptions) -> WindowHandle {
|
pub fn open<H: WindowHandler>(options: WindowOpenOptions) -> WindowHandle {
|
||||||
// Convert the parent to a X11 window ID if we're given one
|
|
||||||
let parent = match options.parent {
|
|
||||||
Parent::None => None,
|
|
||||||
Parent::AsIfParented => None, // TODO: ???
|
|
||||||
Parent::WithParent(p) => Some(p as u32),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Connect to the X server
|
// Connect to the X server
|
||||||
let xcb_connection = XcbConnection::new();
|
// FIXME: baseview error type instead of unwrap()
|
||||||
|
let xcb_connection = XcbConnection::new().unwrap();
|
||||||
|
|
||||||
// Get screen information (?)
|
// Get screen information (?)
|
||||||
let setup = xcb_connection.conn.get_setup();
|
let setup = xcb_connection.conn.get_setup();
|
||||||
|
@ -37,10 +39,9 @@ impl Window {
|
||||||
let foreground = xcb_connection.conn.generate_id();
|
let foreground = xcb_connection.conn.generate_id();
|
||||||
|
|
||||||
// Convert parent into something that X understands
|
// Convert parent into something that X understands
|
||||||
let parent_id = if let Some(p) = parent {
|
let parent_id = match options.parent {
|
||||||
p
|
Parent::WithParent(p) => p as u32,
|
||||||
} else {
|
Parent::None | Parent::AsIfParented => screen.root(),
|
||||||
screen.root()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
xcb::create_gc(
|
xcb::create_gc(
|
||||||
|
@ -76,6 +77,7 @@ impl Window {
|
||||||
| xcb::EVENT_MASK_KEY_RELEASE,
|
| xcb::EVENT_MASK_KEY_RELEASE,
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
|
||||||
xcb::map_window(&xcb_connection.conn, window_id);
|
xcb::map_window(&xcb_connection.conn, window_id);
|
||||||
|
|
||||||
// Change window title
|
// Change window title
|
||||||
|
@ -90,24 +92,219 @@ impl Window {
|
||||||
title.as_bytes(),
|
title.as_bytes(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
xcb_connection.atoms.wm_protocols
|
||||||
|
.zip(xcb_connection.atoms.wm_delete_window)
|
||||||
|
.map(|(wm_protocols, wm_delete_window)| {
|
||||||
|
xcb_util::icccm::set_wm_protocols(
|
||||||
|
&xcb_connection.conn,
|
||||||
|
window_id,
|
||||||
|
wm_protocols,
|
||||||
|
&[wm_delete_window]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
xcb_connection.conn.flush();
|
xcb_connection.conn.flush();
|
||||||
|
|
||||||
let scaling = get_scaling_xft(&xcb_connection)
|
let scaling = xcb_connection.get_scaling().unwrap_or(1.0);
|
||||||
.or(get_scaling_screen_dimensions(&xcb_connection))
|
|
||||||
.unwrap_or(1.0);
|
|
||||||
|
|
||||||
let mut window = Self {
|
let mut window = Self {
|
||||||
xcb_connection,
|
xcb_connection,
|
||||||
window_id,
|
window_id,
|
||||||
scaling,
|
scaling,
|
||||||
|
|
||||||
|
frame_interval: Duration::from_millis(15),
|
||||||
|
event_loop_running: false
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut handler = H::build(&mut window);
|
let mut handler = H::build(&mut window);
|
||||||
|
|
||||||
run_event_loop(&mut window, &mut handler);
|
window.run_event_loop(&mut handler);
|
||||||
|
|
||||||
WindowHandle
|
WindowHandle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn drain_xcb_events<H: WindowHandler>(&mut self, handler: &mut H) {
|
||||||
|
while let Some(event) = self.xcb_connection.conn.poll_for_event() {
|
||||||
|
self.handle_xcb_event(handler, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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<H: WindowHandler>(&mut self, handler: &mut H) {
|
||||||
|
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 next_frame = Instant::now() + self.frame_interval;
|
||||||
|
self.event_loop_running = true;
|
||||||
|
|
||||||
|
while self.event_loop_running {
|
||||||
|
let now = Instant::now();
|
||||||
|
let until_next_frame =
|
||||||
|
if now > next_frame {
|
||||||
|
handler.on_frame();
|
||||||
|
|
||||||
|
next_frame = now + self.frame_interval;
|
||||||
|
self.frame_interval
|
||||||
|
} else {
|
||||||
|
next_frame - now
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut fds = [
|
||||||
|
PollFd::new(xcb_fd, PollFlags::POLLIN)
|
||||||
|
];
|
||||||
|
|
||||||
|
// FIXME: handle errors
|
||||||
|
poll(&mut fds, until_next_frame.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_xcb_event<H: WindowHandler>(&mut self, handler: &mut H, 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 {
|
||||||
|
////
|
||||||
|
// keys
|
||||||
|
////
|
||||||
|
|
||||||
|
xcb::EXPOSE => {
|
||||||
|
handler.on_frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
xcb::CLIENT_MESSAGE => {
|
||||||
|
let event = unsafe { xcb::cast_event::<xcb::ClientMessageEvent>(&event) };
|
||||||
|
|
||||||
|
// what an absolute trajedy 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] {
|
||||||
|
handler.on_event(self, Event::WillClose);
|
||||||
|
|
||||||
|
// FIXME: handler should decide whether window stays open or not
|
||||||
|
self.event_loop_running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////
|
||||||
|
// mouse
|
||||||
|
////
|
||||||
|
|
||||||
|
xcb::MOTION_NOTIFY => {
|
||||||
|
let event = unsafe { xcb::cast_event::<xcb::MotionNotifyEvent>(&event) };
|
||||||
|
let detail = event.detail();
|
||||||
|
|
||||||
|
if detail != 4 && detail != 5 {
|
||||||
|
handler.on_event(
|
||||||
|
self,
|
||||||
|
Event::CursorMotion(event.event_x() as i32, event.event_y() as i32),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xcb::BUTTON_PRESS => {
|
||||||
|
let event = unsafe { xcb::cast_event::<xcb::ButtonPressEvent>(&event) };
|
||||||
|
let detail = event.detail();
|
||||||
|
|
||||||
|
match detail {
|
||||||
|
4 => {
|
||||||
|
handler.on_event(
|
||||||
|
self,
|
||||||
|
Event::MouseScroll(MouseScroll {
|
||||||
|
x_delta: 0.0,
|
||||||
|
y_delta: 1.0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
5 => {
|
||||||
|
handler.on_event(
|
||||||
|
self,
|
||||||
|
Event::MouseScroll(MouseScroll {
|
||||||
|
x_delta: 0.0,
|
||||||
|
y_delta: -1.0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
detail => {
|
||||||
|
let button_id = mouse_id(detail);
|
||||||
|
handler.on_event(self, Event::MouseDown(button_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xcb::BUTTON_RELEASE => {
|
||||||
|
let event = unsafe { xcb::cast_event::<xcb::ButtonPressEvent>(&event) };
|
||||||
|
let detail = event.detail();
|
||||||
|
|
||||||
|
if detail != 4 && detail != 5 {
|
||||||
|
let button_id = mouse_id(detail);
|
||||||
|
handler.on_event(self, Event::MouseUp(button_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////
|
||||||
|
// keys
|
||||||
|
////
|
||||||
|
|
||||||
|
xcb::KEY_PRESS => {
|
||||||
|
let event = unsafe { xcb::cast_event::<xcb::KeyPressEvent>(&event) };
|
||||||
|
let detail = event.detail();
|
||||||
|
|
||||||
|
handler.on_event(self, Event::KeyDown(detail));
|
||||||
|
}
|
||||||
|
|
||||||
|
xcb::KEY_RELEASE => {
|
||||||
|
let event = unsafe { xcb::cast_event::<xcb::KeyReleaseEvent>(&event) };
|
||||||
|
let detail = event.detail();
|
||||||
|
|
||||||
|
handler.on_event(self, Event::KeyUp(detail));
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
println!("Unhandled event type: {:?}", event_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl HasRawWindowHandle for Window {
|
unsafe impl HasRawWindowHandle for Window {
|
||||||
|
@ -120,202 +317,6 @@ unsafe impl HasRawWindowHandle for Window {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WindowHandle;
|
|
||||||
|
|
||||||
// Event loop
|
|
||||||
fn run_event_loop<H: WindowHandler>(window: &mut Window, handler: &mut H) {
|
|
||||||
loop {
|
|
||||||
let ev = window.xcb_connection.conn.wait_for_event();
|
|
||||||
if let Some(event) = ev {
|
|
||||||
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 {
|
|
||||||
xcb::EXPOSE => {
|
|
||||||
handler.draw(window);
|
|
||||||
}
|
|
||||||
xcb::MOTION_NOTIFY => {
|
|
||||||
let event = unsafe { xcb::cast_event::<xcb::MotionNotifyEvent>(&event) };
|
|
||||||
let detail = event.detail();
|
|
||||||
|
|
||||||
if detail != 4 && detail != 5 {
|
|
||||||
handler.on_event(
|
|
||||||
window,
|
|
||||||
Event::Mouse(MouseEvent::CursorMoved {
|
|
||||||
x: event.event_x() as i32,
|
|
||||||
y: event.event_y() as i32,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
xcb::BUTTON_PRESS => {
|
|
||||||
let event = unsafe { xcb::cast_event::<xcb::ButtonPressEvent>(&event) };
|
|
||||||
let detail = event.detail();
|
|
||||||
|
|
||||||
match detail {
|
|
||||||
4 => {
|
|
||||||
handler.on_event(
|
|
||||||
window,
|
|
||||||
Event::Mouse(MouseEvent::WheelScrolled(ScrollDelta::Lines {
|
|
||||||
x: 0.0,
|
|
||||||
y: 1.0,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
5 => {
|
|
||||||
handler.on_event(
|
|
||||||
window,
|
|
||||||
Event::Mouse(MouseEvent::WheelScrolled(ScrollDelta::Lines {
|
|
||||||
x: 0.0,
|
|
||||||
y: -1.0,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
detail => {
|
|
||||||
let button_id = mouse_id(detail);
|
|
||||||
handler.on_event(
|
|
||||||
window,
|
|
||||||
Event::Mouse(MouseEvent::ButtonPressed(button_id)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
xcb::BUTTON_RELEASE => {
|
|
||||||
let event = unsafe { xcb::cast_event::<xcb::ButtonPressEvent>(&event) };
|
|
||||||
let detail = event.detail();
|
|
||||||
|
|
||||||
if detail != 4 && detail != 5 {
|
|
||||||
let button_id = mouse_id(detail);
|
|
||||||
handler
|
|
||||||
.on_event(window, Event::Mouse(MouseEvent::ButtonReleased(button_id)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
xcb::KEY_PRESS => {
|
|
||||||
let event = unsafe { xcb::cast_event::<xcb::KeyPressEvent>(&event) };
|
|
||||||
let detail = event.detail();
|
|
||||||
|
|
||||||
handler.on_event(
|
|
||||||
window,
|
|
||||||
Event::Keyboard(KeyboardEvent::KeyPressed(detail as u32)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
xcb::KEY_RELEASE => {
|
|
||||||
let event = unsafe { xcb::cast_event::<xcb::KeyReleaseEvent>(&event) };
|
|
||||||
let detail = event.detail();
|
|
||||||
|
|
||||||
handler.on_event(
|
|
||||||
window,
|
|
||||||
Event::Keyboard(KeyboardEvent::KeyReleased(detail as u32)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
println!("Unhandled event type: {:?}", event_type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(xcb_connection: &XcbConnection) -> Option<f64> {
|
|
||||||
use std::ffi::CString;
|
|
||||||
use x11::xlib::{
|
|
||||||
XResourceManagerString, XrmDestroyDatabase, XrmGetResource, XrmGetStringDatabase, XrmValue,
|
|
||||||
};
|
|
||||||
|
|
||||||
let display = xcb_connection.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 libc::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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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(xcb_connection: &XcbConnection) -> Option<f64> {
|
|
||||||
// Figure out screen information
|
|
||||||
let setup = xcb_connection.conn.get_setup();
|
|
||||||
let screen = setup
|
|
||||||
.roots()
|
|
||||||
.nth(xcb_connection.xlib_display as usize)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Get the DPI from the screen struct
|
|
||||||
//
|
|
||||||
// there are 2.54 centimeters to an inch; so there are 25.4 millimeters.
|
|
||||||
// dpi = N pixels / (M millimeters / (25.4 millimeters / 1 inch))
|
|
||||||
// = N pixels / (M inch / 25.4)
|
|
||||||
// = N * 25.4 pixels / M inch
|
|
||||||
let width_px = screen.width_in_pixels() as f64;
|
|
||||||
let width_mm = screen.width_in_millimeters() as f64;
|
|
||||||
let height_px = screen.height_in_pixels() as f64;
|
|
||||||
let height_mm = screen.height_in_millimeters() as f64;
|
|
||||||
let _xres = width_px * 25.4 / width_mm;
|
|
||||||
let yres = height_px * 25.4 / height_mm;
|
|
||||||
|
|
||||||
let yscale = yres / 96.0;
|
|
||||||
|
|
||||||
// TODO: choose between `xres` and `yres`? (probably both are the same?)
|
|
||||||
Some(yscale)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mouse_id(id: u8) -> MouseButton {
|
fn mouse_id(id: u8) -> MouseButton {
|
||||||
match id {
|
match id {
|
||||||
1 => MouseButton::Left,
|
1 => MouseButton::Left,
|
||||||
|
|
|
@ -2,14 +2,144 @@
|
||||||
///
|
///
|
||||||
/// Keeps track of the xcb connection itself and the xlib display ID that was used to connect.
|
/// Keeps track of the xcb connection itself and the xlib display ID that was used to connect.
|
||||||
|
|
||||||
|
use std::ffi::{
|
||||||
|
CString,
|
||||||
|
CStr
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) struct Atoms {
|
||||||
|
pub wm_protocols: Option<u32>,
|
||||||
|
pub wm_delete_window: Option<u32>
|
||||||
|
}
|
||||||
|
|
||||||
pub struct XcbConnection {
|
pub struct XcbConnection {
|
||||||
pub conn: xcb::Connection,
|
pub conn: xcb::Connection,
|
||||||
pub xlib_display: i32,
|
pub xlib_display: i32,
|
||||||
|
|
||||||
|
pub(crate) atoms: Atoms
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! intern_atoms {
|
||||||
|
($conn:expr, $( $name:ident ),+ ) => {{
|
||||||
|
$(
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
let $name = xcb::intern_atom($conn, true, stringify!($name));
|
||||||
|
)+
|
||||||
|
|
||||||
|
// splitting request and reply to improve throughput
|
||||||
|
|
||||||
|
(
|
||||||
|
$( $name.get_reply()
|
||||||
|
.map(|r| r.atom())
|
||||||
|
.ok()),+
|
||||||
|
)
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
impl XcbConnection {
|
impl XcbConnection {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Result<Self, xcb::base::ConnError> {
|
||||||
let (conn, xlib_display) = xcb::Connection::connect_with_xlib_display().unwrap();
|
let (conn, xlib_display) = xcb::Connection::connect_with_xlib_display()?;
|
||||||
Self { conn, xlib_display }
|
|
||||||
|
let (wm_protocols, wm_delete_window) =
|
||||||
|
intern_atoms!(&conn,
|
||||||
|
WM_PROTOCOLS,
|
||||||
|
WM_DELETE_WINDOW);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
conn,
|
||||||
|
xlib_display,
|
||||||
|
|
||||||
|
atoms: Atoms {
|
||||||
|
wm_protocols,
|
||||||
|
wm_delete_window
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 libc::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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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> {
|
||||||
|
// Figure out screen information
|
||||||
|
let setup = self.conn.get_setup();
|
||||||
|
let screen = setup
|
||||||
|
.roots()
|
||||||
|
.nth(self.xlib_display as usize)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Get the DPI from the screen struct
|
||||||
|
//
|
||||||
|
// there are 2.54 centimeters to an inch; so there are 25.4 millimeters.
|
||||||
|
// dpi = N pixels / (M millimeters / (25.4 millimeters / 1 inch))
|
||||||
|
// = N pixels / (M inch / 25.4)
|
||||||
|
// = N * 25.4 pixels / M inch
|
||||||
|
let width_px = screen.width_in_pixels() as f64;
|
||||||
|
let width_mm = screen.width_in_millimeters() as f64;
|
||||||
|
let height_px = screen.height_in_pixels() as f64;
|
||||||
|
let height_mm = screen.height_in_millimeters() as f64;
|
||||||
|
let _xres = width_px * 25.4 / width_mm;
|
||||||
|
let yres = height_px * 25.4 / height_mm;
|
||||||
|
|
||||||
|
let yscale = yres / 96.0;
|
||||||
|
|
||||||
|
// TODO: choose between `xres` and `yres`? (probably both are the same?)
|
||||||
|
Some(yscale)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get_scaling(&self) -> Option<f64> {
|
||||||
|
self.get_scaling_xft()
|
||||||
|
.or(self.get_scaling_screen_dimensions())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue