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:
parent
bbd8ddbfc0
commit
ed0b1cfff0
9 changed files with 418 additions and 277 deletions
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
use std::env;
|
||||
use alacritty::index::{Line, Column};
|
||||
use index::{Line, Column};
|
||||
|
||||
/// Options specified on the command line
|
||||
pub struct Options {
|
||||
|
|
246
src/display.rs
Normal file
246
src/display.rs
Normal 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();
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ use std::sync::{Arc, mpsc};
|
|||
use serde_json as json;
|
||||
|
||||
use glutin;
|
||||
|
||||
use window::Window;
|
||||
|
||||
use input;
|
||||
|
|
|
@ -10,14 +10,11 @@ use mio::{self, Events, PollOpt, Ready};
|
|||
use mio::unix::EventedFd;
|
||||
|
||||
use ansi;
|
||||
use display;
|
||||
use term::Term;
|
||||
use util::thread;
|
||||
use sync::FairMutex;
|
||||
|
||||
use window;
|
||||
|
||||
use super::Flag;
|
||||
|
||||
/// Messages that may be sent to the `EventLoop`
|
||||
#[derive(Debug)]
|
||||
pub enum Msg {
|
||||
|
@ -35,8 +32,7 @@ pub struct EventLoop<Io> {
|
|||
rx: mio::channel::Receiver<Msg>,
|
||||
tx: mio::channel::Sender<Msg>,
|
||||
terminal: Arc<FairMutex<Term>>,
|
||||
proxy: window::Proxy,
|
||||
signal_flag: Flag,
|
||||
display: display::Notifier,
|
||||
ref_test: bool,
|
||||
}
|
||||
|
||||
|
@ -131,8 +127,7 @@ impl<Io> EventLoop<Io>
|
|||
/// Create a new event loop
|
||||
pub fn new(
|
||||
terminal: Arc<FairMutex<Term>>,
|
||||
proxy: window::Proxy,
|
||||
signal_flag: Flag,
|
||||
display: display::Notifier,
|
||||
pty: Io,
|
||||
ref_test: bool,
|
||||
) -> EventLoop<Io> {
|
||||
|
@ -143,8 +138,7 @@ impl<Io> EventLoop<Io>
|
|||
tx: tx,
|
||||
rx: rx,
|
||||
terminal: terminal,
|
||||
proxy: proxy,
|
||||
signal_flag: signal_flag,
|
||||
display: display,
|
||||
ref_test: ref_test,
|
||||
}
|
||||
}
|
||||
|
@ -196,14 +190,7 @@ impl<Io> EventLoop<Io>
|
|||
|
||||
terminal.dirty = true;
|
||||
|
||||
// Only wake up the 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.signal_flag.get() {
|
||||
self.proxy.wakeup_event_loop();
|
||||
self.signal_flag.set(true);
|
||||
}
|
||||
self.display.notify();
|
||||
},
|
||||
Err(err) => {
|
||||
match err.kind() {
|
||||
|
|
23
src/lib.rs
23
src/lib.rs
|
@ -49,7 +49,9 @@ extern crate bitflags;
|
|||
pub mod macros;
|
||||
|
||||
pub mod ansi;
|
||||
pub mod cli;
|
||||
pub mod config;
|
||||
pub mod display;
|
||||
pub mod event;
|
||||
pub mod event_loop;
|
||||
pub mod grid;
|
||||
|
@ -63,9 +65,6 @@ pub mod tty;
|
|||
pub mod util;
|
||||
pub mod window;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
pub use grid::Grid;
|
||||
pub use term::Term;
|
||||
|
||||
|
@ -80,21 +79,3 @@ pub mod gl {
|
|||
#![allow(non_upper_case_globals)]
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
274
src/main.rs
274
src/main.rs
|
@ -42,25 +42,20 @@ extern crate vte;
|
|||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
|
||||
use std::error::Error;
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::rc::Rc;
|
||||
|
||||
use parking_lot::{MutexGuard};
|
||||
|
||||
use alacritty::Flag;
|
||||
use alacritty::Rgb;
|
||||
use alacritty::cli;
|
||||
use alacritty::config::{self, Config};
|
||||
use alacritty::display::{self, Display};
|
||||
use alacritty::event;
|
||||
use alacritty::event_loop::EventLoop;
|
||||
use alacritty::input;
|
||||
use alacritty::meter::Meter;
|
||||
use alacritty::renderer::{QuadRenderer, GlyphCache};
|
||||
use alacritty::sync::FairMutex;
|
||||
use alacritty::term::{self, Term};
|
||||
use alacritty::tty::{self, Pty, process_should_exit};
|
||||
use alacritty::window::{self, Window, SetInnerSize, Pixels, Size};
|
||||
use alacritty::term::{Term};
|
||||
use alacritty::tty::{self, process_should_exit};
|
||||
|
||||
mod cli;
|
||||
|
||||
fn main() {
|
||||
// Load configuration
|
||||
|
@ -86,7 +81,11 @@ fn main() {
|
|||
let options = cli::Options::load();
|
||||
|
||||
// Run alacritty
|
||||
run(config, options);
|
||||
if let Err(err) = run(config, options) {
|
||||
die!("{}", err);
|
||||
}
|
||||
|
||||
println!("Goodbye");
|
||||
}
|
||||
|
||||
/// Run Alacritty
|
||||
|
@ -156,124 +155,59 @@ fn main() {
|
|||
///
|
||||
/// 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
|
||||
let font = config.font();
|
||||
let dpi = config.dpi();
|
||||
let render_timer = config.render_timer();
|
||||
// Create the pty
|
||||
//
|
||||
// The pty forks a process to run the shell on the slave side of the
|
||||
// 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
|
||||
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 = 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();
|
||||
}
|
||||
// When the display is resized, inform the kernel of changes to pty
|
||||
// dimensions.
|
||||
//
|
||||
// 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.
|
||||
let pty_ref = pty.clone();
|
||||
display.set_resize_callback(move |size| {
|
||||
pty_ref.resize(size);
|
||||
});
|
||||
|
||||
// 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(
|
||||
terminal.clone(),
|
||||
window.create_window_proxy(),
|
||||
signal_flag.clone(),
|
||||
pty_io,
|
||||
display.notifier(),
|
||||
pty.reader(),
|
||||
options.ref_test,
|
||||
);
|
||||
|
||||
let loop_tx = event_loop.channel();
|
||||
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
|
||||
let resize_tx = display.resize_channel();
|
||||
let mut processor = event::Processor::new(
|
||||
input::LoopNotifier(loop_tx),
|
||||
terminal.clone(),
|
||||
tx,
|
||||
resize_tx,
|
||||
&config,
|
||||
options.ref_test,
|
||||
);
|
||||
|
@ -284,28 +218,26 @@ fn run(config: Config, options: cli::Options) {
|
|||
let _config_reloader = config.path().map(|path| {
|
||||
config::Watcher::new(path, ConfigHandler {
|
||||
tx: config_tx,
|
||||
window: window.create_window_proxy(),
|
||||
loop_kicker: display.notifier(),
|
||||
})
|
||||
});
|
||||
|
||||
// Main loop
|
||||
let mut force_draw;
|
||||
let mut config_updated = false;
|
||||
loop {
|
||||
force_draw = false;
|
||||
// Wait for something to happen
|
||||
processor.process_events(&window);
|
||||
processor.process_events(display.window());
|
||||
|
||||
// Handle config reloads
|
||||
if let Ok(config) = config_rx.try_recv() {
|
||||
force_draw = true;
|
||||
config_updated = true;
|
||||
display.update_config(&config);
|
||||
processor.update_config(&config);
|
||||
}
|
||||
|
||||
// Maybe draw the terminal
|
||||
let terminal = terminal.lock();
|
||||
signal_flag.set(false);
|
||||
if force_draw || terminal.dirty {
|
||||
if terminal.dirty || config_updated {
|
||||
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
|
||||
// joining config reloader is possible
|
||||
//
|
||||
// HELP I don't know what I meant in the above fixme
|
||||
// config_reloader.join().ok();
|
||||
|
||||
// shutdown
|
||||
event_loop_handle.join().ok();
|
||||
println!("Goodbye");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct ConfigHandler {
|
||||
tx: mpsc::Sender<config::Config>,
|
||||
window: window::Proxy,
|
||||
loop_kicker: display::Notifier,
|
||||
}
|
||||
|
||||
impl config::OnConfigReload for ConfigHandler {
|
||||
|
@ -336,100 +271,7 @@ impl config::OnConfigReload for ConfigHandler {
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,20 @@ use grid::{Grid, ClearRegion};
|
|||
use index::{Cursor, Column, Line};
|
||||
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 use self::cell::Cell;
|
||||
|
||||
|
|
33
src/tty.rs
33
src/tty.rs
|
@ -23,8 +23,6 @@ use std::ptr;
|
|||
|
||||
use libc::{self, winsize, c_int, pid_t, WNOHANG, WIFEXITED, WEXITSTATUS, SIGCHLD};
|
||||
|
||||
use index::{Line, Column};
|
||||
|
||||
/// Process ID of child process
|
||||
///
|
||||
/// 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.
|
||||
pub fn new(lines: Line, cols: Column) -> Pty {
|
||||
let (master, slave) = openpty(lines.0 as _, cols.0 as _);
|
||||
pub fn new<T: ToWinsize>(size: T) -> Pty {
|
||||
let win = size.to_winsize();
|
||||
|
||||
let (master, slave) = openpty(win.ws_row as _, win.ws_col as _);
|
||||
|
||||
match fork() {
|
||||
Relation::Child => {
|
||||
|
@ -282,7 +282,9 @@ pub fn new(lines: Line, cols: Column) -> Pty {
|
|||
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) {
|
||||
let lines = lines.0;
|
||||
let cols = cols.0;
|
||||
let win = winsize {
|
||||
ws_row: lines as libc::c_ushort,
|
||||
ws_col: cols as libc::c_ushort,
|
||||
ws_xpixel: px_x as libc::c_ushort,
|
||||
ws_ypixel: px_y as libc::c_ushort,
|
||||
};
|
||||
/// Resize the pty
|
||||
///
|
||||
/// Tells the kernel that the window size changed with the new pixel
|
||||
/// dimensions and line/column counts.
|
||||
pub fn resize<T: ToWinsize>(&self, size: T) {
|
||||
let win = size.to_winsize();
|
||||
|
||||
let res = unsafe {
|
||||
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) {
|
||||
use libc::{fcntl, F_SETFL, F_GETFL, O_NONBLOCK};
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
// 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.
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::convert::From;
|
||||
use std::fmt::{self, Display};
|
||||
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
|
||||
pub struct 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
|
||||
pub struct Proxy(glutin::WindowProxy);
|
||||
pub struct Proxy {
|
||||
inner: glutin::WindowProxy,
|
||||
flag: Flag,
|
||||
}
|
||||
|
||||
/// Information about where the window is being displayed
|
||||
///
|
||||
|
@ -89,6 +98,46 @@ pub struct Pixels<T>(pub T);
|
|||
#[derive(Debug, Copy, Clone)]
|
||||
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 {
|
||||
fn to_points(&self, scale: f32) -> Size<Points<u32>>;
|
||||
}
|
||||
|
@ -216,6 +265,7 @@ impl Window {
|
|||
|
||||
Ok(Window {
|
||||
glutin_window: window,
|
||||
flag: Flag::new(false),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -256,7 +306,10 @@ impl Window {
|
|||
|
||||
#[inline]
|
||||
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]
|
||||
|
@ -267,11 +320,17 @@ impl Window {
|
|||
}
|
||||
|
||||
/// Block waiting for events
|
||||
///
|
||||
/// FIXME should return our own type
|
||||
#[inline]
|
||||
pub fn wait_events(&self) -> glutin::WaitEventsIterator {
|
||||
self.glutin_window.wait_events()
|
||||
pub fn wait_events(&self) -> WaitEventsIterator {
|
||||
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
|
||||
|
@ -289,7 +348,13 @@ impl Proxy {
|
|||
/// This is useful for triggering a draw when the renderer would otherwise
|
||||
/// be waiting on user input.
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue