1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-27 12:23:46 +03:00

window: initial wayland render support

This commit is contained in:
Wez Furlong 2019-11-27 16:39:00 -08:00
parent a159c28be2
commit 51ada155df
9 changed files with 694 additions and 68 deletions

View File

@ -27,6 +27,7 @@ glium = { version = "0.26.0-alpha3", optional=true, default-features = false}
[features]
async_await = []
opengl = ["cgl", "glium", "gl_generator", "libloading"]
wayland = ["smithay-client-toolkit", "memmap"]
[target."cfg(windows)".dependencies]
winapi = { version = "0.3", features = [
@ -48,6 +49,8 @@ xkbcommon = { version = "0.4", features = ["x11"] }
mio = "0.6"
mio-extras = "2.0"
libc = "0.2"
smithay-client-toolkit = {version = "0.6", optional = true}
memmap = {version="0.7", optional=true}
[target.'cfg(target_os="macos")'.dependencies]
cocoa = "0.20"

View File

@ -7,6 +7,7 @@ pub mod input;
pub mod os;
mod spawn;
mod tasks;
mod timerlist;
#[cfg(all(
not(target_os = "macos"),

View File

@ -3,9 +3,12 @@ pub mod windows;
#[cfg(windows)]
pub use windows::*;
#[cfg(all(unix, not(target_os = "macos")))]
pub mod wayland;
pub mod x11;
#[cfg(all(unix, not(target_os = "macos")))]
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
pub use self::wayland::*;
#[cfg(all(unix, not(feature = "wayland"), not(target_os = "macos")))]
pub use self::x11::*;
#[cfg(target_os = "macos")]

View File

@ -0,0 +1,229 @@
#![allow(dead_code)]
use super::window::*;
use crate::connection::ConnectionOps;
use crate::spawn::*;
use crate::tasks::{Task, Tasks};
use crate::timerlist::{TimerEntry, TimerList};
use failure::Fallible;
use mio::unix::EventedFd;
use mio::{Evented, Events, Poll, PollOpt, Ready, Token};
use promise::BasicExecutor;
use smithay_client_toolkit as toolkit;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::atomic::AtomicUsize;
use std::time::{Duration, Instant};
use toolkit::reexports::client::{Display, EventQueue};
use toolkit::Environment;
pub struct Connection {
display: RefCell<Display>,
event_q: RefCell<EventQueue>,
pub(crate) environment: RefCell<Environment>,
should_terminate: RefCell<bool>,
timers: RefCell<TimerList>,
tasks: Tasks,
pub(crate) next_window_id: AtomicUsize,
pub(crate) windows: RefCell<HashMap<usize, Rc<RefCell<WindowInner>>>>,
}
impl Evented for Connection {
fn register(
&self,
poll: &Poll,
token: Token,
interest: Ready,
opts: PollOpt,
) -> std::io::Result<()> {
EventedFd(&self.event_q.borrow().get_connection_fd()).register(poll, token, interest, opts)
}
fn reregister(
&self,
poll: &Poll,
token: Token,
interest: Ready,
opts: PollOpt,
) -> std::io::Result<()> {
EventedFd(&self.event_q.borrow().get_connection_fd())
.reregister(poll, token, interest, opts)
}
fn deregister(&self, poll: &Poll) -> std::io::Result<()> {
EventedFd(&self.event_q.borrow().get_connection_fd()).deregister(poll)
}
}
impl Connection {
pub fn create_new() -> Fallible<Self> {
let (display, mut event_q) = Display::connect_to_env()?;
let environment = Environment::from_display(&*display, &mut event_q)?;
Ok(Self {
display: RefCell::new(display),
event_q: RefCell::new(event_q),
environment: RefCell::new(environment),
should_terminate: RefCell::new(false),
timers: RefCell::new(TimerList::new()),
tasks: Default::default(),
next_window_id: AtomicUsize::new(1),
windows: RefCell::new(HashMap::new()),
})
}
pub(crate) fn next_window_id(&self) -> usize {
self.next_window_id
.fetch_add(1, ::std::sync::atomic::Ordering::Relaxed)
}
fn flush(&self) -> Fallible<()> {
if let Err(e) = self.display.borrow_mut().flush() {
if e.kind() != ::std::io::ErrorKind::WouldBlock {
failure::bail!("Error while flushing display: {}", e);
}
}
Ok(())
}
fn do_paint(&self) {}
fn process_queued_events(&self) -> Fallible<()> {
{
let mut event_q = self.event_q.borrow_mut();
if let Some(guard) = event_q.prepare_read() {
if let Err(e) = guard.read_events() {
if e.kind() != ::std::io::ErrorKind::WouldBlock {
failure::bail!("Error while reading events: {}", e);
}
}
}
event_q.dispatch_pending()?;
}
self.flush()?;
Ok(())
}
pub(crate) fn window_by_id(&self, window_id: usize) -> Option<Rc<RefCell<WindowInner>>> {
self.windows.borrow().get(&window_id).map(Rc::clone)
}
pub(crate) fn with_window_inner<R, F: FnMut(&mut WindowInner) -> Fallible<R> + Send + 'static>(
window: usize,
mut f: F,
) -> promise::Future<R>
where
R: Send + 'static,
{
let mut prom = promise::Promise::new();
let future = prom.get_future().unwrap();
SpawnQueueExecutor {}.execute(Box::new(move || {
if let Some(handle) = Connection::get().unwrap().window_by_id(window) {
let mut inner = handle.borrow_mut();
prom.result(f(&mut inner));
}
}));
future
}
}
impl ConnectionOps for Connection {
fn spawn_task<F: std::future::Future<Output = ()> + 'static>(&self, future: F) {
let id = self.tasks.add_task(Task(Box::pin(future)));
Self::wake_task_by_id(id);
}
fn wake_task_by_id(slot: usize) {
SpawnQueueExecutor {}.execute(Box::new(move || {
let conn = Connection::get().unwrap();
conn.tasks.poll_by_slot(slot);
}));
}
fn terminate_message_loop(&self) {
*self.should_terminate.borrow_mut() = true;
}
fn run_message_loop(&self) -> Fallible<()> {
println!("run_message_loop:flush");
self.flush()?;
const TOK_WAYLAND: usize = 0xffff_fffc;
const TOK_SPAWN: usize = 0xffff_fffd;
let tok_wayland = Token(TOK_WAYLAND);
let tok_spawn = Token(TOK_SPAWN);
let poll = Poll::new()?;
let mut events = Events::with_capacity(8);
poll.register(self, tok_wayland, Ready::readable(), PollOpt::level())?;
poll.register(
&*SPAWN_QUEUE,
tok_spawn,
Ready::readable(),
PollOpt::level(),
)?;
let paint_interval = Duration::from_millis(25);
let mut last_interval = Instant::now();
while !*self.should_terminate.borrow() {
self.timers.borrow_mut().run_ready();
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_events()?;
// Check the spawn queue before we try to sleep; there may
// be work pending and we don't guarantee that there is a
// 1:1 wakeup to queued function, so we need to be assertive
// in order to avoid missing wakeups
let period = if SPAWN_QUEUE.run() {
// if we processed one, we don't want to sleep because
// there may be others to deal with
Duration::new(0, 0)
} else {
self.timers
.borrow()
.time_until_due(Instant::now())
.map(|duration| duration.min(period))
.unwrap_or(period)
};
match poll.poll(&mut events, Some(period)) {
Ok(_) => {
// We process both event sources unconditionally
// in the loop above anyway; we're just using
// this to get woken up.
}
Err(err) => {
failure::bail!("polling for events: {:?}", err);
}
}
}
Ok(())
}
fn schedule_timer<F: FnMut() + 'static>(&self, interval: std::time::Duration, callback: F) {
self.timers.borrow_mut().insert(TimerEntry {
callback: Box::new(callback),
due: Instant::now(),
interval,
});
}
}

