Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

X11: split off Visual Info negotiation into a separate module #177

Merged
merged 3 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
X11: split off Visual Info negotiation into a separate module
  • Loading branch information
prokopyl committed Mar 27, 2024
commit 06070752ae0475cb4d64f6987e6e761c1878ed49
1 change: 1 addition & 0 deletions src/x11/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ pub use window::*;

mod cursor;
mod keyboard;
mod visual_info;
98 changes: 98 additions & 0 deletions src/x11/visual_info.rs
Original file line number Diff line number Diff line change
@@ -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<crate::gl::x11::FbConfig>,

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<crate::gl::GlConfig>,
) -> 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(
micahrj marked this conversation as resolved.
Show resolved Hide resolved
&self, connection: &XcbConnection,
) -> Result<Option<Colormap>, Box<dyn Error>> {
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<Visualid> {
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
}
71 changes: 16 additions & 55 deletions src/x11/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 _;
Expand All @@ -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<RawWindowHandle>,
Expand Down Expand Up @@ -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.conn.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.conn.generate_id()?;
xcb_connection.conn.create_gc(
Expand All @@ -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.conn.generate_id()?;
xcb_connection.conn.create_colormap(ColormapAlloc::NONE, colormap, screen.root, visual)?;
let color_map = visual_info.create_color_map(&xcb_connection)?;

let window_id = xcb_connection.conn.generate_id()?;
xcb_connection.conn.create_window(
depth,
visual_info.visual_depth,
window_id,
parent_id,
0, // x coordinate of the new window
Expand All @@ -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
Expand All @@ -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.conn.map_window(window_id)?;
Expand Down Expand Up @@ -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;
Expand All @@ -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),
Expand Down Expand Up @@ -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<Visualid> {
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 {
Expand Down
9 changes: 6 additions & 3 deletions src/x11/xcb_connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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.conn.setup();
let screen = &setup.roots[self.screen];
let screen = self.screen();

// Get the DPI from the screen struct
//
Expand Down Expand Up @@ -115,4 +114,8 @@ impl XcbConnection {
}
}
}

pub fn screen(&self) -> &Screen {
&self.conn2.setup().roots[self.screen]
}
}