1
0
Fork 0

Merge pull request #41 from RustAudio/x11-frame-timer

X11 frame timer
This commit is contained in:
william light 2020-09-11 17:48:39 +02:00 committed by GitHub
commit cd48fe57c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 269 additions and 205 deletions

View file

@ -18,6 +18,7 @@ raw-window-handle = "0.3.3"
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"] }
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"] }

View file

@ -20,7 +20,7 @@ impl WindowHandler for MyProgram {
Self {} Self {}
} }
fn draw(&mut self, window: &mut Window) {} fn draw(&mut self, _window: &mut Window) {}
fn on_event(&mut self, window: &mut Window, event: Event) { fn on_event(&mut self, window: &mut Window, event: Event) {
match event { match event {

View file

@ -1,28 +1,30 @@
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::{Event, MouseButtonID, MouseScroll, Parent, WindowHandler, WindowOpenOptions}; use crate::{Event, MouseButtonID, MouseScroll, Parent, 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();
@ -34,10 +36,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(
@ -73,6 +74,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
@ -89,41 +91,79 @@ impl 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
} }
}
unsafe impl HasRawWindowHandle for Window { #[inline]
fn raw_window_handle(&self) -> RawWindowHandle { fn drain_xcb_events<H: WindowHandler>(&mut self, handler: &mut H) {
RawWindowHandle::Xlib(XlibHandle { while let Some(event) = self.xcb_connection.conn.poll_for_event() {
window: self.window_id as c_ulong, self.handle_xcb_event(handler, event);
display: self.xcb_connection.conn.get_raw_dpy() as *mut c_void, }
..raw_window_handle::unix::XlibHandle::empty()
})
} }
}
pub struct WindowHandle; // 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::*;
// Event loop let xcb_fd = unsafe {
fn run_event_loop<H: WindowHandler>(window: &mut Window, handler: &mut H) { let raw_conn = self.xcb_connection.conn.get_raw_conn();
loop { xcb::ffi::xcb_get_file_descriptor(raw_conn)
let ev = window.xcb_connection.conn.wait_for_event(); };
if let Some(event) = ev {
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.draw(self);
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; let event_type = event.response_type() & !0x80;
// For all of the keyboard and mouse events, you can fetch // For all of the keyboard and mouse events, you can fetch
@ -148,7 +188,7 @@ fn run_event_loop<H: WindowHandler>(window: &mut Window, handler: &mut H) {
match event_type { match event_type {
xcb::EXPOSE => { xcb::EXPOSE => {
handler.draw(window); handler.draw(self);
} }
xcb::MOTION_NOTIFY => { xcb::MOTION_NOTIFY => {
let event = unsafe { xcb::cast_event::<xcb::MotionNotifyEvent>(&event) }; let event = unsafe { xcb::cast_event::<xcb::MotionNotifyEvent>(&event) };
@ -156,7 +196,7 @@ fn run_event_loop<H: WindowHandler>(window: &mut Window, handler: &mut H) {
if detail != 4 && detail != 5 { if detail != 4 && detail != 5 {
handler.on_event( handler.on_event(
window, self,
Event::CursorMotion(event.event_x() as i32, event.event_y() as i32), Event::CursorMotion(event.event_x() as i32, event.event_y() as i32),
); );
} }
@ -168,7 +208,7 @@ fn run_event_loop<H: WindowHandler>(window: &mut Window, handler: &mut H) {
match detail { match detail {
4 => { 4 => {
handler.on_event( handler.on_event(
window, self,
Event::MouseScroll(MouseScroll { Event::MouseScroll(MouseScroll {
x_delta: 0.0, x_delta: 0.0,
y_delta: 1.0, y_delta: 1.0,
@ -177,7 +217,7 @@ fn run_event_loop<H: WindowHandler>(window: &mut Window, handler: &mut H) {
} }
5 => { 5 => {
handler.on_event( handler.on_event(
window, self,
Event::MouseScroll(MouseScroll { Event::MouseScroll(MouseScroll {
x_delta: 0.0, x_delta: 0.0,
y_delta: -1.0, y_delta: -1.0,
@ -186,7 +226,7 @@ fn run_event_loop<H: WindowHandler>(window: &mut Window, handler: &mut H) {
} }
detail => { detail => {
let button_id = mouse_id(detail); let button_id = mouse_id(detail);
handler.on_event(window, Event::MouseDown(button_id)); handler.on_event(self, Event::MouseDown(button_id));
} }
} }
} }
@ -196,108 +236,36 @@ fn run_event_loop<H: WindowHandler>(window: &mut Window, handler: &mut H) {
if detail != 4 && detail != 5 { if detail != 4 && detail != 5 {
let button_id = mouse_id(detail); let button_id = mouse_id(detail);
handler.on_event(window, Event::MouseUp(button_id)); handler.on_event(self, Event::MouseUp(button_id));
} }
} }
xcb::KEY_PRESS => { xcb::KEY_PRESS => {
let event = unsafe { xcb::cast_event::<xcb::KeyPressEvent>(&event) }; let event = unsafe { xcb::cast_event::<xcb::KeyPressEvent>(&event) };
let detail = event.detail(); let detail = event.detail();
handler.on_event(window, Event::KeyDown(detail)); handler.on_event(self, Event::KeyDown(detail));
} }
xcb::KEY_RELEASE => { xcb::KEY_RELEASE => {
let event = unsafe { xcb::cast_event::<xcb::KeyReleaseEvent>(&event) }; let event = unsafe { xcb::cast_event::<xcb::KeyReleaseEvent>(&event) };
let detail = event.detail(); let detail = event.detail();
handler.on_event(window, Event::KeyUp(detail)); handler.on_event(self, Event::KeyUp(detail));
} }
_ => { _ => {
println!("Unhandled event type: {:?}", event_type); println!("Unhandled event type: {:?}", event_type);
} }
} }
} }
}
} }
// Try to get the scaling with this function first. unsafe impl HasRawWindowHandle for Window {
// If this gives you `None`, fall back to `get_scaling_screen_dimensions`. fn raw_window_handle(&self) -> RawWindowHandle {
// If neither work, I guess just assume 96.0 and don't do any scaling. RawWindowHandle::Xlib(XlibHandle {
fn get_scaling_xft(xcb_connection: &XcbConnection) -> Option<f64> { window: self.window_id as c_ulong,
use std::ffi::CString; display: self.xcb_connection.conn.get_raw_dpy() as *mut c_void,
use x11::xlib::{ ..raw_window_handle::unix::XlibHandle::empty()
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) -> MouseButtonID { fn mouse_id(id: u8) -> MouseButtonID {

View file

@ -2,14 +2,109 @@
/// ///
/// 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 struct XcbConnection { pub struct XcbConnection {
pub conn: xcb::Connection, pub conn: xcb::Connection,
pub xlib_display: i32, pub xlib_display: i32,
} }
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(); xcb::Connection::connect_with_xlib_display()
Self { conn, xlib_display } .map(|(conn, xlib_display)|
Self {
conn,
xlib_display
})
}
// 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())
} }
} }