diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index cb5a8cd..096c5e9 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -21,7 +21,9 @@ jobs: with: toolchain: stable override: true - - name: Build + - name: Build with default features run: cargo build --examples --workspace --verbose + - name: Build again with all features + run: cargo build --examples --workspace --all-features --verbose - name: Run tests - run: cargo test --examples --workspace --verbose + run: cargo test --examples --workspace --all-features --verbose diff --git a/Cargo.toml b/Cargo.toml index d781e3c..eb23f4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,22 +9,28 @@ authors = [ "Billy Messenger ", "Anton Lazarev ", "Joakim FrostegÄrd ", + "Robbert van der Helm ", ] edition = "2018" license = "MIT OR Apache-2.0" +[features] +default = [] +opengl = ["uuid", "x11/glx"] + [dependencies] -keyboard-types = { version = "0.5.0", default-features = false } -raw-window-handle = "0.3.3" +keyboard-types = { version = "0.6.1", default-features = false } +raw-window-handle = "0.4.2" [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"] } -nix = "0.18" +nix = "0.22.0" [target.'cfg(target_os="windows")'.dependencies] winapi = { version = "0.3.8", features = ["libloaderapi", "winuser", "windef", "minwindef", "guiddef", "combaseapi", "wingdi", "errhandlingapi"] } +uuid = { version = "0.8", features = ["v4"], optional = true } [target.'cfg(target_os="macos")'.dependencies] cocoa = "0.24.0" diff --git a/README.md b/README.md index bd6c7ea..1015ac3 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,14 @@ Interested in learning more about the project? Join us on [discord](https://disc Below is a proposed list of milestones (roughly in-order) and their status. Subject to change at any time. -| Feature | Windows | Mac OS | Linux | -| ----------------------------------------------- | ------------------ | ------------------ | ------------------ | -| Spawns a window, no parent | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| Cross-platform API for window spawning | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| Can find DPI scale factor | | :heavy_check_mark: | :heavy_check_mark: | -| Basic event handling (mouse, keyboard) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| Parent window support | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Feature | Windows | Mac OS | Linux | +| ----------------------------------------------------- | ------------------ | ------------------ | ------------------ | +| Spawns a window, no parent | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Cross-platform API for window spawning | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Can find DPI scale factor | | :heavy_check_mark: | :heavy_check_mark: | +| Basic event handling (mouse, keyboard) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Parent window support | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| OpenGL context creation (behind the `opengl` feature) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | ## Prerequisites diff --git a/examples/open_window.rs b/examples/open_window.rs index 88a9ae0..95141b9 100644 --- a/examples/open_window.rs +++ b/examples/open_window.rs @@ -36,6 +36,10 @@ fn main() { 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, }; let (mut tx, rx) = RingBuffer::new(128); diff --git a/src/gl/macos.rs b/src/gl/macos.rs new file mode 100644 index 0000000..0610879 --- /dev/null +++ b/src/gl/macos.rs @@ -0,0 +1,146 @@ +use std::ffi::c_void; +use std::str::FromStr; + +use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; + +use cocoa::appkit::{ + NSOpenGLContext, NSOpenGLContextParameter, NSOpenGLPFAAccelerated, NSOpenGLPFAAlphaSize, + NSOpenGLPFAColorSize, NSOpenGLPFADepthSize, NSOpenGLPFADoubleBuffer, NSOpenGLPFAMultisample, + NSOpenGLPFAOpenGLProfile, NSOpenGLPFASampleBuffers, NSOpenGLPFASamples, NSOpenGLPFAStencilSize, + NSOpenGLPixelFormat, NSOpenGLProfileVersion3_2Core, NSOpenGLProfileVersion4_1Core, + NSOpenGLProfileVersionLegacy, NSOpenGLView, NSView, +}; +use cocoa::base::{id, nil, YES}; + +use core_foundation::base::TCFType; +use core_foundation::bundle::{CFBundleGetBundleWithIdentifier, CFBundleGetFunctionPointerForName}; +use core_foundation::string::CFString; + +use objc::{msg_send, sel, sel_impl}; + +use super::{GlConfig, GlError, Profile}; + +pub type CreationFailedError = (); +pub struct GlContext { + view: id, + context: id, +} + +impl GlContext { + pub unsafe fn create( + parent: &impl HasRawWindowHandle, config: GlConfig, + ) -> Result { + let handle = if let RawWindowHandle::AppKit(handle) = parent.raw_window_handle() { + handle + } else { + return Err(GlError::InvalidWindowHandle); + }; + + if handle.ns_view.is_null() { + return Err(GlError::InvalidWindowHandle); + } + + let parent_view = handle.ns_view as id; + + let version = if config.version < (3, 2) && config.profile == Profile::Compatibility { + NSOpenGLProfileVersionLegacy + } else if config.version == (3, 2) && config.profile == Profile::Core { + NSOpenGLProfileVersion3_2Core + } else if config.version > (3, 2) && config.profile == Profile::Core { + NSOpenGLProfileVersion4_1Core + } else { + return Err(GlError::VersionNotSupported); + }; + + #[rustfmt::skip] + let mut attrs = vec![ + NSOpenGLPFAOpenGLProfile as u32, version as u32, + NSOpenGLPFAColorSize as u32, (config.red_bits + config.blue_bits + config.green_bits) as u32, + NSOpenGLPFAAlphaSize as u32, config.alpha_bits as u32, + NSOpenGLPFADepthSize as u32, config.depth_bits as u32, + NSOpenGLPFAStencilSize as u32, config.stencil_bits as u32, + NSOpenGLPFAAccelerated as u32, + ]; + + if config.samples.is_some() { + #[rustfmt::skip] + attrs.extend_from_slice(&[ + NSOpenGLPFAMultisample as u32, + NSOpenGLPFASampleBuffers as u32, 1, + NSOpenGLPFASamples as u32, config.samples.unwrap() as u32, + ]); + } + + if config.double_buffer { + attrs.push(NSOpenGLPFADoubleBuffer as u32); + } + + attrs.push(0); + + let pixel_format = NSOpenGLPixelFormat::alloc(nil).initWithAttributes_(&attrs); + + if pixel_format == nil { + return Err(GlError::CreationFailed(())); + } + + let view = + NSOpenGLView::alloc(nil).initWithFrame_pixelFormat_(parent_view.frame(), pixel_format); + + if view == nil { + return Err(GlError::CreationFailed(())); + } + + view.setWantsBestResolutionOpenGLSurface_(YES); + + let () = msg_send![view, retain]; + NSOpenGLView::display_(view); + parent_view.addSubview_(view); + + let context: id = msg_send![view, openGLContext]; + let () = msg_send![context, retain]; + + context.setValues_forParameter_( + &(config.vsync as i32), + NSOpenGLContextParameter::NSOpenGLCPSwapInterval, + ); + + let () = msg_send![pixel_format, release]; + + Ok(GlContext { view, context }) + } + + pub unsafe fn make_current(&self) { + self.context.makeCurrentContext(); + } + + pub unsafe fn make_not_current(&self) { + NSOpenGLContext::clearCurrentContext(self.context); + } + + pub fn get_proc_address(&self, symbol: &str) -> *const c_void { + let symbol_name = CFString::from_str(symbol).unwrap(); + 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 + } + + pub fn swap_buffers(&self) { + unsafe { + self.context.flushBuffer(); + let () = msg_send![self.view, setNeedsDisplay: YES]; + } + } +} + +impl Drop for GlContext { + fn drop(&mut self) { + unsafe { + let () = msg_send![self.context, release]; + let () = msg_send![self.view, release]; + } + } +} diff --git a/src/gl/mod.rs b/src/gl/mod.rs new file mode 100644 index 0000000..a7922b7 --- /dev/null +++ b/src/gl/mod.rs @@ -0,0 +1,109 @@ +use std::ffi::c_void; +use std::marker::PhantomData; + +// On X11 creating the context is a two step process +#[cfg(not(target_os = "linux"))] +use raw_window_handle::HasRawWindowHandle; + +#[cfg(target_os = "windows")] +mod win; +#[cfg(target_os = "windows")] +use win as platform; + +// We need to use this directly within the X11 window creation to negotiate the correct visual +#[cfg(target_os = "linux")] +pub(crate) mod x11; +#[cfg(target_os = "linux")] +pub(crate) use self::x11 as platform; + +#[cfg(target_os = "macos")] +mod macos; +#[cfg(target_os = "macos")] +use macos as platform; + +#[derive(Clone, Debug)] +pub struct GlConfig { + pub version: (u8, u8), + pub profile: Profile, + pub red_bits: u8, + pub blue_bits: u8, + pub green_bits: u8, + pub alpha_bits: u8, + pub depth_bits: u8, + pub stencil_bits: u8, + pub samples: Option, + pub srgb: bool, + pub double_buffer: bool, + pub vsync: bool, +} + +impl Default for GlConfig { + fn default() -> Self { + GlConfig { + version: (3, 2), + profile: Profile::Core, + red_bits: 8, + blue_bits: 8, + green_bits: 8, + alpha_bits: 8, + depth_bits: 24, + stencil_bits: 8, + samples: None, + srgb: true, + double_buffer: true, + vsync: false, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Profile { + Compatibility, + Core, +} + +#[derive(Debug)] +pub enum GlError { + InvalidWindowHandle, + VersionNotSupported, + CreationFailed(platform::CreationFailedError), +} + +pub struct GlContext { + context: platform::GlContext, + phantom: PhantomData<*mut ()>, +} + +impl GlContext { + #[cfg(not(target_os = "linux"))] + pub(crate) unsafe fn create( + parent: &impl HasRawWindowHandle, config: GlConfig, + ) -> Result { + platform::GlContext::create(parent, config) + .map(|context| GlContext { context, phantom: PhantomData }) + } + + /// The X11 version needs to be set up in a different way compared to the Windows and macOS + /// versions. So the platform-specific versions should be used to construct the context within + /// baseview, and then this object can be passed to the user. + #[cfg(target_os = "linux")] + pub(crate) fn new(context: platform::GlContext) -> GlContext { + GlContext { context, phantom: PhantomData } + } + + pub unsafe fn make_current(&self) { + self.context.make_current(); + } + + pub unsafe fn make_not_current(&self) { + self.context.make_not_current(); + } + + pub fn get_proc_address(&self, symbol: &str) -> *const c_void { + self.context.get_proc_address(symbol) + } + + pub fn swap_buffers(&self) { + self.context.swap_buffers(); + } +} diff --git a/src/gl/win.rs b/src/gl/win.rs new file mode 100644 index 0000000..f98734f --- /dev/null +++ b/src/gl/win.rs @@ -0,0 +1,306 @@ +use std::ffi::{c_void, CString, OsStr}; +use std::os::windows::ffi::OsStrExt; + +use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; + +use winapi::shared::minwindef::{HINSTANCE, HMODULE}; +use winapi::shared::ntdef::WCHAR; +use winapi::shared::windef::{HDC, HGLRC, HWND}; +use winapi::um::libloaderapi::{FreeLibrary, GetProcAddress, LoadLibraryA}; +use winapi::um::wingdi::{ + wglCreateContext, wglDeleteContext, wglGetProcAddress, wglMakeCurrent, ChoosePixelFormat, + DescribePixelFormat, SetPixelFormat, SwapBuffers, PFD_DOUBLEBUFFER, PFD_DRAW_TO_WINDOW, + PFD_MAIN_PLANE, PFD_SUPPORT_OPENGL, PFD_TYPE_RGBA, PIXELFORMATDESCRIPTOR, +}; +use winapi::um::winnt::IMAGE_DOS_HEADER; +use winapi::um::winuser::{ + CreateWindowExW, DefWindowProcW, DestroyWindow, GetDC, RegisterClassW, ReleaseDC, + UnregisterClassW, CS_OWNDC, CW_USEDEFAULT, WNDCLASSW, +}; + +use super::{GlConfig, GlError, Profile}; + +// See https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_create_context.txt + +type WglCreateContextAttribsARB = extern "system" fn(HDC, HGLRC, *const i32) -> HGLRC; + +const WGL_CONTEXT_MAJOR_VERSION_ARB: i32 = 0x2091; +const WGL_CONTEXT_MINOR_VERSION_ARB: i32 = 0x2092; +const WGL_CONTEXT_PROFILE_MASK_ARB: i32 = 0x9126; + +const WGL_CONTEXT_CORE_PROFILE_BIT_ARB: i32 = 0x00000001; +const WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB: i32 = 0x00000002; + +// See https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_pixel_format.txt + +type WglChoosePixelFormatARB = + extern "system" fn(HDC, *const i32, *const f32, u32, *mut i32, *mut u32) -> i32; + +const WGL_DRAW_TO_WINDOW_ARB: i32 = 0x2001; +const WGL_ACCELERATION_ARB: i32 = 0x2003; +const WGL_SUPPORT_OPENGL_ARB: i32 = 0x2010; +const WGL_DOUBLE_BUFFER_ARB: i32 = 0x2011; +const WGL_PIXEL_TYPE_ARB: i32 = 0x2013; +const WGL_RED_BITS_ARB: i32 = 0x2015; +const WGL_GREEN_BITS_ARB: i32 = 0x2017; +const WGL_BLUE_BITS_ARB: i32 = 0x2019; +const WGL_ALPHA_BITS_ARB: i32 = 0x201B; +const WGL_DEPTH_BITS_ARB: i32 = 0x2022; +const WGL_STENCIL_BITS_ARB: i32 = 0x2023; + +const WGL_FULL_ACCELERATION_ARB: i32 = 0x2027; +const WGL_TYPE_RGBA_ARB: i32 = 0x202B; + +// See https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_multisample.txt + +const WGL_SAMPLE_BUFFERS_ARB: i32 = 0x2041; +const WGL_SAMPLES_ARB: i32 = 0x2042; + +// See https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_framebuffer_sRGB.txt + +const WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB: i32 = 0x20A9; + +// See https://www.khronos.org/registry/OpenGL/extensions/EXT/WGL_EXT_swap_control.txt + +type WglSwapIntervalEXT = extern "system" fn(i32) -> i32; + +pub type CreationFailedError = (); +pub struct GlContext { + hwnd: HWND, + hdc: HDC, + hglrc: HGLRC, + gl_library: HMODULE, +} + +extern "C" { + static __ImageBase: IMAGE_DOS_HEADER; +} + +impl GlContext { + pub unsafe fn create( + parent: &impl HasRawWindowHandle, config: GlConfig, + ) -> Result { + let handle = if let RawWindowHandle::Win32(handle) = parent.raw_window_handle() { + handle + } else { + 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()); + let mut class_name: Vec = OsStr::new(&class_name_str).encode_wide().collect(); + class_name.push(0); + + let hinstance = &__ImageBase as *const IMAGE_DOS_HEADER as HINSTANCE; + + let wnd_class = WNDCLASSW { + style: CS_OWNDC, + lpfnWndProc: Some(DefWindowProcW), + hInstance: hinstance, + lpszClassName: class_name.as_ptr(), + ..std::mem::zeroed() + }; + + let class = RegisterClassW(&wnd_class); + if class == 0 { + return Err(GlError::CreationFailed(())); + } + + let hwnd_tmp = CreateWindowExW( + 0, + class as *const WCHAR, + [0].as_ptr(), + 0, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + std::ptr::null_mut(), + std::ptr::null_mut(), + hinstance, + std::ptr::null_mut(), + ); + + if hwnd_tmp.is_null() { + return Err(GlError::CreationFailed(())); + } + + let hdc_tmp = GetDC(hwnd_tmp); + + let pfd_tmp = PIXELFORMATDESCRIPTOR { + nSize: std::mem::size_of::() as u16, + nVersion: 1, + dwFlags: PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + iPixelType: PFD_TYPE_RGBA, + cColorBits: 32, + cAlphaBits: 8, + cDepthBits: 24, + cStencilBits: 8, + iLayerType: PFD_MAIN_PLANE, + ..std::mem::zeroed() + }; + + SetPixelFormat(hdc_tmp, ChoosePixelFormat(hdc_tmp, &pfd_tmp), &pfd_tmp); + + let hglrc_tmp = wglCreateContext(hdc_tmp); + if hglrc_tmp.is_null() { + ReleaseDC(hwnd_tmp, hdc_tmp); + UnregisterClassW(class as *const WCHAR, hinstance); + DestroyWindow(hwnd_tmp); + return Err(GlError::CreationFailed(())); + } + + wglMakeCurrent(hdc_tmp, hglrc_tmp); + + #[allow(non_snake_case)] + let wglCreateContextAttribsARB: Option = { + let symbol = CString::new("wglCreateContextAttribsARB").unwrap(); + let addr = wglGetProcAddress(symbol.as_ptr()); + if !addr.is_null() { + Some(std::mem::transmute(addr)) + } else { + None + } + }; + + #[allow(non_snake_case)] + let wglChoosePixelFormatARB: Option = { + let symbol = CString::new("wglChoosePixelFormatARB").unwrap(); + let addr = wglGetProcAddress(symbol.as_ptr()); + if !addr.is_null() { + Some(std::mem::transmute(addr)) + } else { + None + } + }; + + #[allow(non_snake_case)] + let wglSwapIntervalEXT: Option = { + let symbol = CString::new("wglSwapIntervalEXT").unwrap(); + let addr = wglGetProcAddress(symbol.as_ptr()); + if !addr.is_null() { + Some(std::mem::transmute(addr)) + } else { + None + } + }; + + wglMakeCurrent(hdc_tmp, std::ptr::null_mut()); + ReleaseDC(hwnd_tmp, hdc_tmp); + UnregisterClassW(class as *const WCHAR, hinstance); + DestroyWindow(hwnd_tmp); + + // Create actual context + + let hwnd = handle.hwnd as HWND; + + let hdc = GetDC(hwnd); + + #[rustfmt::skip] + let pixel_format_attribs = [ + WGL_DRAW_TO_WINDOW_ARB, 1, + WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, + WGL_SUPPORT_OPENGL_ARB, 1, + WGL_DOUBLE_BUFFER_ARB, config.double_buffer as i32, + WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, + WGL_RED_BITS_ARB, config.red_bits as i32, + WGL_GREEN_BITS_ARB, config.green_bits as i32, + WGL_BLUE_BITS_ARB, config.blue_bits as i32, + WGL_ALPHA_BITS_ARB, config.alpha_bits as i32, + WGL_DEPTH_BITS_ARB, config.depth_bits as i32, + WGL_STENCIL_BITS_ARB, config.stencil_bits as i32, + WGL_SAMPLE_BUFFERS_ARB, config.samples.is_some() as i32, + WGL_SAMPLES_ARB, config.samples.unwrap_or(0) as i32, + WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, config.srgb as i32, + 0, + ]; + + let mut pixel_format = 0; + let mut num_formats = 0; + wglChoosePixelFormatARB.unwrap()( + hdc, + pixel_format_attribs.as_ptr(), + std::ptr::null(), + 1, + &mut pixel_format, + &mut num_formats, + ); + + let mut pfd: PIXELFORMATDESCRIPTOR = std::mem::zeroed(); + DescribePixelFormat( + hdc, + pixel_format, + std::mem::size_of::() as u32, + &mut pfd, + ); + SetPixelFormat(hdc, pixel_format, &pfd); + + let profile_mask = match config.profile { + Profile::Core => WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + Profile::Compatibility => WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, + }; + + #[rustfmt::skip] + let ctx_attribs = [ + WGL_CONTEXT_MAJOR_VERSION_ARB, config.version.0 as i32, + WGL_CONTEXT_MINOR_VERSION_ARB, config.version.1 as i32, + WGL_CONTEXT_PROFILE_MASK_ARB, profile_mask, + 0 + ]; + + let hglrc = + wglCreateContextAttribsARB.unwrap()(hdc, std::ptr::null_mut(), ctx_attribs.as_ptr()); + if hglrc.is_null() { + return Err(GlError::CreationFailed(())); + } + + let gl_library_name = CString::new("opengl32.dll").unwrap(); + let gl_library = LoadLibraryA(gl_library_name.as_ptr()); + + wglMakeCurrent(hdc, hglrc); + wglSwapIntervalEXT.unwrap()(config.vsync as i32); + wglMakeCurrent(hdc, std::ptr::null_mut()); + + Ok(GlContext { hwnd, hdc, hglrc, gl_library }) + } + + pub unsafe fn make_current(&self) { + wglMakeCurrent(self.hdc, self.hglrc); + } + + pub unsafe fn make_not_current(&self) { + wglMakeCurrent(self.hdc, std::ptr::null_mut()); + } + + pub fn get_proc_address(&self, symbol: &str) -> *const c_void { + let symbol = CString::new(symbol).unwrap(); + let addr = unsafe { wglGetProcAddress(symbol.as_ptr()) as *const c_void }; + if !addr.is_null() { + addr + } else { + unsafe { GetProcAddress(self.gl_library, symbol.as_ptr()) as *const c_void } + } + } + + pub fn swap_buffers(&self) { + unsafe { + SwapBuffers(self.hdc); + } + } +} + +impl Drop for GlContext { + fn drop(&mut self) { + unsafe { + wglMakeCurrent(std::ptr::null_mut(), std::ptr::null_mut()); + wglDeleteContext(self.hglrc); + ReleaseDC(self.hwnd, self.hdc); + FreeLibrary(self.gl_library); + } + } +} diff --git a/src/gl/x11.rs b/src/gl/x11.rs new file mode 100644 index 0000000..f62d318 --- /dev/null +++ b/src/gl/x11.rs @@ -0,0 +1,253 @@ +use std::ffi::{c_void, CString}; +use std::os::raw::{c_int, c_ulong}; + +use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; + +use x11::glx; +use x11::xlib; + +use super::{GlConfig, GlError, Profile}; + +mod errors; + +#[derive(Debug)] +pub enum CreationFailedError { + InvalidFBConfig, + NoVisual, + GetProcAddressFailed, + MakeCurrentFailed, + ContextCreationFailed, + X11Error(errors::XLibError), +} + +impl From for GlError { + fn from(e: errors::XLibError) -> Self { + GlError::CreationFailed(CreationFailedError::X11Error(e)) + } +} + +// See https://www.khronos.org/registry/OpenGL/extensions/ARB/GLX_ARB_create_context.txt + +type GlXCreateContextAttribsARB = unsafe extern "C" fn( + dpy: *mut xlib::Display, + fbc: glx::GLXFBConfig, + share_context: glx::GLXContext, + direct: xlib::Bool, + attribs: *const c_int, +) -> glx::GLXContext; + +// See https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_swap_control.txt + +type GlXSwapIntervalEXT = + unsafe extern "C" fn(dpy: *mut xlib::Display, drawable: glx::GLXDrawable, interval: i32); + +// See https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_framebuffer_sRGB.txt + +const GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB: i32 = 0x20B2; + +fn get_proc_address(symbol: &str) -> *const c_void { + let symbol = CString::new(symbol).unwrap(); + unsafe { glx::glXGetProcAddress(symbol.as_ptr() as *const u8).unwrap() as *const c_void } +} + +pub struct GlContext { + window: c_ulong, + display: *mut xlib::_XDisplay, + context: glx::GLXContext, +} + +/// The frame buffer configuration along with the general OpenGL configuration to somewhat minimize +/// misuse. +pub struct FbConfig { + gl_config: GlConfig, + fb_config: *mut glx::__GLXFBConfigRec, +} + +/// The configuration a window should be created with after calling +/// [GlContext::get_fb_config_and_visual]. +pub struct WindowConfig { + pub depth: u8, + pub visual: u32, +} + +impl GlContext { + /// Creating an OpenGL context under X11 works slightly different. Different OpenGL + /// configurations require different framebuffer configurations, and to be able to use that + /// context with a window the window needs to be created with a matching visual. This means that + /// you need to decide on the framebuffer config before creating the window, ask the X11 server + /// for a matching visual for that framebuffer config, crate the window with that visual, and + /// only then create the OpenGL context. + /// + /// Use [Self::get_fb_config_and_visual] to create both of these things. + pub unsafe fn create( + parent: &impl HasRawWindowHandle, config: FbConfig, + ) -> Result { + let handle = if let RawWindowHandle::Xlib(handle) = parent.raw_window_handle() { + handle + } else { + return Err(GlError::InvalidWindowHandle); + }; + + if handle.display.is_null() { + return Err(GlError::InvalidWindowHandle); + } + + let display = handle.display as *mut xlib::_XDisplay; + + errors::XErrorHandler::handle(display, |error_handler| { + #[allow(non_snake_case)] + let glXCreateContextAttribsARB: GlXCreateContextAttribsARB = { + let addr = get_proc_address("glXCreateContextAttribsARB"); + if addr.is_null() { + return Err(GlError::CreationFailed(CreationFailedError::GetProcAddressFailed)); + } else { + std::mem::transmute(addr) + } + }; + + #[allow(non_snake_case)] + let glXSwapIntervalEXT: GlXSwapIntervalEXT = { + let addr = get_proc_address("glXSwapIntervalEXT"); + if addr.is_null() { + return Err(GlError::CreationFailed(CreationFailedError::GetProcAddressFailed)); + } else { + std::mem::transmute(addr) + } + }; + + error_handler.check()?; + + let profile_mask = match config.gl_config.profile { + Profile::Core => glx::arb::GLX_CONTEXT_CORE_PROFILE_BIT_ARB, + Profile::Compatibility => glx::arb::GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, + }; + + #[rustfmt::skip] + let ctx_attribs = [ + glx::arb::GLX_CONTEXT_MAJOR_VERSION_ARB, config.gl_config.version.0 as i32, + glx::arb::GLX_CONTEXT_MINOR_VERSION_ARB, config.gl_config.version.1 as i32, + glx::arb::GLX_CONTEXT_PROFILE_MASK_ARB, profile_mask, + 0, + ]; + + let context = glXCreateContextAttribsARB( + display, + config.fb_config, + std::ptr::null_mut(), + 1, + ctx_attribs.as_ptr(), + ); + + error_handler.check()?; + + if context.is_null() { + return Err(GlError::CreationFailed(CreationFailedError::ContextCreationFailed)); + } + + let res = glx::glXMakeCurrent(display, handle.window, context); + error_handler.check()?; + if res == 0 { + return Err(GlError::CreationFailed(CreationFailedError::MakeCurrentFailed)); + } + + glXSwapIntervalEXT(display, handle.window, config.gl_config.vsync as i32); + error_handler.check()?; + + if glx::glXMakeCurrent(display, 0, std::ptr::null_mut()) == 0 { + error_handler.check()?; + return Err(GlError::CreationFailed(CreationFailedError::MakeCurrentFailed)); + } + + Ok(GlContext { window: handle.window, display, context }) + }) + } + + /// Find a matching framebuffer config and window visual for the given OpenGL configuration. + /// This needs to be passed to [Self::create] along with a handle to a window that was created + /// using the visual also returned from this function. + pub unsafe fn get_fb_config_and_visual( + display: *mut xlib::_XDisplay, config: GlConfig, + ) -> Result<(FbConfig, WindowConfig), GlError> { + errors::XErrorHandler::handle(display, |error_handler| { + let screen = xlib::XDefaultScreen(display); + + #[rustfmt::skip] + let fb_attribs = [ + glx::GLX_X_RENDERABLE, 1, + glx::GLX_X_VISUAL_TYPE, glx::GLX_TRUE_COLOR, + glx::GLX_DRAWABLE_TYPE, glx::GLX_WINDOW_BIT, + glx::GLX_RENDER_TYPE, glx::GLX_RGBA_BIT, + glx::GLX_RED_SIZE, config.red_bits as i32, + glx::GLX_GREEN_SIZE, config.green_bits as i32, + glx::GLX_BLUE_SIZE, config.blue_bits as i32, + glx::GLX_ALPHA_SIZE, config.alpha_bits as i32, + glx::GLX_DEPTH_SIZE, config.depth_bits as i32, + glx::GLX_STENCIL_SIZE, config.stencil_bits as i32, + glx::GLX_DOUBLEBUFFER, config.double_buffer as i32, + glx::GLX_SAMPLE_BUFFERS, config.samples.is_some() as i32, + glx::GLX_SAMPLES, config.samples.unwrap_or(0) as i32, + GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, config.srgb as i32, + 0, + ]; + + let mut n_configs = 0; + let fb_config = + glx::glXChooseFBConfig(display, screen, fb_attribs.as_ptr(), &mut n_configs); + + error_handler.check()?; + if n_configs <= 0 || fb_config.is_null() { + return Err(GlError::CreationFailed(CreationFailedError::InvalidFBConfig)); + } + + // Now that we have a matching framebuffer config, we need to know which visual matches + // thsi config so the window is compatible with the OpenGL context we're about to create + let fb_config = *fb_config; + let visual = glx::glXGetVisualFromFBConfig(display, fb_config); + if visual.is_null() { + return Err(GlError::CreationFailed(CreationFailedError::NoVisual)); + } + + Ok(( + FbConfig { fb_config, gl_config: config }, + WindowConfig { depth: (*visual).depth as u8, visual: (*visual).visualid as u32 }, + )) + }) + } + + pub unsafe fn make_current(&self) { + errors::XErrorHandler::handle(self.display, |error_handler| { + let res = glx::glXMakeCurrent(self.display, self.window, self.context); + error_handler.check().unwrap(); + if res == 0 { + panic!("make_current failed") + } + }) + } + + pub unsafe fn make_not_current(&self) { + errors::XErrorHandler::handle(self.display, |error_handler| { + let res = glx::glXMakeCurrent(self.display, 0, std::ptr::null_mut()); + error_handler.check().unwrap(); + if res == 0 { + panic!("make_not_current failed") + } + }) + } + + pub fn get_proc_address(&self, symbol: &str) -> *const c_void { + get_proc_address(symbol) + } + + pub fn swap_buffers(&self) { + errors::XErrorHandler::handle(self.display, |error_handler| { + unsafe { + glx::glXSwapBuffers(self.display, self.window); + } + error_handler.check().unwrap(); + }) + } +} + +impl Drop for GlContext { + fn drop(&mut self) {} +} diff --git a/src/gl/x11/errors.rs b/src/gl/x11/errors.rs new file mode 100644 index 0000000..42847d9 --- /dev/null +++ b/src/gl/x11/errors.rs @@ -0,0 +1,122 @@ +use std::ffi::CStr; +use std::fmt::{Debug, Formatter}; +use x11::xlib; + +use std::cell::RefCell; +use std::panic::AssertUnwindSafe; + +thread_local! { + /// 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> = RefCell::new(None); +} + +/// A helper struct for safe X11 error handling +pub struct XErrorHandler<'a> { + display: *mut xlib::Display, + error: &'a RefCell>, +} + +impl<'a> XErrorHandler<'a> { + /// Syncs and checks if any previous X11 calls returned an error + pub fn check(&mut self) -> Result<(), XLibError> { + // Flush all possible previous errors + unsafe { + xlib::XSync(self.display, 0); + } + let error = self.error.borrow_mut().take(); + + match error { + None => Ok(()), + Some(inner) => Err(XLibError { inner }), + } + } + + /// Sets up a temporary X11 error handler for the duration of the given closure, and allows + /// that closure to check on the latest X11 error at any time + pub fn handle T>( + display: *mut xlib::Display, handler: F, + ) -> T { + 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; + + CURRENT_X11_ERROR.with(|error| { + let mut error = error.borrow_mut(); + match error.as_mut() { + // If multiple errors occur, keep the first one since that's likely going to be the + // cause of the other errors + Some(_) => 1, + None => { + *error = Some(err); + 0 + } + } + }) + } + + // Flush all possible previous errors + unsafe { + xlib::XSync(display, 0); + } + + CURRENT_X11_ERROR.with(|error| { + // Make sure to clear any errors from the last call to this function + *error.borrow_mut() = None; + + let old_handler = unsafe { xlib::XSetErrorHandler(Some(error_handler)) }; + let panic_result = std::panic::catch_unwind(AssertUnwindSafe(|| { + let mut h = XErrorHandler { display, error }; + handler(&mut h) + })); + // Whatever happened, restore old error handler + unsafe { xlib::XSetErrorHandler(old_handler) }; + + match panic_result { + Ok(v) => v, + Err(e) => std::panic::resume_unwind(e), + } + }) + } +} + +pub struct XLibError { + inner: xlib::XErrorEvent, +} + +impl XLibError { + pub fn get_display_name(&self, buf: &mut [u8]) -> &CStr { + unsafe { + xlib::XGetErrorText( + self.inner.display, + self.inner.error_code.into(), + buf.as_mut_ptr().cast(), + (buf.len() - 1) as i32, + ); + } + + *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()) } + } +} + +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) + .finish() + } +} diff --git a/src/keyboard.rs b/src/keyboard.rs index c2af463..c56abd3 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -17,6 +17,7 @@ //! Keyboard types. +#[cfg(any(target_os = "linux", target_os = "macos"))] use keyboard_types::{Code, Location}; #[cfg(any(target_os = "linux", target_os = "macos"))] diff --git a/src/lib.rs b/src/lib.rs index 5a61586..46a3695 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,9 @@ mod window; mod window_info; mod window_open_options; +#[cfg(feature = "opengl")] +pub mod gl; + pub use event::*; pub use mouse_cursor::MouseCursor; pub use window::*; diff --git a/src/macos/window.rs b/src/macos/window.rs index 5b71b6f..0875377 100644 --- a/src/macos/window.rs +++ b/src/macos/window.rs @@ -1,5 +1,6 @@ use std::ffi::c_void; use std::marker::PhantomData; +use std::ptr; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -16,7 +17,7 @@ use keyboard_types::KeyboardEvent; use objc::{msg_send, runtime::Object, sel, sel_impl}; -use raw_window_handle::{macos::MacOSHandle, HasRawWindowHandle, RawWindowHandle}; +use raw_window_handle::{AppKitHandle, HasRawWindowHandle, RawWindowHandle}; use crate::{ Event, EventStatus, WindowEvent, WindowHandler, WindowInfo, WindowOpenOptions, @@ -26,6 +27,12 @@ use crate::{ use super::keyboard::KeyboardState; use super::view::{create_view, BASEVIEW_STATE_IVAR}; +#[cfg(feature = "opengl")] +use crate::{ + gl::{GlConfig, GlContext}, + window::RawWindowHandleWrapper, +}; + pub struct WindowHandle { raw_window_handle: Option, close_requested: Arc, @@ -37,7 +44,7 @@ pub struct WindowHandle { impl WindowHandle { pub fn close(&mut self) { - if let Some(_) = self.raw_window_handle.take() { + if self.raw_window_handle.take().is_some() { self.close_requested.store(true, Ordering::Relaxed); } } @@ -55,7 +62,7 @@ unsafe impl HasRawWindowHandle for WindowHandle { } } - RawWindowHandle::MacOS(MacOSHandle { ..MacOSHandle::empty() }) + RawWindowHandle::AppKit(AppKitHandle::empty()) } } @@ -102,6 +109,9 @@ pub struct Window { /// Our subclassed NSView ns_view: id, close_requested: bool, + + #[cfg(feature = "opengl")] + gl_context: Option, } impl Window { @@ -114,7 +124,7 @@ impl Window { { let pool = unsafe { NSAutoreleasePool::new(nil) }; - let handle = if let RawWindowHandle::MacOS(handle) = parent.raw_window_handle() { + let handle = if let RawWindowHandle::AppKit(handle) = parent.raw_window_handle() { handle } else { panic!("Not a macOS window"); @@ -122,7 +132,17 @@ impl Window { let ns_view = unsafe { create_view(&options) }; - let window = Window { ns_app: None, ns_window: None, ns_view, close_requested: false }; + let window = Window { + ns_app: None, + ns_window: None, + ns_view, + close_requested: false, + + #[cfg(feature = "opengl")] + gl_context: options + .gl_config + .map(|gl_config| Self::create_gl_context(None, ns_view, gl_config)), + }; let window_handle = Self::init(true, window, build); @@ -146,7 +166,17 @@ impl Window { let ns_view = unsafe { create_view(&options) }; - let window = Window { ns_app: None, ns_window: None, ns_view, close_requested: false }; + let window = Window { + ns_app: None, + ns_window: None, + ns_view, + close_requested: false, + + #[cfg(feature = "opengl")] + gl_context: options + .gl_config + .map(|gl_config| Self::create_gl_context(None, ns_view, gl_config)), + }; let window_handle = Self::init(true, window, build); @@ -217,6 +247,11 @@ impl Window { ns_window: Some(ns_window), ns_view, close_requested: false, + + #[cfg(feature = "opengl")] + gl_context: options + .gl_config + .map(|gl_config| Self::create_gl_context(Some(ns_window), ns_view, gl_config)), }; let _ = Self::init(false, window, build); @@ -266,6 +301,21 @@ impl Window { pub fn close(&mut self) { self.close_requested = true; } + + #[cfg(feature = "opengl")] + pub fn gl_context(&self) -> Option<&GlContext> { + self.gl_context.as_ref() + } + + #[cfg(feature = "opengl")] + fn create_gl_context(ns_window: Option, ns_view: id, config: GlConfig) -> GlContext { + let mut handle = AppKitHandle::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 = RawWindowHandleWrapper { handle: RawWindowHandle::AppKit(handle) }; + + unsafe { GlContext::create(&handle, config).expect("Could not create OpenGL context") } + } } pub(super) struct WindowState { @@ -373,7 +423,7 @@ impl WindowState { // Clear ivar before triggering WindowEvent::WillClose. Otherwise, if the // handler of the event causes another call to release, this function could be // called again, leading to a double free. - ns_view_obj.set_ivar(BASEVIEW_STATE_IVAR, ::std::ptr::null() as *const c_void); + ns_view_obj.set_ivar(BASEVIEW_STATE_IVAR, ptr::null() as *const c_void); window_state.trigger_event(Event::Window(WindowEvent::WillClose)); @@ -386,12 +436,12 @@ impl WindowState { unsafe impl HasRawWindowHandle for Window { fn raw_window_handle(&self) -> RawWindowHandle { - let ns_window = self.ns_window.unwrap_or(::std::ptr::null_mut()) as *mut c_void; + let ns_window = self.ns_window.unwrap_or(ptr::null_mut()) as *mut c_void; - RawWindowHandle::MacOS(MacOSHandle { - ns_window, - ns_view: self.ns_view as *mut c_void, - ..MacOSHandle::empty() - }) + let mut handle = AppKitHandle::empty(); + handle.ns_window = ns_window; + handle.ns_view = self.ns_view as *mut c_void; + + RawWindowHandle::AppKit(handle) } } diff --git a/src/win/keyboard.rs b/src/win/keyboard.rs index f04d2cc..e4045d1 100644 --- a/src/win/keyboard.rs +++ b/src/win/keyboard.rs @@ -19,7 +19,6 @@ use std::cmp::Ordering; use std::collections::{HashMap, HashSet}; -use std::convert::TryInto; use std::mem; use std::ops::RangeInclusive; @@ -29,15 +28,15 @@ use winapi::shared::minwindef::{HKL, INT, LPARAM, UINT, WPARAM}; use winapi::shared::ntdef::SHORT; use winapi::shared::windef::HWND; use winapi::um::winuser::{ - GetKeyState, GetKeyboardLayout, MapVirtualKeyExW, PeekMessageW, ToUnicodeEx, VkKeyScanW, - MAPVK_VK_TO_CHAR, MAPVK_VSC_TO_VK_EX, PM_NOREMOVE, VK_ACCEPT, VK_ADD, VK_APPS, VK_ATTN, - VK_BACK, VK_BROWSER_BACK, VK_BROWSER_FAVORITES, VK_BROWSER_FORWARD, VK_BROWSER_HOME, - VK_BROWSER_REFRESH, VK_BROWSER_SEARCH, VK_BROWSER_STOP, VK_CANCEL, VK_CAPITAL, VK_CLEAR, - VK_CONTROL, VK_CONVERT, VK_CRSEL, VK_DECIMAL, VK_DELETE, VK_DIVIDE, VK_DOWN, VK_END, VK_EREOF, - VK_ESCAPE, VK_EXECUTE, VK_EXSEL, VK_F1, VK_F10, VK_F11, VK_F12, VK_F2, VK_F3, VK_F4, VK_F5, - VK_F6, VK_F7, VK_F8, VK_F9, VK_FINAL, VK_HELP, VK_HOME, VK_INSERT, VK_JUNJA, VK_KANA, VK_KANJI, - VK_LAUNCH_APP1, VK_LAUNCH_APP2, VK_LAUNCH_MAIL, VK_LAUNCH_MEDIA_SELECT, VK_LCONTROL, VK_LEFT, - VK_LMENU, VK_LSHIFT, VK_LWIN, VK_MEDIA_NEXT_TRACK, VK_MEDIA_PLAY_PAUSE, VK_MEDIA_PREV_TRACK, + GetKeyState, GetKeyboardLayout, MapVirtualKeyExW, PeekMessageW, ToUnicodeEx, MAPVK_VK_TO_CHAR, + MAPVK_VSC_TO_VK_EX, PM_NOREMOVE, VK_ACCEPT, VK_ADD, VK_APPS, VK_ATTN, VK_BACK, VK_BROWSER_BACK, + VK_BROWSER_FAVORITES, VK_BROWSER_FORWARD, VK_BROWSER_HOME, VK_BROWSER_REFRESH, + VK_BROWSER_SEARCH, VK_BROWSER_STOP, VK_CANCEL, VK_CAPITAL, VK_CLEAR, VK_CONTROL, VK_CONVERT, + VK_CRSEL, VK_DECIMAL, VK_DELETE, VK_DIVIDE, VK_DOWN, VK_END, VK_EREOF, VK_ESCAPE, VK_EXECUTE, + VK_EXSEL, VK_F1, VK_F10, VK_F11, VK_F12, VK_F2, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, + VK_F9, VK_FINAL, VK_HELP, VK_HOME, VK_INSERT, VK_JUNJA, VK_KANA, VK_KANJI, VK_LAUNCH_APP1, + VK_LAUNCH_APP2, VK_LAUNCH_MAIL, VK_LAUNCH_MEDIA_SELECT, VK_LCONTROL, VK_LEFT, VK_LMENU, + VK_LSHIFT, VK_LWIN, VK_MEDIA_NEXT_TRACK, VK_MEDIA_PLAY_PAUSE, VK_MEDIA_PREV_TRACK, VK_MEDIA_STOP, VK_MENU, VK_MODECHANGE, VK_MULTIPLY, VK_NEXT, VK_NONCONVERT, VK_NUMLOCK, VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7, VK_NUMPAD8, VK_NUMPAD9, VK_OEM_ATTN, VK_OEM_CLEAR, VK_PAUSE, VK_PLAY, VK_PRINT, VK_PRIOR, @@ -344,110 +343,6 @@ fn vk_to_key(vk: VkCode) -> Option { }) } -/// Convert a key to a virtual key code. -/// -/// The virtual key code is needed in various winapi interfaces, including -/// accelerators. This provides the virtual key code in the current keyboard -/// map. -/// -/// The virtual key code can have modifiers in the higher order byte when the -/// argument is a `Character` variant. See: -/// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-vkkeyscanw -pub(crate) fn key_to_vk(key: &Key) -> Option { - Some(match key { - Key::Character(s) => { - if let Some(code_point) = s.chars().next() { - if let Ok(wchar) = (code_point as u32).try_into() { - unsafe { VkKeyScanW(wchar) as i32 } - } else { - return None; - } - } else { - return None; - } - } - Key::Cancel => VK_CANCEL, - Key::Backspace => VK_BACK, - Key::Tab => VK_TAB, - Key::Clear => VK_CLEAR, - Key::Enter => VK_RETURN, - Key::Shift => VK_SHIFT, - Key::Control => VK_CONTROL, - Key::Alt => VK_MENU, - Key::Pause => VK_PAUSE, - Key::CapsLock => VK_CAPITAL, - // TODO: disambiguate kana and hangul? same vk - Key::KanaMode => VK_KANA, - Key::JunjaMode => VK_JUNJA, - Key::FinalMode => VK_FINAL, - Key::KanjiMode => VK_KANJI, - Key::Escape => VK_ESCAPE, - Key::NonConvert => VK_NONCONVERT, - Key::Accept => VK_ACCEPT, - Key::PageUp => VK_PRIOR, - Key::PageDown => VK_NEXT, - Key::End => VK_END, - Key::Home => VK_HOME, - Key::ArrowLeft => VK_LEFT, - Key::ArrowUp => VK_UP, - Key::ArrowRight => VK_RIGHT, - Key::ArrowDown => VK_DOWN, - Key::Select => VK_SELECT, - Key::Print => VK_PRINT, - Key::Execute => VK_EXECUTE, - Key::PrintScreen => VK_SNAPSHOT, - Key::Insert => VK_INSERT, - Key::Delete => VK_DELETE, - Key::Help => VK_HELP, - Key::Meta => VK_LWIN, - Key::ContextMenu => VK_APPS, - Key::Standby => VK_SLEEP, - Key::F1 => VK_F1, - Key::F2 => VK_F2, - Key::F3 => VK_F3, - Key::F4 => VK_F4, - Key::F5 => VK_F5, - Key::F6 => VK_F6, - Key::F7 => VK_F7, - Key::F8 => VK_F8, - Key::F9 => VK_F9, - Key::F10 => VK_F10, - Key::F11 => VK_F11, - Key::F12 => VK_F12, - Key::NumLock => VK_NUMLOCK, - Key::ScrollLock => VK_SCROLL, - Key::BrowserBack => VK_BROWSER_BACK, - Key::BrowserForward => VK_BROWSER_FORWARD, - Key::BrowserRefresh => VK_BROWSER_REFRESH, - Key::BrowserStop => VK_BROWSER_STOP, - Key::BrowserSearch => VK_BROWSER_SEARCH, - Key::BrowserFavorites => VK_BROWSER_FAVORITES, - Key::BrowserHome => VK_BROWSER_HOME, - Key::AudioVolumeMute => VK_VOLUME_MUTE, - Key::AudioVolumeDown => VK_VOLUME_DOWN, - Key::AudioVolumeUp => VK_VOLUME_UP, - Key::MediaTrackNext => VK_MEDIA_NEXT_TRACK, - Key::MediaTrackPrevious => VK_MEDIA_PREV_TRACK, - Key::MediaStop => VK_MEDIA_STOP, - Key::MediaPlayPause => VK_MEDIA_PLAY_PAUSE, - Key::LaunchMail => VK_LAUNCH_MAIL, - Key::LaunchMediaPlayer => VK_LAUNCH_MEDIA_SELECT, - Key::LaunchApplication1 => VK_LAUNCH_APP1, - Key::LaunchApplication2 => VK_LAUNCH_APP2, - Key::Alphanumeric => VK_OEM_ATTN, - Key::Convert => VK_CONVERT, - Key::ModeChange => VK_MODECHANGE, - Key::Process => VK_PROCESSKEY, - Key::Attn => VK_ATTN, - Key::CrSel => VK_CRSEL, - Key::ExSel => VK_EXSEL, - Key::EraseEof => VK_EREOF, - Key::Play => VK_PLAY, - Key::ZoomToggle => VK_ZOOM, - _ => return None, - }) -} - fn code_unit_to_key(code_unit: u32) -> Key { match code_unit { 0x8 | 0x7F => Key::Backspace, @@ -692,6 +587,7 @@ impl KeyboardState { key_state[VK_LCONTROL as usize] = if has_altgr { 0x80 } else { 0 }; key_state[VK_MENU as usize] = if has_altgr { 0x80 } else { 0 }; key_state[VK_RMENU as usize] = if has_altgr { 0x80 } else { 0 }; + #[allow(clippy::iter_overeager_cloned)] for vk in PRINTABLE_VKS.iter().cloned().flatten() { let ret = ToUnicodeEx( vk as UINT, diff --git a/src/win/window.rs b/src/win/window.rs index f91ac17..2f4f056 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -16,14 +16,14 @@ use winapi::um::winuser::{ }; use std::cell::RefCell; -use std::ffi::OsStr; +use std::ffi::{c_void, OsStr}; use std::marker::PhantomData; use std::os::windows::ffi::OsStrExt; use std::ptr::null_mut; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; -use raw_window_handle::{windows::WindowsHandle, HasRawWindowHandle, RawWindowHandle}; +use raw_window_handle::{HasRawWindowHandle, RawWindowHandle, Win32Handle}; const BV_WINDOW_MUST_CLOSE: UINT = WM_USER + 1; @@ -34,6 +34,9 @@ use crate::{ use super::keyboard::KeyboardState; +#[cfg(feature = "opengl")] +use crate::{gl::GlContext, window::RawWindowHandleWrapper}; + unsafe fn generate_guid() -> String { let mut guid: GUID = std::mem::zeroed(); CoCreateGuid(&mut guid); @@ -80,12 +83,12 @@ impl WindowHandle { unsafe impl HasRawWindowHandle for WindowHandle { fn raw_window_handle(&self) -> RawWindowHandle { if let Some(hwnd) = self.hwnd { - RawWindowHandle::Windows(WindowsHandle { - hwnd: hwnd as *mut std::ffi::c_void, - ..WindowsHandle::empty() - }) + let mut handle = Win32Handle::empty(); + handle.hwnd = hwnd as *mut c_void; + + RawWindowHandle::Win32(handle) } else { - RawWindowHandle::Windows(WindowsHandle { ..WindowsHandle::empty() }) + RawWindowHandle::Win32(Win32Handle::empty()) } } } @@ -124,18 +127,17 @@ unsafe extern "system" fn wnd_proc( let window_state_ptr = GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *mut RefCell; if !window_state_ptr.is_null() { - let mut window = Window { hwnd }; - let mut window = crate::Window::new(&mut window); - match msg { WM_MOUSEMOVE => { + let mut window_state = (*window_state_ptr).borrow_mut(); + let mut window = window_state.create_window(hwnd); + let mut window = crate::Window::new(&mut window); + let x = (lparam & 0xFFFF) as i16 as i32; let y = ((lparam >> 16) & 0xFFFF) as i16 as i32; let physical_pos = PhyPoint { x, y }; - let mut window_state = (&*window_state_ptr).borrow_mut(); - let logical_pos = physical_pos.to_logical(&window_state.window_info); window_state.handler.on_event( @@ -145,12 +147,14 @@ unsafe extern "system" fn wnd_proc( return 0; } WM_MOUSEWHEEL => { + let mut window_state = (*window_state_ptr).borrow_mut(); + let mut window = window_state.create_window(hwnd); + let mut window = crate::Window::new(&mut window); + let value = (wparam >> 16) as i16; let value = value as i32; let value = value as f32 / WHEEL_DELTA as f32; - let mut window_state = (&*window_state_ptr).borrow_mut(); - window_state.handler.on_event( &mut window, Event::Mouse(MouseEvent::WheelScrolled(ScrollDelta::Lines { @@ -162,7 +166,11 @@ unsafe extern "system" fn wnd_proc( } WM_LBUTTONDOWN | WM_LBUTTONUP | WM_MBUTTONDOWN | WM_MBUTTONUP | WM_RBUTTONDOWN | WM_RBUTTONUP | WM_XBUTTONDOWN | WM_XBUTTONUP => { - let mut mouse_button_counter = (&*window_state_ptr).borrow().mouse_button_counter; + let mut window_state = (*window_state_ptr).borrow_mut(); + let mut window = window_state.create_window(hwnd); + let mut window = crate::Window::new(&mut window); + + let mut mouse_button_counter = window_state.mouse_button_counter; let button = match msg { WM_LBUTTONDOWN | WM_LBUTTONUP => Some(MouseButton::Left), @@ -198,44 +206,48 @@ unsafe extern "system" fn wnd_proc( } }; - (&*window_state_ptr).borrow_mut().mouse_button_counter = mouse_button_counter; + window_state.mouse_button_counter = mouse_button_counter; - (&*window_state_ptr) - .borrow_mut() - .handler - .on_event(&mut window, Event::Mouse(event)); + window_state.handler.on_event(&mut window, Event::Mouse(event)); } } WM_TIMER => { - match wparam { - WIN_FRAME_TIMER => { - (&*window_state_ptr).borrow_mut().handler.on_frame(&mut window); - } - _ => (), + let mut window_state = (*window_state_ptr).borrow_mut(); + let mut window = window_state.create_window(hwnd); + let mut window = crate::Window::new(&mut window); + + if wparam == WIN_FRAME_TIMER { + window_state.handler.on_frame(&mut window); } return 0; } WM_CLOSE => { - (&*window_state_ptr) - .borrow_mut() - .handler - .on_event(&mut window, Event::Window(WindowEvent::WillClose)); + // Make sure to release the borrow before the DefWindowProc call + { + let mut window_state = (*window_state_ptr).borrow_mut(); + let mut window = window_state.create_window(hwnd); + let mut window = crate::Window::new(&mut window); + + window_state + .handler + .on_event(&mut window, Event::Window(WindowEvent::WillClose)); + } + // DestroyWindow(hwnd); // return 0; return DefWindowProcW(hwnd, msg, wparam, lparam); } WM_CHAR | WM_SYSCHAR | WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP | WM_INPUTLANGCHANGE => { - let opt_event = (&*window_state_ptr) - .borrow_mut() - .keyboard_state - .process_message(hwnd, msg, wparam, lparam); + let mut window_state = (*window_state_ptr).borrow_mut(); + let mut window = window_state.create_window(hwnd); + let mut window = crate::Window::new(&mut window); + + let opt_event = + window_state.keyboard_state.process_message(hwnd, msg, wparam, lparam); if let Some(event) = opt_event { - (&*window_state_ptr) - .borrow_mut() - .handler - .on_event(&mut window, Event::Keyboard(event)); + window_state.handler.on_event(&mut window, Event::Keyboard(event)); } if msg != WM_SYSKEYDOWN { @@ -243,11 +255,13 @@ unsafe extern "system" fn wnd_proc( } } WM_SIZE => { + let mut window_state = (*window_state_ptr).borrow_mut(); + let mut window = window_state.create_window(hwnd); + let mut window = crate::Window::new(&mut window); + let width = (lparam & 0xFFFF) as u16 as u32; let height = ((lparam >> 16) & 0xFFFF) as u16 as u32; - let mut window_state = (&*window_state_ptr).borrow_mut(); - window_state.window_info = WindowInfo::from_physical_size( PhySize { width, height }, window_state.window_info.scale(), @@ -260,10 +274,10 @@ unsafe extern "system" fn wnd_proc( .on_event(&mut window, Event::Window(WindowEvent::Resized(window_info))); } WM_DPICHANGED => { + let mut window_state = (*window_state_ptr).borrow_mut(); + // To avoid weirdness with the realtime borrow checker. let new_rect = { - let mut window_state = (&*window_state_ptr).borrow_mut(); - if let WindowScalePolicy::SystemScaleFactor = window_state.scale_policy { let dpi = (wparam & 0xFFFF) as u16 as u32; let scale_factor = dpi as f64 / 96.0; @@ -319,7 +333,7 @@ unsafe extern "system" fn wnd_proc( } } - return DefWindowProcW(hwnd, msg, wparam, lparam); + DefWindowProcW(hwnd, msg, wparam, lparam) } unsafe fn register_wnd_class() -> ATOM { @@ -357,10 +371,28 @@ struct WindowState { handler: Box, scale_policy: WindowScalePolicy, dw_style: u32, + + #[cfg(feature = "opengl")] + gl_context: Arc>, +} + +impl WindowState { + #[cfg(not(feature = "opengl"))] + fn create_window(&self, hwnd: HWND) -> Window { + Window { hwnd } + } + + #[cfg(feature = "opengl")] + fn create_window(&self, hwnd: HWND) -> Window { + Window { hwnd, gl_context: self.gl_context.clone() } + } } pub struct Window { hwnd: HWND, + + #[cfg(feature = "opengl")] + gl_context: Arc>, } impl Window { @@ -372,7 +404,7 @@ impl Window { B: Send + 'static, { let parent = match parent.raw_window_handle() { - RawWindowHandle::Windows(h) => h.hwnd as HWND, + RawWindowHandle::Win32(h) => h.hwnd as HWND, h => panic!("unsupported parent handle {:?}", h), }; @@ -410,8 +442,8 @@ impl Window { break; } - TranslateMessage(&mut msg); - DispatchMessageW(&mut msg); + TranslateMessage(&msg); + DispatchMessageW(&msg); } } } @@ -478,7 +510,22 @@ impl Window { ); // todo: manage error ^ + #[cfg(feature = "opengl")] + let gl_context: Arc> = Arc::new(options.gl_config.map(|gl_config| { + let mut handle = Win32Handle::empty(); + handle.hwnd = hwnd as *mut c_void; + let handle = RawWindowHandleWrapper { handle: RawWindowHandle::Win32(handle) }; + + GlContext::create(&handle, gl_config).expect("Could not create OpenGL context") + })); + + #[cfg(not(feature = "opengl"))] let handler = Box::new(build(&mut crate::Window::new(&mut Window { hwnd }))); + #[cfg(feature = "opengl")] + let handler = Box::new(build(&mut crate::Window::new(&mut Window { + hwnd, + gl_context: gl_context.clone(), + }))); let (parent_handle, window_handle) = ParentHandle::new(hwnd); let parent_handle = if parented { Some(parent_handle) } else { None }; @@ -492,6 +539,9 @@ impl Window { handler, scale_policy: options.scale, dw_style: flags, + + #[cfg(feature = "opengl")] + gl_context, })); // Only works on Windows 10 unfortunately. @@ -556,13 +606,18 @@ impl Window { PostMessageW(self.hwnd, BV_WINDOW_MUST_CLOSE, 0, 0); } } + + #[cfg(feature = "opengl")] + pub fn gl_context(&self) -> Option<&GlContext> { + self.gl_context.as_ref().as_ref() + } } unsafe impl HasRawWindowHandle for Window { fn raw_window_handle(&self) -> RawWindowHandle { - RawWindowHandle::Windows(WindowsHandle { - hwnd: self.hwnd as *mut std::ffi::c_void, - ..WindowsHandle::empty() - }) + let mut handle = Win32Handle::empty(); + handle.hwnd = self.hwnd as *mut c_void; + + RawWindowHandle::Win32(handle) } } diff --git a/src/window.rs b/src/window.rs index 9b5dfbb..a0d91f6 100644 --- a/src/window.rs +++ b/src/window.rs @@ -18,6 +18,12 @@ pub struct WindowHandle { phantom: PhantomData<*mut ()>, } +/// Quick wrapper to satisfy [HasRawWindowHandle], because of course a raw window handle wouldn't +/// have a raw window handle, that would be silly. +pub(crate) struct RawWindowHandleWrapper { + pub handle: RawWindowHandle, +} + impl WindowHandle { fn new(window_handle: platform::WindowHandle) -> Self { Self { window_handle, phantom: PhantomData::default() } @@ -91,6 +97,13 @@ impl<'a> Window<'a> { pub fn close(&mut self) { self.window.close(); } + + /// 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")] + pub fn gl_context(&self) -> Option<&crate::gl::GlContext> { + self.window.gl_context() + } } unsafe impl<'a> HasRawWindowHandle for Window<'a> { @@ -98,3 +111,9 @@ unsafe impl<'a> HasRawWindowHandle for Window<'a> { self.window.raw_window_handle() } } + +unsafe impl HasRawWindowHandle for RawWindowHandleWrapper { + fn raw_window_handle(&self) -> RawWindowHandle { + self.handle + } +} diff --git a/src/window_open_options.rs b/src/window_open_options.rs index c4abf74..7c5cd19 100644 --- a/src/window_open_options.rs +++ b/src/window_open_options.rs @@ -21,4 +21,9 @@ pub struct WindowOpenOptions { /// The dpi scaling policy pub scale: WindowScalePolicy, + + /// 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")] + pub gl_config: Option, } diff --git a/src/x11/cursor.rs b/src/x11/cursor.rs index 6f3851e..8e8fd88 100644 --- a/src/x11/cursor.rs +++ b/src/x11/cursor.rs @@ -101,5 +101,5 @@ pub(super) fn get_xcursor(display: *mut x11::xlib::Display, cursor: MouseCursor) MouseCursor::RowResize => loadn(&[b"split_v\0", b"v_double_arrow\0"]), }; - cursor.or(load(b"left_ptr\0")).unwrap_or(0) + cursor.or_else(|| load(b"left_ptr\0")).unwrap_or(0) } diff --git a/src/x11/window.rs b/src/x11/window.rs index 5c3b7c7..bfe9567 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -6,7 +6,9 @@ use std::sync::Arc; use std::thread; use std::time::*; -use raw_window_handle::{unix::XlibHandle, HasRawWindowHandle, RawWindowHandle}; +use raw_window_handle::{HasRawWindowHandle, RawWindowHandle, XlibHandle}; +use xcb::ffi::xcb_screen_t; +use xcb::StructPtr; use super::XcbConnection; use crate::{ @@ -16,6 +18,12 @@ use crate::{ use super::keyboard::{convert_key_press_event, convert_key_release_event}; +#[cfg(feature = "opengl")] +use crate::{ + gl::{platform, GlContext}, + window::RawWindowHandleWrapper, +}; + pub struct WindowHandle { raw_window_handle: Option, close_requested: Arc, @@ -27,7 +35,7 @@ pub struct WindowHandle { impl WindowHandle { pub fn close(&mut self) { - if let Some(_) = self.raw_window_handle.take() { + if self.raw_window_handle.take().is_some() { // 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). @@ -49,7 +57,7 @@ unsafe impl HasRawWindowHandle for WindowHandle { } } - RawWindowHandle::Xlib(XlibHandle { ..raw_window_handle::unix::XlibHandle::empty() }) + RawWindowHandle::Xlib(XlibHandle::empty()) } } @@ -88,6 +96,7 @@ pub struct Window { xcb_connection: XcbConnection, window_id: u32, window_info: WindowInfo, + // FIXME: There's all this mouse cursor logic but it's never actually used, is this correct? mouse_cursor: MouseCursor, frame_interval: Duration, @@ -96,6 +105,9 @@ pub struct Window { new_physical_size: Option, parent_handle: Option, + + #[cfg(feature = "opengl")] + gl_context: Option, } // Hack to allow sending a RawWindowHandle between threads. Do not make public @@ -124,7 +136,7 @@ impl Window { let (parent_handle, mut window_handle) = ParentHandle::new(); - let thread = thread::spawn(move || { + thread::spawn(move || { Self::window_thread(Some(parent_id), options, build, tx.clone(), Some(parent_handle)); }); @@ -144,7 +156,7 @@ impl Window { let (parent_handle, mut window_handle) = ParentHandle::new(); - let thread = thread::spawn(move || { + thread::spawn(move || { Self::window_thread(None, options, build, tx.clone(), Some(parent_handle)); }); @@ -163,12 +175,14 @@ impl Window { let (tx, rx) = mpsc::sync_channel::(1); let thread = thread::spawn(move || { - Self::window_thread(None, options, build, tx.clone(), None); + Self::window_thread(None, options, build, tx, None); }); let _ = rx.recv().unwrap().unwrap(); - thread.join(); + thread.join().unwrap_or_else(|err| { + eprintln!("Window thread panicked: {:#?}", err); + }); } fn window_thread( @@ -205,10 +219,49 @@ impl Window { let window_info = WindowInfo::from_logical_size(options.size, scaling); - let window_id = xcb_connection.conn.generate_id(); - xcb::create_window( + // 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()), + }; + #[cfg(not(feature = "opengl"))] + let (depth, visual) = create_default_config(); + + // 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::COPY_FROM_PARENT as u8, + xcb::COLORMAP_ALLOC_NONE as u8, + colormap, + screen.root(), + visual, + ); + + let window_id = xcb_connection.conn.generate_id(); + xcb::create_window_checked( + &xcb_connection.conn, + depth, window_id, parent_id, 0, // x coordinate of the new window @@ -217,18 +270,26 @@ impl Window { window_info.physical_size().height as u16, // window height 0, // window border xcb::WINDOW_CLASS_INPUT_OUTPUT as u16, - screen.root_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, - )], - ); + 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, + ), + // As mentioend above, these two values are needed to be able to create a window + // with a dpeth 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); @@ -244,19 +305,35 @@ impl Window { 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], - ); - }, - ); + 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.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 mut handle = XlibHandle::empty(); + handle.window = window_id as c_ulong; + handle.display = xcb_connection.conn.get_raw_dpy() as *mut c_void; + let handle = RawWindowHandleWrapper { handle: RawWindowHandle::Xlib(handle) }; + + // Because of the visual negotation we had to take some extra steps to create this context + let context = unsafe { platform::GlContext::create(&handle, fb_config) } + .expect("Could not create OpenGL context"); + GlContext::new(context) + }); + let mut window = Self { xcb_connection, window_id, @@ -269,6 +346,9 @@ impl Window { new_physical_size: None, parent_handle, + + #[cfg(feature = "opengl")] + gl_context, }; let mut handler = build(&mut crate::Window::new(&mut window)); @@ -309,6 +389,27 @@ impl Window { self.close_requested = true; } + #[cfg(feature = "opengl")] + pub fn gl_context(&self) -> Option<&crate::gl::GlContext> { + self.gl_context.as_ref() + } + + fn find_visual_for_depth(screen: &StructPtr, depth: u8) -> Option { + 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 + } + #[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 @@ -538,7 +639,7 @@ impl Window { handler.on_event( &mut crate::Window::new(self), - Event::Keyboard(convert_key_press_event(&event)), + Event::Keyboard(convert_key_press_event(event)), ); } @@ -547,7 +648,7 @@ impl Window { handler.on_event( &mut crate::Window::new(self), - Event::Keyboard(convert_key_release_event(&event)), + Event::Keyboard(convert_key_release_event(event)), ); } @@ -558,11 +659,11 @@ impl Window { unsafe impl HasRawWindowHandle for Window { fn raw_window_handle(&self) -> RawWindowHandle { - RawWindowHandle::Xlib(XlibHandle { - window: self.window_id as c_ulong, - display: self.xcb_connection.conn.get_raw_dpy() as *mut c_void, - ..raw_window_handle::unix::XlibHandle::empty() - }) + let mut handle = XlibHandle::empty(); + handle.window = self.window_id as c_ulong; + handle.display = self.xcb_connection.conn.get_raw_dpy() as *mut c_void; + + RawWindowHandle::Xlib(handle) } } diff --git a/src/x11/xcb_connection.rs b/src/x11/xcb_connection.rs index 083249b..179e4c7 100644 --- a/src/x11/xcb_connection.rs +++ b/src/x11/xcb_connection.rs @@ -11,7 +11,6 @@ use super::cursor; pub(crate) struct Atoms { pub wm_protocols: Option, pub wm_delete_window: Option, - pub wm_normal_hints: Option, } pub struct XcbConnection { @@ -20,6 +19,7 @@ pub struct XcbConnection { pub(crate) atoms: Atoms, + // FIXME: Same here, there's a ton of unused cursor machinery in here pub(super) cursor_cache: HashMap, } @@ -46,14 +46,13 @@ impl XcbConnection { conn.set_event_queue_owner(xcb::base::EventQueueOwner::Xcb); - let (wm_protocols, wm_delete_window, wm_normal_hints) = - intern_atoms!(&conn, WM_PROTOCOLS, WM_DELETE_WINDOW, WM_NORMAL_HINTS); + 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, wm_normal_hints }, + atoms: Atoms { wm_protocols, wm_delete_window }, cursor_cache: HashMap::new(), }) @@ -136,7 +135,7 @@ impl XcbConnection { #[inline] pub fn get_scaling(&self) -> Option { - self.get_scaling_xft().or(self.get_scaling_screen_dimensions()) + self.get_scaling_xft().or_else(|| self.get_scaling_screen_dimensions()) } #[inline]