diff --git a/src/context.rs b/src/context.rs index 0ad95034..3c75b18d 100644 --- a/src/context.rs +++ b/src/context.rs @@ -9,7 +9,7 @@ use raw_window_handle::{ use std::fmt::Debug; use std::time::Duration; -/// A handle to the window given to a [`WindowHandler`](crate::WindowHandler), which it can then +/// A handle to the window given to a [`WindowHandler`](WindowHandler), which it can then /// use to perform various operations on the window itself. #[derive(Clone)] pub struct WindowContext { @@ -31,7 +31,7 @@ impl WindowContext { /// /// This request is not immediate. When exactly it will be closed is platform-dependent. /// - /// However, it is guaranteed to only get closed after all [`WindowHandler`](crate::WindowHandler) + /// However, it is guaranteed to only get closed after all [`WindowHandler`](WindowHandler) /// methods are completed, and soon enough to be perceived as instantaneous by the user. pub fn request_close(&self) { self.inner.request_close(); @@ -88,11 +88,11 @@ impl WindowContext { PlatformHandle { inner: self.inner.platform_handle() } } - /// Returns the [`GlContext`](crate::gl::GlContext) associated to this window. + /// Returns the [`GlContext`](gl::GlContext) associated to this window. /// /// If the window was not created with a GL context, this will return [`None`]. #[cfg(feature = "opengl")] - pub fn gl_context(&self) -> Option { + pub fn gl_context(&self) -> Option { self.inner.gl_context() } @@ -131,13 +131,13 @@ impl WindowContext { } impl HasWindowHandle for WindowContext { - fn window_handle(&self) -> core::result::Result, HandleError> { + fn window_handle(&self) -> Result, HandleError> { self.inner.window_handle().ok_or(HandleError::Unavailable) } } impl HasDisplayHandle for WindowContext { - fn display_handle(&self) -> core::result::Result, HandleError> { + fn display_handle(&self) -> Result, HandleError> { Ok(self.inner.display_handle()) } } @@ -177,13 +177,13 @@ const _: () = { }; impl HasWindowHandle for PlatformHandle { - fn window_handle(&self) -> core::result::Result, HandleError> { + fn window_handle(&self) -> Result, HandleError> { self.inner.window_handle().ok_or(HandleError::Unavailable) } } impl HasDisplayHandle for PlatformHandle { - fn display_handle(&self) -> core::result::Result, HandleError> { + fn display_handle(&self) -> Result, HandleError> { Ok(self.inner.display_handle()) } } diff --git a/src/gl.rs b/src/gl.rs index 5acae154..e1f5f273 100644 --- a/src/gl.rs +++ b/src/gl.rs @@ -1,3 +1,4 @@ +use crate::platform::gl::PlatformGlContext; use std::ffi::{c_void, CStr, CString}; use std::marker::PhantomData; @@ -54,13 +55,13 @@ pub enum Profile { #[derive(Clone)] pub struct GlContext { - inner: crate::platform::gl::GlContext, + inner: PlatformGlContext, // To make sure this is !Send, !Sync, and !UnwindSafe on all platforms phantom: PhantomData<(*mut (), &'static mut ())>, } impl GlContext { - pub(crate) fn new(context: crate::platform::gl::GlContext) -> GlContext { + pub(crate) fn new(context: PlatformGlContext) -> GlContext { GlContext { inner: context, phantom: PhantomData } } diff --git a/src/platform/macos/gl.rs b/src/platform/macos/gl.rs index 58333972..e550363c 100644 --- a/src/platform/macos/gl.rs +++ b/src/platform/macos/gl.rs @@ -51,16 +51,16 @@ impl Display for GlError { } #[derive(Clone)] -pub struct GlContext { +pub struct PlatformGlContext { pub(crate) view: Retained, context: Retained, gl_bundle: CFRetained, } -impl GlContext { +impl PlatformGlContext { pub(crate) fn create( parent_view: &NSView, config: GlConfig, marker: MainThreadMarker, - ) -> Result { + ) -> Result { let version = if config.version < (3, 2) && config.profile == Profile::Compatibility { NSOpenGLProfileVersionLegacy } else if config.version == (3, 2) && config.profile == Profile::Core { @@ -133,7 +133,7 @@ impl GlContext { let gl_bundle = CFBundle::bundle_with_identifier(Some(&framework_name)) .ok_or(GlError::OpenGlBundleNotFound)?; - Ok(GlContext { view, context, gl_bundle }) + Ok(PlatformGlContext { view, context, gl_bundle }) } pub unsafe fn make_current(&self) -> Result<()> { diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index 7b53e4be..07cd39c6 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -84,7 +84,7 @@ pub(crate) struct BaseviewView { timers: TimerManager, #[cfg(feature = "opengl")] - pub(crate) gl_context: OnceCell, + pub(crate) gl_context: OnceCell, } impl BaseviewView { @@ -133,7 +133,8 @@ impl BaseviewView { #[cfg(feature = "opengl")] if let Some(gl_config) = init.settings.gl_config { - let gl_context = super::gl::GlContext::create(view.view, gl_config, view.mtm)?; + let gl_context = + super::gl::PlatformGlContext::create(view.view, gl_config, view.mtm)?; let Ok(()) = view.gl_context.set(gl_context) else { unreachable!() }; } diff --git a/src/platform/win/gl.rs b/src/platform/win/gl.rs index aac73f6d..ea403674 100644 --- a/src/platform/win/gl.rs +++ b/src/platform/win/gl.rs @@ -11,7 +11,7 @@ use crate::wrappers::win32::window::{ }; use crate::wrappers::win32::RawLibrary; -pub type GlContext = Rc; +pub type PlatformGlContext = Rc; pub struct GlContextInner { hdc: OwnDeviceContext, diff --git a/src/platform/win/window_state.rs b/src/platform/win/window_state.rs index 4a9610c8..4e2c6e78 100644 --- a/src/platform/win/window_state.rs +++ b/src/platform/win/window_state.rs @@ -35,7 +35,7 @@ pub(crate) struct WindowState { pub redraw_requested: Cell, #[cfg(feature = "opengl")] - pub gl_context: std::cell::OnceCell, + pub gl_context: std::cell::OnceCell, } impl WindowState { diff --git a/src/platform/x11/cursor.rs b/src/platform/x11/cursor.rs index 1cf85faf..cc750d72 100644 --- a/src/platform/x11/cursor.rs +++ b/src/platform/x11/cursor.rs @@ -1,13 +1,66 @@ -use x11rb::connection::Connection; -use x11rb::cursor::Handle as CursorHandle; -use x11rb::protocol::xproto::{ConnectionExt as _, Cursor}; -use x11rb::xcb_ffi::XCBConnection; - -use crate::platform::*; +use super::prelude::*; use crate::wrappers::xlib::XlibXcbConnection; use crate::MouseCursor; +use std::cell::RefCell; +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use x11rb::protocol::xproto::ChangeWindowAttributesAux; + +pub struct CursorStateShared { + mouse_cursor: Cell, + cursor_cache: RefCell>, +} + +impl CursorStateShared { + pub(crate) fn set_mouse_cursor( + &self, mouse_cursor: MouseCursor, window: &XcbWindow, + ) -> PlatformResult<()> { + if self.mouse_cursor.get() == mouse_cursor { + return Ok(()); + } + + let xid = self.get_cursor(mouse_cursor, window.connection())?; + + if xid != 0 { + window + .connection() + .conn + .change_window_attributes( + window.id().get(), + &ChangeWindowAttributesAux::new().cursor(xid), + )? + .check()?; + } + + self.mouse_cursor.set(mouse_cursor); + + Ok(()) + } + + #[inline] + fn get_cursor(&self, cursor: MouseCursor, conn: &X11Connection) -> PlatformResult { + // PANIC: this function is the only point where we access the cache, and we never call + // external functions that may make a reentrant call to this function + let mut cursor_cache = self.cursor_cache.borrow_mut(); + + match cursor_cache.entry(cursor) { + Entry::Occupied(entry) => Ok(*entry.get()), + Entry::Vacant(entry) => { + let cursor = get_xcursor(conn, cursor)?; + entry.insert(cursor); + Ok(cursor) + } + } + } +} + +impl CursorStateShared { + pub fn new() -> Self { + Self { mouse_cursor: MouseCursor::Default.into(), cursor_cache: HashMap::new().into() } + } +} -fn create_empty_cursor(conn: &XlibXcbConnection) -> Result { +fn create_empty_cursor(conn: &XlibXcbConnection) -> PlatformResult { let cursor_id = conn.generate_id()?; let pixmap_id = conn.generate_id()?; let root_window = conn.default_screen().root; @@ -18,10 +71,9 @@ fn create_empty_cursor(conn: &XlibXcbConnection) -> Result { Ok(cursor_id) } -fn load_cursor( - conn: &XCBConnection, cursor_handle: &CursorHandle, name: &str, -) -> Result> { - let cursor = cursor_handle.load_cursor(conn, name)?; +#[inline(never)] +fn load_cursor(conn: &X11Connection, name: &str) -> PlatformResult> { + let cursor = conn.resources.cursor_handle.load_cursor(&conn.conn as &XCBConnection, name)?; if cursor != x11rb::NONE { Ok(Some(cursor)) } else { @@ -29,11 +81,12 @@ fn load_cursor( } } +#[inline(never)] fn load_first_existing_cursor( - conn: &XCBConnection, cursor_handle: &CursorHandle, names: &[&str], -) -> Result> { + conn: &X11Connection, names: &[&str], +) -> PlatformResult> { for name in names { - let cursor = load_cursor(conn, cursor_handle, name)?; + let cursor = load_cursor(conn, name)?; if cursor.is_some() { return Ok(cursor); } @@ -42,11 +95,9 @@ fn load_first_existing_cursor( Ok(None) } -pub(crate) fn get_xcursor( - conn: &XlibXcbConnection, cursor_handle: &CursorHandle, cursor: MouseCursor, -) -> Result { - let load = |name: &str| load_cursor(conn, cursor_handle, name); - let loadn = |names: &[&str]| load_first_existing_cursor(conn, cursor_handle, names); +pub(crate) fn get_xcursor(conn: &X11Connection, cursor: MouseCursor) -> PlatformResult { + let load = |name: &str| load_cursor(conn, name); + let loadn = |names: &[&str]| load_first_existing_cursor(conn, names); let cursor = match cursor { MouseCursor::Default => None, // catch this in the fallback case below @@ -55,7 +106,7 @@ pub(crate) fn get_xcursor( MouseCursor::HandGrabbing => loadn(&["closedhand", "grabbing"])?, MouseCursor::Help => load("question_arrow")?, - MouseCursor::Hidden => Some(create_empty_cursor(conn)?), + MouseCursor::Hidden => Some(create_empty_cursor(&conn.conn)?), MouseCursor::Text => loadn(&["text", "xterm"])?, MouseCursor::VerticalText => load("vertical-text")?, diff --git a/src/platform/x11/drag_n_drop.rs b/src/platform/x11/drag_n_drop.rs index a849b316..6d95d3c7 100644 --- a/src/platform/x11/drag_n_drop.rs +++ b/src/platform/x11/drag_n_drop.rs @@ -1,8 +1,8 @@ -use super::xcb_connection::{Atoms, GetPropertyError}; +use super::x11_connection::{Atoms, GetPropertyError}; use super::*; use crate::dpi::PhysicalPosition; -use crate::handler::WindowHandler; use crate::platform::x11::error::ReplyExt; +use crate::platform::x11::handler::Handler; use crate::warn; use crate::{DropData, Event, MouseEvent}; use core::result::Result; @@ -95,7 +95,7 @@ pub(crate) enum DragNDropState { // Other errors (protocol errors, transfer errors) should be dealt with as gracefully as possible. impl DragNDropState { pub fn handle_enter_event( - &mut self, window: &WindowInner, handler: &dyn WindowHandler, event: &ClientMessageEvent, + &mut self, window: &WindowShared, handler: &Handler, event: &ClientMessageEvent, ) -> Result<(), ConnectionError> { let data = event.data.as_data32(); @@ -154,7 +154,7 @@ impl DragNDropState { } pub fn handle_position_event( - &mut self, window: &WindowInner, handler: &dyn WindowHandler, event: &ClientMessageEvent, + &mut self, window: &WindowShared, handler: &Handler, event: &ClientMessageEvent, ) -> Result<(), ConnectionError> { let event_data = event.data.as_data32(); @@ -260,7 +260,7 @@ impl DragNDropState { } } - pub fn handle_leave_event(&mut self, handler: &dyn WindowHandler, event: &ClientMessageEvent) { + pub fn handle_leave_event(&mut self, handler: &Handler, event: &ClientMessageEvent) { let data = event.data.as_data32(); let event_source_window = data[0] as xproto::Window; @@ -291,7 +291,7 @@ impl DragNDropState { } pub fn handle_drop_event( - &mut self, window: &WindowInner, handler: &dyn WindowHandler, event: &ClientMessageEvent, + &mut self, window: &WindowShared, handler: &Handler, event: &ClientMessageEvent, ) -> Result<(), ConnectionError> { let data = event.data.as_data32(); @@ -395,7 +395,7 @@ impl DragNDropState { } pub fn handle_selection_notify_event( - &mut self, window: &WindowInner, handler: &dyn WindowHandler, event: &SelectionNotifyEvent, + &mut self, window: &WindowShared, handler: &Handler, event: &SelectionNotifyEvent, ) -> Result<(), ConnectionError> { // Ignore the event if we weren't actually waiting for a selection notify event let WaitingForData { @@ -488,7 +488,7 @@ impl DragNDropState { } fn send_status_rejected( - source_window: xproto::Window, window: &WindowInner, + source_window: xproto::Window, window: &WindowShared, ) -> Result<(), ConnectionError> { let conn = &window.connection; @@ -509,7 +509,7 @@ fn send_status_rejected( } fn send_status_event( - source_window: xproto::Window, window: &WindowInner, action: Option, + source_window: xproto::Window, window: &WindowShared, action: Option, ) -> Result<(), ConnectionError> { let conn = &window.connection; @@ -532,7 +532,7 @@ fn send_status_event( } pub fn send_finished_rejected( - source_window: xproto::Window, window: &WindowInner, + source_window: xproto::Window, window: &WindowShared, ) -> Result<(), ConnectionError> { let conn = &window.connection; @@ -553,7 +553,7 @@ pub fn send_finished_rejected( } fn send_finished_event( - source_window: xproto::Window, window: &WindowInner, action: Option, + source_window: xproto::Window, window: &WindowShared, action: Option, ) -> Result, ConnectionError> { let conn = &window.connection; let action = @@ -572,7 +572,7 @@ fn send_finished_event( } fn request_convert_selection( - window: &WindowInner, timestamp: Option, + window: &WindowShared, timestamp: Option, ) -> Result, ConnectionError> { window.connection.conn.convert_selection( window.xcb_window.id().get(), @@ -588,7 +588,7 @@ fn decode_xy(data: u32) -> (u16, u16) { } fn translate_root_coordinates( - window: &WindowInner, x: u16, y: u16, + window: &WindowShared, x: u16, y: u16, ) -> Result>, ConnectionError> { let root_id = window.connection.default_screen().root; let x = x.try_into().unwrap_or(i16::MAX); @@ -609,7 +609,7 @@ fn translate_root_coordinates( Ok(Some(PhysicalPosition::new(reply.dst_x, reply.dst_y))) } -fn fetch_dnd_data(window: &WindowInner) -> Result, ConnectionError> { +fn fetch_dnd_data(window: &WindowShared) -> Result, ConnectionError> { let conn = &window.connection; let data: Vec = match conn.get_property( diff --git a/src/platform/x11/error.rs b/src/platform/x11/error.rs index 64fb66d1..83bba98e 100644 --- a/src/platform/x11/error.rs +++ b/src/platform/x11/error.rs @@ -1,7 +1,7 @@ +use super::prelude::*; use crate::platform::x11::drag_n_drop::ParseError; use crate::platform::x11::window_thread::RequestFailed; -use crate::platform::x11::xcb_connection::GetPropertyError; -use crate::warn; +use crate::platform::x11::x11_connection::GetPropertyError; use crate::wrappers::xlib::{DisplayOpenFailedError, InitThreadsFailedError}; use crate::HandlerError; use std::fmt::{Display, Formatter}; @@ -16,6 +16,7 @@ pub enum FatalError { Connection(ConnectionError), Calloop(calloop::Error), SendMainThread, + Redraw(String), } impl Display for FatalError { @@ -26,6 +27,7 @@ impl Display for FatalError { FatalError::SendMainThread => { f.write_str("Failed to send callback from X11 thread to main thread") } + FatalError::Redraw(e) => write!(f, "Fatal error while drawing the window: {}", e), } } } @@ -62,12 +64,13 @@ pub enum PlatformError { Calloop(calloop::Error), RequestFromMainThreadFailed(RequestFailed), SendMainThread, + Redraw(String), #[cfg(feature = "opengl")] XLib(crate::wrappers::xlib::XLibError), #[cfg(feature = "opengl")] EGl(crate::wrappers::egl::EglError), #[cfg(feature = "opengl")] - Gl(super::gl::CreationFailedError), + Gl(super::gl::GlCreationFailedError), } impl Display for PlatformError { @@ -88,7 +91,8 @@ impl Display for PlatformError { PlatformError::Handler(e) => e.fmt(f), PlatformError::MainThreadRecvResult => { f.write_str("Failed to receive Window creation response from X11 thread: channel was closed unexpectedly") - } + }, + PlatformError::Redraw(e) => e.fmt(f), PlatformError::Calloop(e) => e.fmt(f), PlatformError::RequestFromMainThreadFailed(e) => e.fmt(f), PlatformError::SendMainThread => FatalError::SendMainThread.fmt(f), @@ -178,6 +182,7 @@ impl From for PlatformError { FatalError::Connection(e) => Self::Connection(e), FatalError::Calloop(e) => Self::Calloop(e), FatalError::SendMainThread => Self::SendMainThread, + FatalError::Redraw(s) => Self::Redraw(s), } } } @@ -229,8 +234,8 @@ impl From for PlatformError { } #[cfg(feature = "opengl")] -impl From for PlatformError { - fn from(value: super::gl::CreationFailedError) -> Self { +impl From for PlatformError { + fn from(value: super::gl::GlCreationFailedError) -> Self { Self::Gl(value) } } diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 82b26fc8..122dc66f 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -1,26 +1,24 @@ use super::drag_n_drop::DragNDropState; -use super::keyboard::{convert_key_press_event, convert_key_release_event, key_mods}; -use super::*; -use std::result::Result; - -use crate::dpi::{PhysicalPosition, PhysicalSize}; +use super::keyboard::{convert_key_press_event, convert_key_release_event}; +use super::prelude::*; use crate::host::HostMainThreadCaller; use crate::platform::x11::error::FatalError; +use crate::platform::x11::handler::Handler; +use crate::platform::x11::present::PresentState; +use crate::platform::x11::sizing::SizingState; use crate::platform::x11::window_thread::{ HostCallback, WindowThreadRequest, WindowThreadResponseMessage, }; +use crate::warn; use crate::wrappers::xkbcommon::XkbcommonState; -use crate::{warn, DamageArea}; -use crate::{Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowHandler, WindowSize}; +use crate::{Event, WindowEvent, WindowHandler}; use calloop::generic::Generic; -use calloop::timer::{TimeoutAction, Timer}; use calloop::{Interest, LoopHandle, LoopSignal, Mode, PostAction}; -use std::rc::Rc; +use std::num::NonZeroU32; +use std::result::Result; use std::sync::mpsc; use std::sync::mpsc::Receiver; -use std::time::{Duration, Instant}; use x11rb::connection::Connection; -use x11rb::protocol::present::CompleteKind; use x11rb::protocol::Event as XEvent; pub struct MainThreadCaller { @@ -48,30 +46,26 @@ impl MainThreadCaller { } pub(crate) struct EventLoop { - handler: Box, - window: Rc, - - new_size: Option>, - new_parent_size: Option>, - draw_now: bool, - last_requested_serial: Option, - last_received_present: Option<(u32, u64)>, + response_sender: mpsc::Sender, + main_thread: Option, - loop_signal: LoopSignal, - loop_handle: LoopHandle<'static, Self>, + handler: Handler, + shared: Rc, drag_n_drop: DragNDropState, + sizing_state: SizingState, + pub present_state: PresentState, xkb_state: Option, - run_error: Option, + loop_signal: LoopSignal, + loop_handle: LoopHandle<'static, Self>, - response_sender: mpsc::Sender, - main_thread: Option, + run_error: Option, } impl EventLoop { pub fn new( - window: Rc, handler: Box, + window: Rc, handler: Box, parent_id: Option, request_receiver: calloop::channel::Channel, response_sender: mpsc::Sender, main_thread: Option, inner: &mut calloop::EventLoop<'static, Self>, @@ -96,18 +90,16 @@ impl EventLoop { Ok(Self { loop_signal: inner.get_signal(), loop_handle, - handler, - new_size: None, - new_parent_size: None, - draw_now: false, - last_requested_serial: None, - last_received_present: None, + handler: Handler::new(handler), + present_state: PresentState::new(), + sizing_state: SizingState::new(parent_id), + drag_n_drop: DragNDropState::NoCurrentSession, xkb_state: XkbcommonState::new(&window.connection), run_error: None, main_thread, - window, + shared: window, response_sender, }) } @@ -119,7 +111,7 @@ impl EventLoop { #[inline] fn drain_xcb_events(&mut self) -> Result { let mut event_received = false; - while let Some(event) = self.window.connection.conn.poll_for_event()? { + while let Some(event) = self.shared.connection.conn.poll_for_event()? { event_received = true; self.handle_xcb_event(event)?; } @@ -127,139 +119,6 @@ impl EventLoop { Ok(event_received) } - fn setup_fallback_frame_timer(&self) -> Result<(), calloop::Error> { - const FRAME_INTERVAL: Duration = Duration::from_millis(15); - - fn handle_frame(evloop: &mut EventLoop, previous_deadline: Instant) -> TimeoutAction { - evloop.draw_now = true; - - // We'll try to keep a consistent frame pace. If the last frame couldn't be processed in - // the expected frame time, this will throttle down to prevent multiple frames from - // being queued up. - - let now = Instant::now(); - - let Some(next_deadline) = previous_deadline.checked_add(FRAME_INTERVAL) else { - return TimeoutAction::ToDuration(FRAME_INTERVAL); - }; - - if next_deadline >= now { - return TimeoutAction::ToDuration(FRAME_INTERVAL); - } - - TimeoutAction::ToInstant(next_deadline) - } - - self.loop_handle - .insert_source(Timer::from_duration(FRAME_INTERVAL), |i, _, e| handle_frame(e, i)) - .map_err(|e| e.error)?; - - Ok(()) - } - - fn redraw(&mut self) -> Result<(), FatalError> { - self.window.present_notify_requested.set(false); - self.draw_now = false; - - if let Err(e) = self.handler.draw() { - self.trigger_fatal_error(e.into()); - return Ok(()); - } - - self.window.connection.conn.flush()?; - Ok(()) - } - - fn handle_present_notify(&mut self) -> Result<(), FatalError> { - if !self.window.present_notify_requested.get() { - return Ok(()); - } - - if !self.window.xcb_window.present_supported() { - self.window.present_notify_requested.set(false); - return Ok(()); - } - - let (next_serial, target_msc) = - match (self.last_requested_serial, self.last_received_present) { - // First request, always send - (None, None) => (0, 0), - (Some(sent_serial), Some((received_serial, last_msc))) - if sent_serial == received_serial => - { - (sent_serial.wrapping_add(1), last_msc.wrapping_add(2)) - } - // We sent our first request but have not gotten a response yet. - // Or, we sent a request, but the last response we've gotten isn't that one. - // Do not send. - _ => { - self.window.present_notify_requested.set(false); - return Ok(()); - } - }; - - if self.window.xcb_window.present_notify(target_msc, next_serial)?.check_is_ok() { - self.last_requested_serial = Some(next_serial); - } else { - self.last_requested_serial = None; - self.setup_fallback_frame_timer()?; - } - self.window.present_notify_requested.set(false); - - Ok(()) - } - - fn handle_coalesced_resize_events(&mut self) -> Result<(), FatalError> { - let mut comes_from_parent = false; - if let Some(new_parent_size) = self.new_parent_size.take() { - if new_parent_size != self.window.get_size() { - // The parent was resized, which means we should resize ourselves too. - if let Err(e) = self.window.xcb_window.resize(new_parent_size.cast()) { - crate::warn!("Failed to resize window: {}", e); - } else { - // Makes the rest of this function run on the new parent size immediately (without waiting for a ConfigureNotify round-trip) - // Also overrides any new sizes we may have received this event loop iteration,it would probably be invalidated anyway - self.new_size = Some(new_parent_size); - comes_from_parent = true; - } - } - } - - let Some(new_size) = self.new_size.take() else { return Ok(()) }; - let previous = self.window.store_size(new_size); - - if previous == new_size { - return Ok(()); - }; - - let scale_factor = self.window.scaling_factor.get(); - let new_size = WindowSize::from_physical(new_size.cast(), scale_factor); - - if let Err(e) = self.handler.resized(new_size) { - warn!("Window Handler failed to resize: {}", e); - self.window.store_size(previous); - self.window.xcb_window.resize(previous.cast())?.check_warn(); - return Ok(()); - } - - // Host requests use resize_immediately, which stops the previous == new_size condition - // So if we're here, it's guaranteed not to be from a host request - - if !comes_from_parent { - if let Some(host) = self.main_thread.as_mut() { - host.send(HostCallback::Resized { - new_size, - previous: WindowSize::from_physical(previous.cast(), scale_factor), - })?; - } - } - - // Immediately schedule a redraw, do not wait for an "expose" event - self.window.present_notify_requested.set(true); - - Ok(()) - } - fn handle_main_thread_request(&mut self, event: calloop::channel::Event) { match event { calloop::channel::Event::Closed => { @@ -285,12 +144,12 @@ impl EventLoop { } } - fn stop_now(&self) { + pub fn stop_now(&self) { self.loop_signal.stop(); self.loop_signal.wakeup(); } - fn trigger_fatal_error(&mut self, error: PlatformError) { + pub fn trigger_fatal_error(&mut self, error: PlatformError) { if self.run_error.is_none() { self.run_error = Some(error); } @@ -300,40 +159,19 @@ impl EventLoop { fn handle_request(&mut self, req: WindowThreadRequest) -> Result<(), PlatformError> { match req { WindowThreadRequest::Resize(new_size) => { - let scale_factor = self.window.scaling_factor.get(); - let new_size = new_size.to_physical(scale_factor); - - self.window.resize_immediately(new_size, &*self.handler)?; - - Ok(()) - } - WindowThreadRequest::SuggestScaleFactor(scale) => { - // If the scaling factor is already provided by the system, do nothing - if !self.window.scaling_factor.suggest(scale) { - return Ok(()); - }; - - let current_logical_size = self.window.get_size().to_logical::(1.0); - let new_physical_size = current_logical_size.to_physical(scale); - - self.window.resize_immediately(new_physical_size, &*self.handler)?; - - Ok(()) + self.sizing_state.handle_host_resize(new_size, &self.handler, &self.shared)? } + WindowThreadRequest::SuggestScaleFactor(scale) => self + .sizing_state + .handle_host_suggest_scale_factor(scale, &self.handler, &self.shared)?, WindowThreadRequest::SetParent(new_parent) => { - self.window.xcb_window.reparent(Some(new_parent.window_id))?; - - Ok(()) - } - WindowThreadRequest::Show => { - self.window.xcb_window.map_window()?.check()?; - Ok(()) - } - WindowThreadRequest::Hide => { - self.window.xcb_window.unmap_window()?.check()?; - Ok(()) + self.shared.xcb_window.reparent(Some(new_parent.window_id))?.check()? } + WindowThreadRequest::Show => self.shared.xcb_window.map_window()?.check()?, + WindowThreadRequest::Hide => self.shared.xcb_window.unmap_window()?.check()?, } + + Ok(()) } fn handle_connection_event_ready(&mut self) -> Result { @@ -353,32 +191,20 @@ impl EventLoop { self.drain_xcb_events()?; loop { - self.handle_coalesced_resize_events()?; - - // Consume all requests from above poll - if let Some(redraw_after) = self.window.main_thread_shared.take_redraw_request() { - self.window.request_redraw_after(redraw_after) - } + self.sizing_state.handle_coalesced_resize_events( + &self.shared, + &self.handler, + self.main_thread.as_mut(), + )?; - let shared_poll_requested = self.window.main_thread_shared.take_poll_request(); - - if self.draw_now { - self.handler.poll(); - self.redraw()?; - self.window.poll_requested.set(false); - } else if shared_poll_requested || self.window.poll_requested.get() { - self.handler.poll(); - self.window.poll_requested.set(false); - } - - self.handle_present_notify()?; + self.present_state.handle_requests(&self.shared, &self.handler, &self.loop_handle)?; if !self.drain_xcb_events()? { break; } } - self.window.connection.conn.flush()?; + self.shared.connection.conn.flush()?; Ok(()) } @@ -387,10 +213,10 @@ impl EventLoop { self.drain_xcb_events()?; inner.run(None, &mut self, Self::handle_idle)?; - self.handle_event(Event::Window(WindowEvent::WillClose)); + self.handler.on_event(Event::Window(WindowEvent::WillClose)); // If the event loop doesn't stop because the host asked it to, then we should notify it - if !self.window.main_thread_shared.is_stop_host_requested() { + if !self.shared.main_thread_shared.is_stop_host_requested() { if let Some(main_thread) = self.main_thread.as_mut() { if let Err(e) = main_thread.send(HostCallback::Destroyed) { warn!("Could not notify host that X11 thread is stopping: {}", e) @@ -405,64 +231,38 @@ impl EventLoop { Ok(()) } - pub fn request_redraw(&self) { - self.window.request_redraw(); + pub fn shared(&self) -> &WindowShared { + &self.shared } fn handle_xcb_event(&mut self, event: XEvent) -> Result<(), FatalError> { - // For all the keyboard and mouse events, you can fetch - // `x`, `y`, `detail`, and `state`. - // - `x` and `y` are the position inside the window where the cursor currently is - // when the event happened. - // - `detail` will tell you which keycode was pressed/released (for keyboard events) - // or which mouse button was pressed/released (for mouse events). - // For mouse events, here's what the value means (at least on my current mouse): - // 1 = left mouse button - // 2 = middle mouse button (scroll wheel) - // 3 = right mouse button - // 4 = scroll wheel up - // 5 = scroll wheel down - // 8 = lower side button ("back" button) - // 9 = upper side button ("forward" button) - // Note that you *will* get a "button released" event for even the scroll wheel - // events, which you can probably ignore. - // - `state` will tell you the state of the main three mouse buttons and some of - // the keyboard modifier keys at the time of the event. - // http://rtbo.github.io/rust-xcb/src/xcb/ffi/xproto.rs.html#445 - match event { - //// - // window - //// - XEvent::ClientMessage(event) if event.window == self.window.raw_id() => { + XEvent::ClientMessage(event) if event.window == self.shared.raw_id() => { if event.format != 32 { return Ok(()); } - if event.data.as_data32()[0] == self.window.connection.atoms.WM_DELETE_WINDOW { - self.window.request_close(); + if event.data.as_data32()[0] == self.shared.connection.atoms.WM_DELETE_WINDOW { + self.shared.request_close(); return Ok(()); } - //// - // drag n drop - //// - if event.type_ == self.window.connection.atoms.XdndEnter { - self.drag_n_drop.handle_enter_event(&self.window, &*self.handler, &event)?; - } else if event.type_ == self.window.connection.atoms.XdndPosition { - self.drag_n_drop.handle_position_event(&self.window, &*self.handler, &event)?; - } else if event.type_ == self.window.connection.atoms.XdndDrop { - self.drag_n_drop.handle_drop_event(&self.window, &*self.handler, &event)?; - } else if event.type_ == self.window.connection.atoms.XdndLeave { - self.drag_n_drop.handle_leave_event(&*self.handler, &event); + if event.type_ == self.shared.connection.atoms.XdndEnter { + self.drag_n_drop.handle_enter_event(&self.shared, &self.handler, &event)?; + } else if event.type_ == self.shared.connection.atoms.XdndPosition { + self.drag_n_drop.handle_position_event(&self.shared, &self.handler, &event)?; + } else if event.type_ == self.shared.connection.atoms.XdndDrop { + self.drag_n_drop.handle_drop_event(&self.shared, &self.handler, &event)?; + } else if event.type_ == self.shared.connection.atoms.XdndLeave { + self.drag_n_drop.handle_leave_event(&self.handler, &event); } } XEvent::SelectionNotify(event) => { - if event.property == self.window.connection.atoms.XdndSelection { + if event.property == self.shared.connection.atoms.XdndSelection { self.drag_n_drop.handle_selection_notify_event( - &self.window, - &*self.handler, + &self.shared, + &self.handler, &event, )?; } @@ -473,171 +273,77 @@ impl EventLoop { } XEvent::ConfigureNotify(event) => { - if let Some(window_id) = NonZero::new(event.window) { - // These are coalesced and then handled asynchronously at the end of the event loop - if window_id == self.window.xcb_window.id() { - self.new_size = Some(PhysicalSize::new(event.width, event.height)); - } else if Some(window_id) == self.window.parent_id.get() { - // Also resize the window if the parent is resized - // This works around some hosts that might not call set_size() right away (or at all...) - self.new_parent_size = Some(PhysicalSize::new(event.width, event.height)); - } - } - } - - XEvent::Expose(e) if e.window == self.window.raw_id() => { - if e.count == 0 { - self.window.present_notify_requested.set(true); - } - - let current_window_size = self.new_size.unwrap_or_else(|| self.window.get_size()); - - let damage_rect = DamageRect::new(&e); - let area = if damage_rect.fully_covers(current_window_size) { - DamageArea::FullWindow - } else { - DamageArea::Rect(damage_rect.into()) - }; - - self.handler.damage(area); + self.sizing_state.handle_configure_notify_event(event, &self.shared.xcb_window); } //// // mouse //// - XEvent::MotionNotify(event) if event.event == self.window.raw_id() => { - let physical_pos = PhysicalPosition::new(event.event_x, event.event_y); - - self.handle_event(Event::Mouse(MouseEvent::CursorMoved { - position: physical_pos.cast(), - modifiers: key_mods(event.state), - })); + XEvent::MotionNotify(event) if event.event == self.shared.raw_id() => { + handle_motion_notify(event, &self.handler) } - - XEvent::EnterNotify(event) if event.event == self.window.raw_id() => { - self.handle_event(Event::Mouse(MouseEvent::CursorEntered)); - // since no `MOTION_NOTIFY` event is generated when `ENTER_NOTIFY` is generated, - // we generate a CursorMoved as well, so the mouse position from here isn't lost - let physical_pos = PhysicalPosition::new(event.event_x, event.event_y); - self.handle_event(Event::Mouse(MouseEvent::CursorMoved { - position: physical_pos.cast(), - modifiers: key_mods(event.state), - })); + XEvent::EnterNotify(event) if event.event == self.shared.raw_id() => { + handle_enter_notify(event, &self.handler) } - - XEvent::LeaveNotify(event) if event.event == self.window.raw_id() => { - self.handle_event(Event::Mouse(MouseEvent::CursorLeft)); + XEvent::LeaveNotify(event) if event.event == self.shared.raw_id() => { + handle_leave_notify(event, &self.handler) } - - XEvent::ButtonPress(event) if event.event == self.window.raw_id() => { - match event.detail { - 4..=7 => { - self.handle_event(Event::Mouse(MouseEvent::WheelScrolled { - delta: match event.detail { - 4 => ScrollDelta::Lines { x: 0.0, y: 1.0 }, - 5 => ScrollDelta::Lines { x: 0.0, y: -1.0 }, - 6 => ScrollDelta::Lines { x: -1.0, y: 0.0 }, - 7 => ScrollDelta::Lines { x: 1.0, y: 0.0 }, - _ => unreachable!(), - }, - modifiers: key_mods(event.state), - })); - } - detail => { - self.handle_event(Event::Mouse(MouseEvent::ButtonPressed { - button: mouse_id(detail), - modifiers: key_mods(event.state), - })); - } - } + XEvent::ButtonPress(event) if event.event == self.shared.raw_id() => { + handle_button_press(event, &self.handler) } - - XEvent::ButtonRelease(event) - if event.event == self.window.raw_id() && !(4..=7).contains(&event.detail) => - { - let button_id = mouse_id(event.detail); - self.handle_event(Event::Mouse(MouseEvent::ButtonReleased { - button: button_id, - modifiers: key_mods(event.state), - })); + XEvent::ButtonRelease(event) if event.event == self.shared.raw_id() => { + handle_button_release(event, &self.handler); } //// // keys //// - XEvent::KeyPress(event) if event.event == self.window.raw_id() => { - let ev = Event::Keyboard(convert_key_press_event(&event, &mut self.xkb_state)); - self.handle_event(ev); + XEvent::KeyPress(event) if event.event == self.shared.raw_id() => { + self.handler.on_event(Event::Keyboard(convert_key_press_event( + &event, + &mut self.xkb_state, + ))); } - XEvent::KeyRelease(event) if event.event == self.window.raw_id() => { - let ev = Event::Keyboard(convert_key_release_event(&event, &mut self.xkb_state)); - self.handle_event(ev); + XEvent::KeyRelease(event) if event.event == self.shared.raw_id() => { + self.handler.on_event(Event::Keyboard(convert_key_release_event( + &event, + &mut self.xkb_state, + ))); } - XEvent::FocusIn(event) if event.event == self.window.raw_id() => { - self.window.is_focused.set(true); - self.handle_event(Event::Window(WindowEvent::Focused)); + XEvent::FocusIn(event) if event.event == self.shared.raw_id() => { + self.shared.is_focused.set(true); + self.handler.on_event(Event::Window(WindowEvent::Focused)); } - XEvent::FocusOut(e) if e.event == self.window.raw_id() => { - self.window.is_focused.set(false); - self.handle_event(Event::Window(WindowEvent::Unfocused)); + XEvent::FocusOut(e) if e.event == self.shared.raw_id() => { + self.shared.is_focused.set(false); + self.handler.on_event(Event::Window(WindowEvent::Unfocused)); } - XEvent::MapNotify(e) => { - if let Some(window_id) = NonZero::new(e.window) { - if window_id == self.window.xcb_window.id() { - if self.window.xcb_window.present_supported() - && self.window.xcb_window.present_select_input()? - { - self.window.present_notify_requested.set(true); - } else { - self.setup_fallback_frame_timer()?; - } - } - } + XEvent::ReparentNotify(e) if e.window == self.shared.raw_id() => { + self.sizing_state.handle_parent_notify(e) } - XEvent::ReparentNotify(e) => { - if let Some(window_id) = NonZero::new(e.window) { - if window_id == self.window.xcb_window.id() { - self.window.parent_id.set(NonZero::new(e.parent)); - } - } + XEvent::MapNotify(e) if e.window == self.shared.raw_id() => { + self.present_state.handle_window_mapped( + &self.shared.present_state, + &self.shared.xcb_window, + &self.loop_handle, + )?; } - XEvent::PresentCompleteNotify(e) => { - if e.kind != CompleteKind::NOTIFY_MSC { - return Ok(()); - } - - if e.window != self.window.raw_id() { - return Ok(()); - } - - let Some(last_requested_serial) = self.last_requested_serial else { - return Ok(()); - }; - - if last_requested_serial != e.serial { - return Ok(()); - } - - if let Some((last_received_serial, last_received_msc)) = self.last_received_present - { - if last_received_serial == e.serial { - return Ok(()); - } - - if e.msc <= last_received_msc { - self.last_received_present = Some((e.serial, e.msc)); - return Ok(()); - } - } - - self.last_received_present = Some((e.serial, e.msc)); - self.draw_now = true; + XEvent::PresentCompleteNotify(e) if e.window == self.shared.raw_id() => { + self.present_state.handle_present_complete_notify(e); + } + XEvent::Expose(e) if e.window == self.shared.raw_id() => { + self.present_state.handle_expose_event( + e, + &self.handler, + &self.shared, + &self.sizing_state, + ); } _ => {} @@ -645,19 +351,4 @@ impl EventLoop { Ok(()) } - - fn handle_event(&mut self, event: Event) { - self.handler.on_event(event); - } -} - -fn mouse_id(id: u8) -> MouseButton { - match id { - 1 => MouseButton::Left, - 2 => MouseButton::Middle, - 3 => MouseButton::Right, - 8 => MouseButton::Back, - 9 => MouseButton::Forward, - id => MouseButton::Other(id), - } } diff --git a/src/platform/x11/gl.rs b/src/platform/x11/gl.rs index 5dfe68a5..766465f6 100644 --- a/src/platform/x11/gl.rs +++ b/src/platform/x11/gl.rs @@ -1,79 +1,22 @@ -use super::*; +use super::prelude::*; use crate::gl::*; use crate::wrappers::glx::*; -use crate::wrappers::xlib::XLibError; use std::error::Error; use crate::platform::gl::egl::EglGlContext; use crate::platform::gl::glx::GlxGlContext; use crate::platform::x11::xcb_window::XcbWindow; -use crate::wrappers::egl::{EglConfig, EglDisplay, EglError, EglVersion, MissingSymbolError}; +use crate::wrappers::egl::*; use std::ffi::{c_void, CStr}; use std::rc::Rc; -use x11_dl::error::OpenError; use x11rb::protocol::xproto::Visualid; mod egl; +mod error; mod glx; -#[derive(Debug)] -pub enum CreationFailedError { - NoValidFBConfig, - NoVisual, - GetProcAddressFailed, - MakeCurrentFailed, - ContextCreationFailed, - X11Error(XLibError), - OpenError(OpenError), - EGLLoadError(libloading::Error), - EGLMissingSymbol(MissingSymbolError), - EglError(EglError), - EglNoDisplay, - EglUnsupportedVersion(EglVersion), - EglUnknownVisualId(Visualid), - EglInvalidVisualId(i32, TryFromIntError), -} - -impl Display for CreationFailedError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - CreationFailedError::NoValidFBConfig => { - f.write_str("Could not find a valid Framebuffer configuration") - } - CreationFailedError::NoVisual => { - f.write_str("Could not find a matching visual configuration") - } - CreationFailedError::GetProcAddressFailed => f.write_str("GetProcAddress failed"), - CreationFailedError::MakeCurrentFailed => f.write_str("MakeCurrent failed"), - CreationFailedError::ContextCreationFailed => f.write_str("Faile to create GL context"), - CreationFailedError::X11Error(e) => e.fmt(f), - CreationFailedError::OpenError(e) => e.fmt(f), - CreationFailedError::EGLLoadError(e) => { - write!(f, "Could not load EGL library: {e}, {:?}", e.source()) - } - CreationFailedError::EGLMissingSymbol(e) => e.fmt(f), - CreationFailedError::EglError(e) => e.fmt(f), - CreationFailedError::EglNoDisplay => f.write_str("EGL returned no valid display"), - CreationFailedError::EglUnsupportedVersion(e) => { - write!(f, "Unsupported EGL version: {}.{} (EGL 1.5 is required)", e.major, e.minor) - } - CreationFailedError::EglInvalidVisualId(id, e) => { - write!(f, "Invalid Visual ID ({id}) returned by EGL: {e}") - } - CreationFailedError::EglUnknownVisualId(id) => { - write!(f, "Unknown Visual ID returned by EGL: {id}") - } - } - } -} - -impl From for CreationFailedError { - fn from(err: EglError) -> Self { - CreationFailedError::EglError(err) - } -} - -pub type GlContext = Rc; +pub use error::GlCreationFailedError; +pub type PlatformGlContext = Rc; pub enum GlContextInner { Glx(GlxGlContext), @@ -110,7 +53,7 @@ impl GlContextInner { /// Use [Self::get_fb_config_and_visual] to create both of these things. pub fn create( window: &XcbWindow, connection: &Rc, fb_config: FbConfig, - ) -> Result> { + ) -> PlatformResult> { let inner = match fb_config.fb_config { FbConfigInner::Glx { glx, config } => GlContextInner::Glx(GlxGlContext::create( @@ -133,19 +76,19 @@ impl GlContextInner { /// using the visual also returned from this function. pub fn get_fb_config_and_visual( connection: &Rc, config: GlConfig, - ) -> Result<(FbConfig, WindowConfig)> { + ) -> PlatformResult<(FbConfig, WindowConfig)> { EglGlContext::get_fb_config_and_visual(connection, config.clone()) .or_else(|_| GlxGlContext::get_fb_config_and_visual(connection, config)) } - pub unsafe fn make_current(&self) -> Result<()> { + pub unsafe fn make_current(&self) -> PlatformResult<()> { match self { GlContextInner::Glx(glx) => glx.make_current(), GlContextInner::Egl(egl) => egl.make_current(), } } - pub unsafe fn make_not_current(&self) -> Result<()> { + pub unsafe fn make_not_current(&self) -> PlatformResult<()> { match self { GlContextInner::Glx(glx) => glx.make_not_current(), GlContextInner::Egl(egl) => egl.make_not_current(), @@ -159,7 +102,7 @@ impl GlContextInner { } } - pub fn swap_buffers(&self) -> Result<()> { + pub fn swap_buffers(&self) -> PlatformResult<()> { match self { GlContextInner::Glx(glx) => glx.swap_buffers(), GlContextInner::Egl(egl) => egl.swap_buffers(), diff --git a/src/platform/x11/gl/egl.rs b/src/platform/x11/gl/egl.rs index 18930ba5..4505a102 100644 --- a/src/platform/x11/gl/egl.rs +++ b/src/platform/x11/gl/egl.rs @@ -1,11 +1,6 @@ -use crate::gl::GlConfig; -use crate::platform::gl::{CreationFailedError, FbConfig, FbConfigInner, WindowConfig}; +use super::*; use crate::platform::x11::xcb_window::XcbWindow; -use crate::platform::{PlatformError, X11Connection}; -use crate::wrappers::egl::{Egl, EglConfig, EglContext, EglDisplay, EglSurface}; use std::ffi::{c_void, CStr}; -use std::rc::Rc; -use x11rb::protocol::xproto::Visualid; pub struct EglGlContext { surface: EglSurface, @@ -32,11 +27,12 @@ impl EglGlContext { let egl = Egl::open()?; let display = egl.create_display(connection)?; - let config = display.choose_config(&gl_config)?.ok_or(CreationFailedError::EglNoDisplay)?; + let config = + display.choose_config(&gl_config)?.ok_or(GlCreationFailedError::EglNoDisplay)?; let visual = config.get_visual_id(&display)?; let depth = Self::find_visual_depth_for_id(connection, visual) - .ok_or(CreationFailedError::EglUnknownVisualId(visual))?; + .ok_or(GlCreationFailedError::EglUnknownVisualId(visual))?; let window_config = WindowConfig { depth, visual }; let fb_config = FbConfig { gl_config, fb_config: FbConfigInner::Egl { display, config } }; diff --git a/src/platform/x11/gl/error.rs b/src/platform/x11/gl/error.rs new file mode 100644 index 00000000..e0e5e0f4 --- /dev/null +++ b/src/platform/x11/gl/error.rs @@ -0,0 +1,62 @@ +use super::*; +use crate::wrappers::xlib::XLibError; + +use std::fmt::{Display, Formatter}; +use std::num::TryFromIntError; +use x11_dl::error::OpenError; +use x11rb::protocol::xproto::Visualid; + +#[derive(Debug)] +pub enum GlCreationFailedError { + NoValidFBConfig, + NoVisual, + GetProcAddressFailed, + MakeCurrentFailed, + ContextCreationFailed, + X11Error(XLibError), + OpenError(OpenError), + EGLLoadError(libloading::Error), + EGLMissingSymbol(MissingSymbolError), + Egl(EglError), + EglNoDisplay, + EglUnsupportedVersion(EglVersion), + EglUnknownVisualId(Visualid), + EglInvalidVisualId(i32, TryFromIntError), +} + +use GlCreationFailedError::*; + +impl Display for GlCreationFailedError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + NoValidFBConfig => f.write_str("Could not find a valid Framebuffer configuration"), + NoVisual => f.write_str("Could not find a matching visual configuration"), + GetProcAddressFailed => f.write_str("GetProcAddress failed"), + MakeCurrentFailed => f.write_str("MakeCurrent failed"), + ContextCreationFailed => f.write_str("Failed to create GL context"), + X11Error(e) => e.fmt(f), + OpenError(e) => e.fmt(f), + EGLLoadError(e) => { + write!(f, "Could not load EGL library: {e}, {:?}", e.source()) + } + EGLMissingSymbol(e) => e.fmt(f), + Egl(e) => e.fmt(f), + EglNoDisplay => f.write_str("EGL returned no valid display"), + EglUnsupportedVersion(e) => { + write!(f, "Unsupported EGL version: {}.{} (EGL 1.5 is required)", e.major, e.minor) + } + EglInvalidVisualId(id, e) => { + write!(f, "Invalid Visual ID ({id}) returned by EGL: {e}") + } + EglUnknownVisualId(id) => { + write!(f, "Unknown Visual ID returned by EGL: {id}") + } + } + } +} + +impl From for GlCreationFailedError { + fn from(err: EglError) -> Self { + Egl(err) + } +} diff --git a/src/platform/x11/gl/glx.rs b/src/platform/x11/gl/glx.rs index 26896b23..eee3a744 100644 --- a/src/platform/x11/gl/glx.rs +++ b/src/platform/x11/gl/glx.rs @@ -1,8 +1,6 @@ use super::*; use crate::gl::GlConfig; -use crate::platform::gl::CreationFailedError; use crate::platform::x11::xcb_window::XcbWindow; -use crate::platform::X11Connection; use crate::wrappers::glx::{Glx, GlxFbConfig}; use crate::wrappers::xlib::XErrorHandler; use std::ffi::{c_ulong, c_void, CStr}; @@ -21,12 +19,12 @@ impl GlxGlContext { pub fn create( window: &XcbWindow, connection: &Rc, gl_config: GlConfig, fb_config: GlxFbConfig, glx: Glx, - ) -> Result { + ) -> PlatformResult { let xlib_connection = connection.conn.xlib_connection(); XErrorHandler::handle(xlib_connection, |error_handler| { let Some(create_context) = glx.get_glx_create_context_attribs_arb() else { - return Err(CreationFailedError::GetProcAddressFailed.into()); + return Err(GlCreationFailedError::GetProcAddressFailed.into()); }; let context = @@ -38,7 +36,7 @@ impl GlxGlContext { pub fn get_fb_config_and_visual( connection: &X11Connection, config: GlConfig, - ) -> Result<(FbConfig, WindowConfig)> { + ) -> PlatformResult<(FbConfig, WindowConfig)> { let glx = Glx::open()?; let xlib_connection = connection.conn.xlib_connection(); @@ -61,7 +59,7 @@ impl GlxGlContext { }) } - pub unsafe fn make_current(&self) -> Result<()> { + pub unsafe fn make_current(&self) -> PlatformResult<()> { XErrorHandler::handle(self.connection.conn.xlib_connection(), |error_handler| { self.glx.make_current( self.connection.conn.xlib_connection(), @@ -72,7 +70,7 @@ impl GlxGlContext { }) } - pub unsafe fn make_not_current(&self) -> Result<()> { + pub unsafe fn make_not_current(&self) -> PlatformResult<()> { XErrorHandler::handle(self.connection.conn.xlib_connection(), |error_handler| { self.glx.clear_current(self.connection.conn.xlib_connection(), error_handler) }) @@ -89,7 +87,7 @@ impl GlxGlContext { } } - pub fn swap_buffers(&self) -> Result<()> { + pub fn swap_buffers(&self) -> PlatformResult<()> { XErrorHandler::handle(self.connection.conn.xlib_connection(), |error_handler| { self.glx.swap_buffers( self.connection.conn.xlib_connection(), diff --git a/src/platform/x11/handler.rs b/src/platform/x11/handler.rs new file mode 100644 index 00000000..5859b158 --- /dev/null +++ b/src/platform/x11/handler.rs @@ -0,0 +1,41 @@ +use crate::platform::x11::error::FatalError; +use crate::{DamageArea, EventStatus, TimerHandle, WindowHandler, WindowSize}; + +pub struct Handler { + handler: Box, +} + +impl Handler { + pub fn new(handler: Box) -> Self { + Self { handler } + } + + pub fn draw(&self) -> Result<(), FatalError> { + self.handler.draw().map_err(|e| FatalError::Redraw(e.to_string())) + } + + pub fn resize(&self, new_size: WindowSize) -> Result<(), ()> { + if let Err(e) = self.handler.resized(new_size) { + crate::warn!("Failed to resize window: {}", e); + Err(()) + } else { + Ok(()) + } + } + + pub fn poll(&self) { + self.handler.poll() + } + + pub fn on_event(&self, event: crate::Event) -> EventStatus { + self.handler.on_event(event) + } + + pub fn on_timer(&self, handle: &TimerHandle) { + self.handler.on_timer(handle) + } + + pub fn damage(&self, area: DamageArea) { + self.handler.damage(area) + } +} diff --git a/src/platform/x11/mod.rs b/src/platform/x11/mod.rs index 9c106b6c..edeea9d7 100644 --- a/src/platform/x11/mod.rs +++ b/src/platform/x11/mod.rs @@ -1,45 +1,67 @@ -mod xcb_connection; - -use dpi::{PhysicalPosition, PhysicalSize}; -use raw_window_handle::{ - DisplayHandle, HandleError, HasWindowHandle, RawWindowHandle, XcbWindowHandle, -}; -use std::fmt::{Display, Formatter}; -use std::num::{NonZero, NonZeroU32, TryFromIntError}; -use std::rc::Rc; -use std::sync::Arc; -use x11rb::protocol::xproto::ExposeEvent; -pub(crate) use xcb_connection::X11Connection; - -mod window; -pub use window::*; - mod cursor; mod drag_n_drop; mod error; mod event_loop; +#[cfg(feature = "opengl")] +pub mod gl; +mod handler; mod keyboard; -mod visual_info; -mod xcb_window; - +mod mouse; +mod present; +mod sizing; mod timer; +mod visual_info; mod waker; mod window_shared; mod window_thread; +mod x11_connection; +mod xcb_window; + +pub(crate) mod prelude { + pub use super::cursor::*; + pub use super::error::{CookieExt as _, FatalError, PlatformError, ReplyExt as _}; + pub use super::event_loop::*; + pub use super::handler::Handler; + pub use super::mouse::*; + pub use super::present::*; + pub use super::sizing::*; + pub use super::timer::*; + pub use super::visual_info::WindowVisualConfig; + pub use super::waker::WindowWaker; + pub use super::window_shared::WindowShared; + pub use super::window_thread::{HostCallback, WindowThreadHandle, WindowThreadShared}; + pub use super::x11_connection::X11Connection; + pub use super::xcb_window::XcbWindow; + pub(crate) use crate::{dpi::*, tracing::*, MouseCursor, WindowSettings, WindowSize}; + pub use std::cell::Cell; + pub use std::rc::Rc; + pub use std::sync::Arc; + pub use x11rb::connection::Connection; + pub use x11rb::errors::{ConnectionError, ReplyOrIdError}; + pub use x11rb::protocol::xproto::{ConnectionExt as _, Cursor}; + pub use x11rb::xcb_ffi::XCBConnection; + pub type PlatformResult = Result; + #[cfg(feature = "opengl")] + pub use super::gl::{GlContextInner, GlCreationFailedError, PlatformGlContext}; + #[cfg(feature = "opengl")] + pub use crate::gl::{GlConfig, GlContext}; +} -pub use error::{CookieExt as _, PlatformError}; -pub(crate) type Result = std::result::Result; +use prelude::*; -use crate::platform::x11::window_shared::WindowInner; use crate::wrappers::xlib::XlibXcbConnection; +use raw_window_handle::{ + DisplayHandle, HandleError, HasWindowHandle, RawWindowHandle, XcbWindowHandle, +}; +use std::fmt::{Display, Formatter}; +use std::num::{NonZero, NonZeroU32, TryFromIntError}; -pub type WindowContext = Rc; +pub type WindowContext = Rc; +pub use error::PlatformError; +pub use present::DamageRect; pub use timer::TimerHandle; pub use waker::WindowWaker; -#[cfg(feature = "opengl")] -pub mod gl; - #[derive(Clone)] pub struct PlatformHandle { connection: Arc, @@ -129,35 +151,8 @@ pub fn assume_standalone_in_process() { // No-op on X11 } -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct DamageRect { - pos: PhysicalPosition, - size: PhysicalSize, -} +pub type WindowHandle = WindowThreadHandle; -impl DamageRect { - #[inline] - pub fn new(event: &ExposeEvent) -> Self { - Self { - pos: PhysicalPosition::new(event.x, event.y), - size: PhysicalSize::new(event.width, event.height), - } - } - - #[inline] - pub fn position(&self) -> PhysicalPosition { - PhysicalPosition { x: self.pos.x.into(), y: self.pos.y.into() } - } - - #[inline] - pub fn size(&self) -> PhysicalSize { - PhysicalSize { height: self.size.height.into(), width: self.size.width.into() } - } - - pub fn fully_covers(&self, window_size: PhysicalSize) -> bool { - self.pos.x == 0 - && self.pos.y == 0 - && window_size.width <= self.size.width - && window_size.height <= self.size.height - } +pub fn copy_to_clipboard(_data: &str) { + unimplemented!() } diff --git a/src/platform/x11/mouse.rs b/src/platform/x11/mouse.rs new file mode 100644 index 00000000..5ba1b1f9 --- /dev/null +++ b/src/platform/x11/mouse.rs @@ -0,0 +1,94 @@ +// For all the keyboard and mouse events, you can fetch +// `x`, `y`, `detail`, and `state`. +// - `x` and `y` are the position inside the window where the cursor currently is +// when the event happened. +// - `detail` will tell you which keycode was pressed/released (for keyboard events) +// or which mouse button was pressed/released (for mouse events). +// For mouse events, here's what the value means (at least on my current mouse): +// 1 = left mouse button +// 2 = middle mouse button (scroll wheel) +// 3 = right mouse button +// 4 = scroll wheel up +// 5 = scroll wheel down +// 8 = lower side button ("back" button) +// 9 = upper side button ("forward" button) +// Note that you *will* get a "button released" event for even the scroll wheel +// events, which you can probably ignore. +// - `state` will tell you the state of the main three mouse buttons and some of +// the keyboard modifier keys at the time of the event. +// http://rtbo.github.io/rust-xcb/src/xcb/ffi/xproto.rs.html#445 + +use crate::platform::prelude::Handler; +use crate::platform::x11::keyboard::key_mods; +use crate::{Event, MouseButton, MouseEvent, ScrollDelta}; +use dpi::PhysicalPosition; +use x11rb::protocol::xproto::{ + ButtonPressEvent, ButtonReleaseEvent, EnterNotifyEvent, LeaveNotifyEvent, MotionNotifyEvent, +}; + +pub fn handle_motion_notify(event: MotionNotifyEvent, handler: &Handler) { + let physical_pos = PhysicalPosition::new(event.event_x, event.event_y); + + handler.on_event(Event::Mouse(MouseEvent::CursorMoved { + position: physical_pos.cast(), + modifiers: key_mods(event.state), + })); +} + +pub fn handle_enter_notify(event: EnterNotifyEvent, handler: &Handler) { + handler.on_event(Event::Mouse(MouseEvent::CursorEntered)); + // since no `MOTION_NOTIFY` event is generated when `ENTER_NOTIFY` is generated, + // we generate a CursorMoved as well, so the mouse position from here isn't lost + let physical_pos = PhysicalPosition::new(event.event_x, event.event_y); + handler.on_event(Event::Mouse(MouseEvent::CursorMoved { + position: physical_pos.cast(), + modifiers: key_mods(event.state), + })); +} + +pub fn handle_leave_notify(_: LeaveNotifyEvent, handler: &Handler) { + handler.on_event(Event::Mouse(MouseEvent::CursorLeft)); +} + +pub fn handle_button_press(event: ButtonPressEvent, handler: &Handler) { + match event.detail { + 4..=7 => { + handler.on_event(Event::Mouse(MouseEvent::WheelScrolled { + delta: match event.detail { + 4 => ScrollDelta::Lines { x: 0.0, y: 1.0 }, + 5 => ScrollDelta::Lines { x: 0.0, y: -1.0 }, + 6 => ScrollDelta::Lines { x: -1.0, y: 0.0 }, + 7 => ScrollDelta::Lines { x: 1.0, y: 0.0 }, + _ => unreachable!(), + }, + modifiers: key_mods(event.state), + })); + } + detail => { + handler.on_event(Event::Mouse(MouseEvent::ButtonPressed { + button: button_from_id(detail), + modifiers: key_mods(event.state), + })); + } + } +} + +pub fn handle_button_release(event: ButtonReleaseEvent, handler: &Handler) { + if !(4..=7).contains(&event.detail) { + handler.on_event(Event::Mouse(MouseEvent::ButtonReleased { + button: button_from_id(event.detail), + modifiers: key_mods(event.state), + })); + } +} + +fn button_from_id(id: u8) -> MouseButton { + match id { + 1 => MouseButton::Left, + 2 => MouseButton::Middle, + 3 => MouseButton::Right, + 8 => MouseButton::Back, + 9 => MouseButton::Forward, + id => MouseButton::Other(id), + } +} diff --git a/src/platform/x11/present.rs b/src/platform/x11/present.rs new file mode 100644 index 00000000..b73a0aa0 --- /dev/null +++ b/src/platform/x11/present.rs @@ -0,0 +1,296 @@ +use crate::platform::prelude::*; +use crate::platform::x11::handler::Handler; +use crate::platform::x11::sizing::SizingState; +use crate::platform::x11::window_shared::WindowShared; +use crate::platform::x11::window_thread::RedrawRequested; +use crate::DamageArea; +use calloop::timer::{TimeoutAction, Timer}; +use calloop::LoopHandle; +use dpi::{PhysicalPosition, PhysicalSize}; +use std::cell::Cell; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Mutex; +use std::time::{Duration, Instant}; +use x11rb::connection::Connection; +use x11rb::protocol::present::{CompleteKind, CompleteNotifyEvent}; +use x11rb::protocol::xproto::ExposeEvent; + +pub struct PresentStateShared { + present_notify_requested: Cell, + poll_requested: Cell, +} + +impl PresentStateShared { + pub(crate) fn request_present_notify_after( + &self, duration: Duration, loop_handle: &LoopHandle, + ) { + if duration.is_zero() || duration.as_millis() < 1 { + self.request_present_notify(); + return; + } + + let result = loop_handle.insert_source(Timer::from_duration(duration), |_, _, e| { + e.shared().present_state.request_present_notify(); + TimeoutAction::Drop + }); + + if let Err(e) = result { + warn!("{}", e); + self.request_present_notify(); + } + } +} + +impl PresentStateShared { + pub fn new() -> Self { + Self { present_notify_requested: false.into(), poll_requested: false.into() } + } + + pub fn request_present_notify(&self) { + self.present_notify_requested.set(true) + } +} + +pub struct PresentState { + draw_now: bool, + last_requested_serial: Option, + last_received_present: Option<(u32, u64)>, +} + +impl PresentState { + pub(crate) fn handle_requests( + &mut self, shared: &WindowShared, handler: &Handler, loop_handle: &LoopHandle, + ) -> Result<(), FatalError> { + let shared_state = &shared.present_state; + let thread_state = &shared.main_thread_shared.present; + + // Consume all requests from above poll + if let Some(redraw_after) = thread_state.take_redraw_request() { + shared.request_redraw_after(redraw_after) + } + + if self.draw_now { + let _ = thread_state.take_poll_request(); + + handler.poll(); + shared.present_state.present_notify_requested.set(false); + + handler.draw()?; + + shared_state.poll_requested.set(false); + self.draw_now = false; + + shared.connection.conn.flush()?; + } else if thread_state.take_poll_request() || shared_state.poll_requested.take() { + handler.poll(); + } + + self.handle_present_notify(&shared.present_state, &shared.xcb_window, loop_handle)?; + + Ok(()) + } +} + +impl PresentState { + pub(crate) fn handle_expose_event( + &self, e: ExposeEvent, handler: &Handler, shared: &WindowShared, sizing_state: &SizingState, + ) { + if e.count == 0 { + shared.present_state.present_notify_requested.set(true); + } + + let current_window_size = sizing_state.non_coalesced_current_size(shared); + + let damage_rect = DamageRect::new(&e); + let area = if damage_rect.fully_covers(current_window_size) { + DamageArea::FullWindow + } else { + DamageArea::Rect(damage_rect.into()) + }; + + handler.damage(area); + } +} + +impl PresentState { + pub fn handle_window_mapped( + &self, shared: &PresentStateShared, window: &XcbWindow, loop_handle: &LoopHandle, + ) -> Result<(), FatalError> { + if window.present_supported() && window.present_select_input()? { + shared.present_notify_requested.set(true); + } else { + Self::setup_fallback_frame_timer(loop_handle)?; + } + + Ok(()) + } +} + +impl PresentState { + pub fn new() -> Self { + Self { draw_now: false, last_requested_serial: None, last_received_present: None } + } + + pub fn handle_present_complete_notify(&mut self, e: CompleteNotifyEvent) { + if e.kind != CompleteKind::NOTIFY_MSC { + return; + } + + let Some(last_requested_serial) = self.last_requested_serial else { return }; + + if last_requested_serial != e.serial { + return; + } + + if let Some((last_received_serial, last_received_msc)) = self.last_received_present { + if last_received_serial == e.serial { + return; + } + + if e.msc <= last_received_msc { + self.last_received_present = Some((e.serial, e.msc)); + return; + } + } + + self.last_received_present = Some((e.serial, e.msc)); + self.draw_now = true; + } + + fn setup_fallback_frame_timer( + loop_handle: &LoopHandle, + ) -> Result<(), calloop::Error> { + const FRAME_INTERVAL: Duration = Duration::from_millis(15); + + fn handle_frame(evloop: &mut EventLoop, previous_deadline: Instant) -> TimeoutAction { + evloop.present_state.draw_now = true; + + // We'll try to keep a consistent frame pace. If the last frame couldn't be processed in + // the expected frame time, this will throttle down to prevent multiple frames from + // being queued up. + + let now = Instant::now(); + + let Some(next_deadline) = previous_deadline.checked_add(FRAME_INTERVAL) else { + return TimeoutAction::ToDuration(FRAME_INTERVAL); + }; + + if next_deadline >= now { + return TimeoutAction::ToDuration(FRAME_INTERVAL); + } + + TimeoutAction::ToInstant(next_deadline) + } + + loop_handle + .insert_source(Timer::from_duration(FRAME_INTERVAL), |i, _, e| handle_frame(e, i)) + .map_err(|e| e.error)?; + + Ok(()) + } + + pub fn handle_present_notify( + &mut self, shared: &PresentStateShared, window: &XcbWindow, + loop_handle: &LoopHandle, + ) -> Result<(), FatalError> { + if !shared.present_notify_requested.get() { + return Ok(()); + } + + if !window.present_supported() { + shared.present_notify_requested.set(false); + return Ok(()); + } + + let (next_serial, target_msc) = + match (self.last_requested_serial, self.last_received_present) { + // First request, always send + (None, None) => (0, 0), + (Some(sent_serial), Some((received_serial, last_msc))) + if sent_serial == received_serial => + { + (sent_serial.wrapping_add(1), last_msc.wrapping_add(2)) + } + // We sent our first request but have not gotten a response yet. + // Or, we sent a request, but the last response we've gotten isn't that one. + // Do not send. + _ => { + shared.present_notify_requested.set(false); + return Ok(()); + } + }; + + if window.present_notify(target_msc, next_serial)?.check_is_ok() { + self.last_requested_serial = Some(next_serial); + } else { + self.last_requested_serial = None; + Self::setup_fallback_frame_timer(loop_handle)?; + } + shared.present_notify_requested.set(false); + + Ok(()) + } +} + +pub struct PresentThreadShared { + poll_requested: AtomicBool, + redraw_requested_after: Mutex>, +} + +impl PresentThreadShared { + pub(crate) fn new() -> Self { + Self { poll_requested: false.into(), redraw_requested_after: None.into() } + } + + pub fn request_redraw_after(&self, duration: Duration) { + // Ignore a poisoned mutex, we just fully override this value anyway. + let mut guard = self.redraw_requested_after.lock().unwrap_or_else(|g| g.into_inner()); + *guard = Some(RedrawRequested::from_duration(duration)); + } + + fn take_redraw_request(&self) -> Option { + let mut guard = self.redraw_requested_after.lock().unwrap_or_else(|g| g.into_inner()); + guard.take().map(|w| w.to_duration()) + } + + pub fn request_poll(&self) { + self.poll_requested.store(true, Ordering::Relaxed); + } + + fn take_poll_request(&self) -> bool { + self.poll_requested.swap(false, Ordering::Relaxed) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct DamageRect { + pos: PhysicalPosition, + size: PhysicalSize, +} + +impl DamageRect { + #[inline] + pub fn new(event: &ExposeEvent) -> Self { + Self { + pos: PhysicalPosition::new(event.x, event.y), + size: PhysicalSize::new(event.width, event.height), + } + } + + #[inline] + pub fn position(&self) -> PhysicalPosition { + PhysicalPosition { x: self.pos.x.into(), y: self.pos.y.into() } + } + + #[inline] + pub fn size(&self) -> PhysicalSize { + PhysicalSize { height: self.size.height.into(), width: self.size.width.into() } + } + + pub fn fully_covers(&self, window_size: PhysicalSize) -> bool { + self.pos.x == 0 + && self.pos.y == 0 + && window_size.width <= self.size.width + && window_size.height <= self.size.height + } +} diff --git a/src/platform/x11/sizing.rs b/src/platform/x11/sizing.rs new file mode 100644 index 00000000..0c08e221 --- /dev/null +++ b/src/platform/x11/sizing.rs @@ -0,0 +1,316 @@ +use super::prelude::*; +use crate::utils::SizingStrategy; +use crate::{WindowSettings, WindowSize}; +use dpi::{PhysicalSize, Size}; +use std::cell::Cell; +use std::num::{NonZero, NonZeroU32}; +use std::sync::atomic::{AtomicU32, AtomicU64, Ordering}; +use std::sync::OnceLock; +use x11rb::properties::WmSizeHints; +use x11rb::protocol::xproto::{ConfigureNotifyEvent, ReparentNotifyEvent}; + +pub struct SizingState { + new_size: Option>, + new_parent_size: Option>, + parent_id: Option, +} + +impl SizingState { + pub fn new(parent_id: Option) -> Self { + Self { new_size: None, new_parent_size: None, parent_id } + } + + pub fn handle_parent_notify(&mut self, e: ReparentNotifyEvent) { + self.parent_id = NonZero::new(e.parent); + } + + pub fn non_coalesced_current_size(&self, shared: &WindowShared) -> PhysicalSize { + self.new_size.unwrap_or_else(|| shared.sizing_state.size()) + } + + pub fn handle_coalesced_resize_events( + &mut self, shared: &WindowShared, handler: &Handler, + main_thread: Option<&mut MainThreadCaller>, + ) -> Result<(), FatalError> { + let mut comes_from_parent = false; + + if let Some(new_parent_size) = self.new_parent_size.take() { + if new_parent_size != shared.sizing_state.size() { + // The parent was resized, which means we should resize ourselves too. + if let Err(e) = shared.xcb_window.resize(new_parent_size.cast()) { + crate::warn!("Failed to resize window: {}", e); + } else { + // Makes the rest of this function run on the new parent size immediately (without waiting for a ConfigureNotify round-trip) + // Also overrides any new sizes we may have received this event loop iteration,it would probably be invalidated anyway + self.new_size = Some(new_parent_size); + comes_from_parent = true; + } + } + } + + let Some(new_size) = self.new_size.take() else { return Ok(()) }; + let previous = shared.sizing_state.store_size(new_size, &shared.main_thread_shared.sizing); + + if previous == new_size { + return Ok(()); + }; + + let scale_factor = shared.sizing_state.scale_factor(); + let new_size = shared.sizing_state.window_size(); + + if let Err(()) = handler.resize(new_size) { + shared.sizing_state.store_size(previous, &shared.main_thread_shared.sizing); + shared.xcb_window.resize(previous.cast())?.check_warn(); + return Ok(()); + } + + // Host requests use resize_immediately, which stops the previous == new_size condition + // So if we're here, it's guaranteed not to be from a host request + + if !comes_from_parent { + if let Some(host) = main_thread { + host.send(HostCallback::Resized { + new_size, + previous: WindowSize::from_physical(previous.cast(), scale_factor), + })?; + } + } + + // Immediately schedule a redraw, do not wait for an "expose" event + shared.present_state.request_present_notify(); + + Ok(()) + } + + pub fn handle_configure_notify_event( + &mut self, event: ConfigureNotifyEvent, window: &XcbWindow, + ) { + if event.window == 0 { + return; + } + + // These are coalesced and then handled asynchronously at the end of the event loop + if event.window == window.id().get() { + self.new_size = Some(PhysicalSize::new(event.width, event.height)); + } else if self.parent_id.is_some_and(|pid| pid.get() == event.window) { + // Also resize the window if the parent is resized + // This works around some hosts that might not call set_size() right away (or at all...) + self.new_parent_size = Some(PhysicalSize::new(event.width, event.height)); + } + } + + pub fn handle_host_resize( + &mut self, new_size: Size, handler: &Handler, shared: &WindowShared, + ) -> Result<(), PlatformError> { + let scale_factor = shared.sizing_state.scale_factor(); + let new_size = new_size.to_physical(scale_factor); + + shared.sizing_state.resize_from_host(new_size, handler, shared) + } + + pub fn handle_host_suggest_scale_factor( + &mut self, scale: f64, handler: &Handler, shared: &WindowShared, + ) -> Result<(), PlatformError> { + shared.sizing_state.host_suggested_scale_factor.set(Some(scale)); + + // If the scaling factor is already provided by the system, do nothing + if shared.sizing_state.system_scale_factor.get().is_some() { + return Ok(()); + }; + + let current_logical_size = shared.sizing_state.size().to_logical::(1.0); + let new_physical_size = current_logical_size.to_physical(scale); + + shared.sizing_state.resize_from_host(new_physical_size, handler, shared) + } +} + +pub struct SizingStateShared { + window_size: Cell>, + sizing_strategy: SizingStrategy, + + system_scale_factor: Cell>, + host_suggested_scale_factor: Cell>, +} + +impl SizingStateShared { + pub fn load( + connection: &X11Connection, sizing_thread_shared: &SizingThreadShared, + settings: &WindowSettings, + ) -> Result { + let scaling = connection.resources.xft_dpi.map(|dpi| dpi as f64 / 96.0); + let initial_scale_factor = scaling.unwrap_or(1.0); + + let sizing_strategy = SizingStrategy::from_settings(settings); + let window_size = settings.size.to_physical(initial_scale_factor); + + sizing_thread_shared.set_scaling_factor(initial_scale_factor); + + Ok(Self { + sizing_strategy, + window_size: window_size.into(), + system_scale_factor: scaling.into(), + host_suggested_scale_factor: settings.fallback_scale_factor.into(), + }) + } + + pub fn scale_factor(&self) -> f64 { + if let Some(factor) = self.system_scale_factor.get() { + return factor; + }; + + if let Some(factor) = self.host_suggested_scale_factor.get() { + return factor; + } + + 1.0 + } + + pub fn size(&self) -> PhysicalSize { + self.window_size.get() + } + + pub fn window_size(&self) -> WindowSize { + WindowSize::from_physical(self.window_size.get().cast(), self.scale_factor()) + } + + pub fn make_size_hints(&self) -> WmSizeHints { + get_size_hints(&self.sizing_strategy, self.window_size.get(), self.scale_factor()) + } + + pub fn store_size( + &self, size: PhysicalSize, thread_shared: &SizingThreadShared, + ) -> PhysicalSize { + let previous = self.window_size.replace(size); + + if previous != size { + thread_shared.set_size(size); + } + + previous + } + + pub fn resize_from_handler(&self, size: Size, window: &XcbWindow) -> PlatformResult<()> { + let new_size = self.sizing_strategy.adjust_size(size, self.window_size()).physical; + + if new_size == self.window_size.get().cast() { + return Ok(()); + } + + window.resize(new_size)?.check()?; + + if !self.sizing_strategy.is_resizable() { + let size_hints = get_size_hints(&self.sizing_strategy, new_size, self.scale_factor()); + window.set_size_hints(size_hints)?.check()?; + } + + // This will trigger a `ConfigureNotify` event which will in turn change `self.window_info` + // and notify the window handler about it + + Ok(()) + } + + pub fn resize_from_host( + &self, new_size: PhysicalSize, handler: &Handler, shared: &WindowShared, + ) -> PlatformResult<()> { + let previous = self.store_size(new_size, &shared.main_thread_shared.sizing); + + if previous == new_size { + return Ok(()); + }; + + if let Err(()) = + handler.resize(WindowSize::from_physical(new_size.cast(), self.scale_factor())) + { + self.store_size(previous, &shared.main_thread_shared.sizing); + return Ok(()); + } + + shared.xcb_window.resize(new_size.cast())?.check()?; // Will not call handler, as size is the same as above. + if !self.sizing_strategy.is_resizable() { + let size_hints = get_size_hints(&self.sizing_strategy, new_size, self.scale_factor()); + shared.xcb_window.set_size_hints(size_hints)?.check()?; + } + + // These come from the Host, no need to notify it about the new size + + Ok(()) + } +} + +pub struct SizingThreadShared { + scaling_factor: AtomicU64, + size: AtomicU32, + sizing_strategy: OnceLock, +} + +impl SizingThreadShared { + pub fn new() -> Self { + Self { size: 0.into(), scaling_factor: 0.into(), sizing_strategy: OnceLock::new() } + } + + pub fn init(&self, state: &SizingStateShared) { + let Ok(()) = self.sizing_strategy.set(state.sizing_strategy) else { unreachable!() }; + self.set_size(state.size()); + self.set_scaling_factor(state.scale_factor()); + } + + pub fn get_scaling_factor(&self) -> f64 { + f64::from_be_bytes(self.scaling_factor.load(Ordering::Relaxed).to_ne_bytes()) + } + + fn set_scaling_factor(&self, scale_factor: f64) { + self.scaling_factor + .store(u64::from_be_bytes(scale_factor.to_ne_bytes()), Ordering::Relaxed); + } + + pub fn sizing_strategy(&self) -> SizingStrategy { + self.sizing_strategy.get().copied().unwrap_or_default() + } + + pub fn get_size(&self) -> PhysicalSize { + let bytes = self.size.load(Ordering::Relaxed); + let low = (bytes & u16::MAX as u32) as u16; + let high = (bytes >> 16) as u16; + + PhysicalSize::new(low, high) + } + + pub fn set_size(&self, size: PhysicalSize) { + let bytes = ((size.height as u32) << 16) | (size.width as u32); + self.size.store(bytes, Ordering::Relaxed); + } + + pub fn window_size(&self) -> WindowSize { + let scale_factor = self.get_scaling_factor(); + let size = self.get_size(); + + WindowSize::from_physical(size.cast(), scale_factor) + } +} + +pub fn get_size_hints( + strategy: &SizingStrategy, current_size: PhysicalSize, scale_factor: f64, +) -> WmSizeHints { + let mut size_hints = WmSizeHints::default(); + + match strategy { + SizingStrategy::Fixed => { + size_hints.min_size = Some(to_size_hint(current_size)); + size_hints.max_size = size_hints.min_size; + } + SizingStrategy::Resizable { min_size, max_size } => { + size_hints.min_size = + min_size.map(|s| to_size_hint(s.to_physical::(scale_factor))); + size_hints.max_size = + max_size.map(|s| to_size_hint(s.to_physical::(scale_factor))); + } + } + + size_hints +} + +fn to_size_hint(size: PhysicalSize) -> (i32, i32) { + let size = size.cast(); + (size.width, size.height) +} diff --git a/src/platform/x11/visual_info.rs b/src/platform/x11/visual_info.rs index bdaa7199..a75bcc13 100644 --- a/src/platform/x11/visual_info.rs +++ b/src/platform/x11/visual_info.rs @@ -1,40 +1,41 @@ -use super::xcb_connection::X11Connection; -use crate::platform::*; +use super::prelude::*; +use super::x11_connection::X11Connection; use x11rb::connection::Connection; use x11rb::protocol::xproto::{ Colormap, ColormapAlloc, ConnectionExt, Screen, VisualClass, Visualid, }; use x11rb::COPY_FROM_PARENT; -pub(crate) struct WindowVisualConfig { - #[cfg(feature = "opengl")] - pub fb_config: Option, - +pub struct WindowVisualConfig { pub visual_depth: u8, pub visual_id: Visualid, pub color_map: Option, + + #[cfg(feature = "opengl")] + pub fb_config: Option, } // TODO: make visual negotiation actually check all of a visual's parameters impl WindowVisualConfig { - #[cfg(feature = "opengl")] - pub fn find_best_visual_config_for_gl( - connection: &std::rc::Rc, gl_config: Option, - ) -> Result { - let Some(gl_config) = gl_config else { return Self::find_best_visual_config(connection) }; + pub fn find_best_visual_config( + connection: &Rc, settings: &mut WindowSettings, + ) -> PlatformResult { + #[cfg(feature = "opengl")] + if let Some(gl_config) = settings.gl_config.take() { + let (fb_config, window_config) = + GlContextInner::get_fb_config_and_visual(connection, gl_config)?; - let (fb_config, window_config) = - super::gl::GlContextInner::get_fb_config_and_visual(connection, gl_config)?; + return Ok(Self { + fb_config: Some(fb_config), + visual_depth: window_config.depth, + visual_id: window_config.visual, + color_map: Some(create_color_map(connection, window_config.visual)?), + }); + } - Ok(Self { - fb_config: Some(fb_config), - visual_depth: window_config.depth, - visual_id: window_config.visual, - color_map: Some(create_color_map(connection, window_config.visual)?), - }) - } + #[cfg(not(feature = "opengl"))] + let _ = settings; - pub fn find_best_visual_config(connection: &X11Connection) -> Result { match find_visual_for_depth(connection.default_screen(), 32) { None => Ok(Self::copy_from_parent()), Some(visual_id) => Ok(Self { @@ -56,11 +57,21 @@ impl WindowVisualConfig { color_map: None, } } + + #[cfg(feature = "opengl")] + pub fn make_gl_context( + self, window: &XcbWindow, connection: &Rc, + ) -> PlatformResult>> { + match self.fb_config { + None => Ok(None), + Some(fb_config) => Ok(Some(GlContextInner::create(window, connection, fb_config)?)), + } + } } // For this 32-bit depth to work, you also need to define a color map and set a border // pixel: https://cgit.freedesktop.org/xorg/xserver/tree/dix/window.c#n818 -fn create_color_map(connection: &X11Connection, visual_id: Visualid) -> Result { +fn create_color_map(connection: &X11Connection, visual_id: Visualid) -> PlatformResult { let colormap = connection.conn.generate_id()?; connection.conn.create_colormap( ColormapAlloc::NONE, diff --git a/src/platform/x11/waker.rs b/src/platform/x11/waker.rs index c5872720..109f0a4c 100644 --- a/src/platform/x11/waker.rs +++ b/src/platform/x11/waker.rs @@ -15,12 +15,12 @@ impl WindowWaker { } pub fn request_redraw_after(&self, duration: Duration) { - self.shared.request_redraw_after(duration); + self.shared.present.request_redraw_after(duration); self.loop_signal.wakeup(); } pub fn request_poll(&self) { - self.shared.request_poll(); + self.shared.present.request_poll(); self.loop_signal.wakeup(); } } diff --git a/src/platform/x11/window.rs b/src/platform/x11/window.rs deleted file mode 100644 index f8737f5d..00000000 --- a/src/platform/x11/window.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::platform::x11::window_thread::WindowThreadHandle; - -pub type WindowHandle = WindowThreadHandle; - -pub fn copy_to_clipboard(_data: &str) { - unimplemented!() -} diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 3da45c2b..a44a5ad7 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -1,193 +1,85 @@ -use crate::dpi::{PhysicalSize, Size}; -use crate::platform::x11::event_loop::EventLoop; -use crate::platform::x11::timer::insert_timer; -use crate::platform::x11::visual_info::WindowVisualConfig; -use crate::platform::x11::waker::WindowWaker; -use crate::platform::x11::window_thread::WindowThreadShared; -use crate::platform::x11::xcb_connection::get_size_hints; -use crate::platform::x11::xcb_window::XcbWindow; -use crate::platform::*; -use crate::utils::SizingStrategy; -use crate::{warn, MouseCursor, WindowHandler, WindowSettings, WindowSize}; -use calloop::timer::{TimeoutAction, Timer}; +use super::prelude::*; use calloop::{LoopHandle, LoopSignal}; +use dpi::Size; use raw_window_handle::{DisplayHandle, XlibWindowHandle}; -use std::cell::Cell; -use std::num::NonZeroU32; -use std::rc::Rc; -use std::sync::Arc; use std::time::Duration; use x11rb::protocol::xproto; -use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt, InputFocus, Visualid}; -use x11rb::CURRENT_TIME; +use x11rb::protocol::xproto::Visualid; -pub struct ScalingFactor { - system: Cell>, - suggested: Cell>, -} +/// Data that is shared between the event loop and the window handler. +pub struct WindowShared { + pub xcb_window: XcbWindow, + pub connection: Rc, + visual_id: Visualid, -impl ScalingFactor { - pub fn get(&self) -> f64 { - if let Some(factor) = self.system.get() { - return factor; - }; + pub is_focused: Cell, - if let Some(factor) = self.suggested.get() { - return factor; - } + pub cursor_state: CursorStateShared, + pub present_state: PresentStateShared, + pub sizing_state: SizingStateShared, - 1.0 - } + loop_signal: LoopSignal, + loop_handle: LoopHandle<'static, EventLoop>, - pub fn suggest(&self, value: f64) -> bool { - self.suggested.set(Some(value)); + pub main_thread_shared: Arc, - self.system.get().is_none() - } -} - -pub(crate) struct WindowInner { - // GlContext should be dropped **before** XcbConnection is dropped #[cfg(feature = "opengl")] - gl_context: Option, - - pub(crate) xcb_window: XcbWindow, - pub(crate) parent_id: Cell>, - pub(crate) connection: Rc, - - pub(crate) scaling_factor: ScalingFactor, - - window_size: Cell>, - pub(crate) sizing_strategy: SizingStrategy, - mouse_cursor: Cell, - pub(crate) visual_id: Visualid, - - pub(crate) is_focused: Cell, - pub(crate) present_notify_requested: Cell, - pub(crate) poll_requested: Cell, - pub(crate) loop_signal: LoopSignal, - loop_handle: LoopHandle<'static, EventLoop>, - - pub(crate) main_thread_shared: Arc, + gl_context: Option, } -impl WindowInner { +impl WindowShared { pub(crate) fn create( - options: WindowSettings, ev_loop: &calloop::EventLoop<'static, EventLoop>, - shared: Arc, - ) -> Result> { - // Connect to the X server - let xcb_connection = X11Connection::new()?; - - let scaling = xcb_connection.get_scaling(); - - let initial_scale_factor = scaling.unwrap_or(1.0); - shared.set_scaling_factor(initial_scale_factor); - - let physical_size = options.size.to_physical(initial_scale_factor); - - let sizing_strategy = SizingStrategy::from_settings(&options); - - let size_hints = get_size_hints(&sizing_strategy, physical_size, initial_scale_factor); + mut settings: WindowSettings, ev_loop: &calloop::EventLoop<'static, EventLoop>, + thread_shared: Arc, + ) -> PlatformResult> { + let connection = X11Connection::connect()?; - let connection = Rc::new(xcb_connection); + let sizing_state = SizingStateShared::load(&connection, &thread_shared.sizing, &settings)?; - #[cfg(feature = "opengl")] - let visual_info = - WindowVisualConfig::find_best_visual_config_for_gl(&connection, options.gl_config)?; + let connection = Rc::new(connection); - #[cfg(not(feature = "opengl"))] - let visual_info = WindowVisualConfig::find_best_visual_config(&connection)?; + let visual_config = + WindowVisualConfig::find_best_visual_config(&connection, &mut settings)?; - let will_have_parent = options.parent.is_some() || options.wait_for_parent; - let parent_id = options.parent.map(|p| p.inner.window_id); + let parent_id = settings.parent.map(|p| p.inner.window_id); let xcb_window = - XcbWindow::new(Rc::clone(&connection), physical_size, &visual_info, parent_id)?; - - if will_have_parent { - connection.register_tree_structure_events()?.check()?; - } + XcbWindow::new(Rc::clone(&connection), sizing_state.size(), &visual_config, parent_id)?; let cookies = [ - xcb_window.set_title(&options.title)?, + xcb_window.set_title(&settings.title)?, xcb_window.enable_wm_protocols()?, xcb_window.enable_dnd_protocols()?, - xcb_window.set_size_hints(size_hints)?, + xcb_window.set_size_hints(sizing_state.make_size_hints())?, ]; for cookie in cookies { cookie.check()?; } - #[cfg(feature = "opengl")] - let gl_context = match visual_info.fb_config { - None => None, - Some(fb_config) => { - // Because of the visual negotation we had to take some extra steps to create this context - Some(super::gl::GlContextInner::create(&xcb_window, &connection, fb_config)?) - } - }; - Ok(Rc::new(Self { - connection, - xcb_window, - parent_id: parent_id.into(), - visual_id: visual_info.visual_id, - window_size: physical_size.into(), - scaling_factor: ScalingFactor { - system: scaling.into(), - suggested: options.fallback_scale_factor.into(), - }, - sizing_strategy, - mouse_cursor: MouseCursor::default().into(), - loop_signal: ev_loop.get_signal(), - loop_handle: ev_loop.handle(), - - is_focused: false.into(), - present_notify_requested: false.into(), - poll_requested: false.into(), - main_thread_shared: shared, + visual_id: visual_config.visual_id, #[cfg(feature = "opengl")] - gl_context, - })) - } - - pub fn set_mouse_cursor(&self, mouse_cursor: MouseCursor) -> Result<()> { - if self.mouse_cursor.get() == mouse_cursor { - return Ok(()); - } - - let xid = self.connection.get_cursor(mouse_cursor)?; - - if xid != 0 { - self.connection - .conn - .change_window_attributes( - self.xcb_window.id().get(), - &ChangeWindowAttributesAux::new().cursor(xid), - )? - .check()?; - } - - self.mouse_cursor.set(mouse_cursor); + gl_context: visual_config.make_gl_context(&xcb_window, &connection)?, - Ok(()) - } + loop_signal: ev_loop.get_signal(), + loop_handle: ev_loop.handle(), - pub fn store_size(&self, size: PhysicalSize) -> PhysicalSize { - let previous = self.window_size.replace(size); + is_focused: false.into(), - if previous != size { - self.main_thread_shared.set_size(size); - } + cursor_state: CursorStateShared::new(), + present_state: PresentStateShared::new(), + sizing_state, + main_thread_shared: thread_shared, - previous + xcb_window, + connection, + })) } - pub fn get_size(&self) -> PhysicalSize { - self.window_size.get() + pub fn set_mouse_cursor(&self, mouse_cursor: MouseCursor) -> PlatformResult<()> { + self.cursor_state.set_mouse_cursor(mouse_cursor, &self.xcb_window) } pub fn request_close(&self) { @@ -196,24 +88,11 @@ impl WindowInner { } pub fn request_redraw(&self) { - self.present_notify_requested.set(true) + self.present_state.request_present_notify() } pub fn request_redraw_after(&self, duration: Duration) { - if duration.is_zero() || duration.as_millis() < 1 { - self.request_redraw(); - return; - } - - let result = self.loop_handle.insert_source(Timer::from_duration(duration), |_, _, e| { - e.request_redraw(); - TimeoutAction::Drop - }); - - if let Err(e) = result { - warn!("{}", e); - self.request_redraw(); - } + self.present_state.request_present_notify_after(duration, &self.loop_handle); } pub fn waker(&self) -> WindowWaker { @@ -227,66 +106,12 @@ impl WindowInner { self.is_focused.get() } - pub fn focus(&self) -> Result<()> { - self.connection - .conn - .set_input_focus(InputFocus::POINTER_ROOT, self.xcb_window.id(), CURRENT_TIME)? - .check()?; - - Ok(()) + pub fn focus(&self) -> PlatformResult<()> { + self.xcb_window.focus() } - pub fn resize(&self, size: Size) -> Result<()> { - let new_size = self.sizing_strategy.adjust_size(size, self.size()).physical; - - if new_size == self.window_size.get().cast() { - return Ok(()); - } - - self.xcb_window.resize(new_size)?.check()?; - - if !self.sizing_strategy.is_resizable() { - let size_hints = get_size_hints(&self.sizing_strategy, new_size, self.scale_factor()); - self.xcb_window.set_size_hints(size_hints)?.check()?; - } - - // This will trigger a `ConfigureNotify` event which will in turn change `self.window_info` - // and notify the window handler about it - - Ok(()) - } - - #[inline] - pub fn create_timer(&self, duration: Duration) -> Result { - insert_timer(&self.loop_handle, duration) - } - - pub fn resize_immediately( - &self, new_size: PhysicalSize, handler: &dyn WindowHandler, - ) -> Result<()> { - let previous = self.store_size(new_size); - - if previous == new_size { - return Ok(()); - }; - - if let Err(e) = - handler.resized(WindowSize::from_physical(new_size.cast(), self.scaling_factor.get())) - { - warn!("Window Handler failed to resize: {}. Reverting to previous size", &e); - self.store_size(previous); - return Err(e.into()); - } - - self.xcb_window.resize(new_size.cast())?.check()?; // Will not call handler, as size is the same as above. - if !self.sizing_strategy.is_resizable() { - let size_hints = get_size_hints(&self.sizing_strategy, new_size, self.scale_factor()); - self.xcb_window.set_size_hints(size_hints)?.check()?; - } - - // These come from the Host, no need to notify it about the new size - - Ok(()) + pub fn resize(&self, new_size: Size) -> PlatformResult<()> { + self.sizing_state.resize_from_handler(new_size, &self.xcb_window) } pub fn window_handle(&self) -> Option> { @@ -299,8 +124,8 @@ impl WindowInner { self.connection.conn.xlib_display_handle() } - pub fn platform_handle(&self) -> PlatformHandle { - PlatformHandle { + pub fn platform_handle(&self) -> super::PlatformHandle { + super::PlatformHandle { connection: Arc::clone(&self.connection.conn), window_id: self.xcb_window.id(), visual_id: self.visual_id, @@ -308,16 +133,21 @@ impl WindowInner { } #[cfg(feature = "opengl")] - pub fn gl_context(&self) -> Option { - Some(crate::gl::GlContext::new(Rc::clone(self.gl_context.as_ref()?))) + pub fn gl_context(&self) -> Option { + Some(GlContext::new(Rc::clone(self.gl_context.as_ref()?))) + } + + #[inline] + pub fn create_timer(&self, duration: Duration) -> PlatformResult { + insert_timer(&self.loop_handle, duration) } pub fn scale_factor(&self) -> f64 { - self.scaling_factor.get() + self.sizing_state.scale_factor() } pub fn size(&self) -> WindowSize { - WindowSize::from_physical(self.window_size.get().cast(), self.scaling_factor.get()) + self.sizing_state.window_size() } pub fn raw_id(&self) -> xproto::Window { diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs index bb805025..a5e6e545 100644 --- a/src/platform/x11/window_thread.rs +++ b/src/platform/x11/window_thread.rs @@ -1,9 +1,11 @@ use super::*; -use crate::dpi::{PhysicalSize, Size}; +use crate::dpi::Size; use crate::handler::WindowHandlerBuilder; use crate::host::HostCallbacks; use crate::platform::x11::event_loop::{EventLoop, MainThreadCaller}; -use crate::platform::x11::window_shared::WindowInner; +use crate::platform::x11::present::PresentThreadShared; +use crate::platform::x11::sizing::SizingThreadShared; +use crate::platform::x11::window_shared::WindowShared; use crate::utils::SizingStrategy; use crate::warn; use crate::window::WindowInitializer; @@ -12,22 +14,20 @@ use calloop::LoopSignal; use std::cell::{Cell, RefCell}; use std::panic::resume_unwind; use std::rc::Rc; -use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}; -use std::sync::{mpsc, Mutex, OnceLock}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{mpsc, Mutex}; use std::thread; use std::thread::JoinHandle; use std::time::{Duration, Instant}; -pub(crate) struct WindowThreadShared { +pub struct WindowThreadShared { stopped: AtomicBool, - scaling_factor: AtomicU64, - size: AtomicU32, + + pub sizing: SizingThreadShared, + pub present: PresentThreadShared, + final_error: Mutex>, stopped_requested_from_host: AtomicBool, - poll_requested: AtomicBool, - sizing_strategy: OnceLock, - - redraw_requested_after: Mutex>, } pub enum RedrawRequested { @@ -63,69 +63,19 @@ impl WindowThreadShared { Self { stopped: false.into(), final_error: None.into(), - size: 0.into(), - scaling_factor: 0.into(), stopped_requested_from_host: false.into(), - sizing_strategy: OnceLock::new(), - redraw_requested_after: None.into(), - poll_requested: false.into(), + present: PresentThreadShared::new(), + sizing: SizingThreadShared::new(), } } - fn init(&self, window: &WindowInner) { - self.set_size(window.get_size()); - self.set_scaling_factor(window.scale_factor()); - let Ok(()) = self.sizing_strategy.set(window.sizing_strategy) else { unreachable!() }; - } - - pub fn get_size(&self) -> PhysicalSize { - let bytes = self.size.load(Ordering::Relaxed); - let low = (bytes & u16::MAX as u32) as u16; - let high = (bytes >> 16) as u16; - - PhysicalSize::new(low, high) - } - - pub fn set_size(&self, size: PhysicalSize) { - let bytes = ((size.height as u32) << 16) | (size.width as u32); - self.size.store(bytes, Ordering::Relaxed); - } - - pub fn sizing_strategy(&self) -> SizingStrategy { - self.sizing_strategy.get().copied().unwrap_or_default() - } - - pub fn get_scaling_factor(&self) -> f64 { - f64::from_be_bytes(self.scaling_factor.load(Ordering::Relaxed).to_ne_bytes()) - } - - pub fn set_scaling_factor(&self, scale_factor: f64) { - self.scaling_factor - .store(u64::from_be_bytes(scale_factor.to_ne_bytes()), Ordering::Relaxed); + fn init(&self, window: &WindowShared) { + self.sizing.init(&window.sizing_state) } pub fn is_stop_host_requested(&self) -> bool { self.stopped_requested_from_host.load(Ordering::Relaxed) } - - pub fn request_redraw_after(&self, duration: Duration) { - // Ignore a poisoned mutex, we just fully override this value anyway. - let mut guard = self.redraw_requested_after.lock().unwrap_or_else(|g| g.into_inner()); - *guard = Some(RedrawRequested::from_duration(duration)); - } - - pub fn take_redraw_request(&self) -> Option { - let mut guard = self.redraw_requested_after.lock().unwrap_or_else(|g| g.into_inner()); - guard.take().map(|w| w.to_duration()) - } - - pub fn request_poll(&self) { - self.poll_requested.store(true, Ordering::Relaxed); - } - - pub fn take_poll_request(&self) -> bool { - self.poll_requested.swap(false, Ordering::Relaxed) - } } struct ThreadStopWatcher(Arc); @@ -163,7 +113,7 @@ pub struct WindowThreadHandle { } impl WindowThreadHandle { - pub fn create_window(init: WindowInitializer) -> Result { + pub fn create_window(init: WindowInitializer) -> PlatformResult { let (tx, rx) = result_channel(); let shared = Arc::new(WindowThreadShared::new()); let (request_sender, request_receiver) = calloop::channel::sync_channel(1); @@ -211,21 +161,18 @@ impl WindowThreadHandle { } pub fn size(&self) -> WindowSize { - let scale_factor = self.shared.get_scaling_factor(); - let size = self.shared.get_size(); - - WindowSize::from_physical(size.cast(), scale_factor) + self.shared.sizing.window_size() } - pub fn resize(&self, size: Size) -> Result<()> { + pub fn resize(&self, size: Size) -> PlatformResult<()> { self.request(WindowThreadRequest::Resize(size)) } - pub fn suggest_scale_factor(&self, scale_factor: f64) -> Result<()> { + pub fn suggest_scale_factor(&self, scale_factor: f64) -> PlatformResult<()> { self.request(WindowThreadRequest::SuggestScaleFactor(scale_factor)) } - fn request(&self, req: WindowThreadRequest) -> Result<()> { + fn request(&self, req: WindowThreadRequest) -> PlatformResult<()> { self.request_sender.send(req).map_err(|_| RequestFailed::Send)?; let result = self.response_receiver.recv().map_err(|_| RequestFailed::Recv)?; @@ -233,10 +180,10 @@ impl WindowThreadHandle { } pub fn sizing_strategy(&self) -> SizingStrategy { - self.shared.sizing_strategy.get().copied().unwrap_or_default() + self.shared.sizing.sizing_strategy() } - pub fn run_until_closed(&self) -> Result<()> { + pub fn run_until_closed(&self) -> PlatformResult<()> { if !self.shared.stopped.load(Ordering::Relaxed) { self.request(WindowThreadRequest::Show)?; } @@ -255,11 +202,11 @@ impl WindowThreadHandle { Ok(()) } - pub fn show(&self) -> Result<()> { + pub fn show(&self) -> PlatformResult<()> { self.request(WindowThreadRequest::Show) } - pub fn hide(&self) -> Result<()> { + pub fn hide(&self) -> PlatformResult<()> { self.request(WindowThreadRequest::Hide) } @@ -268,15 +215,15 @@ impl WindowThreadHandle { } pub fn is_resizable(&self) -> bool { - self.shared.sizing_strategy().is_resizable() + self.sizing_strategy().is_resizable() } pub fn min_size(&self) -> Option { - self.shared.sizing_strategy().min_size() + self.sizing_strategy().min_size() } pub fn max_size(&self) -> Option { - self.shared.sizing_strategy().max_size() + self.sizing_strategy().max_size() } pub fn handle_main_thread_callback(&self) { @@ -288,12 +235,12 @@ impl WindowThreadHandle { } } - pub fn set_parent(&self, new_parent: ParentWindowHandle) -> Result<()> { + pub fn set_parent(&self, new_parent: ParentWindowHandle) -> PlatformResult<()> { self.request(WindowThreadRequest::SetParent(new_parent)) } - pub fn request_poll(&self) -> Result<()> { - self.shared.request_poll(); + pub fn request_poll(&self) -> PlatformResult<()> { + self.shared.present.request_poll(); self.loop_signal.wakeup(); Ok(()) @@ -371,15 +318,23 @@ impl WindowThread { receiver: calloop::channel::Channel, sender: mpsc::Sender, main_thread_caller: Option, - ) -> Result { + ) -> PlatformResult { let mut ev_loop = calloop::EventLoop::try_new()?; - let inner = WindowInner::create(options, &ev_loop, Arc::clone(&shared))?; + let parent_id = options.parent.as_ref().map(|p| p.inner.window_id); + let inner = WindowShared::create(options, &ev_loop, Arc::clone(&shared))?; shared.init(&inner); let handler = handler.build(WindowContext::new(Rc::clone(&inner)))?; - let event_loop = - EventLoop::new(inner, handler, receiver, sender, main_thread_caller, &mut ev_loop)?; + let event_loop = EventLoop::new( + inner, + handler, + parent_id, + receiver, + sender, + main_thread_caller, + &mut ev_loop, + )?; Ok(Self { event_loop, ev_loop, shared }) } @@ -422,7 +377,7 @@ impl WindowResultSender { struct WindowResultReceiver(mpsc::Receiver); impl WindowResultReceiver { - pub fn receive(self) -> Result { + pub fn receive(self) -> PlatformResult { let result = self.0.recv().map_err(|_| PlatformError::MainThreadRecvResult)?; match result { diff --git a/src/platform/x11/x11_connection.rs b/src/platform/x11/x11_connection.rs new file mode 100644 index 00000000..27502d57 --- /dev/null +++ b/src/platform/x11/x11_connection.rs @@ -0,0 +1,94 @@ +use super::prelude::*; +use crate::wrappers::xlib::XlibXcbConnection; +use x11rb::connection::RequestConnection; +use x11rb::cursor::Handle as CursorHandle; +use x11rb::protocol::present; +use x11rb::protocol::xproto::{self, Atom, Screen}; +use x11rb::resource_manager; +use x11rb::xcb_ffi::XCBConnection; + +mod get_property; +pub use get_property::GetPropertyError; + +x11rb::atom_manager! { + pub Atoms: AtomsCookie { + WM_PROTOCOLS, + WM_DELETE_WINDOW, + + // Drag-N-Drop Atoms + XdndAware, + XdndEnter, + XdndLeave, + XdndDrop, + XdndPosition, + XdndStatus, + XdndSelection, + XdndFinished, + XdndActionPrivate, + XdndActionCopy, + XdndActionMove, + XdndActionLink, + XdndActionAsk, + XdndTypeList, + TextUriList: b"text/uri-list", + None: b"None", + } +} + +/// A very light abstraction around the XCB connection. +/// +/// Keeps track of the xcb connection itself and the xlib display ID that was used to connect. +pub struct X11Connection { + pub(crate) conn: Arc, + pub(crate) atoms: Atoms, + pub(crate) resources: ConnectionResources, + + pub(crate) present_supported: bool, +} + +impl X11Connection { + pub fn connect() -> PlatformResult { + let conn = XlibXcbConnection::open()?; + let atoms = Atoms::new(&*conn)?.reply()?; + + Ok(Self { + atoms, + present_supported: conn.extension_information(present::X11_EXTENSION_NAME)?.is_some(), + resources: ConnectionResources::load(&conn)?, + + conn: Arc::new(conn), + }) + } + + pub fn default_screen(&self) -> &Screen { + self.conn.default_screen() + } + + pub fn get_property( + &self, window: xproto::Window, property: Atom, property_type: Atom, + ) -> Result, GetPropertyError> { + get_property::get_property(window, property, property_type, &self.conn) + } +} + +pub struct ConnectionResources { + pub cursor_handle: CursorHandle, + pub xft_dpi: Option, +} + +impl ConnectionResources { + fn load(conn: &XlibXcbConnection) -> PlatformResult { + let resources = resource_manager::new_from_default(conn as &XCBConnection)?; + + Ok(Self { + xft_dpi: resources.get_value::("Xft.dpi", "").ok().flatten(), + + cursor_handle: CursorHandle::new( + conn as &XCBConnection, + conn.default_screen_index().into(), + &resources, + )? + .reply()?, + }) + } +} diff --git a/src/platform/x11/xcb_connection/get_property.rs b/src/platform/x11/x11_connection/get_property.rs similarity index 100% rename from src/platform/x11/xcb_connection/get_property.rs rename to src/platform/x11/x11_connection/get_property.rs diff --git a/src/platform/x11/xcb_connection.rs b/src/platform/x11/xcb_connection.rs deleted file mode 100644 index 920661db..00000000 --- a/src/platform/x11/xcb_connection.rs +++ /dev/null @@ -1,128 +0,0 @@ -use super::cursor; -use crate::platform::*; -use crate::wrappers::xlib::XlibXcbConnection; -use crate::MouseCursor; -use std::cell::RefCell; -use std::collections::hash_map::{Entry, HashMap}; -use std::sync::Arc; -use x11rb::connection::RequestConnection; -use x11rb::cookie::VoidCookie; -use x11rb::cursor::Handle as CursorHandle; -use x11rb::errors::ConnectionError; -use x11rb::protocol::present; -use x11rb::protocol::xproto::{ - self, ChangeWindowAttributesAux, ConnectionExt, Cursor, EventMask, Screen, -}; -use x11rb::resource_manager; -use x11rb::xcb_ffi::XCBConnection; - -mod get_property; -pub use get_property::GetPropertyError; -mod size_hints; -pub use size_hints::get_size_hints; - -x11rb::atom_manager! { - pub Atoms: AtomsCookie { - WM_PROTOCOLS, - WM_DELETE_WINDOW, - - // Drag-N-Drop Atoms - XdndAware, - XdndEnter, - XdndLeave, - XdndDrop, - XdndPosition, - XdndStatus, - XdndSelection, - XdndFinished, - XdndActionPrivate, - XdndActionCopy, - XdndActionMove, - XdndActionLink, - XdndActionAsk, - XdndTypeList, - TextUriList: b"text/uri-list", - None: b"None", - } -} - -/// A very light abstraction around the XCB connection. -/// -/// Keeps track of the xcb connection itself and the xlib display ID that was used to connect. -pub struct X11Connection { - pub(crate) conn: Arc, - pub(crate) atoms: Atoms, - pub(crate) resources: resource_manager::Database, - pub(crate) cursor_handle: CursorHandle, - pub(crate) cursor_cache: RefCell>, - - pub(crate) present_supported: bool, -} - -impl X11Connection { - pub fn new() -> Result { - let conn = XlibXcbConnection::open()?; - let screen = conn.default_screen_index(); - let xcb_conn = conn.xcb_connection(); - - let atoms = Atoms::new(xcb_conn)?.reply()?; - let resources = resource_manager::new_from_default(xcb_conn)?; - let cursor_handle = CursorHandle::new(xcb_conn, screen.into(), &resources)?.reply()?; - - let present_supported = conn.extension_information(present::X11_EXTENSION_NAME)?.is_some(); - - Ok(Self { - conn: Arc::new(conn), - atoms, - resources, - cursor_handle, - cursor_cache: RefCell::new(HashMap::new()), - present_supported, - }) - } - - pub fn get_scaling(&self) -> Option { - if let Ok(Some(dpi)) = self.resources.get_value::("Xft.dpi", "") { - Some(dpi as f64 / 96.0) - } else { - None - } - } - - #[inline] - pub fn get_cursor(&self, cursor: MouseCursor) -> Result { - // PANIC: this function is the only point where we access the cache, and we never call - // external functions that may make a reentrant call to this function - let mut cursor_cache = self.cursor_cache.borrow_mut(); - - match cursor_cache.entry(cursor) { - Entry::Occupied(entry) => Ok(*entry.get()), - Entry::Vacant(entry) => { - let cursor = cursor::get_xcursor(&self.conn, &self.cursor_handle, cursor)?; - entry.insert(cursor); - Ok(cursor) - } - } - } - - pub fn default_screen(&self) -> &Screen { - self.conn.default_screen() - } - - pub fn get_property( - &self, window: xproto::Window, property: xproto::Atom, property_type: xproto::Atom, - ) -> core::result::Result, GetPropertyError> { - get_property::get_property(window, property, property_type, &self.conn) - } - - pub fn register_tree_structure_events( - &self, - ) -> core::result::Result, ConnectionError> { - let root = self.default_screen().root; - - self.conn.change_window_attributes( - root, - &ChangeWindowAttributesAux::new().event_mask(EventMask::SUBSTRUCTURE_NOTIFY), - ) - } -} diff --git a/src/platform/x11/xcb_connection/size_hints.rs b/src/platform/x11/xcb_connection/size_hints.rs deleted file mode 100644 index eb11cde4..00000000 --- a/src/platform/x11/xcb_connection/size_hints.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::utils::SizingStrategy; -use dpi::{PhysicalSize, Pixel}; -use x11rb::properties::WmSizeHints; - -pub fn get_size_hints( - strategy: &SizingStrategy, current_size: PhysicalSize, scale_factor: f64, -) -> WmSizeHints { - let mut size_hints = WmSizeHints::default(); - - match strategy { - SizingStrategy::Fixed => { - size_hints.min_size = Some(to_size_hint(current_size)); - size_hints.max_size = size_hints.min_size; - } - SizingStrategy::Resizable { min_size, max_size } => { - size_hints.min_size = - min_size.map(|s| to_size_hint(s.to_physical::(scale_factor))); - size_hints.max_size = - max_size.map(|s| to_size_hint(s.to_physical::(scale_factor))); - } - } - - size_hints -} - -fn to_size_hint(size: PhysicalSize) -> (i32, i32) { - let size = size.cast(); - (size.width, size.height) -} diff --git a/src/platform/x11/xcb_window.rs b/src/platform/x11/xcb_window.rs index e301e8b3..d3501b9d 100644 --- a/src/platform/x11/xcb_window.rs +++ b/src/platform/x11/xcb_window.rs @@ -1,21 +1,16 @@ -use crate::dpi::PhysicalSize; -use crate::platform::x11::error::CookieExt; +use super::prelude::*; use crate::platform::x11::visual_info::WindowVisualConfig; -use crate::platform::X11Connection; use std::num::{NonZero, NonZeroU32}; -use std::rc::Rc; -use x11rb::connection::Connection; use x11rb::cookie::VoidCookie; -use x11rb::errors::{ConnectionError, ReplyOrIdError}; use x11rb::properties::WmSizeHints; use x11rb::protocol::present; -use x11rb::protocol::present::ConnectionExt; +use x11rb::protocol::present::ConnectionExt as _; use x11rb::protocol::xproto::{ - AtomEnum, ConfigureWindowAux, ConnectionExt as _, CreateWindowAux, EventMask, PropMode, - WindowClass, + AtomEnum, ConfigureWindowAux, ConnectionExt as _, CreateWindowAux, EventMask, InputFocus, + PropMode, WindowClass, }; use x11rb::wrapper::ConnectionExt as _; -use x11rb::xcb_ffi::XCBConnection; +use x11rb::CURRENT_TIME; pub struct XcbWindow { connection: Rc, @@ -149,6 +144,15 @@ impl XcbWindow { ) } + pub fn focus(&self) -> PlatformResult<()> { + self.connection + .conn + .set_input_focus(InputFocus::POINTER_ROOT, self.id(), CURRENT_TIME)? + .check()?; + + Ok(()) + } + pub fn set_size_hints( &self, size_hints: WmSizeHints, ) -> Result, ConnectionError> { @@ -169,6 +173,10 @@ impl XcbWindow { pub fn id(&self) -> NonZeroU32 { self.window_id } + + pub fn connection(&self) -> &X11Connection { + &self.connection + } } impl Drop for XcbWindow { diff --git a/src/wrappers/egl.rs b/src/wrappers/egl.rs index df8717b9..6c82e330 100644 --- a/src/wrappers/egl.rs +++ b/src/wrappers/egl.rs @@ -1,4 +1,4 @@ -use crate::platform::gl::CreationFailedError; +use crate::platform::gl::GlCreationFailedError; use libloading::Library; use std::ffi::{c_void, CStr}; use std::rc::Rc; @@ -34,7 +34,7 @@ pub struct Egl { } impl Egl { - pub fn open() -> Result { + pub fn open() -> Result { let library = unsafe { Library::new("libEGL.so.1").or_else(|_| Library::new("libEGL.so")) }?; diff --git a/src/wrappers/egl/config.rs b/src/wrappers/egl/config.rs index 483092e8..42f511c0 100644 --- a/src/wrappers/egl/config.rs +++ b/src/wrappers/egl/config.rs @@ -57,10 +57,10 @@ impl EglConfig { Ok(value) } - pub fn get_visual_id(&self, display: &EglDisplay) -> Result { + pub fn get_visual_id(&self, display: &EglDisplay) -> Result { let value = self.get_attrib(display, EGL_NATIVE_VISUAL_ID)?; let value: Visualid = - value.try_into().map_err(|e| CreationFailedError::EglInvalidVisualId(value, e))?; + value.try_into().map_err(|e| GlCreationFailedError::EglInvalidVisualId(value, e))?; Ok(value) } } diff --git a/src/wrappers/egl/display.rs b/src/wrappers/egl/display.rs index 4185711f..d0fae957 100644 --- a/src/wrappers/egl/display.rs +++ b/src/wrappers/egl/display.rs @@ -1,11 +1,5 @@ -use crate::gl::GlConfig; -use crate::platform::gl::CreationFailedError; -use crate::platform::X11Connection; -use crate::wrappers::egl::bound_api::BoundApi; -use crate::wrappers::egl::config::EglConfig; -use crate::wrappers::egl::context::EglContext; -use crate::wrappers::egl::surface::EglSurface; -use crate::wrappers::egl::{sys, Egl, EglError}; +use super::*; +use crate::platform::prelude::*; use crate::wrappers::xlib::XlibConnection; use std::ffi::c_void; use std::ptr::NonNull; @@ -67,10 +61,10 @@ pub struct EglVersion { impl Egl { pub fn create_display( &self, connection: &Rc, - ) -> Result { + ) -> Result { let display = self .create_display_basic(connection.conn.xlib_connection()) - .ok_or(CreationFailedError::EglNoDisplay)?; + .ok_or(GlCreationFailedError::EglNoDisplay)?; let egl = self.clone(); @@ -80,7 +74,7 @@ impl Egl { let inner = EglDisplayInner { egl, raw: display, _connection: Rc::clone(connection) }; if version.major != 1 || version.minor < 5 { - return Err(CreationFailedError::EglUnsupportedVersion(version)); + return Err(GlCreationFailedError::EglUnsupportedVersion(version)); } Ok(EglDisplay { inner: Rc::new(inner) }) diff --git a/src/wrappers/egl/sys.rs b/src/wrappers/egl/sys.rs index bd5435fd..a2fd96d2 100644 --- a/src/wrappers/egl/sys.rs +++ b/src/wrappers/egl/sys.rs @@ -1,6 +1,6 @@ #![allow(non_snake_case, non_camel_case_types, reason = "To match EGL function naming")] -use crate::platform::gl::CreationFailedError; +use crate::platform::gl::GlCreationFailedError; use libloading::Library; use std::ffi::*; use std::fmt::{Display, Formatter}; @@ -106,13 +106,13 @@ impl Display for MissingSymbolError { } } -impl From for CreationFailedError { +impl From for GlCreationFailedError { fn from(value: MissingSymbolError) -> Self { Self::EGLMissingSymbol(value) } } -impl From for CreationFailedError { +impl From for GlCreationFailedError { fn from(value: libloading::Error) -> Self { Self::EGLLoadError(value) } @@ -139,7 +139,7 @@ pub struct Functions { } impl Functions { - pub unsafe fn load_from(library: &Library) -> Result { + pub unsafe fn load_from(library: &Library) -> Result { Ok(Self { eglGetError: Self::get(library, c"eglGetError")?, eglBindAPI: Self::get(library, c"eglBindAPI")?, @@ -163,7 +163,7 @@ impl Functions { unsafe fn get( library: &Library, name: &'static CStr, - ) -> Result { + ) -> Result { let symbol = library.get::>(name.to_bytes_with_nul())?; let symbol = symbol.lift_option().ok_or(MissingSymbolError { name })?; Ok(*symbol) diff --git a/src/wrappers/glx.rs b/src/wrappers/glx.rs index 60f3ed88..7f71c258 100644 --- a/src/wrappers/glx.rs +++ b/src/wrappers/glx.rs @@ -1,12 +1,10 @@ use super::xlib::*; use crate::gl::{GlConfig, Profile}; -use crate::platform::gl::CreationFailedError; -use crate::platform::*; +use crate::platform::prelude::*; use std::ffi::{c_ulong, c_void, CStr}; use std::os::raw::c_int; use std::ptr::NonNull; -use std::rc::Rc; use x11_dl::glx::{arb::*, *}; use x11_dl::xlib; use x11_dl::xlib::XVisualInfo; @@ -29,7 +27,7 @@ pub struct Glx { } impl Glx { - pub fn open() -> Result { + pub fn open() -> PlatformResult { Ok(Self { inner: Rc::new(x11_dl::glx::Glx::open()?) }) } @@ -58,7 +56,7 @@ impl Glx { pub fn choose_best_fb_config( &self, connection: &XlibConnection, config: &GlConfig, error_handler: &XErrorHandler, - ) -> Result { + ) -> PlatformResult { let fb_attribs = Self::get_fb_attribs(config); let mut nelements = 0; @@ -75,7 +73,7 @@ impl Glx { error_handler.check()?; if nelements == 0 || result.is_null() { - return Err(CreationFailedError::NoValidFBConfig.into()); + return Err(GlCreationFailedError::NoValidFBConfig.into()); } // SAFETY: If nelements != 0, the result pointer is non-null, and no Xlib error occured, then @@ -91,14 +89,14 @@ impl Glx { pub fn get_visual_from_fb_config( &self, connection: &XlibConnection, fb_config: GlxFbConfig, error_handler: &XErrorHandler, - ) -> Result { + ) -> PlatformResult { // SAFETY: XlibConnection guarantees the inner dpy is valid. let result = unsafe { (self.inner.glXGetVisualFromFBConfig)(connection.as_raw(), fb_config.0) }; error_handler.check()?; if result.is_null() { - return Err(CreationFailedError::NoVisual.into()); + return Err(GlCreationFailedError::NoVisual.into()); } // SAFETY: If the result pointer is non-null, and no Xlib error occured, then @@ -113,7 +111,7 @@ impl Glx { pub fn swap_buffers( &self, connection: &XlibConnection, window_id: c_ulong, error_handler: &XErrorHandler, - ) -> Result<()> { + ) -> PlatformResult<()> { // SAFETY: XlibConnection guarantees the inner dpy is valid. unsafe { (self.inner.glXSwapBuffers)(connection.as_raw(), window_id) }; @@ -143,13 +141,13 @@ impl Glx { pub unsafe fn make_current( &self, connection: &XlibConnection, window_id: c_ulong, context: GLXContext, error_handler: &XErrorHandler, - ) -> Result<()> { + ) -> PlatformResult<()> { // SAFETY: XlibConnection guarantees the inner dpy is valid. let res = unsafe { (self.inner.glXMakeCurrent)(connection.as_raw(), window_id, context) }; error_handler.check()?; if res == 0 { - return Err(CreationFailedError::MakeCurrentFailed.into()); + return Err(GlCreationFailedError::MakeCurrentFailed.into()); } Ok(()) @@ -157,7 +155,7 @@ impl Glx { pub unsafe fn clear_current( &self, connection: &XlibConnection, error_handler: &XErrorHandler, - ) -> Result<()> { + ) -> PlatformResult<()> { self.make_current(connection, 0, core::ptr::null_mut(), error_handler) } } @@ -185,7 +183,7 @@ impl GlxCreateContextAttribsARB { pub fn call( &self, connection: &XlibConnection, gl_config: &GlConfig, glx_fb_config: GlxFbConfig, error_handler: &XErrorHandler, - ) -> Result { + ) -> PlatformResult { let ctx_attribs = Self::get_ctx_attribs(gl_config); let context = unsafe { @@ -201,7 +199,7 @@ impl GlxCreateContextAttribsARB { error_handler.check()?; if context.is_null() { - return Err(CreationFailedError::ContextCreationFailed.into()); + return Err(GlCreationFailedError::ContextCreationFailed.into()); } Ok(context) diff --git a/src/wrappers/xkbcommon.rs b/src/wrappers/xkbcommon.rs index 88ff5a63..f3d96926 100644 --- a/src/wrappers/xkbcommon.rs +++ b/src/wrappers/xkbcommon.rs @@ -1,3 +1,4 @@ +use crate::platform::prelude::*; use xkbcommon_dl as xkbc; pub(crate) type Keycode = xkbcommon_dl::xkb_keycode_t; @@ -8,7 +9,7 @@ pub struct XkbcommonState { } impl XkbcommonState { - pub fn new(xcb_connection: &crate::platform::X11Connection) -> Option { + pub fn new(xcb_connection: &X11Connection) -> Option { let xkb_common = xkbc::xkbcommon_option()?; let xkb_x11 = xkbc::x11::xkbcommon_x11_option()?; diff --git a/src/wrappers/xlib/xlib_connection.rs b/src/wrappers/xlib/xlib_connection.rs index afdffd0b..21e5f495 100644 --- a/src/wrappers/xlib/xlib_connection.rs +++ b/src/wrappers/xlib/xlib_connection.rs @@ -1,4 +1,4 @@ -use crate::platform::*; +use crate::platform::prelude::*; use std::error::Error; use std::ffi::CStr; use std::fmt::Formatter; @@ -25,7 +25,7 @@ unsafe impl Send for XlibConnection {} unsafe impl Sync for XlibConnection {} impl XlibConnection { - pub fn open() -> Result { + pub fn open() -> PlatformResult { let xlib = Box::new(Xlib::open()?); if unsafe { (xlib.XInitThreads)() } == 0 { diff --git a/src/wrappers/xlib/xlib_xcb.rs b/src/wrappers/xlib/xlib_xcb.rs index 3fb374e8..0db1a16b 100644 --- a/src/wrappers/xlib/xlib_xcb.rs +++ b/src/wrappers/xlib/xlib_xcb.rs @@ -1,12 +1,10 @@ -use crate::platform::*; -use crate::wrappers::xlib::xlib_connection::XlibConnection; -use crate::wrappers::xlib::ScreenIndex; +use super::*; +use crate::platform::prelude::*; use raw_window_handle::{DisplayHandle, XcbDisplayHandle, XlibDisplayHandle}; use std::ops::Deref; use std::os::fd::{AsFd, BorrowedFd}; use std::ptr::NonNull; use x11_dl::xlib_xcb::Xlib_xcb; -use x11rb::connection::Connection; use x11rb::protocol::xproto::Screen; use x11rb::xcb_ffi::XCBConnection; @@ -26,7 +24,7 @@ pub struct XlibXcbConnection { } impl XlibXcbConnection { - pub fn open() -> Result { + pub fn open() -> PlatformResult { let xlib_xcb = Xlib_xcb::open()?; // Open the connection to the X11 server as a Xlib/XCB connection object let xlib_connection = XlibConnection::open()?; @@ -43,7 +41,7 @@ impl XlibXcbConnection { assert!(!xcb_connection.is_null()); // Wrap the XCB connection object in a x11rb connection object - // SAFETY: The xcb_connection pointer should be valid. We also enforce the drop order in this + // SAFETY: The x11_connection pointer should be valid. We also enforce the drop order in this let xcb_connection = unsafe { XCBConnection::from_raw_xcb_connection(xcb_connection, false)? };