aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbnewbold <bnewbold@robocracy.org>2016-06-10 18:32:26 -0400
committerbnewbold <bnewbold@robocracy.org>2016-06-12 12:23:15 -0400
commitbea8daf5e49055bc72ab7f61d245463b1ef1f1bd (patch)
tree13b47f64316102b01e4c719c958f51a952ce7210
parent8c6a3cb1ac90cc8c625c735193c49489379940d8 (diff)
downloadglutin-bea8daf5e49055bc72ab7f61d245463b1ef1f1bd.tar.gz
glutin-bea8daf5e49055bc72ab7f61d245463b1ef1f1bd.zip
x11: initial implementation of from_existing_window
This adds the ability to create a glutin Window that wraps a pre-existing X Window. The proximal motivation for this is to allow writing of XScreensaver hacks in Rust, but it might also be useful for embedding 3D graphics in larger applications with full process separation, etc. This commit includes a bit of inline documentation, but no tests, and the details of what if any WindowAttributes or pixel information should be applied to the existing window aren't worked out.
-rw-r--r--src/api/x11/window.rs232
-rw-r--r--src/os/unix.rs15
-rw-r--r--src/platform/linux/api_dispatch.rs14
3 files changed, 255 insertions, 6 deletions
diff --git a/src/api/x11/window.rs b/src/api/x11/window.rs
index dc7fa62..247e4e4 100644
--- a/src/api/x11/window.rs
+++ b/src/api/x11/window.rs
@@ -11,6 +11,7 @@ use std::sync::{Arc, Mutex};
use std::os::raw::c_long;
use std::thread;
use std::time::Duration;
+use std::os::raw::c_ulong;
use Api;
use ContextError;
@@ -704,6 +705,237 @@ impl Window {
Ok(window)
}
+ /// Instead of actually creating an X11 window and returning a handle to
+ /// it, this function will use Xlib to find a pre-existing window (eg, one
+ /// created by another process) and return a wrapper for that.
+ ///
+ /// `window_attrs` and `pf_reqs` will mostly be ignored, while the `opengl`
+ /// attributes will be honored.
+ ///
+ /// `window_id` is the X window id of the existing window. This can be
+ /// found with, eg, the `xwininfo -tree` command. If the window can't be
+ /// found, an error will be returned.
+ ///
+ /// You almost certainly want to use `new()` instead, unless you are doing
+ /// something like drawing into an embedded sub-window of an existing
+ /// application.
+ pub fn from_existing_window(display: &Arc<XConnection>, window_attrs: &WindowAttributes,
+ pf_reqs: &PixelFormatRequirements, opengl: &GlAttributes<&Window>,
+ window_id: c_ulong)
+ -> Result<Window, CreationError>
+ {
+
+ // check early that window_id actually exists, and grab attributes for it
+ let window = window_id;
+ let (screen_id, root) = unsafe {
+ let mut existing_attr: ffi::XWindowAttributes = mem::uninitialized();
+ (display.xlib.XGetWindowAttributes)(display.display, window_id, &mut existing_attr);
+ display.check_errors().expect("window_id did not map to an accessible existing window");
+ // Seems like there would be a cleaner way to find the screen_number, but there doesn't seem to be.
+ let max_screen_id = (display.xlib.XScreenCount)(display.display);
+ let mut screen_id = 0;
+ for id in 0..max_screen_id {
+ if existing_attr.screen == (display.xlib.XScreenOfDisplay)(display.display, id) {
+ screen_id = id;
+ break;
+ }
+ display.check_errors().expect("Problem fetching screen info");
+ if id == max_screen_id {
+ return Err(CreationError::OsError(format!("Couldn't find screen_number for window's screen")));
+ }
+ }
+ // get the root window handle
+ let root = existing_attr.root;
+ (screen_id, root)
+ };
+
+ // We aren't handling mode changes, so pretend we're not full-screen
+ // (even if existing window is)
+ let is_fullscreen = false;
+ let xf86_desk_mode = None;
+
+ // start the (OpenGL) context building process (same as new())
+ enum Prototype<'a> {
+ Glx(::api::glx::ContextPrototype<'a>),
+ Egl(::api::egl::ContextPrototype<'a>),
+ }
+ let builder_clone_opengl_glx = opengl.clone().map_sharing(|_| unimplemented!()); // FIXME:
+ let builder_clone_opengl_egl = opengl.clone().map_sharing(|_| unimplemented!()); // FIXME:
+ let context = match opengl.version {
+ GlRequest::Latest | GlRequest::Specific(Api::OpenGl, _) | GlRequest::GlThenGles { .. } => {
+ // GLX should be preferred over EGL, otherwise crashes may occur
+ // on X11 – issue #314
+ if let Some(ref glx) = display.glx {
+ Prototype::Glx(try!(GlxContext::new(glx.clone(), &display.xlib, pf_reqs, &builder_clone_opengl_glx, display.display, screen_id)))
+ } else if let Some(ref egl) = display.egl {
+ Prototype::Egl(try!(EglContext::new(egl.clone(), pf_reqs, &builder_clone_opengl_egl, egl::NativeDisplay::X11(Some(display.display as *const _)))))
+ } else {
+ return Err(CreationError::NotSupported);
+ }
+ },
+ GlRequest::Specific(Api::OpenGlEs, _) => {
+ if let Some(ref egl) = display.egl {
+ Prototype::Egl(try!(EglContext::new(egl.clone(), pf_reqs, &builder_clone_opengl_egl, egl::NativeDisplay::X11(Some(display.display as *const _)))))
+ } else {
+ return Err(CreationError::NotSupported);
+ }
+ },
+ GlRequest::Specific(_, _) => {
+ return Err(CreationError::NotSupported);
+ },
+ };
+
+ // getting the `visual_infos` (a struct that contains information about the visual to use) (same as new() )
+ let visual_infos = match context {
+ Prototype::Glx(ref p) => p.get_visual_infos().clone(),
+ Prototype::Egl(ref p) => {
+ unsafe {
+ let mut template: ffi::XVisualInfo = mem::zeroed();
+ template.visualid = p.get_native_visual_id() as ffi::VisualID;
+
+ let mut num_visuals = 0;
+ let vi = (display.xlib.XGetVisualInfo)(display.display, ffi::VisualIDMask,
+ &mut template, &mut num_visuals);
+ display.check_errors().expect("Failed to call XGetVisualInfo");
+ assert!(!vi.is_null());
+ assert!(num_visuals == 1);
+
+ let vi_copy = ptr::read(vi as *const _);
+ (display.xlib.XFree)(vi as *mut _);
+ vi_copy
+ }
+ },
+ };
+
+ // creating the color map (same as new() )
+ let cmap = unsafe {
+ let cmap = (display.xlib.XCreateColormap)(display.display, root,
+ visual_infos.visual as *mut _,
+ ffi::AllocNone);
+ display.check_errors().expect("Failed to call XCreateColormap");
+ cmap
+ };
+
+ // subscribe to particular events (*not* the same as new() )
+ let mut set_win_attr = {
+ let mut swa: ffi::XSetWindowAttributes = unsafe { mem::zeroed() };
+ swa.event_mask = ffi::ExposureMask | ffi::StructureNotifyMask |
+ ffi::VisibilityChangeMask | ffi::KeyPressMask | ffi::PointerMotionMask |
+ ffi::KeyReleaseMask | ffi::ButtonPressMask |
+ ffi::ButtonReleaseMask | ffi::KeymapStateMask;
+ swa.override_redirect = 0;
+ swa
+ };
+
+ let window_attributes = ffi::CWEventMask;
+
+ /* XXX: This seems important, but results in failure
+ unsafe {
+ (display.xlib.XChangeWindowAttributes)(display.display, window_id, window_attributes, &mut set_win_attr);
+ }
+ display.check_errors().expect("Failed to (re)configure existing X window");
+ */
+
+ // creating window, step 2
+ let wm_delete_window = unsafe {
+ let mut wm_delete_window = with_c_str("WM_DELETE_WINDOW", |delete_window|
+ (display.xlib.XInternAtom)(display.display, delete_window, 0)
+ );
+ display.check_errors().expect("Failed to call XInternAtom");
+ (display.xlib.XSetWMProtocols)(display.display, window, &mut wm_delete_window, 1);
+ display.check_errors().expect("Failed to call XSetWMProtocols");
+ (display.xlib.XFlush)(display.display);
+ display.check_errors().expect("Failed to call XFlush");
+
+ wm_delete_window
+ };
+
+ // creating IM
+ let im = unsafe {
+ let _lock = GLOBAL_XOPENIM_LOCK.lock().unwrap();
+
+ let im = (display.xlib.XOpenIM)(display.display, ptr::null_mut(), ptr::null_mut(), ptr::null_mut());
+ if im.is_null() {
+ return Err(OsError(format!("XOpenIM failed")));
+ }
+ im
+ };
+
+ // creating input context
+ let ic = unsafe {
+ let ic = with_c_str("inputStyle", |input_style|
+ with_c_str("clientWindow", |client_window|
+ (display.xlib.XCreateIC)(
+ im, input_style,
+ ffi::XIMPreeditNothing | ffi::XIMStatusNothing, client_window,
+ window, ptr::null::<()>()
+ )
+ )
+ );
+ if ic.is_null() {
+ return Err(OsError(format!("XCreateIC failed")));
+ }
+ (display.xlib.XSetICFocus)(ic);
+ display.check_errors().expect("Failed to call XSetICFocus");
+ ic
+ };
+
+ // Attempt to make keyboard input repeat detectable
+ unsafe {
+ let mut supported_ptr = ffi::False;
+ (display.xlib.XkbSetDetectableAutoRepeat)(display.display, ffi::True, &mut supported_ptr);
+ if supported_ptr == ffi::False {
+ return Err(OsError(format!("XkbSetDetectableAutoRepeat failed")));
+ }
+ }
+
+ // finish creating the OpenGL context
+ let context = match context {
+ Prototype::Glx(ctxt) => {
+ Context::Glx(try!(ctxt.finish(window)))
+ },
+ Prototype::Egl(ctxt) => {
+ Context::Egl(try!(ctxt.finish(window as *const libc::c_void)))
+ },
+ };
+
+ // creating the OpenGL can produce errors, but since everything is checked we ignore
+ display.ignore_error();
+
+ // creating the window object
+ let window_proxy_data = WindowProxyData {
+ display: display.clone(),
+ window: window,
+ };
+ let window_proxy_data = Arc::new(Mutex::new(Some(window_proxy_data)));
+
+ let window = Window {
+ x: Arc::new(XWindow {
+ display: display.clone(),
+ window: window,
+ im: im,
+ ic: ic,
+ context: context,
+ screen_id: screen_id,
+ is_fullscreen: is_fullscreen,
+ xf86_desk_mode: xf86_desk_mode,
+ colormap: cmap,
+ window_proxy_data: window_proxy_data,
+ }),
+ is_closed: AtomicBool::new(false),
+ wm_delete_window: wm_delete_window,
+ current_size: Cell::new((0, 0)),
+ pending_events: Mutex::new(VecDeque::new()),
+ cursor_state: Mutex::new(CursorState::Normal),
+ input_handler: Mutex::new(XInputEventHandler::new(display, window, ic, window_attrs))
+ };
+
+ // Don't set title or "make visible"
+
+ // returning
+ Ok(window)
+ }
+
pub fn set_title(&self, title: &str) {
let wm_name = unsafe {
(self.x.display.xlib.XInternAtom)(self.x.display.display, b"_NET_WM_NAME\0".as_ptr() as *const _, 0)
diff --git a/src/os/unix.rs b/src/os/unix.rs
index 61c89bc..0d89571 100644
--- a/src/os/unix.rs
+++ b/src/os/unix.rs
@@ -1,5 +1,7 @@
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
+use std::os::raw::c_ulong;
+
use libc;
use Window;
use platform::Window as LinuxWindow;
@@ -41,9 +43,16 @@ impl WindowExt for Window {
}
/// Additional methods on `WindowBuilder` that are specific to Unix.
-pub trait WindowBuilderExt {
-
+pub trait WindowBuilderExt<'a> {
+ fn from_existing_window(mut self, window_id: c_ulong) -> WindowBuilder<'a>;
}
-impl<'a> WindowBuilderExt for WindowBuilder<'a> {
+impl<'a> WindowBuilderExt<'a> for WindowBuilder<'a> {
+
+ /// Tells this (UNIX/X11) WindowBuilder to use an existing X window (eg,
+ /// one created by another application) instead of creating a new window.
+ fn from_existing_window(mut self, window_id: c_ulong) -> WindowBuilder<'a> {
+ self.platform_specific.existing_x11_window_id = Some(window_id);
+ self
+ }
}
diff --git a/src/platform/linux/api_dispatch.rs b/src/platform/linux/api_dispatch.rs
index 54e0186..3a2a1e8 100644
--- a/src/platform/linux/api_dispatch.rs
+++ b/src/platform/linux/api_dispatch.rs
@@ -21,9 +21,14 @@ use api::x11;
use api::x11::XConnection;
use api::x11::XError;
use api::x11::XNotSupported;
+use std::os::raw::c_ulong;
#[derive(Clone, Default)]
-pub struct PlatformSpecificWindowBuilderAttributes;
+pub struct PlatformSpecificWindowBuilderAttributes {
+ /// Optionally tells the WindowBuilder to re-use an existing X window with
+ /// the given Window id number
+ pub existing_x11_window_id: Option<c_ulong>,
+}
enum Backend {
X(Arc<XConnection>),
@@ -175,7 +180,7 @@ impl<'a> Iterator for WaitEventsIterator<'a> {
impl Window {
#[inline]
pub fn new(window: &WindowAttributes, pf_reqs: &PixelFormatRequirements,
- opengl: &GlAttributes<&Window>, _: &PlatformSpecificWindowBuilderAttributes)
+ opengl: &GlAttributes<&Window>, platform: &PlatformSpecificWindowBuilderAttributes)
-> Result<Window, CreationError>
{
match *BACKEND {
@@ -194,7 +199,10 @@ impl Window {
_ => panic!() // TODO: return an error
});
- x11::Window::new(connec, window, pf_reqs, &opengl).map(Window::X)
+ match platform.existing_x11_window_id {
+ None => x11::Window::new(connec, window, pf_reqs, &opengl).map(Window::X),
+ Some(window_id) => x11::Window::from_existing_window(connec, window, pf_reqs, &opengl, window_id).map(Window::X),
+ }
},
Backend::Error(ref error) => Err(CreationError::NoBackendAvailable(Box::new(error.clone())))