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
|
// 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
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 serde_json as json;
|
||||||
|
|
||||||
use glutin;
|
use glutin;
|
||||||
|
|
||||||
use window::Window;
|
use window::Window;
|
||||||
|
|
||||||
use input;
|
use input;
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
23
src/lib.rs
23
src/lib.rs
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
274
src/main.rs
274
src/main.rs
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
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 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};
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue