alacritty

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit dbd8538762ef8968a493e1bf996e8693479ca783
parent 9c6d12ea2c863ba76015bdedc00db13b7307725a
Author: Theodore Dubois <tblodt@icloud.com>
Date:   Sun, 28 Apr 2019 06:24:58 -0700

Split alacritty into a separate crates

The crate containing the entry point is called alacritty, and the crate
containing everything else is called alacritty_terminal.

Diffstat:
MCargo.lock | 285+++++++++++++++++++++++++++++++++++++------------------------------------------
MCargo.toml | 105++-----------------------------------------------------------------------------
MINSTALL.md | 2+-
Aalacritty/Cargo.toml | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Aalacritty/src/logging.rs | 198+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aalacritty/src/main.rs | 271+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aalacritty_terminal/Cargo.toml | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aalacritty_terminal/build.rs | 28++++++++++++++++++++++++++++
Rsrc/ansi.rs -> alacritty_terminal/src/ansi.rs | 0
Rsrc/cli.rs -> alacritty_terminal/src/cli.rs | 0
Rsrc/config/bindings.rs -> alacritty_terminal/src/config/bindings.rs | 0
Aalacritty_terminal/src/config/mod.rs | 2749+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/cursor.rs -> alacritty_terminal/src/cursor.rs | 0
Rsrc/display.rs -> alacritty_terminal/src/display.rs | 0
Rsrc/event.rs -> alacritty_terminal/src/event.rs | 0
Rsrc/event_loop.rs -> alacritty_terminal/src/event_loop.rs | 0
Rsrc/grid/mod.rs -> alacritty_terminal/src/grid/mod.rs | 0
Rsrc/grid/row.rs -> alacritty_terminal/src/grid/row.rs | 0
Rsrc/grid/storage.rs -> alacritty_terminal/src/grid/storage.rs | 0
Rsrc/grid/tests.rs -> alacritty_terminal/src/grid/tests.rs | 0
Rsrc/index.rs -> alacritty_terminal/src/index.rs | 0
Rsrc/input.rs -> alacritty_terminal/src/input.rs | 0
Aalacritty_terminal/src/lib.rs | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/locale.rs -> alacritty_terminal/src/locale.rs | 0
Rsrc/macros.rs -> alacritty_terminal/src/macros.rs | 0
Rsrc/message_bar.rs -> alacritty_terminal/src/message_bar.rs | 0
Aalacritty_terminal/src/meter.rs | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/panic.rs -> alacritty_terminal/src/panic.rs | 0
Aalacritty_terminal/src/renderer/mod.rs | 1629+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/renderer/rects.rs -> alacritty_terminal/src/renderer/rects.rs | 0
Rsrc/selection.rs -> alacritty_terminal/src/selection.rs | 0
Rsrc/sync.rs -> alacritty_terminal/src/sync.rs | 0
Rsrc/term/cell.rs -> alacritty_terminal/src/term/cell.rs | 0
Rsrc/term/color.rs -> alacritty_terminal/src/term/color.rs | 0
Rsrc/term/mod.rs -> alacritty_terminal/src/term/mod.rs | 0
Rsrc/tty/mod.rs -> alacritty_terminal/src/tty/mod.rs | 0
Rsrc/tty/unix.rs -> alacritty_terminal/src/tty/unix.rs | 0
Rsrc/tty/windows/conpty.rs -> alacritty_terminal/src/tty/windows/conpty.rs | 0
Rsrc/tty/windows/mod.rs -> alacritty_terminal/src/tty/windows/mod.rs | 0
Rsrc/tty/windows/winpty.rs -> alacritty_terminal/src/tty/windows/winpty.rs | 0
Rsrc/url.rs -> alacritty_terminal/src/url.rs | 0
Rsrc/util.rs -> alacritty_terminal/src/util.rs | 0
Aalacritty_terminal/src/window.rs | 497+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aalacritty_terminal/tests/ref.rs | 128+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rtests/ref/csi_rep/alacritty.recording -> alacritty_terminal/tests/ref/csi_rep/alacritty.recording | 0
Rtests/ref/csi_rep/grid.json -> alacritty_terminal/tests/ref/csi_rep/grid.json | 0
Rtests/ref/csi_rep/size.json -> alacritty_terminal/tests/ref/csi_rep/size.json | 0
Rtests/ref/fish_cc/alacritty.recording -> alacritty_terminal/tests/ref/fish_cc/alacritty.recording | 0
Rtests/ref/fish_cc/grid.json -> alacritty_terminal/tests/ref/fish_cc/grid.json | 0
Rtests/ref/fish_cc/size.json -> alacritty_terminal/tests/ref/fish_cc/size.json | 0
Rtests/ref/grid_reset/alacritty.recording -> alacritty_terminal/tests/ref/grid_reset/alacritty.recording | 0
Rtests/ref/grid_reset/config.json -> alacritty_terminal/tests/ref/grid_reset/config.json | 0
Rtests/ref/grid_reset/grid.json -> alacritty_terminal/tests/ref/grid_reset/grid.json | 0
Rtests/ref/grid_reset/size.json -> alacritty_terminal/tests/ref/grid_reset/size.json | 0
Rtests/ref/history/alacritty.recording -> alacritty_terminal/tests/ref/history/alacritty.recording | 0
Rtests/ref/history/config.json -> alacritty_terminal/tests/ref/history/config.json | 0
Rtests/ref/history/grid.json -> alacritty_terminal/tests/ref/history/grid.json | 0
Rtests/ref/history/size.json -> alacritty_terminal/tests/ref/history/size.json | 0
Rtests/ref/indexed_256_colors/alacritty.recording -> alacritty_terminal/tests/ref/indexed_256_colors/alacritty.recording | 0
Rtests/ref/indexed_256_colors/grid.json -> alacritty_terminal/tests/ref/indexed_256_colors/grid.json | 0
Rtests/ref/indexed_256_colors/size.json -> alacritty_terminal/tests/ref/indexed_256_colors/size.json | 0
Rtests/ref/issue_855/alacritty.recording -> alacritty_terminal/tests/ref/issue_855/alacritty.recording | 0
Rtests/ref/issue_855/grid.json -> alacritty_terminal/tests/ref/issue_855/grid.json | 0
Rtests/ref/issue_855/size.json -> alacritty_terminal/tests/ref/issue_855/size.json | 0
Rtests/ref/ll/alacritty.recording -> alacritty_terminal/tests/ref/ll/alacritty.recording | 0
Rtests/ref/ll/grid.json -> alacritty_terminal/tests/ref/ll/grid.json | 0
Rtests/ref/ll/size.json -> alacritty_terminal/tests/ref/ll/size.json | 0
Rtests/ref/newline_with_cursor_beyond_scroll_region/alacritty.recording -> alacritty_terminal/tests/ref/newline_with_cursor_beyond_scroll_region/alacritty.recording | 0
Rtests/ref/newline_with_cursor_beyond_scroll_region/grid.json -> alacritty_terminal/tests/ref/newline_with_cursor_beyond_scroll_region/grid.json | 0
Rtests/ref/newline_with_cursor_beyond_scroll_region/size.json -> alacritty_terminal/tests/ref/newline_with_cursor_beyond_scroll_region/size.json | 0
Rtests/ref/tab_rendering/alacritty.recording -> alacritty_terminal/tests/ref/tab_rendering/alacritty.recording | 0
Rtests/ref/tab_rendering/grid.json -> alacritty_terminal/tests/ref/tab_rendering/grid.json | 0
Rtests/ref/tab_rendering/size.json -> alacritty_terminal/tests/ref/tab_rendering/size.json | 0
Rtests/ref/tmux_git_log/alacritty.recording -> alacritty_terminal/tests/ref/tmux_git_log/alacritty.recording | 0
Rtests/ref/tmux_git_log/grid.json -> alacritty_terminal/tests/ref/tmux_git_log/grid.json | 0
Rtests/ref/tmux_git_log/size.json -> alacritty_terminal/tests/ref/tmux_git_log/size.json | 0
Rtests/ref/tmux_htop/alacritty.recording -> alacritty_terminal/tests/ref/tmux_htop/alacritty.recording | 0
Rtests/ref/tmux_htop/grid.json -> alacritty_terminal/tests/ref/tmux_htop/grid.json | 0
Rtests/ref/tmux_htop/size.json -> alacritty_terminal/tests/ref/tmux_htop/size.json | 0
Rtests/ref/vim_24bitcolors_bce/alacritty.recording -> alacritty_terminal/tests/ref/vim_24bitcolors_bce/alacritty.recording | 0
Rtests/ref/vim_24bitcolors_bce/grid.json -> alacritty_terminal/tests/ref/vim_24bitcolors_bce/grid.json | 0
Rtests/ref/vim_24bitcolors_bce/size.json -> alacritty_terminal/tests/ref/vim_24bitcolors_bce/size.json | 0
Rtests/ref/vim_large_window_scroll/alacritty.recording -> alacritty_terminal/tests/ref/vim_large_window_scroll/alacritty.recording | 0
Rtests/ref/vim_large_window_scroll/grid.json -> alacritty_terminal/tests/ref/vim_large_window_scroll/grid.json | 0
Rtests/ref/vim_large_window_scroll/size.json -> alacritty_terminal/tests/ref/vim_large_window_scroll/size.json | 0
Rtests/ref/vim_simple_edit/alacritty.recording -> alacritty_terminal/tests/ref/vim_simple_edit/alacritty.recording | 0
Rtests/ref/vim_simple_edit/grid.json -> alacritty_terminal/tests/ref/vim_simple_edit/grid.json | 0
Rtests/ref/vim_simple_edit/size.json -> alacritty_terminal/tests/ref/vim_simple_edit/size.json | 0
Rtests/ref/vttest_cursor_movement_1/alacritty.recording -> alacritty_terminal/tests/ref/vttest_cursor_movement_1/alacritty.recording | 0
Rtests/ref/vttest_cursor_movement_1/grid.json -> alacritty_terminal/tests/ref/vttest_cursor_movement_1/grid.json | 0
Rtests/ref/vttest_cursor_movement_1/size.json -> alacritty_terminal/tests/ref/vttest_cursor_movement_1/size.json | 0
Rtests/ref/vttest_insert/alacritty.recording -> alacritty_terminal/tests/ref/vttest_insert/alacritty.recording | 0
Rtests/ref/vttest_insert/grid.json -> alacritty_terminal/tests/ref/vttest_insert/grid.json | 0
Rtests/ref/vttest_insert/size.json -> alacritty_terminal/tests/ref/vttest_insert/size.json | 0
Rtests/ref/vttest_origin_mode_1/alacritty.recording -> alacritty_terminal/tests/ref/vttest_origin_mode_1/alacritty.recording | 0
Rtests/ref/vttest_origin_mode_1/grid.json -> alacritty_terminal/tests/ref/vttest_origin_mode_1/grid.json | 0
Rtests/ref/vttest_origin_mode_1/size.json -> alacritty_terminal/tests/ref/vttest_origin_mode_1/size.json | 0
Rtests/ref/vttest_origin_mode_2/alacritty.recording -> alacritty_terminal/tests/ref/vttest_origin_mode_2/alacritty.recording | 0
Rtests/ref/vttest_origin_mode_2/grid.json -> alacritty_terminal/tests/ref/vttest_origin_mode_2/grid.json | 0
Rtests/ref/vttest_origin_mode_2/size.json -> alacritty_terminal/tests/ref/vttest_origin_mode_2/size.json | 0
Rtests/ref/vttest_scroll/alacritty.recording -> alacritty_terminal/tests/ref/vttest_scroll/alacritty.recording | 0
Rtests/ref/vttest_scroll/grid.json -> alacritty_terminal/tests/ref/vttest_scroll/grid.json | 0
Rtests/ref/vttest_scroll/size.json -> alacritty_terminal/tests/ref/vttest_scroll/size.json | 0
Rtests/ref/vttest_tab_clear_set/alacritty.recording -> alacritty_terminal/tests/ref/vttest_tab_clear_set/alacritty.recording | 0
Rtests/ref/vttest_tab_clear_set/grid.json -> alacritty_terminal/tests/ref/vttest_tab_clear_set/grid.json | 0
Rtests/ref/vttest_tab_clear_set/size.json -> alacritty_terminal/tests/ref/vttest_tab_clear_set/size.json | 0
Rtests/ref/zerowidth/alacritty.recording -> alacritty_terminal/tests/ref/zerowidth/alacritty.recording | 0
Rtests/ref/zerowidth/config.json -> alacritty_terminal/tests/ref/zerowidth/config.json | 0
Rtests/ref/zerowidth/grid.json -> alacritty_terminal/tests/ref/zerowidth/grid.json | 0
Rtests/ref/zerowidth/size.json -> alacritty_terminal/tests/ref/zerowidth/size.json | 0
Rtests/ref/zsh_tab_completion/alacritty.recording -> alacritty_terminal/tests/ref/zsh_tab_completion/alacritty.recording | 0
Rtests/ref/zsh_tab_completion/grid.json -> alacritty_terminal/tests/ref/zsh_tab_completion/grid.json | 0
Rtests/ref/zsh_tab_completion/size.json -> alacritty_terminal/tests/ref/zsh_tab_completion/size.json | 0
Dbuild.rs | 83-------------------------------------------------------------------------------
Mci/before_deploy.sh | 4++--
Mci/script.sh | 27++++-----------------------
Mcopypasta/src/x11.rs | 21---------------------
Dsrc/config/mod.rs | 2749-------------------------------------------------------------------------------
Dsrc/lib.rs | 61-------------------------------------------------------------
Dsrc/logging.rs | 198-------------------------------------------------------------------------------
Dsrc/main.rs | 270-------------------------------------------------------------------------------
Dsrc/meter.rs | 110-------------------------------------------------------------------------------
Dsrc/renderer/mod.rs | 1625-------------------------------------------------------------------------------
Dsrc/window.rs | 497-------------------------------------------------------------------------------
Dtests/ref.rs | 128-------------------------------------------------------------------------------
Mwinpty/Cargo.toml | 7+++++++
Mwinpty/build.rs | 71++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mwinpty/src/windows.rs | 5++---
128 files changed, 6000 insertions(+), 6035 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1,14 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] -name = "MacTypes-sys" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] name = "adler32" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -33,6 +25,19 @@ dependencies = [ name = "alacritty" version = "0.3.2" dependencies = [ + "alacritty_terminal 0.3.2", + "crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "alacritty_terminal" +version = "0.3.2" +dependencies = [ "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -40,15 +45,13 @@ dependencies = [ "crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "dunce 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "embed-resource 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "font 0.1.0", "gl_generator 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "glutin 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", "mio-anonymous-pipes 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -59,16 +62,13 @@ dependencies = [ "notify 4.0.10 (registry+https://github.com/rust-lang/crates.io-index)", "objc 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "reqwest 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "serde_yaml 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)", "signal-hook 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "terminfo 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "vte 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -77,7 +77,6 @@ dependencies = [ "winpty 0.1.0", "x11-dl 2.18.3 (registry+https://github.com/rust-lang/crates.io-index)", "xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "zip 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -141,7 +140,7 @@ name = "atty" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -159,7 +158,7 @@ dependencies = [ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-demangle 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -170,7 +169,7 @@ version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -248,7 +247,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bzip2-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -257,7 +256,7 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -287,7 +286,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "gleam 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -296,7 +295,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -360,7 +359,7 @@ dependencies = [ "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", "core-graphics 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)", "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "objc 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -414,28 +413,11 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "core-foundation-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "core-foundation" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "core-foundation-sys" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -451,7 +433,7 @@ dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -462,7 +444,7 @@ dependencies = [ "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", "core-graphics 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)", "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -574,9 +556,9 @@ name = "derivative" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.28 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.32 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.33 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -584,7 +566,7 @@ name = "dirs" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "redox_users 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -618,7 +600,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -676,7 +658,7 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "errno-dragonfly 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -686,7 +668,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -711,9 +693,9 @@ name = "euclid_macros" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.28 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.32 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.33 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -739,19 +721,19 @@ name = "failure_derive" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.28 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.32 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.33 (registry+https://github.com/rust-lang/crates.io-index)", "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "filetime" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -761,7 +743,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "miniz_oxide_c_api 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -782,7 +764,7 @@ dependencies = [ "euclid 0.19.8 (registry+https://github.com/rust-lang/crates.io-index)", "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "freetype-rs 0.19.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "servo-fontconfig 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -807,7 +789,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "freetype-sys 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -815,7 +797,7 @@ name = "freetype-sys" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -827,7 +809,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -835,7 +817,7 @@ name = "fsevent-sys" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1031,12 +1013,12 @@ dependencies = [ "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-threadpool 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-threadpool 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "want 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1049,7 +1031,7 @@ dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.12.27 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1075,7 +1057,7 @@ dependencies = [ "num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", "num-rational 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "png 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", + "png 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", "scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "tiff 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1100,7 +1082,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "inotify-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1108,7 +1090,7 @@ name = "inotify-sys" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1116,7 +1098,7 @@ name = "iovec" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1160,7 +1142,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" -version = "0.2.51" +version = "0.2.53" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1188,7 +1170,7 @@ version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1233,7 +1215,7 @@ name = "malloc_buf" version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1246,7 +1228,7 @@ name = "memchr" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1259,7 +1241,7 @@ name = "memmap" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1302,7 +1284,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", "crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "miniz_oxide 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1316,7 +1298,7 @@ dependencies = [ "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1363,7 +1345,7 @@ version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1398,18 +1380,18 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "openssl 0.10.20 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-sys 0.9.43 (registry+https://github.com/rust-lang/crates.io-index)", "schannel 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1419,7 +1401,7 @@ version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1431,7 +1413,7 @@ dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1463,12 +1445,12 @@ version = "4.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "filetime 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "filetime 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "fsevent 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", "fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "inotify 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", "mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1477,12 +1459,12 @@ dependencies = [ [[package]] name = "num-derive" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.28 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.32 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.33 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1521,7 +1503,7 @@ name = "num_cpus" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1564,7 +1546,7 @@ dependencies = [ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-sys 0.9.43 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1579,7 +1561,7 @@ version = "0.9.43" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1623,7 +1605,7 @@ name = "parking_lot_core" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1682,7 +1664,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "png" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1698,7 +1680,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "proc-macro2" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1731,7 +1713,7 @@ name = "quote" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.28 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1740,7 +1722,7 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1795,7 +1777,7 @@ name = "rand_jitter" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1807,7 +1789,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1847,7 +1829,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1949,15 +1931,15 @@ dependencies = [ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", "mime_guess 2.0.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-threadpool 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-threadpool 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2021,23 +2003,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "security-framework" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "core-foundation 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "core-foundation-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "security-framework-sys" -version = "0.2.3" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "MacTypes-sys 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "core-foundation-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2063,9 +2043,9 @@ name = "serde_derive" version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.28 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.32 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.33 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2105,7 +2085,7 @@ name = "servo-fontconfig" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "servo-fontconfig-sys 4.0.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2132,7 +2112,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2141,7 +2121,7 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)", "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2183,7 +2163,7 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2223,10 +2203,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "syn" -version = "0.15.32" +version = "0.15.33" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.28 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2236,9 +2216,9 @@ name = "synstructure" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.28 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.32 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.33 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2248,7 +2228,7 @@ version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", "remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2279,7 +2259,7 @@ name = "termion" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2308,7 +2288,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-derive 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2317,14 +2297,14 @@ name = "time" version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "tokio" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2336,7 +2316,7 @@ dependencies = [ "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-threadpool 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-threadpool 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-trace-core 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2384,12 +2364,12 @@ dependencies = [ "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-sync 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-sync 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "tokio-sync" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2411,7 +2391,7 @@ dependencies = [ [[package]] name = "tokio-threadpool" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2557,7 +2537,7 @@ name = "vswhom" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "vswhom-sys 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2567,7 +2547,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2605,7 +2585,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "downcast-rs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "wayland-commons 0.21.12 (registry+https://github.com/rust-lang/crates.io-index)", "wayland-scanner 0.21.12 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2638,7 +2618,7 @@ name = "wayland-scanner" version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.28 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", "xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2657,7 +2637,7 @@ name = "which" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2724,7 +2704,7 @@ dependencies = [ "core-graphics 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "objc 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2740,10 +2720,14 @@ name = "winpty" version = "0.1.0" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "embed-resource 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "named_pipe 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "reqwest 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "winpty-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "zip 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2786,7 +2770,7 @@ version = "2.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2795,7 +2779,7 @@ name = "xcb" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2830,7 +2814,6 @@ dependencies = [ ] [metadata] -"checksum MacTypes-sys 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eaf9f0d0b1cc33a4d2aee14fb4b2eac03462ef4db29c8ac4057327d8a71ad86f" "checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" "checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" "checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c" @@ -2871,9 +2854,7 @@ dependencies = [ "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" "checksum cookie 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1465f8134efa296b4c19db34d909637cb2bf0f7aaf21299e23e18fa29ac557cf" "checksum cookie_store 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b0d2f2ecb21dce00e2453268370312978af9b8024020c7a37ae2cc6dbbe64685" -"checksum core-foundation 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "286e0b41c3a20da26536c6000a280585d519fd07b3956b43aed8a79e9edce980" "checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" -"checksum core-foundation-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "716c271e8613ace48344f723b60b900a93150271e5be206212d052bbc0883efa" "checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" "checksum core-graphics 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)" = "56790968ab1c8a1202a102e6de05fc6e1ec87da99e4e93e9a7d13efbfc1e95a9" "checksum core-text 13.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d95a72b5e50e549969dd88eff3047495fe5b8c6f028635442c2b708be707e669" @@ -2908,7 +2889,7 @@ dependencies = [ "checksum expat-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "658f19728920138342f68408b7cf7644d90d4784353d8ebc32e7e8663dbe45fa" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" -"checksum filetime 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a2df5c1a8c4be27e7707789dc42ae65976e60b394afd293d1419ab915833e646" +"checksum filetime 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2f8c63033fcba1f51ef744505b3cad42510432b904c062afa67ad7ece008429d" "checksum flate2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f87e68aa82b2de08a6e037f1385455759df6e445a8df5e005b4297191dbf18aa" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" @@ -2952,7 +2933,7 @@ dependencies = [ "checksum khronos_api 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" -"checksum libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "bedcc7a809076656486ffe045abeeac163da1b558e963a31e29fbfbeba916917" +"checksum libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)" = "ec350a9417dfd244dc9a6c4a71e13895a4db6b92f0b106f07ebbc3f3bc580cee" "checksum libflate 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)" = "c52384aeb22d0ce82a10d8ddf35f7fb4717d1b23eac5b94cd38d2050fb53766a" "checksum libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3ad660d7cb8c5822cd83d10897b0f1f1526792737a179e73896152f85b88c2" "checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" @@ -2979,14 +2960,14 @@ dependencies = [ "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" "checksum miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226" "checksum named_pipe 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ed10a5ac4f5f7e5d75552b12c1d5d542debca81e573279dd1e4c19fde6efa6d" -"checksum native-tls 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ff8e08de0070bbf4c31f452ea2a70db092f36f6f2e4d897adf5674477d488fb2" +"checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" "checksum nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46f0f3210768d796e8fa79ec70ee6af172dacbe7147f5e69be5240a47778302b" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b" "checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" "checksum notify 4.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "abb1581693e44d8a0ec347ef12289625063f52a1dddc3f3c9befd5fc59e88943" -"checksum num-derive 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d9fe8fcafd1b86a37ce8a1cfa15ae504817e0c8c2e7ad42767371461ac1d316d" +"checksum num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "eafd0b45c5537c3ba526f79d3e75120036502bebacbb3f3220914067ce39dbf2" "checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" "checksum num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "af3fdbbc3291a5464dc57b03860ec37ca6bf915ed6ee385e7c6c052c422b2124" "checksum num-rational 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e96f040177bb3da242b5b1ecf3f54b5d5af3efbbfb18608977a5d2767b22f10" @@ -3011,9 +2992,9 @@ dependencies = [ "checksum phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" "checksum phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" "checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" -"checksum png 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9adebf7fb91ccf5eac9da1a8e00e83cb8ae882c3e8d8e4ad59da73cb8c82a2c9" +"checksum png 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "63daf481fdd0defa2d1d2be15c674fbfa1b0fd71882c303a91f9a79b3252c359" "checksum podio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "780fb4b6698bbf9cf2444ea5d22411cef2953f0824b98f33cf454ec5615645bd" -"checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" +"checksum proc-macro2 0.4.28 (registry+https://github.com/rust-lang/crates.io-index)" = "ba92c84f814b3f9a44c5cfca7d2ad77fa10710867d2bbb1b3d175ab5f47daa12" "checksum publicsuffix 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5afecba86dcf1e4fd610246f89899d1924fe12e1e89f555eb7c7f710f3c5ad1d" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" @@ -3048,8 +3029,8 @@ dependencies = [ "checksum schannel 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "f2f6abf258d99c3c1c5c2131d99d064e94b7b3dd5f416483057f308fea253339" "checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" -"checksum security-framework 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfab8dda0e7a327c696d893df9ffa19cadc4bd195797997f5223cf5831beaf05" -"checksum security-framework-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3d6696852716b589dff9e886ff83778bb635150168e83afa8ac6b8a78cb82abc" +"checksum security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eee63d0f4a9ec776eeb30e220f0bc1e092c3ad744b2a379e3993070364d3adc2" +"checksum security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9636f8989cbf61385ae4824b98c1aaa54c994d7d8b41f11c601ed799f0549a56" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)" = "aa5f7c20820475babd2c077c3ab5f8c77a31c15e16ea38687b4c02d3e48680f4" @@ -3072,7 +3053,7 @@ dependencies = [ "checksum stb_truetype 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "69b7df505db8e81d54ff8be4693421e5b543e08214bd8d99eb761fcb4d5668ba" "checksum string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b639411d0b9c738748b5397d5ceba08e648f4f1992231aa859af1a017f31f60b" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -"checksum syn 0.15.32 (registry+https://github.com/rust-lang/crates.io-index)" = "846620ec526c1599c070eff393bfeeeb88a93afa2513fc3b49f1fea84cf7b0ed" +"checksum syn 0.15.33 (registry+https://github.com/rust-lang/crates.io-index)" = "ec52cd796e5f01d0067225a5392e70084acc4c0013fa71d55166d38a8b307836" "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" "checksum tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b86c784c88d98c801132806dadd3819ed29d8600836c4088e855cdf3e178ed8a" "checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f" @@ -3082,14 +3063,14 @@ dependencies = [ "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum tiff 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4834f28a0330cb9f3f2c87d2649dca723cb33802e2bdcf18da32759fbec7ce" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" -"checksum tokio 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "65641e515a437b308ab131a82ce3042ff9795bef5d6c5a9be4eb24195c417fd9" +"checksum tokio 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "cec6c34409089be085de9403ba2010b80e36938c9ca992c4f67f407bb13db0b1" "checksum tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443" "checksum tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "83ea44c6c0773cc034771693711c35c677b4b5a4b21b9e7071704c54de7d555e" "checksum tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926" "checksum tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6af16bfac7e112bea8b0442542161bfc41cbfa4466b580bdda7d18cb88b911ce" -"checksum tokio-sync 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "fda385df506bf7546e70872767f71e81640f1f251bdf2fd8eb81a0eaec5fe022" +"checksum tokio-sync 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "5b2f843ffdf8d6e1f90bddd48da43f99ab071660cd92b7ec560ef3cdfd7a409a" "checksum tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119" -"checksum tokio-threadpool 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "ec5759cf26cf9659555f36c431b515e3d05f66831741c85b4b5d5dfb9cf1323c" +"checksum tokio-threadpool 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72558af20be886ea124595ea0f806dd5703b8958e4705429dd58b3d8231f72f2" "checksum tokio-timer 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2910970404ba6fa78c5539126a9ae2045d62e3713041e447f695f41405a120c6" "checksum tokio-trace-core 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "350c9edade9830dc185ae48ba45667a445ab59f6167ef6d0254ec9d2430d9dd3" "checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" diff --git a/Cargo.toml b/Cargo.toml @@ -1,117 +1,16 @@ -[package] -name = "alacritty" -version = "0.3.2" -authors = ["Joe Wilm <joe@jwilm.com>"] -license = "Apache-2.0" -build = "build.rs" -description = "GPU-accelerated terminal emulator" -readme = "README.md" -homepage = "https://github.com/jwilm/alacritty" -edition = "2018" - [workspace] members = [ + "alacritty", + "alacritty_terminal", "font", "copypasta", "winpty" ] -[[bin]] -doc = false -path = "src/main.rs" -name = "alacritty" - -[dependencies] -libc = "0.2" -notify = "4" -bitflags = "1" -font = { path = "./font" } -errno = "0.2" -parking_lot = "0.7" -serde = "1" -serde_derive = "1" -serde_json = "1" -serde_yaml = "0.8" -vte = "0.3" -mio = "0.6" -mio-extras = "2" -copypasta = { path = "./copypasta" } -xdg = "2" -log = "0.4" -clap = "2" -fnv = "1" -unicode-width = "0.1" -glutin = { version = "0.21.0", features = ["icon_loading"] } -env_logger = "0.6.0" -base64 = "0.10.0" -static_assertions = "0.3.0" -terminfo = "0.6.1" -url = "1.7.1" -time = "0.1.40" -crossbeam-channel = "0.3.8" - -[target.'cfg(unix)'.dependencies] -nix = "0.13" -signal-hook = { version = "0.1", features = ["mio-support"] } - -[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os="dragonfly", target_os="openbsd"))'.dependencies] -x11-dl = "2" - -[target.'cfg(windows)'.dependencies] -winpty = { path = "./winpty" } -mio-named-pipes = "0.1" -miow = "0.3" -dunce = "1.0" -winapi = { version = "0.3.7", features = ["impl-default", "winuser", "synchapi", "roerrorapi", "winerror", "wincon", "wincontypes"]} -dirs = "1.0" -widestring = "0.4" -mio-anonymous-pipes = "0.1" -image = "0.21.0" - -[target.'cfg(target_os = "macos")'.dependencies] -objc = "0.2.2" -dirs = "1.0.2" - -[features] -default = [] -# Enabling this feature makes shaders automatically reload when changed -live-shader-reload = [] -nightly = [] -bench = [] - -[build-dependencies] -gl_generator = "0.11.0" - -[target.'cfg(windows)'.build-dependencies] -embed-resource = "1.1.4" -tempfile = "3.0.4" -reqwest = "0.9" -zip = "0.5" - [profile.release] lto = true debug = 1 incremental = false -[package.metadata.deb] -maintainer = "Joe Wilm <joe@jwilm.com>" -license-file = ["LICENSE-APACHE", "3"] -extended-description = """\ -Alacritty is the fastest terminal emulator in existence. Using the GPU for \ -rendering enables optimizations that simply aren't possible without it. """ -depends = "$auto" -section = "rust" -priority = "optional" -assets = [ - ["target/release/alacritty", "usr/bin/", "755"], - ["extra/linux/alacritty.desktop", "usr/share/applications/", "644"], - ["extra/logo/alacritty-term.svg", "usr/share/pixmaps/Alacritty.svg", "644"], - ["extra/completions/alacritty.bash", "usr/share/bash-completion/completions/alacritty", "644"], - ["extra/completions/alacritty.fish", "usr/share/fish/completions/alacritty.fish", "644"], - ["extra/completions/_alacritty", "usr/share/zsh/vendor-completions/_alacritty", "644"], - ["extra/alacritty.info", "usr/share/terminfo/a/alacritty", "644"], -] -maintainer-scripts = "extra/linux/debian" - [patch.crates-io] servo-freetype-sys = { path = "servo-freetype-proxy" } diff --git a/INSTALL.md b/INSTALL.md @@ -228,7 +228,7 @@ Using `cargo deb`, you can create and install a deb file. ```sh cargo install cargo-deb -cargo deb --install +cargo deb --install --manifest-path alacritty/Cargo.toml ``` To choose a default terminal app, use Debian's `update-alternatives`. diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "alacritty" +version = "0.3.2" +authors = ["Joe Wilm <joe@jwilm.com>"] +license = "Apache-2.0" +description = "GPU-accelerated terminal emulator" +readme = "../README.md" +homepage = "https://github.com/jwilm/alacritty" +edition = "2018" + +[dependencies] +alacritty_terminal = { path = "../alacritty_terminal" } +log = "0.4" +time = "0.1.40" +env_logger = "0.6.0" +crossbeam-channel = "0.3.8" + +[target.'cfg(target_os = "macos")'.dependencies] +dirs = "1.0.2" + +[target.'cfg(windows)'.dependencies] +winapi = { version = "0.3.7", features = ["impl-default", "winuser", "synchapi", "roerrorapi", "winerror", "wincon", "wincontypes"]} + +[features] +default = [] +# Enabling this feature makes shaders automatically reload when changed +live-shader-reload = ["alacritty_terminal/live-shader-reload"] +nightly = [] +bench = [] + +[package.metadata.deb] +maintainer = "Joe Wilm <joe@jwilm.com>" +license-file = ["LICENSE-APACHE", "3"] +extended-description = """\ +Alacritty is the fastest terminal emulator in existence. Using the GPU for \ +rendering enables optimizations that simply aren't possible without it. """ +depends = "$auto" +section = "rust" +priority = "optional" +assets = [ + ["../target/release/alacritty", "usr/bin/", "755"], + ["../extra/linux/alacritty.desktop", "usr/share/applications/", "644"], + ["../extra/logo/alacritty-term.svg", "usr/share/pixmaps/Alacritty.svg", "644"], + ["../extra/completions/alacritty.bash", "usr/share/bash-completion/completions/alacritty", "644"], + ["../extra/completions/alacritty.fish", "usr/share/fish/completions/alacritty.fish", "644"], + ["../extra/completions/_alacritty", "usr/share/zsh/vendor-completions/_alacritty", "644"], + ["../extra/alacritty.info", "usr/share/terminfo/a/alacritty", "644"], +] +maintainer-scripts = "../extra/linux/debian" diff --git a/alacritty/src/logging.rs b/alacritty/src/logging.rs @@ -0,0 +1,198 @@ +// 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. +// +//! Logging for alacritty. +//! +//! The main executable is supposed to call `initialize()` exactly once during +//! startup. All logging messages are written to stdout, given that their +//! log-level is sufficient for the level configured in `cli::Options`. +use std::env; +use std::fs::{File, OpenOptions}; +use std::io::{self, LineWriter, Stdout, Write}; +use std::path::PathBuf; +use std::process; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; + +use crossbeam_channel::Sender; +use log::{self, Level}; +use time; + +use alacritty_terminal::cli; +use alacritty_terminal::message_bar::Message; +use alacritty_terminal::term::color; + +const ALACRITTY_LOG_ENV: &str = "ALACRITTY_LOG"; + +pub fn initialize( + options: &cli::Options, + message_tx: Sender<Message>, +) -> Result<Option<PathBuf>, log::SetLoggerError> { + // Use env_logger if RUST_LOG environment variable is defined. Otherwise, + // use the alacritty-only logger. + if ::std::env::var("RUST_LOG").is_ok() { + ::env_logger::try_init()?; + Ok(None) + } else { + let logger = Logger::new(options.log_level, message_tx); + let path = logger.file_path(); + log::set_boxed_logger(Box::new(logger))?; + Ok(path) + } +} + +pub struct Logger { + level: log::LevelFilter, + logfile: Mutex<OnDemandLogFile>, + stdout: Mutex<LineWriter<Stdout>>, + message_tx: Sender<Message>, +} + +impl Logger { + // False positive, see: https://github.com/rust-lang-nursery/rust-clippy/issues/734 + #[allow(clippy::new_ret_no_self)] + fn new(level: log::LevelFilter, message_tx: Sender<Message>) -> Self { + log::set_max_level(level); + + let logfile = Mutex::new(OnDemandLogFile::new()); + let stdout = Mutex::new(LineWriter::new(io::stdout())); + + Logger { level, logfile, stdout, message_tx } + } + + fn file_path(&self) -> Option<PathBuf> { + if let Ok(logfile) = self.logfile.lock() { + Some(logfile.path().clone()) + } else { + None + } + } +} + +impl log::Log for Logger { + fn enabled(&self, metadata: &log::Metadata<'_>) -> bool { + metadata.level() <= self.level + } + + fn log(&self, record: &log::Record<'_>) { + if self.enabled(record.metadata()) && record.target().starts_with("alacritty") { + let now = time::strftime("%F %R", &time::now()).unwrap(); + + let msg = if record.level() >= Level::Trace { + format!( + "[{}] [{}] [{}:{}] {}\n", + now, + record.level(), + record.file().unwrap_or("?"), + record.line().map(|l| l.to_string()).unwrap_or_else(|| "?".into()), + record.args() + ) + } else { + format!("[{}] [{}] {}\n", now, record.level(), record.args()) + }; + + if let Ok(ref mut logfile) = self.logfile.lock() { + let _ = logfile.write_all(msg.as_ref()); + + if record.level() <= Level::Warn { + #[cfg(not(windows))] + let env_var = format!("${}", ALACRITTY_LOG_ENV); + #[cfg(windows)] + let env_var = format!("%{}%", ALACRITTY_LOG_ENV); + + let msg = format!( + "[{}] See log at {} ({}):\n{}", + record.level(), + logfile.path.to_string_lossy(), + env_var, + record.args(), + ); + let color = match record.level() { + Level::Error => color::RED, + Level::Warn => color::YELLOW, + _ => unreachable!(), + }; + + let mut message = Message::new(msg, color); + message.set_topic(record.file().unwrap_or("?").into()); + let _ = self.message_tx.send(message); + } + } + + if let Ok(ref mut stdout) = self.stdout.lock() { + let _ = stdout.write_all(msg.as_ref()); + } + } + } + + fn flush(&self) {} +} + +struct OnDemandLogFile { + file: Option<LineWriter<File>>, + created: Arc<AtomicBool>, + path: PathBuf, +} + +impl OnDemandLogFile { + fn new() -> Self { + let mut path = env::temp_dir(); + path.push(format!("Alacritty-{}.log", process::id())); + + // Set log path as an environment variable + env::set_var(ALACRITTY_LOG_ENV, path.as_os_str()); + + OnDemandLogFile { path, file: None, created: Arc::new(AtomicBool::new(false)) } + } + + fn file(&mut self) -> Result<&mut LineWriter<File>, io::Error> { + // Allow to recreate the file if it has been deleted at runtime + if self.file.is_some() && !self.path.as_path().exists() { + self.file = None; + } + + // Create the file if it doesn't exist yet + if self.file.is_none() { + let file = OpenOptions::new().append(true).create(true).open(&self.path); + + match file { + Ok(file) => { + self.file = Some(io::LineWriter::new(file)); + self.created.store(true, Ordering::Relaxed); + let _ = writeln!(io::stdout(), "Created log file at {:?}", self.path); + }, + Err(e) => { + let _ = writeln!(io::stdout(), "Unable to create log file: {}", e); + return Err(e); + }, + } + } + + Ok(self.file.as_mut().unwrap()) + } + + fn path(&self) -> &PathBuf { + &self.path + } +} + +impl Write for OnDemandLogFile { + fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> { + self.file()?.write(buf) + } + + fn flush(&mut self) -> Result<(), io::Error> { + self.file()?.flush() + } +} diff --git a/alacritty/src/main.rs b/alacritty/src/main.rs @@ -0,0 +1,271 @@ +// 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. +// +//! Alacritty - The GPU Enhanced Terminal +#![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use, clippy::wrong_pub_self_convention)] +#![cfg_attr(feature = "nightly", feature(core_intrinsics))] +#![cfg_attr(all(test, feature = "bench"), feature(test))] +// With the default subsystem, 'console', windows creates an additional console +// window for the program. +// This is silently ignored on non-windows systems. +// See https://msdn.microsoft.com/en-us/library/4cc7ya5b.aspx for more details. +#![windows_subsystem = "windows"] + +#[cfg(target_os = "macos")] +use dirs; + +#[cfg(windows)] +use winapi::um::wincon::{AttachConsole, FreeConsole, ATTACH_PARENT_PROCESS}; + +use log::{error, info}; + +use std::error::Error; +use std::fs; +use std::io::{self, Write}; +use std::sync::Arc; + +#[cfg(target_os = "macos")] +use std::env; + +#[cfg(not(windows))] +use std::os::unix::io::AsRawFd; + +use alacritty_terminal::config::{self, Config, Monitor}; +use alacritty_terminal::display::Display; +use alacritty_terminal::event_loop::{self, EventLoop, Msg}; +#[cfg(target_os = "macos")] +use alacritty_terminal::locale; +use alacritty_terminal::message_bar::MessageBuffer; +use alacritty_terminal::panic; +use alacritty_terminal::sync::FairMutex; +use alacritty_terminal::term::Term; +use alacritty_terminal::tty; +use alacritty_terminal::util::fmt::Red; +use alacritty_terminal::{cli, die, event}; + +mod logging; + +fn main() { + panic::attach_handler(); + + // When linked with the windows subsystem windows won't automatically attach + // to the console of the parent process, so we do it explicitly. This fails + // silently if the parent has no console. + #[cfg(windows)] + unsafe { + AttachConsole(ATTACH_PARENT_PROCESS); + } + + // Load command line options + let options = cli::Options::load(); + + // Setup storage for message UI + let message_buffer = MessageBuffer::new(); + + // Initialize the logger as soon as possible as to capture output from other subsystems + let log_file = + logging::initialize(&options, message_buffer.tx()).expect("Unable to initialize logger"); + + // Load configuration file + // If the file is a command line argument, we won't write a generated default file + let config_path = options + .config_path() + .or_else(Config::installed_config) + .or_else(|| Config::write_defaults().ok()) + .map(|path| path.to_path_buf()); + let config = if let Some(path) = config_path { + Config::load_from(path).update_dynamic_title(&options) + } else { + error!("Unable to write the default config"); + Config::default() + }; + + // Switch to home directory + #[cfg(target_os = "macos")] + env::set_current_dir(dirs::home_dir().unwrap()).unwrap(); + // Set locale + #[cfg(target_os = "macos")] + locale::set_locale_environment(); + + // Store if log file should be deleted before moving config + let persistent_logging = options.persistent_logging || config.persistent_logging(); + + // Run alacritty + if let Err(err) = run(config, &options, message_buffer) { + die!("Alacritty encountered an unrecoverable error:\n\n\t{}\n", Red(err)); + } + + // Clean up logfile + if let Some(log_file) = log_file { + if !persistent_logging && fs::remove_file(&log_file).is_ok() { + let _ = writeln!(io::stdout(), "Deleted log file at {:?}", log_file); + } + } +} + +/// Run Alacritty +/// +/// Creates a window, the terminal state, pty, I/O event loop, input processor, +/// config change monitor, and runs the main display loop. +fn run( + mut config: Config, + options: &cli::Options, + message_buffer: MessageBuffer, +) -> Result<(), Box<dyn Error>> { + info!("Welcome to Alacritty"); + if let Some(config_path) = config.path() { + info!("Configuration loaded from {:?}", config_path.display()); + }; + + // Set environment variables + tty::setup_env(&config); + + // Create a display. + // + // The display manages a window and can draw the terminal + let mut display = Display::new(&config, options)?; + + info!("PTY Dimensions: {:?} x {:?}", display.size().lines(), display.size().cols()); + + // 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 = Term::new(&config, display.size().to_owned(), message_buffer); + let terminal = Arc::new(FairMutex::new(terminal)); + + // Find the window ID for setting $WINDOWID + let window_id = display.get_window_id(); + + // 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 = tty::new(&config, options, &display.size(), window_id); + + // Get a reference to something that we can resize + // + // This exists because rust doesn't know the interface is thread-safe + // and we need to be able to resize the PTY from the main thread while the IO + // thread owns the EventedRW object. + #[cfg(windows)] + let mut resize_handle = pty.resize_handle(); + #[cfg(not(windows))] + let mut resize_handle = pty.fd.as_raw_fd(); + + // 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(Arc::clone(&terminal), display.notifier(), pty, options.ref_test); + + // The event loop channel allows write requests from the event processor + // to be sent to the loop and ultimately written to the pty. + let loop_tx = event_loop.channel(); + + // Event processor + // + // Need the Rc<RefCell<_>> here since a ref is shared in the resize callback + let mut processor = event::Processor::new( + event_loop::Notifier(event_loop.channel()), + display.resize_channel(), + options, + &config, + options.ref_test, + display.size().to_owned(), + ); + + // Create a config monitor when config was loaded from path + // + // The monitor watches the config file for changes and reloads it. Pending + // config changes are processed in the main loop. + let config_monitor = match (options.live_config_reload, config.live_config_reload()) { + // Start monitor if CLI flag says yes + (Some(true), _) | + // Or if no CLI flag was passed and the config says yes + (None, true) => config.path() + .map(|path| config::Monitor::new(path, display.notifier())), + // Otherwise, don't start the monitor + _ => None, + }; + + // Kick off the I/O thread + let _io_thread = event_loop.spawn(None); + + info!("Initialisation complete"); + + // Main display loop + loop { + // Process input and window events + let mut terminal_lock = processor.process_events(&terminal, display.window()); + + // Handle config reloads + if let Some(ref path) = config_monitor.as_ref().and_then(Monitor::pending) { + // Clear old config messages from bar + terminal_lock.message_buffer_mut().remove_topic(config::SOURCE_FILE_PATH); + + if let Ok(new_config) = Config::reload_from(path) { + config = new_config.update_dynamic_title(options); + display.update_config(&config); + processor.update_config(&config); + terminal_lock.update_config(&config); + } + + terminal_lock.dirty = true; + } + + // Begin shutdown if the flag was raised + if terminal_lock.should_exit() || tty::process_should_exit() { + break; + } + + // Maybe draw the terminal + if terminal_lock.needs_draw() { + // Try to update the position of the input method editor + #[cfg(not(windows))] + display.update_ime_position(&terminal_lock); + + // Handle pending resize events + // + // The second argument is a list of types that want to be notified + // of display size changes. + display.handle_resize(&mut terminal_lock, &config, &mut resize_handle, &mut processor); + + drop(terminal_lock); + + // Draw the current state of the terminal + display.draw(&terminal, &config); + } + } + + loop_tx.send(Msg::Shutdown).expect("Error sending shutdown to event loop"); + + // FIXME patch notify library to have a shutdown method + // config_reloader.join().ok(); + + // Without explicitly detaching the console cmd won't redraw it's prompt + #[cfg(windows)] + unsafe { + FreeConsole(); + } + + info!("Goodbye"); + + Ok(()) +} diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml @@ -0,0 +1,68 @@ +[package] +name = "alacritty_terminal" +version = "0.3.2" +authors = ["Joe Wilm <joe@jwilm.com>"] +license = "Apache-2.0" +build = "build.rs" +description = "Library for writing terminal emulators" +readme = "../README.md" +homepage = "https://github.com/jwilm/alacritty" +edition = "2018" + +[dependencies] +libc = "0.2" +notify = "4" +bitflags = "1" +font = { path = "../font" } +errno = "0.2" +parking_lot = "0.7" +serde = "1" +serde_derive = "1" +serde_json = "1" +serde_yaml = "0.8" +vte = "0.3" +mio = "0.6" +mio-extras = "2" +copypasta = { path = "../copypasta" } +xdg = "2" +log = "0.4" +clap = "2" +fnv = "1" +unicode-width = "0.1" +glutin = { version = "0.21.0", features = ["icon_loading"] } +base64 = "0.10.0" +static_assertions = "0.3.0" +terminfo = "0.6.1" +url = "1.7.1" +crossbeam-channel = "0.3.8" + +[target.'cfg(unix)'.dependencies] +nix = "0.13" +signal-hook = { version = "0.1", features = ["mio-support"] } + +[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os="dragonfly", target_os="openbsd"))'.dependencies] +x11-dl = "2" + +[target.'cfg(windows)'.dependencies] +winpty = { path = "../winpty" } +mio-named-pipes = "0.1" +miow = "0.3" +dunce = "1.0" +winapi = { version = "0.3.7", features = ["impl-default", "winuser", "synchapi", "roerrorapi", "winerror", "wincon", "wincontypes"]} +dirs = "1.0" +widestring = "0.4" +mio-anonymous-pipes = "0.1" +image = "0.21.0" + +[target.'cfg(target_os = "macos")'.dependencies] +objc = "0.2.2" + +[features] +default = [] +# Enabling this feature makes shaders automatically reload when changed +live-shader-reload = [] +nightly = [] +bench = [] + +[build-dependencies] +gl_generator = "0.11.0" diff --git a/alacritty_terminal/build.rs b/alacritty_terminal/build.rs @@ -0,0 +1,28 @@ +// 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. + +use gl_generator::{Api, Fallbacks, GlobalGenerator, Profile, Registry}; + +use std::env; +use std::fs::File; +use std::path::Path; + +fn main() { + let dest = env::var("OUT_DIR").unwrap(); + let mut file = File::create(&Path::new(&dest).join("gl_bindings.rs")).unwrap(); + + Registry::new(Api::Gl, (4, 5), Profile::Core, Fallbacks::All, ["GL_ARB_blend_func_extended"]) + .write_bindings(GlobalGenerator, &mut file) + .unwrap(); +} diff --git a/src/ansi.rs b/alacritty_terminal/src/ansi.rs diff --git a/src/cli.rs b/alacritty_terminal/src/cli.rs diff --git a/src/config/bindings.rs b/alacritty_terminal/src/config/bindings.rs diff --git a/alacritty_terminal/src/config/mod.rs b/alacritty_terminal/src/config/mod.rs @@ -0,0 +1,2749 @@ +//! Configuration definitions and file loading +//! +//! Alacritty reads from a config file at startup to determine various runtime +//! parameters including font family and style, font size, etc. In the future, +//! the config file will also hold user and platform specific keybindings. +use std::borrow::Cow; +use std::collections::HashMap; +use std::fs::File; +use std::io::{self, Read, Write}; +use std::path::{Path, PathBuf}; +use std::str::FromStr; +use std::sync::mpsc; +use std::time::Duration; +use std::{env, fmt}; + +use font::Size; +use glutin::ModifiersState; +use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; +use serde::de::Error as SerdeError; +use serde::de::{MapAccess, Unexpected, Visitor}; +use serde::{self, de, Deserialize}; +use serde_yaml; + +use crate::ansi::CursorStyle; +use crate::cli::Options; +use crate::index::{Column, Line}; +use crate::input::{Action, Binding, KeyBinding, MouseBinding}; +use crate::term::color::Rgb; + +mod bindings; + +pub const SOURCE_FILE_PATH: &str = file!(); +const MAX_SCROLLBACK_LINES: u32 = 100_000; +static DEFAULT_ALACRITTY_CONFIG: &'static str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../alacritty.yml")); + +#[serde(default)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] +pub struct Selection { + #[serde(deserialize_with = "deserialize_escape_chars")] + pub semantic_escape_chars: String, + #[serde(deserialize_with = "failure_default")] + pub save_to_clipboard: bool, +} + +impl Default for Selection { + fn default() -> Selection { + Selection { + semantic_escape_chars: default_escape_chars(), + save_to_clipboard: Default::default(), + } + } +} + +fn deserialize_escape_chars<'a, D>(deserializer: D) -> ::std::result::Result<String, D::Error> +where + D: de::Deserializer<'a>, +{ + match String::deserialize(deserializer) { + Ok(escape_chars) => Ok(escape_chars), + Err(err) => { + error!("Problem with config: {}; using default value", err); + Ok(default_escape_chars()) + }, + } +} + +fn default_escape_chars() -> String { + String::from(",│`|:\"' ()[]{}<>") +} + +#[serde(default)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] +pub struct ClickHandler { + #[serde(deserialize_with = "deserialize_duration_ms")] + pub threshold: Duration, +} + +impl Default for ClickHandler { + fn default() -> Self { + ClickHandler { threshold: default_threshold_ms() } + } +} + +fn default_threshold_ms() -> Duration { + Duration::from_millis(300) +} + +fn deserialize_duration_ms<'a, D>(deserializer: D) -> ::std::result::Result<Duration, D::Error> +where + D: de::Deserializer<'a>, +{ + match u64::deserialize(deserializer) { + Ok(threshold_ms) => Ok(Duration::from_millis(threshold_ms)), + Err(err) => { + error!("Problem with config: {}; using default value", err); + Ok(default_threshold_ms()) + }, + } +} + +#[serde(default)] +#[derive(Default, Clone, Debug, Deserialize, PartialEq, Eq)] +pub struct Mouse { + #[serde(deserialize_with = "failure_default")] + pub double_click: ClickHandler, + #[serde(deserialize_with = "failure_default")] + pub triple_click: ClickHandler, + #[serde(deserialize_with = "failure_default")] + pub hide_when_typing: bool, + #[serde(deserialize_with = "failure_default")] + pub url: Url, + + // TODO: DEPRECATED + pub faux_scrollback_lines: Option<usize>, +} + +#[serde(default)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] +pub struct Url { + // Program for opening links + #[serde(deserialize_with = "deserialize_launcher")] + pub launcher: Option<CommandWrapper>, + + // Modifier used to open links + #[serde(deserialize_with = "deserialize_modifiers")] + pub modifiers: ModifiersState, +} + +fn deserialize_launcher<'a, D>( + deserializer: D, +) -> ::std::result::Result<Option<CommandWrapper>, D::Error> +where + D: de::Deserializer<'a>, +{ + let default = Url::default().launcher; + + // Deserialize to generic value + let val = match serde_yaml::Value::deserialize(deserializer) { + Ok(val) => val, + Err(err) => { + error!("Problem with config: {}; using {}", err, default.clone().unwrap().program()); + return Ok(default); + }, + }; + + // Accept `None` to disable the launcher + if val.as_str().filter(|v| v.to_lowercase() == "none").is_some() { + return Ok(None); + } + + match <Option<CommandWrapper>>::deserialize(val) { + Ok(launcher) => Ok(launcher), + Err(err) => { + error!("Problem with config: {}; using {}", err, default.clone().unwrap().program()); + Ok(default) + }, + } +} + +impl Default for Url { + fn default() -> Url { + Url { + #[cfg(not(any(target_os = "macos", windows)))] + launcher: Some(CommandWrapper::Just(String::from("xdg-open"))), + #[cfg(target_os = "macos")] + launcher: Some(CommandWrapper::Just(String::from("open"))), + #[cfg(windows)] + launcher: Some(CommandWrapper::Just(String::from("explorer"))), + modifiers: Default::default(), + } + } +} + +fn deserialize_modifiers<'a, D>(deserializer: D) -> ::std::result::Result<ModifiersState, D::Error> +where + D: de::Deserializer<'a>, +{ + ModsWrapper::deserialize(deserializer).map(ModsWrapper::into_inner) +} + +/// `VisualBellAnimations` are modeled after a subset of CSS transitions and Robert +/// Penner's Easing Functions. +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)] +pub enum VisualBellAnimation { + Ease, // CSS + EaseOut, // CSS + EaseOutSine, // Penner + EaseOutQuad, // Penner + EaseOutCubic, // Penner + EaseOutQuart, // Penner + EaseOutQuint, // Penner + EaseOutExpo, // Penner + EaseOutCirc, // Penner + Linear, +} + +impl Default for VisualBellAnimation { + fn default() -> Self { + VisualBellAnimation::EaseOutExpo + } +} + +#[serde(default)] +#[derive(Debug, Deserialize, PartialEq, Eq)] +pub struct VisualBellConfig { + /// Visual bell animation function + #[serde(deserialize_with = "failure_default")] + animation: VisualBellAnimation, + + /// Visual bell duration in milliseconds + #[serde(deserialize_with = "failure_default")] + duration: u16, + + /// Visual bell flash color + #[serde(deserialize_with = "rgb_from_hex")] + color: Rgb, +} + +impl Default for VisualBellConfig { + fn default() -> VisualBellConfig { + VisualBellConfig { + animation: Default::default(), + duration: Default::default(), + color: default_visual_bell_color(), + } + } +} + +fn default_visual_bell_color() -> Rgb { + Rgb { r: 255, g: 255, b: 255 } +} + +impl VisualBellConfig { + /// Visual bell animation + #[inline] + pub fn animation(&self) -> VisualBellAnimation { + self.animation + } + + /// Visual bell duration in milliseconds + #[inline] + pub fn duration(&self) -> Duration { + Duration::from_millis(u64::from(self.duration)) + } + + /// Visual bell flash color + #[inline] + pub fn color(&self) -> Rgb { + self.color + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq)] +pub struct Shell<'a> { + program: Cow<'a, str>, + + #[serde(default, deserialize_with = "failure_default")] + args: Vec<String>, +} + +impl<'a> Shell<'a> { + pub fn new<S>(program: S) -> Shell<'a> + where + S: Into<Cow<'a, str>>, + { + Shell { program: program.into(), args: Vec::new() } + } + + pub fn new_with_args<S>(program: S, args: Vec<String>) -> Shell<'a> + where + S: Into<Cow<'a, str>>, + { + Shell { program: program.into(), args } + } + + pub fn program(&self) -> &str { + &*self.program + } + + pub fn args(&self) -> &[String] { + self.args.as_slice() + } +} + +/// Wrapper around f32 that represents an alpha value between 0.0 and 1.0 +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Alpha(f32); + +impl Alpha { + pub fn new(value: f32) -> Self { + Alpha(Self::clamp_to_valid_range(value)) + } + + pub fn set(&mut self, value: f32) { + self.0 = Self::clamp_to_valid_range(value); + } + + #[inline] + pub fn get(self) -> f32 { + self.0 + } + + fn clamp_to_valid_range(value: f32) -> f32 { + if value < 0.0 { + 0.0 + } else if value > 1.0 { + 1.0 + } else { + value + } + } +} + +impl Default for Alpha { + fn default() -> Self { + Alpha(1.0) + } +} + +#[derive(Debug, Deserialize, Copy, Clone, PartialEq, Eq)] +pub enum StartupMode { + Windowed, + Maximized, + Fullscreen, + #[cfg(target_os = "macos")] + SimpleFullscreen, +} + +impl Default for StartupMode { + fn default() -> StartupMode { + StartupMode::Windowed + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Decorations { + Full, + Transparent, + Buttonless, + None, +} + +impl Default for Decorations { + fn default() -> Decorations { + Decorations::Full + } +} + +impl<'de> Deserialize<'de> for Decorations { + fn deserialize<D>(deserializer: D) -> ::std::result::Result<Decorations, D::Error> + where + D: de::Deserializer<'de>, + { + struct DecorationsVisitor; + + impl<'de> Visitor<'de> for DecorationsVisitor { + type Value = Decorations; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Some subset of full|transparent|buttonless|none") + } + + #[cfg(target_os = "macos")] + fn visit_str<E>(self, value: &str) -> ::std::result::Result<Decorations, E> + where + E: de::Error, + { + match value.to_lowercase().as_str() { + "transparent" => Ok(Decorations::Transparent), + "buttonless" => Ok(Decorations::Buttonless), + "none" => Ok(Decorations::None), + "full" => Ok(Decorations::Full), + "true" => { + error!( + "Deprecated decorations boolean value, use one of \ + transparent|buttonless|none|full instead; falling back to \"full\"" + ); + Ok(Decorations::Full) + }, + "false" => { + error!( + "Deprecated decorations boolean value, use one of \ + transparent|buttonless|none|full instead; falling back to \"none\"" + ); + Ok(Decorations::None) + }, + _ => { + error!("Invalid decorations value: {}; using default value", value); + Ok(Decorations::Full) + }, + } + } + + #[cfg(not(target_os = "macos"))] + fn visit_str<E>(self, value: &str) -> ::std::result::Result<Decorations, E> + where + E: de::Error, + { + match value.to_lowercase().as_str() { + "none" => Ok(Decorations::None), + "full" => Ok(Decorations::Full), + "true" => { + error!( + "Deprecated decorations boolean value, use one of none|full instead; \ + falling back to \"full\"" + ); + Ok(Decorations::Full) + }, + "false" => { + error!( + "Deprecated decorations boolean value, use one of none|full instead; \ + falling back to \"none\"" + ); + Ok(Decorations::None) + }, + "transparent" | "buttonless" => { + error!("macOS-only decorations value: {}; using default value", value); + Ok(Decorations::Full) + }, + _ => { + error!("Invalid decorations value: {}; using default value", value); + Ok(Decorations::Full) + }, + } + } + } + + deserializer.deserialize_str(DecorationsVisitor) + } +} + +#[serde(default)] +#[derive(Debug, Copy, Clone, Deserialize, PartialEq, Eq)] +pub struct WindowConfig { + /// Initial dimensions + #[serde(default, deserialize_with = "failure_default")] + dimensions: Dimensions, + + /// Initial position + #[serde(default, deserialize_with = "failure_default")] + position: Option<Delta<i32>>, + + /// Pixel padding + #[serde(deserialize_with = "deserialize_padding")] + padding: Delta<u8>, + + /// Draw the window with title bar / borders + #[serde(deserialize_with = "failure_default")] + decorations: Decorations, + + /// Spread out additional padding evenly + #[serde(deserialize_with = "failure_default")] + dynamic_padding: bool, + + /// Startup mode + #[serde(deserialize_with = "failure_default")] + startup_mode: StartupMode, + + /// TODO: DEPRECATED + #[serde(deserialize_with = "failure_default")] + start_maximized: Option<bool>, +} + +impl Default for WindowConfig { + fn default() -> Self { + WindowConfig { + dimensions: Default::default(), + position: Default::default(), + padding: default_padding(), + decorations: Default::default(), + dynamic_padding: Default::default(), + start_maximized: Default::default(), + startup_mode: Default::default(), + } + } +} + +fn default_padding() -> Delta<u8> { + Delta { x: 2, y: 2 } +} + +fn deserialize_padding<'a, D>(deserializer: D) -> ::std::result::Result<Delta<u8>, D::Error> +where + D: de::Deserializer<'a>, +{ + match Delta::deserialize(deserializer) { + Ok(delta) => Ok(delta), + Err(err) => { + error!("Problem with config: {}; using default value", err); + Ok(default_padding()) + }, + } +} + +impl WindowConfig { + pub fn decorations(&self) -> Decorations { + self.decorations + } + + pub fn dynamic_padding(&self) -> bool { + self.dynamic_padding + } + + pub fn startup_mode(&self) -> StartupMode { + self.startup_mode + } + + pub fn position(&self) -> Option<Delta<i32>> { + self.position + } +} + +/// Top-level config type +#[derive(Debug, PartialEq, Deserialize)] +pub struct Config { + /// Pixel padding + #[serde(default, deserialize_with = "failure_default")] + padding: Option<Delta<u8>>, + + /// TERM env variable + #[serde(default, deserialize_with = "failure_default")] + env: HashMap<String, String>, + + /// Font configuration + #[serde(default, deserialize_with = "failure_default")] + font: Font, + + /// Should show render timer + #[serde(default, deserialize_with = "failure_default")] + render_timer: bool, + + /// Should draw bold text with brighter colors instead of bold font + #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")] + draw_bold_text_with_bright_colors: bool, + + #[serde(default, deserialize_with = "failure_default")] + colors: Colors, + + /// Background opacity from 0.0 to 1.0 + #[serde(default, deserialize_with = "failure_default")] + background_opacity: Alpha, + + /// Window configuration + #[serde(default, deserialize_with = "failure_default")] + window: WindowConfig, + + /// Keybindings + #[serde(default = "default_key_bindings", deserialize_with = "deserialize_key_bindings")] + key_bindings: Vec<KeyBinding>, + + /// Bindings for the mouse + #[serde(default = "default_mouse_bindings", deserialize_with = "deserialize_mouse_bindings")] + mouse_bindings: Vec<MouseBinding>, + + #[serde(default, deserialize_with = "failure_default")] + selection: Selection, + + #[serde(default, deserialize_with = "failure_default")] + mouse: Mouse, + + /// Path to a shell program to run on startup + #[serde(default, deserialize_with = "failure_default")] + shell: Option<Shell<'static>>, + + /// Path where config was loaded from + #[serde(default, deserialize_with = "failure_default")] + config_path: Option<PathBuf>, + + /// Visual bell configuration + #[serde(default, deserialize_with = "failure_default")] + visual_bell: VisualBellConfig, + + /// Use dynamic title + #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")] + dynamic_title: bool, + + /// Live config reload + #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")] + live_config_reload: bool, + + /// Number of spaces in one tab + #[serde(default = "default_tabspaces", deserialize_with = "deserialize_tabspaces")] + tabspaces: usize, + + /// How much scrolling history to keep + #[serde(default, deserialize_with = "failure_default")] + scrolling: Scrolling, + + /// Cursor configuration + #[serde(default, deserialize_with = "failure_default")] + cursor: Cursor, + + /// Keep the log file after quitting + #[serde(default, deserialize_with = "failure_default")] + persistent_logging: bool, + + /// Enable experimental conpty backend instead of using winpty. + /// Will only take effect on Windows 10 Oct 2018 and later. + #[cfg(windows)] + #[serde(default, deserialize_with = "failure_default")] + enable_experimental_conpty_backend: bool, + + /// Send escape sequences using the alt key. + #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")] + alt_send_esc: bool, + + // TODO: DEPRECATED + custom_cursor_colors: Option<bool>, + + // TODO: DEPRECATED + hide_cursor_when_typing: Option<bool>, + + // TODO: DEPRECATED + cursor_style: Option<CursorStyle>, + + // TODO: DEPRECATED + unfocused_hollow_cursor: Option<bool>, + + // TODO: DEPRECATED + dimensions: Option<Dimensions>, +} + +impl Default for Config { + fn default() -> Self { + serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("default config is invalid") + } +} + +fn default_key_bindings() -> Vec<KeyBinding> { + bindings::default_key_bindings() +} + +fn default_mouse_bindings() -> Vec<MouseBinding> { + bindings::default_mouse_bindings() +} + +fn deserialize_key_bindings<'a, D>( + deserializer: D, +) -> ::std::result::Result<Vec<KeyBinding>, D::Error> +where + D: de::Deserializer<'a>, +{ + deserialize_bindings(deserializer, bindings::default_key_bindings()) +} + +fn deserialize_mouse_bindings<'a, D>( + deserializer: D, +) -> ::std::result::Result<Vec<MouseBinding>, D::Error> +where + D: de::Deserializer<'a>, +{ + deserialize_bindings(deserializer, bindings::default_mouse_bindings()) +} + +fn deserialize_bindings<'a, D, T>( + deserializer: D, + mut default: Vec<Binding<T>>, +) -> ::std::result::Result<Vec<Binding<T>>, D::Error> +where + D: de::Deserializer<'a>, + T: Copy + Eq + std::hash::Hash + std::fmt::Debug, + Binding<T>: de::Deserialize<'a>, +{ + let mut bindings: Vec<Binding<T>> = failure_default_vec(deserializer)?; + + for binding in bindings.iter() { + default.retain(|b| !b.triggers_match(binding)); + } + + bindings.extend(default); + + Ok(bindings) +} + +fn failure_default_vec<'a, D, T>(deserializer: D) -> ::std::result::Result<Vec<T>, D::Error> +where + D: de::Deserializer<'a>, + T: Deserialize<'a>, +{ + // Deserialize as generic vector + let vec = match Vec::<serde_yaml::Value>::deserialize(deserializer) { + Ok(vec) => vec, + Err(err) => { + error!("Problem with config: {}; using empty vector", err); + return Ok(Vec::new()); + }, + }; + + // Move to lossy vector + let mut bindings: Vec<T> = Vec::new(); + for value in vec { + match T::deserialize(value) { + Ok(binding) => bindings.push(binding), + Err(err) => { + error!("Problem with config: {}; skipping value", err); + }, + } + } + + Ok(bindings) +} + +fn default_tabspaces() -> usize { + 8 +} + +fn deserialize_tabspaces<'a, D>(deserializer: D) -> ::std::result::Result<usize, D::Error> +where + D: de::Deserializer<'a>, +{ + match usize::deserialize(deserializer) { + Ok(value) => Ok(value), + Err(err) => { + error!("Problem with config: {}; using 8", err); + Ok(default_tabspaces()) + }, + } +} + +fn deserialize_true_bool<'a, D>(deserializer: D) -> ::std::result::Result<bool, D::Error> +where + D: de::Deserializer<'a>, +{ + match bool::deserialize(deserializer) { + Ok(value) => Ok(value), + Err(err) => { + error!("Problem with config: {}; using true", err); + Ok(true) + }, + } +} + +fn default_true_bool() -> bool { + true +} + +fn failure_default<'a, D, T>(deserializer: D) -> ::std::result::Result<T, D::Error> +where + D: de::Deserializer<'a>, + T: Deserialize<'a> + Default, +{ + match T::deserialize(deserializer) { + Ok(value) => Ok(value), + Err(err) => { + error!("Problem with config: {}; using default value", err); + Ok(T::default()) + }, + } +} + +/// Struct for scrolling related settings +#[serde(default)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)] +pub struct Scrolling { + #[serde(deserialize_with = "deserialize_scrolling_history")] + pub history: u32, + #[serde(deserialize_with = "deserialize_scrolling_multiplier")] + pub multiplier: u8, + #[serde(deserialize_with = "deserialize_scrolling_multiplier")] + pub faux_multiplier: u8, + #[serde(deserialize_with = "failure_default")] + pub auto_scroll: bool, +} + +impl Default for Scrolling { + fn default() -> Self { + Self { + history: default_scrolling_history(), + multiplier: default_scrolling_multiplier(), + faux_multiplier: default_scrolling_multiplier(), + auto_scroll: Default::default(), + } + } +} + +fn default_scrolling_history() -> u32 { + 10_000 +} + +// Default for normal and faux scrolling +fn default_scrolling_multiplier() -> u8 { + 3 +} + +fn deserialize_scrolling_history<'a, D>(deserializer: D) -> ::std::result::Result<u32, D::Error> +where + D: de::Deserializer<'a>, +{ + match u32::deserialize(deserializer) { + Ok(lines) => { + if lines > MAX_SCROLLBACK_LINES { + error!( + "Problem with config: scrollback size is {}, but expected a maximum of {}; \ + using {1} instead", + lines, MAX_SCROLLBACK_LINES, + ); + Ok(MAX_SCROLLBACK_LINES) + } else { + Ok(lines) + } + }, + Err(err) => { + error!("Problem with config: {}; using default value", err); + Ok(default_scrolling_history()) + }, + } +} + +fn deserialize_scrolling_multiplier<'a, D>(deserializer: D) -> ::std::result::Result<u8, D::Error> +where + D: de::Deserializer<'a>, +{ + match u8::deserialize(deserializer) { + Ok(lines) => Ok(lines), + Err(err) => { + error!("Problem with config: {}; using default value", err); + Ok(default_scrolling_multiplier()) + }, + } +} + +/// Newtype for implementing deserialize on glutin Mods +/// +/// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the +/// impl below. +#[derive(Debug, Copy, Clone, Hash, Default, Eq, PartialEq)] +struct ModsWrapper(ModifiersState); + +impl ModsWrapper { + fn into_inner(self) -> ModifiersState { + self.0 + } +} + +impl<'a> de::Deserialize<'a> for ModsWrapper { + fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> + where + D: de::Deserializer<'a>, + { + struct ModsVisitor; + + impl<'a> Visitor<'a> for ModsVisitor { + type Value = ModsWrapper; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Some subset of Command|Shift|Super|Alt|Option|Control") + } + + fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModsWrapper, E> + where + E: de::Error, + { + let mut res = ModifiersState::default(); + for modifier in value.split('|') { + match modifier.trim() { + "Command" | "Super" => res.logo = true, + "Shift" => res.shift = true, + "Alt" | "Option" => res.alt = true, + "Control" => res.ctrl = true, + "None" => (), + _ => error!("Unknown modifier {:?}", modifier), + } + } + + Ok(ModsWrapper(res)) + } + } + + deserializer.deserialize_str(ModsVisitor) + } +} + +struct ActionWrapper(crate::input::Action); + +impl ActionWrapper { + fn into_inner(self) -> crate::input::Action { + self.0 + } +} + +impl<'a> de::Deserialize<'a> for ActionWrapper { + fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> + where + D: de::Deserializer<'a>, + { + struct ActionVisitor; + + impl<'a> Visitor<'a> for ActionVisitor { + type Value = ActionWrapper; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str( + "Paste, Copy, PasteSelection, IncreaseFontSize, DecreaseFontSize, \ + ResetFontSize, ScrollPageUp, ScrollPageDown, ScrollLineUp, ScrollLineDown, \ + ScrollToTop, ScrollToBottom, ClearHistory, Hide, ClearLogNotice, \ + SpawnNewInstance, ToggleFullscreen, ToggleSimpleFullscreen, None or Quit", + ) + } + + fn visit_str<E>(self, value: &str) -> ::std::result::Result<ActionWrapper, E> + where + E: de::Error, + { + Ok(ActionWrapper(match value { + "Paste" => Action::Paste, + "Copy" => Action::Copy, + "PasteSelection" => Action::PasteSelection, + "IncreaseFontSize" => Action::IncreaseFontSize, + "DecreaseFontSize" => Action::DecreaseFontSize, + "ResetFontSize" => Action::ResetFontSize, + "ScrollPageUp" => Action::ScrollPageUp, + "ScrollPageDown" => Action::ScrollPageDown, + "ScrollLineUp" => Action::ScrollLineUp, + "ScrollLineDown" => Action::ScrollLineDown, + "ScrollToTop" => Action::ScrollToTop, + "ScrollToBottom" => Action::ScrollToBottom, + "ClearHistory" => Action::ClearHistory, + "Hide" => Action::Hide, + "Quit" => Action::Quit, + "ClearLogNotice" => Action::ClearLogNotice, + "SpawnNewInstance" => Action::SpawnNewInstance, + "ToggleFullscreen" => Action::ToggleFullscreen, + #[cfg(target_os = "macos")] + "ToggleSimpleFullscreen" => Action::ToggleSimpleFullscreen, + "None" => Action::None, + _ => return Err(E::invalid_value(Unexpected::Str(value), &self)), + })) + } + } + deserializer.deserialize_str(ActionVisitor) + } +} + +#[serde(untagged)] +#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] +pub enum CommandWrapper { + Just(String), + WithArgs { + program: String, + #[serde(default)] + args: Vec<String>, + }, +} + +impl CommandWrapper { + pub fn program(&self) -> &str { + match self { + CommandWrapper::Just(program) => program, + CommandWrapper::WithArgs { program, .. } => program, + } + } + + pub fn args(&self) -> &[String] { + match self { + CommandWrapper::Just(_) => &[], + CommandWrapper::WithArgs { args, .. } => args, + } + } +} + +use crate::term::{mode, TermMode}; + +struct ModeWrapper { + pub mode: TermMode, + pub not_mode: TermMode, +} + +impl<'a> de::Deserialize<'a> for ModeWrapper { + fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> + where + D: de::Deserializer<'a>, + { + struct ModeVisitor; + + impl<'a> Visitor<'a> for ModeVisitor { + type Value = ModeWrapper; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Combination of AppCursor | AppKeypad, possibly with negation (~)") + } + + fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModeWrapper, E> + where + E: de::Error, + { + let mut res = ModeWrapper { mode: TermMode::empty(), not_mode: TermMode::empty() }; + + for modifier in value.split('|') { + match modifier.trim() { + "AppCursor" => res.mode |= mode::TermMode::APP_CURSOR, + "~AppCursor" => res.not_mode |= mode::TermMode::APP_CURSOR, + "AppKeypad" => res.mode |= mode::TermMode::APP_KEYPAD, + "~AppKeypad" => res.not_mode |= mode::TermMode::APP_KEYPAD, + "~Alt" => res.not_mode |= mode::TermMode::ALT_SCREEN, + "Alt" => res.mode |= mode::TermMode::ALT_SCREEN, + _ => error!("Unknown mode {:?}", modifier), + } + } + + Ok(res) + } + } + deserializer.deserialize_str(ModeVisitor) + } +} + +struct MouseButton(::glutin::MouseButton); + +impl MouseButton { + fn into_inner(self) -> ::glutin::MouseButton { + self.0 + } +} + +impl<'a> de::Deserialize<'a> for MouseButton { + fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> + where + D: de::Deserializer<'a>, + { + struct MouseButtonVisitor; + + impl<'a> Visitor<'a> for MouseButtonVisitor { + type Value = MouseButton; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Left, Right, Middle, or a number") + } + + fn visit_str<E>(self, value: &str) -> ::std::result::Result<MouseButton, E> + where + E: de::Error, + { + match value { + "Left" => Ok(MouseButton(::glutin::MouseButton::Left)), + "Right" => Ok(MouseButton(::glutin::MouseButton::Right)), + "Middle" => Ok(MouseButton(::glutin::MouseButton::Middle)), + _ => { + if let Ok(index) = u8::from_str(value) { + Ok(MouseButton(::glutin::MouseButton::Other(index))) + } else { + Err(E::invalid_value(Unexpected::Str(value), &self)) + } + }, + } + } + } + + deserializer.deserialize_str(MouseButtonVisitor) + } +} + +/// Bindings are deserialized into a `RawBinding` before being parsed as a +/// `KeyBinding` or `MouseBinding`. +#[derive(PartialEq, Eq)] +struct RawBinding { + key: Option<Key>, + mouse: Option<::glutin::MouseButton>, + mods: ModifiersState, + mode: TermMode, + notmode: TermMode, + action: Action, +} + +impl RawBinding { + fn into_mouse_binding(self) -> ::std::result::Result<MouseBinding, Self> { + if let Some(mouse) = self.mouse { + Ok(Binding { + trigger: mouse, + mods: self.mods, + action: self.action, + mode: self.mode, + notmode: self.notmode, + }) + } else { + Err(self) + } + } + + fn into_key_binding(self) -> ::std::result::Result<KeyBinding, Self> { + if let Some(key) = self.key { + Ok(KeyBinding { + trigger: key, + mods: self.mods, + action: self.action, + mode: self.mode, + notmode: self.notmode, + }) + } else { + Err(self) + } + } +} + +impl<'a> de::Deserialize<'a> for RawBinding { + fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> + where + D: de::Deserializer<'a>, + { + enum Field { + Key, + Mods, + Mode, + Action, + Chars, + Mouse, + Command, + } + + impl<'a> de::Deserialize<'a> for Field { + fn deserialize<D>(deserializer: D) -> ::std::result::Result<Field, D::Error> + where + D: de::Deserializer<'a>, + { + struct FieldVisitor; + + static FIELDS: &'static [&'static str] = + &["key", "mods", "mode", "action", "chars", "mouse", "command"]; + + impl<'a> Visitor<'a> for FieldVisitor { + type Value = Field; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("binding fields") + } + + fn visit_str<E>(self, value: &str) -> ::std::result::Result<Field, E> + where + E: de::Error, + { + match value { + "key" => Ok(Field::Key), + "mods" => Ok(Field::Mods), + "mode" => Ok(Field::Mode), + "action" => Ok(Field::Action), + "chars" => Ok(Field::Chars), + "mouse" => Ok(Field::Mouse), + "command" => Ok(Field::Command), + _ => Err(E::unknown_field(value, FIELDS)), + } + } + } + + deserializer.deserialize_str(FieldVisitor) + } + } + + struct RawBindingVisitor; + impl<'a> Visitor<'a> for RawBindingVisitor { + type Value = RawBinding; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("binding specification") + } + + fn visit_map<V>(self, mut map: V) -> ::std::result::Result<RawBinding, V::Error> + where + V: MapAccess<'a>, + { + let mut mods: Option<ModifiersState> = None; + let mut key: Option<Key> = None; + let mut chars: Option<String> = None; + let mut action: Option<crate::input::Action> = None; + let mut mode: Option<TermMode> = None; + let mut not_mode: Option<TermMode> = None; + let mut mouse: Option<::glutin::MouseButton> = None; + let mut command: Option<CommandWrapper> = None; + + use ::serde::de::Error; + + while let Some(struct_key) = map.next_key::<Field>()? { + match struct_key { + Field::Key => { + if key.is_some() { + return Err(<V::Error as Error>::duplicate_field("key")); + } + + let val = map.next_value::<serde_yaml::Value>()?; + if val.is_u64() { + let scancode = val.as_u64().unwrap(); + if scancode > u64::from(::std::u32::MAX) { + return Err(<V::Error as Error>::custom(format!( + "Invalid key binding, scancode too big: {}", + scancode + ))); + } + key = Some(Key::Scancode(scancode as u32)); + } else { + let k = Key::deserialize(val).map_err(V::Error::custom)?; + key = Some(k); + } + }, + Field::Mods => { + if mods.is_some() { + return Err(<V::Error as Error>::duplicate_field("mods")); + } + + mods = Some(map.next_value::<ModsWrapper>()?.into_inner()); + }, + Field::Mode => { + if mode.is_some() { + return Err(<V::Error as Error>::duplicate_field("mode")); + } + + let mode_deserializer = map.next_value::<ModeWrapper>()?; + mode = Some(mode_deserializer.mode); + not_mode = Some(mode_deserializer.not_mode); + }, + Field::Action => { + if action.is_some() { + return Err(<V::Error as Error>::duplicate_field("action")); + } + + action = Some(map.next_value::<ActionWrapper>()?.into_inner()); + }, + Field::Chars => { + if chars.is_some() { + return Err(<V::Error as Error>::duplicate_field("chars")); + } + + chars = Some(map.next_value()?); + }, + Field::Mouse => { + if chars.is_some() { + return Err(<V::Error as Error>::duplicate_field("mouse")); + } + + mouse = Some(map.next_value::<MouseButton>()?.into_inner()); + }, + Field::Command => { + if command.is_some() { + return Err(<V::Error as Error>::duplicate_field("command")); + } + + command = Some(map.next_value::<CommandWrapper>()?); + }, + } + } + + let action = match (action, chars, command) { + (Some(action), None, None) => action, + (None, Some(chars), None) => Action::Esc(chars), + (None, None, Some(cmd)) => match cmd { + CommandWrapper::Just(program) => Action::Command(program, vec![]), + CommandWrapper::WithArgs { program, args } => { + Action::Command(program, args) + }, + }, + (None, None, None) => { + return Err(V::Error::custom("must specify chars, action or command")); + }, + _ => { + return Err(V::Error::custom("must specify only chars, action or command")) + }, + }; + + let mode = mode.unwrap_or_else(TermMode::empty); + let not_mode = not_mode.unwrap_or_else(TermMode::empty); + let mods = mods.unwrap_or_else(ModifiersState::default); + + if mouse.is_none() && key.is_none() { + return Err(V::Error::custom("bindings require mouse button or key")); + } + + Ok(RawBinding { mode, notmode: not_mode, action, key, mouse, mods }) + } + } + + const FIELDS: &[&str] = &["key", "mods", "mode", "action", "chars", "mouse", "command"]; + + deserializer.deserialize_struct("RawBinding", FIELDS, RawBindingVisitor) + } +} + +impl<'a> de::Deserialize<'a> for Alpha { + fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> + where + D: de::Deserializer<'a>, + { + let value = f32::deserialize(deserializer)?; + Ok(Alpha::new(value)) + } +} + +impl<'a> de::Deserialize<'a> for MouseBinding { + fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> + where + D: de::Deserializer<'a>, + { + let raw = RawBinding::deserialize(deserializer)?; + raw.into_mouse_binding().map_err(|_| D::Error::custom("expected mouse binding")) + } +} + +impl<'a> de::Deserialize<'a> for KeyBinding { + fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> + where + D: de::Deserializer<'a>, + { + let raw = RawBinding::deserialize(deserializer)?; + raw.into_key_binding().map_err(|_| D::Error::custom("expected key binding")) + } +} + +/// Errors occurring during config loading +#[derive(Debug)] +pub enum Error { + /// Config file not found + NotFound, + + /// Config file empty + Empty, + + /// Couldn't read $HOME environment variable + ReadingEnvHome(env::VarError), + + /// io error reading file + Io(io::Error), + + /// Not valid yaml or missing parameters + Yaml(serde_yaml::Error), +} + +#[serde(default)] +#[derive(Debug, Deserialize, PartialEq, Eq)] +pub struct Colors { + #[serde(deserialize_with = "failure_default")] + pub primary: PrimaryColors, + #[serde(deserialize_with = "failure_default")] + pub cursor: CursorColors, + #[serde(deserialize_with = "failure_default")] + pub selection: SelectionColors, + #[serde(deserialize_with = "deserialize_normal_colors")] + pub normal: AnsiColors, + #[serde(deserialize_with = "deserialize_bright_colors")] + pub bright: AnsiColors, + #[serde(deserialize_with = "failure_default")] + pub dim: Option<AnsiColors>, + #[serde(deserialize_with = "failure_default_vec")] + pub indexed_colors: Vec<IndexedColor>, +} + +impl Default for Colors { + fn default() -> Colors { + Colors { + primary: Default::default(), + cursor: Default::default(), + selection: Default::default(), + normal: default_normal_colors(), + bright: default_bright_colors(), + dim: Default::default(), + indexed_colors: Default::default(), + } + } +} + +fn default_normal_colors() -> AnsiColors { + AnsiColors { + black: Rgb { r: 0x00, g: 0x00, b: 0x00 }, + red: Rgb { r: 0xd5, g: 0x4e, b: 0x53 }, + green: Rgb { r: 0xb9, g: 0xca, b: 0x4a }, + yellow: Rgb { r: 0xe6, g: 0xc5, b: 0x47 }, + blue: Rgb { r: 0x7a, g: 0xa6, b: 0xda }, + magenta: Rgb { r: 0xc3, g: 0x97, b: 0xd8 }, + cyan: Rgb { r: 0x70, g: 0xc0, b: 0xba }, + white: Rgb { r: 0xea, g: 0xea, b: 0xea }, + } +} + +fn default_bright_colors() -> AnsiColors { + AnsiColors { + black: Rgb { r: 0x66, g: 0x66, b: 0x66 }, + red: Rgb { r: 0xff, g: 0x33, b: 0x34 }, + green: Rgb { r: 0x9e, g: 0xc4, b: 0x00 }, + yellow: Rgb { r: 0xe7, g: 0xc5, b: 0x47 }, + blue: Rgb { r: 0x7a, g: 0xa6, b: 0xda }, + magenta: Rgb { r: 0xb7, g: 0x7e, b: 0xe0 }, + cyan: Rgb { r: 0x54, g: 0xce, b: 0xd6 }, + white: Rgb { r: 0xff, g: 0xff, b: 0xff }, + } +} + +fn deserialize_normal_colors<'a, D>(deserializer: D) -> ::std::result::Result<AnsiColors, D::Error> +where + D: de::Deserializer<'a>, +{ + match AnsiColors::deserialize(deserializer) { + Ok(escape_chars) => Ok(escape_chars), + Err(err) => { + error!("Problem with config: {}; using default value", err); + Ok(default_normal_colors()) + }, + } +} + +fn deserialize_bright_colors<'a, D>(deserializer: D) -> ::std::result::Result<AnsiColors, D::Error> +where + D: de::Deserializer<'a>, +{ + match AnsiColors::deserialize(deserializer) { + Ok(escape_chars) => Ok(escape_chars), + Err(err) => { + error!("Problem with config: {}; using default value", err); + Ok(default_bright_colors()) + }, + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq)] +pub struct IndexedColor { + #[serde(deserialize_with = "deserialize_color_index")] + pub index: u8, + #[serde(deserialize_with = "rgb_from_hex")] + pub color: Rgb, +} + +fn deserialize_color_index<'a, D>(deserializer: D) -> ::std::result::Result<u8, D::Error> +where + D: de::Deserializer<'a>, +{ + match u8::deserialize(deserializer) { + Ok(index) => { + if index < 16 { + error!( + "Problem with config: indexed_color's index is {}, but a value bigger than 15 \ + was expected; ignoring setting", + index + ); + + // Return value out of range to ignore this color + Ok(0) + } else { + Ok(index) + } + }, + Err(err) => { + error!("Problem with config: {}; ignoring setting", err); + + // Return value out of range to ignore this color + Ok(0) + }, + } +} + +#[serde(default)] +#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq)] +pub struct Cursor { + #[serde(deserialize_with = "failure_default")] + pub style: CursorStyle, + #[serde(deserialize_with = "deserialize_true_bool")] + pub unfocused_hollow: bool, +} + +impl Default for Cursor { + fn default() -> Self { + Self { style: Default::default(), unfocused_hollow: true } + } +} + +#[serde(default)] +#[derive(Debug, Copy, Clone, Default, Deserialize, PartialEq, Eq)] +pub struct CursorColors { + #[serde(deserialize_with = "deserialize_optional_color")] + pub text: Option<Rgb>, + #[serde(deserialize_with = "deserialize_optional_color")] + pub cursor: Option<Rgb>, +} + +#[serde(default)] +#[derive(Debug, Copy, Clone, Default, Deserialize, PartialEq, Eq)] +pub struct SelectionColors { + #[serde(deserialize_with = "deserialize_optional_color")] + pub text: Option<Rgb>, + #[serde(deserialize_with = "deserialize_optional_color")] + pub background: Option<Rgb>, +} + +#[serde(default)] +#[derive(Debug, Deserialize, PartialEq, Eq)] +pub struct PrimaryColors { + #[serde(deserialize_with = "rgb_from_hex")] + pub background: Rgb, + #[serde(deserialize_with = "rgb_from_hex")] + pub foreground: Rgb, + #[serde(deserialize_with = "deserialize_optional_color")] + pub bright_foreground: Option<Rgb>, + #[serde(deserialize_with = "deserialize_optional_color")] + pub dim_foreground: Option<Rgb>, +} + +impl Default for PrimaryColors { + fn default() -> Self { + PrimaryColors { + background: default_background(), + foreground: default_foreground(), + bright_foreground: Default::default(), + dim_foreground: Default::default(), + } + } +} + +fn deserialize_optional_color<'a, D>( + deserializer: D, +) -> ::std::result::Result<Option<Rgb>, D::Error> +where + D: de::Deserializer<'a>, +{ + match Option::deserialize(deserializer) { + Ok(Some(color)) => { + let color: serde_yaml::Value = color; + Ok(Some(rgb_from_hex(color).unwrap())) + }, + Ok(None) => Ok(None), + Err(err) => { + error!("Problem with config: {}; using standard foreground color", err); + Ok(None) + }, + } +} + +fn default_background() -> Rgb { + Rgb { r: 0, g: 0, b: 0 } +} + +fn default_foreground() -> Rgb { + Rgb { r: 0xea, g: 0xea, b: 0xea } +} + +/// The 8-colors sections of config +#[derive(Debug, Deserialize, PartialEq, Eq)] +pub struct AnsiColors { + #[serde(deserialize_with = "rgb_from_hex")] + pub black: Rgb, + #[serde(deserialize_with = "rgb_from_hex")] + pub red: Rgb, + #[serde(deserialize_with = "rgb_from_hex")] + pub green: Rgb, + #[serde(deserialize_with = "rgb_from_hex")] + pub yellow: Rgb, + #[serde(deserialize_with = "rgb_from_hex")] + pub blue: Rgb, + #[serde(deserialize_with = "rgb_from_hex")] + pub magenta: Rgb, + #[serde(deserialize_with = "rgb_from_hex")] + pub cyan: Rgb, + #[serde(deserialize_with = "rgb_from_hex")] + pub white: Rgb, +} + +/// Deserialize an Rgb from a hex string +/// +/// This is *not* the deserialize impl for Rgb since we want a symmetric +/// serialize/deserialize impl for ref tests. +fn rgb_from_hex<'a, D>(deserializer: D) -> ::std::result::Result<Rgb, D::Error> +where + D: de::Deserializer<'a>, +{ + struct RgbVisitor; + + impl<'a> Visitor<'a> for RgbVisitor { + type Value = Rgb; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("hex color like 0xff00ff") + } + + fn visit_str<E>(self, value: &str) -> ::std::result::Result<Rgb, E> + where + E: ::serde::de::Error, + { + Rgb::from_str(&value[..]) + .map_err(|_| E::custom("failed to parse rgb; expected hex color like 0xff00ff")) + } + } + + let rgb = deserializer.deserialize_str(RgbVisitor); + + // Use #ff00ff as fallback color + match rgb { + Ok(rgb) => Ok(rgb), + Err(err) => { + error!("Problem with config: {}; using color #ff00ff", err); + Ok(Rgb { r: 255, g: 0, b: 255 }) + }, + } +} + +impl FromStr for Rgb { + type Err = (); + + fn from_str(s: &str) -> ::std::result::Result<Rgb, ()> { + let mut chars = s.chars(); + let mut rgb = Rgb::default(); + + macro_rules! component { + ($($c:ident),*) => { + $( + match chars.next().and_then(|c| c.to_digit(16)) { + Some(val) => rgb.$c = (val as u8) << 4, + None => return Err(()) + } + + match chars.next().and_then(|c| c.to_digit(16)) { + Some(val) => rgb.$c |= val as u8, + None => return Err(()) + } + )* + } + } + + match chars.next() { + Some('0') => { + if chars.next() != Some('x') { + return Err(()); + } + }, + Some('#') => (), + _ => return Err(()), + } + + component!(r, g, b); + + Ok(rgb) + } +} + +impl ::std::error::Error for Error { + fn cause(&self) -> Option<&dyn (::std::error::Error)> { + match *self { + Error::NotFound | Error::Empty => None, + Error::ReadingEnvHome(ref err) => Some(err), + Error::Io(ref err) => Some(err), + Error::Yaml(ref err) => Some(err), + } + } + + fn description(&self) -> &str { + match *self { + Error::NotFound => "Couldn't locate config file", + Error::Empty => "Empty config file", + Error::ReadingEnvHome(ref err) => err.description(), + Error::Io(ref err) => err.description(), + Error::Yaml(ref err) => err.description(), + } + } +} + +impl ::std::fmt::Display for Error { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + match *self { + Error::NotFound | Error::Empty => { + write!(f, "{}", ::std::error::Error::description(self)) + }, + Error::ReadingEnvHome(ref err) => { + write!(f, "Couldn't read $HOME environment variable: {}", err) + }, + Error::Io(ref err) => write!(f, "Error reading config file: {}", err), + Error::Yaml(ref err) => write!(f, "Problem with config: {}", err), + } + } +} + +impl From<env::VarError> for Error { + fn from(val: env::VarError) -> Error { + Error::ReadingEnvHome(val) + } +} + +impl From<io::Error> for Error { + fn from(val: io::Error) -> Error { + if val.kind() == io::ErrorKind::NotFound { + Error::NotFound + } else { + Error::Io(val) + } + } +} + +impl From<serde_yaml::Error> for Error { + fn from(val: serde_yaml::Error) -> Error { + Error::Yaml(val) + } +} + +/// Result from config loading +pub type Result<T> = ::std::result::Result<T, Error>; + +impl Config { + /// Get the location of the first found default config file paths + /// according to the following order: + /// + /// 1. $XDG_CONFIG_HOME/alacritty/alacritty.yml + /// 2. $XDG_CONFIG_HOME/alacritty.yml + /// 3. $HOME/.config/alacritty/alacritty.yml + /// 4. $HOME/.alacritty.yml + #[cfg(not(windows))] + pub fn installed_config<'a>() -> Option<Cow<'a, Path>> { + // Try using XDG location by default + ::xdg::BaseDirectories::with_prefix("alacritty") + .ok() + .and_then(|xdg| xdg.find_config_file("alacritty.yml")) + .or_else(|| { + ::xdg::BaseDirectories::new() + .ok() + .and_then(|fallback| fallback.find_config_file("alacritty.yml")) + }) + .or_else(|| { + if let Ok(home) = env::var("HOME") { + // Fallback path: $HOME/.config/alacritty/alacritty.yml + let fallback = PathBuf::from(&home).join(".config/alacritty/alacritty.yml"); + if fallback.exists() { + return Some(fallback); + } + // Fallback path: $HOME/.alacritty.yml + let fallback = PathBuf::from(&home).join(".alacritty.yml"); + if fallback.exists() { + return Some(fallback); + } + } + None + }) + .map(Into::into) + } + + // TODO: Remove old configuration location warning (Deprecated 03/12/2018) + #[cfg(windows)] + pub fn installed_config<'a>() -> Option<Cow<'a, Path>> { + let old = dirs::home_dir().map(|path| path.join("alacritty.yml")); + let new = dirs::config_dir().map(|path| path.join("alacritty\\alacritty.yml")); + + if let Some(old_path) = old.as_ref().filter(|old| old.exists()) { + warn!( + "Found configuration at: {}; this file should be moved to the new location: {}", + old_path.to_string_lossy(), + new.as_ref().map(|new| new.to_string_lossy()).unwrap(), + ); + + old.map(Cow::from) + } else { + new.filter(|new| new.exists()).map(Cow::from) + } + } + + #[cfg(not(windows))] + pub fn write_defaults() -> io::Result<Cow<'static, Path>> { + let path = xdg::BaseDirectories::with_prefix("alacritty") + .map_err(|err| io::Error::new(io::ErrorKind::NotFound, err.to_string().as_str())) + .and_then(|p| p.place_config_file("alacritty.yml"))?; + + File::create(&path)?.write_all(DEFAULT_ALACRITTY_CONFIG.as_bytes())?; + + Ok(path.into()) + } + + #[cfg(windows)] + pub fn write_defaults() -> io::Result<Cow<'static, Path>> { + let mut path = dirs::config_dir().ok_or_else(|| { + io::Error::new(io::ErrorKind::NotFound, "Couldn't find profile directory") + })?; + + path = path.join("alacritty/alacritty.yml"); + + std::fs::create_dir_all(path.parent().unwrap())?; + + File::create(&path)?.write_all(DEFAULT_ALACRITTY_CONFIG.as_bytes())?; + + Ok(path.into()) + } + + /// Get list of colors + /// + /// The ordering returned here is expected by the terminal. Colors are simply indexed in this + /// array for performance. + pub fn colors(&self) -> &Colors { + &self.colors + } + + #[inline] + pub fn background_opacity(&self) -> Alpha { + self.background_opacity + } + + pub fn key_bindings(&self) -> &[KeyBinding] { + &self.key_bindings[..] + } + + pub fn mouse_bindings(&self) -> &[MouseBinding] { + &self.mouse_bindings[..] + } + + pub fn mouse(&self) -> &Mouse { + &self.mouse + } + + pub fn selection(&self) -> &Selection { + &self.selection + } + + pub fn tabspaces(&self) -> usize { + self.tabspaces + } + + pub fn padding(&self) -> &Delta<u8> { + self.padding.as_ref().unwrap_or(&self.window.padding) + } + + #[inline] + pub fn draw_bold_text_with_bright_colors(&self) -> bool { + self.draw_bold_text_with_bright_colors + } + + /// Get font config + #[inline] + pub fn font(&self) -> &Font { + &self.font + } + + /// Get window dimensions + #[inline] + pub fn dimensions(&self) -> Dimensions { + self.dimensions.unwrap_or(self.window.dimensions) + } + + /// Get window config + #[inline] + pub fn window(&self) -> &WindowConfig { + &self.window + } + + /// Get visual bell config + #[inline] + pub fn visual_bell(&self) -> &VisualBellConfig { + &self.visual_bell + } + + /// Should show render timer + #[inline] + pub fn render_timer(&self) -> bool { + self.render_timer + } + + #[cfg(target_os = "macos")] + #[inline] + pub fn use_thin_strokes(&self) -> bool { + self.font.use_thin_strokes + } + + #[cfg(not(target_os = "macos"))] + #[inline] + pub fn use_thin_strokes(&self) -> bool { + false + } + + pub fn path(&self) -> Option<&Path> { + self.config_path.as_ref().map(PathBuf::as_path) + } + + pub fn shell(&self) -> Option<&Shell<'_>> { + self.shell.as_ref() + } + + pub fn env(&self) -> &HashMap<String, String> { + &self.env + } + + /// Should hide mouse cursor when typing + #[inline] + pub fn hide_mouse_when_typing(&self) -> bool { + self.hide_cursor_when_typing.unwrap_or(self.mouse.hide_when_typing) + } + + /// Style of the cursor + #[inline] + pub fn cursor_style(&self) -> CursorStyle { + self.cursor_style.unwrap_or(self.cursor.style) + } + + /// Use hollow block cursor when unfocused + #[inline] + pub fn unfocused_hollow_cursor(&self) -> bool { + self.unfocused_hollow_cursor.unwrap_or(self.cursor.unfocused_hollow) + } + + /// Live config reload + #[inline] + pub fn live_config_reload(&self) -> bool { + self.live_config_reload + } + + #[inline] + pub fn dynamic_title(&self) -> bool { + self.dynamic_title + } + + /// Scrolling settings + #[inline] + pub fn scrolling(&self) -> Scrolling { + self.scrolling + } + + /// Cursor foreground color + #[inline] + pub fn cursor_text_color(&self) -> Option<Rgb> { + self.colors.cursor.text + } + + /// Cursor background color + #[inline] + pub fn cursor_cursor_color(&self) -> Option<Rgb> { + self.colors.cursor.cursor + } + + /// Enable experimental conpty backend (Windows only) + #[cfg(windows)] + #[inline] + pub fn enable_experimental_conpty_backend(&self) -> bool { + self.enable_experimental_conpty_backend + } + + /// Send escape sequences using the alt key + #[inline] + pub fn alt_send_esc(&self) -> bool { + self.alt_send_esc + } + + // Update the history size, used in ref tests + pub fn set_history(&mut self, history: u32) { + self.scrolling.history = history; + } + + /// Keep the log file after quitting Alacritty + #[inline] + pub fn persistent_logging(&self) -> bool { + self.persistent_logging + } + + /// Overrides the `dynamic_title` configuration based on `--title`. + pub fn update_dynamic_title(mut self, options: &Options) -> Self { + if options.title.is_some() { + self.dynamic_title = false; + } + self + } + + pub fn load_from(path: PathBuf) -> Config { + let mut config = Config::reload_from(&path).unwrap_or_else(|_| Config::default()); + config.config_path = Some(path); + config + } + + pub fn reload_from(path: &PathBuf) -> Result<Config> { + match Config::read_config(path) { + Ok(config) => Ok(config), + Err(err) => { + error!("Unable to load config {:?}: {}", path, err); + Err(err) + }, + } + } + + fn read_config(path: &PathBuf) -> Result<Config> { + let mut contents = String::new(); + File::open(path)?.read_to_string(&mut contents)?; + + // Prevent parsing error with empty string + if contents.is_empty() { + return Ok(Config::default()); + } + + let mut config: Config = serde_yaml::from_str(&contents)?; + config.print_deprecation_warnings(); + + Ok(config) + } + + fn print_deprecation_warnings(&mut self) { + if self.dimensions.is_some() { + warn!("Config dimensions is deprecated; please use window.dimensions instead"); + } + + if self.padding.is_some() { + warn!("Config padding is deprecated; please use window.padding instead"); + } + + if self.mouse.faux_scrollback_lines.is_some() { + warn!( + "Config mouse.faux_scrollback_lines is deprecated; please use \ + mouse.faux_scrolling_lines instead" + ); + } + + if let Some(custom_cursor_colors) = self.custom_cursor_colors { + warn!("Config custom_cursor_colors is deprecated"); + + if !custom_cursor_colors { + self.colors.cursor.cursor = None; + self.colors.cursor.text = None; + } + } + + if self.cursor_style.is_some() { + warn!("Config cursor_style is deprecated; please use cursor.style instead"); + } + + if self.hide_cursor_when_typing.is_some() { + warn!( + "Config hide_cursor_when_typing is deprecated; please use mouse.hide_when_typing \ + instead" + ); + } + + if self.unfocused_hollow_cursor.is_some() { + warn!( + "Config unfocused_hollow_cursor is deprecated; please use cursor.unfocused_hollow \ + instead" + ); + } + + if let Some(start_maximized) = self.window.start_maximized { + warn!( + "Config window.start_maximized is deprecated; please use window.startup_mode \ + instead" + ); + + // While `start_maximized` is deprecated its setting takes precedence. + if start_maximized { + self.window.startup_mode = StartupMode::Maximized; + } + } + } +} + +/// Window Dimensions +/// +/// Newtype to avoid passing values incorrectly +#[serde(default)] +#[derive(Default, Debug, Copy, Clone, Deserialize, PartialEq, Eq)] +pub struct Dimensions { + /// Window width in character columns + #[serde(deserialize_with = "failure_default")] + columns: Column, + + /// Window Height in character lines + #[serde(deserialize_with = "failure_default")] + lines: Line, +} + +impl Dimensions { + pub fn new(columns: Column, lines: Line) -> Self { + Dimensions { columns, lines } + } + + /// Get lines + #[inline] + pub fn lines_u32(&self) -> u32 { + self.lines.0 as u32 + } + + /// Get columns + #[inline] + pub fn columns_u32(&self) -> u32 { + self.columns.0 as u32 + } +} + +/// A delta for a point in a 2 dimensional plane +#[serde(default, bound(deserialize = "T: Deserialize<'de> + Default"))] +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)] +pub struct Delta<T: Default + PartialEq + Eq> { + /// Horizontal change + #[serde(deserialize_with = "failure_default")] + pub x: T, + /// Vertical change + #[serde(deserialize_with = "failure_default")] + pub y: T, +} + +trait DeserializeSize: Sized { + fn deserialize<'a, D>(_: D) -> ::std::result::Result<Self, D::Error> + where + D: serde::de::Deserializer<'a>; +} + +impl DeserializeSize for Size { + fn deserialize<'a, D>(deserializer: D) -> ::std::result::Result<Self, D::Error> + where + D: serde::de::Deserializer<'a>, + { + use std::marker::PhantomData; + + struct NumVisitor<__D> { + _marker: PhantomData<__D>, + } + + impl<'a, __D> Visitor<'a> for NumVisitor<__D> + where + __D: serde::de::Deserializer<'a>, + { + type Value = f64; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("f64 or u64") + } + + fn visit_f64<E>(self, value: f64) -> ::std::result::Result<Self::Value, E> + where + E: ::serde::de::Error, + { + Ok(value) + } + + fn visit_u64<E>(self, value: u64) -> ::std::result::Result<Self::Value, E> + where + E: ::serde::de::Error, + { + Ok(value as f64) + } + } + + let size = deserializer + .deserialize_any(NumVisitor::<D> { _marker: PhantomData }) + .map(|v| Size::new(v as _)); + + // Use default font size as fallback + match size { + Ok(size) => Ok(size), + Err(err) => { + let size = default_font_size(); + error!("Problem with config: {}; using size {}", err, size.as_f32_pts()); + Ok(size) + }, + } + } +} + +/// Font config +/// +/// Defaults are provided at the level of this struct per platform, but not per +/// field in this struct. It might be nice in the future to have defaults for +/// each value independently. Alternatively, maybe erroring when the user +/// doesn't provide complete config is Ok. +#[serde(default)] +#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] +pub struct Font { + /// Normal font face + #[serde(deserialize_with = "failure_default")] + normal: FontDescription, + + /// Bold font face + #[serde(deserialize_with = "failure_default")] + italic: SecondaryFontDescription, + + /// Italic font face + #[serde(deserialize_with = "failure_default")] + bold: SecondaryFontDescription, + + /// Font size in points + #[serde(deserialize_with = "DeserializeSize::deserialize")] + pub size: Size, + + /// Extra spacing per character + #[serde(deserialize_with = "failure_default")] + offset: Delta<i8>, + + /// Glyph offset within character cell + #[serde(deserialize_with = "failure_default")] + glyph_offset: Delta<i8>, + + #[cfg(target_os = "macos")] + #[serde(deserialize_with = "deserialize_true_bool")] + use_thin_strokes: bool, + + // TODO: Deprecated + #[serde(deserialize_with = "deserialize_scale_with_dpi")] + scale_with_dpi: Option<()>, +} + +impl Default for Font { + fn default() -> Font { + Font { + #[cfg(target_os = "macos")] + use_thin_strokes: true, + size: default_font_size(), + normal: Default::default(), + bold: Default::default(), + italic: Default::default(), + scale_with_dpi: Default::default(), + glyph_offset: Default::default(), + offset: Default::default(), + } + } +} + +impl Font { + /// Get the font size in points + #[inline] + pub fn size(&self) -> Size { + self.size + } + + /// Get offsets to font metrics + #[inline] + pub fn offset(&self) -> &Delta<i8> { + &self.offset + } + + /// Get cell offsets for glyphs + #[inline] + pub fn glyph_offset(&self) -> &Delta<i8> { + &self.glyph_offset + } + + /// Get a font clone with a size modification + pub fn with_size(self, size: Size) -> Font { + Font { size, ..self } + } + + // Get normal font description + pub fn normal(&self) -> &FontDescription { + &self.normal + } + + // Get italic font description + pub fn italic(&self) -> FontDescription { + self.italic.desc(&self.normal) + } + + // Get bold font description + pub fn bold(&self) -> FontDescription { + self.bold.desc(&self.normal) + } +} + +fn default_font_size() -> Size { + Size::new(11.) +} + +fn deserialize_scale_with_dpi<'a, D>(deserializer: D) -> ::std::result::Result<Option<()>, D::Error> +where + D: de::Deserializer<'a>, +{ + // This is necessary in order to get serde to complete deserialization of the configuration + let _ignored = bool::deserialize(deserializer); + error!( + "The scale_with_dpi setting has been removed, on X11 the WINIT_HIDPI_FACTOR environment \ + variable can be used instead." + ); + Ok(None) +} + +/// Description of the normal font +#[serde(default)] +#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] +pub struct FontDescription { + #[serde(deserialize_with = "failure_default")] + pub family: String, + #[serde(deserialize_with = "failure_default")] + pub style: Option<String>, +} + +impl Default for FontDescription { + fn default() -> FontDescription { + FontDescription { + #[cfg(not(any(target_os = "macos", windows)))] + family: "monospace".into(), + #[cfg(target_os = "macos")] + family: "Menlo".into(), + #[cfg(windows)] + family: "Consolas".into(), + style: None, + } + } +} + +/// Description of the italic and bold font +#[serde(default)] +#[derive(Debug, Default, Deserialize, Clone, PartialEq, Eq)] +pub struct SecondaryFontDescription { + #[serde(deserialize_with = "failure_default")] + family: Option<String>, + #[serde(deserialize_with = "failure_default")] + style: Option<String>, +} + +impl SecondaryFontDescription { + pub fn desc(&self, fallback: &FontDescription) -> FontDescription { + FontDescription { + family: self.family.clone().unwrap_or_else(|| fallback.family.clone()), + style: self.style.clone(), + } + } +} + +pub struct Monitor { + _thread: ::std::thread::JoinHandle<()>, + rx: mpsc::Receiver<PathBuf>, +} + +pub trait OnConfigReload { + fn on_config_reload(&mut self); +} + +impl OnConfigReload for crate::display::Notifier { + fn on_config_reload(&mut self) { + self.notify(); + } +} + +impl Monitor { + /// Get pending config changes + pub fn pending(&self) -> Option<PathBuf> { + let mut config = None; + while let Ok(new) = self.rx.try_recv() { + config = Some(new); + } + + config + } + + pub fn new<H, P>(path: P, mut handler: H) -> Monitor + where + H: OnConfigReload + Send + 'static, + P: Into<PathBuf>, + { + let path = path.into(); + + let (config_tx, config_rx) = mpsc::channel(); + + Monitor { + _thread: crate::util::thread::spawn_named("config watcher", move || { + let (tx, rx) = mpsc::channel(); + // The Duration argument is a debouncing period. + let mut watcher = + watcher(tx, Duration::from_millis(10)).expect("Unable to spawn file watcher"); + let config_path = ::std::fs::canonicalize(path).expect("canonicalize config path"); + + // Get directory of config + let mut parent = config_path.clone(); + parent.pop(); + + // Watch directory + watcher + .watch(&parent, RecursiveMode::NonRecursive) + .expect("watch alacritty.yml dir"); + + loop { + match rx.recv().expect("watcher event") { + DebouncedEvent::Rename(..) => continue, + DebouncedEvent::Write(path) + | DebouncedEvent::Create(path) + | DebouncedEvent::Chmod(path) => { + if path != config_path { + continue; + } + + let _ = config_tx.send(path); + handler.on_config_reload(); + }, + _ => {}, + } + } + }), + rx: config_rx, + } + } +} + +#[derive(Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub enum Key { + Scancode(u32), + Key1, + Key2, + Key3, + Key4, + Key5, + Key6, + Key7, + Key8, + Key9, + Key0, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + Escape, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, + F21, + F22, + F23, + F24, + Snapshot, + Scroll, + Pause, + Insert, + Home, + Delete, + End, + PageDown, + PageUp, + Left, + Up, + Right, + Down, + Back, + Return, + Space, + Compose, + Numlock, + Numpad0, + Numpad1, + Numpad2, + Numpad3, + Numpad4, + Numpad5, + Numpad6, + Numpad7, + Numpad8, + Numpad9, + AbntC1, + AbntC2, + Add, + Apostrophe, + Apps, + At, + Ax, + Backslash, + Calculator, + Capital, + Colon, + Comma, + Convert, + Decimal, + Divide, + Equals, + Grave, + Kana, + Kanji, + LAlt, + LBracket, + LControl, + LShift, + LWin, + Mail, + MediaSelect, + MediaStop, + Minus, + Multiply, + Mute, + MyComputer, + NavigateForward, + NavigateBackward, + NextTrack, + NoConvert, + NumpadComma, + NumpadEnter, + NumpadEquals, + OEM102, + Period, + PlayPause, + Power, + PrevTrack, + RAlt, + RBracket, + RControl, + RShift, + RWin, + Semicolon, + Slash, + Sleep, + Stop, + Subtract, + Sysrq, + Tab, + Underline, + Unlabeled, + VolumeDown, + VolumeUp, + Wake, + WebBack, + WebFavorites, + WebForward, + WebHome, + WebRefresh, + WebSearch, + WebStop, + Yen, + Caret, + Copy, + Paste, + Cut, +} + +impl Key { + pub fn from_glutin_input(key: ::glutin::VirtualKeyCode) -> Self { + use glutin::VirtualKeyCode::*; + // Thank you, vim macros and regex! + match key { + Key1 => Key::Key1, + Key2 => Key::Key2, + Key3 => Key::Key3, + Key4 => Key::Key4, + Key5 => Key::Key5, + Key6 => Key::Key6, + Key7 => Key::Key7, + Key8 => Key::Key8, + Key9 => Key::Key9, + Key0 => Key::Key0, + A => Key::A, + B => Key::B, + C => Key::C, + D => Key::D, + E => Key::E, + F => Key::F, + G => Key::G, + H => Key::H, + I => Key::I, + J => Key::J, + K => Key::K, + L => Key::L, + M => Key::M, + N => Key::N, + O => Key::O, + P => Key::P, + Q => Key::Q, + R => Key::R, + S => Key::S, + T => Key::T, + U => Key::U, + V => Key::V, + W => Key::W, + X => Key::X, + Y => Key::Y, + Z => Key::Z, + Escape => Key::Escape, + F1 => Key::F1, + F2 => Key::F2, + F3 => Key::F3, + F4 => Key::F4, + F5 => Key::F5, + F6 => Key::F6, + F7 => Key::F7, + F8 => Key::F8, + F9 => Key::F9, + F10 => Key::F10, + F11 => Key::F11, + F12 => Key::F12, + F13 => Key::F13, + F14 => Key::F14, + F15 => Key::F15, + F16 => Key::F16, + F17 => Key::F17, + F18 => Key::F18, + F19 => Key::F19, + F20 => Key::F20, + F21 => Key::F21, + F22 => Key::F22, + F23 => Key::F23, + F24 => Key::F24, + Snapshot => Key::Snapshot, + Scroll => Key::Scroll, + Pause => Key::Pause, + Insert => Key::Insert, + Home => Key::Home, + Delete => Key::Delete, + End => Key::End, + PageDown => Key::PageDown, + PageUp => Key::PageUp, + Left => Key::Left, + Up => Key::Up, + Right => Key::Right, + Down => Key::Down, + Back => Key::Back, + Return => Key::Return, + Space => Key::Space, + Compose => Key::Compose, + Numlock => Key::Numlock, + Numpad0 => Key::Numpad0, + Numpad1 => Key::Numpad1, + Numpad2 => Key::Numpad2, + Numpad3 => Key::Numpad3, + Numpad4 => Key::Numpad4, + Numpad5 => Key::Numpad5, + Numpad6 => Key::Numpad6, + Numpad7 => Key::Numpad7, + Numpad8 => Key::Numpad8, + Numpad9 => Key::Numpad9, + AbntC1 => Key::AbntC1, + AbntC2 => Key::AbntC2, + Add => Key::Add, + Apostrophe => Key::Apostrophe, + Apps => Key::Apps, + At => Key::At, + Ax => Key::Ax, + Backslash => Key::Backslash, + Calculator => Key::Calculator, + Capital => Key::Capital, + Colon => Key::Colon, + Comma => Key::Comma, + Convert => Key::Convert, + Decimal => Key::Decimal, + Divide => Key::Divide, + Equals => Key::Equals, + Grave => Key::Grave, + Kana => Key::Kana, + Kanji => Key::Kanji, + LAlt => Key::LAlt, + LBracket => Key::LBracket, + LControl => Key::LControl, + LShift => Key::LShift, + LWin => Key::LWin, + Mail => Key::Mail, + MediaSelect => Key::MediaSelect, + MediaStop => Key::MediaStop, + Minus => Key::Minus, + Multiply => Key::Multiply, + Mute => Key::Mute, + MyComputer => Key::MyComputer, + NavigateForward => Key::NavigateForward, + NavigateBackward => Key::NavigateBackward, + NextTrack => Key::NextTrack, + NoConvert => Key::NoConvert, + NumpadComma => Key::NumpadComma, + NumpadEnter => Key::NumpadEnter, + NumpadEquals => Key::NumpadEquals, + OEM102 => Key::OEM102, + Period => Key::Period, + PlayPause => Key::PlayPause, + Power => Key::Power, + PrevTrack => Key::PrevTrack, + RAlt => Key::RAlt, + RBracket => Key::RBracket, + RControl => Key::RControl, + RShift => Key::RShift, + RWin => Key::RWin, + Semicolon => Key::Semicolon, + Slash => Key::Slash, + Sleep => Key::Sleep, + Stop => Key::Stop, + Subtract => Key::Subtract, + Sysrq => Key::Sysrq, + Tab => Key::Tab, + Underline => Key::Underline, + Unlabeled => Key::Unlabeled, + VolumeDown => Key::VolumeDown, + VolumeUp => Key::VolumeUp, + Wake => Key::Wake, + WebBack => Key::WebBack, + WebFavorites => Key::WebFavorites, + WebForward => Key::WebForward, + WebHome => Key::WebHome, + WebRefresh => Key::WebRefresh, + WebSearch => Key::WebSearch, + WebStop => Key::WebStop, + Yen => Key::Yen, + Caret => Key::Caret, + Copy => Key::Copy, + Paste => Key::Paste, + Cut => Key::Cut, + } + } +} + +#[cfg(test)] +mod tests { + use super::{Config, DEFAULT_ALACRITTY_CONFIG}; + use crate::cli::Options; + + #[test] + fn parse_config() { + let config: Config = + ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config"); + + // Sanity check that mouse bindings are being parsed + assert!(!config.mouse_bindings.is_empty()); + + // Sanity check that key bindings are being parsed + assert!(!config.key_bindings.is_empty()); + } + + #[test] + fn dynamic_title_ignoring_options_by_default() { + let config: Config = + ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config"); + let old_dynamic_title = config.dynamic_title; + let options = Options::default(); + let config = config.update_dynamic_title(&options); + assert_eq!(old_dynamic_title, config.dynamic_title); + } + + #[test] + fn dynamic_title_overridden_by_options() { + let config: Config = + ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config"); + let mut options = Options::default(); + options.title = Some("foo".to_owned()); + let config = config.update_dynamic_title(&options); + assert!(!config.dynamic_title); + } + + #[test] + fn default_match_empty() { + let default = Config::default(); + + let empty = serde_yaml::from_str("key: val\n").unwrap(); + + assert_eq!(default, empty); + } +} diff --git a/src/cursor.rs b/alacritty_terminal/src/cursor.rs diff --git a/src/display.rs b/alacritty_terminal/src/display.rs diff --git a/src/event.rs b/alacritty_terminal/src/event.rs diff --git a/src/event_loop.rs b/alacritty_terminal/src/event_loop.rs diff --git a/src/grid/mod.rs b/alacritty_terminal/src/grid/mod.rs diff --git a/src/grid/row.rs b/alacritty_terminal/src/grid/row.rs diff --git a/src/grid/storage.rs b/alacritty_terminal/src/grid/storage.rs diff --git a/src/grid/tests.rs b/alacritty_terminal/src/grid/tests.rs diff --git a/src/index.rs b/alacritty_terminal/src/index.rs diff --git a/src/input.rs b/alacritty_terminal/src/input.rs diff --git a/alacritty_terminal/src/lib.rs b/alacritty_terminal/src/lib.rs @@ -0,0 +1,60 @@ +// 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. +// +//! Alacritty - The GPU Enhanced Terminal +#![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use, clippy::wrong_pub_self_convention)] +#![cfg_attr(feature = "nightly", feature(core_intrinsics))] +#![cfg_attr(all(test, feature = "bench"), feature(test))] + +#[macro_use] +extern crate log; +#[macro_use] +extern crate serde_derive; + +#[cfg(target_os = "macos")] +#[macro_use] +extern crate objc; + +#[macro_use] +pub mod macros; +pub mod ansi; +pub mod cli; +pub mod config; +mod cursor; +pub mod display; +pub mod event; +pub mod event_loop; +pub mod grid; +pub mod index; +pub mod input; +pub mod locale; +pub mod message_bar; +pub mod meter; +pub mod panic; +pub mod renderer; +pub mod selection; +pub mod sync; +pub mod term; +pub mod tty; +mod url; +pub mod util; +pub mod window; + +pub use crate::grid::Grid; +pub use crate::term::Term; + +pub mod gl { + #![allow(clippy::all)] + include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); +} diff --git a/src/locale.rs b/alacritty_terminal/src/locale.rs diff --git a/src/macros.rs b/alacritty_terminal/src/macros.rs diff --git a/src/message_bar.rs b/alacritty_terminal/src/message_bar.rs diff --git a/alacritty_terminal/src/meter.rs b/alacritty_terminal/src/meter.rs @@ -0,0 +1,110 @@ +// 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. +// +//! Rendering time meter +//! +//! Used to track rendering times and provide moving averages. +//! +//! # Examples +//! +//! ```rust +//! // create a meter +//! let mut meter = alacritty_terminal::meter::Meter::new(); +//! +//! // Sample something. +//! { +//! let _sampler = meter.sampler(); +//! } +//! +//! // Get the moving average. The meter tracks a fixed number of samples, and +//! // the average won't mean much until it's filled up at least once. +//! println!("Average time: {}", meter.average()); + +use std::time::{Duration, Instant}; + +const NUM_SAMPLES: usize = 10; + +/// The meter +#[derive(Default)] +pub struct Meter { + /// Track last 60 timestamps + times: [f64; NUM_SAMPLES], + + /// Average sample time in microseconds + avg: f64, + + /// Index of next time to update. + index: usize, +} + +/// Sampler +/// +/// Samplers record how long they are "alive" for and update the meter on drop. +pub struct Sampler<'a> { + /// Reference to meter that created the sampler + meter: &'a mut Meter, + + // When the sampler was created + created_at: Instant, +} + +impl<'a> Sampler<'a> { + fn new(meter: &'a mut Meter) -> Sampler<'a> { + Sampler { meter, created_at: Instant::now() } + } + + #[inline] + fn alive_duration(&self) -> Duration { + self.created_at.elapsed() + } +} + +impl<'a> Drop for Sampler<'a> { + fn drop(&mut self) { + self.meter.add_sample(self.alive_duration()); + } +} + +impl Meter { + /// Create a meter + pub fn new() -> Meter { + Default::default() + } + + /// Get a sampler + pub fn sampler(&mut self) -> Sampler<'_> { + Sampler::new(self) + } + + /// Get the current average sample duration in microseconds + pub fn average(&self) -> f64 { + self.avg + } + + /// Add a sample + /// + /// Used by Sampler::drop. + fn add_sample(&mut self, sample: Duration) { + let mut usec = 0f64; + + usec += f64::from(sample.subsec_nanos()) / 1e3; + usec += (sample.as_secs() as f64) * 1e6; + + let prev = self.times[self.index]; + self.times[self.index] = usec; + self.avg -= prev / NUM_SAMPLES as f64; + self.avg += usec / NUM_SAMPLES as f64; + self.index = (self.index + 1) % NUM_SAMPLES; + } +} diff --git a/src/panic.rs b/alacritty_terminal/src/panic.rs diff --git a/alacritty_terminal/src/renderer/mod.rs b/alacritty_terminal/src/renderer/mod.rs @@ -0,0 +1,1629 @@ +// 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. +use std::collections::HashMap; +use std::fs::File; +use std::hash::BuildHasherDefault; +use std::io::{self, Read}; +use std::mem::size_of; +use std::path::PathBuf; +use std::ptr; +use std::sync::mpsc; +use std::time::Duration; + +use fnv::FnvHasher; +use font::{self, FontDesc, FontKey, GlyphKey, Rasterize, RasterizedGlyph, Rasterizer}; +use glutin::dpi::PhysicalSize; +use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; + +use crate::ansi::CursorStyle; +use crate::config::{self, Config, Delta}; +use crate::gl; +use crate::gl::types::*; +use crate::index::{Column, Line}; +use crate::renderer::rects::{Rect, Rects}; +use crate::term::color::Rgb; +use crate::term::{self, cell, RenderableCell, RenderableCellContent}; + +pub mod rects; + +// Shader paths for live reload +static TEXT_SHADER_F_PATH: &'static str = + concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.f.glsl"); +static TEXT_SHADER_V_PATH: &'static str = + concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.v.glsl"); +static RECT_SHADER_F_PATH: &'static str = + concat!(env!("CARGO_MANIFEST_DIR"), "/../res/rect.f.glsl"); +static RECT_SHADER_V_PATH: &'static str = + concat!(env!("CARGO_MANIFEST_DIR"), "/../res/rect.v.glsl"); + +// Shader source which is used when live-shader-reload feature is disable +static TEXT_SHADER_F: &'static str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.f.glsl")); +static TEXT_SHADER_V: &'static str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.v.glsl")); +static RECT_SHADER_F: &'static str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../res/rect.f.glsl")); +static RECT_SHADER_V: &'static str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../res/rect.v.glsl")); + +/// `LoadGlyph` allows for copying a rasterized glyph into graphics memory +pub trait LoadGlyph { + /// Load the rasterized glyph into GPU memory + fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph; + + /// Clear any state accumulated from previous loaded glyphs + /// + /// This can, for instance, be used to reset the texture Atlas. + fn clear(&mut self); +} + +enum Msg { + ShaderReload, +} + +#[derive(Debug)] +pub enum Error { + ShaderCreation(ShaderCreationError), +} + +impl ::std::error::Error for Error { + fn cause(&self) -> Option<&dyn (::std::error::Error)> { + match *self { + Error::ShaderCreation(ref err) => Some(err), + } + } + + fn description(&self) -> &str { + match *self { + Error::ShaderCreation(ref err) => err.description(), + } + } +} + +impl ::std::fmt::Display for Error { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + match *self { + Error::ShaderCreation(ref err) => { + write!(f, "There was an error initializing the shaders: {}", err) + }, + } + } +} + +impl From<ShaderCreationError> for Error { + fn from(val: ShaderCreationError) -> Error { + Error::ShaderCreation(val) + } +} + +/// Text drawing program +/// +/// Uniforms are prefixed with "u", and vertex attributes are prefixed with "a". +#[derive(Debug)] +pub struct TextShaderProgram { + // Program id + id: GLuint, + + /// projection scale and offset uniform + u_projection: GLint, + + /// Cell dimensions (pixels) + u_cell_dim: GLint, + + /// Background pass flag + /// + /// Rendering is split into two passes; 1 for backgrounds, and one for text + u_background: GLint, +} + +/// Rectangle drawing program +/// +/// Uniforms are prefixed with "u" +#[derive(Debug)] +pub struct RectShaderProgram { + // Program id + id: GLuint, + /// Rectangle color + u_color: GLint, +} + +#[derive(Copy, Debug, Clone)] +pub struct Glyph { + tex_id: GLuint, + top: f32, + left: f32, + width: f32, + height: f32, + uv_bot: f32, + uv_left: f32, + uv_width: f32, + uv_height: f32, +} + +/// Naïve glyph cache +/// +/// Currently only keyed by `char`, and thus not possible to hold different +/// representations of the same code point. +pub struct GlyphCache { + /// Cache of buffered glyphs + cache: HashMap<GlyphKey, Glyph, BuildHasherDefault<FnvHasher>>, + + /// Cache of buffered cursor glyphs + cursor_cache: HashMap<CursorStyle, Glyph, BuildHasherDefault<FnvHasher>>, + + /// Rasterizer for loading new glyphs + rasterizer: Rasterizer, + + /// regular font + font_key: FontKey, + + /// italic font + italic_key: FontKey, + + /// bold font + bold_key: FontKey, + + /// font size + font_size: font::Size, + + /// glyph offset + glyph_offset: Delta<i8>, + + metrics: ::font::Metrics, +} + +impl GlyphCache { + pub fn new<L>( + mut rasterizer: Rasterizer, + font: &config::Font, + loader: &mut L, + ) -> Result<GlyphCache, font::Error> + where + L: LoadGlyph, + { + let (regular, bold, italic) = Self::compute_font_keys(font, &mut rasterizer)?; + + // Need to load at least one glyph for the face before calling metrics. + // The glyph requested here ('m' at the time of writing) has no special + // meaning. + rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; + + let metrics = rasterizer.metrics(regular, font.size())?; + + let mut cache = GlyphCache { + cache: HashMap::default(), + cursor_cache: HashMap::default(), + rasterizer, + font_size: font.size(), + font_key: regular, + bold_key: bold, + italic_key: italic, + glyph_offset: *font.glyph_offset(), + metrics, + }; + + cache.load_glyphs_for_font(regular, loader); + cache.load_glyphs_for_font(bold, loader); + cache.load_glyphs_for_font(italic, loader); + + Ok(cache) + } + + fn load_glyphs_for_font<L: LoadGlyph>(&mut self, font: FontKey, loader: &mut L) { + let size = self.font_size; + for i in 32u8..=128u8 { + self.get(GlyphKey { font_key: font, c: i as char, size }, loader); + } + } + + /// Computes font keys for (Regular, Bold, Italic) + fn compute_font_keys( + font: &config::Font, + rasterizer: &mut Rasterizer, + ) -> Result<(FontKey, FontKey, FontKey), font::Error> { + let size = font.size(); + + // Load regular font + let regular_desc = + Self::make_desc(&font.normal(), font::Slant::Normal, font::Weight::Normal); + + let regular = rasterizer.load_font(&regular_desc, size)?; + + // helper to load a description if it is not the regular_desc + let mut load_or_regular = |desc: FontDesc| { + if desc == regular_desc { + regular + } else { + rasterizer.load_font(&desc, size).unwrap_or_else(|_| regular) + } + }; + + // Load bold font + let bold_desc = Self::make_desc(&font.bold(), font::Slant::Normal, font::Weight::Bold); + + let bold = load_or_regular(bold_desc); + + // Load italic font + let italic_desc = + Self::make_desc(&font.italic(), font::Slant::Italic, font::Weight::Normal); + + let italic = load_or_regular(italic_desc); + + Ok((regular, bold, italic)) + } + + fn make_desc( + desc: &config::FontDescription, + slant: font::Slant, + weight: font::Weight, + ) -> FontDesc { + let style = if let Some(ref spec) = desc.style { + font::Style::Specific(spec.to_owned()) + } else { + font::Style::Description { slant, weight } + }; + FontDesc::new(desc.family.clone(), style) + } + + pub fn font_metrics(&self) -> font::Metrics { + self.rasterizer + .metrics(self.font_key, self.font_size) + .expect("metrics load since font is loaded at glyph cache creation") + } + + pub fn get<'a, L>(&'a mut self, glyph_key: GlyphKey, loader: &mut L) -> &'a Glyph + where + L: LoadGlyph, + { + let glyph_offset = self.glyph_offset; + let rasterizer = &mut self.rasterizer; + let metrics = &self.metrics; + self.cache.entry(glyph_key).or_insert_with(|| { + let mut rasterized = + rasterizer.get_glyph(glyph_key).unwrap_or_else(|_| Default::default()); + + rasterized.left += i32::from(glyph_offset.x); + rasterized.top += i32::from(glyph_offset.y); + rasterized.top -= metrics.descent as i32; + + loader.load_glyph(&rasterized) + }) + } + + pub fn update_font_size<L: LoadGlyph>( + &mut self, + font: &config::Font, + size: font::Size, + dpr: f64, + loader: &mut L, + ) -> Result<(), font::Error> { + // Clear currently cached data in both GL and the registry + loader.clear(); + self.cache = HashMap::default(); + self.cursor_cache = HashMap::default(); + + // Update dpi scaling + self.rasterizer.update_dpr(dpr as f32); + + // Recompute font keys + let font = font.to_owned().with_size(size); + let (regular, bold, italic) = Self::compute_font_keys(&font, &mut self.rasterizer)?; + + self.rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; + let metrics = self.rasterizer.metrics(regular, size)?; + + info!("Font size changed to {:?} with DPR of {}", font.size, dpr); + + self.font_size = font.size; + self.font_key = regular; + self.bold_key = bold; + self.italic_key = italic; + self.metrics = metrics; + + self.load_glyphs_for_font(regular, loader); + self.load_glyphs_for_font(bold, loader); + self.load_glyphs_for_font(italic, loader); + + Ok(()) + } + + // Calculate font metrics without access to a glyph cache + // + // This should only be used *before* OpenGL is initialized and the glyph cache can be filled. + pub fn static_metrics(config: &Config, dpr: f32) -> Result<font::Metrics, font::Error> { + let font = config.font().clone(); + + let mut rasterizer = font::Rasterizer::new(dpr, config.use_thin_strokes())?; + let regular_desc = + GlyphCache::make_desc(&font.normal(), font::Slant::Normal, font::Weight::Normal); + let regular = rasterizer.load_font(&regular_desc, font.size())?; + rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; + + rasterizer.metrics(regular, font.size()) + } +} + +#[derive(Debug)] +#[repr(C)] +struct InstanceData { + // coords + col: f32, + row: f32, + // glyph offset + left: f32, + top: f32, + // glyph scale + width: f32, + height: f32, + // uv offset + uv_left: f32, + uv_bot: f32, + // uv scale + uv_width: f32, + uv_height: f32, + // color + r: f32, + g: f32, + b: f32, + // background color + bg_r: f32, + bg_g: f32, + bg_b: f32, + bg_a: f32, +} + +#[derive(Debug)] +pub struct QuadRenderer { + program: TextShaderProgram, + rect_program: RectShaderProgram, + vao: GLuint, + ebo: GLuint, + vbo_instance: GLuint, + rect_vao: GLuint, + rect_vbo: GLuint, + atlas: Vec<Atlas>, + current_atlas: usize, + active_tex: GLuint, + batch: Batch, + rx: mpsc::Receiver<Msg>, +} + +#[derive(Debug)] +pub struct RenderApi<'a> { + active_tex: &'a mut GLuint, + batch: &'a mut Batch, + atlas: &'a mut Vec<Atlas>, + current_atlas: &'a mut usize, + program: &'a mut TextShaderProgram, + config: &'a Config, +} + +#[derive(Debug)] +pub struct LoaderApi<'a> { + active_tex: &'a mut GLuint, + atlas: &'a mut Vec<Atlas>, + current_atlas: &'a mut usize, +} + +#[derive(Debug)] +pub struct PackedVertex { + x: f32, + y: f32, +} + +#[derive(Debug, Default)] +pub struct Batch { + tex: GLuint, + instances: Vec<InstanceData>, +} + +impl Batch { + #[inline] + pub fn new() -> Batch { + Batch { tex: 0, instances: Vec::with_capacity(BATCH_MAX) } + } + + pub fn add_item(&mut self, cell: &RenderableCell, glyph: &Glyph) { + if self.is_empty() { + self.tex = glyph.tex_id; + } + + self.instances.push(InstanceData { + col: cell.column.0 as f32, + row: cell.line.0 as f32, + + top: glyph.top, + left: glyph.left, + width: glyph.width, + height: glyph.height, + + uv_bot: glyph.uv_bot, + uv_left: glyph.uv_left, + uv_width: glyph.uv_width, + uv_height: glyph.uv_height, + + r: f32::from(cell.fg.r), + g: f32::from(cell.fg.g), + b: f32::from(cell.fg.b), + + bg_r: f32::from(cell.bg.r), + bg_g: f32::from(cell.bg.g), + bg_b: f32::from(cell.bg.b), + bg_a: cell.bg_alpha, + }); + } + + #[inline] + pub fn full(&self) -> bool { + self.capacity() == self.len() + } + + #[inline] + pub fn len(&self) -> usize { + self.instances.len() + } + + #[inline] + pub fn capacity(&self) -> usize { + BATCH_MAX + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + #[inline] + pub fn size(&self) -> usize { + self.len() * size_of::<InstanceData>() + } + + pub fn clear(&mut self) { + self.tex = 0; + self.instances.clear(); + } +} + +/// Maximum items to be drawn in a batch. +const BATCH_MAX: usize = 0x1_0000; +const ATLAS_SIZE: i32 = 1024; + +impl QuadRenderer { + pub fn new() -> Result<QuadRenderer, Error> { + let program = TextShaderProgram::new()?; + let rect_program = RectShaderProgram::new()?; + + let mut vao: GLuint = 0; + let mut ebo: GLuint = 0; + + let mut vbo_instance: GLuint = 0; + + let mut rect_vao: GLuint = 0; + let mut rect_vbo: GLuint = 0; + let mut rect_ebo: GLuint = 0; + + unsafe { + gl::Enable(gl::BLEND); + gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR); + gl::Enable(gl::MULTISAMPLE); + + // Disable depth mask, as the renderer never uses depth tests + gl::DepthMask(gl::FALSE); + + gl::GenVertexArrays(1, &mut vao); + gl::GenBuffers(1, &mut ebo); + gl::GenBuffers(1, &mut vbo_instance); + gl::BindVertexArray(vao); + + // --------------------- + // Set up element buffer + // --------------------- + let indices: [u32; 6] = [0, 1, 3, 1, 2, 3]; + + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo); + gl::BufferData( + gl::ELEMENT_ARRAY_BUFFER, + (6 * size_of::<u32>()) as isize, + indices.as_ptr() as *const _, + gl::STATIC_DRAW, + ); + + // ---------------------------- + // Setup vertex instance buffer + // ---------------------------- + gl::BindBuffer(gl::ARRAY_BUFFER, vbo_instance); + gl::BufferData( + gl::ARRAY_BUFFER, + (BATCH_MAX * size_of::<InstanceData>()) as isize, + ptr::null(), + gl::STREAM_DRAW, + ); + // coords + gl::VertexAttribPointer( + 0, + 2, + gl::FLOAT, + gl::FALSE, + size_of::<InstanceData>() as i32, + ptr::null(), + ); + gl::EnableVertexAttribArray(0); + gl::VertexAttribDivisor(0, 1); + // glyphoffset + gl::VertexAttribPointer( + 1, + 4, + gl::FLOAT, + gl::FALSE, + size_of::<InstanceData>() as i32, + (2 * size_of::<f32>()) as *const _, + ); + gl::EnableVertexAttribArray(1); + gl::VertexAttribDivisor(1, 1); + // uv + gl::VertexAttribPointer( + 2, + 4, + gl::FLOAT, + gl::FALSE, + size_of::<InstanceData>() as i32, + (6 * size_of::<f32>()) as *const _, + ); + gl::EnableVertexAttribArray(2); + gl::VertexAttribDivisor(2, 1); + // color + gl::VertexAttribPointer( + 3, + 3, + gl::FLOAT, + gl::FALSE, + size_of::<InstanceData>() as i32, + (10 * size_of::<f32>()) as *const _, + ); + gl::EnableVertexAttribArray(3); + gl::VertexAttribDivisor(3, 1); + // color + gl::VertexAttribPointer( + 4, + 4, + gl::FLOAT, + gl::FALSE, + size_of::<InstanceData>() as i32, + (13 * size_of::<f32>()) as *const _, + ); + gl::EnableVertexAttribArray(4); + gl::VertexAttribDivisor(4, 1); + + // Rectangle setup + gl::GenVertexArrays(1, &mut rect_vao); + gl::GenBuffers(1, &mut rect_vbo); + gl::GenBuffers(1, &mut rect_ebo); + gl::BindVertexArray(rect_vao); + let indices: [i32; 6] = [0, 1, 3, 1, 2, 3]; + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, rect_ebo); + gl::BufferData( + gl::ELEMENT_ARRAY_BUFFER, + (size_of::<i32>() * indices.len()) as _, + indices.as_ptr() as *const _, + gl::STATIC_DRAW, + ); + + // Cleanup + gl::BindVertexArray(0); + gl::BindBuffer(gl::ARRAY_BUFFER, 0); + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0); + } + + let (msg_tx, msg_rx) = mpsc::channel(); + + if cfg!(feature = "live-shader-reload") { + ::std::thread::spawn(move || { + let (tx, rx) = ::std::sync::mpsc::channel(); + // The Duration argument is a debouncing period. + let mut watcher = + watcher(tx, Duration::from_millis(10)).expect("create file watcher"); + watcher + .watch(TEXT_SHADER_F_PATH, RecursiveMode::NonRecursive) + .expect("watch fragment shader"); + watcher + .watch(TEXT_SHADER_V_PATH, RecursiveMode::NonRecursive) + .expect("watch vertex shader"); + + loop { + let event = rx.recv().expect("watcher event"); + + match event { + DebouncedEvent::Rename(..) => continue, + DebouncedEvent::Create(_) + | DebouncedEvent::Write(_) + | DebouncedEvent::Chmod(_) => { + msg_tx.send(Msg::ShaderReload).expect("msg send ok"); + }, + _ => {}, + } + } + }); + } + + let mut renderer = QuadRenderer { + program, + rect_program, + vao, + ebo, + vbo_instance, + rect_vao, + rect_vbo, + atlas: Vec::new(), + current_atlas: 0, + active_tex: 0, + batch: Batch::new(), + rx: msg_rx, + }; + + let atlas = Atlas::new(ATLAS_SIZE); + renderer.atlas.push(atlas); + + Ok(renderer) + } + + // Draw all rectangles simultaneously to prevent excessive program swaps + pub fn draw_rects( + &mut self, + config: &Config, + props: &term::SizeInfo, + visual_bell_intensity: f64, + cell_line_rects: Rects, + ) { + // Swap to rectangle rendering program + unsafe { + // Swap program + gl::UseProgram(self.rect_program.id); + + // Remove padding from viewport + gl::Viewport(0, 0, props.width as i32, props.height as i32); + + // Change blending strategy + gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); + + // Setup data and buffers + gl::BindVertexArray(self.rect_vao); + gl::BindBuffer(gl::ARRAY_BUFFER, self.rect_vbo); + + // Position + gl::VertexAttribPointer( + 0, + 2, + gl::FLOAT, + gl::FALSE, + (size_of::<f32>() * 2) as _, + ptr::null(), + ); + gl::EnableVertexAttribArray(0); + } + + // Draw visual bell + let color = config.visual_bell().color(); + let rect = Rect::new(0., 0., props.width, props.height); + self.render_rect(&rect, color, visual_bell_intensity as f32, props); + + // Draw underlines and strikeouts + for cell_line_rect in cell_line_rects.rects() { + self.render_rect(&cell_line_rect.0, cell_line_rect.1, 255., props); + } + + // Deactivate rectangle program again + unsafe { + // Reset blending strategy + gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR); + + // Reset data and buffers + gl::BindBuffer(gl::ARRAY_BUFFER, 0); + gl::BindVertexArray(0); + + let padding_x = props.padding_x as i32; + let padding_y = props.padding_y as i32; + let width = props.width as i32; + let height = props.height as i32; + gl::Viewport(padding_x, padding_y, width - 2 * padding_x, height - 2 * padding_y); + + // Disable program + gl::UseProgram(0); + } + } + + pub fn with_api<F, T>(&mut self, config: &Config, props: &term::SizeInfo, func: F) -> T + where + F: FnOnce(RenderApi<'_>) -> T, + { + // Flush message queue + if let Ok(Msg::ShaderReload) = self.rx.try_recv() { + self.reload_shaders(props); + } + while let Ok(_) = self.rx.try_recv() {} + + unsafe { + gl::UseProgram(self.program.id); + self.program.set_term_uniforms(props); + + gl::BindVertexArray(self.vao); + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo); + gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo_instance); + gl::ActiveTexture(gl::TEXTURE0); + } + + let res = func(RenderApi { + active_tex: &mut self.active_tex, + batch: &mut self.batch, + atlas: &mut self.atlas, + current_atlas: &mut self.current_atlas, + program: &mut self.program, + config, + }); + + unsafe { + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0); + gl::BindBuffer(gl::ARRAY_BUFFER, 0); + gl::BindVertexArray(0); + + gl::UseProgram(0); + } + + res + } + + pub fn with_loader<F, T>(&mut self, func: F) -> T + where + F: FnOnce(LoaderApi<'_>) -> T, + { + unsafe { + gl::ActiveTexture(gl::TEXTURE0); + } + + func(LoaderApi { + active_tex: &mut self.active_tex, + atlas: &mut self.atlas, + current_atlas: &mut self.current_atlas, + }) + } + + pub fn reload_shaders(&mut self, props: &term::SizeInfo) { + info!("Reloading shaders..."); + let result = (TextShaderProgram::new(), RectShaderProgram::new()); + let (program, rect_program) = match result { + (Ok(program), Ok(rect_program)) => { + unsafe { + gl::UseProgram(program.id); + program.update_projection( + props.width, + props.height, + props.padding_x, + props.padding_y, + ); + gl::UseProgram(0); + } + + info!("... successfully reloaded shaders"); + (program, rect_program) + }, + (Err(err), _) | (_, Err(err)) => { + error!("{}", err); + return; + }, + }; + + self.active_tex = 0; + self.program = program; + self.rect_program = rect_program; + } + + pub fn resize(&mut self, size: PhysicalSize, padding_x: f32, padding_y: f32) { + let (width, height): (u32, u32) = size.into(); + + // viewport + unsafe { + let width = width as i32; + let height = height as i32; + let padding_x = padding_x as i32; + let padding_y = padding_y as i32; + gl::Viewport(padding_x, padding_y, width - 2 * padding_x, height - 2 * padding_y); + + // update projection + gl::UseProgram(self.program.id); + self.program.update_projection( + width as f32, + height as f32, + padding_x as f32, + padding_y as f32, + ); + gl::UseProgram(0); + } + } + + // Render a rectangle + // + // This requires the rectangle program to be activated + fn render_rect(&mut self, rect: &Rect<f32>, color: Rgb, alpha: f32, size: &term::SizeInfo) { + // Do nothing when alpha is fully transparent + if alpha == 0. { + return; + } + + // Calculate rectangle position + let center_x = size.width / 2.; + let center_y = size.height / 2.; + let x = (rect.x - center_x) / center_x; + let y = -(rect.y - center_y) / center_y; + let width = rect.width / center_x; + let height = rect.height / center_y; + + unsafe { + // Setup vertices + let vertices: [f32; 8] = [x + width, y, x + width, y - height, x, y - height, x, y]; + + // Load vertex data into array buffer + gl::BufferData( + gl::ARRAY_BUFFER, + (size_of::<f32>() * vertices.len()) as _, + vertices.as_ptr() as *const _, + gl::STATIC_DRAW, + ); + + // Color + self.rect_program.set_color(color, alpha); + + // Draw the rectangle + gl::DrawElements(gl::TRIANGLES, 6, gl::UNSIGNED_INT, ptr::null()); + } + } +} + +impl<'a> RenderApi<'a> { + pub fn clear(&self, color: Rgb) { + let alpha = self.config.background_opacity().get(); + unsafe { + gl::ClearColor( + (f32::from(color.r) / 255.0).min(1.0) * alpha, + (f32::from(color.g) / 255.0).min(1.0) * alpha, + (f32::from(color.b) / 255.0).min(1.0) * alpha, + alpha, + ); + gl::Clear(gl::COLOR_BUFFER_BIT); + } + } + + fn render_batch(&mut self) { + unsafe { + gl::BufferSubData( + gl::ARRAY_BUFFER, + 0, + self.batch.size() as isize, + self.batch.instances.as_ptr() as *const _, + ); + } + + // Bind texture if necessary + if *self.active_tex != self.batch.tex { + unsafe { + gl::BindTexture(gl::TEXTURE_2D, self.batch.tex); + } + *self.active_tex = self.batch.tex; + } + + unsafe { + self.program.set_background_pass(true); + gl::DrawElementsInstanced( + gl::TRIANGLES, + 6, + gl::UNSIGNED_INT, + ptr::null(), + self.batch.len() as GLsizei, + ); + self.program.set_background_pass(false); + gl::DrawElementsInstanced( + gl::TRIANGLES, + 6, + gl::UNSIGNED_INT, + ptr::null(), + self.batch.len() as GLsizei, + ); + } + + self.batch.clear(); + } + + /// Render a string in a variable location. Used for printing the render timer, warnings and + /// errors. + pub fn render_string( + &mut self, + string: &str, + line: Line, + glyph_cache: &mut GlyphCache, + color: Option<Rgb>, + ) { + let bg_alpha = color.map(|_| 1.0).unwrap_or(0.0); + let col = Column(0); + + let cells = string + .chars() + .enumerate() + .map(|(i, c)| RenderableCell { + line, + column: col + i, + inner: RenderableCellContent::Chars({ + let mut chars = [' '; cell::MAX_ZEROWIDTH_CHARS + 1]; + chars[0] = c; + chars + }), + bg: color.unwrap_or(Rgb { r: 0, g: 0, b: 0 }), + fg: Rgb { r: 0, g: 0, b: 0 }, + flags: cell::Flags::empty(), + bg_alpha, + }) + .collect::<Vec<_>>(); + + for cell in cells { + self.render_cell(cell, glyph_cache); + } + } + + #[inline] + fn add_render_item(&mut self, cell: &RenderableCell, glyph: &Glyph) { + // Flush batch if tex changing + if !self.batch.is_empty() && self.batch.tex != glyph.tex_id { + self.render_batch(); + } + + self.batch.add_item(cell, glyph); + + // Render batch and clear if it's full + if self.batch.full() { + self.render_batch(); + } + } + + pub fn render_cell(&mut self, cell: RenderableCell, glyph_cache: &mut GlyphCache) { + let chars = match cell.inner { + RenderableCellContent::Cursor((cursor_style, ref raw)) => { + // Raw cell pixel buffers like cursors don't need to go through font lookup + let glyph = glyph_cache + .cursor_cache + .entry(cursor_style) + .or_insert_with(|| self.load_glyph(raw)); + self.add_render_item(&cell, &glyph); + return; + }, + RenderableCellContent::Chars(chars) => chars, + }; + + // Get font key for cell + // FIXME this is super inefficient. + let font_key = if cell.flags.contains(cell::Flags::BOLD) { + glyph_cache.bold_key + } else if cell.flags.contains(cell::Flags::ITALIC) { + glyph_cache.italic_key + } else { + glyph_cache.font_key + }; + + // Don't render text of HIDDEN cells + let mut chars = if cell.flags.contains(cell::Flags::HIDDEN) { + [' '; cell::MAX_ZEROWIDTH_CHARS + 1] + } else { + chars + }; + + // Render tabs as spaces in case the font doesn't support it + if chars[0] == '\t' { + chars[0] = ' '; + } + + let mut glyph_key = GlyphKey { font_key, size: glyph_cache.font_size, c: chars[0] }; + + // Add cell to batch + let glyph = glyph_cache.get(glyph_key, self); + self.add_render_item(&cell, glyph); + + // Render zero-width characters + for c in (&chars[1..]).iter().filter(|c| **c != ' ') { + glyph_key.c = *c; + let mut glyph = *glyph_cache.get(glyph_key, self); + + // The metrics of zero-width characters are based on rendering + // the character after the current cell, with the anchor at the + // right side of the preceding character. Since we render the + // zero-width characters inside the preceding character, the + // anchor has been moved to the right by one cell. + glyph.left += glyph_cache.metrics.average_advance as f32; + + self.add_render_item(&cell, &glyph); + } + } +} + +/// Load a glyph into a texture atlas +/// +/// If the current atlas is full, a new one will be created. +#[inline] +fn load_glyph( + active_tex: &mut GLuint, + atlas: &mut Vec<Atlas>, + current_atlas: &mut usize, + rasterized: &RasterizedGlyph, +) -> Glyph { + // At least one atlas is guaranteed to be in the `self.atlas` list; thus + // the unwrap. + match atlas[*current_atlas].insert(rasterized, active_tex) { + Ok(glyph) => glyph, + Err(AtlasInsertError::Full) => { + *current_atlas += 1; + if *current_atlas == atlas.len() { + let new = Atlas::new(ATLAS_SIZE); + *active_tex = 0; // Atlas::new binds a texture. Ugh this is sloppy. + atlas.push(new); + } + load_glyph(active_tex, atlas, current_atlas, rasterized) + }, + Err(AtlasInsertError::GlyphTooLarge) => Glyph { + tex_id: atlas[*current_atlas].id, + top: 0.0, + left: 0.0, + width: 0.0, + height: 0.0, + uv_bot: 0.0, + uv_left: 0.0, + uv_width: 0.0, + uv_height: 0.0, + }, + } +} + +#[inline] +fn clear_atlas(atlas: &mut Vec<Atlas>, current_atlas: &mut usize) { + for atlas in atlas.iter_mut() { + atlas.clear(); + } + *current_atlas = 0; +} + +impl<'a> LoadGlyph for LoaderApi<'a> { + fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph { + load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized) + } + + fn clear(&mut self) { + clear_atlas(self.atlas, self.current_atlas) + } +} + +impl<'a> LoadGlyph for RenderApi<'a> { + fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph { + load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized) + } + + fn clear(&mut self) { + clear_atlas(self.atlas, self.current_atlas) + } +} + +impl<'a> Drop for RenderApi<'a> { + fn drop(&mut self) { + if !self.batch.is_empty() { + self.render_batch(); + } + } +} + +impl TextShaderProgram { + pub fn new() -> Result<TextShaderProgram, ShaderCreationError> { + let (vertex_src, fragment_src) = if cfg!(feature = "live-shader-reload") { + (None, None) + } else { + (Some(TEXT_SHADER_V), Some(TEXT_SHADER_F)) + }; + let vertex_shader = create_shader(TEXT_SHADER_V_PATH, gl::VERTEX_SHADER, vertex_src)?; + let fragment_shader = create_shader(TEXT_SHADER_F_PATH, gl::FRAGMENT_SHADER, fragment_src)?; + let program = create_program(vertex_shader, fragment_shader)?; + + unsafe { + gl::DeleteShader(fragment_shader); + gl::DeleteShader(vertex_shader); + gl::UseProgram(program); + } + + macro_rules! cptr { + ($thing:expr) => { + $thing.as_ptr() as *const _ + }; + } + + macro_rules! assert_uniform_valid { + ($uniform:expr) => { + assert!($uniform != gl::INVALID_VALUE as i32); + assert!($uniform != gl::INVALID_OPERATION as i32); + }; + ( $( $uniform:expr ),* ) => { + $( assert_uniform_valid!($uniform); )* + }; + } + + // get uniform locations + let (projection, cell_dim, background) = unsafe { + ( + gl::GetUniformLocation(program, cptr!(b"projection\0")), + gl::GetUniformLocation(program, cptr!(b"cellDim\0")), + gl::GetUniformLocation(program, cptr!(b"backgroundPass\0")), + ) + }; + + assert_uniform_valid!(projection, cell_dim, background); + + let shader = TextShaderProgram { + id: program, + u_projection: projection, + u_cell_dim: cell_dim, + u_background: background, + }; + + unsafe { + gl::UseProgram(0); + } + + Ok(shader) + } + + fn update_projection(&self, width: f32, height: f32, padding_x: f32, padding_y: f32) { + // Bounds check + if (width as u32) < (2 * padding_x as u32) || (height as u32) < (2 * padding_y as u32) { + return; + } + + // Compute scale and offset factors, from pixel to ndc space. Y is inverted + // [0, width - 2 * padding_x] to [-1, 1] + // [height - 2 * padding_y, 0] to [-1, 1] + let scale_x = 2. / (width - 2. * padding_x); + let scale_y = -2. / (height - 2. * padding_y); + let offset_x = -1.; + let offset_y = 1.; + + info!("Width: {}, Height: {}", width, height); + + unsafe { + gl::Uniform4f(self.u_projection, offset_x, offset_y, scale_x, scale_y); + } + } + + fn set_term_uniforms(&self, props: &term::SizeInfo) { + unsafe { + gl::Uniform2f(self.u_cell_dim, props.cell_width, props.cell_height); + } + } + + fn set_background_pass(&self, background_pass: bool) { + let value = if background_pass { 1 } else { 0 }; + + unsafe { + gl::Uniform1i(self.u_background, value); + } + } +} + +impl Drop for TextShaderProgram { + fn drop(&mut self) { + unsafe { + gl::DeleteProgram(self.id); + } + } +} + +impl RectShaderProgram { + pub fn new() -> Result<Self, ShaderCreationError> { + let (vertex_src, fragment_src) = if cfg!(feature = "live-shader-reload") { + (None, None) + } else { + (Some(RECT_SHADER_V), Some(RECT_SHADER_F)) + }; + let vertex_shader = create_shader(RECT_SHADER_V_PATH, gl::VERTEX_SHADER, vertex_src)?; + let fragment_shader = create_shader(RECT_SHADER_F_PATH, gl::FRAGMENT_SHADER, fragment_src)?; + let program = create_program(vertex_shader, fragment_shader)?; + + unsafe { + gl::DeleteShader(fragment_shader); + gl::DeleteShader(vertex_shader); + gl::UseProgram(program); + } + + // get uniform locations + let u_color = unsafe { gl::GetUniformLocation(program, b"color\0".as_ptr() as *const _) }; + + let shader = RectShaderProgram { id: program, u_color }; + + unsafe { gl::UseProgram(0) } + + Ok(shader) + } + + fn set_color(&self, color: Rgb, alpha: f32) { + unsafe { + gl::Uniform4f( + self.u_color, + f32::from(color.r) / 255., + f32::from(color.g) / 255., + f32::from(color.b) / 255., + alpha, + ); + } + } +} + +impl Drop for RectShaderProgram { + fn drop(&mut self) { + unsafe { + gl::DeleteProgram(self.id); + } + } +} + +fn create_program(vertex: GLuint, fragment: GLuint) -> Result<GLuint, ShaderCreationError> { + unsafe { + let program = gl::CreateProgram(); + gl::AttachShader(program, vertex); + gl::AttachShader(program, fragment); + gl::LinkProgram(program); + + let mut success: GLint = 0; + gl::GetProgramiv(program, gl::LINK_STATUS, &mut success); + + if success == i32::from(gl::TRUE) { + Ok(program) + } else { + Err(ShaderCreationError::Link(get_program_info_log(program))) + } + } +} + +fn create_shader( + path: &str, + kind: GLenum, + source: Option<&'static str>, +) -> Result<GLuint, ShaderCreationError> { + let from_disk; + let source = if let Some(src) = source { + src + } else { + from_disk = read_file(path)?; + &from_disk[..] + }; + + let len: [GLint; 1] = [source.len() as GLint]; + + let shader = unsafe { + let shader = gl::CreateShader(kind); + gl::ShaderSource(shader, 1, &(source.as_ptr() as *const _), len.as_ptr()); + gl::CompileShader(shader); + shader + }; + + let mut success: GLint = 0; + unsafe { + gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut success); + } + + if success == GLint::from(gl::TRUE) { + Ok(shader) + } else { + // Read log + let log = get_shader_info_log(shader); + + // Cleanup + unsafe { + gl::DeleteShader(shader); + } + + Err(ShaderCreationError::Compile(PathBuf::from(path), log)) + } +} + +fn get_program_info_log(program: GLuint) -> String { + // Get expected log length + let mut max_length: GLint = 0; + unsafe { + gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut max_length); + } + + // Read the info log + let mut actual_length: GLint = 0; + let mut buf: Vec<u8> = Vec::with_capacity(max_length as usize); + unsafe { + gl::GetProgramInfoLog(program, max_length, &mut actual_length, buf.as_mut_ptr() as *mut _); + } + + // Build a string + unsafe { + buf.set_len(actual_length as usize); + } + + // XXX should we expect opengl to return garbage? + String::from_utf8(buf).unwrap() +} + +fn get_shader_info_log(shader: GLuint) -> String { + // Get expected log length + let mut max_length: GLint = 0; + unsafe { + gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut max_length); + } + + // Read the info log + let mut actual_length: GLint = 0; + let mut buf: Vec<u8> = Vec::with_capacity(max_length as usize); + unsafe { + gl::GetShaderInfoLog(shader, max_length, &mut actual_length, buf.as_mut_ptr() as *mut _); + } + + // Build a string + unsafe { + buf.set_len(actual_length as usize); + } + + // XXX should we expect opengl to return garbage? + String::from_utf8(buf).unwrap() +} + +fn read_file(path: &str) -> Result<String, io::Error> { + let mut f = File::open(path)?; + let mut buf = String::new(); + f.read_to_string(&mut buf)?; + + Ok(buf) +} + +#[derive(Debug)] +pub enum ShaderCreationError { + /// Error reading file + Io(io::Error), + + /// Error compiling shader + Compile(PathBuf, String), + + /// Problem linking + Link(String), +} + +impl ::std::error::Error for ShaderCreationError { + fn cause(&self) -> Option<&dyn (::std::error::Error)> { + match *self { + ShaderCreationError::Io(ref err) => Some(err), + _ => None, + } + } + + fn description(&self) -> &str { + match *self { + ShaderCreationError::Io(ref err) => err.description(), + ShaderCreationError::Compile(ref _path, ref s) => s.as_str(), + ShaderCreationError::Link(ref s) => s.as_str(), + } + } +} + +impl ::std::fmt::Display for ShaderCreationError { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + match *self { + ShaderCreationError::Io(ref err) => write!(f, "Couldn't read shader: {}", err), + ShaderCreationError::Compile(ref path, ref log) => { + write!(f, "Failed compiling shader at {}: {}", path.display(), log) + }, + ShaderCreationError::Link(ref log) => write!(f, "Failed linking shader: {}", log), + } + } +} + +impl From<io::Error> for ShaderCreationError { + fn from(val: io::Error) -> ShaderCreationError { + ShaderCreationError::Io(val) + } +} + +/// Manages a single texture atlas +/// +/// The strategy for filling an atlas looks roughly like this: +/// +/// ```ignore +/// (width, height) +/// ┌─────┬─────┬─────┬─────┬─────┐ +/// │ 10 │ │ │ │ │ <- Empty spaces; can be filled while +/// │ │ │ │ │ │ glyph_height < height - row_baseline +/// ├⎼⎼⎼⎼⎼┼⎼⎼⎼⎼⎼┼⎼⎼⎼⎼⎼┼⎼⎼⎼⎼⎼┼⎼⎼⎼⎼⎼┤ +/// │ 5 │ 6 │ 7 │ 8 │ 9 │ +/// │ │ │ │ │ │ +/// ├⎼⎼⎼⎼⎼┼⎼⎼⎼⎼⎼┼⎼⎼⎼⎼⎼┼⎼⎼⎼⎼⎼┴⎼⎼⎼⎼⎼┤ <- Row height is tallest glyph in row; this is +/// │ 1 │ 2 │ 3 │ 4 │ used as the baseline for the following row. +/// │ │ │ │ │ <- Row considered full when next glyph doesn't +/// └─────┴─────┴─────┴───────────┘ fit in the row. +/// (0, 0) x-> +/// ``` +#[derive(Debug)] +struct Atlas { + /// Texture id for this atlas + id: GLuint, + + /// Width of atlas + width: i32, + + /// Height of atlas + height: i32, + + /// Left-most free pixel in a row. + /// + /// This is called the extent because it is the upper bound of used pixels + /// in a row. + row_extent: i32, + + /// Baseline for glyphs in the current row + row_baseline: i32, + + /// Tallest glyph in current row + /// + /// This is used as the advance when end of row is reached + row_tallest: i32, +} + +/// Error that can happen when inserting a texture to the Atlas +enum AtlasInsertError { + /// Texture atlas is full + Full, + + /// The glyph cannot fit within a single texture + GlyphTooLarge, +} + +impl Atlas { + fn new(size: i32) -> Atlas { + let mut id: GLuint = 0; + unsafe { + gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1); + gl::GenTextures(1, &mut id); + gl::BindTexture(gl::TEXTURE_2D, id); + gl::TexImage2D( + gl::TEXTURE_2D, + 0, + gl::RGB as i32, + size, + size, + 0, + gl::RGB, + gl::UNSIGNED_BYTE, + ptr::null(), + ); + + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32); + + gl::BindTexture(gl::TEXTURE_2D, 0); + } + + Atlas { id, width: size, height: size, row_extent: 0, row_baseline: 0, row_tallest: 0 } + } + + pub fn clear(&mut self) { + self.row_extent = 0; + self.row_baseline = 0; + self.row_tallest = 0; + } + + /// Insert a RasterizedGlyph into the texture atlas + pub fn insert( + &mut self, + glyph: &RasterizedGlyph, + active_tex: &mut u32, + ) -> Result<Glyph, AtlasInsertError> { + if glyph.width > self.width || glyph.height > self.height { + return Err(AtlasInsertError::GlyphTooLarge); + } + + // If there's not enough room in current row, go onto next one + if !self.room_in_row(glyph) { + self.advance_row()?; + } + + // If there's still not room, there's nothing that can be done here. + if !self.room_in_row(glyph) { + return Err(AtlasInsertError::Full); + } + + // There appears to be room; load the glyph. + Ok(self.insert_inner(glyph, active_tex)) + } + + /// Insert the glyph without checking for room + /// + /// Internal function for use once atlas has been checked for space. GL + /// errors could still occur at this point if we were checking for them; + /// hence, the Result. + fn insert_inner(&mut self, glyph: &RasterizedGlyph, active_tex: &mut u32) -> Glyph { + let offset_y = self.row_baseline; + let offset_x = self.row_extent; + let height = glyph.height as i32; + let width = glyph.width as i32; + + unsafe { + gl::BindTexture(gl::TEXTURE_2D, self.id); + + // Load data into OpenGL + gl::TexSubImage2D( + gl::TEXTURE_2D, + 0, + offset_x, + offset_y, + width, + height, + gl::RGB, + gl::UNSIGNED_BYTE, + glyph.buf.as_ptr() as *const _, + ); + + gl::BindTexture(gl::TEXTURE_2D, 0); + *active_tex = 0; + } + + // Update Atlas state + self.row_extent = offset_x + width; + if height > self.row_tallest { + self.row_tallest = height; + } + + // Generate UV coordinates + let uv_bot = offset_y as f32 / self.height as f32; + let uv_left = offset_x as f32 / self.width as f32; + let uv_height = height as f32 / self.height as f32; + let uv_width = width as f32 / self.width as f32; + + Glyph { + tex_id: self.id, + top: glyph.top as f32, + width: width as f32, + height: height as f32, + left: glyph.left as f32, + uv_bot, + uv_left, + uv_width, + uv_height, + } + } + + /// Check if there's room in the current row for given glyph + fn room_in_row(&self, raw: &RasterizedGlyph) -> bool { + let next_extent = self.row_extent + raw.width as i32; + let enough_width = next_extent <= self.width; + let enough_height = (raw.height as i32) < (self.height - self.row_baseline); + + enough_width && enough_height + } + + /// Mark current row as finished and prepare to insert into the next row + fn advance_row(&mut self) -> Result<(), AtlasInsertError> { + let advance_to = self.row_baseline + self.row_tallest; + if self.height - advance_to <= 0 { + return Err(AtlasInsertError::Full); + } + + self.row_baseline = advance_to; + self.row_extent = 0; + self.row_tallest = 0; + + Ok(()) + } +} diff --git a/src/renderer/rects.rs b/alacritty_terminal/src/renderer/rects.rs diff --git a/src/selection.rs b/alacritty_terminal/src/selection.rs diff --git a/src/sync.rs b/alacritty_terminal/src/sync.rs diff --git a/src/term/cell.rs b/alacritty_terminal/src/term/cell.rs diff --git a/src/term/color.rs b/alacritty_terminal/src/term/color.rs diff --git a/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs diff --git a/src/tty/mod.rs b/alacritty_terminal/src/tty/mod.rs diff --git a/src/tty/unix.rs b/alacritty_terminal/src/tty/unix.rs diff --git a/src/tty/windows/conpty.rs b/alacritty_terminal/src/tty/windows/conpty.rs diff --git a/src/tty/windows/mod.rs b/alacritty_terminal/src/tty/windows/mod.rs diff --git a/src/tty/windows/winpty.rs b/alacritty_terminal/src/tty/windows/winpty.rs diff --git a/src/url.rs b/alacritty_terminal/src/url.rs diff --git a/src/util.rs b/alacritty_terminal/src/util.rs diff --git a/alacritty_terminal/src/window.rs b/alacritty_terminal/src/window.rs @@ -0,0 +1,497 @@ +// 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. +use std::convert::From; +use std::fmt::Display; + +use crate::gl; +use glutin::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; +#[cfg(not(any(target_os = "macos", windows)))] +use glutin::os::unix::EventsLoopExt; +#[cfg(windows)] +use glutin::Icon; +use glutin::{ + self, ContextBuilder, ControlFlow, Event, EventsLoop, MouseCursor, PossiblyCurrent, + WindowBuilder, +}; +#[cfg(windows)] +use image::ImageFormat; + +use crate::cli::Options; +use crate::config::{Decorations, StartupMode, WindowConfig}; + +#[cfg(windows)] +static WINDOW_ICON: &'static [u8] = include_bytes!("../../extra/windows/alacritty.ico"); + +/// Default Alacritty name, used for window title and class. +pub const DEFAULT_NAME: &str = "Alacritty"; + +/// Window errors +#[derive(Debug)] +pub enum Error { + /// Error creating the window + ContextCreation(glutin::CreationError), + + /// Error manipulating the rendering context + Context(glutin::ContextError), +} + +/// Result of fallible operations concerning a Window. +type Result<T> = ::std::result::Result<T, Error>; + +/// A window which can be used for displaying the terminal +/// +/// Wraps the underlying windowing library to provide a stable API in Alacritty +pub struct Window { + event_loop: EventsLoop, + windowed_context: glutin::WindowedContext<PossiblyCurrent>, + mouse_visible: bool, + + /// Whether or not the window is the focused window. + pub is_focused: bool, +} + +/// Threadsafe APIs for the window +pub struct Proxy { + inner: glutin::EventsLoopProxy, +} + +/// Information about where the window is being displayed +/// +/// Useful for subsystems like the font rasterized which depend on DPI and scale +/// factor. +pub struct DeviceProperties { + /// Scale factor for pixels <-> points. + /// + /// This will be 1. on standard displays and may have a different value on + /// hidpi displays. + pub scale_factor: f64, +} + +impl ::std::error::Error for Error { + fn cause(&self) -> Option<&dyn (::std::error::Error)> { + match *self { + Error::ContextCreation(ref err) => Some(err), + Error::Context(ref err) => Some(err), + } + } + + fn description(&self) -> &str { + match *self { + Error::ContextCreation(ref _err) => "Error creating gl context", + Error::Context(ref _err) => "Error operating on render context", + } + } +} + +impl Display for Error { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + match *self { + Error::ContextCreation(ref err) => write!(f, "Error creating GL context; {}", err), + Error::Context(ref err) => write!(f, "Error operating on render context; {}", err), + } + } +} + +impl From<glutin::CreationError> for Error { + fn from(val: glutin::CreationError) -> Error { + Error::ContextCreation(val) + } +} + +impl From<glutin::ContextError> for Error { + fn from(val: glutin::ContextError) -> Error { + Error::Context(val) + } +} + +fn create_gl_window( + mut window: WindowBuilder, + event_loop: &EventsLoop, + srgb: bool, + dimensions: Option<LogicalSize>, +) -> Result<glutin::WindowedContext<PossiblyCurrent>> { + if let Some(dimensions) = dimensions { + window = window.with_dimensions(dimensions); + } + + let windowed_context = ContextBuilder::new() + .with_srgb(srgb) + .with_vsync(true) + .with_hardware_acceleration(None) + .build_windowed(window, event_loop)?; + + // Make the context current so OpenGL operations can run + let windowed_context = unsafe { windowed_context.make_current().map_err(|(_, e)| e)? }; + + Ok(windowed_context) +} + +impl Window { + /// Create a new window + /// + /// This creates a window and fully initializes a window. + pub fn new( + event_loop: EventsLoop, + options: &Options, + window_config: &WindowConfig, + dimensions: Option<LogicalSize>, + ) -> Result<Window> { + let title = options.title.as_ref().map_or(DEFAULT_NAME, |t| t); + let class = options.class.as_ref().map_or(DEFAULT_NAME, |c| c); + let window_builder = Window::get_platform_window(title, class, window_config); + let windowed_context = + create_gl_window(window_builder.clone(), &event_loop, false, dimensions) + .or_else(|_| create_gl_window(window_builder, &event_loop, true, dimensions))?; + let window = windowed_context.window(); + window.show(); + + // Maximize window after mapping in X11 + #[cfg(not(any(target_os = "macos", windows)))] + { + if event_loop.is_x11() && window_config.startup_mode() == StartupMode::Maximized { + window.set_maximized(true); + } + } + + // Set window position + // + // TODO: replace `set_position` with `with_position` once available + // Upstream issue: https://github.com/tomaka/winit/issues/806 + let position = options.position().or_else(|| window_config.position()); + if let Some(position) = position { + let physical = PhysicalPosition::from((position.x, position.y)); + let logical = physical.to_logical(window.get_hidpi_factor()); + window.set_position(logical); + } + + if let StartupMode::Fullscreen = window_config.startup_mode() { + let current_monitor = window.get_current_monitor(); + window.set_fullscreen(Some(current_monitor)); + } + + #[cfg(target_os = "macos")] + { + if let StartupMode::SimpleFullscreen = window_config.startup_mode() { + use glutin::os::macos::WindowExt; + window.set_simple_fullscreen(true); + } + } + + // Text cursor + window.set_cursor(MouseCursor::Text); + + // Set OpenGL symbol loader. This call MUST be after window.make_current on windows. + gl::load_with(|symbol| windowed_context.get_proc_address(symbol) as *const _); + + let window = + Window { event_loop, windowed_context, mouse_visible: true, is_focused: false }; + + window.run_os_extensions(); + + Ok(window) + } + + /// Get some properties about the device + /// + /// Some window properties are provided since subsystems like font + /// rasterization depend on DPI and scale factor. + pub fn device_properties(&self) -> DeviceProperties { + DeviceProperties { scale_factor: self.window().get_hidpi_factor() } + } + + pub fn inner_size_pixels(&self) -> Option<LogicalSize> { + self.window().get_inner_size() + } + + pub fn set_inner_size(&mut self, size: LogicalSize) { + self.window().set_inner_size(size); + } + + #[inline] + pub fn hidpi_factor(&self) -> f64 { + self.window().get_hidpi_factor() + } + + #[inline] + pub fn create_window_proxy(&self) -> Proxy { + Proxy { inner: self.event_loop.create_proxy() } + } + + #[inline] + pub fn swap_buffers(&self) -> Result<()> { + self.windowed_context.swap_buffers().map_err(From::from) + } + + /// Poll for any available events + #[inline] + pub fn poll_events<F>(&mut self, func: F) + where + F: FnMut(Event), + { + self.event_loop.poll_events(func); + } + + #[inline] + pub fn resize(&self, size: PhysicalSize) { + self.windowed_context.resize(size); + } + + /// Block waiting for events + #[inline] + pub fn wait_events<F>(&mut self, func: F) + where + F: FnMut(Event) -> ControlFlow, + { + self.event_loop.run_forever(func); + } + + /// Set the window title + #[inline] + pub fn set_title(&self, title: &str) { + self.window().set_title(title); + } + + #[inline] + pub fn set_mouse_cursor(&self, cursor: MouseCursor) { + self.window().set_cursor(cursor); + } + + /// Set mouse cursor visible + pub fn set_mouse_visible(&mut self, visible: bool) { + if visible != self.mouse_visible { + self.mouse_visible = visible; + self.window().hide_cursor(!visible); + } + } + + #[cfg(not(any(target_os = "macos", windows)))] + pub fn get_platform_window( + title: &str, + class: &str, + window_config: &WindowConfig, + ) -> WindowBuilder { + use glutin::os::unix::WindowBuilderExt; + + let decorations = match window_config.decorations() { + Decorations::None => false, + _ => true, + }; + + WindowBuilder::new() + .with_title(title) + .with_visibility(false) + .with_transparency(true) + .with_decorations(decorations) + .with_maximized(window_config.startup_mode() == StartupMode::Maximized) + // X11 + .with_class(class.into(), DEFAULT_NAME.into()) + // Wayland + .with_app_id(class.into()) + } + + #[cfg(windows)] + pub fn get_platform_window( + title: &str, + _class: &str, + window_config: &WindowConfig, + ) -> WindowBuilder { + let icon = Icon::from_bytes_with_format(WINDOW_ICON, ImageFormat::ICO).unwrap(); + + let decorations = match window_config.decorations() { + Decorations::None => false, + _ => true, + }; + + WindowBuilder::new() + .with_title(title) + .with_visibility(cfg!(windows)) + .with_decorations(decorations) + .with_transparency(true) + .with_maximized(window_config.startup_mode() == StartupMode::Maximized) + .with_window_icon(Some(icon)) + } + + #[cfg(target_os = "macos")] + pub fn get_platform_window( + title: &str, + _class: &str, + window_config: &WindowConfig, + ) -> WindowBuilder { + use glutin::os::macos::WindowBuilderExt; + + let window = WindowBuilder::new() + .with_title(title) + .with_visibility(false) + .with_transparency(true) + .with_maximized(window_config.startup_mode() == StartupMode::Maximized); + + match window_config.decorations() { + Decorations::Full => window, + Decorations::Transparent => window + .with_title_hidden(true) + .with_titlebar_transparent(true) + .with_fullsize_content_view(true), + Decorations::Buttonless => window + .with_title_hidden(true) + .with_titlebar_buttons_hidden(true) + .with_titlebar_transparent(true) + .with_fullsize_content_view(true), + Decorations::None => window.with_titlebar_hidden(true), + } + } + + #[cfg(any( + target_os = "linux", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd" + ))] + pub fn set_urgent(&self, is_urgent: bool) { + use glutin::os::unix::WindowExt; + self.window().set_urgent(is_urgent); + } + + #[cfg(target_os = "macos")] + pub fn set_urgent(&self, is_urgent: bool) { + use glutin::os::macos::WindowExt; + self.window().request_user_attention(is_urgent); + } + + #[cfg(windows)] + pub fn set_urgent(&self, _is_urgent: bool) {} + + pub fn set_ime_spot(&self, pos: LogicalPosition) { + self.window().set_ime_spot(pos); + } + + #[cfg(not(any(target_os = "macos", target_os = "windows")))] + pub fn get_window_id(&self) -> Option<usize> { + use glutin::os::unix::WindowExt; + + match self.window().get_xlib_window() { + Some(xlib_window) => Some(xlib_window as usize), + None => None, + } + } + + #[cfg(any(target_os = "macos", target_os = "windows"))] + pub fn get_window_id(&self) -> Option<usize> { + None + } + + /// Hide the window + pub fn hide(&self) { + self.window().hide(); + } + + /// Fullscreens the window on the current monitor. + pub fn set_fullscreen(&self, fullscreen: bool) { + let glutin_window = self.window(); + if fullscreen { + let current_monitor = glutin_window.get_current_monitor(); + glutin_window.set_fullscreen(Some(current_monitor)); + } else { + glutin_window.set_fullscreen(None); + } + } + + #[cfg(target_os = "macos")] + pub fn set_simple_fullscreen(&self, fullscreen: bool) { + use glutin::os::macos::WindowExt; + self.window().set_simple_fullscreen(fullscreen); + } + + fn window(&self) -> &glutin::Window { + self.windowed_context.window() + } +} + +pub trait OsExtensions { + fn run_os_extensions(&self) {} +} + +#[cfg(not(any( + target_os = "linux", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd" +)))] +impl OsExtensions for Window {} + +#[cfg(any( + target_os = "linux", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd" +))] +impl OsExtensions for Window { + fn run_os_extensions(&self) { + use glutin::os::unix::WindowExt; + use libc::getpid; + use std::ffi::CStr; + use std::ptr; + use x11_dl::xlib::{self, PropModeReplace, XA_CARDINAL}; + + let xlib_display = self.window().get_xlib_display(); + let xlib_window = self.window().get_xlib_window(); + + if let (Some(xlib_window), Some(xlib_display)) = (xlib_window, xlib_display) { + let xlib = xlib::Xlib::open().expect("get xlib"); + + // Set _NET_WM_PID to process pid + unsafe { + let _net_wm_pid = CStr::from_ptr(b"_NET_WM_PID\0".as_ptr() as *const _); + let atom = (xlib.XInternAtom)(xlib_display as *mut _, _net_wm_pid.as_ptr(), 0); + let pid = getpid(); + + (xlib.XChangeProperty)( + xlib_display as _, + xlib_window as _, + atom, + XA_CARDINAL, + 32, + PropModeReplace, + &pid as *const i32 as *const u8, + 1, + ); + } + // Although this call doesn't actually pass any data, it does cause + // WM_CLIENT_MACHINE to be set. WM_CLIENT_MACHINE MUST be set if _NET_WM_PID is set + // (which we do above). + unsafe { + (xlib.XSetWMProperties)( + xlib_display as _, + xlib_window as _, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + 0, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ); + } + } + } +} + +impl Proxy { + /// Wakes up the event loop of the window + /// + /// This is useful for triggering a draw when the renderer would otherwise + /// be waiting on user input. + pub fn wakeup_event_loop(&self) { + self.inner.wakeup().unwrap(); + } +} diff --git a/alacritty_terminal/tests/ref.rs b/alacritty_terminal/tests/ref.rs @@ -0,0 +1,128 @@ +#[macro_use] +extern crate serde_derive; +use serde_json as json; + +use std::fs::File; +use std::io::{self, Read}; +use std::path::Path; + +use alacritty_terminal::ansi; +use alacritty_terminal::config::Config; +use alacritty_terminal::index::Column; +use alacritty_terminal::message_bar::MessageBuffer; +use alacritty_terminal::term::cell::Cell; +use alacritty_terminal::term::SizeInfo; +use alacritty_terminal::util::fmt::{Green, Red}; +use alacritty_terminal::Grid; +use alacritty_terminal::Term; + +macro_rules! ref_tests { + ($($name:ident)*) => { + $( + #[test] + fn $name() { + let test_dir = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/ref")); + let test_path = test_dir.join(stringify!($name)); + ref_test(&test_path); + } + )* + } +} + +ref_tests! { + csi_rep + fish_cc + indexed_256_colors + issue_855 + ll + newline_with_cursor_beyond_scroll_region + tab_rendering + tmux_git_log + tmux_htop + vim_24bitcolors_bce + vim_large_window_scroll + vim_simple_edit + vttest_cursor_movement_1 + vttest_insert + vttest_origin_mode_1 + vttest_origin_mode_2 + vttest_scroll + vttest_tab_clear_set + zsh_tab_completion + history + grid_reset + zerowidth +} + +fn read_u8<P>(path: P) -> Vec<u8> +where + P: AsRef<Path>, +{ + let mut res = Vec::new(); + File::open(path.as_ref()).unwrap().read_to_end(&mut res).unwrap(); + + res +} + +fn read_string<P>(path: P) -> Result<String, ::std::io::Error> +where + P: AsRef<Path>, +{ + let mut res = String::new(); + File::open(path.as_ref()).and_then(|mut f| f.read_to_string(&mut res))?; + + Ok(res) +} + +#[derive(Deserialize, Default)] +struct RefConfig { + history_size: u32, +} + +fn ref_test(dir: &Path) { + let recording = read_u8(dir.join("alacritty.recording")); + let serialized_size = read_string(dir.join("size.json")).unwrap(); + let serialized_grid = read_string(dir.join("grid.json")).unwrap(); + let serialized_cfg = read_string(dir.join("config.json")).unwrap_or_default(); + + let size: SizeInfo = json::from_str(&serialized_size).unwrap(); + let grid: Grid<Cell> = json::from_str(&serialized_grid).unwrap(); + let ref_config: RefConfig = json::from_str(&serialized_cfg).unwrap_or_default(); + + let mut config: Config = Default::default(); + config.set_history(ref_config.history_size); + + let mut terminal = Term::new(&config, size, MessageBuffer::new()); + let mut parser = ansi::Processor::new(); + + for byte in recording { + parser.advance(&mut terminal, byte, &mut io::sink()); + } + + // Truncate invisible lines from the grid + let mut term_grid = terminal.grid().clone(); + term_grid.initialize_all(&Cell::default()); + term_grid.truncate(); + + if grid != term_grid { + for i in 0..grid.len() { + for j in 0..grid.num_cols().0 { + let cell = term_grid[i][Column(j)]; + let original_cell = grid[i][Column(j)]; + if original_cell != cell { + println!( + "[{i}][{j}] {original:?} => {now:?}", + i = i, + j = j, + original = Green(original_cell), + now = Red(cell) + ); + } + } + } + + panic!("Ref test failed; grid doesn't match"); + } + + assert_eq!(grid, term_grid); +} diff --git a/tests/ref/csi_rep/alacritty.recording b/alacritty_terminal/tests/ref/csi_rep/alacritty.recording diff --git a/tests/ref/csi_rep/grid.json b/alacritty_terminal/tests/ref/csi_rep/grid.json diff --git a/tests/ref/csi_rep/size.json b/alacritty_terminal/tests/ref/csi_rep/size.json diff --git a/tests/ref/fish_cc/alacritty.recording b/alacritty_terminal/tests/ref/fish_cc/alacritty.recording diff --git a/tests/ref/fish_cc/grid.json b/alacritty_terminal/tests/ref/fish_cc/grid.json diff --git a/tests/ref/fish_cc/size.json b/alacritty_terminal/tests/ref/fish_cc/size.json diff --git a/tests/ref/grid_reset/alacritty.recording b/alacritty_terminal/tests/ref/grid_reset/alacritty.recording diff --git a/tests/ref/grid_reset/config.json b/alacritty_terminal/tests/ref/grid_reset/config.json diff --git a/tests/ref/grid_reset/grid.json b/alacritty_terminal/tests/ref/grid_reset/grid.json diff --git a/tests/ref/grid_reset/size.json b/alacritty_terminal/tests/ref/grid_reset/size.json diff --git a/tests/ref/history/alacritty.recording b/alacritty_terminal/tests/ref/history/alacritty.recording diff --git a/tests/ref/history/config.json b/alacritty_terminal/tests/ref/history/config.json diff --git a/tests/ref/history/grid.json b/alacritty_terminal/tests/ref/history/grid.json diff --git a/tests/ref/history/size.json b/alacritty_terminal/tests/ref/history/size.json diff --git a/tests/ref/indexed_256_colors/alacritty.recording b/alacritty_terminal/tests/ref/indexed_256_colors/alacritty.recording diff --git a/tests/ref/indexed_256_colors/grid.json b/alacritty_terminal/tests/ref/indexed_256_colors/grid.json diff --git a/tests/ref/indexed_256_colors/size.json b/alacritty_terminal/tests/ref/indexed_256_colors/size.json diff --git a/tests/ref/issue_855/alacritty.recording b/alacritty_terminal/tests/ref/issue_855/alacritty.recording diff --git a/tests/ref/issue_855/grid.json b/alacritty_terminal/tests/ref/issue_855/grid.json diff --git a/tests/ref/issue_855/size.json b/alacritty_terminal/tests/ref/issue_855/size.json diff --git a/tests/ref/ll/alacritty.recording b/alacritty_terminal/tests/ref/ll/alacritty.recording diff --git a/tests/ref/ll/grid.json b/alacritty_terminal/tests/ref/ll/grid.json diff --git a/tests/ref/ll/size.json b/alacritty_terminal/tests/ref/ll/size.json diff --git a/tests/ref/newline_with_cursor_beyond_scroll_region/alacritty.recording b/alacritty_terminal/tests/ref/newline_with_cursor_beyond_scroll_region/alacritty.recording diff --git a/tests/ref/newline_with_cursor_beyond_scroll_region/grid.json b/alacritty_terminal/tests/ref/newline_with_cursor_beyond_scroll_region/grid.json diff --git a/tests/ref/newline_with_cursor_beyond_scroll_region/size.json b/alacritty_terminal/tests/ref/newline_with_cursor_beyond_scroll_region/size.json diff --git a/tests/ref/tab_rendering/alacritty.recording b/alacritty_terminal/tests/ref/tab_rendering/alacritty.recording diff --git a/tests/ref/tab_rendering/grid.json b/alacritty_terminal/tests/ref/tab_rendering/grid.json diff --git a/tests/ref/tab_rendering/size.json b/alacritty_terminal/tests/ref/tab_rendering/size.json diff --git a/tests/ref/tmux_git_log/alacritty.recording b/alacritty_terminal/tests/ref/tmux_git_log/alacritty.recording diff --git a/tests/ref/tmux_git_log/grid.json b/alacritty_terminal/tests/ref/tmux_git_log/grid.json diff --git a/tests/ref/tmux_git_log/size.json b/alacritty_terminal/tests/ref/tmux_git_log/size.json diff --git a/tests/ref/tmux_htop/alacritty.recording b/alacritty_terminal/tests/ref/tmux_htop/alacritty.recording diff --git a/tests/ref/tmux_htop/grid.json b/alacritty_terminal/tests/ref/tmux_htop/grid.json diff --git a/tests/ref/tmux_htop/size.json b/alacritty_terminal/tests/ref/tmux_htop/size.json diff --git a/tests/ref/vim_24bitcolors_bce/alacritty.recording b/alacritty_terminal/tests/ref/vim_24bitcolors_bce/alacritty.recording diff --git a/tests/ref/vim_24bitcolors_bce/grid.json b/alacritty_terminal/tests/ref/vim_24bitcolors_bce/grid.json diff --git a/tests/ref/vim_24bitcolors_bce/size.json b/alacritty_terminal/tests/ref/vim_24bitcolors_bce/size.json diff --git a/tests/ref/vim_large_window_scroll/alacritty.recording b/alacritty_terminal/tests/ref/vim_large_window_scroll/alacritty.recording diff --git a/tests/ref/vim_large_window_scroll/grid.json b/alacritty_terminal/tests/ref/vim_large_window_scroll/grid.json diff --git a/tests/ref/vim_large_window_scroll/size.json b/alacritty_terminal/tests/ref/vim_large_window_scroll/size.json diff --git a/tests/ref/vim_simple_edit/alacritty.recording b/alacritty_terminal/tests/ref/vim_simple_edit/alacritty.recording diff --git a/tests/ref/vim_simple_edit/grid.json b/alacritty_terminal/tests/ref/vim_simple_edit/grid.json diff --git a/tests/ref/vim_simple_edit/size.json b/alacritty_terminal/tests/ref/vim_simple_edit/size.json diff --git a/tests/ref/vttest_cursor_movement_1/alacritty.recording b/alacritty_terminal/tests/ref/vttest_cursor_movement_1/alacritty.recording diff --git a/tests/ref/vttest_cursor_movement_1/grid.json b/alacritty_terminal/tests/ref/vttest_cursor_movement_1/grid.json diff --git a/tests/ref/vttest_cursor_movement_1/size.json b/alacritty_terminal/tests/ref/vttest_cursor_movement_1/size.json diff --git a/tests/ref/vttest_insert/alacritty.recording b/alacritty_terminal/tests/ref/vttest_insert/alacritty.recording diff --git a/tests/ref/vttest_insert/grid.json b/alacritty_terminal/tests/ref/vttest_insert/grid.json diff --git a/tests/ref/vttest_insert/size.json b/alacritty_terminal/tests/ref/vttest_insert/size.json diff --git a/tests/ref/vttest_origin_mode_1/alacritty.recording b/alacritty_terminal/tests/ref/vttest_origin_mode_1/alacritty.recording diff --git a/tests/ref/vttest_origin_mode_1/grid.json b/alacritty_terminal/tests/ref/vttest_origin_mode_1/grid.json diff --git a/tests/ref/vttest_origin_mode_1/size.json b/alacritty_terminal/tests/ref/vttest_origin_mode_1/size.json diff --git a/tests/ref/vttest_origin_mode_2/alacritty.recording b/alacritty_terminal/tests/ref/vttest_origin_mode_2/alacritty.recording diff --git a/tests/ref/vttest_origin_mode_2/grid.json b/alacritty_terminal/tests/ref/vttest_origin_mode_2/grid.json diff --git a/tests/ref/vttest_origin_mode_2/size.json b/alacritty_terminal/tests/ref/vttest_origin_mode_2/size.json diff --git a/tests/ref/vttest_scroll/alacritty.recording b/alacritty_terminal/tests/ref/vttest_scroll/alacritty.recording diff --git a/tests/ref/vttest_scroll/grid.json b/alacritty_terminal/tests/ref/vttest_scroll/grid.json diff --git a/tests/ref/vttest_scroll/size.json b/alacritty_terminal/tests/ref/vttest_scroll/size.json diff --git a/tests/ref/vttest_tab_clear_set/alacritty.recording b/alacritty_terminal/tests/ref/vttest_tab_clear_set/alacritty.recording diff --git a/tests/ref/vttest_tab_clear_set/grid.json b/alacritty_terminal/tests/ref/vttest_tab_clear_set/grid.json diff --git a/tests/ref/vttest_tab_clear_set/size.json b/alacritty_terminal/tests/ref/vttest_tab_clear_set/size.json diff --git a/tests/ref/zerowidth/alacritty.recording b/alacritty_terminal/tests/ref/zerowidth/alacritty.recording diff --git a/tests/ref/zerowidth/config.json b/alacritty_terminal/tests/ref/zerowidth/config.json diff --git a/tests/ref/zerowidth/grid.json b/alacritty_terminal/tests/ref/zerowidth/grid.json diff --git a/tests/ref/zerowidth/size.json b/alacritty_terminal/tests/ref/zerowidth/size.json diff --git a/tests/ref/zsh_tab_completion/alacritty.recording b/alacritty_terminal/tests/ref/zsh_tab_completion/alacritty.recording diff --git a/tests/ref/zsh_tab_completion/grid.json b/alacritty_terminal/tests/ref/zsh_tab_completion/grid.json diff --git a/tests/ref/zsh_tab_completion/size.json b/alacritty_terminal/tests/ref/zsh_tab_completion/size.json diff --git a/build.rs b/build.rs @@ -1,83 +0,0 @@ -// 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. -#[cfg(windows)] -use embed_resource; -#[cfg(windows)] -use reqwest; -#[cfg(windows)] -use tempfile; -#[cfg(windows)] -use zip; - -use gl_generator::{Api, Fallbacks, GlobalGenerator, Profile, Registry}; - -use std::env; -use std::fs::File; -use std::path::Path; - -#[cfg(windows)] -use std::fs::OpenOptions; -#[cfg(windows)] -use std::io; - -#[cfg(windows)] -const WINPTY_PACKAGE_URL: &str = - "https://github.com/rprichard/winpty/releases/download/0.4.3/winpty-0.4.3-msvc2015.zip"; - -fn main() { - let dest = env::var("OUT_DIR").unwrap(); - let mut file = File::create(&Path::new(&dest).join("gl_bindings.rs")).unwrap(); - - Registry::new(Api::Gl, (4, 5), Profile::Core, Fallbacks::All, ["GL_ARB_blend_func_extended"]) - .write_bindings(GlobalGenerator, &mut file) - .unwrap(); - - #[cfg(windows)] - { - embed_resource::compile("extra/windows/windows.rc"); - - // Path is relative to target/{profile}/build/alacritty-HASH/out - let file = Path::new(&env::var("OUT_DIR").unwrap()).join("../../../winpty-agent.exe"); - if !file.exists() { - aquire_winpty_agent(&file); - } - } -} - -#[cfg(windows)] -fn aquire_winpty_agent(out_path: &Path) { - let tmp_dir = tempfile::Builder::new().prefix("alacritty_build").tempdir().unwrap(); - - let mut response = reqwest::get(WINPTY_PACKAGE_URL).unwrap(); - let mut file = OpenOptions::new() - .read(true) - .write(true) - .create(true) - .open(tmp_dir.path().join("winpty_package.zip")) - .unwrap(); - - io::copy(&mut response, &mut file).unwrap(); - - let mut archive = zip::ZipArchive::new(file).unwrap(); - - let target = match env::var("TARGET").unwrap().split("-").next().unwrap() { - "x86_64" => "x64", - "i386" => "ia32", - _ => panic!("architecture has no winpty binary"), - }; - - let mut winpty_agent = archive.by_name(&format!("{}/bin/winpty-agent.exe", target)).unwrap(); - - io::copy(&mut winpty_agent, &mut File::create(out_path).unwrap()).unwrap(); -} diff --git a/ci/before_deploy.sh b/ci/before_deploy.sh @@ -34,7 +34,7 @@ elif [ "$TRAVIS_OS_NAME" == "linux" ] && [ "$ARCH" != "i386" ]; then # x86_64 deb docker run -v "$(pwd):/source" undeadleech/alacritty-ubuntu \ sh -c "cd /source && \ - /root/.cargo/bin/cargo deb --no-build --output ./target/deploy/${name}-ubuntu_18_04_amd64.deb" + /root/.cargo/bin/cargo deb --no-build --manifest-path alacritty/Cargo.toml --output ./target/deploy/${name}-ubuntu_18_04_amd64.deb" # Make sure all files can be uploaded without permission errors sudo chown -R $USER:$USER "./target" @@ -49,7 +49,7 @@ elif [ "$TRAVIS_OS_NAME" == "linux" ] && [ "$ARCH" == "i386" ]; then # i386 deb docker run -v "$(pwd):/source" undeadleech/alacritty-ubuntu-i386 \ sh -c "cd /source && \ - /root/.cargo/bin/cargo deb --no-build --output ./target/deploy/${name}-ubuntu_18_04_i386.deb" + /root/.cargo/bin/cargo deb --no-build --manifest-path alacritty/Cargo.toml --output ./target/deploy/${name}-ubuntu_18_04_i386.deb" # Make sure all files can be uploaded without permission errors sudo chown -R $USER:$USER "./target" diff --git a/ci/script.sh b/ci/script.sh @@ -1,8 +1,5 @@ #!/bin/bash -# Check if any command failed -error=false - # Run clippy checks if [ "$CLIPPY" == "true" ]; then cargo clippy --all-targets @@ -17,25 +14,9 @@ fi # Run test in release mode if a tag is present, to produce an optimized binary if [ -n "$TRAVIS_TAG" ]; then - cargo test --release || error=true + # Build separately so we generate an 'alacritty' binary without -HASH appended + cargo build --release + cargo test --release else - cargo test || error=true -fi - -# Test the font subcrate -cargo test -p font || error=true - -# Test the winpty subcrate -if [ "$TRAVIS_OS_NAME" == "windows" ]; then - if [ -n "$TRAVIS_TAG" ]; then - mkdir -p "./target/debug/deps" - cp "./target/release/winpty-agent.exe" "./target/debug/deps" - else - cp "./target/debug/winpty-agent.exe" "./target/debug/deps" - fi - cargo test -p winpty || error=true -fi - -if [ $error == "true" ]; then - exit 1 + cargo test fi diff --git a/copypasta/src/x11.rs b/copypasta/src/x11.rs @@ -142,24 +142,3 @@ impl Clipboard { } } } - -#[cfg(test)] -mod tests { - use super::Clipboard; - use {Load, Store}; - - #[test] - fn clipboard_works() { - let mut clipboard = Clipboard::new().expect("create clipboard"); - let arst = "arst"; - let oien = "oien"; - clipboard.store_primary(arst).expect("store selection"); - clipboard.store_selection(oien).expect("store selection"); - - let selection = clipboard.load_selection().expect("load selection"); - let primary = clipboard.load_primary().expect("load selection"); - - assert_eq!(arst, primary); - assert_eq!(oien, selection); - } -} diff --git a/src/config/mod.rs b/src/config/mod.rs @@ -1,2749 +0,0 @@ -//! Configuration definitions and file loading -//! -//! Alacritty reads from a config file at startup to determine various runtime -//! parameters including font family and style, font size, etc. In the future, -//! the config file will also hold user and platform specific keybindings. -use std::borrow::Cow; -use std::collections::HashMap; -use std::fs::File; -use std::io::{self, Read, Write}; -use std::path::{Path, PathBuf}; -use std::str::FromStr; -use std::sync::mpsc; -use std::time::Duration; -use std::{env, fmt}; - -use font::Size; -use glutin::ModifiersState; -use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; -use serde::de::Error as SerdeError; -use serde::de::{MapAccess, Unexpected, Visitor}; -use serde::{self, de, Deserialize}; -use serde_yaml; - -use crate::ansi::CursorStyle; -use crate::cli::Options; -use crate::index::{Column, Line}; -use crate::input::{Action, Binding, KeyBinding, MouseBinding}; -use crate::term::color::Rgb; - -mod bindings; - -pub const SOURCE_FILE_PATH: &str = file!(); -const MAX_SCROLLBACK_LINES: u32 = 100_000; -static DEFAULT_ALACRITTY_CONFIG: &'static str = - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/alacritty.yml")); - -#[serde(default)] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct Selection { - #[serde(deserialize_with = "deserialize_escape_chars")] - pub semantic_escape_chars: String, - #[serde(deserialize_with = "failure_default")] - pub save_to_clipboard: bool, -} - -impl Default for Selection { - fn default() -> Selection { - Selection { - semantic_escape_chars: default_escape_chars(), - save_to_clipboard: Default::default(), - } - } -} - -fn deserialize_escape_chars<'a, D>(deserializer: D) -> ::std::result::Result<String, D::Error> -where - D: de::Deserializer<'a>, -{ - match String::deserialize(deserializer) { - Ok(escape_chars) => Ok(escape_chars), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_escape_chars()) - }, - } -} - -fn default_escape_chars() -> String { - String::from(",│`|:\"' ()[]{}<>") -} - -#[serde(default)] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct ClickHandler { - #[serde(deserialize_with = "deserialize_duration_ms")] - pub threshold: Duration, -} - -impl Default for ClickHandler { - fn default() -> Self { - ClickHandler { threshold: default_threshold_ms() } - } -} - -fn default_threshold_ms() -> Duration { - Duration::from_millis(300) -} - -fn deserialize_duration_ms<'a, D>(deserializer: D) -> ::std::result::Result<Duration, D::Error> -where - D: de::Deserializer<'a>, -{ - match u64::deserialize(deserializer) { - Ok(threshold_ms) => Ok(Duration::from_millis(threshold_ms)), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_threshold_ms()) - }, - } -} - -#[serde(default)] -#[derive(Default, Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct Mouse { - #[serde(deserialize_with = "failure_default")] - pub double_click: ClickHandler, - #[serde(deserialize_with = "failure_default")] - pub triple_click: ClickHandler, - #[serde(deserialize_with = "failure_default")] - pub hide_when_typing: bool, - #[serde(deserialize_with = "failure_default")] - pub url: Url, - - // TODO: DEPRECATED - pub faux_scrollback_lines: Option<usize>, -} - -#[serde(default)] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct Url { - // Program for opening links - #[serde(deserialize_with = "deserialize_launcher")] - pub launcher: Option<CommandWrapper>, - - // Modifier used to open links - #[serde(deserialize_with = "deserialize_modifiers")] - pub modifiers: ModifiersState, -} - -fn deserialize_launcher<'a, D>( - deserializer: D, -) -> ::std::result::Result<Option<CommandWrapper>, D::Error> -where - D: de::Deserializer<'a>, -{ - let default = Url::default().launcher; - - // Deserialize to generic value - let val = match serde_yaml::Value::deserialize(deserializer) { - Ok(val) => val, - Err(err) => { - error!("Problem with config: {}; using {}", err, default.clone().unwrap().program()); - return Ok(default); - }, - }; - - // Accept `None` to disable the launcher - if val.as_str().filter(|v| v.to_lowercase() == "none").is_some() { - return Ok(None); - } - - match <Option<CommandWrapper>>::deserialize(val) { - Ok(launcher) => Ok(launcher), - Err(err) => { - error!("Problem with config: {}; using {}", err, default.clone().unwrap().program()); - Ok(default) - }, - } -} - -impl Default for Url { - fn default() -> Url { - Url { - #[cfg(not(any(target_os = "macos", windows)))] - launcher: Some(CommandWrapper::Just(String::from("xdg-open"))), - #[cfg(target_os = "macos")] - launcher: Some(CommandWrapper::Just(String::from("open"))), - #[cfg(windows)] - launcher: Some(CommandWrapper::Just(String::from("explorer"))), - modifiers: Default::default(), - } - } -} - -fn deserialize_modifiers<'a, D>(deserializer: D) -> ::std::result::Result<ModifiersState, D::Error> -where - D: de::Deserializer<'a>, -{ - ModsWrapper::deserialize(deserializer).map(ModsWrapper::into_inner) -} - -/// `VisualBellAnimations` are modeled after a subset of CSS transitions and Robert -/// Penner's Easing Functions. -#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)] -pub enum VisualBellAnimation { - Ease, // CSS - EaseOut, // CSS - EaseOutSine, // Penner - EaseOutQuad, // Penner - EaseOutCubic, // Penner - EaseOutQuart, // Penner - EaseOutQuint, // Penner - EaseOutExpo, // Penner - EaseOutCirc, // Penner - Linear, -} - -impl Default for VisualBellAnimation { - fn default() -> Self { - VisualBellAnimation::EaseOutExpo - } -} - -#[serde(default)] -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct VisualBellConfig { - /// Visual bell animation function - #[serde(deserialize_with = "failure_default")] - animation: VisualBellAnimation, - - /// Visual bell duration in milliseconds - #[serde(deserialize_with = "failure_default")] - duration: u16, - - /// Visual bell flash color - #[serde(deserialize_with = "rgb_from_hex")] - color: Rgb, -} - -impl Default for VisualBellConfig { - fn default() -> VisualBellConfig { - VisualBellConfig { - animation: Default::default(), - duration: Default::default(), - color: default_visual_bell_color(), - } - } -} - -fn default_visual_bell_color() -> Rgb { - Rgb { r: 255, g: 255, b: 255 } -} - -impl VisualBellConfig { - /// Visual bell animation - #[inline] - pub fn animation(&self) -> VisualBellAnimation { - self.animation - } - - /// Visual bell duration in milliseconds - #[inline] - pub fn duration(&self) -> Duration { - Duration::from_millis(u64::from(self.duration)) - } - - /// Visual bell flash color - #[inline] - pub fn color(&self) -> Rgb { - self.color - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct Shell<'a> { - program: Cow<'a, str>, - - #[serde(default, deserialize_with = "failure_default")] - args: Vec<String>, -} - -impl<'a> Shell<'a> { - pub fn new<S>(program: S) -> Shell<'a> - where - S: Into<Cow<'a, str>>, - { - Shell { program: program.into(), args: Vec::new() } - } - - pub fn new_with_args<S>(program: S, args: Vec<String>) -> Shell<'a> - where - S: Into<Cow<'a, str>>, - { - Shell { program: program.into(), args } - } - - pub fn program(&self) -> &str { - &*self.program - } - - pub fn args(&self) -> &[String] { - self.args.as_slice() - } -} - -/// Wrapper around f32 that represents an alpha value between 0.0 and 1.0 -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct Alpha(f32); - -impl Alpha { - pub fn new(value: f32) -> Self { - Alpha(Self::clamp_to_valid_range(value)) - } - - pub fn set(&mut self, value: f32) { - self.0 = Self::clamp_to_valid_range(value); - } - - #[inline] - pub fn get(self) -> f32 { - self.0 - } - - fn clamp_to_valid_range(value: f32) -> f32 { - if value < 0.0 { - 0.0 - } else if value > 1.0 { - 1.0 - } else { - value - } - } -} - -impl Default for Alpha { - fn default() -> Self { - Alpha(1.0) - } -} - -#[derive(Debug, Deserialize, Copy, Clone, PartialEq, Eq)] -pub enum StartupMode { - Windowed, - Maximized, - Fullscreen, - #[cfg(target_os = "macos")] - SimpleFullscreen, -} - -impl Default for StartupMode { - fn default() -> StartupMode { - StartupMode::Windowed - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum Decorations { - Full, - Transparent, - Buttonless, - None, -} - -impl Default for Decorations { - fn default() -> Decorations { - Decorations::Full - } -} - -impl<'de> Deserialize<'de> for Decorations { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Decorations, D::Error> - where - D: de::Deserializer<'de>, - { - struct DecorationsVisitor; - - impl<'de> Visitor<'de> for DecorationsVisitor { - type Value = Decorations; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Some subset of full|transparent|buttonless|none") - } - - #[cfg(target_os = "macos")] - fn visit_str<E>(self, value: &str) -> ::std::result::Result<Decorations, E> - where - E: de::Error, - { - match value.to_lowercase().as_str() { - "transparent" => Ok(Decorations::Transparent), - "buttonless" => Ok(Decorations::Buttonless), - "none" => Ok(Decorations::None), - "full" => Ok(Decorations::Full), - "true" => { - error!( - "Deprecated decorations boolean value, use one of \ - transparent|buttonless|none|full instead; falling back to \"full\"" - ); - Ok(Decorations::Full) - }, - "false" => { - error!( - "Deprecated decorations boolean value, use one of \ - transparent|buttonless|none|full instead; falling back to \"none\"" - ); - Ok(Decorations::None) - }, - _ => { - error!("Invalid decorations value: {}; using default value", value); - Ok(Decorations::Full) - }, - } - } - - #[cfg(not(target_os = "macos"))] - fn visit_str<E>(self, value: &str) -> ::std::result::Result<Decorations, E> - where - E: de::Error, - { - match value.to_lowercase().as_str() { - "none" => Ok(Decorations::None), - "full" => Ok(Decorations::Full), - "true" => { - error!( - "Deprecated decorations boolean value, use one of none|full instead; \ - falling back to \"full\"" - ); - Ok(Decorations::Full) - }, - "false" => { - error!( - "Deprecated decorations boolean value, use one of none|full instead; \ - falling back to \"none\"" - ); - Ok(Decorations::None) - }, - "transparent" | "buttonless" => { - error!("macOS-only decorations value: {}; using default value", value); - Ok(Decorations::Full) - }, - _ => { - error!("Invalid decorations value: {}; using default value", value); - Ok(Decorations::Full) - }, - } - } - } - - deserializer.deserialize_str(DecorationsVisitor) - } -} - -#[serde(default)] -#[derive(Debug, Copy, Clone, Deserialize, PartialEq, Eq)] -pub struct WindowConfig { - /// Initial dimensions - #[serde(default, deserialize_with = "failure_default")] - dimensions: Dimensions, - - /// Initial position - #[serde(default, deserialize_with = "failure_default")] - position: Option<Delta<i32>>, - - /// Pixel padding - #[serde(deserialize_with = "deserialize_padding")] - padding: Delta<u8>, - - /// Draw the window with title bar / borders - #[serde(deserialize_with = "failure_default")] - decorations: Decorations, - - /// Spread out additional padding evenly - #[serde(deserialize_with = "failure_default")] - dynamic_padding: bool, - - /// Startup mode - #[serde(deserialize_with = "failure_default")] - startup_mode: StartupMode, - - /// TODO: DEPRECATED - #[serde(deserialize_with = "failure_default")] - start_maximized: Option<bool>, -} - -impl Default for WindowConfig { - fn default() -> Self { - WindowConfig { - dimensions: Default::default(), - position: Default::default(), - padding: default_padding(), - decorations: Default::default(), - dynamic_padding: Default::default(), - start_maximized: Default::default(), - startup_mode: Default::default(), - } - } -} - -fn default_padding() -> Delta<u8> { - Delta { x: 2, y: 2 } -} - -fn deserialize_padding<'a, D>(deserializer: D) -> ::std::result::Result<Delta<u8>, D::Error> -where - D: de::Deserializer<'a>, -{ - match Delta::deserialize(deserializer) { - Ok(delta) => Ok(delta), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_padding()) - }, - } -} - -impl WindowConfig { - pub fn decorations(&self) -> Decorations { - self.decorations - } - - pub fn dynamic_padding(&self) -> bool { - self.dynamic_padding - } - - pub fn startup_mode(&self) -> StartupMode { - self.startup_mode - } - - pub fn position(&self) -> Option<Delta<i32>> { - self.position - } -} - -/// Top-level config type -#[derive(Debug, PartialEq, Deserialize)] -pub struct Config { - /// Pixel padding - #[serde(default, deserialize_with = "failure_default")] - padding: Option<Delta<u8>>, - - /// TERM env variable - #[serde(default, deserialize_with = "failure_default")] - env: HashMap<String, String>, - - /// Font configuration - #[serde(default, deserialize_with = "failure_default")] - font: Font, - - /// Should show render timer - #[serde(default, deserialize_with = "failure_default")] - render_timer: bool, - - /// Should draw bold text with brighter colors instead of bold font - #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")] - draw_bold_text_with_bright_colors: bool, - - #[serde(default, deserialize_with = "failure_default")] - colors: Colors, - - /// Background opacity from 0.0 to 1.0 - #[serde(default, deserialize_with = "failure_default")] - background_opacity: Alpha, - - /// Window configuration - #[serde(default, deserialize_with = "failure_default")] - window: WindowConfig, - - /// Keybindings - #[serde(default = "default_key_bindings", deserialize_with = "deserialize_key_bindings")] - key_bindings: Vec<KeyBinding>, - - /// Bindings for the mouse - #[serde(default = "default_mouse_bindings", deserialize_with = "deserialize_mouse_bindings")] - mouse_bindings: Vec<MouseBinding>, - - #[serde(default, deserialize_with = "failure_default")] - selection: Selection, - - #[serde(default, deserialize_with = "failure_default")] - mouse: Mouse, - - /// Path to a shell program to run on startup - #[serde(default, deserialize_with = "failure_default")] - shell: Option<Shell<'static>>, - - /// Path where config was loaded from - #[serde(default, deserialize_with = "failure_default")] - config_path: Option<PathBuf>, - - /// Visual bell configuration - #[serde(default, deserialize_with = "failure_default")] - visual_bell: VisualBellConfig, - - /// Use dynamic title - #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")] - dynamic_title: bool, - - /// Live config reload - #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")] - live_config_reload: bool, - - /// Number of spaces in one tab - #[serde(default = "default_tabspaces", deserialize_with = "deserialize_tabspaces")] - tabspaces: usize, - - /// How much scrolling history to keep - #[serde(default, deserialize_with = "failure_default")] - scrolling: Scrolling, - - /// Cursor configuration - #[serde(default, deserialize_with = "failure_default")] - cursor: Cursor, - - /// Keep the log file after quitting - #[serde(default, deserialize_with = "failure_default")] - persistent_logging: bool, - - /// Enable experimental conpty backend instead of using winpty. - /// Will only take effect on Windows 10 Oct 2018 and later. - #[cfg(windows)] - #[serde(default, deserialize_with = "failure_default")] - enable_experimental_conpty_backend: bool, - - /// Send escape sequences using the alt key. - #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")] - alt_send_esc: bool, - - // TODO: DEPRECATED - custom_cursor_colors: Option<bool>, - - // TODO: DEPRECATED - hide_cursor_when_typing: Option<bool>, - - // TODO: DEPRECATED - cursor_style: Option<CursorStyle>, - - // TODO: DEPRECATED - unfocused_hollow_cursor: Option<bool>, - - // TODO: DEPRECATED - dimensions: Option<Dimensions>, -} - -impl Default for Config { - fn default() -> Self { - serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("default config is invalid") - } -} - -fn default_key_bindings() -> Vec<KeyBinding> { - bindings::default_key_bindings() -} - -fn default_mouse_bindings() -> Vec<MouseBinding> { - bindings::default_mouse_bindings() -} - -fn deserialize_key_bindings<'a, D>( - deserializer: D, -) -> ::std::result::Result<Vec<KeyBinding>, D::Error> -where - D: de::Deserializer<'a>, -{ - deserialize_bindings(deserializer, bindings::default_key_bindings()) -} - -fn deserialize_mouse_bindings<'a, D>( - deserializer: D, -) -> ::std::result::Result<Vec<MouseBinding>, D::Error> -where - D: de::Deserializer<'a>, -{ - deserialize_bindings(deserializer, bindings::default_mouse_bindings()) -} - -fn deserialize_bindings<'a, D, T>( - deserializer: D, - mut default: Vec<Binding<T>>, -) -> ::std::result::Result<Vec<Binding<T>>, D::Error> -where - D: de::Deserializer<'a>, - T: Copy + Eq + std::hash::Hash + std::fmt::Debug, - Binding<T>: de::Deserialize<'a>, -{ - let mut bindings: Vec<Binding<T>> = failure_default_vec(deserializer)?; - - for binding in bindings.iter() { - default.retain(|b| !b.triggers_match(binding)); - } - - bindings.extend(default); - - Ok(bindings) -} - -fn failure_default_vec<'a, D, T>(deserializer: D) -> ::std::result::Result<Vec<T>, D::Error> -where - D: de::Deserializer<'a>, - T: Deserialize<'a>, -{ - // Deserialize as generic vector - let vec = match Vec::<serde_yaml::Value>::deserialize(deserializer) { - Ok(vec) => vec, - Err(err) => { - error!("Problem with config: {}; using empty vector", err); - return Ok(Vec::new()); - }, - }; - - // Move to lossy vector - let mut bindings: Vec<T> = Vec::new(); - for value in vec { - match T::deserialize(value) { - Ok(binding) => bindings.push(binding), - Err(err) => { - error!("Problem with config: {}; skipping value", err); - }, - } - } - - Ok(bindings) -} - -fn default_tabspaces() -> usize { - 8 -} - -fn deserialize_tabspaces<'a, D>(deserializer: D) -> ::std::result::Result<usize, D::Error> -where - D: de::Deserializer<'a>, -{ - match usize::deserialize(deserializer) { - Ok(value) => Ok(value), - Err(err) => { - error!("Problem with config: {}; using 8", err); - Ok(default_tabspaces()) - }, - } -} - -fn deserialize_true_bool<'a, D>(deserializer: D) -> ::std::result::Result<bool, D::Error> -where - D: de::Deserializer<'a>, -{ - match bool::deserialize(deserializer) { - Ok(value) => Ok(value), - Err(err) => { - error!("Problem with config: {}; using true", err); - Ok(true) - }, - } -} - -fn default_true_bool() -> bool { - true -} - -fn failure_default<'a, D, T>(deserializer: D) -> ::std::result::Result<T, D::Error> -where - D: de::Deserializer<'a>, - T: Deserialize<'a> + Default, -{ - match T::deserialize(deserializer) { - Ok(value) => Ok(value), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(T::default()) - }, - } -} - -/// Struct for scrolling related settings -#[serde(default)] -#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)] -pub struct Scrolling { - #[serde(deserialize_with = "deserialize_scrolling_history")] - pub history: u32, - #[serde(deserialize_with = "deserialize_scrolling_multiplier")] - pub multiplier: u8, - #[serde(deserialize_with = "deserialize_scrolling_multiplier")] - pub faux_multiplier: u8, - #[serde(deserialize_with = "failure_default")] - pub auto_scroll: bool, -} - -impl Default for Scrolling { - fn default() -> Self { - Self { - history: default_scrolling_history(), - multiplier: default_scrolling_multiplier(), - faux_multiplier: default_scrolling_multiplier(), - auto_scroll: Default::default(), - } - } -} - -fn default_scrolling_history() -> u32 { - 10_000 -} - -// Default for normal and faux scrolling -fn default_scrolling_multiplier() -> u8 { - 3 -} - -fn deserialize_scrolling_history<'a, D>(deserializer: D) -> ::std::result::Result<u32, D::Error> -where - D: de::Deserializer<'a>, -{ - match u32::deserialize(deserializer) { - Ok(lines) => { - if lines > MAX_SCROLLBACK_LINES { - error!( - "Problem with config: scrollback size is {}, but expected a maximum of {}; \ - using {1} instead", - lines, MAX_SCROLLBACK_LINES, - ); - Ok(MAX_SCROLLBACK_LINES) - } else { - Ok(lines) - } - }, - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_scrolling_history()) - }, - } -} - -fn deserialize_scrolling_multiplier<'a, D>(deserializer: D) -> ::std::result::Result<u8, D::Error> -where - D: de::Deserializer<'a>, -{ - match u8::deserialize(deserializer) { - Ok(lines) => Ok(lines), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_scrolling_multiplier()) - }, - } -} - -/// Newtype for implementing deserialize on glutin Mods -/// -/// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the -/// impl below. -#[derive(Debug, Copy, Clone, Hash, Default, Eq, PartialEq)] -struct ModsWrapper(ModifiersState); - -impl ModsWrapper { - fn into_inner(self) -> ModifiersState { - self.0 - } -} - -impl<'a> de::Deserialize<'a> for ModsWrapper { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - struct ModsVisitor; - - impl<'a> Visitor<'a> for ModsVisitor { - type Value = ModsWrapper; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Some subset of Command|Shift|Super|Alt|Option|Control") - } - - fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModsWrapper, E> - where - E: de::Error, - { - let mut res = ModifiersState::default(); - for modifier in value.split('|') { - match modifier.trim() { - "Command" | "Super" => res.logo = true, - "Shift" => res.shift = true, - "Alt" | "Option" => res.alt = true, - "Control" => res.ctrl = true, - "None" => (), - _ => error!("Unknown modifier {:?}", modifier), - } - } - - Ok(ModsWrapper(res)) - } - } - - deserializer.deserialize_str(ModsVisitor) - } -} - -struct ActionWrapper(crate::input::Action); - -impl ActionWrapper { - fn into_inner(self) -> crate::input::Action { - self.0 - } -} - -impl<'a> de::Deserialize<'a> for ActionWrapper { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - struct ActionVisitor; - - impl<'a> Visitor<'a> for ActionVisitor { - type Value = ActionWrapper; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str( - "Paste, Copy, PasteSelection, IncreaseFontSize, DecreaseFontSize, \ - ResetFontSize, ScrollPageUp, ScrollPageDown, ScrollLineUp, ScrollLineDown, \ - ScrollToTop, ScrollToBottom, ClearHistory, Hide, ClearLogNotice, \ - SpawnNewInstance, ToggleFullscreen, ToggleSimpleFullscreen, None or Quit", - ) - } - - fn visit_str<E>(self, value: &str) -> ::std::result::Result<ActionWrapper, E> - where - E: de::Error, - { - Ok(ActionWrapper(match value { - "Paste" => Action::Paste, - "Copy" => Action::Copy, - "PasteSelection" => Action::PasteSelection, - "IncreaseFontSize" => Action::IncreaseFontSize, - "DecreaseFontSize" => Action::DecreaseFontSize, - "ResetFontSize" => Action::ResetFontSize, - "ScrollPageUp" => Action::ScrollPageUp, - "ScrollPageDown" => Action::ScrollPageDown, - "ScrollLineUp" => Action::ScrollLineUp, - "ScrollLineDown" => Action::ScrollLineDown, - "ScrollToTop" => Action::ScrollToTop, - "ScrollToBottom" => Action::ScrollToBottom, - "ClearHistory" => Action::ClearHistory, - "Hide" => Action::Hide, - "Quit" => Action::Quit, - "ClearLogNotice" => Action::ClearLogNotice, - "SpawnNewInstance" => Action::SpawnNewInstance, - "ToggleFullscreen" => Action::ToggleFullscreen, - #[cfg(target_os = "macos")] - "ToggleSimpleFullscreen" => Action::ToggleSimpleFullscreen, - "None" => Action::None, - _ => return Err(E::invalid_value(Unexpected::Str(value), &self)), - })) - } - } - deserializer.deserialize_str(ActionVisitor) - } -} - -#[serde(untagged)] -#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] -pub enum CommandWrapper { - Just(String), - WithArgs { - program: String, - #[serde(default)] - args: Vec<String>, - }, -} - -impl CommandWrapper { - pub fn program(&self) -> &str { - match self { - CommandWrapper::Just(program) => program, - CommandWrapper::WithArgs { program, .. } => program, - } - } - - pub fn args(&self) -> &[String] { - match self { - CommandWrapper::Just(_) => &[], - CommandWrapper::WithArgs { args, .. } => args, - } - } -} - -use crate::term::{mode, TermMode}; - -struct ModeWrapper { - pub mode: TermMode, - pub not_mode: TermMode, -} - -impl<'a> de::Deserialize<'a> for ModeWrapper { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - struct ModeVisitor; - - impl<'a> Visitor<'a> for ModeVisitor { - type Value = ModeWrapper; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Combination of AppCursor | AppKeypad, possibly with negation (~)") - } - - fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModeWrapper, E> - where - E: de::Error, - { - let mut res = ModeWrapper { mode: TermMode::empty(), not_mode: TermMode::empty() }; - - for modifier in value.split('|') { - match modifier.trim() { - "AppCursor" => res.mode |= mode::TermMode::APP_CURSOR, - "~AppCursor" => res.not_mode |= mode::TermMode::APP_CURSOR, - "AppKeypad" => res.mode |= mode::TermMode::APP_KEYPAD, - "~AppKeypad" => res.not_mode |= mode::TermMode::APP_KEYPAD, - "~Alt" => res.not_mode |= mode::TermMode::ALT_SCREEN, - "Alt" => res.mode |= mode::TermMode::ALT_SCREEN, - _ => error!("Unknown mode {:?}", modifier), - } - } - - Ok(res) - } - } - deserializer.deserialize_str(ModeVisitor) - } -} - -struct MouseButton(::glutin::MouseButton); - -impl MouseButton { - fn into_inner(self) -> ::glutin::MouseButton { - self.0 - } -} - -impl<'a> de::Deserialize<'a> for MouseButton { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - struct MouseButtonVisitor; - - impl<'a> Visitor<'a> for MouseButtonVisitor { - type Value = MouseButton; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Left, Right, Middle, or a number") - } - - fn visit_str<E>(self, value: &str) -> ::std::result::Result<MouseButton, E> - where - E: de::Error, - { - match value { - "Left" => Ok(MouseButton(::glutin::MouseButton::Left)), - "Right" => Ok(MouseButton(::glutin::MouseButton::Right)), - "Middle" => Ok(MouseButton(::glutin::MouseButton::Middle)), - _ => { - if let Ok(index) = u8::from_str(value) { - Ok(MouseButton(::glutin::MouseButton::Other(index))) - } else { - Err(E::invalid_value(Unexpected::Str(value), &self)) - } - }, - } - } - } - - deserializer.deserialize_str(MouseButtonVisitor) - } -} - -/// Bindings are deserialized into a `RawBinding` before being parsed as a -/// `KeyBinding` or `MouseBinding`. -#[derive(PartialEq, Eq)] -struct RawBinding { - key: Option<Key>, - mouse: Option<::glutin::MouseButton>, - mods: ModifiersState, - mode: TermMode, - notmode: TermMode, - action: Action, -} - -impl RawBinding { - fn into_mouse_binding(self) -> ::std::result::Result<MouseBinding, Self> { - if let Some(mouse) = self.mouse { - Ok(Binding { - trigger: mouse, - mods: self.mods, - action: self.action, - mode: self.mode, - notmode: self.notmode, - }) - } else { - Err(self) - } - } - - fn into_key_binding(self) -> ::std::result::Result<KeyBinding, Self> { - if let Some(key) = self.key { - Ok(KeyBinding { - trigger: key, - mods: self.mods, - action: self.action, - mode: self.mode, - notmode: self.notmode, - }) - } else { - Err(self) - } - } -} - -impl<'a> de::Deserialize<'a> for RawBinding { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - enum Field { - Key, - Mods, - Mode, - Action, - Chars, - Mouse, - Command, - } - - impl<'a> de::Deserialize<'a> for Field { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Field, D::Error> - where - D: de::Deserializer<'a>, - { - struct FieldVisitor; - - static FIELDS: &'static [&'static str] = - &["key", "mods", "mode", "action", "chars", "mouse", "command"]; - - impl<'a> Visitor<'a> for FieldVisitor { - type Value = Field; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("binding fields") - } - - fn visit_str<E>(self, value: &str) -> ::std::result::Result<Field, E> - where - E: de::Error, - { - match value { - "key" => Ok(Field::Key), - "mods" => Ok(Field::Mods), - "mode" => Ok(Field::Mode), - "action" => Ok(Field::Action), - "chars" => Ok(Field::Chars), - "mouse" => Ok(Field::Mouse), - "command" => Ok(Field::Command), - _ => Err(E::unknown_field(value, FIELDS)), - } - } - } - - deserializer.deserialize_str(FieldVisitor) - } - } - - struct RawBindingVisitor; - impl<'a> Visitor<'a> for RawBindingVisitor { - type Value = RawBinding; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("binding specification") - } - - fn visit_map<V>(self, mut map: V) -> ::std::result::Result<RawBinding, V::Error> - where - V: MapAccess<'a>, - { - let mut mods: Option<ModifiersState> = None; - let mut key: Option<Key> = None; - let mut chars: Option<String> = None; - let mut action: Option<crate::input::Action> = None; - let mut mode: Option<TermMode> = None; - let mut not_mode: Option<TermMode> = None; - let mut mouse: Option<::glutin::MouseButton> = None; - let mut command: Option<CommandWrapper> = None; - - use ::serde::de::Error; - - while let Some(struct_key) = map.next_key::<Field>()? { - match struct_key { - Field::Key => { - if key.is_some() { - return Err(<V::Error as Error>::duplicate_field("key")); - } - - let val = map.next_value::<serde_yaml::Value>()?; - if val.is_u64() { - let scancode = val.as_u64().unwrap(); - if scancode > u64::from(::std::u32::MAX) { - return Err(<V::Error as Error>::custom(format!( - "Invalid key binding, scancode too big: {}", - scancode - ))); - } - key = Some(Key::Scancode(scancode as u32)); - } else { - let k = Key::deserialize(val).map_err(V::Error::custom)?; - key = Some(k); - } - }, - Field::Mods => { - if mods.is_some() { - return Err(<V::Error as Error>::duplicate_field("mods")); - } - - mods = Some(map.next_value::<ModsWrapper>()?.into_inner()); - }, - Field::Mode => { - if mode.is_some() { - return Err(<V::Error as Error>::duplicate_field("mode")); - } - - let mode_deserializer = map.next_value::<ModeWrapper>()?; - mode = Some(mode_deserializer.mode); - not_mode = Some(mode_deserializer.not_mode); - }, - Field::Action => { - if action.is_some() { - return Err(<V::Error as Error>::duplicate_field("action")); - } - - action = Some(map.next_value::<ActionWrapper>()?.into_inner()); - }, - Field::Chars => { - if chars.is_some() { - return Err(<V::Error as Error>::duplicate_field("chars")); - } - - chars = Some(map.next_value()?); - }, - Field::Mouse => { - if chars.is_some() { - return Err(<V::Error as Error>::duplicate_field("mouse")); - } - - mouse = Some(map.next_value::<MouseButton>()?.into_inner()); - }, - Field::Command => { - if command.is_some() { - return Err(<V::Error as Error>::duplicate_field("command")); - } - - command = Some(map.next_value::<CommandWrapper>()?); - }, - } - } - - let action = match (action, chars, command) { - (Some(action), None, None) => action, - (None, Some(chars), None) => Action::Esc(chars), - (None, None, Some(cmd)) => match cmd { - CommandWrapper::Just(program) => Action::Command(program, vec![]), - CommandWrapper::WithArgs { program, args } => { - Action::Command(program, args) - }, - }, - (None, None, None) => { - return Err(V::Error::custom("must specify chars, action or command")); - }, - _ => { - return Err(V::Error::custom("must specify only chars, action or command")) - }, - }; - - let mode = mode.unwrap_or_else(TermMode::empty); - let not_mode = not_mode.unwrap_or_else(TermMode::empty); - let mods = mods.unwrap_or_else(ModifiersState::default); - - if mouse.is_none() && key.is_none() { - return Err(V::Error::custom("bindings require mouse button or key")); - } - - Ok(RawBinding { mode, notmode: not_mode, action, key, mouse, mods }) - } - } - - const FIELDS: &[&str] = &["key", "mods", "mode", "action", "chars", "mouse", "command"]; - - deserializer.deserialize_struct("RawBinding", FIELDS, RawBindingVisitor) - } -} - -impl<'a> de::Deserialize<'a> for Alpha { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - let value = f32::deserialize(deserializer)?; - Ok(Alpha::new(value)) - } -} - -impl<'a> de::Deserialize<'a> for MouseBinding { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - let raw = RawBinding::deserialize(deserializer)?; - raw.into_mouse_binding().map_err(|_| D::Error::custom("expected mouse binding")) - } -} - -impl<'a> de::Deserialize<'a> for KeyBinding { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - let raw = RawBinding::deserialize(deserializer)?; - raw.into_key_binding().map_err(|_| D::Error::custom("expected key binding")) - } -} - -/// Errors occurring during config loading -#[derive(Debug)] -pub enum Error { - /// Config file not found - NotFound, - - /// Config file empty - Empty, - - /// Couldn't read $HOME environment variable - ReadingEnvHome(env::VarError), - - /// io error reading file - Io(io::Error), - - /// Not valid yaml or missing parameters - Yaml(serde_yaml::Error), -} - -#[serde(default)] -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct Colors { - #[serde(deserialize_with = "failure_default")] - pub primary: PrimaryColors, - #[serde(deserialize_with = "failure_default")] - pub cursor: CursorColors, - #[serde(deserialize_with = "failure_default")] - pub selection: SelectionColors, - #[serde(deserialize_with = "deserialize_normal_colors")] - pub normal: AnsiColors, - #[serde(deserialize_with = "deserialize_bright_colors")] - pub bright: AnsiColors, - #[serde(deserialize_with = "failure_default")] - pub dim: Option<AnsiColors>, - #[serde(deserialize_with = "failure_default_vec")] - pub indexed_colors: Vec<IndexedColor>, -} - -impl Default for Colors { - fn default() -> Colors { - Colors { - primary: Default::default(), - cursor: Default::default(), - selection: Default::default(), - normal: default_normal_colors(), - bright: default_bright_colors(), - dim: Default::default(), - indexed_colors: Default::default(), - } - } -} - -fn default_normal_colors() -> AnsiColors { - AnsiColors { - black: Rgb { r: 0x00, g: 0x00, b: 0x00 }, - red: Rgb { r: 0xd5, g: 0x4e, b: 0x53 }, - green: Rgb { r: 0xb9, g: 0xca, b: 0x4a }, - yellow: Rgb { r: 0xe6, g: 0xc5, b: 0x47 }, - blue: Rgb { r: 0x7a, g: 0xa6, b: 0xda }, - magenta: Rgb { r: 0xc3, g: 0x97, b: 0xd8 }, - cyan: Rgb { r: 0x70, g: 0xc0, b: 0xba }, - white: Rgb { r: 0xea, g: 0xea, b: 0xea }, - } -} - -fn default_bright_colors() -> AnsiColors { - AnsiColors { - black: Rgb { r: 0x66, g: 0x66, b: 0x66 }, - red: Rgb { r: 0xff, g: 0x33, b: 0x34 }, - green: Rgb { r: 0x9e, g: 0xc4, b: 0x00 }, - yellow: Rgb { r: 0xe7, g: 0xc5, b: 0x47 }, - blue: Rgb { r: 0x7a, g: 0xa6, b: 0xda }, - magenta: Rgb { r: 0xb7, g: 0x7e, b: 0xe0 }, - cyan: Rgb { r: 0x54, g: 0xce, b: 0xd6 }, - white: Rgb { r: 0xff, g: 0xff, b: 0xff }, - } -} - -fn deserialize_normal_colors<'a, D>(deserializer: D) -> ::std::result::Result<AnsiColors, D::Error> -where - D: de::Deserializer<'a>, -{ - match AnsiColors::deserialize(deserializer) { - Ok(escape_chars) => Ok(escape_chars), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_normal_colors()) - }, - } -} - -fn deserialize_bright_colors<'a, D>(deserializer: D) -> ::std::result::Result<AnsiColors, D::Error> -where - D: de::Deserializer<'a>, -{ - match AnsiColors::deserialize(deserializer) { - Ok(escape_chars) => Ok(escape_chars), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_bright_colors()) - }, - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct IndexedColor { - #[serde(deserialize_with = "deserialize_color_index")] - pub index: u8, - #[serde(deserialize_with = "rgb_from_hex")] - pub color: Rgb, -} - -fn deserialize_color_index<'a, D>(deserializer: D) -> ::std::result::Result<u8, D::Error> -where - D: de::Deserializer<'a>, -{ - match u8::deserialize(deserializer) { - Ok(index) => { - if index < 16 { - error!( - "Problem with config: indexed_color's index is {}, but a value bigger than 15 \ - was expected; ignoring setting", - index - ); - - // Return value out of range to ignore this color - Ok(0) - } else { - Ok(index) - } - }, - Err(err) => { - error!("Problem with config: {}; ignoring setting", err); - - // Return value out of range to ignore this color - Ok(0) - }, - } -} - -#[serde(default)] -#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct Cursor { - #[serde(deserialize_with = "failure_default")] - pub style: CursorStyle, - #[serde(deserialize_with = "deserialize_true_bool")] - pub unfocused_hollow: bool, -} - -impl Default for Cursor { - fn default() -> Self { - Self { style: Default::default(), unfocused_hollow: true } - } -} - -#[serde(default)] -#[derive(Debug, Copy, Clone, Default, Deserialize, PartialEq, Eq)] -pub struct CursorColors { - #[serde(deserialize_with = "deserialize_optional_color")] - pub text: Option<Rgb>, - #[serde(deserialize_with = "deserialize_optional_color")] - pub cursor: Option<Rgb>, -} - -#[serde(default)] -#[derive(Debug, Copy, Clone, Default, Deserialize, PartialEq, Eq)] -pub struct SelectionColors { - #[serde(deserialize_with = "deserialize_optional_color")] - pub text: Option<Rgb>, - #[serde(deserialize_with = "deserialize_optional_color")] - pub background: Option<Rgb>, -} - -#[serde(default)] -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct PrimaryColors { - #[serde(deserialize_with = "rgb_from_hex")] - pub background: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub foreground: Rgb, - #[serde(deserialize_with = "deserialize_optional_color")] - pub bright_foreground: Option<Rgb>, - #[serde(deserialize_with = "deserialize_optional_color")] - pub dim_foreground: Option<Rgb>, -} - -impl Default for PrimaryColors { - fn default() -> Self { - PrimaryColors { - background: default_background(), - foreground: default_foreground(), - bright_foreground: Default::default(), - dim_foreground: Default::default(), - } - } -} - -fn deserialize_optional_color<'a, D>( - deserializer: D, -) -> ::std::result::Result<Option<Rgb>, D::Error> -where - D: de::Deserializer<'a>, -{ - match Option::deserialize(deserializer) { - Ok(Some(color)) => { - let color: serde_yaml::Value = color; - Ok(Some(rgb_from_hex(color).unwrap())) - }, - Ok(None) => Ok(None), - Err(err) => { - error!("Problem with config: {}; using standard foreground color", err); - Ok(None) - }, - } -} - -fn default_background() -> Rgb { - Rgb { r: 0, g: 0, b: 0 } -} - -fn default_foreground() -> Rgb { - Rgb { r: 0xea, g: 0xea, b: 0xea } -} - -/// The 8-colors sections of config -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct AnsiColors { - #[serde(deserialize_with = "rgb_from_hex")] - pub black: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub red: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub green: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub yellow: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub blue: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub magenta: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub cyan: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub white: Rgb, -} - -/// Deserialize an Rgb from a hex string -/// -/// This is *not* the deserialize impl for Rgb since we want a symmetric -/// serialize/deserialize impl for ref tests. -fn rgb_from_hex<'a, D>(deserializer: D) -> ::std::result::Result<Rgb, D::Error> -where - D: de::Deserializer<'a>, -{ - struct RgbVisitor; - - impl<'a> Visitor<'a> for RgbVisitor { - type Value = Rgb; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("hex color like 0xff00ff") - } - - fn visit_str<E>(self, value: &str) -> ::std::result::Result<Rgb, E> - where - E: ::serde::de::Error, - { - Rgb::from_str(&value[..]) - .map_err(|_| E::custom("failed to parse rgb; expected hex color like 0xff00ff")) - } - } - - let rgb = deserializer.deserialize_str(RgbVisitor); - - // Use #ff00ff as fallback color - match rgb { - Ok(rgb) => Ok(rgb), - Err(err) => { - error!("Problem with config: {}; using color #ff00ff", err); - Ok(Rgb { r: 255, g: 0, b: 255 }) - }, - } -} - -impl FromStr for Rgb { - type Err = (); - - fn from_str(s: &str) -> ::std::result::Result<Rgb, ()> { - let mut chars = s.chars(); - let mut rgb = Rgb::default(); - - macro_rules! component { - ($($c:ident),*) => { - $( - match chars.next().and_then(|c| c.to_digit(16)) { - Some(val) => rgb.$c = (val as u8) << 4, - None => return Err(()) - } - - match chars.next().and_then(|c| c.to_digit(16)) { - Some(val) => rgb.$c |= val as u8, - None => return Err(()) - } - )* - } - } - - match chars.next() { - Some('0') => { - if chars.next() != Some('x') { - return Err(()); - } - }, - Some('#') => (), - _ => return Err(()), - } - - component!(r, g, b); - - Ok(rgb) - } -} - -impl ::std::error::Error for Error { - fn cause(&self) -> Option<&dyn (::std::error::Error)> { - match *self { - Error::NotFound | Error::Empty => None, - Error::ReadingEnvHome(ref err) => Some(err), - Error::Io(ref err) => Some(err), - Error::Yaml(ref err) => Some(err), - } - } - - fn description(&self) -> &str { - match *self { - Error::NotFound => "Couldn't locate config file", - Error::Empty => "Empty config file", - Error::ReadingEnvHome(ref err) => err.description(), - Error::Io(ref err) => err.description(), - Error::Yaml(ref err) => err.description(), - } - } -} - -impl ::std::fmt::Display for Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - match *self { - Error::NotFound | Error::Empty => { - write!(f, "{}", ::std::error::Error::description(self)) - }, - Error::ReadingEnvHome(ref err) => { - write!(f, "Couldn't read $HOME environment variable: {}", err) - }, - Error::Io(ref err) => write!(f, "Error reading config file: {}", err), - Error::Yaml(ref err) => write!(f, "Problem with config: {}", err), - } - } -} - -impl From<env::VarError> for Error { - fn from(val: env::VarError) -> Error { - Error::ReadingEnvHome(val) - } -} - -impl From<io::Error> for Error { - fn from(val: io::Error) -> Error { - if val.kind() == io::ErrorKind::NotFound { - Error::NotFound - } else { - Error::Io(val) - } - } -} - -impl From<serde_yaml::Error> for Error { - fn from(val: serde_yaml::Error) -> Error { - Error::Yaml(val) - } -} - -/// Result from config loading -pub type Result<T> = ::std::result::Result<T, Error>; - -impl Config { - /// Get the location of the first found default config file paths - /// according to the following order: - /// - /// 1. $XDG_CONFIG_HOME/alacritty/alacritty.yml - /// 2. $XDG_CONFIG_HOME/alacritty.yml - /// 3. $HOME/.config/alacritty/alacritty.yml - /// 4. $HOME/.alacritty.yml - #[cfg(not(windows))] - pub fn installed_config<'a>() -> Option<Cow<'a, Path>> { - // Try using XDG location by default - ::xdg::BaseDirectories::with_prefix("alacritty") - .ok() - .and_then(|xdg| xdg.find_config_file("alacritty.yml")) - .or_else(|| { - ::xdg::BaseDirectories::new() - .ok() - .and_then(|fallback| fallback.find_config_file("alacritty.yml")) - }) - .or_else(|| { - if let Ok(home) = env::var("HOME") { - // Fallback path: $HOME/.config/alacritty/alacritty.yml - let fallback = PathBuf::from(&home).join(".config/alacritty/alacritty.yml"); - if fallback.exists() { - return Some(fallback); - } - // Fallback path: $HOME/.alacritty.yml - let fallback = PathBuf::from(&home).join(".alacritty.yml"); - if fallback.exists() { - return Some(fallback); - } - } - None - }) - .map(Into::into) - } - - // TODO: Remove old configuration location warning (Deprecated 03/12/2018) - #[cfg(windows)] - pub fn installed_config<'a>() -> Option<Cow<'a, Path>> { - let old = dirs::home_dir().map(|path| path.join("alacritty.yml")); - let new = dirs::config_dir().map(|path| path.join("alacritty\\alacritty.yml")); - - if let Some(old_path) = old.as_ref().filter(|old| old.exists()) { - warn!( - "Found configuration at: {}; this file should be moved to the new location: {}", - old_path.to_string_lossy(), - new.as_ref().map(|new| new.to_string_lossy()).unwrap(), - ); - - old.map(Cow::from) - } else { - new.filter(|new| new.exists()).map(Cow::from) - } - } - - #[cfg(not(windows))] - pub fn write_defaults() -> io::Result<Cow<'static, Path>> { - let path = xdg::BaseDirectories::with_prefix("alacritty") - .map_err(|err| io::Error::new(io::ErrorKind::NotFound, err.to_string().as_str())) - .and_then(|p| p.place_config_file("alacritty.yml"))?; - - File::create(&path)?.write_all(DEFAULT_ALACRITTY_CONFIG.as_bytes())?; - - Ok(path.into()) - } - - #[cfg(windows)] - pub fn write_defaults() -> io::Result<Cow<'static, Path>> { - let mut path = dirs::config_dir().ok_or_else(|| { - io::Error::new(io::ErrorKind::NotFound, "Couldn't find profile directory") - })?; - - path = path.join("alacritty/alacritty.yml"); - - std::fs::create_dir_all(path.parent().unwrap())?; - - File::create(&path)?.write_all(DEFAULT_ALACRITTY_CONFIG.as_bytes())?; - - Ok(path.into()) - } - - /// Get list of colors - /// - /// The ordering returned here is expected by the terminal. Colors are simply indexed in this - /// array for performance. - pub fn colors(&self) -> &Colors { - &self.colors - } - - #[inline] - pub fn background_opacity(&self) -> Alpha { - self.background_opacity - } - - pub fn key_bindings(&self) -> &[KeyBinding] { - &self.key_bindings[..] - } - - pub fn mouse_bindings(&self) -> &[MouseBinding] { - &self.mouse_bindings[..] - } - - pub fn mouse(&self) -> &Mouse { - &self.mouse - } - - pub fn selection(&self) -> &Selection { - &self.selection - } - - pub fn tabspaces(&self) -> usize { - self.tabspaces - } - - pub fn padding(&self) -> &Delta<u8> { - self.padding.as_ref().unwrap_or(&self.window.padding) - } - - #[inline] - pub fn draw_bold_text_with_bright_colors(&self) -> bool { - self.draw_bold_text_with_bright_colors - } - - /// Get font config - #[inline] - pub fn font(&self) -> &Font { - &self.font - } - - /// Get window dimensions - #[inline] - pub fn dimensions(&self) -> Dimensions { - self.dimensions.unwrap_or(self.window.dimensions) - } - - /// Get window config - #[inline] - pub fn window(&self) -> &WindowConfig { - &self.window - } - - /// Get visual bell config - #[inline] - pub fn visual_bell(&self) -> &VisualBellConfig { - &self.visual_bell - } - - /// Should show render timer - #[inline] - pub fn render_timer(&self) -> bool { - self.render_timer - } - - #[cfg(target_os = "macos")] - #[inline] - pub fn use_thin_strokes(&self) -> bool { - self.font.use_thin_strokes - } - - #[cfg(not(target_os = "macos"))] - #[inline] - pub fn use_thin_strokes(&self) -> bool { - false - } - - pub fn path(&self) -> Option<&Path> { - self.config_path.as_ref().map(PathBuf::as_path) - } - - pub fn shell(&self) -> Option<&Shell<'_>> { - self.shell.as_ref() - } - - pub fn env(&self) -> &HashMap<String, String> { - &self.env - } - - /// Should hide mouse cursor when typing - #[inline] - pub fn hide_mouse_when_typing(&self) -> bool { - self.hide_cursor_when_typing.unwrap_or(self.mouse.hide_when_typing) - } - - /// Style of the cursor - #[inline] - pub fn cursor_style(&self) -> CursorStyle { - self.cursor_style.unwrap_or(self.cursor.style) - } - - /// Use hollow block cursor when unfocused - #[inline] - pub fn unfocused_hollow_cursor(&self) -> bool { - self.unfocused_hollow_cursor.unwrap_or(self.cursor.unfocused_hollow) - } - - /// Live config reload - #[inline] - pub fn live_config_reload(&self) -> bool { - self.live_config_reload - } - - #[inline] - pub fn dynamic_title(&self) -> bool { - self.dynamic_title - } - - /// Scrolling settings - #[inline] - pub fn scrolling(&self) -> Scrolling { - self.scrolling - } - - /// Cursor foreground color - #[inline] - pub fn cursor_text_color(&self) -> Option<Rgb> { - self.colors.cursor.text - } - - /// Cursor background color - #[inline] - pub fn cursor_cursor_color(&self) -> Option<Rgb> { - self.colors.cursor.cursor - } - - /// Enable experimental conpty backend (Windows only) - #[cfg(windows)] - #[inline] - pub fn enable_experimental_conpty_backend(&self) -> bool { - self.enable_experimental_conpty_backend - } - - /// Send escape sequences using the alt key - #[inline] - pub fn alt_send_esc(&self) -> bool { - self.alt_send_esc - } - - // Update the history size, used in ref tests - pub fn set_history(&mut self, history: u32) { - self.scrolling.history = history; - } - - /// Keep the log file after quitting Alacritty - #[inline] - pub fn persistent_logging(&self) -> bool { - self.persistent_logging - } - - /// Overrides the `dynamic_title` configuration based on `--title`. - pub fn update_dynamic_title(mut self, options: &Options) -> Self { - if options.title.is_some() { - self.dynamic_title = false; - } - self - } - - pub fn load_from(path: PathBuf) -> Config { - let mut config = Config::reload_from(&path).unwrap_or_else(|_| Config::default()); - config.config_path = Some(path); - config - } - - pub fn reload_from(path: &PathBuf) -> Result<Config> { - match Config::read_config(path) { - Ok(config) => Ok(config), - Err(err) => { - error!("Unable to load config {:?}: {}", path, err); - Err(err) - }, - } - } - - fn read_config(path: &PathBuf) -> Result<Config> { - let mut contents = String::new(); - File::open(path)?.read_to_string(&mut contents)?; - - // Prevent parsing error with empty string - if contents.is_empty() { - return Ok(Config::default()); - } - - let mut config: Config = serde_yaml::from_str(&contents)?; - config.print_deprecation_warnings(); - - Ok(config) - } - - fn print_deprecation_warnings(&mut self) { - if self.dimensions.is_some() { - warn!("Config dimensions is deprecated; please use window.dimensions instead"); - } - - if self.padding.is_some() { - warn!("Config padding is deprecated; please use window.padding instead"); - } - - if self.mouse.faux_scrollback_lines.is_some() { - warn!( - "Config mouse.faux_scrollback_lines is deprecated; please use \ - mouse.faux_scrolling_lines instead" - ); - } - - if let Some(custom_cursor_colors) = self.custom_cursor_colors { - warn!("Config custom_cursor_colors is deprecated"); - - if !custom_cursor_colors { - self.colors.cursor.cursor = None; - self.colors.cursor.text = None; - } - } - - if self.cursor_style.is_some() { - warn!("Config cursor_style is deprecated; please use cursor.style instead"); - } - - if self.hide_cursor_when_typing.is_some() { - warn!( - "Config hide_cursor_when_typing is deprecated; please use mouse.hide_when_typing \ - instead" - ); - } - - if self.unfocused_hollow_cursor.is_some() { - warn!( - "Config unfocused_hollow_cursor is deprecated; please use cursor.unfocused_hollow \ - instead" - ); - } - - if let Some(start_maximized) = self.window.start_maximized { - warn!( - "Config window.start_maximized is deprecated; please use window.startup_mode \ - instead" - ); - - // While `start_maximized` is deprecated its setting takes precedence. - if start_maximized { - self.window.startup_mode = StartupMode::Maximized; - } - } - } -} - -/// Window Dimensions -/// -/// Newtype to avoid passing values incorrectly -#[serde(default)] -#[derive(Default, Debug, Copy, Clone, Deserialize, PartialEq, Eq)] -pub struct Dimensions { - /// Window width in character columns - #[serde(deserialize_with = "failure_default")] - columns: Column, - - /// Window Height in character lines - #[serde(deserialize_with = "failure_default")] - lines: Line, -} - -impl Dimensions { - pub fn new(columns: Column, lines: Line) -> Self { - Dimensions { columns, lines } - } - - /// Get lines - #[inline] - pub fn lines_u32(&self) -> u32 { - self.lines.0 as u32 - } - - /// Get columns - #[inline] - pub fn columns_u32(&self) -> u32 { - self.columns.0 as u32 - } -} - -/// A delta for a point in a 2 dimensional plane -#[serde(default, bound(deserialize = "T: Deserialize<'de> + Default"))] -#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)] -pub struct Delta<T: Default + PartialEq + Eq> { - /// Horizontal change - #[serde(deserialize_with = "failure_default")] - pub x: T, - /// Vertical change - #[serde(deserialize_with = "failure_default")] - pub y: T, -} - -trait DeserializeSize: Sized { - fn deserialize<'a, D>(_: D) -> ::std::result::Result<Self, D::Error> - where - D: serde::de::Deserializer<'a>; -} - -impl DeserializeSize for Size { - fn deserialize<'a, D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: serde::de::Deserializer<'a>, - { - use std::marker::PhantomData; - - struct NumVisitor<__D> { - _marker: PhantomData<__D>, - } - - impl<'a, __D> Visitor<'a> for NumVisitor<__D> - where - __D: serde::de::Deserializer<'a>, - { - type Value = f64; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("f64 or u64") - } - - fn visit_f64<E>(self, value: f64) -> ::std::result::Result<Self::Value, E> - where - E: ::serde::de::Error, - { - Ok(value) - } - - fn visit_u64<E>(self, value: u64) -> ::std::result::Result<Self::Value, E> - where - E: ::serde::de::Error, - { - Ok(value as f64) - } - } - - let size = deserializer - .deserialize_any(NumVisitor::<D> { _marker: PhantomData }) - .map(|v| Size::new(v as _)); - - // Use default font size as fallback - match size { - Ok(size) => Ok(size), - Err(err) => { - let size = default_font_size(); - error!("Problem with config: {}; using size {}", err, size.as_f32_pts()); - Ok(size) - }, - } - } -} - -/// Font config -/// -/// Defaults are provided at the level of this struct per platform, but not per -/// field in this struct. It might be nice in the future to have defaults for -/// each value independently. Alternatively, maybe erroring when the user -/// doesn't provide complete config is Ok. -#[serde(default)] -#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] -pub struct Font { - /// Normal font face - #[serde(deserialize_with = "failure_default")] - normal: FontDescription, - - /// Bold font face - #[serde(deserialize_with = "failure_default")] - italic: SecondaryFontDescription, - - /// Italic font face - #[serde(deserialize_with = "failure_default")] - bold: SecondaryFontDescription, - - /// Font size in points - #[serde(deserialize_with = "DeserializeSize::deserialize")] - pub size: Size, - - /// Extra spacing per character - #[serde(deserialize_with = "failure_default")] - offset: Delta<i8>, - - /// Glyph offset within character cell - #[serde(deserialize_with = "failure_default")] - glyph_offset: Delta<i8>, - - #[cfg(target_os = "macos")] - #[serde(deserialize_with = "deserialize_true_bool")] - use_thin_strokes: bool, - - // TODO: Deprecated - #[serde(deserialize_with = "deserialize_scale_with_dpi")] - scale_with_dpi: Option<()>, -} - -impl Default for Font { - fn default() -> Font { - Font { - #[cfg(target_os = "macos")] - use_thin_strokes: true, - size: default_font_size(), - normal: Default::default(), - bold: Default::default(), - italic: Default::default(), - scale_with_dpi: Default::default(), - glyph_offset: Default::default(), - offset: Default::default(), - } - } -} - -impl Font { - /// Get the font size in points - #[inline] - pub fn size(&self) -> Size { - self.size - } - - /// Get offsets to font metrics - #[inline] - pub fn offset(&self) -> &Delta<i8> { - &self.offset - } - - /// Get cell offsets for glyphs - #[inline] - pub fn glyph_offset(&self) -> &Delta<i8> { - &self.glyph_offset - } - - /// Get a font clone with a size modification - pub fn with_size(self, size: Size) -> Font { - Font { size, ..self } - } - - // Get normal font description - pub fn normal(&self) -> &FontDescription { - &self.normal - } - - // Get italic font description - pub fn italic(&self) -> FontDescription { - self.italic.desc(&self.normal) - } - - // Get bold font description - pub fn bold(&self) -> FontDescription { - self.bold.desc(&self.normal) - } -} - -fn default_font_size() -> Size { - Size::new(11.) -} - -fn deserialize_scale_with_dpi<'a, D>(deserializer: D) -> ::std::result::Result<Option<()>, D::Error> -where - D: de::Deserializer<'a>, -{ - // This is necessary in order to get serde to complete deserialization of the configuration - let _ignored = bool::deserialize(deserializer); - error!( - "The scale_with_dpi setting has been removed, on X11 the WINIT_HIDPI_FACTOR environment \ - variable can be used instead." - ); - Ok(None) -} - -/// Description of the normal font -#[serde(default)] -#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] -pub struct FontDescription { - #[serde(deserialize_with = "failure_default")] - pub family: String, - #[serde(deserialize_with = "failure_default")] - pub style: Option<String>, -} - -impl Default for FontDescription { - fn default() -> FontDescription { - FontDescription { - #[cfg(not(any(target_os = "macos", windows)))] - family: "monospace".into(), - #[cfg(target_os = "macos")] - family: "Menlo".into(), - #[cfg(windows)] - family: "Consolas".into(), - style: None, - } - } -} - -/// Description of the italic and bold font -#[serde(default)] -#[derive(Debug, Default, Deserialize, Clone, PartialEq, Eq)] -pub struct SecondaryFontDescription { - #[serde(deserialize_with = "failure_default")] - family: Option<String>, - #[serde(deserialize_with = "failure_default")] - style: Option<String>, -} - -impl SecondaryFontDescription { - pub fn desc(&self, fallback: &FontDescription) -> FontDescription { - FontDescription { - family: self.family.clone().unwrap_or_else(|| fallback.family.clone()), - style: self.style.clone(), - } - } -} - -pub struct Monitor { - _thread: ::std::thread::JoinHandle<()>, - rx: mpsc::Receiver<PathBuf>, -} - -pub trait OnConfigReload { - fn on_config_reload(&mut self); -} - -impl OnConfigReload for crate::display::Notifier { - fn on_config_reload(&mut self) { - self.notify(); - } -} - -impl Monitor { - /// Get pending config changes - pub fn pending(&self) -> Option<PathBuf> { - let mut config = None; - while let Ok(new) = self.rx.try_recv() { - config = Some(new); - } - - config - } - - pub fn new<H, P>(path: P, mut handler: H) -> Monitor - where - H: OnConfigReload + Send + 'static, - P: Into<PathBuf>, - { - let path = path.into(); - - let (config_tx, config_rx) = mpsc::channel(); - - Monitor { - _thread: crate::util::thread::spawn_named("config watcher", move || { - let (tx, rx) = mpsc::channel(); - // The Duration argument is a debouncing period. - let mut watcher = - watcher(tx, Duration::from_millis(10)).expect("Unable to spawn file watcher"); - let config_path = ::std::fs::canonicalize(path).expect("canonicalize config path"); - - // Get directory of config - let mut parent = config_path.clone(); - parent.pop(); - - // Watch directory - watcher - .watch(&parent, RecursiveMode::NonRecursive) - .expect("watch alacritty.yml dir"); - - loop { - match rx.recv().expect("watcher event") { - DebouncedEvent::Rename(..) => continue, - DebouncedEvent::Write(path) - | DebouncedEvent::Create(path) - | DebouncedEvent::Chmod(path) => { - if path != config_path { - continue; - } - - let _ = config_tx.send(path); - handler.on_config_reload(); - }, - _ => {}, - } - } - }), - rx: config_rx, - } - } -} - -#[derive(Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub enum Key { - Scancode(u32), - Key1, - Key2, - Key3, - Key4, - Key5, - Key6, - Key7, - Key8, - Key9, - Key0, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - Escape, - F1, - F2, - F3, - F4, - F5, - F6, - F7, - F8, - F9, - F10, - F11, - F12, - F13, - F14, - F15, - F16, - F17, - F18, - F19, - F20, - F21, - F22, - F23, - F24, - Snapshot, - Scroll, - Pause, - Insert, - Home, - Delete, - End, - PageDown, - PageUp, - Left, - Up, - Right, - Down, - Back, - Return, - Space, - Compose, - Numlock, - Numpad0, - Numpad1, - Numpad2, - Numpad3, - Numpad4, - Numpad5, - Numpad6, - Numpad7, - Numpad8, - Numpad9, - AbntC1, - AbntC2, - Add, - Apostrophe, - Apps, - At, - Ax, - Backslash, - Calculator, - Capital, - Colon, - Comma, - Convert, - Decimal, - Divide, - Equals, - Grave, - Kana, - Kanji, - LAlt, - LBracket, - LControl, - LShift, - LWin, - Mail, - MediaSelect, - MediaStop, - Minus, - Multiply, - Mute, - MyComputer, - NavigateForward, - NavigateBackward, - NextTrack, - NoConvert, - NumpadComma, - NumpadEnter, - NumpadEquals, - OEM102, - Period, - PlayPause, - Power, - PrevTrack, - RAlt, - RBracket, - RControl, - RShift, - RWin, - Semicolon, - Slash, - Sleep, - Stop, - Subtract, - Sysrq, - Tab, - Underline, - Unlabeled, - VolumeDown, - VolumeUp, - Wake, - WebBack, - WebFavorites, - WebForward, - WebHome, - WebRefresh, - WebSearch, - WebStop, - Yen, - Caret, - Copy, - Paste, - Cut, -} - -impl Key { - pub fn from_glutin_input(key: ::glutin::VirtualKeyCode) -> Self { - use glutin::VirtualKeyCode::*; - // Thank you, vim macros and regex! - match key { - Key1 => Key::Key1, - Key2 => Key::Key2, - Key3 => Key::Key3, - Key4 => Key::Key4, - Key5 => Key::Key5, - Key6 => Key::Key6, - Key7 => Key::Key7, - Key8 => Key::Key8, - Key9 => Key::Key9, - Key0 => Key::Key0, - A => Key::A, - B => Key::B, - C => Key::C, - D => Key::D, - E => Key::E, - F => Key::F, - G => Key::G, - H => Key::H, - I => Key::I, - J => Key::J, - K => Key::K, - L => Key::L, - M => Key::M, - N => Key::N, - O => Key::O, - P => Key::P, - Q => Key::Q, - R => Key::R, - S => Key::S, - T => Key::T, - U => Key::U, - V => Key::V, - W => Key::W, - X => Key::X, - Y => Key::Y, - Z => Key::Z, - Escape => Key::Escape, - F1 => Key::F1, - F2 => Key::F2, - F3 => Key::F3, - F4 => Key::F4, - F5 => Key::F5, - F6 => Key::F6, - F7 => Key::F7, - F8 => Key::F8, - F9 => Key::F9, - F10 => Key::F10, - F11 => Key::F11, - F12 => Key::F12, - F13 => Key::F13, - F14 => Key::F14, - F15 => Key::F15, - F16 => Key::F16, - F17 => Key::F17, - F18 => Key::F18, - F19 => Key::F19, - F20 => Key::F20, - F21 => Key::F21, - F22 => Key::F22, - F23 => Key::F23, - F24 => Key::F24, - Snapshot => Key::Snapshot, - Scroll => Key::Scroll, - Pause => Key::Pause, - Insert => Key::Insert, - Home => Key::Home, - Delete => Key::Delete, - End => Key::End, - PageDown => Key::PageDown, - PageUp => Key::PageUp, - Left => Key::Left, - Up => Key::Up, - Right => Key::Right, - Down => Key::Down, - Back => Key::Back, - Return => Key::Return, - Space => Key::Space, - Compose => Key::Compose, - Numlock => Key::Numlock, - Numpad0 => Key::Numpad0, - Numpad1 => Key::Numpad1, - Numpad2 => Key::Numpad2, - Numpad3 => Key::Numpad3, - Numpad4 => Key::Numpad4, - Numpad5 => Key::Numpad5, - Numpad6 => Key::Numpad6, - Numpad7 => Key::Numpad7, - Numpad8 => Key::Numpad8, - Numpad9 => Key::Numpad9, - AbntC1 => Key::AbntC1, - AbntC2 => Key::AbntC2, - Add => Key::Add, - Apostrophe => Key::Apostrophe, - Apps => Key::Apps, - At => Key::At, - Ax => Key::Ax, - Backslash => Key::Backslash, - Calculator => Key::Calculator, - Capital => Key::Capital, - Colon => Key::Colon, - Comma => Key::Comma, - Convert => Key::Convert, - Decimal => Key::Decimal, - Divide => Key::Divide, - Equals => Key::Equals, - Grave => Key::Grave, - Kana => Key::Kana, - Kanji => Key::Kanji, - LAlt => Key::LAlt, - LBracket => Key::LBracket, - LControl => Key::LControl, - LShift => Key::LShift, - LWin => Key::LWin, - Mail => Key::Mail, - MediaSelect => Key::MediaSelect, - MediaStop => Key::MediaStop, - Minus => Key::Minus, - Multiply => Key::Multiply, - Mute => Key::Mute, - MyComputer => Key::MyComputer, - NavigateForward => Key::NavigateForward, - NavigateBackward => Key::NavigateBackward, - NextTrack => Key::NextTrack, - NoConvert => Key::NoConvert, - NumpadComma => Key::NumpadComma, - NumpadEnter => Key::NumpadEnter, - NumpadEquals => Key::NumpadEquals, - OEM102 => Key::OEM102, - Period => Key::Period, - PlayPause => Key::PlayPause, - Power => Key::Power, - PrevTrack => Key::PrevTrack, - RAlt => Key::RAlt, - RBracket => Key::RBracket, - RControl => Key::RControl, - RShift => Key::RShift, - RWin => Key::RWin, - Semicolon => Key::Semicolon, - Slash => Key::Slash, - Sleep => Key::Sleep, - Stop => Key::Stop, - Subtract => Key::Subtract, - Sysrq => Key::Sysrq, - Tab => Key::Tab, - Underline => Key::Underline, - Unlabeled => Key::Unlabeled, - VolumeDown => Key::VolumeDown, - VolumeUp => Key::VolumeUp, - Wake => Key::Wake, - WebBack => Key::WebBack, - WebFavorites => Key::WebFavorites, - WebForward => Key::WebForward, - WebHome => Key::WebHome, - WebRefresh => Key::WebRefresh, - WebSearch => Key::WebSearch, - WebStop => Key::WebStop, - Yen => Key::Yen, - Caret => Key::Caret, - Copy => Key::Copy, - Paste => Key::Paste, - Cut => Key::Cut, - } - } -} - -#[cfg(test)] -mod tests { - use super::{Config, DEFAULT_ALACRITTY_CONFIG}; - use crate::cli::Options; - - #[test] - fn parse_config() { - let config: Config = - ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config"); - - // Sanity check that mouse bindings are being parsed - assert!(!config.mouse_bindings.is_empty()); - - // Sanity check that key bindings are being parsed - assert!(!config.key_bindings.is_empty()); - } - - #[test] - fn dynamic_title_ignoring_options_by_default() { - let config: Config = - ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config"); - let old_dynamic_title = config.dynamic_title; - let options = Options::default(); - let config = config.update_dynamic_title(&options); - assert_eq!(old_dynamic_title, config.dynamic_title); - } - - #[test] - fn dynamic_title_overridden_by_options() { - let config: Config = - ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config"); - let mut options = Options::default(); - options.title = Some("foo".to_owned()); - let config = config.update_dynamic_title(&options); - assert!(!config.dynamic_title); - } - - #[test] - fn default_match_empty() { - let default = Config::default(); - - let empty = serde_yaml::from_str("key: val\n").unwrap(); - - assert_eq!(default, empty); - } -} diff --git a/src/lib.rs b/src/lib.rs @@ -1,61 +0,0 @@ -// 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. -// -//! Alacritty - The GPU Enhanced Terminal -#![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use, clippy::wrong_pub_self_convention)] -#![cfg_attr(feature = "nightly", feature(core_intrinsics))] -#![cfg_attr(all(test, feature = "bench"), feature(test))] - -#[macro_use] -extern crate log; -#[macro_use] -extern crate serde_derive; - -#[cfg(target_os = "macos")] -#[macro_use] -extern crate objc; - -#[macro_use] -pub mod macros; -pub mod ansi; -pub mod cli; -pub mod config; -mod cursor; -pub mod display; -pub mod event; -pub mod event_loop; -pub mod grid; -pub mod index; -pub mod input; -pub mod locale; -pub mod logging; -pub mod message_bar; -pub mod meter; -pub mod panic; -pub mod renderer; -pub mod selection; -pub mod sync; -pub mod term; -pub mod tty; -mod url; -pub mod util; -pub mod window; - -pub use crate::grid::Grid; -pub use crate::term::Term; - -pub mod gl { - #![allow(clippy::all)] - include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); -} diff --git a/src/logging.rs b/src/logging.rs @@ -1,198 +0,0 @@ -// 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. -// -//! Logging for alacritty. -//! -//! The main executable is supposed to call `initialize()` exactly once during -//! startup. All logging messages are written to stdout, given that their -//! log-level is sufficient for the level configured in `cli::Options`. -use std::env; -use std::fs::{File, OpenOptions}; -use std::io::{self, LineWriter, Stdout, Write}; -use std::path::PathBuf; -use std::process; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Mutex}; - -use crossbeam_channel::Sender; -use log::{self, Level}; -use time; - -use crate::cli; -use crate::message_bar::Message; -use crate::term::color; - -const ALACRITTY_LOG_ENV: &str = "ALACRITTY_LOG"; - -pub fn initialize( - options: &cli::Options, - message_tx: Sender<Message>, -) -> Result<Option<PathBuf>, log::SetLoggerError> { - // Use env_logger if RUST_LOG environment variable is defined. Otherwise, - // use the alacritty-only logger. - if ::std::env::var("RUST_LOG").is_ok() { - ::env_logger::try_init()?; - Ok(None) - } else { - let logger = Logger::new(options.log_level, message_tx); - let path = logger.file_path(); - log::set_boxed_logger(Box::new(logger))?; - Ok(path) - } -} - -pub struct Logger { - level: log::LevelFilter, - logfile: Mutex<OnDemandLogFile>, - stdout: Mutex<LineWriter<Stdout>>, - message_tx: Sender<Message>, -} - -impl Logger { - // False positive, see: https://github.com/rust-lang-nursery/rust-clippy/issues/734 - #[allow(clippy::new_ret_no_self)] - fn new(level: log::LevelFilter, message_tx: Sender<Message>) -> Self { - log::set_max_level(level); - - let logfile = Mutex::new(OnDemandLogFile::new()); - let stdout = Mutex::new(LineWriter::new(io::stdout())); - - Logger { level, logfile, stdout, message_tx } - } - - fn file_path(&self) -> Option<PathBuf> { - if let Ok(logfile) = self.logfile.lock() { - Some(logfile.path().clone()) - } else { - None - } - } -} - -impl log::Log for Logger { - fn enabled(&self, metadata: &log::Metadata<'_>) -> bool { - metadata.level() <= self.level - } - - fn log(&self, record: &log::Record<'_>) { - if self.enabled(record.metadata()) && record.target().starts_with("alacritty") { - let now = time::strftime("%F %R", &time::now()).unwrap(); - - let msg = if record.level() >= Level::Trace { - format!( - "[{}] [{}] [{}:{}] {}\n", - now, - record.level(), - record.file().unwrap_or("?"), - record.line().map(|l| l.to_string()).unwrap_or_else(|| "?".into()), - record.args() - ) - } else { - format!("[{}] [{}] {}\n", now, record.level(), record.args()) - }; - - if let Ok(ref mut logfile) = self.logfile.lock() { - let _ = logfile.write_all(msg.as_ref()); - - if record.level() <= Level::Warn { - #[cfg(not(windows))] - let env_var = format!("${}", ALACRITTY_LOG_ENV); - #[cfg(windows)] - let env_var = format!("%{}%", ALACRITTY_LOG_ENV); - - let msg = format!( - "[{}] See log at {} ({}):\n{}", - record.level(), - logfile.path.to_string_lossy(), - env_var, - record.args(), - ); - let color = match record.level() { - Level::Error => color::RED, - Level::Warn => color::YELLOW, - _ => unreachable!(), - }; - - let mut message = Message::new(msg, color); - message.set_topic(record.file().unwrap_or("?").into()); - let _ = self.message_tx.send(message); - } - } - - if let Ok(ref mut stdout) = self.stdout.lock() { - let _ = stdout.write_all(msg.as_ref()); - } - } - } - - fn flush(&self) {} -} - -struct OnDemandLogFile { - file: Option<LineWriter<File>>, - created: Arc<AtomicBool>, - path: PathBuf, -} - -impl OnDemandLogFile { - fn new() -> Self { - let mut path = env::temp_dir(); - path.push(format!("Alacritty-{}.log", process::id())); - - // Set log path as an environment variable - env::set_var(ALACRITTY_LOG_ENV, path.as_os_str()); - - OnDemandLogFile { path, file: None, created: Arc::new(AtomicBool::new(false)) } - } - - fn file(&mut self) -> Result<&mut LineWriter<File>, io::Error> { - // Allow to recreate the file if it has been deleted at runtime - if self.file.is_some() && !self.path.as_path().exists() { - self.file = None; - } - - // Create the file if it doesn't exist yet - if self.file.is_none() { - let file = OpenOptions::new().append(true).create(true).open(&self.path); - - match file { - Ok(file) => { - self.file = Some(io::LineWriter::new(file)); - self.created.store(true, Ordering::Relaxed); - let _ = writeln!(io::stdout(), "Created log file at {:?}", self.path); - }, - Err(e) => { - let _ = writeln!(io::stdout(), "Unable to create log file: {}", e); - return Err(e); - }, - } - } - - Ok(self.file.as_mut().unwrap()) - } - - fn path(&self) -> &PathBuf { - &self.path - } -} - -impl Write for OnDemandLogFile { - fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> { - self.file()?.write(buf) - } - - fn flush(&mut self) -> Result<(), io::Error> { - self.file()?.flush() - } -} diff --git a/src/main.rs b/src/main.rs @@ -1,270 +0,0 @@ -// 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. -// -//! Alacritty - The GPU Enhanced Terminal -#![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use, clippy::wrong_pub_self_convention)] -#![cfg_attr(feature = "nightly", feature(core_intrinsics))] -#![cfg_attr(all(test, feature = "bench"), feature(test))] -// With the default subsystem, 'console', windows creates an additional console -// window for the program. -// This is silently ignored on non-windows systems. -// See https://msdn.microsoft.com/en-us/library/4cc7ya5b.aspx for more details. -#![windows_subsystem = "windows"] - -#[cfg(target_os = "macos")] -use dirs; - -#[cfg(windows)] -use winapi::um::wincon::{AttachConsole, FreeConsole, ATTACH_PARENT_PROCESS}; - -use log::{error, info}; - -use std::error::Error; -use std::fs; -use std::io::{self, Write}; -use std::sync::Arc; - -#[cfg(target_os = "macos")] -use std::env; - -#[cfg(not(windows))] -use std::os::unix::io::AsRawFd; - -use alacritty::config::{self, Config, Monitor}; -use alacritty::display::Display; -use alacritty::event_loop::{self, EventLoop, Msg}; -#[cfg(target_os = "macos")] -use alacritty::locale; -use alacritty::logging; -use alacritty::message_bar::MessageBuffer; -use alacritty::panic; -use alacritty::sync::FairMutex; -use alacritty::term::Term; -use alacritty::tty; -use alacritty::util::fmt::Red; -use alacritty::{cli, die, event}; - -fn main() { - panic::attach_handler(); - - // When linked with the windows subsystem windows won't automatically attach - // to the console of the parent process, so we do it explicitly. This fails - // silently if the parent has no console. - #[cfg(windows)] - unsafe { - AttachConsole(ATTACH_PARENT_PROCESS); - } - - // Load command line options - let options = cli::Options::load(); - - // Setup storage for message UI - let message_buffer = MessageBuffer::new(); - - // Initialize the logger as soon as possible as to capture output from other subsystems - let log_file = - logging::initialize(&options, message_buffer.tx()).expect("Unable to initialize logger"); - - // Load configuration file - // If the file is a command line argument, we won't write a generated default file - let config_path = options - .config_path() - .or_else(Config::installed_config) - .or_else(|| Config::write_defaults().ok()) - .map(|path| path.to_path_buf()); - let config = if let Some(path) = config_path { - Config::load_from(path).update_dynamic_title(&options) - } else { - error!("Unable to write the default config"); - Config::default() - }; - - // Switch to home directory - #[cfg(target_os = "macos")] - env::set_current_dir(dirs::home_dir().unwrap()).unwrap(); - // Set locale - #[cfg(target_os = "macos")] - locale::set_locale_environment(); - - // Store if log file should be deleted before moving config - let persistent_logging = options.persistent_logging || config.persistent_logging(); - - // Run alacritty - if let Err(err) = run(config, &options, message_buffer) { - die!("Alacritty encountered an unrecoverable error:\n\n\t{}\n", Red(err)); - } - - // Clean up logfile - if let Some(log_file) = log_file { - if !persistent_logging && fs::remove_file(&log_file).is_ok() { - let _ = writeln!(io::stdout(), "Deleted log file at {:?}", log_file); - } - } -} - -/// Run Alacritty -/// -/// Creates a window, the terminal state, pty, I/O event loop, input processor, -/// config change monitor, and runs the main display loop. -fn run( - mut config: Config, - options: &cli::Options, - message_buffer: MessageBuffer, -) -> Result<(), Box<dyn Error>> { - info!("Welcome to Alacritty"); - if let Some(config_path) = config.path() { - info!("Configuration loaded from {:?}", config_path.display()); - }; - - // Set environment variables - tty::setup_env(&config); - - // Create a display. - // - // The display manages a window and can draw the terminal - let mut display = Display::new(&config, options)?; - - info!("PTY Dimensions: {:?} x {:?}", display.size().lines(), display.size().cols()); - - // 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 = Term::new(&config, display.size().to_owned(), message_buffer); - let terminal = Arc::new(FairMutex::new(terminal)); - - // Find the window ID for setting $WINDOWID - let window_id = display.get_window_id(); - - // 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 = tty::new(&config, options, &display.size(), window_id); - - // Get a reference to something that we can resize - // - // This exists because rust doesn't know the interface is thread-safe - // and we need to be able to resize the PTY from the main thread while the IO - // thread owns the EventedRW object. - #[cfg(windows)] - let mut resize_handle = pty.resize_handle(); - #[cfg(not(windows))] - let mut resize_handle = pty.fd.as_raw_fd(); - - // 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(Arc::clone(&terminal), display.notifier(), pty, options.ref_test); - - // The event loop channel allows write requests from the event processor - // to be sent to the loop and ultimately written to the pty. - let loop_tx = event_loop.channel(); - - // Event processor - // - // Need the Rc<RefCell<_>> here since a ref is shared in the resize callback - let mut processor = event::Processor::new( - event_loop::Notifier(event_loop.channel()), - display.resize_channel(), - options, - &config, - options.ref_test, - display.size().to_owned(), - ); - - // Create a config monitor when config was loaded from path - // - // The monitor watches the config file for changes and reloads it. Pending - // config changes are processed in the main loop. - let config_monitor = match (options.live_config_reload, config.live_config_reload()) { - // Start monitor if CLI flag says yes - (Some(true), _) | - // Or if no CLI flag was passed and the config says yes - (None, true) => config.path() - .map(|path| config::Monitor::new(path, display.notifier())), - // Otherwise, don't start the monitor - _ => None, - }; - - // Kick off the I/O thread - let _io_thread = event_loop.spawn(None); - - info!("Initialisation complete"); - - // Main display loop - loop { - // Process input and window events - let mut terminal_lock = processor.process_events(&terminal, display.window()); - - // Handle config reloads - if let Some(ref path) = config_monitor.as_ref().and_then(Monitor::pending) { - // Clear old config messages from bar - terminal_lock.message_buffer_mut().remove_topic(config::SOURCE_FILE_PATH); - - if let Ok(new_config) = Config::reload_from(path) { - config = new_config.update_dynamic_title(options); - display.update_config(&config); - processor.update_config(&config); - terminal_lock.update_config(&config); - } - - terminal_lock.dirty = true; - } - - // Begin shutdown if the flag was raised - if terminal_lock.should_exit() || tty::process_should_exit() { - break; - } - - // Maybe draw the terminal - if terminal_lock.needs_draw() { - // Try to update the position of the input method editor - #[cfg(not(windows))] - display.update_ime_position(&terminal_lock); - - // Handle pending resize events - // - // The second argument is a list of types that want to be notified - // of display size changes. - display.handle_resize(&mut terminal_lock, &config, &mut resize_handle, &mut processor); - - drop(terminal_lock); - - // Draw the current state of the terminal - display.draw(&terminal, &config); - } - } - - loop_tx.send(Msg::Shutdown).expect("Error sending shutdown to event loop"); - - // FIXME patch notify library to have a shutdown method - // config_reloader.join().ok(); - - // Without explicitly detaching the console cmd won't redraw it's prompt - #[cfg(windows)] - unsafe { - FreeConsole(); - } - - info!("Goodbye"); - - Ok(()) -} diff --git a/src/meter.rs b/src/meter.rs @@ -1,110 +0,0 @@ -// 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. -// -//! Rendering time meter -//! -//! Used to track rendering times and provide moving averages. -//! -//! # Examples -//! -//! ```rust -//! // create a meter -//! let mut meter = alacritty::meter::Meter::new(); -//! -//! // Sample something. -//! { -//! let _sampler = meter.sampler(); -//! } -//! -//! // Get the moving average. The meter tracks a fixed number of samples, and -//! // the average won't mean much until it's filled up at least once. -//! println!("Average time: {}", meter.average()); - -use std::time::{Duration, Instant}; - -const NUM_SAMPLES: usize = 10; - -/// The meter -#[derive(Default)] -pub struct Meter { - /// Track last 60 timestamps - times: [f64; NUM_SAMPLES], - - /// Average sample time in microseconds - avg: f64, - - /// Index of next time to update. - index: usize, -} - -/// Sampler -/// -/// Samplers record how long they are "alive" for and update the meter on drop. -pub struct Sampler<'a> { - /// Reference to meter that created the sampler - meter: &'a mut Meter, - - // When the sampler was created - created_at: Instant, -} - -impl<'a> Sampler<'a> { - fn new(meter: &'a mut Meter) -> Sampler<'a> { - Sampler { meter, created_at: Instant::now() } - } - - #[inline] - fn alive_duration(&self) -> Duration { - self.created_at.elapsed() - } -} - -impl<'a> Drop for Sampler<'a> { - fn drop(&mut self) { - self.meter.add_sample(self.alive_duration()); - } -} - -impl Meter { - /// Create a meter - pub fn new() -> Meter { - Default::default() - } - - /// Get a sampler - pub fn sampler(&mut self) -> Sampler<'_> { - Sampler::new(self) - } - - /// Get the current average sample duration in microseconds - pub fn average(&self) -> f64 { - self.avg - } - - /// Add a sample - /// - /// Used by Sampler::drop. - fn add_sample(&mut self, sample: Duration) { - let mut usec = 0f64; - - usec += f64::from(sample.subsec_nanos()) / 1e3; - usec += (sample.as_secs() as f64) * 1e6; - - let prev = self.times[self.index]; - self.times[self.index] = usec; - self.avg -= prev / NUM_SAMPLES as f64; - self.avg += usec / NUM_SAMPLES as f64; - self.index = (self.index + 1) % NUM_SAMPLES; - } -} diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs @@ -1,1625 +0,0 @@ -// 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. -use std::collections::HashMap; -use std::fs::File; -use std::hash::BuildHasherDefault; -use std::io::{self, Read}; -use std::mem::size_of; -use std::path::PathBuf; -use std::ptr; -use std::sync::mpsc; -use std::time::Duration; - -use fnv::FnvHasher; -use font::{self, FontDesc, FontKey, GlyphKey, Rasterize, RasterizedGlyph, Rasterizer}; -use glutin::dpi::PhysicalSize; -use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; - -use crate::ansi::CursorStyle; -use crate::config::{self, Config, Delta}; -use crate::gl; -use crate::gl::types::*; -use crate::index::{Column, Line}; -use crate::renderer::rects::{Rect, Rects}; -use crate::term::color::Rgb; -use crate::term::{self, cell, RenderableCell, RenderableCellContent}; - -pub mod rects; - -// Shader paths for live reload -static TEXT_SHADER_F_PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.f.glsl"); -static TEXT_SHADER_V_PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.v.glsl"); -static RECT_SHADER_F_PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/rect.f.glsl"); -static RECT_SHADER_V_PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/rect.v.glsl"); - -// Shader source which is used when live-shader-reload feature is disable -static TEXT_SHADER_F: &'static str = - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.f.glsl")); -static TEXT_SHADER_V: &'static str = - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.v.glsl")); -static RECT_SHADER_F: &'static str = - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/res/rect.f.glsl")); -static RECT_SHADER_V: &'static str = - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/res/rect.v.glsl")); - -/// `LoadGlyph` allows for copying a rasterized glyph into graphics memory -pub trait LoadGlyph { - /// Load the rasterized glyph into GPU memory - fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph; - - /// Clear any state accumulated from previous loaded glyphs - /// - /// This can, for instance, be used to reset the texture Atlas. - fn clear(&mut self); -} - -enum Msg { - ShaderReload, -} - -#[derive(Debug)] -pub enum Error { - ShaderCreation(ShaderCreationError), -} - -impl ::std::error::Error for Error { - fn cause(&self) -> Option<&dyn (::std::error::Error)> { - match *self { - Error::ShaderCreation(ref err) => Some(err), - } - } - - fn description(&self) -> &str { - match *self { - Error::ShaderCreation(ref err) => err.description(), - } - } -} - -impl ::std::fmt::Display for Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - match *self { - Error::ShaderCreation(ref err) => { - write!(f, "There was an error initializing the shaders: {}", err) - }, - } - } -} - -impl From<ShaderCreationError> for Error { - fn from(val: ShaderCreationError) -> Error { - Error::ShaderCreation(val) - } -} - -/// Text drawing program -/// -/// Uniforms are prefixed with "u", and vertex attributes are prefixed with "a". -#[derive(Debug)] -pub struct TextShaderProgram { - // Program id - id: GLuint, - - /// projection scale and offset uniform - u_projection: GLint, - - /// Cell dimensions (pixels) - u_cell_dim: GLint, - - /// Background pass flag - /// - /// Rendering is split into two passes; 1 for backgrounds, and one for text - u_background: GLint, -} - -/// Rectangle drawing program -/// -/// Uniforms are prefixed with "u" -#[derive(Debug)] -pub struct RectShaderProgram { - // Program id - id: GLuint, - /// Rectangle color - u_color: GLint, -} - -#[derive(Copy, Debug, Clone)] -pub struct Glyph { - tex_id: GLuint, - top: f32, - left: f32, - width: f32, - height: f32, - uv_bot: f32, - uv_left: f32, - uv_width: f32, - uv_height: f32, -} - -/// Naïve glyph cache -/// -/// Currently only keyed by `char`, and thus not possible to hold different -/// representations of the same code point. -pub struct GlyphCache { - /// Cache of buffered glyphs - cache: HashMap<GlyphKey, Glyph, BuildHasherDefault<FnvHasher>>, - - /// Cache of buffered cursor glyphs - cursor_cache: HashMap<CursorStyle, Glyph, BuildHasherDefault<FnvHasher>>, - - /// Rasterizer for loading new glyphs - rasterizer: Rasterizer, - - /// regular font - font_key: FontKey, - - /// italic font - italic_key: FontKey, - - /// bold font - bold_key: FontKey, - - /// font size - font_size: font::Size, - - /// glyph offset - glyph_offset: Delta<i8>, - - metrics: ::font::Metrics, -} - -impl GlyphCache { - pub fn new<L>( - mut rasterizer: Rasterizer, - font: &config::Font, - loader: &mut L, - ) -> Result<GlyphCache, font::Error> - where - L: LoadGlyph, - { - let (regular, bold, italic) = Self::compute_font_keys(font, &mut rasterizer)?; - - // Need to load at least one glyph for the face before calling metrics. - // The glyph requested here ('m' at the time of writing) has no special - // meaning. - rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; - - let metrics = rasterizer.metrics(regular, font.size())?; - - let mut cache = GlyphCache { - cache: HashMap::default(), - cursor_cache: HashMap::default(), - rasterizer, - font_size: font.size(), - font_key: regular, - bold_key: bold, - italic_key: italic, - glyph_offset: *font.glyph_offset(), - metrics, - }; - - cache.load_glyphs_for_font(regular, loader); - cache.load_glyphs_for_font(bold, loader); - cache.load_glyphs_for_font(italic, loader); - - Ok(cache) - } - - fn load_glyphs_for_font<L: LoadGlyph>(&mut self, font: FontKey, loader: &mut L) { - let size = self.font_size; - for i in 32u8..=128u8 { - self.get(GlyphKey { font_key: font, c: i as char, size }, loader); - } - } - - /// Computes font keys for (Regular, Bold, Italic) - fn compute_font_keys( - font: &config::Font, - rasterizer: &mut Rasterizer, - ) -> Result<(FontKey, FontKey, FontKey), font::Error> { - let size = font.size(); - - // Load regular font - let regular_desc = - Self::make_desc(&font.normal(), font::Slant::Normal, font::Weight::Normal); - - let regular = rasterizer.load_font(&regular_desc, size)?; - - // helper to load a description if it is not the regular_desc - let mut load_or_regular = |desc: FontDesc| { - if desc == regular_desc { - regular - } else { - rasterizer.load_font(&desc, size).unwrap_or_else(|_| regular) - } - }; - - // Load bold font - let bold_desc = Self::make_desc(&font.bold(), font::Slant::Normal, font::Weight::Bold); - - let bold = load_or_regular(bold_desc); - - // Load italic font - let italic_desc = - Self::make_desc(&font.italic(), font::Slant::Italic, font::Weight::Normal); - - let italic = load_or_regular(italic_desc); - - Ok((regular, bold, italic)) - } - - fn make_desc( - desc: &config::FontDescription, - slant: font::Slant, - weight: font::Weight, - ) -> FontDesc { - let style = if let Some(ref spec) = desc.style { - font::Style::Specific(spec.to_owned()) - } else { - font::Style::Description { slant, weight } - }; - FontDesc::new(desc.family.clone(), style) - } - - pub fn font_metrics(&self) -> font::Metrics { - self.rasterizer - .metrics(self.font_key, self.font_size) - .expect("metrics load since font is loaded at glyph cache creation") - } - - pub fn get<'a, L>(&'a mut self, glyph_key: GlyphKey, loader: &mut L) -> &'a Glyph - where - L: LoadGlyph, - { - let glyph_offset = self.glyph_offset; - let rasterizer = &mut self.rasterizer; - let metrics = &self.metrics; - self.cache.entry(glyph_key).or_insert_with(|| { - let mut rasterized = - rasterizer.get_glyph(glyph_key).unwrap_or_else(|_| Default::default()); - - rasterized.left += i32::from(glyph_offset.x); - rasterized.top += i32::from(glyph_offset.y); - rasterized.top -= metrics.descent as i32; - - loader.load_glyph(&rasterized) - }) - } - - pub fn update_font_size<L: LoadGlyph>( - &mut self, - font: &config::Font, - size: font::Size, - dpr: f64, - loader: &mut L, - ) -> Result<(), font::Error> { - // Clear currently cached data in both GL and the registry - loader.clear(); - self.cache = HashMap::default(); - self.cursor_cache = HashMap::default(); - - // Update dpi scaling - self.rasterizer.update_dpr(dpr as f32); - - // Recompute font keys - let font = font.to_owned().with_size(size); - let (regular, bold, italic) = Self::compute_font_keys(&font, &mut self.rasterizer)?; - - self.rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; - let metrics = self.rasterizer.metrics(regular, size)?; - - info!("Font size changed to {:?} with DPR of {}", font.size, dpr); - - self.font_size = font.size; - self.font_key = regular; - self.bold_key = bold; - self.italic_key = italic; - self.metrics = metrics; - - self.load_glyphs_for_font(regular, loader); - self.load_glyphs_for_font(bold, loader); - self.load_glyphs_for_font(italic, loader); - - Ok(()) - } - - // Calculate font metrics without access to a glyph cache - // - // This should only be used *before* OpenGL is initialized and the glyph cache can be filled. - pub fn static_metrics(config: &Config, dpr: f32) -> Result<font::Metrics, font::Error> { - let font = config.font().clone(); - - let mut rasterizer = font::Rasterizer::new(dpr, config.use_thin_strokes())?; - let regular_desc = - GlyphCache::make_desc(&font.normal(), font::Slant::Normal, font::Weight::Normal); - let regular = rasterizer.load_font(&regular_desc, font.size())?; - rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; - - rasterizer.metrics(regular, font.size()) - } -} - -#[derive(Debug)] -#[repr(C)] -struct InstanceData { - // coords - col: f32, - row: f32, - // glyph offset - left: f32, - top: f32, - // glyph scale - width: f32, - height: f32, - // uv offset - uv_left: f32, - uv_bot: f32, - // uv scale - uv_width: f32, - uv_height: f32, - // color - r: f32, - g: f32, - b: f32, - // background color - bg_r: f32, - bg_g: f32, - bg_b: f32, - bg_a: f32, -} - -#[derive(Debug)] -pub struct QuadRenderer { - program: TextShaderProgram, - rect_program: RectShaderProgram, - vao: GLuint, - ebo: GLuint, - vbo_instance: GLuint, - rect_vao: GLuint, - rect_vbo: GLuint, - atlas: Vec<Atlas>, - current_atlas: usize, - active_tex: GLuint, - batch: Batch, - rx: mpsc::Receiver<Msg>, -} - -#[derive(Debug)] -pub struct RenderApi<'a> { - active_tex: &'a mut GLuint, - batch: &'a mut Batch, - atlas: &'a mut Vec<Atlas>, - current_atlas: &'a mut usize, - program: &'a mut TextShaderProgram, - config: &'a Config, -} - -#[derive(Debug)] -pub struct LoaderApi<'a> { - active_tex: &'a mut GLuint, - atlas: &'a mut Vec<Atlas>, - current_atlas: &'a mut usize, -} - -#[derive(Debug)] -pub struct PackedVertex { - x: f32, - y: f32, -} - -#[derive(Debug, Default)] -pub struct Batch { - tex: GLuint, - instances: Vec<InstanceData>, -} - -impl Batch { - #[inline] - pub fn new() -> Batch { - Batch { tex: 0, instances: Vec::with_capacity(BATCH_MAX) } - } - - pub fn add_item(&mut self, cell: &RenderableCell, glyph: &Glyph) { - if self.is_empty() { - self.tex = glyph.tex_id; - } - - self.instances.push(InstanceData { - col: cell.column.0 as f32, - row: cell.line.0 as f32, - - top: glyph.top, - left: glyph.left, - width: glyph.width, - height: glyph.height, - - uv_bot: glyph.uv_bot, - uv_left: glyph.uv_left, - uv_width: glyph.uv_width, - uv_height: glyph.uv_height, - - r: f32::from(cell.fg.r), - g: f32::from(cell.fg.g), - b: f32::from(cell.fg.b), - - bg_r: f32::from(cell.bg.r), - bg_g: f32::from(cell.bg.g), - bg_b: f32::from(cell.bg.b), - bg_a: cell.bg_alpha, - }); - } - - #[inline] - pub fn full(&self) -> bool { - self.capacity() == self.len() - } - - #[inline] - pub fn len(&self) -> usize { - self.instances.len() - } - - #[inline] - pub fn capacity(&self) -> usize { - BATCH_MAX - } - - #[inline] - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - #[inline] - pub fn size(&self) -> usize { - self.len() * size_of::<InstanceData>() - } - - pub fn clear(&mut self) { - self.tex = 0; - self.instances.clear(); - } -} - -/// Maximum items to be drawn in a batch. -const BATCH_MAX: usize = 0x1_0000; -const ATLAS_SIZE: i32 = 1024; - -impl QuadRenderer { - pub fn new() -> Result<QuadRenderer, Error> { - let program = TextShaderProgram::new()?; - let rect_program = RectShaderProgram::new()?; - - let mut vao: GLuint = 0; - let mut ebo: GLuint = 0; - - let mut vbo_instance: GLuint = 0; - - let mut rect_vao: GLuint = 0; - let mut rect_vbo: GLuint = 0; - let mut rect_ebo: GLuint = 0; - - unsafe { - gl::Enable(gl::BLEND); - gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR); - gl::Enable(gl::MULTISAMPLE); - - // Disable depth mask, as the renderer never uses depth tests - gl::DepthMask(gl::FALSE); - - gl::GenVertexArrays(1, &mut vao); - gl::GenBuffers(1, &mut ebo); - gl::GenBuffers(1, &mut vbo_instance); - gl::BindVertexArray(vao); - - // --------------------- - // Set up element buffer - // --------------------- - let indices: [u32; 6] = [0, 1, 3, 1, 2, 3]; - - gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo); - gl::BufferData( - gl::ELEMENT_ARRAY_BUFFER, - (6 * size_of::<u32>()) as isize, - indices.as_ptr() as *const _, - gl::STATIC_DRAW, - ); - - // ---------------------------- - // Setup vertex instance buffer - // ---------------------------- - gl::BindBuffer(gl::ARRAY_BUFFER, vbo_instance); - gl::BufferData( - gl::ARRAY_BUFFER, - (BATCH_MAX * size_of::<InstanceData>()) as isize, - ptr::null(), - gl::STREAM_DRAW, - ); - // coords - gl::VertexAttribPointer( - 0, - 2, - gl::FLOAT, - gl::FALSE, - size_of::<InstanceData>() as i32, - ptr::null(), - ); - gl::EnableVertexAttribArray(0); - gl::VertexAttribDivisor(0, 1); - // glyphoffset - gl::VertexAttribPointer( - 1, - 4, - gl::FLOAT, - gl::FALSE, - size_of::<InstanceData>() as i32, - (2 * size_of::<f32>()) as *const _, - ); - gl::EnableVertexAttribArray(1); - gl::VertexAttribDivisor(1, 1); - // uv - gl::VertexAttribPointer( - 2, - 4, - gl::FLOAT, - gl::FALSE, - size_of::<InstanceData>() as i32, - (6 * size_of::<f32>()) as *const _, - ); - gl::EnableVertexAttribArray(2); - gl::VertexAttribDivisor(2, 1); - // color - gl::VertexAttribPointer( - 3, - 3, - gl::FLOAT, - gl::FALSE, - size_of::<InstanceData>() as i32, - (10 * size_of::<f32>()) as *const _, - ); - gl::EnableVertexAttribArray(3); - gl::VertexAttribDivisor(3, 1); - // color - gl::VertexAttribPointer( - 4, - 4, - gl::FLOAT, - gl::FALSE, - size_of::<InstanceData>() as i32, - (13 * size_of::<f32>()) as *const _, - ); - gl::EnableVertexAttribArray(4); - gl::VertexAttribDivisor(4, 1); - - // Rectangle setup - gl::GenVertexArrays(1, &mut rect_vao); - gl::GenBuffers(1, &mut rect_vbo); - gl::GenBuffers(1, &mut rect_ebo); - gl::BindVertexArray(rect_vao); - let indices: [i32; 6] = [0, 1, 3, 1, 2, 3]; - gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, rect_ebo); - gl::BufferData( - gl::ELEMENT_ARRAY_BUFFER, - (size_of::<i32>() * indices.len()) as _, - indices.as_ptr() as *const _, - gl::STATIC_DRAW, - ); - - // Cleanup - gl::BindVertexArray(0); - gl::BindBuffer(gl::ARRAY_BUFFER, 0); - gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0); - } - - let (msg_tx, msg_rx) = mpsc::channel(); - - if cfg!(feature = "live-shader-reload") { - ::std::thread::spawn(move || { - let (tx, rx) = ::std::sync::mpsc::channel(); - // The Duration argument is a debouncing period. - let mut watcher = - watcher(tx, Duration::from_millis(10)).expect("create file watcher"); - watcher - .watch(TEXT_SHADER_F_PATH, RecursiveMode::NonRecursive) - .expect("watch fragment shader"); - watcher - .watch(TEXT_SHADER_V_PATH, RecursiveMode::NonRecursive) - .expect("watch vertex shader"); - - loop { - let event = rx.recv().expect("watcher event"); - - match event { - DebouncedEvent::Rename(..) => continue, - DebouncedEvent::Create(_) - | DebouncedEvent::Write(_) - | DebouncedEvent::Chmod(_) => { - msg_tx.send(Msg::ShaderReload).expect("msg send ok"); - }, - _ => {}, - } - } - }); - } - - let mut renderer = QuadRenderer { - program, - rect_program, - vao, - ebo, - vbo_instance, - rect_vao, - rect_vbo, - atlas: Vec::new(), - current_atlas: 0, - active_tex: 0, - batch: Batch::new(), - rx: msg_rx, - }; - - let atlas = Atlas::new(ATLAS_SIZE); - renderer.atlas.push(atlas); - - Ok(renderer) - } - - // Draw all rectangles simultaneously to prevent excessive program swaps - pub fn draw_rects( - &mut self, - config: &Config, - props: &term::SizeInfo, - visual_bell_intensity: f64, - cell_line_rects: Rects, - ) { - // Swap to rectangle rendering program - unsafe { - // Swap program - gl::UseProgram(self.rect_program.id); - - // Remove padding from viewport - gl::Viewport(0, 0, props.width as i32, props.height as i32); - - // Change blending strategy - gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); - - // Setup data and buffers - gl::BindVertexArray(self.rect_vao); - gl::BindBuffer(gl::ARRAY_BUFFER, self.rect_vbo); - - // Position - gl::VertexAttribPointer( - 0, - 2, - gl::FLOAT, - gl::FALSE, - (size_of::<f32>() * 2) as _, - ptr::null(), - ); - gl::EnableVertexAttribArray(0); - } - - // Draw visual bell - let color = config.visual_bell().color(); - let rect = Rect::new(0., 0., props.width, props.height); - self.render_rect(&rect, color, visual_bell_intensity as f32, props); - - // Draw underlines and strikeouts - for cell_line_rect in cell_line_rects.rects() { - self.render_rect(&cell_line_rect.0, cell_line_rect.1, 255., props); - } - - // Deactivate rectangle program again - unsafe { - // Reset blending strategy - gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR); - - // Reset data and buffers - gl::BindBuffer(gl::ARRAY_BUFFER, 0); - gl::BindVertexArray(0); - - let padding_x = props.padding_x as i32; - let padding_y = props.padding_y as i32; - let width = props.width as i32; - let height = props.height as i32; - gl::Viewport(padding_x, padding_y, width - 2 * padding_x, height - 2 * padding_y); - - // Disable program - gl::UseProgram(0); - } - } - - pub fn with_api<F, T>(&mut self, config: &Config, props: &term::SizeInfo, func: F) -> T - where - F: FnOnce(RenderApi<'_>) -> T, - { - // Flush message queue - if let Ok(Msg::ShaderReload) = self.rx.try_recv() { - self.reload_shaders(props); - } - while let Ok(_) = self.rx.try_recv() {} - - unsafe { - gl::UseProgram(self.program.id); - self.program.set_term_uniforms(props); - - gl::BindVertexArray(self.vao); - gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo); - gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo_instance); - gl::ActiveTexture(gl::TEXTURE0); - } - - let res = func(RenderApi { - active_tex: &mut self.active_tex, - batch: &mut self.batch, - atlas: &mut self.atlas, - current_atlas: &mut self.current_atlas, - program: &mut self.program, - config, - }); - - unsafe { - gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0); - gl::BindBuffer(gl::ARRAY_BUFFER, 0); - gl::BindVertexArray(0); - - gl::UseProgram(0); - } - - res - } - - pub fn with_loader<F, T>(&mut self, func: F) -> T - where - F: FnOnce(LoaderApi<'_>) -> T, - { - unsafe { - gl::ActiveTexture(gl::TEXTURE0); - } - - func(LoaderApi { - active_tex: &mut self.active_tex, - atlas: &mut self.atlas, - current_atlas: &mut self.current_atlas, - }) - } - - pub fn reload_shaders(&mut self, props: &term::SizeInfo) { - info!("Reloading shaders..."); - let result = (TextShaderProgram::new(), RectShaderProgram::new()); - let (program, rect_program) = match result { - (Ok(program), Ok(rect_program)) => { - unsafe { - gl::UseProgram(program.id); - program.update_projection( - props.width, - props.height, - props.padding_x, - props.padding_y, - ); - gl::UseProgram(0); - } - - info!("... successfully reloaded shaders"); - (program, rect_program) - }, - (Err(err), _) | (_, Err(err)) => { - error!("{}", err); - return; - }, - }; - - self.active_tex = 0; - self.program = program; - self.rect_program = rect_program; - } - - pub fn resize(&mut self, size: PhysicalSize, padding_x: f32, padding_y: f32) { - let (width, height): (u32, u32) = size.into(); - - // viewport - unsafe { - let width = width as i32; - let height = height as i32; - let padding_x = padding_x as i32; - let padding_y = padding_y as i32; - gl::Viewport(padding_x, padding_y, width - 2 * padding_x, height - 2 * padding_y); - - // update projection - gl::UseProgram(self.program.id); - self.program.update_projection( - width as f32, - height as f32, - padding_x as f32, - padding_y as f32, - ); - gl::UseProgram(0); - } - } - - // Render a rectangle - // - // This requires the rectangle program to be activated - fn render_rect(&mut self, rect: &Rect<f32>, color: Rgb, alpha: f32, size: &term::SizeInfo) { - // Do nothing when alpha is fully transparent - if alpha == 0. { - return; - } - - // Calculate rectangle position - let center_x = size.width / 2.; - let center_y = size.height / 2.; - let x = (rect.x - center_x) / center_x; - let y = -(rect.y - center_y) / center_y; - let width = rect.width / center_x; - let height = rect.height / center_y; - - unsafe { - // Setup vertices - let vertices: [f32; 8] = [x + width, y, x + width, y - height, x, y - height, x, y]; - - // Load vertex data into array buffer - gl::BufferData( - gl::ARRAY_BUFFER, - (size_of::<f32>() * vertices.len()) as _, - vertices.as_ptr() as *const _, - gl::STATIC_DRAW, - ); - - // Color - self.rect_program.set_color(color, alpha); - - // Draw the rectangle - gl::DrawElements(gl::TRIANGLES, 6, gl::UNSIGNED_INT, ptr::null()); - } - } -} - -impl<'a> RenderApi<'a> { - pub fn clear(&self, color: Rgb) { - let alpha = self.config.background_opacity().get(); - unsafe { - gl::ClearColor( - (f32::from(color.r) / 255.0).min(1.0) * alpha, - (f32::from(color.g) / 255.0).min(1.0) * alpha, - (f32::from(color.b) / 255.0).min(1.0) * alpha, - alpha, - ); - gl::Clear(gl::COLOR_BUFFER_BIT); - } - } - - fn render_batch(&mut self) { - unsafe { - gl::BufferSubData( - gl::ARRAY_BUFFER, - 0, - self.batch.size() as isize, - self.batch.instances.as_ptr() as *const _, - ); - } - - // Bind texture if necessary - if *self.active_tex != self.batch.tex { - unsafe { - gl::BindTexture(gl::TEXTURE_2D, self.batch.tex); - } - *self.active_tex = self.batch.tex; - } - - unsafe { - self.program.set_background_pass(true); - gl::DrawElementsInstanced( - gl::TRIANGLES, - 6, - gl::UNSIGNED_INT, - ptr::null(), - self.batch.len() as GLsizei, - ); - self.program.set_background_pass(false); - gl::DrawElementsInstanced( - gl::TRIANGLES, - 6, - gl::UNSIGNED_INT, - ptr::null(), - self.batch.len() as GLsizei, - ); - } - - self.batch.clear(); - } - - /// Render a string in a variable location. Used for printing the render timer, warnings and - /// errors. - pub fn render_string( - &mut self, - string: &str, - line: Line, - glyph_cache: &mut GlyphCache, - color: Option<Rgb>, - ) { - let bg_alpha = color.map(|_| 1.0).unwrap_or(0.0); - let col = Column(0); - - let cells = string - .chars() - .enumerate() - .map(|(i, c)| RenderableCell { - line, - column: col + i, - inner: RenderableCellContent::Chars({ - let mut chars = [' '; cell::MAX_ZEROWIDTH_CHARS + 1]; - chars[0] = c; - chars - }), - bg: color.unwrap_or(Rgb { r: 0, g: 0, b: 0 }), - fg: Rgb { r: 0, g: 0, b: 0 }, - flags: cell::Flags::empty(), - bg_alpha, - }) - .collect::<Vec<_>>(); - - for cell in cells { - self.render_cell(cell, glyph_cache); - } - } - - #[inline] - fn add_render_item(&mut self, cell: &RenderableCell, glyph: &Glyph) { - // Flush batch if tex changing - if !self.batch.is_empty() && self.batch.tex != glyph.tex_id { - self.render_batch(); - } - - self.batch.add_item(cell, glyph); - - // Render batch and clear if it's full - if self.batch.full() { - self.render_batch(); - } - } - - pub fn render_cell(&mut self, cell: RenderableCell, glyph_cache: &mut GlyphCache) { - let chars = match cell.inner { - RenderableCellContent::Cursor((cursor_style, ref raw)) => { - // Raw cell pixel buffers like cursors don't need to go through font lookup - let glyph = glyph_cache - .cursor_cache - .entry(cursor_style) - .or_insert_with(|| self.load_glyph(raw)); - self.add_render_item(&cell, &glyph); - return; - }, - RenderableCellContent::Chars(chars) => chars, - }; - - // Get font key for cell - // FIXME this is super inefficient. - let font_key = if cell.flags.contains(cell::Flags::BOLD) { - glyph_cache.bold_key - } else if cell.flags.contains(cell::Flags::ITALIC) { - glyph_cache.italic_key - } else { - glyph_cache.font_key - }; - - // Don't render text of HIDDEN cells - let mut chars = if cell.flags.contains(cell::Flags::HIDDEN) { - [' '; cell::MAX_ZEROWIDTH_CHARS + 1] - } else { - chars - }; - - // Render tabs as spaces in case the font doesn't support it - if chars[0] == '\t' { - chars[0] = ' '; - } - - let mut glyph_key = GlyphKey { font_key, size: glyph_cache.font_size, c: chars[0] }; - - // Add cell to batch - let glyph = glyph_cache.get(glyph_key, self); - self.add_render_item(&cell, glyph); - - // Render zero-width characters - for c in (&chars[1..]).iter().filter(|c| **c != ' ') { - glyph_key.c = *c; - let mut glyph = *glyph_cache.get(glyph_key, self); - - // The metrics of zero-width characters are based on rendering - // the character after the current cell, with the anchor at the - // right side of the preceding character. Since we render the - // zero-width characters inside the preceding character, the - // anchor has been moved to the right by one cell. - glyph.left += glyph_cache.metrics.average_advance as f32; - - self.add_render_item(&cell, &glyph); - } - } -} - -/// Load a glyph into a texture atlas -/// -/// If the current atlas is full, a new one will be created. -#[inline] -fn load_glyph( - active_tex: &mut GLuint, - atlas: &mut Vec<Atlas>, - current_atlas: &mut usize, - rasterized: &RasterizedGlyph, -) -> Glyph { - // At least one atlas is guaranteed to be in the `self.atlas` list; thus - // the unwrap. - match atlas[*current_atlas].insert(rasterized, active_tex) { - Ok(glyph) => glyph, - Err(AtlasInsertError::Full) => { - *current_atlas += 1; - if *current_atlas == atlas.len() { - let new = Atlas::new(ATLAS_SIZE); - *active_tex = 0; // Atlas::new binds a texture. Ugh this is sloppy. - atlas.push(new); - } - load_glyph(active_tex, atlas, current_atlas, rasterized) - }, - Err(AtlasInsertError::GlyphTooLarge) => Glyph { - tex_id: atlas[*current_atlas].id, - top: 0.0, - left: 0.0, - width: 0.0, - height: 0.0, - uv_bot: 0.0, - uv_left: 0.0, - uv_width: 0.0, - uv_height: 0.0, - }, - } -} - -#[inline] -fn clear_atlas(atlas: &mut Vec<Atlas>, current_atlas: &mut usize) { - for atlas in atlas.iter_mut() { - atlas.clear(); - } - *current_atlas = 0; -} - -impl<'a> LoadGlyph for LoaderApi<'a> { - fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph { - load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized) - } - - fn clear(&mut self) { - clear_atlas(self.atlas, self.current_atlas) - } -} - -impl<'a> LoadGlyph for RenderApi<'a> { - fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph { - load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized) - } - - fn clear(&mut self) { - clear_atlas(self.atlas, self.current_atlas) - } -} - -impl<'a> Drop for RenderApi<'a> { - fn drop(&mut self) { - if !self.batch.is_empty() { - self.render_batch(); - } - } -} - -impl TextShaderProgram { - pub fn new() -> Result<TextShaderProgram, ShaderCreationError> { - let (vertex_src, fragment_src) = if cfg!(feature = "live-shader-reload") { - (None, None) - } else { - (Some(TEXT_SHADER_V), Some(TEXT_SHADER_F)) - }; - let vertex_shader = create_shader(TEXT_SHADER_V_PATH, gl::VERTEX_SHADER, vertex_src)?; - let fragment_shader = create_shader(TEXT_SHADER_F_PATH, gl::FRAGMENT_SHADER, fragment_src)?; - let program = create_program(vertex_shader, fragment_shader)?; - - unsafe { - gl::DeleteShader(fragment_shader); - gl::DeleteShader(vertex_shader); - gl::UseProgram(program); - } - - macro_rules! cptr { - ($thing:expr) => { - $thing.as_ptr() as *const _ - }; - } - - macro_rules! assert_uniform_valid { - ($uniform:expr) => { - assert!($uniform != gl::INVALID_VALUE as i32); - assert!($uniform != gl::INVALID_OPERATION as i32); - }; - ( $( $uniform:expr ),* ) => { - $( assert_uniform_valid!($uniform); )* - }; - } - - // get uniform locations - let (projection, cell_dim, background) = unsafe { - ( - gl::GetUniformLocation(program, cptr!(b"projection\0")), - gl::GetUniformLocation(program, cptr!(b"cellDim\0")), - gl::GetUniformLocation(program, cptr!(b"backgroundPass\0")), - ) - }; - - assert_uniform_valid!(projection, cell_dim, background); - - let shader = TextShaderProgram { - id: program, - u_projection: projection, - u_cell_dim: cell_dim, - u_background: background, - }; - - unsafe { - gl::UseProgram(0); - } - - Ok(shader) - } - - fn update_projection(&self, width: f32, height: f32, padding_x: f32, padding_y: f32) { - // Bounds check - if (width as u32) < (2 * padding_x as u32) || (height as u32) < (2 * padding_y as u32) { - return; - } - - // Compute scale and offset factors, from pixel to ndc space. Y is inverted - // [0, width - 2 * padding_x] to [-1, 1] - // [height - 2 * padding_y, 0] to [-1, 1] - let scale_x = 2. / (width - 2. * padding_x); - let scale_y = -2. / (height - 2. * padding_y); - let offset_x = -1.; - let offset_y = 1.; - - info!("Width: {}, Height: {}", width, height); - - unsafe { - gl::Uniform4f(self.u_projection, offset_x, offset_y, scale_x, scale_y); - } - } - - fn set_term_uniforms(&self, props: &term::SizeInfo) { - unsafe { - gl::Uniform2f(self.u_cell_dim, props.cell_width, props.cell_height); - } - } - - fn set_background_pass(&self, background_pass: bool) { - let value = if background_pass { 1 } else { 0 }; - - unsafe { - gl::Uniform1i(self.u_background, value); - } - } -} - -impl Drop for TextShaderProgram { - fn drop(&mut self) { - unsafe { - gl::DeleteProgram(self.id); - } - } -} - -impl RectShaderProgram { - pub fn new() -> Result<Self, ShaderCreationError> { - let (vertex_src, fragment_src) = if cfg!(feature = "live-shader-reload") { - (None, None) - } else { - (Some(RECT_SHADER_V), Some(RECT_SHADER_F)) - }; - let vertex_shader = create_shader(RECT_SHADER_V_PATH, gl::VERTEX_SHADER, vertex_src)?; - let fragment_shader = create_shader(RECT_SHADER_F_PATH, gl::FRAGMENT_SHADER, fragment_src)?; - let program = create_program(vertex_shader, fragment_shader)?; - - unsafe { - gl::DeleteShader(fragment_shader); - gl::DeleteShader(vertex_shader); - gl::UseProgram(program); - } - - // get uniform locations - let u_color = unsafe { gl::GetUniformLocation(program, b"color\0".as_ptr() as *const _) }; - - let shader = RectShaderProgram { id: program, u_color };