1
0
Fork 0

Merge pull request #32 from BillyDM/master

Add beginnings of cross-platform window events struct and AppWindow trait.
This commit is contained in:
william light 2020-09-06 00:36:42 +02:00 committed by GitHub
commit 00c18dd91e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 379 additions and 394 deletions

View file

@ -6,21 +6,22 @@ authors = [
"Charles Saracco <crsaracco@gmail.com>", "Charles Saracco <crsaracco@gmail.com>",
"Mirko Covizzi <mrkcvzz@gmail.com>", "Mirko Covizzi <mrkcvzz@gmail.com>",
"Micah Johnston <micah@glowcoil.com>", "Micah Johnston <micah@glowcoil.com>",
"Billy Messenger <billydm@protonmail.com>",
] ]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
gl = "0.14.0" log = "0.4.11"
log = "0.4.8" 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.3", features = ["xlib", "glx"]} x11 = { version = "2.18", features = ["xlib"] }
libc = "0.2" libc = "0.2"
[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"] }
[target.'cfg(target_os="macos")'.dependencies] [target.'cfg(target_os="macos")'.dependencies]
cocoa = "0.20.1" cocoa = "0.23.0"
objc = "0.2.7" objc = "0.2.7"

View file

@ -1,3 +1,7 @@
use std::sync::mpsc;
use baseview::Event;
fn main() { fn main() {
let window_open_options = baseview::WindowOpenOptions { let window_open_options = baseview::WindowOpenOptions {
title: "baseview", title: "baseview",
@ -6,5 +10,64 @@ fn main() {
parent: baseview::Parent::None, parent: baseview::Parent::None,
}; };
baseview::Window::open(window_open_options); let (_app_message_tx, app_message_rx) = mpsc::channel::<()>();
// Send _app_message_tx to a separate thread, then send messages to the GUI thread.
let _ = baseview::Window::<MyProgram>::open(window_open_options, app_message_rx);
}
struct MyProgram {}
impl baseview::AppWindow for MyProgram {
type AppMessage = ();
fn build(_window_handle: baseview::RawWindow, window_info: &baseview::WindowInfo) -> Self {
println!("Window info: {:?}", window_info);
Self {}
}
fn draw(&mut self) {}
fn on_event(&mut self, event: Event) {
match event {
Event::CursorMotion(x, y) => {
println!("Cursor moved, x: {}, y: {}", x, y);
}
Event::MouseDown(button_id) => {
println!("Mouse down, button id: {:?}", button_id);
}
Event::MouseUp(button_id) => {
println!("Mouse up, button id: {:?}", button_id);
}
Event::MouseScroll(mouse_scroll) => {
println!("Mouse scroll, {:?}", mouse_scroll);
}
Event::MouseClick(mouse_click) => {
println!("Mouse click, {:?}", mouse_click);
}
Event::KeyDown(keycode) => {
println!("Key down, keycode: {}", keycode);
}
Event::KeyUp(keycode) => {
println!("Key up, keycode: {}", keycode);
}
Event::CharacterInput(char_code) => {
println!("Character input, char_code: {}", char_code);
}
Event::WindowResized(window_info) => {
println!("Window resized, {:?}", window_info);
}
Event::WindowFocus => {
println!("Window focused");
}
Event::WindowUnfocus => {
println!("Window unfocused");
}
Event::WillClose => {
println!("Window will close");
}
}
}
fn on_app_message(&mut self, _message: Self::AppMessage) {}
} }

46
src/event.rs Normal file
View file

@ -0,0 +1,46 @@
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum MouseButtonID {
Left,
Middle,
Right,
Back,
Forward,
Other(u8),
}
#[derive(Debug, Copy, Clone)]
pub struct MouseScroll {
pub x_delta: f64,
pub y_delta: f64,
}
#[derive(Debug, Copy, Clone)]
pub struct MouseClick {
pub id: MouseButtonID,
pub click_count: usize,
pub x: i32,
pub y: i32,
}
#[derive(Debug)]
pub struct WindowInfo {
pub width: u32,
pub height: u32,
pub scale: f64,
}
#[derive(Debug)]
pub enum Event {
CursorMotion(i32, i32), // new (x, y) relative to window
MouseDown(MouseButtonID),
MouseUp(MouseButtonID),
MouseScroll(MouseScroll),
MouseClick(MouseClick),
KeyDown(u8), // keycode
KeyUp(u8), // keycode
CharacterInput(u32), // character code
WindowResized(WindowInfo), // new (width, height)
WindowFocus,
WindowUnfocus,
WillClose,
}

View file

@ -15,6 +15,9 @@ mod macos;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
pub use macos::*; pub use macos::*;
mod event;
pub use event::*;
pub enum Parent { pub enum Parent {
None, None,
AsIfParented, AsIfParented,
@ -29,3 +32,25 @@ pub struct WindowOpenOptions<'a> {
pub parent: Parent, pub parent: Parent,
} }
pub trait AppWindow {
type AppMessage;
fn build(window_handle: RawWindow, window_info: &WindowInfo) -> Self;
fn draw(&mut self);
fn on_event(&mut self, event: Event);
fn on_app_message(&mut self, message: Self::AppMessage);
}
/// A wrapper for a `RawWindowHandle`. Some context creators expect an `&impl HasRawWindowHandle`.
#[derive(Debug, Copy, Clone)]
pub struct RawWindow {
pub raw_window_handle: raw_window_handle::RawWindowHandle,
}
unsafe impl raw_window_handle::HasRawWindowHandle for RawWindow {
fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
self.raw_window_handle
}
}

View file

@ -6,12 +6,19 @@ use cocoa::appkit::{
use cocoa::base::{nil, NO}; use cocoa::base::{nil, NO};
use cocoa::foundation::{NSAutoreleasePool, NSPoint, NSRect, NSSize, NSString}; use cocoa::foundation::{NSAutoreleasePool, NSPoint, NSRect, NSSize, NSString};
use crate::WindowOpenOptions; use crate::{AppWindow, Event, MouseButtonID, MouseScroll, WindowOpenOptions};
pub struct Window; pub struct Window<A: AppWindow> {
app_window: A,
app_message_rx: mpsc::Receiver<A::AppMessage>,
}
impl Window { impl<A: Application> Window<A> {
pub fn open(options: WindowOpenOptions) -> Self { pub fn open(
options: WindowOpenOptions,
app_window: A,
app_message_rx: mpsc::Receiver<A::AppMessage>,
) -> Self {
unsafe { unsafe {
let _pool = NSAutoreleasePool::new(nil); let _pool = NSAutoreleasePool::new(nil);
@ -42,7 +49,10 @@ impl Window {
current_app.activateWithOptions_(NSApplicationActivateIgnoringOtherApps); current_app.activateWithOptions_(NSApplicationActivateIgnoringOtherApps);
app.run(); app.run();
Window Window {
app_window,
app_message_rx,
}
} }
} }
} }

View file

@ -2,6 +2,7 @@ extern crate winapi;
use std::ffi::CString; use std::ffi::CString;
use std::ptr::null_mut; use std::ptr::null_mut;
use std::sync::mpsc;
use self::winapi::shared::guiddef::GUID; use self::winapi::shared::guiddef::GUID;
use self::winapi::shared::minwindef::{ATOM, FALSE, LPARAM, LRESULT, UINT, WPARAM}; use self::winapi::shared::minwindef::{ATOM, FALSE, LPARAM, LRESULT, UINT, WPARAM};
@ -14,23 +15,24 @@ use self::winapi::um::wingdi::{
PFD_TYPE_RGBA, PIXELFORMATDESCRIPTOR, PFD_TYPE_RGBA, PIXELFORMATDESCRIPTOR,
}; };
use self::winapi::um::winuser::{ use self::winapi::um::winuser::{
AdjustWindowRectEx, CreateWindowExA, DefWindowProcA, DestroyWindow, DispatchMessageA, GetDC, AdjustWindowRectEx, CreateWindowExA, DefWindowProcA, DestroyWindow, DispatchEventA, EventBoxA,
GetMessageA, GetWindowLongPtrA, MessageBoxA, PeekMessageA, PostMessageA, RegisterClassA, GetDC, GetEventA, GetWindowLongPtrA, PeekEventA, PostEventA, RegisterClassA, ReleaseDC,
ReleaseDC, SetTimer, SetWindowLongPtrA, TranslateMessage, UnregisterClassA, CS_OWNDC, SetTimer, SetWindowLongPtrA, TranslateEvent, UnregisterClassA, CS_OWNDC, GWLP_USERDATA,
GWLP_USERDATA, MB_ICONERROR, MB_OK, MB_TOPMOST, MSG, PM_REMOVE, WM_CREATE, WM_QUIT, MB_ICONERROR, MB_OK, MB_TOPMOST, MSG, PM_REMOVE, WM_CREATE, WM_QUIT, WM_SHOWWINDOW, WM_TIMER,
WM_SHOWWINDOW, WM_TIMER, WNDCLASSA, WS_CAPTION, WS_CHILD, WS_CLIPSIBLINGS, WS_MAXIMIZEBOX, WNDCLASSA, WS_CAPTION, WS_CHILD, WS_CLIPSIBLINGS, WS_MAXIMIZEBOX, WS_MINIMIZEBOX,
WS_MINIMIZEBOX, WS_POPUPWINDOW, WS_SIZEBOX, WS_VISIBLE, WS_POPUPWINDOW, WS_SIZEBOX, WS_VISIBLE,
}; };
use self::winapi::ctypes::c_void; use self::winapi::ctypes::c_void;
use crate::Parent::WithParent; use crate::Parent::WithParent;
use crate::{handle_message, WindowOpenOptions}; use crate::{handle_message, WindowOpenOptions};
use crate::{AppWindow, Event, MouseButtonID, MouseScroll};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
unsafe fn message_box(title: &str, msg: &str) { unsafe fn message_box(title: &str, msg: &str) {
let title = (title.to_owned() + "\0").as_ptr() as *const i8; let title = (title.to_owned() + "\0").as_ptr() as *const i8;
let msg = (msg.to_owned() + "\0").as_ptr() as *const i8; let msg = (msg.to_owned() + "\0").as_ptr() as *const i8;
MessageBoxA(null_mut(), msg, title, MB_ICONERROR | MB_OK | MB_TOPMOST); EventBoxA(null_mut(), msg, title, MB_ICONERROR | MB_OK | MB_TOPMOST);
} }
unsafe fn generate_guid() -> String { unsafe fn generate_guid() -> String {
@ -61,7 +63,7 @@ unsafe extern "system" fn wnd_proc(
let win_ptr = GetWindowLongPtrA(hwnd, GWLP_USERDATA) as *const c_void; let win_ptr = GetWindowLongPtrA(hwnd, GWLP_USERDATA) as *const c_void;
match msg { match msg {
WM_CREATE => { WM_CREATE => {
PostMessageA(hwnd, WM_SHOWWINDOW, 0, 0); PostEventA(hwnd, WM_SHOWWINDOW, 0, 0);
0 0
} }
_ => { _ => {
@ -111,24 +113,34 @@ unsafe fn unregister_wnd_class(wnd_class: ATOM) {
unsafe fn init_gl_context() {} unsafe fn init_gl_context() {}
pub struct Window { pub struct Window<A: AppWindow> {
pub(crate) hwnd: HWND, pub(crate) hwnd: HWND,
hdc: HDC, hdc: HDC,
gl_context: HGLRC, gl_context: HGLRC,
window_class: ATOM, window_class: ATOM,
app_window: A,
app_message_rx: mpsc::Receiver<A::AppMessage>,
scaling: Option<f64>, // DPI scale, 96.0 is "default".
r: f32, r: f32,
g: f32, g: f32,
b: f32, b: f32,
} }
impl Window { impl<A: AppWindow> Window<A> {
pub fn open(options: WindowOpenOptions) { pub fn open(
options: WindowOpenOptions,
app_window: A,
app_message_rx: mpsc::Receiver<A::AppMessage>,
) {
unsafe { unsafe {
let mut window = Window { let mut window = Window {
hwnd: null_mut(), hwnd: null_mut(),
hdc: null_mut(), hdc: null_mut(),
gl_context: null_mut(), gl_context: null_mut(),
window_class: 0, window_class: 0,
app_window,
app_message_rx,
scaling: None,
r: 0.3, r: 0.3,
g: 0.8, g: 0.8,
b: 0.3, b: 0.3,
@ -240,11 +252,11 @@ impl Window {
if parent.is_null() { if parent.is_null() {
let mut msg: MSG = std::mem::zeroed(); let mut msg: MSG = std::mem::zeroed();
loop { loop {
let status = GetMessageA(&mut msg, hwnd, 0, 0); let status = GetEventA(&mut msg, hwnd, 0, 0);
if status == -1 { if status == -1 {
break; break;
} }
TranslateMessage(&mut msg); TranslateEvent(&mut msg);
handle_message(Arc::clone(&win_p), msg.message, msg.wParam, msg.lParam); handle_message(Arc::clone(&win_p), msg.message, msg.wParam, msg.lParam);
} }
} }
@ -252,6 +264,8 @@ impl Window {
} }
pub fn close(&self) { pub fn close(&self) {
self.app_window.on_event(Event::WillClose);
// todo: see https://github.com/wrl/rutabaga/blob/f30ff67e157375cafdbafe5fb549f1790443a3a8/src/platform/win/window.c#L402 // todo: see https://github.com/wrl/rutabaga/blob/f30ff67e157375cafdbafe5fb549f1790443a3a8/src/platform/win/window.c#L402
unsafe { unsafe {
wglMakeCurrent(null_mut(), null_mut()); wglMakeCurrent(null_mut(), null_mut());
@ -270,10 +284,11 @@ impl Window {
} }
pub(crate) fn handle_mouse_motion(&mut self, x: i32, y: i32) { pub(crate) fn handle_mouse_motion(&mut self, x: i32, y: i32) {
println!("{}, {}", x, y);
let r = (x as f32) / 1000.0; let r = (x as f32) / 1000.0;
let g = (y as f32) / 1000.0; let g = (y as f32) / 1000.0;
self.r = r; self.r = r;
self.g = g; self.g = g;
self.app_window.on_message(Event::CursorMotion(x, y));
} }
} }

View file

@ -1,7 +1,5 @@
mod window;
pub use window::*;
mod xcb_connection; mod xcb_connection;
use xcb_connection::XcbConnection; use xcb_connection::XcbConnection;
mod opengl_util; mod window;
pub use window::*;

View file

@ -1,66 +0,0 @@
use std::ffi::CString;
use std::os::raw::{c_int, c_void};
use ::x11::{glx, xlib};
use super::XcbConnection;
pub type GlXCreateContextAttribsARBProc = unsafe extern "C" fn(
dpy: *mut xlib::Display,
fbc: glx::GLXFBConfig,
share_context: glx::GLXContext,
direct: xlib::Bool,
attribs: *const c_int,
) -> glx::GLXContext;
// Check to make sure this system supports the correct version of GLX (>= 1.3 for now)
// For now it just panics if not, but TODO: do correct error handling
pub fn check_glx_version(xcb_connection: &XcbConnection) {
let raw_display = xcb_connection.conn.get_raw_dpy();
let mut maj: c_int = 0;
let mut min: c_int = 0;
unsafe {
if glx::glXQueryVersion(raw_display, &mut maj as *mut c_int, &mut min as *mut c_int) == 0 {
panic!("Cannot get GLX version");
}
if (maj < 1) || (maj == 1 && min < 3) {
panic!("GLX version >= 1.3 required! (have {}.{})", maj, min);
}
}
}
// Get GLX framebuffer config
// History: https://stackoverflow.com/questions/51558473/whats-the-difference-between-a-glx-visual-and-a-fbconfig
pub fn get_glxfbconfig(xcb_connection: &XcbConnection, visual_attribs: &[i32]) -> glx::GLXFBConfig {
let raw_display = xcb_connection.conn.get_raw_dpy();
let xlib_display = xcb_connection.xlib_display;
unsafe {
let mut fbcount: c_int = 0;
let fbcs = glx::glXChooseFBConfig(
raw_display,
xlib_display,
visual_attribs.as_ptr(),
&mut fbcount as *mut c_int,
);
if fbcount == 0 {
panic!("Could not find compatible GLX FB config.");
}
// If we get more than one, any of the different configs work. Just choose the first one.
let fbc = *fbcs;
xlib::XFree(fbcs as *mut c_void);
fbc
}
}
pub unsafe fn load_gl_func(name: &str) -> *mut c_void {
let cname = CString::new(name).unwrap();
let ptr: *mut c_void = std::mem::transmute(glx::glXGetProcAddress(cname.as_ptr() as *const u8));
if ptr.is_null() {
panic!("could not load {}", name);
}
ptr
}

View file

@ -1,29 +1,23 @@
// TODO: messy for now, will refactor when I have more of an idea of the API/architecture
// TODO: close window
// TODO: proper error handling (no bare `unwrap`s, no panics)
// TODO: move more OpenGL-related stuff into opengl_util.rs
// TODO: consider researching all unsafe calls here and figuring out what invariants need to be upheld.
// (write safe wrappers?)
use std::ffi::CStr; use std::ffi::CStr;
use std::os::raw::{c_int, c_void}; use std::os::raw::{c_ulong, c_void};
use std::ptr::null_mut; use std::sync::mpsc;
use ::x11::{glx, xlib};
// use xcb::dri2; // needed later
use super::opengl_util;
use super::XcbConnection; use super::XcbConnection;
use crate::Parent; use crate::{
use crate::WindowOpenOptions; AppWindow, Event, MouseButtonID, MouseScroll, Parent, RawWindow, WindowInfo, WindowOpenOptions,
};
pub struct Window { use raw_window_handle::RawWindowHandle;
pub struct Window<A: AppWindow> {
scaling: f64,
xcb_connection: XcbConnection, xcb_connection: XcbConnection,
scaling: Option<f64>, // DPI scale, 96.0 is "default". app_window: A,
app_message_rx: mpsc::Receiver<A::AppMessage>,
} }
impl Window { impl<A: AppWindow> Window<A> {
pub fn open(options: WindowOpenOptions) -> Self { pub fn open(options: WindowOpenOptions, app_message_rx: mpsc::Receiver<A::AppMessage>) -> Self {
// Convert the parent to a X11 window ID if we're given one // Convert the parent to a X11 window ID if we're given one
let parent = match options.parent { let parent = match options.parent {
Parent::None => None, Parent::None => None,
@ -34,49 +28,14 @@ impl Window {
// Connect to the X server // Connect to the X server
let xcb_connection = XcbConnection::new(); let xcb_connection = XcbConnection::new();
// Check GLX version (>= 1.3 needed)
opengl_util::check_glx_version(&xcb_connection);
// Get GLX framebuffer config (requires GLX >= 1.3)
#[rustfmt::skip]
let fb_config = opengl_util::get_glxfbconfig(
&xcb_connection,
&[
glx::GLX_X_RENDERABLE, 1,
glx::GLX_DRAWABLE_TYPE, glx::GLX_WINDOW_BIT,
glx::GLX_RENDER_TYPE, glx::GLX_RGBA_BIT,
glx::GLX_X_VISUAL_TYPE, glx::GLX_TRUE_COLOR,
glx::GLX_RED_SIZE, 8,
glx::GLX_GREEN_SIZE, 8,
glx::GLX_BLUE_SIZE, 8,
glx::GLX_ALPHA_SIZE, 8,
glx::GLX_DEPTH_SIZE, 24,
glx::GLX_STENCIL_SIZE, 8,
glx::GLX_DOUBLEBUFFER, 1,
0
],
);
// The GLX framebuffer config holds an XVisualInfo, which we'll need for other X operations.
let x_visual_info: *const xlib::XVisualInfo =
unsafe { glx::glXGetVisualFromFBConfig(xcb_connection.conn.get_raw_dpy(), fb_config) };
// Load up DRI2 extensions.
// See also: https://www.x.org/releases/X11R7.7/doc/dri2proto/dri2proto.txt
/*
// needed later when we handle events
let dri2_ev = {
xcb_connection.conn.prefetch_extension_data(dri2::id());
match xcb_connection.conn.get_extension_data(dri2::id()) {
None => panic!("could not load dri2 extension"),
Some(r) => r.first_event(),
}
};
*/
// Get screen information (?) // Get screen information (?)
let setup = xcb_connection.conn.get_setup(); let setup = xcb_connection.conn.get_setup();
let screen = unsafe { setup.roots().nth((*x_visual_info).screen as usize).unwrap() }; let screen = setup
.roots()
.nth(xcb_connection.xlib_display as usize)
.unwrap();
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 = if let Some(p) = parent {
@ -85,24 +44,30 @@ impl Window {
screen.root() screen.root()
}; };
// Create a colormap xcb::create_gc(
let colormap = xcb_connection.conn.generate_id();
unsafe {
xcb::create_colormap(
&xcb_connection.conn, &xcb_connection.conn,
xcb::COLORMAP_ALLOC_NONE as u8, foreground,
colormap,
parent_id, parent_id,
(*x_visual_info).visualid as u32, &[
(xcb::GC_FOREGROUND, screen.black_pixel()),
(xcb::GC_GRAPHICS_EXPOSURES, 0),
],
); );
}
// Create window, connecting to the parent if we have one
let window_id = xcb_connection.conn.generate_id(); let window_id = xcb_connection.conn.generate_id();
let cw_values = [ xcb::create_window(
(xcb::CW_BACK_PIXEL, screen.white_pixel()), &xcb_connection.conn,
(xcb::CW_BORDER_PIXEL, screen.black_pixel()), xcb::COPY_FROM_PARENT as u8,
( window_id,
parent_id,
0, // x coordinate of the new window
0, // y coordinate of the new window
options.width as u16, // window width
options.height as u16, // window height
0, // window border
xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
screen.root_visual(),
&[(
xcb::CW_EVENT_MASK, xcb::CW_EVENT_MASK,
xcb::EVENT_MASK_EXPOSURE xcb::EVENT_MASK_EXPOSURE
| xcb::EVENT_MASK_POINTER_MOTION | xcb::EVENT_MASK_POINTER_MOTION
@ -110,40 +75,9 @@ impl Window {
| xcb::EVENT_MASK_BUTTON_RELEASE | xcb::EVENT_MASK_BUTTON_RELEASE
| xcb::EVENT_MASK_KEY_PRESS | xcb::EVENT_MASK_KEY_PRESS
| xcb::EVENT_MASK_KEY_RELEASE, | xcb::EVENT_MASK_KEY_RELEASE,
), )],
(xcb::CW_COLORMAP, colormap),
];
xcb::create_window(
// Connection
&xcb_connection.conn,
// Depth
unsafe { *x_visual_info }.depth as u8,
// Window ID
window_id,
// Parent ID
parent_id,
// x
0,
// y
0,
// width
options.width as u16,
// height
options.height as u16,
// border width
0,
// class
xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
// visual
unsafe { *x_visual_info }.visualid as u32,
// value list
&cw_values,
); );
xcb::map_window(&xcb_connection.conn, window_id);
// Don't need the visual info anymore
unsafe {
xlib::XFree(x_visual_info as *mut c_void);
}
// Change window title // Change window title
let title = options.title; let title = options.title;
@ -153,103 +87,51 @@ impl Window {
window_id, window_id,
xcb::ATOM_WM_NAME, xcb::ATOM_WM_NAME,
xcb::ATOM_STRING, xcb::ATOM_STRING,
8, 8, // view data as 8-bit
title.as_bytes(), title.as_bytes(),
); );
// Load GLX extensions
// We need at least `GLX_ARB_create_context`
let glx_extensions = unsafe {
CStr::from_ptr(glx::glXQueryExtensionsString(
xcb_connection.conn.get_raw_dpy(),
xcb_connection.xlib_display,
))
.to_str()
.unwrap()
};
glx_extensions
.find("GLX_ARB_create_context")
.expect("could not find GLX extension GLX_ARB_create_context");
// With GLX, we don't need a context pre-created in order to load symbols.
// Otherwise, we would need to create a temporary legacy (dummy) GL context to load them.
// (something that has at least GlXCreateContextAttribsARB)
let glx_create_context_attribs: opengl_util::GlXCreateContextAttribsARBProc =
unsafe { std::mem::transmute(opengl_util::load_gl_func("glXCreateContextAttribsARB")) };
// Load all other symbols
unsafe {
gl::load_with(|n| opengl_util::load_gl_func(&n));
}
// Check GL3 support
if !gl::GenVertexArrays::is_loaded() {
panic!("no GL3 support available!");
}
// TODO: This requires a global, which is a no. Figure out if there's a better way to do it.
/*
// installing an event handler to check if error is generated
unsafe {
ctx_error_occurred = false;
}
let old_handler = unsafe {
xlib::XSetErrorHandler(Some(ctx_error_handler))
};
*/
// Create GLX context attributes. (?)
let context_attribs: [c_int; 5] = [
glx::arb::GLX_CONTEXT_MAJOR_VERSION_ARB as c_int,
3,
glx::arb::GLX_CONTEXT_MINOR_VERSION_ARB as c_int,
0,
0,
];
let ctx = unsafe {
glx_create_context_attribs(
xcb_connection.conn.get_raw_dpy(),
fb_config,
null_mut(),
xlib::True,
&context_attribs[0] as *const c_int,
)
};
if ctx.is_null()
/* || ctx_error_occurred */
{
panic!("Error when creating a GL 3.0 context");
}
if unsafe { glx::glXIsDirect(xcb_connection.conn.get_raw_dpy(), ctx) } == 0 {
panic!("Obtained indirect rendering context");
}
// Display the window
xcb::map_window(&xcb_connection.conn, window_id);
xcb_connection.conn.flush(); xcb_connection.conn.flush();
unsafe {
xlib::XSync(xcb_connection.conn.get_raw_dpy(), xlib::False); let raw_handle = RawWindowHandle::Xlib(raw_window_handle::unix::XlibHandle {
} window: window_id as c_ulong,
display: xcb_connection.conn.get_raw_dpy() as *mut c_void,
..raw_window_handle::unix::XlibHandle::empty()
});
let raw_window = RawWindow {
raw_window_handle: raw_handle,
};
let scaling = get_scaling_xft(&xcb_connection)
.or(get_scaling_screen_dimensions(&xcb_connection))
.unwrap_or(1.0);
let window_info = WindowInfo {
width: options.width as u32,
height: options.height as u32,
scale: scaling,
};
let app_window = A::build(raw_window, &window_info);
let mut x11_window = Self { let mut x11_window = Self {
scaling,
xcb_connection, xcb_connection,
scaling: None, app_window,
app_message_rx,
}; };
x11_window.scaling = x11_window x11_window.run_event_loop();
.get_scaling_xft()
.or(x11_window.get_scaling_screen_dimensions());
println!("Scale factor: {:?}", x11_window.scaling);
x11_window.handle_events(window_id, ctx);
return x11_window; x11_window
} }
// Event loop // Event loop
fn handle_events(&self, window_id: u32, ctx: *mut x11::glx::__GLXcontextRec) { fn run_event_loop(&mut self) {
let raw_display = self.xcb_connection.conn.get_raw_dpy();
loop { loop {
// somehow poll self.app_message_rx for messages at the same time
let ev = self.xcb_connection.conn.wait_for_event(); let ev = self.xcb_connection.conn.wait_for_event();
if let Some(event) = ev { if let Some(event) = ev {
let event_type = event.response_type() & !0x80; let event_type = event.response_type() & !0x80;
@ -275,65 +157,63 @@ impl Window {
// http://rtbo.github.io/rust-xcb/src/xcb/ffi/xproto.rs.html#445 // http://rtbo.github.io/rust-xcb/src/xcb/ffi/xproto.rs.html#445
match event_type { match event_type {
xcb::EXPOSE => unsafe { xcb::EXPOSE => {
glx::glXMakeCurrent(raw_display, window_id as xlib::XID, ctx); self.app_window.draw();
gl::ClearColor(0.3, 0.8, 0.3, 1.0); }
gl::Clear(gl::COLOR_BUFFER_BIT);
gl::Flush();
glx::glXSwapBuffers(raw_display, window_id as xlib::XID);
glx::glXMakeCurrent(raw_display, 0, null_mut());
},
xcb::MOTION_NOTIFY => { xcb::MOTION_NOTIFY => {
let event = unsafe { xcb::cast_event::<xcb::MotionNotifyEvent>(&event) }; let event = unsafe { xcb::cast_event::<xcb::MotionNotifyEvent>(&event) };
let x = event.event_x();
let y = event.event_y();
let detail = event.detail(); let detail = event.detail();
let state = event.state();
println!("Mouse motion: ({}, {}) -- {} / {}", x, y, detail, state); if detail != 4 && detail != 5 {
self.app_window.on_event(Event::CursorMotion(
event.event_x() as i32,
event.event_y() as i32,
));
}
} }
xcb::BUTTON_PRESS => { xcb::BUTTON_PRESS => {
let event = unsafe { xcb::cast_event::<xcb::ButtonPressEvent>(&event) }; let event = unsafe { xcb::cast_event::<xcb::ButtonPressEvent>(&event) };
let x = event.event_x();
let y = event.event_y();
let detail = event.detail(); let detail = event.detail();
let state = event.state();
println!( match detail {
"Mouse button pressed: ({}, {}) -- {} / {}", 4 => {
x, y, detail, state self.app_window.on_event(Event::MouseScroll(MouseScroll {
); x_delta: 0.0,
y_delta: 1.0,
}));
}
5 => {
self.app_window.on_event(Event::MouseScroll(MouseScroll {
x_delta: 0.0,
y_delta: -1.0,
}));
}
detail => {
let button_id = mouse_id(detail);
self.app_window.on_event(Event::MouseDown(button_id));
}
}
} }
xcb::BUTTON_RELEASE => { xcb::BUTTON_RELEASE => {
let event = unsafe { xcb::cast_event::<xcb::ButtonReleaseEvent>(&event) }; let event = unsafe { xcb::cast_event::<xcb::ButtonPressEvent>(&event) };
let x = event.event_x();
let y = event.event_y();
let detail = event.detail(); let detail = event.detail();
let state = event.state();
println!( if detail != 4 && detail != 5 {
"Mouse button released: ({}, {}) -- {} / {}", let button_id = mouse_id(detail);
x, y, detail, state self.app_window.on_event(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 x = event.event_x();
let y = event.event_y();
let detail = event.detail(); let detail = event.detail();
let state = event.state();
println!( self.app_window.on_event(Event::KeyDown(detail));
"Keyboard key pressed: ({}, {}) -- {} / {}",
x, y, detail, state
);
} }
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 x = event.event_x();
let y = event.event_y();
let detail = event.detail(); let detail = event.detail();
let state = event.state();
println!( self.app_window.on_event(Event::KeyUp(detail));
"Keyboard key released: ({}, {}) -- {} / {}",
x, y, detail, state
);
} }
_ => { _ => {
println!("Unhandled event type: {:?}", event_type); println!("Unhandled event type: {:?}", event_type);
@ -342,18 +222,18 @@ impl Window {
} }
} }
} }
}
// Try to get the scaling with this function first. // Try to get the scaling with this function first.
// If this gives you `None`, fall back to `get_scaling_screen_dimensions`. // 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. // If neither work, I guess just assume 96.0 and don't do any scaling.
fn get_scaling_xft(&self) -> Option<f64> { fn get_scaling_xft(xcb_connection: &XcbConnection) -> Option<f64> {
use std::ffi::CString; use std::ffi::CString;
use x11::xlib::{ use x11::xlib::{
XResourceManagerString, XrmDestroyDatabase, XrmGetResource, XrmGetStringDatabase, XResourceManagerString, XrmDestroyDatabase, XrmGetResource, XrmGetStringDatabase, XrmValue,
XrmValue,
}; };
let display = self.xcb_connection.conn.get_raw_dpy(); let display = xcb_connection.conn.get_raw_dpy();
unsafe { unsafe {
let rms = XResourceManagerString(display); let rms = XResourceManagerString(display);
if !rms.is_null() { if !rms.is_null() {
@ -380,8 +260,9 @@ impl Window {
let value_addr: &CStr = CStr::from_ptr(value.addr); let value_addr: &CStr = CStr::from_ptr(value.addr);
value_addr.to_str().ok(); value_addr.to_str().ok();
let value_str = value_addr.to_str().ok()?; let value_str = value_addr.to_str().ok()?;
let value_f64 = value_str.parse().ok()?; let value_f64: f64 = value_str.parse().ok()?;
Some(value_f64) let dpi_to_scale = value_f64 / 96.0;
Some(dpi_to_scale)
} else { } else {
None None
}; };
@ -392,17 +273,17 @@ impl Window {
} }
} }
None None
} }
// Try to get the scaling with `get_scaling_xft` first. // Try to get the scaling with `get_scaling_xft` first.
// Only use this function as a fallback. // Only use this function as a fallback.
// If neither work, I guess just assume 96.0 and don't do any scaling. // 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(xcb_connection: &XcbConnection) -> Option<f64> {
// Figure out screen information // Figure out screen information
let setup = self.xcb_connection.conn.get_setup(); let setup = xcb_connection.conn.get_setup();
let screen = setup let screen = setup
.roots() .roots()
.nth(self.xcb_connection.xlib_display as usize) .nth(xcb_connection.xlib_display as usize)
.unwrap(); .unwrap();
// Get the DPI from the screen struct // Get the DPI from the screen struct
@ -418,7 +299,19 @@ impl Window {
let _xres = width_px * 25.4 / width_mm; let _xres = width_px * 25.4 / width_mm;
let yres = height_px * 25.4 / height_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?) // TODO: choose between `xres` and `yres`? (probably both are the same?)
Some(yres) Some(yscale)
}
fn mouse_id(id: u8) -> MouseButtonID {
match id {
1 => MouseButtonID::Left,
2 => MouseButtonID::Middle,
3 => MouseButtonID::Right,
6 => MouseButtonID::Back,
7 => MouseButtonID::Forward,
id => MouseButtonID::Other(id),
} }
} }