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/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
96 changes: 95 additions & 1 deletion 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,69 @@ impl Application {
quitting: false,
windows: HashMap::new(),
}));
let present_opcode = 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 @@ -249,6 +308,41 @@ impl Application {
self.finalize_quit();
}
}
Event::ConfigureNotify(ev) => {
jneem marked this conversation as resolved.
Show resolved Hide resolved
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")?;
}
// In XCB, errors are reported asynchronously by default, by sending them to the event
// loop. You can opt-out of this by using ..._checked variants of a function, and then
// getting the error synchronously. We use this in window initialization, but otherwise
// we take the async route.
Event::Error(e) => {
if let x11rb::protocol::Error::Request(req) = e {
if self.present_opcode == Some(req.major_opcode) {
for window in borrow!(self.state)?.windows.values() {
window.disable_present()?;
}
}
}
return Err(x11rb::errors::ReplyError::from(e.clone()).into());
}
_ => {}
}
Ok(false)
Expand All @@ -264,7 +358,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))
}
}
Loading