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 present #989

Merged
merged 13 commits into from
Jun 30, 2020
3 changes: 2 additions & 1 deletion druid-shell/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ gtk = { version = "0.8.1", optional = true }
glib = { version = "0.9.3", optional = true }
glib-sys = { version = "0.9.1", optional = true }
gtk-sys = { version = "0.9.2", optional = true }
x11rb = { version = "0.6.0", features = ["allow-unsafe-code", "randr"], optional = true }
x11rb = { version = "0.6.0", features = ["allow-unsafe-code", "present", "randr", "xfixes"], optional = true }

[target.'cfg(target_os="windows")'.dependencies]
wio = "0.2.2"
Expand Down Expand Up @@ -78,3 +78,4 @@ features = ["Window", "MouseEvent", "CssStyleDeclaration", "WheelEvent", "KeyEve

[dev-dependencies]
piet-common = { version = "0.1.1", features = ["png"] }
simple_logger = { version = "1.6.0", default-features = false }
1 change: 1 addition & 0 deletions druid-shell/examples/invalidate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ impl WinHandler for InvalidateTest {
}

fn main() {
simple_logger::init().expect("Failed to init simple logger");
let app = Application::new().unwrap();
let mut builder = WindowBuilder::new(app.clone());
let inv_test = InvalidateTest {
Expand Down
1 change: 1 addition & 0 deletions druid-shell/examples/perftest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ impl WinHandler for PerfTest {
}

fn main() {
simple_logger::init().expect("Failed to init simple logger");
jneem marked this conversation as resolved.
Show resolved Hide resolved
let app = Application::new().unwrap();
let mut builder = WindowBuilder::new(app.clone());
let perf_test = PerfTest {
Expand Down
1 change: 1 addition & 0 deletions druid-shell/examples/shello.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ impl WinHandler for HelloState {
}

fn main() {
simple_logger::init().expect("Failed to init simple logger");
let mut file_menu = Menu::new();
file_menu.add_item(
0x100,
Expand Down
8 changes: 8 additions & 0 deletions druid-shell/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@
//! `druid-shell` is an abstraction around a given platform UI & application
//! framework. It provides common types, which then defer to a platform-defined
//! implementation.
//!
//! # Env
//!
//! For testing and debugging, `druid-shell` can change its behavior based on environment
//! variables. Here is a list of environment variables that `druid-shell` supports:
//!
//! - `DRUID_SHELL_DISABLE_X11_PRESENT`: if this is set and `druid-shell` is using the `x11`
//! backend, it will avoid using the Present extension.

#![deny(intra_doc_link_resolution_failure)]
#![allow(clippy::new_without_default)]
Expand Down
102 changes: 99 additions & 3 deletions druid-shell/src/platform/x11/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ use std::rc::Rc;

use anyhow::{anyhow, Context, Error};
use x11rb::connection::Connection;
use x11rb::protocol::present::ConnectionExt as _;
use x11rb::protocol::xfixes::ConnectionExt as _;
use x11rb::protocol::xproto::{ConnectionExt, CreateWindowAux, EventMask, WindowClass};
use x11rb::protocol::Event;
use x11rb::xcb_ffi::XCBConnection;
Expand Down Expand Up @@ -60,6 +62,8 @@ pub(crate) struct Application {
window_id: u32,
/// The mutable `Application` state.
state: Rc<RefCell<State>>,
/// The major opcode of the Present extension, if it is supported.
present_opcode: Option<u8>,
}

/// The mutable `Application` state.
Expand Down Expand Up @@ -87,14 +91,76 @@ impl Application {
quitting: false,
windows: HashMap::new(),
}));

let present_opcode = if std::env::var_os("DRUID_SHELL_DISABLE_X11_PRESENT").is_some() {
jneem marked this conversation as resolved.
Show resolved Hide resolved
// Allow disabling Present with an environment variable.
None
} else {
match Application::query_present_opcode(&connection) {
Ok(p) => p,
Err(e) => {
log::info!("failed to find Present extension: {}", e);
None
}
}
};

Ok(Application {
connection,
screen_num: screen_num as i32,
window_id,
state,
present_opcode,
})
}

// Check if the Present extension is supported, returning its opcode if it is.
fn query_present_opcode(conn: &Rc<XCBConnection>) -> Result<Option<u8>, Error> {
let query = conn
.query_extension(b"Present")?
.reply()
.context("query Present extension")?;

if !query.present {
return Ok(None);
}

let opcode = Some(query.major_opcode);

// If Present is there at all, version 1.0 should be supported. This code
// shouldn't have a real effect; it's just a sanity check.
let version = conn
.present_query_version(1, 0)?
.reply()
.context("query Present version")?;
log::info!(
"X server supports Present version {}.{}",
version.major_version,
version.minor_version,
);

// We need the XFIXES extension to use regions. This code looks like it's just doing a
// sanity check but it is *necessary*: XFIXES doesn't work until we've done version
// negotiation
// (https://www.x.org/releases/X11R7.7/doc/fixesproto/fixesproto.txt)
let version = conn
.xfixes_query_version(5, 0)?
.reply()
.context("query XFIXES version")?;
log::info!(
"X server supports XFIXES version {}.{}",
version.major_version,
version.minor_version,
);

Ok(opcode)
}

#[inline]
pub(crate) fn present_opcode(&self) -> Option<u8> {
self.present_opcode
}

fn create_event_window(conn: &Rc<XCBConnection>, screen_num: i32) -> Result<u32, Error> {
let id = conn.generate_id()?;
let setup = conn.setup();
Expand Down Expand Up @@ -128,7 +194,8 @@ impl Application {
// Window properties mask
&CreateWindowAux::new().event_mask(EventMask::StructureNotify),
)?
.check()?;
.check()
.context("create input-only window")?;

Ok(id)
}
Expand Down Expand Up @@ -171,7 +238,7 @@ impl Application {
// to know if the event must be ignored.
// Otherwise there will be a "failed to get window" error.
//
// CIRCULATE_NOTIFY, CONFIGURE_NOTIFY, GRAVITY_NOTIFY
// CIRCULATE_NOTIFY, GRAVITY_NOTIFY
// MAP_NOTIFY, REPARENT_NOTIFY, UNMAP_NOTIFY
Event::Expose(ev) => {
let w = self
Expand Down Expand Up @@ -249,6 +316,35 @@ impl Application {
self.finalize_quit();
}
}
Event::ConfigureNotify(ev) => {
jneem marked this conversation as resolved.
Show resolved Hide resolved
if ev.window != self.window_id {
let w = self
.window(ev.window)
.context("CONFIGURE_NOTIFY - failed to get window")?;
w.handle_configure_notify(ev)
.context("CONFIGURE_NOTIFY - failed to handle")?;
}
}
Event::PresentCompleteNotify(ev) => {
let w = self
.window(ev.window)
.context("COMPLETE_NOTIFY - failed to get window")?;
w.handle_complete_notify(ev)
.context("COMPLETE_NOTIFY - failed to handle")?;
}
Event::PresentIdleNotify(ev) => {
let w = self
.window(ev.window)
.context("IDLE_NOTIFY - failed to get window")?;
w.handle_idle_notify(ev)
.context("IDLE_NOTIFY - failed to handle")?;
}
Event::Error(e) => {
// TODO: if an error is caused by the present extension, disable it and fall back
// to copying pixels. This is blocked on
// https://github.com/psychon/x11rb/issues/503
return Err(x11rb::errors::ReplyError::from(e.clone()).into());
}
_ => {}
}
Ok(false)
Expand All @@ -264,7 +360,7 @@ impl Application {
}
}
Err(e) => {
log::error!("Error handling event: {}", e);
log::error!("Error handling event: {:#}", e);
}
}
}
Expand Down
24 changes: 19 additions & 5 deletions druid-shell/src/platform/x11/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,30 @@
//! Errors at the application shell level.

use std::fmt;
use std::sync::Arc;

/// The X11 backend doesn't currently define any platform-specific errors;
/// it uses the `crate::error::Other` variant instead.
#[derive(Debug, Clone)]
pub enum Error {}
pub enum Error {
XError(Arc<x11rb::errors::ReplyError>),
}

impl fmt::Display for Error {
fn fmt(&self, _: &mut fmt::Formatter) -> Result<(), fmt::Error> {
Ok(())
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let Error::XError(e) = self;
e.fmt(f)
}
}

impl std::error::Error for Error {}

impl From<x11rb::protocol::Error> for Error {
fn from(err: x11rb::protocol::Error) -> Error {
Error::XError(Arc::new(x11rb::errors::ReplyError::X11Error(err)))
}
}

impl From<x11rb::errors::ReplyError> for Error {
fn from(err: x11rb::errors::ReplyError) -> Error {
Error::XError(Arc::new(err))
}
}
12 changes: 12 additions & 0 deletions druid-shell/src/platform/x11/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@
// Might be related to the "sleep scheduler" in XWindow::render()?
// TODO(x11/render_improvements): double-buffering / present strategies / etc?

// # Notes on error handling in X11
//
// In XCB, errors are reported asynchronously by default, by sending them to the event
// loop. You can also request a synchronous error for a given call; we use this in
// window initialization, but otherwise we take the async route.
//
// When checking for X11 errors synchronously, there are two places where the error could
// happen. An error on the request means the connection is broken. There's no need for
// extra error context here, because the fact that the connection broke has nothing to do
// with what we're trying to do. An error on the reply means there was something wrong with
// the request, and so we add context. This convention is used throughout the x11 backend.

#[macro_use]
mod util;

Expand Down
Loading