1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-22 22:42:48 +03:00

hook up paint method for x11

This commit is contained in:
Wez Furlong 2019-08-09 11:15:09 -07:00
parent 1de4165152
commit 56dc24a1ee
7 changed files with 635 additions and 118 deletions

View File

@ -28,6 +28,7 @@ case `lsb_release -ds` in
libxcb-ewmh-dev \
libxcb-icccm4-dev \
libxcb-keysyms1-dev \
libxcb-shm0-dev \
libxcb-xkb-dev \
libxkbcommon-dev \
libxkbcommon-x11-dev \

View File

@ -29,10 +29,11 @@ winapi = { version = "0.3", features = [
[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies]
x11 = {version ="2.18", features = ["xlib_xcb"]}
xcb = "0.8"
xcb-util = { features = [ "icccm", "ewmh", "keysyms", ], version = "0.2" }
xcb-util = { features = [ "icccm", "ewmh", "keysyms", "shm"], version = "0.2" }
xkbcommon = { version = "0.4", features = ["x11"] }
mio = "0.6"
mio-extras = "2.0"
libc = "0.2"
[target.'cfg(target_os="macos")'.dependencies]
cocoa = "0.19"

View File

@ -26,6 +26,10 @@ impl WindowCallbacks for MyWindow {
eprintln!("destroy was called!");
Connection::get().unwrap().terminate_message_loop();
}
fn paint(&mut self, context: &mut dyn PaintContext) {
context.clear(Color::rgb(0x40, 0, 0));
}
}
fn main() -> Fallible<()> {

233
window/src/os/x11/bitmap.rs Normal file
View File

@ -0,0 +1,233 @@
use super::*;
use crate::bitmaps::*;
use failure::{bail, Fallible};
use std::sync::Arc;
/// The X protocol allows referencing a number of drawable
/// objects. This trait marks those objects here in code.
pub trait Drawable {
fn as_drawable(&self) -> xcb::xproto::Drawable;
}
impl Drawable for xcb::xproto::Window {
fn as_drawable(&self) -> xcb::xproto::Drawable {
*self
}
}
pub struct Context {
gc_id: xcb::xproto::Gcontext,
conn: Arc<Connection>,
drawable: xcb::xproto::Drawable,
}
impl Context {
pub fn new(conn: &Arc<Connection>, d: &Drawable) -> Context {
let gc_id = conn.conn().generate_id();
let drawable = d.as_drawable();
xcb::create_gc(conn.conn(), gc_id, drawable, &[]);
Context {
gc_id,
conn: Arc::clone(conn),
drawable,
}
}
/// Copy an area from one drawable to another using the settings
/// defined in this context.
pub fn copy_area(
&self,
src: &Drawable,
src_x: i16,
src_y: i16,
dest: &Drawable,
dest_x: i16,
dest_y: i16,
width: u16,
height: u16,
) -> xcb::VoidCookie {
xcb::copy_area(
self.conn.conn(),
src.as_drawable(),
dest.as_drawable(),
self.gc_id,
src_x,
src_y,
dest_x,
dest_y,
width,
height,
)
}
/// Send image bytes and render them into the drawable that was used to
/// create this context.
pub fn put_image(&self, dest_x: i16, dest_y: i16, im: &dyn BitmapImage) -> xcb::VoidCookie {
let (width, height) = im.image_dimensions();
let pixel_slice =
unsafe { std::slice::from_raw_parts(im.pixel_data(), width * height * 4) };
xcb::put_image(
self.conn.conn(),
xcb::xproto::IMAGE_FORMAT_Z_PIXMAP as u8,
self.drawable,
self.gc_id,
width as u16,
height as u16,
dest_x,
dest_y,
0,
24,
pixel_slice,
)
}
}
impl Drop for Context {
fn drop(&mut self) {
xcb::free_gc(self.conn.conn(), self.gc_id);
}
}
/// Holder for a shared memory segment id.
/// We hold on to the id only until the server has attached
/// (or failed to attach) to the segment.
/// The id is removed on Drop.
struct ShmId {
id: libc::c_int,
}
/// Holder for a shared memory mapping.
/// The mapping is removed on Drop.
struct ShmData {
/// the base address of the mapping
data: *mut u8,
}
impl ShmId {
/// Create a new private shared memory segment of the specified size
fn new(size: usize) -> Fallible<ShmId> {
let id = unsafe { libc::shmget(libc::IPC_PRIVATE, size, libc::IPC_CREAT | 0o600) };
if id == -1 {
bail!(
"shmget failed for {} bytes: {:?}",
size,
std::io::Error::last_os_error()
);
}
Ok(ShmId { id })
}
/// Attach the segment to our address space
fn attach(&self) -> Fallible<ShmData> {
let data = unsafe { libc::shmat(self.id, std::ptr::null(), 0) };
if data as usize == !0 {
bail!(
"shmat failed: {:?} {}",
data,
std::io::Error::last_os_error()
);
}
Ok(ShmData {
data: data as *mut u8,
})
}
}
impl Drop for ShmId {
fn drop(&mut self) {
unsafe {
libc::shmctl(self.id, libc::IPC_RMID, std::ptr::null_mut());
}
}
}
impl Drop for ShmData {
fn drop(&mut self) {
unsafe {
libc::shmdt(self.data as *const _);
}
}
}
/// An image implementation backed by shared memory.
/// This also has an associated pixmap on the server side,
/// so we implement both BitmapImage and Drawable.
pub struct ShmImage {
data: ShmData,
seg_id: xcb::shm::Seg,
draw_id: u32,
conn: Arc<Connection>,
width: usize,
height: usize,
}
impl ShmImage {
pub fn new(
conn: &Arc<Connection>,
drawable: xcb::xproto::Drawable,
width: usize,
height: usize,
) -> Fallible<ShmImage> {
if !conn.shm_available {
bail!("SHM not available");
}
// Allocate and attach memory of the desired size
let id = ShmId::new(width * height * 4)?;
let data = id.attach()?;
// Tell the server to attach to it
let seg_id = conn.generate_id();
xcb::shm::attach_checked(conn, seg_id, id.id as u32, false).request_check()?;
// Now create a pixmap that references it
let draw_id = conn.generate_id();
xcb::shm::create_pixmap_checked(
conn,
draw_id,
drawable,
width as u16,
height as u16,
24,
seg_id,
0,
)
.request_check()?;
Ok(ShmImage {
data,
seg_id,
draw_id,
conn: Arc::clone(conn),
width,
height,
})
}
}
impl BitmapImage for ShmImage {
unsafe fn pixel_data(&self) -> *const u8 {
self.data.data as *const u8
}
unsafe fn pixel_data_mut(&mut self) -> *mut u8 {
self.data.data
}
fn image_dimensions(&self) -> (usize, usize) {
(self.width, self.height)
}
}
impl Drop for ShmImage {
fn drop(&mut self) {
xcb::free_pixmap(self.conn.conn(), self.draw_id);
xcb::shm::detach(self.conn.conn(), self.seg_id);
}
}
impl Drawable for ShmImage {
fn as_drawable(&self) -> xcb::xproto::Drawable {
self.draw_id
}
}

View File

@ -1,11 +1,12 @@
use crate::Window;
use failure::Fallible;
use mio::unix::EventedFd;
use mio::{Evented, Poll, PollOpt, Ready, Token};
use mio::{Evented, Events, Poll, PollOpt, Ready, Token};
use std::cell::RefCell;
use std::collections::HashMap;
use std::os::unix::io::AsRawFd;
use std::sync::Arc;
use std::time::{Duration, Instant};
use xcb_util::ffi::keysyms::{xcb_key_symbols_alloc, xcb_key_symbols_free, xcb_key_symbols_t};
pub struct Connection {
@ -21,6 +22,7 @@ pub struct Connection {
keysyms: *mut xcb_key_symbols_t,
pub(crate) windows: RefCell<HashMap<xcb::xproto::Window, Window>>,
should_terminate: RefCell<bool>,
pub(crate) shm_available: bool,
}
impl std::ops::Deref for Connection {
@ -118,17 +120,81 @@ impl Connection {
pub fn run_message_loop(&self) -> Fallible<()> {
self.conn.flush();
while let Some(event) = self.conn.wait_for_event() {
self.process_xcb_event(&event)?;
self.conn.flush();
if *self.should_terminate.borrow() {
break;
const TOK_XCB: usize = 0xffff_fffc;
let tok_xcb = Token(TOK_XCB);
let poll = Poll::new()?;
let mut events = Events::with_capacity(8);
poll.register(self, tok_xcb, Ready::readable(), PollOpt::level())?;
let paint_interval = Duration::from_millis(50);
let mut last_interval = Instant::now();
while !*self.should_terminate.borrow() {
let now = Instant::now();
let diff = now - last_interval;
let period = if diff >= paint_interval {
self.do_paint();
last_interval = now;
paint_interval
} else {
paint_interval - diff
};
// Process any events that might have accumulated in the local
// buffer (eg: due to a flush) before we potentially go to sleep.
// The locally queued events won't mark the fd as ready, so we
// could potentially sleep when there is work to be done if we
// relied solely on that.
self.process_queued_xcb()?;
match poll.poll(&mut events, Some(period)) {
Ok(_) => {
for event in &events {
let t = event.token();
if t == tok_xcb {
self.process_queued_xcb()?;
} else {
}
}
// self.process_sigchld();
}
Err(err) => {
failure::bail!("polling for events: {:?}", err);
}
}
}
Ok(())
}
fn process_queued_xcb(&self) -> Fallible<()> {
match self.conn.poll_for_event() {
None => match self.conn.has_error() {
Ok(_) => (),
Err(err) => {
failure::bail!("X11 connection is broken: {:?}", err);
}
},
Some(event) => {
if let Err(err) = self.process_xcb_event(&event) {
return Err(err);
}
}
}
self.conn.flush();
loop {
match self.conn.poll_for_queued_event() {
None => return Ok(()),
Some(event) => self.process_xcb_event(&event)?,
}
self.conn.flush();
}
}
fn process_xcb_event(&self, event: &xcb::GenericEvent) -> Fallible<()> {
if let Some(window_id) = window_id_from_event(event) {
self.process_window_event(window_id, event)?;
@ -192,6 +258,15 @@ impl Connection {
let keysyms = unsafe { xcb_key_symbols_alloc(conn.get_raw_conn()) };
// Take care here: xcb_shm_query_version can successfully return
// a nullptr, and a subsequent deref will segfault, so we need
// to check the ptr before accessing it!
/*
let reply = xcb::shm::query_version(&conn).get_reply()?;
let shm_available = !reply.ptr.is_null() && reply.shared_pixmaps();
*/
let shm_available = false;
let conn = Arc::new(Connection {
display,
conn,
@ -205,6 +280,7 @@ impl Connection {
atom_targets,
windows: RefCell::new(HashMap::new()),
should_terminate: RefCell::new(false),
shm_available,
});
CONN.with(|m| *m.borrow_mut() = Some(Arc::clone(&conn)));
@ -222,6 +298,15 @@ impl Connection {
pub fn atom_delete(&self) -> xcb::Atom {
self.atom_delete
}
/// Run through all of the windows and cause them to paint if they need it.
/// This happens ~50ms or so.
fn do_paint(&self) {
for window in self.windows.borrow().values() {
window.paint_if_needed().unwrap();
}
self.conn.flush();
}
}
impl Drop for Connection {

View File

@ -1,4 +1,6 @@
pub mod bitmap;
pub mod connection;
pub mod window;
pub use bitmap::*;
pub use connection::*;
pub use window::*;

View File

@ -1,137 +1,214 @@
use super::connection::*;
use crate::WindowCallbacks;
use super::*;
use crate::bitmaps::*;
use crate::{Color, Dimensions, Operator, PaintContext, WindowCallbacks};
use failure::Fallible;
use std::collections::VecDeque;
use std::convert::TryInto;
use std::sync::{Arc, Mutex};
struct WindowHolder {
window_id: xcb::xproto::Window,
conn: Arc<Connection>,
callbacks: Mutex<Box<WindowCallbacks>>,
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Rect {
x: u16,
y: u16,
width: u16,
height: u16,
}
impl Drop for WindowHolder {
fn value_in_range(value: u16, min: u16, max: u16) -> bool {
value >= min && value <= max
}
impl Rect {
fn right(&self) -> u16 {
self.x + self.width
}
fn bottom(&self) -> u16 {
self.y + self.height
}
fn enclosing_boundary_with(&self, other: &Rect) -> Self {
let left = self.x.min(other.x);
let right = self.right().max(other.right());
let top = self.y.min(other.y);
let bottom = self.bottom().max(other.bottom());
Self {
x: left,
y: top,
width: right - left,
height: bottom - top,
}
}
// https://stackoverflow.com/a/306379/149111
fn intersects_with(&self, other: &Rect) -> bool {
let x_overlaps = value_in_range(self.x, other.x, other.right())
|| value_in_range(other.x, self.x, self.right());
let y_overlaps = value_in_range(self.y, other.y, other.bottom())
|| value_in_range(other.y, self.x, self.bottom());
x_overlaps && y_overlaps
}
}
struct WindowInner {
window_id: xcb::xproto::Window,
conn: Arc<Connection>,
callbacks: Box<WindowCallbacks>,
window_context: Context,
width: u16,
height: u16,
expose: VecDeque<Rect>,
paint_all: bool,
}
impl Drop for WindowInner {
fn drop(&mut self) {
xcb::destroy_window(self.conn.conn(), self.window_id);
}
}
/// A Window!
#[derive(Clone)]
pub struct Window {
window: Arc<WindowHolder>,
struct X11GraphicsContext<'a> {
buffer: &'a mut Image,
}
impl Window {
/// Create a new window on the specified screen with the specified
/// dimensions
pub fn new_window(
_class_name: &str,
name: &str,
impl<'a> PaintContext for X11GraphicsContext<'a> {
fn clear_rect(
&mut self,
dest_x: isize,
dest_y: isize,
width: usize,
height: usize,
callbacks: Box<WindowCallbacks>,
) -> Fallible<Window> {
let conn = Connection::get().ok_or_else(|| {
failure::err_msg(
"new_window must be called on the gui thread after Connection::init has succeeded",
)
})?;
color: Color,
) {
self.buffer.clear_rect(dest_x, dest_y, width, height, color)
}
let window = {
let setup = conn.conn().get_setup();
let screen = setup
.roots()
.nth(conn.screen_num() as usize)
.ok_or_else(|| failure::err_msg("no screen?"))?;
fn clear(&mut self, color: Color) {
self.buffer.clear(color);
}
let window_id = conn.conn().generate_id();
fn get_dimensions(&self) -> Dimensions {
let (pixel_width, pixel_height) = self.buffer.image_dimensions();
Dimensions {
pixel_width,
pixel_height,
dpi: 96,
}
}
xcb::create_window_checked(
conn.conn(),
xcb::COPY_FROM_PARENT as u8,
window_id,
screen.root(),
// x, y
0,
0,
// width, height
width.try_into()?,
height.try_into()?,
// border width
0,
xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
screen.root_visual(),
&[(
xcb::CW_EVENT_MASK,
xcb::EVENT_MASK_EXPOSURE
| xcb::EVENT_MASK_KEY_PRESS
| xcb::EVENT_MASK_BUTTON_PRESS
| xcb::EVENT_MASK_BUTTON_RELEASE
| xcb::EVENT_MASK_POINTER_MOTION
| xcb::EVENT_MASK_BUTTON_MOTION
| xcb::EVENT_MASK_KEY_RELEASE
| xcb::EVENT_MASK_STRUCTURE_NOTIFY,
)],
)
.request_check()?;
Arc::new(WindowHolder {
window_id,
conn: Arc::clone(&conn),
callbacks: Mutex::new(callbacks),
})
fn draw_image_subset(
&mut self,
dest_x: isize,
dest_y: isize,
src_x: usize,
src_y: usize,
width: usize,
height: usize,
im: &dyn BitmapImage,
operator: Operator,
) {
self.buffer
.draw_image_subset(dest_x, dest_y, src_x, src_y, width, height, im, operator)
}
}
impl WindowInner {
fn paint(&mut self) -> Fallible<()> {
let window_dimensions = Rect {
x: 0,
y: 0,
width: self.width,
height: self.height,
};
xcb::change_property(
&*conn,
xcb::PROP_MODE_REPLACE as u8,
window.window_id,
conn.atom_protocols,
4,
32,
&[conn.atom_delete],
);
if self.paint_all {
self.paint_all = false;
self.expose.clear();
self.expose.push_back(window_dimensions);
}
let window = Window { window };
for rect in self.expose.drain(..) {
// Clip the rectangle to the current window size.
// It can be larger than the window size in the case where we are working
// through a series of resize exposures during a live resize, and we're
// now sized smaller then when we queued the exposure.
let rect = Rect {
x: rect.x,
y: rect.y,
width: rect.width.min(self.width),
height: rect.height.min(self.height),
};
conn.windows
.borrow_mut()
.insert(window.window.window_id, window.clone());
eprintln!("paint {:?}", rect);
window.set_title(name);
window.show();
let mut buffer = Image::new(self.width as usize, self.height as usize);
Ok(window)
let mut context = X11GraphicsContext {
buffer: &mut buffer,
};
self.callbacks.paint(&mut context);
if rect == window_dimensions {
self.window_context.put_image(0, 0, &buffer);
} else {
let mut im = Image::new(rect.width as usize, rect.height as usize);
im.draw_image_subset(
0,
0,
rect.x as usize,
rect.y as usize,
rect.width as usize,
rect.height as usize,
&buffer,
Operator::Source,
);
self.window_context
.put_image(rect.x as i16, rect.y as i16, &im);
}
}
Ok(())
}
/// Change the title for the window manager
pub fn set_title(&self, title: &str) {
xcb_util::icccm::set_wm_name(self.window.conn.conn(), self.window.window_id, title);
/// Add a region to the list of exposed/damaged/dirty regions.
/// Note that a window resize will likely invalidate the entire window.
/// If the new region intersects with the prior region, then we expand
/// it to encompass both. This avoids bloating the list with a series
/// of increasing rectangles when resizing larger or smaller.
fn expose(&mut self, x: u16, y: u16, width: u16, height: u16) {
let expose = Rect {
x,
y,
width,
height,
};
if let Some(prior) = self.expose.back_mut() {
if prior.intersects_with(&expose) {
*prior = prior.enclosing_boundary_with(&expose);
return;
}
}
self.expose.push_back(expose);
}
/// Display the window
pub fn show(&self) {
xcb::map_window(self.window.conn.conn(), self.window.window_id);
}
pub fn dispatch_event(&self, event: &xcb::GenericEvent) -> Fallible<()> {
fn dispatch_event(&mut self, event: &xcb::GenericEvent) -> Fallible<()> {
let r = event.response_type() & 0x7f;
match r {
xcb::EXPOSE => {
let expose: &xcb::ExposeEvent = unsafe { xcb::cast_event(event) };
eprintln!("EXPOSE");
//self.expose(expose.x(), expose.y(), expose.width(), expose.height())?;
self.expose(expose.x(), expose.y(), expose.width(), expose.height());
}
xcb::CONFIGURE_NOTIFY => {
let cfg: &xcb::ConfigureNotifyEvent = unsafe { xcb::cast_event(event) };
eprintln!("CONFIGURE_NOTIFY");
/*
let schedule = self.have_pending_resize.is_none();
self.have_pending_resize = Some((cfg.width(), cfg.height()));
if schedule {
self.host.with_window(|win| win.check_for_resize());
}
*/
self.width = cfg.width();
self.height = cfg.height();
}
xcb::KEY_PRESS => {
let key_press: &xcb::KeyPressEvent = unsafe { xcb::cast_event(event) };
@ -153,7 +230,7 @@ impl Window {
}
xcb::MOTION_NOTIFY => {
let motion: &xcb::MotionNotifyEvent = unsafe { xcb::cast_event(event) };
eprintln!("MOTION_NOTIFY");
//eprintln!("MOTION_NOTIFY");
/*
let event = MouseEvent {
@ -199,21 +276,17 @@ impl Window {
xcb::CLIENT_MESSAGE => {
let msg: &xcb::ClientMessageEvent = unsafe { xcb::cast_event(event) };
eprintln!("CLIENT_MESSAGE {:?}", msg.data().data32());
if msg.data().data32()[0] == self.window.conn.atom_delete() {
if msg.data().data32()[0] == self.conn.atom_delete() {
eprintln!("close requested");
if self.window.callbacks.lock().unwrap().can_close() {
self.close_window();
if self.callbacks.can_close() {
xcb::destroy_window(self.conn.conn(), self.window_id);
}
}
}
xcb::DESTROY_NOTIFY => {
eprintln!("DESTROY");
self.window.callbacks.lock().unwrap().destroy();
self.window
.conn
.windows
.borrow_mut()
.remove(&self.window.window_id);
self.callbacks.destroy();
self.conn.windows.borrow_mut().remove(&self.window_id);
}
_ => {
eprintln!("unhandled: {:x}", r);
@ -222,8 +295,126 @@ impl Window {
Ok(())
}
}
pub fn close_window(&self) {
xcb::destroy_window(self.window.conn.conn(), self.window.window_id);
/// A Window!
#[derive(Clone)]
pub struct Window {
window: Arc<Mutex<WindowInner>>,
}
impl Window {
/// Create a new window on the specified screen with the specified
/// dimensions
pub fn new_window(
_class_name: &str,
name: &str,
width: usize,
height: usize,
callbacks: Box<WindowCallbacks>,
) -> Fallible<Window> {
let conn = Connection::get().ok_or_else(|| {
failure::err_msg(
"new_window must be called on the gui thread after Connection::init has succeeded",
)
})?;
let window_id;
let window = {
let setup = conn.conn().get_setup();
let screen = setup
.roots()
.nth(conn.screen_num() as usize)
.ok_or_else(|| failure::err_msg("no screen?"))?;
window_id = conn.conn().generate_id();
xcb::create_window_checked(
conn.conn(),
xcb::COPY_FROM_PARENT as u8,
window_id,
screen.root(),
// x, y
0,
0,
// width, height
width.try_into()?,
height.try_into()?,
// border width
0,
xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
screen.root_visual(),
&[(
xcb::CW_EVENT_MASK,
xcb::EVENT_MASK_EXPOSURE
| xcb::EVENT_MASK_KEY_PRESS
| xcb::EVENT_MASK_BUTTON_PRESS
| xcb::EVENT_MASK_BUTTON_RELEASE
| xcb::EVENT_MASK_POINTER_MOTION
| xcb::EVENT_MASK_BUTTON_MOTION
| xcb::EVENT_MASK_KEY_RELEASE
| xcb::EVENT_MASK_STRUCTURE_NOTIFY,
)],
)
.request_check()?;
let window_context = Context::new(&conn, &window_id);
Arc::new(Mutex::new(WindowInner {
window_id,
conn: Arc::clone(&conn),
callbacks: callbacks,
window_context,
width: width.try_into()?,
height: height.try_into()?,
expose: VecDeque::new(),
paint_all: true,
}))
};
xcb::change_property(
&*conn,
xcb::PROP_MODE_REPLACE as u8,
window_id,
conn.atom_protocols,
4,
32,
&[conn.atom_delete],
);
let window = Window { window };
conn.windows.borrow_mut().insert(window_id, window.clone());
window.set_title(name);
window.show();
Ok(window)
}
/// Change the title for the window manager
pub fn set_title(&self, title: &str) {
let window = self.window.lock().unwrap();
xcb_util::icccm::set_wm_name(window.conn.conn(), window.window_id, title);
}
/// Display the window
pub fn show(&self) {
let window = self.window.lock().unwrap();
xcb::map_window(window.conn.conn(), window.window_id);
}
pub fn dispatch_event(&self, event: &xcb::GenericEvent) -> Fallible<()> {
self.window.lock().unwrap().dispatch_event(event)
}
pub(crate) fn paint_if_needed(&self) -> Fallible<()> {
self.window.lock().unwrap().paint()
}
}
impl Drawable for Window {
fn as_drawable(&self) -> xcb::xproto::Drawable {
self.window.lock().unwrap().window_id
}
}