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:
parent
1de4165152
commit
56dc24a1ee
1
get-deps
1
get-deps
@ -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 \
|
||||
|
@ -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"
|
||||
|
@ -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
233
window/src/os/x11/bitmap.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -1,4 +1,6 @@
|
||||
pub mod bitmap;
|
||||
pub mod connection;
|
||||
pub mod window;
|
||||
pub use bitmap::*;
|
||||
pub use connection::*;
|
||||
pub use window::*;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user