From 4fd042c03675136163ce6fee4fe2a9bb3b97a1c2 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz Date: Tue, 26 Mar 2024 01:11:05 +0100 Subject: [PATCH] X11: split off Visual Info negotiation into a separate module --- src/x11/mod.rs | 1 + src/x11/visual_info.rs | 98 +++++++++++++++++++++++++++++++++++++++ src/x11/window.rs | 71 +++++++--------------------- src/x11/xcb_connection.rs | 9 ++-- 4 files changed, 121 insertions(+), 58 deletions(-) create mode 100644 src/x11/visual_info.rs diff --git a/src/x11/mod.rs b/src/x11/mod.rs index 57284203..71faf045 100644 --- a/src/x11/mod.rs +++ b/src/x11/mod.rs @@ -6,3 +6,4 @@ pub use window::*; mod cursor; mod keyboard; +mod visual_info; diff --git a/src/x11/visual_info.rs b/src/x11/visual_info.rs new file mode 100644 index 00000000..de94ee5f --- /dev/null +++ b/src/x11/visual_info.rs @@ -0,0 +1,98 @@ +use crate::x11::xcb_connection::XcbConnection; +use std::error::Error; +use x11rb::connection::Connection; +use x11rb::protocol::xproto::{ + Colormap, ColormapAlloc, ConnectionExt, Screen, VisualClass, Visualid, +}; +use x11rb::COPY_FROM_PARENT; + +pub(super) struct WindowVisualConfig { + #[cfg(feature = "opengl")] + pub fb_config: Option, + + pub visual_depth: u8, + pub visual_id: Visualid, + pub is_copy_from_parent: bool, +} + +// 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: &XcbConnection, gl_config: Option, + ) -> Self { + let Some(gl_config) = gl_config else { return Self::find_best_visual_config(connection) }; + + // SAFETY: TODO + let (fb_config, window_config) = unsafe { + crate::gl::platform::GlContext::get_fb_config_and_visual(connection.dpy, gl_config) + } + .expect("Could not fetch framebuffer config"); + + Self { + fb_config: Some(fb_config), + visual_depth: window_config.depth, + visual_id: window_config.visual, + is_copy_from_parent: false, + } + } + + pub fn find_best_visual_config(connection: &XcbConnection) -> Self { + match find_visual_for_depth(connection.screen(), 32) { + None => Self::copy_from_parent(), + Some(visual_id) => Self { + #[cfg(feature = "opengl")] + fb_config: None, + visual_id, + visual_depth: 32, + is_copy_from_parent: false, + }, + } + } + + const fn copy_from_parent() -> Self { + Self { + #[cfg(feature = "opengl")] + fb_config: None, + visual_depth: COPY_FROM_PARENT as u8, + visual_id: COPY_FROM_PARENT, + is_copy_from_parent: true, + } + } + + // 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 + pub fn create_color_map( + &self, connection: &XcbConnection, + ) -> Result, Box> { + if self.is_copy_from_parent { + return Ok(None); + } + + let colormap = connection.conn2.generate_id()?; + connection.conn2.create_colormap( + ColormapAlloc::NONE, + colormap, + connection.screen().root, + self.visual_id, + )?; + + Ok(Some(colormap)) + } +} + +fn find_visual_for_depth(screen: &Screen, depth: u8) -> Option { + for candidate_depth in &screen.allowed_depths { + if candidate_depth.depth != depth { + continue; + } + + for candidate_visual in &candidate_depth.visuals { + if candidate_visual.class == VisualClass::TRUE_COLOR { + return Some(candidate_visual.visual_id); + } + } + } + + None +} diff --git a/src/x11/window.rs b/src/x11/window.rs index 6c5d5b6d..f0f75397 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -14,9 +14,8 @@ use raw_window_handle::{ use x11rb::connection::Connection; use x11rb::protocol::xproto::{ - AtomEnum, ChangeWindowAttributesAux, ColormapAlloc, ConfigureWindowAux, ConnectionExt as _, - CreateGCAux, CreateWindowAux, EventMask, PropMode, Screen, VisualClass, Visualid, - Window as XWindow, WindowClass, + AtomEnum, ChangeWindowAttributesAux, ConfigureWindowAux, ConnectionExt as _, CreateGCAux, + CreateWindowAux, EventMask, PropMode, Visualid, Window as XWindow, WindowClass, }; use x11rb::protocol::Event as XEvent; use x11rb::wrapper::ConnectionExt as _; @@ -31,6 +30,7 @@ use super::keyboard::{convert_key_press_event, convert_key_release_event, key_mo #[cfg(feature = "opengl")] use crate::gl::{platform, GlContext}; +use crate::x11::visual_info::WindowVisualConfig; pub struct WindowHandle { raw_window_handle: Option, @@ -187,11 +187,9 @@ impl<'a> Window<'a> { // FIXME: baseview error type instead of unwrap() let xcb_connection = XcbConnection::new()?; - // Get screen information (?) - let setup = xcb_connection.conn2.setup(); - let screen = &setup.roots[xcb_connection.screen]; - - let parent_id = parent.unwrap_or_else(|| screen.root); + // Get screen information + let screen = xcb_connection.screen(); + let parent_id = parent.unwrap_or(screen.root); let gc_id = xcb_connection.conn2.generate_id()?; xcb_connection.conn2.create_gc( @@ -207,39 +205,18 @@ impl<'a> Window<'a> { let window_info = WindowInfo::from_logical_size(options.size, scaling); - // Now it starts becoming fun. If we're creating an OpenGL context, then we need to create - // the window with a visual that matches the framebuffer used for the OpenGL context. So the - // idea is that we first retrieve a framebuffer config that matches our wanted OpenGL - // configuration, find the visual that matches that framebuffer config, create the window - // with that visual, and then finally create an OpenGL context for the window. If we don't - // use OpenGL, then we'll just take a random visual with a 32-bit depth. - let create_default_config = || { - Self::find_visual_for_depth(screen, 32) - .map(|visual| (32, visual)) - .unwrap_or((x11rb::COPY_FROM_PARENT as u8, x11rb::COPY_FROM_PARENT as u32)) - }; #[cfg(feature = "opengl")] - let (fb_config, (depth, visual)) = match options.gl_config { - Some(gl_config) => unsafe { - platform::GlContext::get_fb_config_and_visual(xcb_connection.dpy, gl_config) - .map(|(fb_config, window_config)| { - (Some(fb_config), (window_config.depth, window_config.visual)) - }) - .expect("Could not fetch framebuffer config") - }, - None => (None, create_default_config()), - }; + let visual_info = + WindowVisualConfig::find_best_visual_config_for_gl(&xcb_connection, options.gl_config); + #[cfg(not(feature = "opengl"))] - let (depth, visual) = create_default_config(); + let visual_info = WindowVisualConfig::find_best_visual_config(&xcb_connection); - // For this 32-bith depth to work, you also need to define a color map and set a border - // pixel: https://cgit.freedesktop.org/xorg/xserver/tree/dix/window.c#n818 - let colormap = xcb_connection.conn2.generate_id()?; - xcb_connection.conn2.create_colormap(ColormapAlloc::NONE, colormap, screen.root, visual)?; + let color_map = visual_info.create_color_map(&xcb_connection)?; let window_id = xcb_connection.conn2.generate_id()?; xcb_connection.conn2.create_window( - depth, + visual_info.visual_depth, window_id, parent_id, 0, // x coordinate of the new window @@ -248,7 +225,7 @@ impl<'a> Window<'a> { window_info.physical_size().height as u16, // window height 0, // window border WindowClass::INPUT_OUTPUT, - visual, + visual_info.visual_id, &CreateWindowAux::new() .event_mask( EventMask::EXPOSURE @@ -263,7 +240,7 @@ impl<'a> Window<'a> { ) // As mentioned above, these two values are needed to be able to create a window // with a depth of 32-bits when the parent window has a different depth - .colormap(colormap) + .colormap(color_map) .border_pixel(0), )?; xcb_connection.conn2.map_window(window_id)?; @@ -292,7 +269,7 @@ impl<'a> Window<'a> { // no error handling anymore at this point. Everything is more or less unchanged // compared to when raw-gl-context was a separate crate. #[cfg(feature = "opengl")] - let gl_context = fb_config.map(|fb_config| { + let gl_context = visual_info.fb_config.map(|fb_config| { use std::ffi::c_ulong; let window = window_id as c_ulong; @@ -308,7 +285,7 @@ impl<'a> Window<'a> { xcb_connection, window_id, window_info, - visual_id: visual, + visual_id: visual_info.visual_id, mouse_cursor: MouseCursor::default(), frame_interval: Duration::from_millis(15), @@ -387,22 +364,6 @@ impl<'a> Window<'a> { pub fn gl_context(&self) -> Option<&crate::gl::GlContext> { self.inner.gl_context.as_ref() } - - fn find_visual_for_depth(screen: &Screen, depth: u8) -> Option { - for candidate_depth in &screen.allowed_depths { - if candidate_depth.depth != depth { - continue; - } - - for candidate_visual in &candidate_depth.visuals { - if candidate_visual.class == VisualClass::TRUE_COLOR { - return Some(candidate_visual.visual_id); - } - } - } - - None - } } impl WindowInner { diff --git a/src/x11/xcb_connection.rs b/src/x11/xcb_connection.rs index 34b400ab..a6bf4fe6 100644 --- a/src/x11/xcb_connection.rs +++ b/src/x11/xcb_connection.rs @@ -5,7 +5,7 @@ use x11::{xlib, xlib::Display, xlib_xcb}; use x11rb::connection::Connection; use x11rb::cursor::Handle as CursorHandle; -use x11rb::protocol::xproto::Cursor; +use x11rb::protocol::xproto::{Cursor, Screen}; use x11rb::resource_manager; use x11rb::xcb_ffi::XCBConnection; @@ -76,8 +76,7 @@ impl XcbConnection { // If neither work, I guess just assume 96.0 and don't do any scaling. fn get_scaling_screen_dimensions(&self) -> f64 { // Figure out screen information - let setup = self.conn2.setup(); - let screen = &setup.roots[self.screen]; + let screen = self.screen(); // Get the DPI from the screen struct // @@ -115,4 +114,8 @@ impl XcbConnection { } } } + + pub fn screen(&self) -> &Screen { + &self.conn2.setup().roots[self.screen] + } }