View File

@ -0,0 +1,6 @@
#![cfg(all(unix, feature="wayland", not(target_os = "macos")))]
pub mod connection;
pub mod window;
pub use connection::*;
pub use window::*;

View File

@ -0,0 +1,380 @@
use crate::bitmaps::BitmapImage;
use crate::color::Color;
use crate::connection::ConnectionOps;
use crate::{
Connection, Dimensions, MouseCursor, Operator, PaintContext, Point, Rect, ScreenPoint,
WindowCallbacks, WindowOps, WindowOpsMut,
};
use failure::Fallible;
use promise::Future;
use smithay_client_toolkit as toolkit;
use std::any::Any;
use std::cell::RefCell;
use std::rc::Rc;
use toolkit::reexports::client::protocol::wl_seat::WlSeat;
use toolkit::reexports::client::protocol::wl_surface::WlSurface;
use toolkit::reexports::client::NewProxy;
use toolkit::utils::DoubleMemPool;
use toolkit::window::Event;
struct MyTheme;
use toolkit::window::ButtonState;
impl toolkit::window::Theme for MyTheme {
fn get_primary_color(&self, _active: bool) -> [u8; 4] {
[0xff, 0x80, 0x80, 0x80]
}
fn get_secondary_color(&self, _active: bool) -> [u8; 4] {
[0xff, 0x60, 0x60, 0x60]
}
fn get_close_button_color(&self, _status: ButtonState) -> [u8; 4] {
[0xff, 0xff, 0xff, 0xff]
}
fn get_maximize_button_color(&self, _status: ButtonState) -> [u8; 4] {
[0xff, 0xff, 0xff, 0xff]
}
fn get_minimize_button_color(&self, _status: ButtonState) -> [u8; 4] {
[0xff, 0xff, 0xff, 0xff]
}
}
pub struct WindowInner {
window_id: usize,
callbacks: Box<dyn WindowCallbacks>,
surface: WlSurface,
seat: WlSeat,
window: toolkit::window::Window<toolkit::window::ConceptFrame>,
pool: DoubleMemPool,
dimensions: (u32, u32),
}
pub struct Window(usize);
impl Window {
pub fn new_window(
class_name: &str,
name: &str,
width: usize,
height: usize,
callbacks: Box<dyn 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 = conn.next_window_id();
let surface = conn
.environment
.borrow_mut()
.compositor
.create_surface(NewProxy::implement_dummy)
.map_err(|_| failure::err_msg("new_window: failed to create a surface"))?;
let dimensions = (width as u32, height as u32);
let mut window = toolkit::window::Window::<toolkit::window::ConceptFrame>::init_from_env(
&*conn.environment.borrow(),
surface.clone(),
dimensions,
move |evt| {
Connection::with_window_inner(window_id, move |inner| {
inner.handle_event(evt.clone());
Ok(())
});
},
)
.map_err(|e| failure::format_err!("Failed to create window: {}", e))?;
window.set_app_id(class_name.to_string());
window.set_decorate(true);
window.set_resizable(true);
window.set_theme(MyTheme {});
let pool = DoubleMemPool::new(&conn.environment.borrow().shm, || {})?;
let seat = conn
.environment
.borrow()
.manager
.instantiate_range(1, 6, NewProxy::implement_dummy)
.map_err(|_| failure::format_err!("Failed to create seat"))?;
window.new_seat(&seat);
let inner = Rc::new(RefCell::new(WindowInner {
window_id,
callbacks,
surface,
seat,
window,
pool,
dimensions,
}));
let window_handle = Window(window_id);
conn.windows.borrow_mut().insert(window_id, inner.clone());
inner.borrow_mut().callbacks.created(&window_handle);
Ok(window_handle)
}
}
impl WindowInner {
fn handle_event(&mut self, evt: Event) {
match evt {
Event::Close => {
if self.callbacks.can_close() {
println!("FIXME: I should destroy all refs to the window now");
}
}
Event::Refresh => {
self.window.refresh();
self.window.surface().commit();
}
Event::Configure { new_size, states } => {
if let Some((w, h)) = new_size {
self.window.resize(w, h);
self.dimensions = (w, h);
}
self.window.refresh();
self.do_paint().unwrap();
}
}
}
fn do_paint(&mut self) -> Fallible<()> {
let pool = match self.pool.pool() {
Some(pool) => pool,
None => {
// Buffer still in use by server; retry later
return Ok(());
}
};
pool.resize((4 * self.dimensions.0 * self.dimensions.1) as usize)?;
let mut context = MmapImage {
mmap: pool.mmap(),
dimensions: (self.dimensions.0 as usize, self.dimensions.1 as usize),
};
self.callbacks.paint(&mut context);
let buffer = pool.buffer(
0,
self.dimensions.0 as i32,
self.dimensions.1 as i32,
4 * self.dimensions.0 as i32,
toolkit::reexports::client::protocol::wl_shm::Format::Argb8888,
);
self.surface.attach(Some(&buffer), 0, 0);
self.surface.commit();
self.window.refresh();
Ok(())
}
}
struct MmapImage<'a> {
mmap: &'a mut memmap::MmapMut,
dimensions: (usize, usize),
}
impl<'a> BitmapImage for MmapImage<'a> {
unsafe fn pixel_data(&self) -> *const u8 {
self.mmap.as_ptr()
}
unsafe fn pixel_data_mut(&mut self) -> *mut u8 {
self.mmap.as_mut_ptr()
}
fn image_dimensions(&self) -> (usize, usize) {
self.dimensions
}
}
impl<'a> PaintContext for MmapImage<'a> {
fn clear_rect(&mut self, rect: Rect, color: Color) {
BitmapImage::clear_rect(self, rect, color)
}
fn clear(&mut self, color: Color) {
BitmapImage::clear(self, color);
}
fn get_dimensions(&self) -> Dimensions {
let (pixel_width, pixel_height) = self.image_dimensions();
Dimensions {
pixel_width,
pixel_height,
dpi: 96,
}
}
fn draw_image(
&mut self,
dest_top_left: Point,
src_rect: Option<Rect>,
im: &dyn BitmapImage,
operator: Operator,
) {
BitmapImage::draw_image(self, dest_top_left, src_rect, im, operator)
}
fn draw_line(&mut self, start: Point, end: Point, color: Color, operator: Operator) {
BitmapImage::draw_line(self, start, end, color, operator);
}
}
impl WindowOps for Window {
fn close(&self) -> Future<()> {
Connection::with_window_inner(self.0, |inner| {
inner.close();
Ok(())
})
}
fn hide(&self) -> Future<()> {
Connection::with_window_inner(self.0, |inner| {
inner.hide();
Ok(())
})
}
fn show(&self) -> Future<()> {
Connection::with_window_inner(self.0, |inner| {
inner.show();
Ok(())
})
}
fn set_cursor(&self, cursor: Option<MouseCursor>) -> Future<()> {
Connection::with_window_inner(self.0, move |inner| {
let _ = inner.set_cursor(cursor);
Ok(())
})
}
fn invalidate(&self) -> Future<()> {
Connection::with_window_inner(self.0, |inner| {
inner.invalidate();
Ok(())
})
}
fn set_title(&self, title: &str) -> Future<()> {
let title = title.to_owned();
Connection::with_window_inner(self.0, move |inner| {
inner.set_title(&title);
Ok(())
})
}
fn set_inner_size(&self, width: usize, height: usize) -> Future<()> {
Connection::with_window_inner(self.0, move |inner| {
inner.set_inner_size(width, height);
Ok(())
})
}
fn set_window_position(&self, coords: ScreenPoint) -> Future<()> {
Connection::with_window_inner(self.0, move |inner| {
inner.set_window_position(coords);
Ok(())
})
}
fn apply<R, F: Send + 'static + Fn(&mut dyn Any, &dyn WindowOps) -> Fallible<R>>(
&self,
func: F,
) -> promise::Future<R>
where
Self: Sized,
R: Send + 'static,
{
Connection::with_window_inner(self.0, move |inner| {
let window = Window(inner.window_id);
func(inner.callbacks.as_any(), &window)
})
}
#[cfg(feature = "opengl")]
fn enable_opengl<
R,
F: Send
+ 'static
+ Fn(
&mut dyn Any,
&dyn WindowOps,
failure::Fallible<std::rc::Rc<glium::backend::Context>>,
) -> failure::Fallible<R>,
>(
&self,
func: F,
) -> promise::Future<R>
where
Self: Sized,
R: Send + 'static,
{
Connection::with_window_inner(self.0, move |inner| {
let window = Window(inner.window_id);
let gl_state = crate::egl::GlState::create(
Some(inner.conn.display as *const _),
inner.window_id as *mut _,
)
.map(Rc::new)
.and_then(|state| unsafe {
Ok(glium::backend::Context::new(
Rc::clone(&state),
true,
if cfg!(debug_assertions) {
glium::debug::DebugCallbackBehavior::DebugMessageOnError
} else {
glium::debug::DebugCallbackBehavior::Ignore
},
)?)
});
inner.gl_state = gl_state.as_ref().map(Rc::clone).ok();
func(inner.callbacks.as_any(), &window, gl_state)
})
}
}
impl WindowOpsMut for WindowInner {
fn close(&mut self) {}
fn hide(&mut self) {}
fn show(&mut self) {
let conn = Connection::get().unwrap();
if !conn.environment.borrow().shell.needs_configure() {
self.do_paint().unwrap();
} else {
self.window.refresh();
}
}
fn set_cursor(&mut self, cursor: Option<MouseCursor>) {}
fn invalidate(&mut self) {
self.window
.surface()
.damage(0, 0, self.dimensions.0 as i32, self.dimensions.1 as i32);
}
fn set_inner_size(&self, width: usize, height: usize) {}
fn set_window_position(&self, coords: ScreenPoint) {}
/// Change the title for the window manager
fn set_title(&mut self, title: &str) {
self.window.set_title(title.to_string());
}
}

