diff --git a/Cargo.toml b/Cargo.toml index 25e34d44..fcebe2ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,8 @@ dwmapi-sys = "0.1" [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))'.dependencies] wayland-client = { version = "0.9.9", features = ["dlopen"] } +wayland-protocols = { version = "0.9.9", features = ["unstable_protocols"] } wayland-kbd = "0.9.1" -wayland-window = "0.6.1" +wayland-window = "0.7.0" tempfile = "2.1" x11-dl = "2.8" diff --git a/src/platform/linux/wayland/context.rs b/src/platform/linux/wayland/context.rs index 54e18c67..98367bbf 100644 --- a/src/platform/linux/wayland/context.rs +++ b/src/platform/linux/wayland/context.rs @@ -8,15 +8,18 @@ use wayland_client::{EnvHandler, default_connect, EventQueue, EventQueueHandle, use wayland_client::protocol::{wl_compositor, wl_seat, wl_shell, wl_shm, wl_subcompositor, wl_display, wl_registry, wl_output, wl_surface, wl_buffer}; +use super::wayland_protocols::unstable::xdg_shell::client::zxdg_shell_v6; + use super::{wayland_window, tempfile}; +use super::wayland_window::Shell; + /* * Registry and globals handling */ wayland_env!(InnerEnv, compositor: wl_compositor::WlCompositor, - shell: wl_shell::WlShell, shm: wl_shm::WlShm, subcompositor: wl_subcompositor::WlSubcompositor ); @@ -24,6 +27,7 @@ wayland_env!(InnerEnv, struct WaylandEnv { registry: wl_registry::WlRegistry, inner: EnvHandler, + shell: Option, monitors: Vec, my_id: usize, } @@ -53,6 +57,7 @@ impl WaylandEnv { WaylandEnv { registry: registry, inner: EnvHandler::new(), + shell: None, monitors: Vec::new(), my_id: 0, } @@ -70,6 +75,24 @@ impl WaylandEnv { } None } + + fn ensure_shell(&mut self) -> bool { + if self.shell.is_some() { + return true; + } + // xdg_shell is not available, so initialize wl_shell + for &(name, ref interface, _) in self.inner.globals() { + if interface == "wl_shell" { + self.shell = Some(Shell::Wl(self.registry.bind::(1, name))); + return true; + } + } + return false; + } + + fn get_shell(&self) -> &Shell { + self.shell.as_ref().expect("Shell was not properly initialized") + } } impl Init for WaylandEnv { @@ -87,13 +110,19 @@ impl wl_registry::Handler for WaylandEnv { interface: String, version: u32) { - if interface == "wl_output" { + if interface == wl_output::WlOutput::interface_name() { // intercept outputs // this "expect" cannot trigger (see https://github.com/vberger/wayland-client-rs/issues/69) let output = self.registry.bind::(1, name); evqh.register::<_, WaylandEnv>(&output, self.my_id); self.monitors.push(OutputInfo::new(output, name)); + } else if interface == zxdg_shell_v6::ZxdgShellV6::interface_name() { + let xdg_shell = self.registry.bind::(1, name); + let xdg_ping_hid = evqh.add_handler(XdgShellPingHandler); + evqh.register::<_, XdgShellPingHandler>(&xdg_shell, xdg_ping_hid); + self.shell = Some(Shell::Xdg(xdg_shell)); } + self.inner.global(evqh, registry, name, interface, version); } @@ -108,6 +137,16 @@ impl wl_registry::Handler for WaylandEnv { } } +struct XdgShellPingHandler; + +impl zxdg_shell_v6::Handler for XdgShellPingHandler { + fn ping(&mut self, _: &mut EventQueueHandle, proxy: &zxdg_shell_v6::ZxdgShellV6, serial: u32) { + proxy.pong(serial); + } +} + +declare_handler!(XdgShellPingHandler, zxdg_shell_v6::Handler, zxdg_shell_v6::ZxdgShellV6); + declare_handler!(WaylandEnv, wl_registry::Handler, wl_registry::WlRegistry); impl wl_output::Handler for WaylandEnv { @@ -175,10 +214,19 @@ impl WaylandContext { // this "expect" cannot trigger (see https://github.com/vberger/wayland-client-rs/issues/69) let registry = display.get_registry(); let env_id = event_queue.add_handler_with_init(WaylandEnv::new(registry)); - // two syncs fully initialize + // two round trips to fully initialize event_queue.sync_roundtrip().expect("Wayland connection unexpectedly lost"); event_queue.sync_roundtrip().expect("Wayland connection unexpectedly lost"); + { + let mut state = event_queue.state(); + let mut env = state.get_mut_handler::(env_id); + if !env.ensure_shell() { + // This is a compositor bug, it _must_ at least support xl_shell + panic!("Compositor didi not advertize xdg_shell not wl_shell."); + } + } + Some(WaylandContext { evq: Mutex::new(event_queue), display: display, @@ -216,35 +264,65 @@ impl WaylandContext { } } - pub fn create_window(&self, width: u32, height: u32) - -> (Arc, wayland_window::DecoratedSurface, wl_buffer::WlBuffer, File) - { - let mut guard = self.evq.lock().unwrap(); - let mut state = guard.state(); - let env = state.get_mut_handler::(self.env_id); - let surface = Arc::new(env.inner.compositor.create_surface()); - let decorated = wayland_window::DecoratedSurface::new( - &*surface, 800, 600, - &env.inner.compositor, - &env.inner.subcompositor, - &env.inner.shm, - &env.inner.shell, - env.get_seat(), - false - ).expect("Failed to create a tmpfile buffer."); - // prepare a white content for the window, so that it exists + fn blank_surface(&self, surface: &wl_surface::WlSurface, evq: &mut EventQueue, width: i32, height: i32) { let mut tmp = tempfile::tempfile().expect("Failed to create a tmpfile buffer."); for _ in 0..(width*height) { tmp.write_all(&[0xff,0xff,0xff,0xff]).unwrap(); } tmp.flush().unwrap(); - let pool = env.inner.shm.create_pool(tmp.as_raw_fd(), (width*height*4) as i32); - let buffer = pool.create_buffer(0, width as i32, height as i32, width as i32, wl_shm::Format::Argb8888).expect("Pool cannot be already dead"); + let pool = { + let mut state = evq.state(); + let env = state.get_mut_handler::(self.env_id); + env.inner.shm.create_pool(tmp.as_raw_fd(), width*height*4) + }; + let buffer = pool.create_buffer(0, width, height, width, wl_shm::Format::Argb8888).expect("Pool cannot be already dead"); surface.attach(Some(&buffer), 0, 0); surface.commit(); - // the buffer wiil keep the contents alive as needed + // the buffer will keep the contents alive as needed pool.destroy(); - (surface, decorated, buffer, tmp) + + // create a handler to clean up initial buffer + let init_buffer_handler = InitialBufferHandler { + initial_buffer: Some((buffer.clone().unwrap(), tmp)) + }; + let initial_buffer_handler_id = evq.add_handler(init_buffer_handler); + // register the buffer to it + evq.register::<_, InitialBufferHandler>(&buffer, initial_buffer_handler_id); + } + + pub fn create_window(&self, width: u32, height: u32) + -> (Arc, wayland_window::DecoratedSurface) + { + let mut guard = self.evq.lock().unwrap(); + let (surface, decorated, xdg) = { + let mut state = guard.state(); + let env = state.get_mut_handler::(self.env_id); + let surface = Arc::new(env.inner.compositor.create_surface()); + let decorated = wayland_window::DecoratedSurface::new( + &*surface, 800, 600, + &env.inner.compositor, + &env.inner.subcompositor, + &env.inner.shm, + env.get_shell(), + env.get_seat(), + false + ).expect("Failed to create a tmpfile buffer."); + let xdg = match env.get_shell() { + &Shell::Xdg(_) => true, + &Shell::Wl(_) => false + }; + (surface, decorated, xdg) + }; + + if !xdg { + // if using wl_shell, we need to draw something in order to kickstart + // the event loop + // if using xdg_shell, it is an error to do it now, and the events loop will not + // be stuck. We cannot draw anything before having received an appropriate event + // from the compositor + self.blank_surface(&surface, &mut *guard, width as i32, height as i32); + } + (surface, decorated) } } @@ -309,3 +387,19 @@ impl MonitorId { (0,0) } } + +// a handler to release the ressources acquired to draw the initial white screen as soon as +// the compositor does not use them any more +pub struct InitialBufferHandler { + initial_buffer: Option<(wl_buffer::WlBuffer, File)> +} + +impl wl_buffer::Handler for InitialBufferHandler { + fn release(&mut self, _: &mut EventQueueHandle, buffer: &wl_buffer::WlBuffer) { + // release the ressources we've acquired for initial white window + buffer.destroy(); + self.initial_buffer = None; + } +} + +declare_handler!(InitialBufferHandler, wl_buffer::Handler, wl_buffer::WlBuffer); diff --git a/src/platform/linux/wayland/event_loop.rs b/src/platform/linux/wayland/event_loop.rs index 670d0158..031d992f 100644 --- a/src/platform/linux/wayland/event_loop.rs +++ b/src/platform/linux/wayland/event_loop.rs @@ -155,6 +155,15 @@ impl EventsLoop { } ); } + if decorated.handler().as_ref().map(|h| h.is_closed()).unwrap_or(false) { + callback( + ::Event::WindowEvent { + window_id: ::WindowId(::platform::WindowId::Wayland(make_wid(&window))), + event: ::WindowEvent::Closed + } + ); + + } } } diff --git a/src/platform/linux/wayland/mod.rs b/src/platform/linux/wayland/mod.rs index 46665ee3..c789379b 100644 --- a/src/platform/linux/wayland/mod.rs +++ b/src/platform/linux/wayland/mod.rs @@ -10,6 +10,7 @@ use self::event_loop::EventsLoopSink; extern crate wayland_kbd; extern crate wayland_window; +extern crate wayland_protocols; extern crate tempfile; mod context; diff --git a/src/platform/linux/wayland/window.rs b/src/platform/linux/wayland/window.rs index 10e9124a..b67d77f2 100644 --- a/src/platform/linux/wayland/window.rs +++ b/src/platform/linux/wayland/window.rs @@ -1,9 +1,8 @@ -use std::fs::File; use std::sync::{Arc, Mutex}; use std::sync::atomic::AtomicBool; use wayland_client::{EventQueue, EventQueueHandle, Proxy}; -use wayland_client::protocol::{wl_display,wl_surface,wl_shell_surface,wl_buffer}; +use wayland_client::protocol::{wl_display,wl_surface}; use {CreationError, MouseCursor, CursorState, WindowAttributes}; use platform::MonitorId as PlatformMonitorId; @@ -40,26 +39,16 @@ impl Window { { let (width, height) = attributes.dimensions.unwrap_or((800,600)); - let (surface, decorated, buffer, tmpfile) = ctxt.create_window::(width, height); + let (surface, decorated) = ctxt.create_window::(width, height); // init DecoratedSurface let (evq, cleanup_signal) = evlp.get_window_init(); let decorated_id = { let mut evq_guard = evq.lock().unwrap(); - // create a handler to clean up initial buffer - let initial_buffer_handler_id = evq_guard.add_handler(InitialBufferHandler::new()); - // register the buffer to it - evq_guard.register::<_, InitialBufferHandler>(&buffer, initial_buffer_handler_id); // store the DecoratedSurface handler let decorated_id = evq_guard.add_handler_with_init(decorated); { let mut state = evq_guard.state(); - { - // store the buffer and tempfile in the handler, to be cleanded up at the right - // time - let initial_buffer_h = state.get_mut_handler::(initial_buffer_handler_id); - initial_buffer_h.initial_buffer = Some((buffer, tmpfile)); - } // initialize the DecoratedHandler let decorated = state.get_mut_handler::>(decorated_id); *(decorated.handler()) = Some(DecoratedHandler::new()); @@ -67,11 +56,7 @@ impl Window { // set fullscreen if necessary if let Some(PlatformMonitorId::Wayland(ref monitor_id)) = attributes.monitor { ctxt.with_output(monitor_id.clone(), |output| { - decorated.set_fullscreen( - wl_shell_surface::FullscreenMethod::Default, - 0, - Some(output) - ) + decorated.set_fullscreen(Some(output)) }); } else if attributes.decorations { decorated.set_decorate(true); @@ -147,6 +132,7 @@ impl Window { let mut state = guard.state(); let mut decorated = state.get_mut_handler::>(self.decorated_id); decorated.resize(x as i32, y as i32); + *(self.size.lock().unwrap()) = (x, y); } #[inline] @@ -194,50 +180,38 @@ impl Drop for Window { } pub struct DecoratedHandler { - newsize: Option<(u32, u32)> + newsize: Option<(u32, u32)>, + closed: bool, } impl DecoratedHandler { - fn new() -> DecoratedHandler { DecoratedHandler { newsize: None }} + fn new() -> DecoratedHandler { + DecoratedHandler { + newsize: None, + closed: false, + } + } pub fn take_newsize(&mut self) -> Option<(u32, u32)> { self.newsize.take() } + + pub fn is_closed(&self) -> bool { self.closed } } impl wayland_window::Handler for DecoratedHandler { fn configure(&mut self, _: &mut EventQueueHandle, - _: wl_shell_surface::Resize, + _: wayland_window::Configure, width: i32, height: i32) { use std::cmp::max; self.newsize = Some((max(width,1) as u32, max(height,1) as u32)); } -} -// a handler to release the ressources acquired to draw the initial white screen as soon as -// the compositor does not use them any more - -pub struct InitialBufferHandler { - initial_buffer: Option<(wl_buffer::WlBuffer, File)> -} - -impl InitialBufferHandler { - fn new() -> InitialBufferHandler { - InitialBufferHandler { - initial_buffer: None, - } + fn close(&mut self, _: &mut EventQueueHandle) { + self.closed = true; } } -impl wl_buffer::Handler for InitialBufferHandler { - fn release(&mut self, _: &mut EventQueueHandle, buffer: &wl_buffer::WlBuffer) { - // release the ressources we've acquired for initial white window - buffer.destroy(); - self.initial_buffer = None; - } -} - -declare_handler!(InitialBufferHandler, wl_buffer::Handler, wl_buffer::WlBuffer);