OpenGL surface for X11/XCB
This commit is contained in:
parent
8d43a9e87e
commit
cc6d17169e
6
.github/workflows/rust.yml
vendored
6
.github/workflows/rust.yml
vendored
|
@ -11,12 +11,12 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: install XCB dependencies
|
- name: Install XCB and GL dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install libx11-xcb-dev libxcb-dri2-0-dev
|
sudo apt install libx11-xcb-dev libxcb-dri2-0-dev libgl1-mesa-dev
|
||||||
if: contains(matrix.os, 'ubuntu')
|
if: contains(matrix.os, 'ubuntu')
|
||||||
- name: install rust stable
|
- name: Install rust stable
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
|
|
|
@ -10,13 +10,14 @@ authors = [
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
gl = "0.14.0"
|
||||||
|
|
||||||
[target.'cfg(target_os="linux")'.dependencies]
|
[target.'cfg(target_os="linux")'.dependencies]
|
||||||
xcb = { version = "0.9", features = ["thread", "xlib_xcb", "dri2"] }
|
xcb = { version = "0.9", features = ["thread", "xlib_xcb", "dri2"] }
|
||||||
|
x11 = { version = "2.3", features = ["xlib", "glx"]}
|
||||||
|
|
||||||
[target.'cfg(target_os="windows")'.dependencies]
|
[target.'cfg(target_os="windows")'.dependencies]
|
||||||
winapi = { version = "0.3.8", features = ["libloaderapi", "winuser", "windef", "minwindef", "guiddef", "combaseapi", "wingdi"] }
|
winapi = { version = "0.3.8", features = ["libloaderapi", "winuser", "windef", "minwindef", "guiddef", "combaseapi", "wingdi"] }
|
||||||
gl = "0.14.0"
|
|
||||||
|
|
||||||
[target.'cfg(target_os="macos")'.dependencies]
|
[target.'cfg(target_os="macos")'.dependencies]
|
||||||
cocoa = "0.20.1"
|
cocoa = "0.20.1"
|
||||||
|
|
|
@ -9,8 +9,8 @@ A low-level windowing system geared towards making audio plugin UIs.
|
||||||
Below is a proposed list of milestones (roughly in-order) and their status. Subject to change at any time.
|
Below is a proposed list of milestones (roughly in-order) and their status. Subject to change at any time.
|
||||||
|
|
||||||
| Feature | Windows | Mac OS | Linux |
|
| Feature | Windows | Mac OS | Linux |
|
||||||
| -------------------------------------- | ------------------ | ------------------ | ----------------- |
|
| -------------------------------------- | ------------------ | ------------------ | ------------------ |
|
||||||
| Spawns a window, no parent | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark:|
|
| Spawns a window, no parent | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||||
| Cross-platform API for window spawning | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark:|
|
| Cross-platform API for window spawning | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||||
| Window uses an OpenGL surface | :heavy_check_mark: | | |
|
| Window uses an OpenGL surface | :heavy_check_mark: | | :heavy_check_mark: |
|
||||||
| Basic DPI scaling support | | | :question: |
|
| Basic DPI scaling support | | | :question: |
|
||||||
|
|
|
@ -8,7 +8,7 @@ pub use win::*;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
mod x11;
|
mod x11;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub use x11::*;
|
pub use crate::x11::*;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
mod macos;
|
mod macos;
|
||||||
|
|
|
@ -1,2 +1,7 @@
|
||||||
mod window;
|
mod window;
|
||||||
pub use window::*;
|
pub use window::*;
|
||||||
|
|
||||||
|
mod xcb_connection;
|
||||||
|
use xcb_connection::XcbConnection;
|
||||||
|
|
||||||
|
mod opengl_util;
|
||||||
|
|
66
src/x11/opengl_util.rs
Normal file
66
src/x11/opengl_util.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
use std::ffi::CString;
|
||||||
|
use std::os::raw::{c_int, c_void};
|
||||||
|
|
||||||
|
use ::x11::{glx, xlib};
|
||||||
|
|
||||||
|
use super::XcbConnection;
|
||||||
|
|
||||||
|
pub type GlXCreateContextAttribsARBProc = unsafe extern "C" fn(
|
||||||
|
dpy: *mut xlib::Display,
|
||||||
|
fbc: glx::GLXFBConfig,
|
||||||
|
share_context: glx::GLXContext,
|
||||||
|
direct: xlib::Bool,
|
||||||
|
attribs: *const c_int,
|
||||||
|
) -> glx::GLXContext;
|
||||||
|
|
||||||
|
// Check to make sure this system supports the correct version of GLX (>= 1.3 for now)
|
||||||
|
// For now it just panics if not, but TODO: do correct error handling
|
||||||
|
pub fn check_glx_version(xcb_connection: &XcbConnection) {
|
||||||
|
let raw_display = xcb_connection.conn.get_raw_dpy();
|
||||||
|
let mut maj: c_int = 0;
|
||||||
|
let mut min: c_int = 0;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
if glx::glXQueryVersion(raw_display, &mut maj as *mut c_int, &mut min as *mut c_int) == 0 {
|
||||||
|
panic!("Cannot get GLX version");
|
||||||
|
}
|
||||||
|
if (maj < 1) || (maj == 1 && min < 3) {
|
||||||
|
panic!("GLX version >= 1.3 required! (have {}.{})", maj, min);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get GLX framebuffer config
|
||||||
|
// History: https://stackoverflow.com/questions/51558473/whats-the-difference-between-a-glx-visual-and-a-fbconfig
|
||||||
|
pub fn get_glxfbconfig(xcb_connection: &XcbConnection, visual_attribs: &[i32]) -> glx::GLXFBConfig {
|
||||||
|
let raw_display = xcb_connection.conn.get_raw_dpy();
|
||||||
|
let xlib_display = xcb_connection.xlib_display;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut fbcount: c_int = 0;
|
||||||
|
let fbcs = glx::glXChooseFBConfig(
|
||||||
|
raw_display,
|
||||||
|
xlib_display,
|
||||||
|
visual_attribs.as_ptr(),
|
||||||
|
&mut fbcount as *mut c_int,
|
||||||
|
);
|
||||||
|
|
||||||
|
if fbcount == 0 {
|
||||||
|
panic!("Could not find compatible GLX FB config.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get more than one, any of the different configs work. Just choose the first one.
|
||||||
|
let fbc = *fbcs;
|
||||||
|
xlib::XFree(fbcs as *mut c_void);
|
||||||
|
fbc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn load_gl_func(name: &str) -> *mut c_void {
|
||||||
|
let cname = CString::new(name).unwrap();
|
||||||
|
let ptr: *mut c_void = std::mem::transmute(glx::glXGetProcAddress(cname.as_ptr() as *const u8));
|
||||||
|
if ptr.is_null() {
|
||||||
|
panic!("could not load {}", name);
|
||||||
|
}
|
||||||
|
ptr
|
||||||
|
}
|
|
@ -1,15 +1,24 @@
|
||||||
// TODO: messy for now, will refactor when I have more of an idea of the API/architecture
|
// TODO: messy for now, will refactor when I have more of an idea of the API/architecture
|
||||||
// TODO: actually handle events
|
|
||||||
// TODO: set window title
|
|
||||||
// TODO: close window
|
// TODO: close window
|
||||||
// TODO: proper error handling (no bare `unwrap`s)
|
// TODO: proper error handling (no bare `unwrap`s, no panics)
|
||||||
// TODO: refactor X connections (+setup, +screen) to a new struct
|
// TODO: move more OpenGL-related stuff into opengl_util.rs
|
||||||
|
// TODO: consider researching all unsafe calls here and figuring out what invariants need to be upheld.
|
||||||
|
// (write safe wrappers?)
|
||||||
|
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::os::raw::{c_int, c_void};
|
||||||
|
use std::ptr::null_mut;
|
||||||
|
|
||||||
|
use ::x11::{glx, xlib};
|
||||||
|
// use xcb::dri2; // needed later
|
||||||
|
|
||||||
|
use super::opengl_util;
|
||||||
|
use super::XcbConnection;
|
||||||
use crate::Parent;
|
use crate::Parent;
|
||||||
use crate::WindowOpenOptions;
|
use crate::WindowOpenOptions;
|
||||||
|
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
xcb_connection: xcb::Connection,
|
xcb_connection: XcbConnection,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
|
@ -17,21 +26,81 @@ impl Window {
|
||||||
// Convert the parent to a X11 window ID if we're given one
|
// Convert the parent to a X11 window ID if we're given one
|
||||||
let parent = match options.parent {
|
let parent = match options.parent {
|
||||||
Parent::None => None,
|
Parent::None => None,
|
||||||
Parent::AsIfParented => None, // ???
|
Parent::AsIfParented => None, // TODO: ???
|
||||||
Parent::WithParent(p) => Some(p as u32),
|
Parent::WithParent(p) => Some(p as u32),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Connect to the X server
|
// Connect to the X server
|
||||||
let (conn, screen_num) = xcb::Connection::connect_with_xlib_display().unwrap();
|
let xcb_connection = XcbConnection::new();
|
||||||
|
|
||||||
// Figure out screen information
|
// Check GLX version (>= 1.3 needed)
|
||||||
let setup = conn.get_setup();
|
opengl_util::check_glx_version(&xcb_connection);
|
||||||
let screen = setup.roots().nth(screen_num as usize).unwrap();
|
|
||||||
|
// Get GLX framebuffer config (requires GLX >= 1.3)
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let fb_config = opengl_util::get_glxfbconfig(
|
||||||
|
&xcb_connection,
|
||||||
|
&[
|
||||||
|
glx::GLX_X_RENDERABLE, 1,
|
||||||
|
glx::GLX_DRAWABLE_TYPE, glx::GLX_WINDOW_BIT,
|
||||||
|
glx::GLX_RENDER_TYPE, glx::GLX_RGBA_BIT,
|
||||||
|
glx::GLX_X_VISUAL_TYPE, glx::GLX_TRUE_COLOR,
|
||||||
|
glx::GLX_RED_SIZE, 8,
|
||||||
|
glx::GLX_GREEN_SIZE, 8,
|
||||||
|
glx::GLX_BLUE_SIZE, 8,
|
||||||
|
glx::GLX_ALPHA_SIZE, 8,
|
||||||
|
glx::GLX_DEPTH_SIZE, 24,
|
||||||
|
glx::GLX_STENCIL_SIZE, 8,
|
||||||
|
glx::GLX_DOUBLEBUFFER, 1,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
// The GLX framebuffer config holds an XVisualInfo, which we'll need for other X operations.
|
||||||
|
let x_visual_info: *const xlib::XVisualInfo =
|
||||||
|
unsafe { glx::glXGetVisualFromFBConfig(xcb_connection.conn.get_raw_dpy(), fb_config) };
|
||||||
|
|
||||||
|
// Load up DRI2 extensions.
|
||||||
|
// See also: https://www.x.org/releases/X11R7.7/doc/dri2proto/dri2proto.txt
|
||||||
|
/*
|
||||||
|
// needed later when we handle events
|
||||||
|
let dri2_ev = {
|
||||||
|
xcb_connection.conn.prefetch_extension_data(dri2::id());
|
||||||
|
match xcb_connection.conn.get_extension_data(dri2::id()) {
|
||||||
|
None => panic!("could not load dri2 extension"),
|
||||||
|
Some(r) => r.first_event(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Get screen information (?)
|
||||||
|
let setup = xcb_connection.conn.get_setup();
|
||||||
|
let screen = unsafe { setup.roots().nth((*x_visual_info).screen as usize).unwrap() };
|
||||||
|
|
||||||
|
// Convert parent into something that X understands
|
||||||
|
let parent_id = if let Some(p) = parent {
|
||||||
|
p
|
||||||
|
} else {
|
||||||
|
screen.root()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a colormap
|
||||||
|
let colormap = xcb_connection.conn.generate_id();
|
||||||
|
unsafe {
|
||||||
|
xcb::create_colormap(
|
||||||
|
&xcb_connection.conn,
|
||||||
|
xcb::COLORMAP_ALLOC_NONE as u8,
|
||||||
|
colormap,
|
||||||
|
parent_id,
|
||||||
|
(*x_visual_info).visualid as u32,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Create window, connecting to the parent if we have one
|
// Create window, connecting to the parent if we have one
|
||||||
let win = conn.generate_id();
|
let window_id = xcb_connection.conn.generate_id();
|
||||||
let event_mask = &[
|
let cw_values = [
|
||||||
(xcb::CW_BACK_PIXEL, screen.black_pixel()),
|
(xcb::CW_BACK_PIXEL, screen.white_pixel()),
|
||||||
|
(xcb::CW_BORDER_PIXEL, screen.black_pixel()),
|
||||||
(
|
(
|
||||||
xcb::CW_EVENT_MASK,
|
xcb::CW_EVENT_MASK,
|
||||||
xcb::EVENT_MASK_EXPOSURE
|
xcb::EVENT_MASK_EXPOSURE
|
||||||
|
@ -39,69 +108,154 @@ impl Window {
|
||||||
| xcb::EVENT_MASK_BUTTON_RELEASE
|
| xcb::EVENT_MASK_BUTTON_RELEASE
|
||||||
| xcb::EVENT_MASK_BUTTON_1_MOTION,
|
| xcb::EVENT_MASK_BUTTON_1_MOTION,
|
||||||
),
|
),
|
||||||
|
(xcb::CW_COLORMAP, colormap),
|
||||||
];
|
];
|
||||||
xcb::create_window(
|
xcb::create_window(
|
||||||
// Connection
|
// Connection
|
||||||
&conn,
|
&xcb_connection.conn,
|
||||||
// Depth
|
// Depth
|
||||||
xcb::COPY_FROM_PARENT as u8,
|
unsafe { *x_visual_info }.depth as u8,
|
||||||
// Window ID
|
// Window ID
|
||||||
win,
|
window_id,
|
||||||
// Parent ID
|
// Parent ID
|
||||||
if let Some(p) = parent {
|
parent_id,
|
||||||
p
|
|
||||||
} else {
|
|
||||||
screen.root()
|
|
||||||
},
|
|
||||||
// x
|
// x
|
||||||
0,
|
0,
|
||||||
// y
|
// y
|
||||||
0,
|
0,
|
||||||
// width
|
// width
|
||||||
1024,
|
options.width as u16,
|
||||||
// height
|
// height
|
||||||
1024,
|
options.height as u16,
|
||||||
// border width
|
// border width
|
||||||
0,
|
0,
|
||||||
// class
|
// class
|
||||||
xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
|
xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
|
||||||
// visual
|
// visual
|
||||||
screen.root_visual(),
|
unsafe { *x_visual_info }.visualid as u32,
|
||||||
// masks
|
// value list
|
||||||
event_mask,
|
&cw_values,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Don't need the visual info anymore
|
||||||
|
unsafe {
|
||||||
|
xlib::XFree(x_visual_info as *mut c_void);
|
||||||
|
}
|
||||||
|
|
||||||
// Change window title
|
// Change window title
|
||||||
let title = options.title;
|
let title = options.title;
|
||||||
xcb::change_property(
|
xcb::change_property(
|
||||||
&conn,
|
&xcb_connection.conn,
|
||||||
xcb::PROP_MODE_REPLACE as u8,
|
xcb::PROP_MODE_REPLACE as u8,
|
||||||
win,
|
window_id,
|
||||||
xcb::ATOM_WM_NAME,
|
xcb::ATOM_WM_NAME,
|
||||||
xcb::ATOM_STRING,
|
xcb::ATOM_STRING,
|
||||||
8,
|
8,
|
||||||
title.as_bytes(),
|
title.as_bytes(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Display the window
|
// Load GLX extensions
|
||||||
xcb::map_window(&conn, win);
|
// We need at least `GLX_ARB_create_context`
|
||||||
conn.flush();
|
let glx_extensions = unsafe {
|
||||||
|
CStr::from_ptr(glx::glXQueryExtensionsString(
|
||||||
|
xcb_connection.conn.get_raw_dpy(),
|
||||||
|
xcb_connection.xlib_display,
|
||||||
|
))
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
glx_extensions
|
||||||
|
.find("GLX_ARB_create_context")
|
||||||
|
.expect("could not find GLX extension GLX_ARB_create_context");
|
||||||
|
|
||||||
let x11_window = Self {
|
// With GLX, we don't need a context pre-created in order to load symbols.
|
||||||
xcb_connection: conn,
|
// Otherwise, we would need to create a temporary legacy (dummy) GL context to load them.
|
||||||
|
// (something that has at least GlXCreateContextAttribsARB)
|
||||||
|
let glx_create_context_attribs: opengl_util::GlXCreateContextAttribsARBProc =
|
||||||
|
unsafe { std::mem::transmute(opengl_util::load_gl_func("glXCreateContextAttribsARB")) };
|
||||||
|
|
||||||
|
// Load all other symbols
|
||||||
|
unsafe {
|
||||||
|
gl::load_with(|n| opengl_util::load_gl_func(&n));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check GL3 support
|
||||||
|
if !gl::GenVertexArrays::is_loaded() {
|
||||||
|
panic!("no GL3 support available!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This requires a global, which is a no. Figure out if there's a better way to do it.
|
||||||
|
/*
|
||||||
|
// installing an event handler to check if error is generated
|
||||||
|
unsafe {
|
||||||
|
ctx_error_occurred = false;
|
||||||
|
}
|
||||||
|
let old_handler = unsafe {
|
||||||
|
xlib::XSetErrorHandler(Some(ctx_error_handler))
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Create GLX context attributes. (?)
|
||||||
|
let context_attribs: [c_int; 5] = [
|
||||||
|
glx::arb::GLX_CONTEXT_MAJOR_VERSION_ARB as c_int,
|
||||||
|
3,
|
||||||
|
glx::arb::GLX_CONTEXT_MINOR_VERSION_ARB as c_int,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
];
|
||||||
|
let ctx = unsafe {
|
||||||
|
glx_create_context_attribs(
|
||||||
|
xcb_connection.conn.get_raw_dpy(),
|
||||||
|
fb_config,
|
||||||
|
null_mut(),
|
||||||
|
xlib::True,
|
||||||
|
&context_attribs[0] as *const c_int,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
x11_window.handle_events();
|
if ctx.is_null()
|
||||||
|
/* || ctx_error_occurred */
|
||||||
|
{
|
||||||
|
panic!("Error when creating a GL 3.0 context");
|
||||||
|
}
|
||||||
|
if unsafe { glx::glXIsDirect(xcb_connection.conn.get_raw_dpy(), ctx) } == 0 {
|
||||||
|
panic!("Obtained indirect rendering context");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display the window
|
||||||
|
xcb::map_window(&xcb_connection.conn, window_id);
|
||||||
|
xcb_connection.conn.flush();
|
||||||
|
unsafe {
|
||||||
|
xlib::XSync(xcb_connection.conn.get_raw_dpy(), xlib::False);
|
||||||
|
}
|
||||||
|
|
||||||
|
let x11_window = Self { xcb_connection };
|
||||||
|
|
||||||
|
x11_window.handle_events(window_id, ctx);
|
||||||
|
|
||||||
return x11_window;
|
return x11_window;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event loop
|
// Event loop
|
||||||
fn handle_events(&self) {
|
fn handle_events(&self, window_id: u32, ctx: *mut x11::glx::__GLXcontextRec) {
|
||||||
|
let raw_display = self.xcb_connection.conn.get_raw_dpy();
|
||||||
loop {
|
loop {
|
||||||
let ev = self.xcb_connection.wait_for_event();
|
let ev = self.xcb_connection.conn.wait_for_event();
|
||||||
if let Some(event) = ev {
|
if let Some(event) = ev {
|
||||||
println!("{:?}", event.response_type());
|
let event_type = event.response_type() & !0x80;
|
||||||
|
//println!("{:?}", event_type);
|
||||||
|
|
||||||
|
match event_type {
|
||||||
|
xcb::EXPOSE => unsafe {
|
||||||
|
glx::glXMakeCurrent(raw_display, window_id as xlib::XID, ctx);
|
||||||
|
gl::ClearColor(0.3, 0.8, 0.3, 1.0);
|
||||||
|
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||||
|
gl::Flush();
|
||||||
|
glx::glXSwapBuffers(raw_display, window_id as xlib::XID);
|
||||||
|
glx::glXMakeCurrent(raw_display, 0, null_mut());
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,11 +265,15 @@ impl Window {
|
||||||
// TODO: currently returning (96, 96) on my system, even though I have 4k screens. Problem with my setup perhaps?
|
// TODO: currently returning (96, 96) on my system, even though I have 4k screens. Problem with my setup perhaps?
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn get_scaling() -> (u32, u32) {
|
pub fn get_scaling() -> (u32, u32) {
|
||||||
let (conn, screen_num) = xcb::Connection::connect_with_xlib_display().unwrap();
|
// Connect to the X server
|
||||||
|
let xcb_connection = XcbConnection::new();
|
||||||
|
|
||||||
// Figure out screen information
|
// Figure out screen information
|
||||||
let setup = conn.get_setup();
|
let setup = xcb_connection.conn.get_setup();
|
||||||
let screen = setup.roots().nth(screen_num as usize).unwrap();
|
let screen = setup
|
||||||
|
.roots()
|
||||||
|
.nth(xcb_connection.xlib_display as usize)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Get the DPI from the screen struct
|
// Get the DPI from the screen struct
|
||||||
//
|
//
|
||||||
|
|
15
src/x11/xcb_connection.rs
Normal file
15
src/x11/xcb_connection.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
/// 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 XcbConnection {
|
||||||
|
pub conn: xcb::Connection,
|
||||||
|
pub xlib_display: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XcbConnection {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let (conn, xlib_display) = xcb::Connection::connect_with_xlib_display().unwrap();
|
||||||
|
Self { conn, xlib_display }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue