1
0
Fork 0
mirror of https://github.com/alacritty/alacritty.git synced 2024-11-25 14:05:41 -05:00

Display manages window, renderer, rasterizer

This is part of an ongoing decoupling effort across the codebase and
tidying effort in main.rs. Everything to do with showing the window with
a grid of characters is now managed by the `Display` type. It owns the
window, the font rasterizer, and the renderer. The only info needed from
it are dimensions of characters and the window itself for sizing the
terminal properly. Additionally, the I/O loop has access to wake it up
when new data arrives.
This commit is contained in:
Joe Wilm 2016-12-10 22:44:13 -08:00
parent bbd8ddbfc0
commit ed0b1cfff0
9 changed files with 418 additions and 277 deletions

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::env; use std::env;
use alacritty::index::{Line, Column}; use index::{Line, Column};
/// Options specified on the command line /// Options specified on the command line
pub struct Options { pub struct Options {

246
src/display.rs Normal file
View file

@ -0,0 +1,246 @@
// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! The display subsystem including window management, font rasterization, and
//! GPU drawing.
use std::sync::mpsc;
use parking_lot::{MutexGuard};
use font;
use Rgb;
use ansi::Color;
use cli;
use config::Config;
use meter::Meter;
use renderer::{GlyphCache, QuadRenderer};
use term::{Term, SizeInfo};
use window::{self, Size, Pixels, Window, SetInnerSize};
/// The display wraps a window, font rasterizer, and GPU renderer
pub struct Display<F> {
window: Window,
renderer: QuadRenderer,
glyph_cache: GlyphCache,
render_timer: bool,
rx: mpsc::Receiver<(u32, u32)>,
tx: mpsc::Sender<(u32, u32)>,
meter: Meter,
resize_callback: Option<F>,
size_info: SizeInfo,
}
/// Can wakeup the render loop from other threads
pub struct Notifier(window::Proxy);
impl Notifier {
pub fn notify(&self) {
self.0.wakeup_event_loop();
}
}
impl<F> Display<F>
where F: Fn(&SizeInfo)
{
pub fn notifier(&self) -> Notifier {
Notifier(self.window.create_window_proxy())
}
pub fn update_config(&mut self, config: &Config) {
self.renderer.update_config(config);
self.render_timer = config.render_timer();
}
/// Provide a callback to be invoked then the display changes size.
pub fn set_resize_callback(&mut self, callback: F) {
self.resize_callback = Some(callback);
}
/// Get size info about the display
pub fn size(&self) -> &SizeInfo {
&self.size_info
}
pub fn new(
config: &Config,
options: &cli::Options,
) -> Result<Display<F>, window::Error> {
// Extract some properties from config
let font = config.font();
let dpi = config.dpi();
let render_timer = config.render_timer();
// Create the window where Alacritty will be displayed
let mut window = match Window::new() {
Ok(window) => window,
Err(err) => die!("{}", err)
};
// get window properties for initializing the other subsytems
let size = window.inner_size_pixels().unwrap();
let dpr = window.hidpi_factor();
println!("device_pixel_ratio: {}", dpr);
let rasterizer = font::Rasterizer::new(dpi.x(), dpi.y(), dpr);
// Create renderer
let mut renderer = QuadRenderer::new(&config, size);
// Initialize glyph cache
let glyph_cache = {
println!("Initializing glyph cache");
let init_start = ::std::time::Instant::now();
let cache = renderer.with_loader(|mut api| {
GlyphCache::new(rasterizer, &config, &mut api)
});
let stop = init_start.elapsed();
let stop_f = stop.as_secs() as f64 + stop.subsec_nanos() as f64 / 1_000_000_000f64;
println!("Finished initializing glyph cache in {}", stop_f);
cache
};
// Need font metrics to resize the window properly. This suggests to me the
// font metrics should be computed before creating the window in the first
// place so that a resize is not needed.
let metrics = glyph_cache.font_metrics();
let cell_width = (metrics.average_advance + font.offset().x() as f64) as u32;
let cell_height = (metrics.line_height + font.offset().y() as f64) as u32;
// Resize window to specified dimensions
let width = cell_width * options.columns_u32() + 4;
let height = cell_height * options.lines_u32() + 4;
let size = Size { width: Pixels(width), height: Pixels(height) };
println!("set_inner_size: {}", size);
window.set_inner_size(size);
renderer.resize(*size.width as _, *size.height as _);
println!("Cell Size: ({} x {})", cell_width, cell_height);
let size_info = SizeInfo {
width: *size.width as f32,
height: *size.height as f32,
cell_width: cell_width as f32,
cell_height: cell_height as f32
};
// Channel for resize events
//
// macOS has a callback for getting resize events, the channel is used
// to queue resize events until the next draw call. Unfortunately, it
// seems that the event loop is blocked until the window is done
// resizing. If any drawing were to happen during a resize, it would
// need to be in the callback.
let (tx, rx) = mpsc::channel();
let mut display = Display {
window: window,
renderer: renderer,
glyph_cache: glyph_cache,
render_timer: render_timer,
tx: tx,
rx: rx,
meter: Meter::new(),
resize_callback: None,
size_info: size_info,
};
let resize_tx = display.resize_channel();
let proxy = display.window.create_window_proxy();
display.window.set_resize_callback(move |width, height| {
let _ = resize_tx.send((width, height));
proxy.wakeup_event_loop();
});
Ok(display)
}
#[inline]
pub fn resize_channel(&self) -> mpsc::Sender<(u32, u32)> {
self.tx.clone()
}
pub fn window(&self) -> &Window {
&self.window
}
/// Draw the screen
///
/// A reference to Term whose state is being drawn must be provided.
///
/// This call may block if vsync is enabled
pub fn draw(&mut self, mut terminal: MutexGuard<Term>, config: &Config) {
// This is a hack since sometimes we get stuck waiting for events
// in the main loop otherwise.
//
// TODO figure out why this is necessary
self.window.clear_wakeup_flag();
// Clear dirty flag
terminal.dirty = false;
// Resize events new_size and are handled outside the poll_events
// iterator. This has the effect of coalescing multiple resize
// events into one.
let mut new_size = None;
// Check for any out-of-band resize events (mac only)
while let Ok(sz) = self.rx.try_recv() {
new_size = Some(sz);
}
// Receive any resize events; only call gl::Viewport on last
// available
if let Some((w, h)) = new_size.take() {
terminal.resize(w as f32, h as f32);
let size = terminal.size_info();
self.resize_callback.as_ref()
.map(|func| func(&size));
self.renderer.resize(w as i32, h as i32);
}
{
let glyph_cache = &mut self.glyph_cache;
// Draw grid
{
let _sampler = self.meter.sampler();
let size_info = terminal.size_info().clone();
self.renderer.with_api(config, &size_info, |mut api| {
api.clear();
// Draw the grid
api.render_cells(terminal.renderable_cells(), glyph_cache);
});
}
// Draw render timer
if self.render_timer {
let timing = format!("{:.3} usec", self.meter.average());
let color = Color::Spec(Rgb { r: 0xd5, g: 0x4e, b: 0x53 });
self.renderer.with_api(config, terminal.size_info(), |mut api| {
api.render_string(&timing[..], glyph_cache, &color);
});
}
}
// Unlock the terminal mutex
drop(terminal);
self.window.swap_buffers().unwrap();
}
}

View file

@ -5,6 +5,7 @@ use std::sync::{Arc, mpsc};
use serde_json as json; use serde_json as json;
use glutin; use glutin;
use window::Window; use window::Window;
use input; use input;

View file

@ -10,14 +10,11 @@ use mio::{self, Events, PollOpt, Ready};
use mio::unix::EventedFd; use mio::unix::EventedFd;
use ansi; use ansi;
use display;
use term::Term; use term::Term;
use util::thread; use util::thread;
use sync::FairMutex; use sync::FairMutex;
use window;
use super::Flag;
/// Messages that may be sent to the `EventLoop` /// Messages that may be sent to the `EventLoop`
#[derive(Debug)] #[derive(Debug)]
pub enum Msg { pub enum Msg {
@ -35,8 +32,7 @@ pub struct EventLoop<Io> {
rx: mio::channel::Receiver<Msg>, rx: mio::channel::Receiver<Msg>,
tx: mio::channel::Sender<Msg>, tx: mio::channel::Sender<Msg>,
terminal: Arc<FairMutex<Term>>, terminal: Arc<FairMutex<Term>>,
proxy: window::Proxy, display: display::Notifier,
signal_flag: Flag,
ref_test: bool, ref_test: bool,
} }
@ -131,8 +127,7 @@ impl<Io> EventLoop<Io>
/// Create a new event loop /// Create a new event loop
pub fn new( pub fn new(
terminal: Arc<FairMutex<Term>>, terminal: Arc<FairMutex<Term>>,
proxy: window::Proxy, display: display::Notifier,
signal_flag: Flag,
pty: Io, pty: Io,
ref_test: bool, ref_test: bool,
) -> EventLoop<Io> { ) -> EventLoop<Io> {
@ -143,8 +138,7 @@ impl<Io> EventLoop<Io>
tx: tx, tx: tx,
rx: rx, rx: rx,
terminal: terminal, terminal: terminal,
proxy: proxy, display: display,
signal_flag: signal_flag,
ref_test: ref_test, ref_test: ref_test,
} }
} }
@ -196,14 +190,7 @@ impl<Io> EventLoop<Io>
terminal.dirty = true; terminal.dirty = true;
// Only wake up the event loop if it hasn't already been self.display.notify();
// signaled. This is a really important optimization because
// waking up the event loop redundantly burns *a lot* of
// cycles.
if !self.signal_flag.get() {
self.proxy.wakeup_event_loop();
self.signal_flag.set(true);
}
}, },
Err(err) => { Err(err) => {
match err.kind() { match err.kind() {

View file

@ -49,7 +49,9 @@ extern crate bitflags;
pub mod macros; pub mod macros;
pub mod ansi; pub mod ansi;
pub mod cli;
pub mod config; pub mod config;
pub mod display;
pub mod event; pub mod event;
pub mod event_loop; pub mod event_loop;
pub mod grid; pub mod grid;
@ -63,9 +65,6 @@ pub mod tty;
pub mod util; pub mod util;
pub mod window; pub mod window;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
pub use grid::Grid; pub use grid::Grid;
pub use term::Term; pub use term::Term;
@ -80,21 +79,3 @@ pub mod gl {
#![allow(non_upper_case_globals)] #![allow(non_upper_case_globals)]
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
} }
#[derive(Clone)]
pub struct Flag(pub Arc<AtomicBool>);
impl Flag {
pub fn new(initial_value: bool) -> Flag {
Flag(Arc::new(AtomicBool::new(initial_value)))
}
#[inline]
pub fn get(&self) -> bool {
self.0.load(Ordering::Acquire)
}
#[inline]
pub fn set(&self, value: bool) {
self.0.store(value, Ordering::Release)
}
}

View file

@ -42,25 +42,20 @@ extern crate vte;
#[macro_use] #[macro_use]
extern crate bitflags; extern crate bitflags;
use std::error::Error;
use std::sync::{mpsc, Arc}; use std::sync::{mpsc, Arc};
use std::sync::atomic::Ordering; use std::rc::Rc;
use parking_lot::{MutexGuard}; use alacritty::cli;
use alacritty::Flag;
use alacritty::Rgb;
use alacritty::config::{self, Config}; use alacritty::config::{self, Config};
use alacritty::display::{self, Display};
use alacritty::event; use alacritty::event;
use alacritty::event_loop::EventLoop; use alacritty::event_loop::EventLoop;
use alacritty::input; use alacritty::input;
use alacritty::meter::Meter;
use alacritty::renderer::{QuadRenderer, GlyphCache};
use alacritty::sync::FairMutex; use alacritty::sync::FairMutex;
use alacritty::term::{self, Term}; use alacritty::term::{Term};
use alacritty::tty::{self, Pty, process_should_exit}; use alacritty::tty::{self, process_should_exit};
use alacritty::window::{self, Window, SetInnerSize, Pixels, Size};
mod cli;
fn main() { fn main() {
// Load configuration // Load configuration
@ -86,7 +81,11 @@ fn main() {
let options = cli::Options::load(); let options = cli::Options::load();
// Run alacritty // Run alacritty
run(config, options); if let Err(err) = run(config, options) {
die!("{}", err);
}
println!("Goodbye");
} }
/// Run Alacritty /// Run Alacritty
@ -156,124 +155,59 @@ fn main() {
/// ///
/// instead of the 200 line monster it currently is. /// instead of the 200 line monster it currently is.
/// ///
fn run(config: Config, options: cli::Options) { fn run(config: Config, options: cli::Options) -> Result<(), Box<Error>> {
// Create a display.
//
// The display manages a window and can draw the terminal
let mut display = Display::new(&config, &options)?;
// Create the terminal
//
// This object contains all of the state about what's being displayed. It's
// wrapped in a clonable mutex since both the I/O loop and display need to
// access it.
let terminal = Arc::new(FairMutex::new(Term::new(display.size().to_owned())));
// Extract some properties from config // Create the pty
let font = config.font(); //
let dpi = config.dpi(); // The pty forks a process to run the shell on the slave side of the
let render_timer = config.render_timer(); // pseudoterminal. A file descriptor for the master side is retained for
// reading/writing to the shell.
let pty = Rc::new(tty::new(display.size()));
// Create the window where Alacritty will be displayed // When the display is resized, inform the kernel of changes to pty
let mut window = match Window::new() { // dimensions.
Ok(window) => window, //
Err(err) => die!("{}", err) // TODO: The Rc on pty is needed due to a borrowck error here. The borrow
}; // checker says that `pty` is still borrowed when it is dropped at the end
// of the `run` function.
// get window properties for initializing the other subsytems let pty_ref = pty.clone();
let size = window.inner_size_pixels().unwrap(); display.set_resize_callback(move |size| {
let dpr = window.hidpi_factor(); pty_ref.resize(size);
println!("device_pixel_ratio: {}", dpr);
let rasterizer = font::Rasterizer::new(dpi.x(), dpi.y(), dpr);
// Create renderer
let mut renderer = QuadRenderer::new(&config, size);
// Initialize glyph cache
let glyph_cache = {
println!("Initializing glyph cache");
let init_start = ::std::time::Instant::now();
let cache = renderer.with_loader(|mut api| {
GlyphCache::new(rasterizer, &config, &mut api)
});
let stop = init_start.elapsed();
let stop_f = stop.as_secs() as f64 + stop.subsec_nanos() as f64 / 1_000_000_000f64;
println!("Finished initializing glyph cache in {}", stop_f);
cache
};
// Need font metrics to resize the window properly. This suggests to me the
// font metrics should be computed before creating the window in the first
// place so that a resize is not needed.
let metrics = glyph_cache.font_metrics();
let cell_width = (metrics.average_advance + font.offset().x() as f64) as u32;
let cell_height = (metrics.line_height + font.offset().y() as f64) as u32;
// Resize window to specified dimensions
let width = cell_width * options.columns_u32() + 4;
let height = cell_height * options.lines_u32() + 4;
let size = Size { width: Pixels(width), height: Pixels(height) };
println!("set_inner_size: {}", size);
window.set_inner_size(size);
renderer.resize(*size.width as _, *size.height as _);
println!("Cell Size: ({} x {})", cell_width, cell_height);
let size = term::SizeInfo {
width: *size.width as f32,
height: *size.height as f32,
cell_width: cell_width as f32,
cell_height: cell_height as f32
};
let terminal = Term::new(size);
let pty = tty::new(size.lines(), size.cols());
pty.resize(size.lines(), size.cols(), size.width as usize, size.height as usize);
let pty_io = pty.reader();
let (tx, rx) = mpsc::channel();
let signal_flag = Flag::new(false);
let terminal = Arc::new(FairMutex::new(terminal));
// Setup the rsize callback for osx
let terminal_ref = terminal.clone();
let signal_flag_ref = signal_flag.clone();
let proxy = window.create_window_proxy();
let tx2 = tx.clone();
window.set_resize_callback(move |width, height| {
let _ = tx2.send((width, height));
if !signal_flag_ref.0.swap(true, Ordering::AcqRel) {
// We raised the signal flag
let mut terminal = terminal_ref.lock();
terminal.dirty = true;
proxy.wakeup_event_loop();
}
}); });
// Create the pseudoterminal I/O loop
//
// pty I/O is ran on another thread as to not occupy cycles used by the
// renderer and input processing. Note that access to the terminal state is
// synchronized since the I/O loop updates the state, and the display
// consumes it periodically.
let event_loop = EventLoop::new( let event_loop = EventLoop::new(
terminal.clone(), terminal.clone(),
window.create_window_proxy(), display.notifier(),
signal_flag.clone(), pty.reader(),
pty_io,
options.ref_test, options.ref_test,
); );
let loop_tx = event_loop.channel(); let loop_tx = event_loop.channel();
let event_loop_handle = event_loop.spawn(None); let event_loop_handle = event_loop.spawn(None);
// Wraps a renderer and gives simple draw() api.
let mut display = Display::new(
&window,
renderer,
glyph_cache,
render_timer,
rx,
pty
);
// Event processor // Event processor
let resize_tx = display.resize_channel();
let mut processor = event::Processor::new( let mut processor = event::Processor::new(
input::LoopNotifier(loop_tx), input::LoopNotifier(loop_tx),
terminal.clone(), terminal.clone(),
tx, resize_tx,
&config, &config,
options.ref_test, options.ref_test,
); );
@ -284,28 +218,26 @@ fn run(config: Config, options: cli::Options) {
let _config_reloader = config.path().map(|path| { let _config_reloader = config.path().map(|path| {
config::Watcher::new(path, ConfigHandler { config::Watcher::new(path, ConfigHandler {
tx: config_tx, tx: config_tx,
window: window.create_window_proxy(), loop_kicker: display.notifier(),
}) })
}); });
// Main loop // Main loop
let mut force_draw; let mut config_updated = false;
loop { loop {
force_draw = false;
// Wait for something to happen // Wait for something to happen
processor.process_events(&window); processor.process_events(display.window());
// Handle config reloads // Handle config reloads
if let Ok(config) = config_rx.try_recv() { if let Ok(config) = config_rx.try_recv() {
force_draw = true; config_updated = true;
display.update_config(&config); display.update_config(&config);
processor.update_config(&config); processor.update_config(&config);
} }
// Maybe draw the terminal // Maybe draw the terminal
let terminal = terminal.lock(); let terminal = terminal.lock();
signal_flag.set(false); if terminal.dirty || config_updated {
if force_draw || terminal.dirty {
display.draw(terminal, &config); display.draw(terminal, &config);
} }
@ -317,16 +249,19 @@ fn run(config: Config, options: cli::Options) {
// FIXME need file watcher to work with custom delegates before // FIXME need file watcher to work with custom delegates before
// joining config reloader is possible // joining config reloader is possible
//
// HELP I don't know what I meant in the above fixme
// config_reloader.join().ok(); // config_reloader.join().ok();
// shutdown // shutdown
event_loop_handle.join().ok(); event_loop_handle.join().ok();
println!("Goodbye");
Ok(())
} }
struct ConfigHandler { struct ConfigHandler {
tx: mpsc::Sender<config::Config>, tx: mpsc::Sender<config::Config>,
window: window::Proxy, loop_kicker: display::Notifier,
} }
impl config::OnConfigReload for ConfigHandler { impl config::OnConfigReload for ConfigHandler {
@ -336,100 +271,7 @@ impl config::OnConfigReload for ConfigHandler {
return; return;
} }
self.window.wakeup_event_loop(); self.loop_kicker.notify();
} }
} }
struct Display<'a> {
window: &'a Window,
renderer: QuadRenderer,
glyph_cache: GlyphCache,
render_timer: bool,
rx: mpsc::Receiver<(u32, u32)>,
meter: Meter,
pty: Pty,
}
impl<'a> Display<'a> {
pub fn update_config(&mut self, config: &Config) {
self.renderer.update_config(config);
self.render_timer = config.render_timer();
}
pub fn new(
window: &Window,
renderer: QuadRenderer,
glyph_cache: GlyphCache,
render_timer: bool,
rx: mpsc::Receiver<(u32, u32)>,
pty: Pty
) -> Display {
Display {
window: window,
renderer: renderer,
glyph_cache: glyph_cache,
render_timer: render_timer,
rx: rx,
meter: Meter::new(),
pty: pty,
}
}
/// Draw the screen
///
/// A reference to Term whose state is being drawn must be provided.
///
/// This call may block if vsync is enabled
pub fn draw(&mut self, mut terminal: MutexGuard<Term>, config: &Config) {
terminal.dirty = false;
// Resize events new_size and are handled outside the poll_events
// iterator. This has the effect of coalescing multiple resize
// events into one.
let mut new_size = None;
// Check for any out-of-band resize events (mac only)
while let Ok(sz) = self.rx.try_recv() {
new_size = Some(sz);
}
// Receive any resize events; only call gl::Viewport on last
// available
if let Some((w, h)) = new_size.take() {
terminal.resize(w as f32, h as f32);
let size = terminal.size_info();
self.pty.resize(size.lines(), size.cols(), w as _, h as _);
self.renderer.resize(w as i32, h as i32);
}
{
let glyph_cache = &mut self.glyph_cache;
// Draw grid
{
let _sampler = self.meter.sampler();
let size_info = terminal.size_info().clone();
self.renderer.with_api(config, &size_info, |mut api| {
api.clear();
// Draw the grid
api.render_cells(terminal.renderable_cells(), glyph_cache);
});
}
// Draw render timer
if self.render_timer {
let timing = format!("{:.3} usec", self.meter.average());
let color = alacritty::ansi::Color::Spec(Rgb { r: 0xd5, g: 0x4e, b: 0x53 });
self.renderer.with_api(config, terminal.size_info(), |mut api| {
api.render_string(&timing[..], glyph_cache, &color);
});
}
}
// Unlock the terminal mutex
drop(terminal);
self.window.swap_buffers().unwrap();
}
}

View file

@ -23,6 +23,20 @@ use grid::{Grid, ClearRegion};
use index::{Cursor, Column, Line}; use index::{Cursor, Column, Line};
use ansi::{Color, NamedColor}; use ansi::{Color, NamedColor};
use tty::ToWinsize;
use libc::{self, winsize};
impl<'a> ToWinsize for &'a SizeInfo {
fn to_winsize(&self) -> winsize {
winsize {
ws_row: self.lines().0 as libc::c_ushort,
ws_col: self.cols().0 as libc::c_ushort,
ws_xpixel: self.width as libc::c_ushort,
ws_ypixel: self.height as libc::c_ushort,
}
}
}
pub mod cell; pub mod cell;
pub use self::cell::Cell; pub use self::cell::Cell;

View file

@ -23,8 +23,6 @@ use std::ptr;
use libc::{self, winsize, c_int, pid_t, WNOHANG, WIFEXITED, WEXITSTATUS, SIGCHLD}; use libc::{self, winsize, c_int, pid_t, WNOHANG, WIFEXITED, WEXITSTATUS, SIGCHLD};
use index::{Line, Column};
/// Process ID of child process /// Process ID of child process
/// ///
/// Necessary to put this in static storage for `sigchld` to have access /// Necessary to put this in static storage for `sigchld` to have access
@ -238,8 +236,10 @@ fn execsh() -> ! {
} }
/// Create a new tty and return a handle to interact with it. /// Create a new tty and return a handle to interact with it.
pub fn new(lines: Line, cols: Column) -> Pty { pub fn new<T: ToWinsize>(size: T) -> Pty {
let (master, slave) = openpty(lines.0 as _, cols.0 as _); let win = size.to_winsize();
let (master, slave) = openpty(win.ws_row as _, win.ws_col as _);
match fork() { match fork() {
Relation::Child => { Relation::Child => {
@ -282,7 +282,9 @@ pub fn new(lines: Line, cols: Column) -> Pty {
set_nonblocking(master); set_nonblocking(master);
} }
Pty { fd: master } let pty = Pty { fd: master };
pty.resize(size);
pty
} }
} }
} }
@ -301,15 +303,12 @@ impl Pty {
} }
} }
pub fn resize(&self, lines: Line, cols: Column, px_x: usize, px_y: usize) { /// Resize the pty
let lines = lines.0; ///
let cols = cols.0; /// Tells the kernel that the window size changed with the new pixel
let win = winsize { /// dimensions and line/column counts.
ws_row: lines as libc::c_ushort, pub fn resize<T: ToWinsize>(&self, size: T) {
ws_col: cols as libc::c_ushort, let win = size.to_winsize();
ws_xpixel: px_x as libc::c_ushort,
ws_ypixel: px_y as libc::c_ushort,
};
let res = unsafe { let res = unsafe {
libc::ioctl(self.fd, libc::TIOCSWINSZ, &win as *const _) libc::ioctl(self.fd, libc::TIOCSWINSZ, &win as *const _)
@ -321,6 +320,12 @@ impl Pty {
} }
} }
/// Types that can produce a `libc::winsize`
pub trait ToWinsize {
/// Get a `libc::winsize`
fn to_winsize(&self) -> winsize;
}
unsafe fn set_nonblocking(fd: c_int) { unsafe fn set_nonblocking(fd: c_int) {
use libc::{fcntl, F_SETFL, F_GETFL, O_NONBLOCK}; use libc::{fcntl, F_SETFL, F_GETFL, O_NONBLOCK};

View file

@ -11,6 +11,8 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::convert::From; use std::convert::From;
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use std::ops::Deref; use std::ops::Deref;
@ -55,10 +57,17 @@ type Result<T> = ::std::result::Result<T, Error>;
/// Wraps the underlying windowing library to provide a stable API in Alacritty /// Wraps the underlying windowing library to provide a stable API in Alacritty
pub struct Window { pub struct Window {
glutin_window: glutin::Window, glutin_window: glutin::Window,
/// This flag allows calls to wakeup_event_loop to be coalesced. Waking the
/// event loop is potentially very slow (and indeed is on X11).
flag: Flag
} }
/// Threadsafe APIs for the window /// Threadsafe APIs for the window
pub struct Proxy(glutin::WindowProxy); pub struct Proxy {
inner: glutin::WindowProxy,
flag: Flag,
}
/// Information about where the window is being displayed /// Information about where the window is being displayed
/// ///
@ -89,6 +98,46 @@ pub struct Pixels<T>(pub T);
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct Points<T>(pub T); pub struct Points<T>(pub T);
/// A wrapper around glutin's WaitEventsIterator that clears the wakeup
/// optimization flag on drop.
pub struct WaitEventsIterator<'a> {
inner: glutin::WaitEventsIterator<'a>,
flag: Flag
}
impl<'a> Iterator for WaitEventsIterator<'a> {
type Item = glutin::Event;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.inner.next()
}
}
impl<'a> Drop for WaitEventsIterator<'a> {
fn drop(&mut self) {
self.flag.set(false);
}
}
#[derive(Clone)]
pub struct Flag(pub Arc<AtomicBool>);
impl Flag {
pub fn new(initial_value: bool) -> Flag {
Flag(Arc::new(AtomicBool::new(initial_value)))
}
#[inline]
pub fn get(&self) -> bool {
self.0.load(Ordering::Acquire)
}
#[inline]
pub fn set(&self, value: bool) {
self.0.store(value, Ordering::Release)
}
}
pub trait ToPoints { pub trait ToPoints {
fn to_points(&self, scale: f32) -> Size<Points<u32>>; fn to_points(&self, scale: f32) -> Size<Points<u32>>;
} }
@ -216,6 +265,7 @@ impl Window {
Ok(Window { Ok(Window {
glutin_window: window, glutin_window: window,
flag: Flag::new(false),
}) })
} }
@ -256,7 +306,10 @@ impl Window {
#[inline] #[inline]
pub fn create_window_proxy(&self) -> Proxy { pub fn create_window_proxy(&self) -> Proxy {
Proxy(self.glutin_window.create_window_proxy()) Proxy {
inner: self.glutin_window.create_window_proxy(),
flag: self.flag.clone(),
}
} }
#[inline] #[inline]
@ -267,11 +320,17 @@ impl Window {
} }
/// Block waiting for events /// Block waiting for events
///
/// FIXME should return our own type
#[inline] #[inline]
pub fn wait_events(&self) -> glutin::WaitEventsIterator { pub fn wait_events(&self) -> WaitEventsIterator {
self.glutin_window.wait_events() WaitEventsIterator {
inner: self.glutin_window.wait_events(),
flag: self.flag.clone()
}
}
/// Clear the wakeup optimization flag
pub fn clear_wakeup_flag(&self) {
self.flag.set(false);
} }
/// Block waiting for events /// Block waiting for events
@ -289,7 +348,13 @@ impl Proxy {
/// This is useful for triggering a draw when the renderer would otherwise /// This is useful for triggering a draw when the renderer would otherwise
/// be waiting on user input. /// be waiting on user input.
pub fn wakeup_event_loop(&self) { pub fn wakeup_event_loop(&self) {
self.0.wakeup_event_loop(); // Only wake up the window event loop if it hasn't already been
// signaled. This is a really important optimization because waking up
// the event loop redundantly burns *a lot* of cycles.
if !self.flag.get() {
self.inner.wakeup_event_loop();
self.flag.set(true);
}
} }
} }