View File

@ -2,83 +2,19 @@ use super::keyboard::Keyboard;
use crate::connection::ConnectionOps;
use crate::spawn::*;
use crate::tasks::{Task, Tasks};
use crate::timerlist::{TimerEntry, TimerList};
use crate::WindowInner;
use failure::Fallible;
use mio::unix::EventedFd;
use mio::{Evented, Events, Poll, PollOpt, Ready, Token};
use promise::BasicExecutor;
use std::cell::RefCell;
use std::cmp::Ordering;
use std::collections::{HashMap, VecDeque};
use std::collections::HashMap;
use std::os::unix::io::AsRawFd;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use xcb_util::ffi::keysyms::{xcb_key_symbols_alloc, xcb_key_symbols_free, xcb_key_symbols_t};
struct TimerEntry {
callback: Box<dyn FnMut()>,
due: Instant,
interval: Duration,
}
#[derive(Default)]
struct TimerList {
timers: VecDeque<TimerEntry>,
}
impl TimerList {
pub fn new() -> Self {
Default::default()
}
fn find_index_after(&self, due: &Instant) -> usize {
for (idx, entry) in self.timers.iter().enumerate() {
if entry.due.cmp(due) == Ordering::Greater {
return idx;
}
}
self.timers.len()
}
pub fn insert(&mut self, mut entry: TimerEntry) {
entry.due = Instant::now() + entry.interval;
let idx = self.find_index_after(&entry.due);
self.timers.insert(idx, entry);
}
pub fn time_until_due(&self, now: Instant) -> Option<Duration> {
self.timers.front().map(|entry| {
if entry.due <= now {
Duration::from_secs(0)
} else {
entry.due - now
}
})
}
fn first_is_ready(&self, now: Instant) -> bool {
if let Some(first) = self.timers.front() {
first.due <= now
} else {
false
}
}
pub fn run_ready(&mut self) {
let now = Instant::now();
let mut requeue = vec![];
while self.first_is_ready(now) {
let mut first = self.timers.pop_front().expect("first_is_ready");
(first.callback)();
requeue.push(first);
}
for entry in requeue.into_iter() {
self.insert(entry);
}
}
}
pub struct Connection {
pub display: *mut x11::xlib::Display,
conn: xcb_util::ewmh::Connection,

View File

@ -1,3 +1,4 @@
#![cfg(all(unix, not(feature="wayland"), not(target_os = "macos")))]
pub mod bitmap;
pub mod connection;
pub mod keyboard;

67
window/src/timerlist.rs Normal file
View File

@ -0,0 +1,67 @@
use std::cmp::Ordering;
use std::collections::VecDeque;
use std::time::{Duration, Instant};
pub struct TimerEntry {
pub callback: Box<dyn FnMut()>,
pub due: Instant,
pub interval: Duration,
}
#[derive(Default)]
pub struct TimerList {
timers: VecDeque<TimerEntry>,
}
impl TimerList {
pub fn new() -> Self {
Default::default()
}
fn find_index_after(&self, due: &Instant) -> usize {
for (idx, entry) in self.timers.iter().enumerate() {
if entry.due.cmp(due) == Ordering::Greater {
return idx;
}
}
self.timers.len()
}
pub fn insert(&mut self, mut entry: TimerEntry) {
entry.due = Instant::now() + entry.interval;
let idx = self.find_index_after(&entry.due);
self.timers.insert(idx, entry);
}
pub fn time_until_due(&self, now: Instant) -> Option<Duration> {
self.timers.front().map(|entry| {
if entry.due <= now {
Duration::from_secs(0)
} else {
entry.due - now
}
})
}
fn first_is_ready(&self, now: Instant) -> bool {
if let Some(first) = self.timers.front() {
first.due <= now
} else {
false
}
}
pub fn run_ready(&mut self) {
let now = Instant::now();
let mut requeue = vec![];
while self.first_is_ready(now) {
let mut first = self.timers.pop_front().expect("first_is_ready");
(first.callback)();
requeue.push(first);
}
for entry in requeue.into_iter() {
self.insert(entry);
}
}
}