From 0e418bc2f761617455cc58aaabc375055dfe4284 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Thu, 3 Nov 2022 19:37:54 +0300 Subject: [PATCH] Update glutin to 0.30.0 The glutin 0.30.0 update decouples glutin from winit which provides us with basis for a multithreaded renderer. This also improves robustness of our configuration picking, context creation, and surface handling. As an example we're now able to start on systems without a vsync, we don't try to build lots of contexts to check if some config works, and so on. That also brings us possibility to handle context losses, but that's a future work. Fixes #1268. --- .builds/freebsd.yml | 4 +- .builds/linux.yml | 4 +- .github/workflows/ci.yml | 2 +- CHANGELOG.md | 6 + CONTRIBUTING.md | 2 +- Cargo.lock | 93 +++--- alacritty/Cargo.toml | 19 +- alacritty/src/config/bindings.rs | 8 +- alacritty/src/config/monitor.rs | 2 +- alacritty/src/config/ui_config.rs | 2 +- alacritty/src/config/window.rs | 10 +- alacritty/src/display/damage.rs | 24 +- alacritty/src/display/hint.rs | 2 +- alacritty/src/display/mod.rs | 449 ++++++++++++++++------------- alacritty/src/display/window.rs | 310 ++++++-------------- alacritty/src/event.rs | 132 +++++---- alacritty/src/input.rs | 34 +-- alacritty/src/ipc.rs | 4 +- alacritty/src/logging.rs | 2 +- alacritty/src/main.rs | 10 +- alacritty/src/renderer/mod.rs | 21 +- alacritty/src/renderer/platform.rs | 116 ++++++++ alacritty/src/scheduler.rs | 6 +- alacritty/src/window_context.rs | 132 +++++++-- alacritty_config/Cargo.toml | 2 +- alacritty_config_derive/Cargo.toml | 2 +- alacritty_terminal/Cargo.toml | 2 +- 27 files changed, 776 insertions(+), 624 deletions(-) create mode 100644 alacritty/src/renderer/platform.rs diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index 3cd2a60d..4aa4113b 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -24,8 +24,8 @@ tasks: cargo test - oldstable: | cd alacritty - rustup toolchain install --profile minimal 1.57.0 - rustup default 1.57.0 + rustup toolchain install --profile minimal 1.60.0 + rustup default 1.60.0 cargo test - clippy: | cd alacritty diff --git a/.builds/linux.yml b/.builds/linux.yml index a61afd3a..4e6e0a0b 100644 --- a/.builds/linux.yml +++ b/.builds/linux.yml @@ -27,8 +27,8 @@ tasks: cargo +nightly fmt -- --check - oldstable: | cd alacritty - rustup toolchain install --profile minimal 1.57.0 - rustup default 1.57.0 + rustup toolchain install --profile minimal 1.60.0 + rustup default 1.60.0 cargo test - clippy: | cd alacritty diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dea6dd4a..652e65aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: run: cargo test - name: Oldstable run: | - rustup default 1.57.0 + rustup default 1.60.0 cargo test - name: Clippy run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bc9fe55..7a3cfe0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Packaging +- Minimum Rust version has been bumped to 1.60.0 + +## 0.11.0 + +### Packaging + - Minimum Rust version has been bumped to 1.57.0 - Renamed `io.alacritty.Alacritty.appdata.xml` to `org.alacritty.Alacritty.appdata.xml` - Renamed `io.alacritty` to `org.alacritty` for `Alacritty.app` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bd2dbd77..65879dbd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,7 +42,7 @@ and [easy](https://github.com/alacritty/alacritty/issues?q=is%3Aopen+is%3Aissue+label%3A%22D+-+easy%22) issues. -Please note that the minimum supported version of Alacritty is Rust 1.57.0. All patches are expected +Please note that the minimum supported version of Alacritty is Rust 1.60.0. All patches are expected to work with the minimum supported version. Since `alacritty_terminal`'s version always tracks the next release, make sure that the version is diff --git a/Cargo.lock b/Cargo.lock index a545c28d..fa2eac1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,6 +40,7 @@ dependencies = [ "unicode-width", "wayland-client", "windows-sys", + "winit", "x11-dl", "xdg", ] @@ -184,6 +185,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "cgl" version = "0.3.2" @@ -660,55 +667,42 @@ dependencies = [ [[package]] name = "glutin" -version = "0.29.1" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444c9ad294fdcaf20ccf6726b78f380b5450275540c9b68ab62f49726ad1c713" +checksum = "34acbf502536f1d125f0fc09b6ad8824e93e6da7b99e86d3383e6b8310ba3554" dependencies = [ + "bitflags", + "cfg_aliases", "cgl", "cocoa", "core-foundation", "glutin_egl_sys", - "glutin_gles2_sys", "glutin_glx_sys", "glutin_wgl_sys", "libloading", - "log", "objc", "once_cell", - "osmesa-sys", - "parking_lot 0.12.1", "raw-window-handle 0.5.0", - "wayland-client", - "wayland-egl", - "winapi 0.3.9", - "winit", + "wayland-sys 0.30.0-beta.12", + "windows-sys", + "x11-dl", ] [[package]] name = "glutin_egl_sys" -version = "0.1.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68900f84b471f31ea1d1355567eb865a2cf446294f06cef8d653ed7bcf5f013d" +checksum = "2c3c95a2d7a54bab0c74759794efb5cd63470d4504cbe85ed4114dc82c98bdc1" dependencies = [ "gl_generator", - "winapi 0.3.9", -] - -[[package]] -name = "glutin_gles2_sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094e708b730a7c8a1954f4f8a31880af00eb8a1c5b5bf85d28a0a3c6d69103" -dependencies = [ - "gl_generator", - "objc", + "windows-sys", ] [[package]] name = "glutin_glx_sys" -version = "0.1.8" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93d0575865098580c5b3a423188cd959419912ea60b1e48e8b3b526f6d02468" +checksum = "947c4850c58211c9627969c2b4e2674764b81ae5b47bab2c9a477d7942f96e0f" dependencies = [ "gl_generator", "x11-dl", @@ -716,9 +710,9 @@ dependencies = [ [[package]] name = "glutin_wgl_sys" -version = "0.1.5" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5951a1569dbab865c6f2a863efafff193a93caf05538d193e9e3816d21696" +checksum = "20c33975a6c9d49d72c8f032a60079bf8df536954fbf9e4cee90396ace815c57" dependencies = [ "gl_generator", ] @@ -1205,15 +1199,6 @@ version = "6.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" -[[package]] -name = "osmesa-sys" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b" -dependencies = [ - "shared_library", -] - [[package]] name = "parking_lot" version = "0.11.2" @@ -1528,16 +1513,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "shared_library" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" -dependencies = [ - "lazy_static", - "libc", -] - [[package]] name = "signal-hook" version = "0.3.14" @@ -1868,7 +1843,7 @@ dependencies = [ "scoped-tls", "wayland-commons", "wayland-scanner", - "wayland-sys", + "wayland-sys 0.29.5", ] [[package]] @@ -1880,7 +1855,7 @@ dependencies = [ "nix", "once_cell", "smallvec", - "wayland-sys", + "wayland-sys 0.29.5", ] [[package]] @@ -1894,16 +1869,6 @@ dependencies = [ "xcursor", ] -[[package]] -name = "wayland-egl" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402de949f81a012926d821a2d659f930694257e76dd92b6e0042ceb27be4107d" -dependencies = [ - "wayland-client", - "wayland-sys", -] - [[package]] name = "wayland-protocols" version = "0.29.5" @@ -1938,6 +1903,18 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "wayland-sys" +version = "0.30.0-beta.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1117fe4570fe063122ba2b1b1e39e56fb1a73921d395f9288af06af0dd1c7f55" +dependencies = [ + "dlib", + "lazy_static", + "log", + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.60" diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml index 4ca77b11..c414e766 100644 --- a/alacritty/Cargo.toml +++ b/alacritty/Cargo.toml @@ -7,7 +7,7 @@ description = "A fast, cross-platform, OpenGL terminal emulator" readme = "README.md" homepage = "https://github.com/alacritty/alacritty" edition = "2021" -rust-version = "1.57.0" +rust-version = "1.60.0" [dependencies.alacritty_terminal] path = "../alacritty_terminal" @@ -29,11 +29,13 @@ fnv = "1" serde = { version = "1", features = ["derive"] } serde_yaml = "0.8" serde_json = "1" -glutin = { version = "0.29.1", default-features = false, features = ["serde"] } +glutin = { version = "0.30.0", default-features = false, features = ["egl", "wgl"] } +winit = { version = "0.27.4", default-features = false, features = ["serde"] } notify = "4" parking_lot = "0.12.0" crossfont = { version = "0.5.0", features = ["force_system_fontconfig"] } copypasta = { version = "0.8.1", default-features = false } +raw-window-handle = "0.5.0" libc = "0.2" unicode-width = "0.1" bitflags = "1" @@ -74,11 +76,18 @@ embed-resource = "1.7.2" [features] default = ["wayland", "x11"] -x11 = ["copypasta/x11", "glutin/x11", "x11-dl", "png"] +x11 = [ + "copypasta/x11", + "winit/x11", + "glutin/x11", + "glutin/glx", + "x11-dl", + "png"] wayland = [ "copypasta/wayland", "glutin/wayland", - "glutin/wayland-dlopen", - "glutin/wayland-csd-adwaita", + "winit/wayland", + "winit/wayland-dlopen", + "winit/wayland-csd-adwaita", "wayland-client"] nightly = [] diff --git a/alacritty/src/config/bindings.rs b/alacritty/src/config/bindings.rs index 3aae25ed..7b654b66 100644 --- a/alacritty/src/config/bindings.rs +++ b/alacritty/src/config/bindings.rs @@ -3,11 +3,11 @@ use std::fmt::{self, Debug, Display}; use bitflags::bitflags; -use glutin::event::VirtualKeyCode::*; -use glutin::event::{ModifiersState, MouseButton, VirtualKeyCode}; use serde::de::{self, Error as SerdeError, MapAccess, Unexpected, Visitor}; use serde::{Deserialize, Deserializer}; use serde_yaml::Value as SerdeValue; +use winit::event::VirtualKeyCode::*; +use winit::event::{ModifiersState, MouseButton, VirtualKeyCode}; use alacritty_config_derive::{ConfigDeserialize, SerdeReplace}; @@ -1187,7 +1187,7 @@ impl<'a> Deserialize<'a> for KeyBinding { } } -/// Newtype for implementing deserialize on glutin Mods. +/// Newtype for implementing deserialize on winit Mods. /// /// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the /// impl below. @@ -1242,7 +1242,7 @@ impl<'a> de::Deserialize<'a> for ModsWrapper { mod tests { use super::*; - use glutin::event::ModifiersState; + use winit::event::ModifiersState; type MockBinding = Binding; diff --git a/alacritty/src/config/monitor.rs b/alacritty/src/config/monitor.rs index 8981570c..f6d52e2f 100644 --- a/alacritty/src/config/monitor.rs +++ b/alacritty/src/config/monitor.rs @@ -2,9 +2,9 @@ use std::path::PathBuf; use std::sync::mpsc; use std::time::Duration; -use glutin::event_loop::EventLoopProxy; use log::{debug, error}; use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; +use winit::event_loop::EventLoopProxy; use alacritty_terminal::thread; diff --git a/alacritty/src/config/ui_config.rs b/alacritty/src/config/ui_config.rs index a332b737..f58097cf 100644 --- a/alacritty/src/config/ui_config.rs +++ b/alacritty/src/config/ui_config.rs @@ -3,11 +3,11 @@ use std::fmt::{self, Formatter}; use std::path::PathBuf; use std::rc::Rc; -use glutin::event::{ModifiersState, VirtualKeyCode}; use log::error; use serde::de::{Error as SerdeError, MapAccess, Visitor}; use serde::{self, Deserialize, Deserializer}; use unicode_width::UnicodeWidthChar; +use winit::event::{ModifiersState, VirtualKeyCode}; use alacritty_config_derive::{ConfigDeserialize, SerdeReplace}; use alacritty_terminal::config::{ diff --git a/alacritty/src/config/window.rs b/alacritty/src/config/window.rs index 5d63d60f..f1a74232 100644 --- a/alacritty/src/config/window.rs +++ b/alacritty/src/config/window.rs @@ -1,10 +1,10 @@ use std::fmt::{self, Formatter}; use std::os::raw::c_ulong; -use glutin::window::Fullscreen; use log::{error, warn}; use serde::de::{self, MapAccess, Visitor}; use serde::{Deserialize, Deserializer, Serialize}; +use winit::window::Fullscreen; use alacritty_config_derive::{ConfigDeserialize, SerdeReplace}; use alacritty_terminal::config::{Percentage, LOG_TARGET_CONFIG}; @@ -116,14 +116,14 @@ impl WindowConfig { pub fn decorations_theme_variant(&self) -> Option<&str> { self.gtk_theme_variant .as_ref() - .or_else(|| self.decorations_theme_variant.as_ref()) + .or(self.decorations_theme_variant.as_ref()) .map(|theme| theme.as_str()) } #[inline] - pub fn padding(&self, scale_factor: f64) -> (f32, f32) { - let padding_x = (f32::from(self.padding.x) * scale_factor as f32).floor(); - let padding_y = (f32::from(self.padding.y) * scale_factor as f32).floor(); + pub fn padding(&self, scale_factor: f32) -> (f32, f32) { + let padding_x = (f32::from(self.padding.x) * scale_factor).floor(); + let padding_y = (f32::from(self.padding.y) * scale_factor).floor(); (padding_x, padding_y) } diff --git a/alacritty/src/display/damage.rs b/alacritty/src/display/damage.rs index d9b271c5..380a2f63 100644 --- a/alacritty/src/display/damage.rs +++ b/alacritty/src/display/damage.rs @@ -1,7 +1,7 @@ use std::cmp; use std::iter::Peekable; -use glutin::Rect; +use glutin::surface::Rect; use alacritty_terminal::term::{LineDamageBounds, TermDamageIterator}; @@ -25,17 +25,23 @@ impl<'a> RenderDamageIterator<'a> { let x = size_info.padding_x() + line_damage.left as u32 * size_info.cell_width(); let y = y_top - (line_damage.line + 1) as u32 * size_info.cell_height(); let width = (line_damage.right - line_damage.left + 1) as u32 * size_info.cell_width(); - Rect { x, y, height: size_info.cell_height(), width } + Rect::new(x as i32, y as i32, width as i32, size_info.cell_height() as i32) } // Make sure to damage near cells to include wide chars. #[inline] fn overdamage(&self, mut rect: Rect) -> Rect { let size_info = &self.size_info; - rect.x = rect.x.saturating_sub(size_info.cell_width()); - rect.width = cmp::min(size_info.width() - rect.x, rect.width + 2 * size_info.cell_width()); - rect.y = rect.y.saturating_sub(size_info.cell_height() / 2); - rect.height = cmp::min(size_info.height() - rect.y, rect.height + size_info.cell_height()); + rect.x = rect.x.saturating_sub(size_info.cell_width() as i32); + rect.width = cmp::min( + size_info.width() as i32 - rect.x, + rect.width + 2 * size_info.cell_width() as i32, + ); + rect.y = rect.y.saturating_sub(size_info.cell_height() as i32 / 2); + rect.height = cmp::min( + size_info.height() as i32 - rect.y, + rect.height + size_info.cell_height() as i32, + ); rect } @@ -63,7 +69,7 @@ impl<'a> Iterator for RenderDamageIterator<'a> { } } -/// Check if two given [`glutin::Rect`] overlap. +/// Check if two given [`glutin::surface::Rect`] overlap. fn rects_overlap(lhs: Rect, rhs: Rect) -> bool { !( // `lhs` is left of `rhs`. @@ -77,12 +83,12 @@ fn rects_overlap(lhs: Rect, rhs: Rect) -> bool { ) } -/// Merge two [`glutin::Rect`] by producing the smallest rectangle that contains both. +/// Merge two [`glutin::surface::Rect`] by producing the smallest rectangle that contains both. #[inline] fn merge_rects(lhs: Rect, rhs: Rect) -> Rect { let left_x = cmp::min(lhs.x, rhs.x); let right_x = cmp::max(lhs.x + lhs.width, rhs.x + rhs.width); let y_top = cmp::max(lhs.y + lhs.height, rhs.y + rhs.height); let y_bottom = cmp::min(lhs.y, rhs.y); - Rect { x: left_x, y: y_bottom, width: right_x - left_x, height: y_top - y_bottom } + Rect::new(left_x, y_bottom, right_x - left_x, y_top - y_bottom) } diff --git a/alacritty/src/display/hint.rs b/alacritty/src/display/hint.rs index 8d374bdb..202b8f97 100644 --- a/alacritty/src/display/hint.rs +++ b/alacritty/src/display/hint.rs @@ -2,7 +2,7 @@ use std::cmp::Reverse; use std::collections::HashSet; use std::iter; -use glutin::event::ModifiersState; +use winit::event::ModifiersState; use alacritty_terminal::grid::{BidirectionalIterator, Dimensions}; use alacritty_terminal::index::{Boundary, Column, Direction, Line, Point}; diff --git a/alacritty/src/display/mod.rs b/alacritty/src/display/mod.rs index c17d8aa7..ed1b1fc5 100644 --- a/alacritty/src/display/mod.rs +++ b/alacritty/src/display/mod.rs @@ -1,23 +1,24 @@ //! The display subsystem including window management, font rasterization, and //! GPU drawing. +use std::cmp; use std::fmt::{self, Formatter}; +use std::mem::{self, ManuallyDrop}; +use std::num::NonZeroU32; +use std::ops::{Deref, DerefMut}; #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] use std::sync::atomic::Ordering; -use std::{cmp, mem}; -use glutin::dpi::PhysicalSize; -use glutin::event::ModifiersState; -use glutin::event_loop::EventLoopWindowTarget; -#[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] -use glutin::platform::unix::EventLoopWindowTargetExtUnix; -use glutin::window::CursorIcon; -use glutin::Rect as DamageRect; -use log::{debug, info}; +use glutin::context::{NotCurrentContext, PossiblyCurrentContext}; +use glutin::prelude::*; +use glutin::surface::{Rect as DamageRect, Surface, SwapInterval, WindowSurface}; + +use log::{debug, info, warn}; use parking_lot::MutexGuard; use serde::{Deserialize, Serialize}; -#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] -use wayland_client::EventQueue; +use winit::dpi::PhysicalSize; +use winit::event::ModifiersState; +use winit::window::CursorIcon; use crossfont::{self, Rasterize, Rasterizer}; use unicode_width::UnicodeWidthChar; @@ -33,9 +34,9 @@ use alacritty_terminal::term::color::Rgb; use alacritty_terminal::term::{self, Term, TermDamage, TermMode, MIN_COLUMNS, MIN_SCREEN_LINES}; use crate::config::font::Font; +use crate::config::window::Dimensions; #[cfg(not(windows))] use crate::config::window::StartupMode; -use crate::config::window::{Dimensions, Identity}; use crate::config::UiConfig; use crate::display::bell::VisualBell; use crate::display::color::List; @@ -84,8 +85,8 @@ pub enum Error { /// Error in renderer. Render(renderer::Error), - /// Error during buffer swap. - Context(glutin::ContextError), + /// Error during context operations. + Context(glutin::error::Error), } impl std::error::Error for Error { @@ -128,8 +129,8 @@ impl From for Error { } } -impl From for Error { - fn from(val: glutin::ContextError) -> Self { +impl From for Error { + fn from(val: glutin::error::Error) -> Self { Error::Context(val) } } @@ -334,17 +335,17 @@ impl DisplayUpdate { /// The display wraps a window, font rasterizer, and GPU renderer. pub struct Display { - pub size_info: SizeInfo, pub window: Window, + pub size_info: SizeInfo, + /// Hint highlighted by the mouse. pub highlighted_hint: Option, /// Hint highlighted by the vi mode cursor. pub vi_highlighted_hint: Option, - #[cfg(not(any(target_os = "macos", windows)))] - pub is_x11: bool, + pub is_wayland: bool, /// UI cursor visibility for blinking. pub cursor_hidden: bool, @@ -369,161 +370,57 @@ pub struct Display { // Mouse point position when highlighting hints. hint_mouse_point: Option, - is_damage_supported: bool, + renderer: ManuallyDrop, + + surface: ManuallyDrop>, + + context: ManuallyDrop>, + debug_damage: bool, damage_rects: Vec, next_frame_damage_rects: Vec, - renderer: Renderer, glyph_cache: GlyphCache, meter: Meter, } -/// Input method state. -#[derive(Debug, Default)] -pub struct Ime { - /// Whether the IME is enabled. - enabled: bool, - - /// Current IME preedit. - preedit: Option, -} - -impl Ime { - pub fn new() -> Self { - Default::default() - } - - #[inline] - pub fn set_enabled(&mut self, is_enabled: bool) { - if is_enabled { - self.enabled = is_enabled - } else { - // Clear state when disabling IME. - *self = Default::default(); - } - } - - #[inline] - pub fn is_enabled(&self) -> bool { - self.enabled - } - - #[inline] - pub fn set_preedit(&mut self, preedit: Option) { - self.preedit = preedit; - } - - #[inline] - pub fn preedit(&self) -> Option<&Preedit> { - self.preedit.as_ref() - } -} - -#[derive(Debug, Default, PartialEq, Eq)] -pub struct Preedit { - /// The preedit text. - text: String, - - /// Byte offset for cursor start into the preedit text. - /// - /// `None` means that the cursor is invisible. - cursor_byte_offset: Option, - - /// The cursor offset from the end of the preedit in char width. - cursor_end_offset: Option, -} - -impl Preedit { - pub fn new(text: String, cursor_byte_offset: Option) -> Self { - let cursor_end_offset = if let Some(byte_offset) = cursor_byte_offset { - // Convert byte offset into char offset. - let cursor_end_offset = - text[byte_offset..].chars().fold(0, |acc, ch| acc + ch.width().unwrap_or(1)); - - Some(cursor_end_offset) - } else { - None - }; - - Self { text, cursor_byte_offset, cursor_end_offset } - } -} - -/// Pending renderer updates. -/// -/// All renderer updates are cached to be applied just before rendering, to avoid platform-specific -/// rendering issues. -#[derive(Debug, Default, Copy, Clone)] -pub struct RendererUpdate { - /// Should resize the window. - resize: bool, - - /// Clear font caches. - clear_font_cache: bool, -} - impl Display { - pub fn new( + pub fn new( + window: Window, + gl_context: NotCurrentContext, config: &UiConfig, - event_loop: &EventLoopWindowTarget, - identity: &Identity, - #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] - wayland_event_queue: Option<&EventQueue>, ) -> Result { - #[cfg(any(not(feature = "x11"), target_os = "macos", windows))] - let is_x11 = false; - #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] - let is_x11 = event_loop.is_x11(); + #[cfg(any(not(feature = "wayland"), target_os = "macos", windows))] + let is_wayland = false; + #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] + let is_wayland = window.wayland_surface().is_some(); - // Guess scale_factor based on first monitor. On Wayland the initial frame always renders at - // a scale factor of 1. - let estimated_scale_factor = if cfg!(any(target_os = "macos", windows)) || is_x11 { - event_loop.available_monitors().next().map_or(1., |m| m.scale_factor()) - } else { - 1. - }; + let scale_factor = window.scale_factor as f32; + let rasterizer = Rasterizer::new(scale_factor)?; - // Guess the target window dimensions. debug!("Loading \"{}\" font", &config.font.normal().family); - let font = &config.font; - let rasterizer = Rasterizer::new(estimated_scale_factor as f32)?; - let mut glyph_cache = GlyphCache::new(rasterizer, font)?; + let mut glyph_cache = GlyphCache::new(rasterizer, &config.font)?; + let metrics = glyph_cache.font_metrics(); let (cell_width, cell_height) = compute_cell_size(config, &metrics); - // Guess the target window size if the user has specified the number of lines/columns. - let dimensions = config.window.dimensions(); - let estimated_size = dimensions.map(|dimensions| { - window_size(config, dimensions, cell_width, cell_height, estimated_scale_factor) - }); + // Resize the window to account for the user configured size. + if let Some(dimensions) = config.window.dimensions() { + let size = window_size(config, dimensions, cell_width, cell_height, scale_factor); + window.set_inner_size(size); + } - debug!("Estimated scaling factor: {}", estimated_scale_factor); - debug!("Estimated window size: {:?}", estimated_size); - debug!("Estimated cell size: {} x {}", cell_width, cell_height); - - // Spawn the Alacritty window. - let window = Window::new( - event_loop, - config, - identity, - estimated_size, - #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] - wayland_event_queue, + // Create the GL surface to draw into. + let surface = renderer::platform::create_gl_surface( + &gl_context, + window.inner_size(), + window.raw_window_handle(), )?; + // Make the context current. + let context = gl_context.make_current(&surface)?; + // Create renderer. - let mut renderer = Renderer::new()?; - - let scale_factor = window.scale_factor; - info!("Display scale factor: {}", scale_factor); - - // If the scaling factor changed update the glyph cache and mark for resize. - let should_resize = (estimated_scale_factor - window.scale_factor).abs() > f64::EPSILON; - let (cell_width, cell_height) = if should_resize { - Self::update_font_size(&mut glyph_cache, scale_factor, config, font) - } else { - (cell_width, cell_height) - }; + let mut renderer = Renderer::new(&context)?; // Load font common glyphs to accelerate rendering. debug!("Filling glyph cache with common glyphs"); @@ -531,14 +428,7 @@ impl Display { glyph_cache.reset_glyph_cache(&mut api); }); - if let Some(dimensions) = dimensions.filter(|_| should_resize) { - // Resize the window again if the scale factor was not estimated correctly. - let size = - window_size(config, dimensions, cell_width, cell_height, window.scale_factor); - window.set_inner_size(size); - } - - let padding = config.window.padding(window.scale_factor); + let padding = config.window.padding(window.scale_factor as f32); let viewport_size = window.inner_size(); // Create new size with at least one column and row. @@ -549,7 +439,7 @@ impl Display { cell_height, padding.0, padding.1, - config.window.dynamic_padding && dimensions.is_none(), + config.window.dynamic_padding && config.window.dimensions().is_none(), ); info!("Cell size: {} x {}", cell_width, cell_height); @@ -570,8 +460,8 @@ impl Display { // On Wayland we can safely ignore this call, since the window isn't visible until you // actually draw something into it and commit those changes. #[cfg(not(any(target_os = "macos", windows)))] - if is_x11 { - window.swap_buffers(); + if !is_wayland { + surface.swap_buffers(&context).expect("failed to swap buffers."); renderer.finish(); } @@ -582,24 +472,35 @@ impl Display { match config.window.startup_mode { #[cfg(target_os = "macos")] StartupMode::SimpleFullscreen => window.set_simple_fullscreen(true), - #[cfg(not(target_os = "macos"))] - StartupMode::Maximized if is_x11 => window.set_maximized(true), + #[cfg(not(any(target_os = "macos", windows)))] + StartupMode::Maximized if !is_wayland => window.set_maximized(true), _ => (), } let hint_state = HintState::new(config.hints.alphabet()); - let is_damage_supported = window.swap_buffers_with_damage_supported(); + let debug_damage = config.debug.highlight_damage; - let (damage_rects, next_frame_damage_rects) = if is_damage_supported || debug_damage { + let (damage_rects, next_frame_damage_rects) = if is_wayland || debug_damage { let vec = Vec::with_capacity(size_info.screen_lines()); (vec.clone(), vec) } else { (Vec::new(), Vec::new()) }; + // We use vsync everywhere except wayland. + if !is_wayland { + if let Err(err) = + surface.set_swap_interval(&context, SwapInterval::Wait(NonZeroU32::new(1).unwrap())) + { + warn!("Error setting vsync: {:?}", err); + } + } + Ok(Self { window, - renderer, + context: ManuallyDrop::new(Replaceable::new(context)), + surface: ManuallyDrop::new(surface), + renderer: ManuallyDrop::new(renderer), glyph_cache, hint_state, meter: Meter::new(), @@ -607,14 +508,12 @@ impl Display { ime: Ime::new(), highlighted_hint: None, vi_highlighted_hint: None, - #[cfg(not(any(target_os = "macos", windows)))] - is_x11, + is_wayland, cursor_hidden: false, visual_bell: VisualBell::from(&config.bell), colors: List::from(&config.colors), pending_update: Default::default(), pending_renderer_update: Default::default(), - is_damage_supported, debug_damage, damage_rects, next_frame_damage_rects, @@ -622,6 +521,42 @@ impl Display { }) } + #[inline] + pub fn gl_context(&self) -> &PossiblyCurrentContext { + self.context.get() + } + + pub fn make_not_current(&mut self) { + if self.context.get().is_current() { + self.context.replace_with(|context| { + context + .make_not_current() + .expect("failed to disable context") + .treat_as_possibly_current() + }); + } + } + + pub fn make_current(&self) { + if !self.context.get().is_current() { + self.context.make_current(&self.surface).expect("failed to make context current") + } + } + + fn swap_buffers(&self) { + #[allow(clippy::single_match)] + match (self.surface.deref(), &self.context.get()) { + #[cfg(not(any(target_os = "macos", windows)))] + (Surface::Egl(surface), PossiblyCurrentContext::Egl(context)) + if self.is_wayland && !self.debug_damage => + { + surface.swap_buffers_with_damage(context, &self.damage_rects) + }, + (surface, context) => surface.swap_buffers(context), + } + .expect("failed to swap buffers."); + } + /// Update font size and cell dimensions. /// /// This will return a tuple of the cell width and height. @@ -690,7 +625,7 @@ impl Display { renderer_update.resize = true } - let padding = config.window.padding(self.window.scale_factor); + let padding = config.window.padding(self.window.scale_factor as f32); self.size_info = SizeInfo::new( width, @@ -731,13 +666,13 @@ impl Display { // Resize renderer. if renderer_update.resize { - let physical = - PhysicalSize::new(self.size_info.width() as _, self.size_info.height() as _); - self.window.resize(physical); + let width = NonZeroU32::new(self.size_info.width() as u32).unwrap(); + let height = NonZeroU32::new(self.size_info.height() as u32).unwrap(); + self.surface.resize(&self.context, width, height); } // Ensure we're modifying the correct OpenGL context. - self.window.make_current(); + self.make_current(); if renderer_update.clear_font_cache { self.reset_glyph_cache(); @@ -763,12 +698,8 @@ impl Display { /// Damage the entire window. fn fully_damage(&mut self) { - let screen_rect = DamageRect { - x: 0, - y: 0, - width: self.size_info.width() as u32, - height: self.size_info.height() as u32, - }; + let screen_rect = + DamageRect::new(0, 0, self.size_info.width() as i32, self.size_info.height() as i32); self.damage_rects.push(screen_rect); } @@ -847,7 +778,7 @@ impl Display { drop(terminal); // Make sure this window's OpenGL context is active. - self.window.make_current(); + self.make_current(); self.renderer.clear(background_color, config.window_opacity()); let mut lines = RenderLines::new(); @@ -1036,14 +967,10 @@ impl Display { self.request_frame(&self.window); // Clearing debug highlights from the previous frame requires full redraw. - if self.is_damage_supported && !self.debug_damage { - self.window.swap_buffers_with_damage(&self.damage_rects); - } else { - self.window.swap_buffers(); - } + self.swap_buffers(); #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] - if self.is_x11 { + if !self.is_wayland { // On X11 `swap_buffers` does not block for vsync. However the next OpenGl command // will block to synchronize (this is `glClear` in Alacritty), which causes a // permanent one frame delay. @@ -1397,7 +1324,7 @@ impl Display { let y_top = size_info.height() - size_info.padding_y(); let y = y_top - (point.line as u32 + 1) * size_info.cell_height(); let width = len * size_info.cell_width(); - DamageRect { x, y, width, height: size_info.cell_height() } + DamageRect::new(x as i32, y as i32, width as i32, size_info.cell_height() as i32) } /// Damage currently highlighted `Display` hints. @@ -1420,7 +1347,7 @@ impl Display { /// Returns `true` if damage information should be collected, `false` otherwise. #[inline] fn collect_damage(&self) -> bool { - self.is_damage_supported || self.debug_damage + self.is_wayland || self.debug_damage } /// Highlight damaged rects. @@ -1462,8 +1389,138 @@ impl Display { impl Drop for Display { fn drop(&mut self) { // Switch OpenGL context before dropping, otherwise objects (like programs) from other - // contexts might be deleted. - self.window.make_current() + // contexts might be deleted during droping renderer. + self.make_current(); + unsafe { + ManuallyDrop::drop(&mut self.renderer); + ManuallyDrop::drop(&mut self.context); + ManuallyDrop::drop(&mut self.surface); + } + } +} + +/// Input method state. +#[derive(Debug, Default)] +pub struct Ime { + /// Whether the IME is enabled. + enabled: bool, + + /// Current IME preedit. + preedit: Option, +} + +impl Ime { + pub fn new() -> Self { + Default::default() + } + + #[inline] + pub fn set_enabled(&mut self, is_enabled: bool) { + if is_enabled { + self.enabled = is_enabled + } else { + // Clear state when disabling IME. + *self = Default::default(); + } + } + + #[inline] + pub fn is_enabled(&self) -> bool { + self.enabled + } + + #[inline] + pub fn set_preedit(&mut self, preedit: Option) { + self.preedit = preedit; + } + + #[inline] + pub fn preedit(&self) -> Option<&Preedit> { + self.preedit.as_ref() + } +} + +#[derive(Debug, Default, PartialEq, Eq)] +pub struct Preedit { + /// The preedit text. + text: String, + + /// Byte offset for cursor start into the preedit text. + /// + /// `None` means that the cursor is invisible. + cursor_byte_offset: Option, + + /// The cursor offset from the end of the preedit in char width. + cursor_end_offset: Option, +} + +impl Preedit { + pub fn new(text: String, cursor_byte_offset: Option) -> Self { + let cursor_end_offset = if let Some(byte_offset) = cursor_byte_offset { + // Convert byte offset into char offset. + let cursor_end_offset = + text[byte_offset..].chars().fold(0, |acc, ch| acc + ch.width().unwrap_or(1)); + + Some(cursor_end_offset) + } else { + None + }; + + Self { text, cursor_byte_offset, cursor_end_offset } + } +} + +/// Pending renderer updates. +/// +/// All renderer updates are cached to be applied just before rendering, to avoid platform-specific +/// rendering issues. +#[derive(Debug, Default, Copy, Clone)] +pub struct RendererUpdate { + /// Should resize the window. + resize: bool, + + /// Clear font caches. + clear_font_cache: bool, +} + +/// Struct for safe in-place replacement. +/// +/// This struct allows easily replacing struct fields that provide `self -> Self` methods in-place, +/// without having to deal with constantly unwrapping the underlying [`Option`]. +struct Replaceable(Option); + +impl Replaceable { + pub fn new(inner: T) -> Self { + Self(Some(inner)) + } + + /// Replace the contents of the container. + pub fn replace_with T>(&mut self, f: F) { + self.0 = self.0.take().map(f); + } + + /// Get immutable access to the wrapped value. + pub fn get(&self) -> &T { + self.0.as_ref().unwrap() + } + + /// Get mutable access to the wrapped value. + pub fn get_mut(&mut self) -> &mut T { + self.0.as_mut().unwrap() + } +} + +impl Deref for Replaceable { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.get() + } +} + +impl DerefMut for Replaceable { + fn deref_mut(&mut self) -> &mut Self::Target { + self.get_mut() } } @@ -1486,7 +1543,7 @@ fn window_size( dimensions: Dimensions, cell_width: f32, cell_height: f32, - scale_factor: f64, + scale_factor: f32, ) -> PhysicalSize { let padding = config.window.padding(scale_factor); diff --git a/alacritty/src/display/window.rs b/alacritty/src/display/window.rs index f558a654..93e83677 100644 --- a/alacritty/src/display/window.rs +++ b/alacritty/src/display/window.rs @@ -4,7 +4,7 @@ use { std::sync::atomic::AtomicBool, std::sync::Arc, - glutin::platform::unix::{WindowBuilderExtUnix, WindowExtUnix}, + winit::platform::unix::{WindowBuilderExtUnix, WindowExtUnix}, }; #[rustfmt::skip] @@ -12,8 +12,8 @@ use { use { wayland_client::protocol::wl_surface::WlSurface, wayland_client::{Attached, EventQueue, Proxy}, - glutin::platform::unix::EventLoopWindowTargetExtUnix, - glutin::window::Theme, + winit::platform::unix::EventLoopWindowTargetExtUnix, + winit::window::Theme, }; #[rustfmt::skip] @@ -21,39 +21,35 @@ use { use { std::io::Cursor, + glutin::platform::x11::X11VisualInfo, x11_dl::xlib::{Display as XDisplay, PropModeReplace, XErrorEvent, Xlib}, - glutin::window::Icon, + winit::window::Icon, png::Decoder, }; use std::fmt::{self, Display, Formatter}; -use std::ops::{Deref, DerefMut}; -use std::sync::atomic::{AtomicU8, Ordering}; -use bitflags::bitflags; #[cfg(target_os = "macos")] use cocoa::base::{id, NO, YES}; -use glutin::dpi::{PhysicalPosition, PhysicalSize}; -use glutin::event_loop::EventLoopWindowTarget; -#[cfg(target_os = "macos")] -use glutin::platform::macos::{WindowBuilderExtMacOS, WindowExtMacOS}; -#[cfg(windows)] -use glutin::platform::windows::IconExtWindows; -use glutin::window::{ - CursorIcon, Fullscreen, UserAttentionType, Window as GlutinWindow, WindowBuilder, WindowId, -}; -use glutin::{self, ContextBuilder, PossiblyCurrent, Rect, WindowedContext}; #[cfg(target_os = "macos")] use objc::{msg_send, sel, sel_impl}; -#[cfg(target_os = "macos")] use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; +use winit::dpi::{PhysicalPosition, PhysicalSize}; +use winit::event_loop::EventLoopWindowTarget; +#[cfg(target_os = "macos")] +use winit::platform::macos::{WindowBuilderExtMacOS, WindowExtMacOS}; +#[cfg(windows)] +use winit::platform::windows::IconExtWindows; +use winit::window::{ + CursorIcon, Fullscreen, UserAttentionType, Window as WinitWindow, WindowBuilder, WindowId, +}; + use alacritty_terminal::index::Point; use crate::config::window::{Decorations, Identity, WindowConfig}; use crate::config::UiConfig; use crate::display::SizeInfo; -use crate::gl; /// Window icon for `_NET_WM_ICON` property. #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] @@ -63,28 +59,14 @@ static WINDOW_ICON: &[u8] = include_bytes!("../../extra/logo/compat/alacritty-te #[cfg(windows)] const IDI_ICON: u16 = 0x101; -/// Context creation flags from probing config. -static GL_CONTEXT_CREATION_FLAGS: AtomicU8 = AtomicU8::new(GlContextFlags::SRGB.bits); - -bitflags! { - pub struct GlContextFlags: u8 { - const EMPTY = 0b000000000; - const SRGB = 0b0000_0001; - const DEEP_COLOR = 0b0000_0010; - } -} - /// Window errors. #[derive(Debug)] pub enum Error { /// Error creating the window. - ContextCreation(glutin::CreationError), + WindowCreation(winit::error::OsError), /// Error dealing with fonts. Font(crossfont::Error), - - /// Error manipulating the rendering context. - Context(glutin::ContextError), } /// Result of fallible operations concerning a Window. @@ -93,8 +75,7 @@ type Result = std::result::Result; impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { - Error::ContextCreation(err) => err.source(), - Error::Context(err) => err.source(), + Error::WindowCreation(err) => err.source(), Error::Font(err) => err.source(), } } @@ -103,22 +84,15 @@ impl std::error::Error for Error { impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - Error::ContextCreation(err) => write!(f, "Error creating GL context; {}", err), - Error::Context(err) => write!(f, "Error operating on render context; {}", err), + Error::WindowCreation(err) => write!(f, "Error creating GL context; {}", err), Error::Font(err) => err.fmt(f), } } } -impl From for Error { - fn from(val: glutin::CreationError) -> Self { - Error::ContextCreation(val) - } -} - -impl From for Error { - fn from(val: glutin::ContextError) -> Self { - Error::Context(val) +impl From for Error { + fn from(val: winit::error::OsError) -> Self { + Error::WindowCreation(val) } } @@ -128,34 +102,6 @@ impl From for Error { } } -fn create_gl_window( - mut window: WindowBuilder, - event_loop: &EventLoopWindowTarget, - flags: GlContextFlags, - vsync: bool, - dimensions: Option>, -) -> Result> { - if let Some(dimensions) = dimensions { - window = window.with_inner_size(dimensions); - } - - let mut windowed_context_builder = ContextBuilder::new() - .with_srgb(flags.contains(GlContextFlags::SRGB)) - .with_vsync(vsync) - .with_hardware_acceleration(None); - - if flags.contains(GlContextFlags::DEEP_COLOR) { - windowed_context_builder = windowed_context_builder.with_pixel_format(30, 2); - } - - let windowed_context = windowed_context_builder.build_windowed(window, event_loop)?; - - // Make the context current so OpenGL operations can run. - let windowed_context = unsafe { windowed_context.make_current().map_err(|(_, err)| err)? }; - - Ok(windowed_context) -} - /// A window which can be used for displaying the terminal. /// /// Wraps the underlying windowing library to provide a stable API in Alacritty. @@ -171,10 +117,11 @@ pub struct Window { /// Cached scale factor for quickly scaling pixel sizes. pub scale_factor: f64, + window: WinitWindow, + /// Current window title. title: String, - windowed_context: Replaceable>, current_mouse_cursor: CursorIcon, mouse_visible: bool, } @@ -187,81 +134,65 @@ impl Window { event_loop: &EventLoopWindowTarget, config: &UiConfig, identity: &Identity, - size: Option>, #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] wayland_event_queue: Option<&EventQueue>, + #[rustfmt::skip] + #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] + x11_visual: Option, ) -> Result { let identity = identity.clone(); - let mut window_builder = Window::get_platform_window(&identity, &config.window); + let mut window_builder = Window::get_platform_window( + &identity, + &config.window, + #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] + x11_visual, + ); if let Some(position) = config.window.position { window_builder = window_builder .with_position(PhysicalPosition::::from((position.x, position.y))); } + let window = window_builder.build(event_loop)?; + // Check if we're running Wayland to disable vsync. #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] let is_wayland = event_loop.is_wayland(); - #[cfg(any(not(feature = "wayland"), target_os = "macos", windows))] + #[cfg(all(not(feature = "wayland"), not(any(target_os = "macos", windows))))] let is_wayland = false; - let mut windowed_context = None; - let current_flags = - GlContextFlags::from_bits_truncate(GL_CONTEXT_CREATION_FLAGS.load(Ordering::Relaxed)); - for flags in [ - current_flags, - GlContextFlags::EMPTY, - GlContextFlags::SRGB | GlContextFlags::DEEP_COLOR, - GlContextFlags::DEEP_COLOR, - ] { - windowed_context = Some(create_gl_window( - window_builder.clone(), - event_loop, - flags, - !is_wayland, - size, - )); - if windowed_context.as_ref().unwrap().is_ok() { - GL_CONTEXT_CREATION_FLAGS.store(flags.bits, Ordering::Relaxed); - break; - } - } - let windowed_context = windowed_context.unwrap()?; - // Text cursor. let current_mouse_cursor = CursorIcon::Text; - windowed_context.window().set_cursor_icon(current_mouse_cursor); + window.set_cursor_icon(current_mouse_cursor); // Enable IME. - windowed_context.window().set_ime_allowed(true); - - // 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 _); + window.set_ime_allowed(true); #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] if !is_wayland { // On X11, embed the window inside another if the parent ID has been set. if let Some(parent_window_id) = config.window.embed { - x_embed_window(windowed_context.window(), parent_window_id); + x_embed_window(&window, parent_window_id); } } #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] let wayland_surface = if is_wayland { // Attach surface to Alacritty's internal wayland queue to handle frame callbacks. - let surface = windowed_context.window().wayland_surface().unwrap(); + let surface = window.wayland_surface().unwrap(); let proxy: Proxy = unsafe { Proxy::from_c_ptr(surface as _) }; Some(proxy.attach(wayland_event_queue.as_ref().unwrap().token())) } else { None }; - let scale_factor = windowed_context.window().scale_factor(); + let scale_factor = window.scale_factor(); + log::info!("Window scale factor: {}", scale_factor); Ok(Self { current_mouse_cursor, mouse_visible: true, - windowed_context: Replaceable::new(windowed_context), + window, title: identity.title, #[cfg(not(any(target_os = "macos", windows)))] should_draw: Arc::new(AtomicBool::new(true)), @@ -271,26 +202,31 @@ impl Window { }) } + #[inline] + pub fn raw_window_handle(&self) -> RawWindowHandle { + self.window.raw_window_handle() + } + #[inline] pub fn set_inner_size(&self, size: PhysicalSize) { - self.window().set_inner_size(size); + self.window.set_inner_size(size); } #[inline] pub fn inner_size(&self) -> PhysicalSize { - self.window().inner_size() + self.window.inner_size() } #[inline] pub fn set_visible(&self, visibility: bool) { - self.window().set_visible(visibility); + self.window.set_visible(visibility); } /// Set the window title. #[inline] pub fn set_title(&mut self, title: String) { self.title = title; - self.window().set_title(&self.title); + self.window.set_title(&self.title); } /// Get the window title. @@ -301,14 +237,14 @@ impl Window { #[inline] pub fn request_redraw(&self) { - self.window().request_redraw(); + self.window.request_redraw(); } #[inline] pub fn set_mouse_cursor(&mut self, cursor: CursorIcon) { if cursor != self.current_mouse_cursor { self.current_mouse_cursor = cursor; - self.window().set_cursor_icon(cursor); + self.window.set_cursor_icon(cursor); } } @@ -316,12 +252,18 @@ impl Window { pub fn set_mouse_visible(&mut self, visible: bool) { if visible != self.mouse_visible { self.mouse_visible = visible; - self.window().set_cursor_visible(visible); + self.window.set_cursor_visible(visible); } } #[cfg(not(any(target_os = "macos", windows)))] - pub fn get_platform_window(identity: &Identity, window_config: &WindowConfig) -> WindowBuilder { + pub fn get_platform_window( + identity: &Identity, + window_config: &WindowConfig, + #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] x11_visual: Option< + X11VisualInfo, + >, + ) -> WindowBuilder { #[cfg(feature = "x11")] let icon = { let mut decoder = Decoder::new(Cursor::new(WINDOW_ICON)); @@ -351,6 +293,12 @@ impl Window { None => builder, }; + #[cfg(feature = "x11")] + let builder = match x11_visual { + Some(visual) => builder.with_x11_visual(visual.into_raw()), + None => builder, + }; + #[cfg(feature = "wayland")] let builder = match window_config.decorations_theme_variant() { Some("light") => builder.with_wayland_csd_theme(Theme::Light), @@ -363,7 +311,7 @@ impl Window { #[cfg(windows)] pub fn get_platform_window(identity: &Identity, window_config: &WindowConfig) -> WindowBuilder { - let icon = glutin::window::Icon::from_resource(IDI_ICON, None); + let icon = winit::window::Icon::from_resource(IDI_ICON, None); WindowBuilder::new() .with_title(&identity.title) @@ -402,47 +350,47 @@ impl Window { pub fn set_urgent(&self, is_urgent: bool) { let attention = if is_urgent { Some(UserAttentionType::Critical) } else { None }; - self.window().request_user_attention(attention); + self.window.request_user_attention(attention); } pub fn id(&self) -> WindowId { - self.window().id() + self.window.id() } pub fn set_maximized(&self, maximized: bool) { - self.window().set_maximized(maximized); + self.window.set_maximized(maximized); } pub fn set_minimized(&self, minimized: bool) { - self.window().set_minimized(minimized); + self.window.set_minimized(minimized); } /// Toggle the window's fullscreen state. pub fn toggle_fullscreen(&self) { - self.set_fullscreen(self.window().fullscreen().is_none()); + self.set_fullscreen(self.window.fullscreen().is_none()); } /// Toggle the window's maximized state. pub fn toggle_maximized(&self) { - self.set_maximized(!self.window().is_maximized()); + self.set_maximized(!self.window.is_maximized()); } #[cfg(target_os = "macos")] pub fn toggle_simple_fullscreen(&self) { - self.set_simple_fullscreen(!self.window().simple_fullscreen()); + self.set_simple_fullscreen(!self.window.simple_fullscreen()); } pub fn set_fullscreen(&self, fullscreen: bool) { if fullscreen { - self.window().set_fullscreen(Some(Fullscreen::Borderless(None))); + self.window.set_fullscreen(Some(Fullscreen::Borderless(None))); } else { - self.window().set_fullscreen(None); + self.window.set_fullscreen(None); } } #[cfg(target_os = "macos")] pub fn set_simple_fullscreen(&self, simple_fullscreen: bool) { - self.window().set_simple_fullscreen(simple_fullscreen); + self.window.set_simple_fullscreen(simple_fullscreen); } #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] @@ -451,7 +399,7 @@ impl Window { } pub fn set_ime_allowed(&self, allowed: bool) { - self.windowed_context.window().set_ime_allowed(allowed); + self.window.set_ime_allowed(allowed); } /// Adjust the IME editor position according to the new location of the cursor. @@ -459,56 +407,7 @@ impl Window { let nspot_x = f64::from(size.padding_x() + point.column.0 as f32 * size.cell_width()); let nspot_y = f64::from(size.padding_y() + (point.line + 1) as f32 * size.cell_height()); - self.window().set_ime_position(PhysicalPosition::new(nspot_x, nspot_y)); - } - - pub fn swap_buffers(&self) { - self.windowed_context.swap_buffers().expect("swap buffers"); - } - - pub fn swap_buffers_with_damage(&self, damage: &[Rect]) { - self.windowed_context.swap_buffers_with_damage(damage).expect("swap buffes with damage"); - } - - #[cfg(any(target_os = "macos", windows))] - pub fn swap_buffers_with_damage_supported(&self) -> bool { - // Disable damage tracking on macOS/Windows since there's no observation of it working. - false - } - - #[cfg(not(any(target_os = "macos", windows)))] - pub fn swap_buffers_with_damage_supported(&self) -> bool { - // On X11 damage tracking is behaving in unexpected ways on some NVIDIA systems. Since - // there's no compositor supporting it, damage tracking is disabled on X11. - // - // For more see https://github.com/alacritty/alacritty/issues/6051. - #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] - if self.window().xlib_window().is_some() { - return false; - } - - self.windowed_context.swap_buffers_with_damage_supported() - } - - pub fn resize(&self, size: PhysicalSize) { - self.windowed_context.resize(size); - } - - pub fn make_not_current(&mut self) { - if self.windowed_context.is_current() { - self.windowed_context.replace_with(|context| unsafe { - // We do ensure that context is current before any rendering operation due to multi - // window support, so we don't need extra "type aid" from glutin here. - context.make_not_current().expect("context swap").treat_as_current() - }); - } - } - - pub fn make_current(&mut self) { - if !self.windowed_context.is_current() { - self.windowed_context - .replace_with(|context| unsafe { context.make_current().expect("context swap") }); - } + self.window.set_ime_position(PhysicalPosition::new(nspot_x, nspot_y)); } /// Disable macOS window shadows. @@ -516,7 +415,7 @@ impl Window { /// This prevents rendering artifacts from showing up when the window is transparent. #[cfg(target_os = "macos")] pub fn set_has_shadow(&self, has_shadows: bool) { - let raw_window = match self.window().raw_window_handle() { + let raw_window = match self.raw_window_handle() { RawWindowHandle::AppKit(handle) => handle.ns_window as id, _ => return, }; @@ -526,14 +425,10 @@ impl Window { let _: id = msg_send![raw_window, setHasShadow: value]; } } - - fn window(&self) -> &GlutinWindow { - self.windowed_context.window() - } } #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] -fn x_embed_window(window: &GlutinWindow, parent_id: std::os::raw::c_ulong) { +fn x_embed_window(window: &WinitWindow, parent_id: std::os::raw::c_ulong) { let (xlib_display, xlib_window) = match (window.xlib_display(), window.xlib_window()) { (Some(display), Some(window)) => (display, window), _ => return, @@ -571,44 +466,3 @@ unsafe extern "C" fn xembed_error_handler(_: *mut XDisplay, _: *mut XErrorEvent) log::error!("Could not embed into specified window."); std::process::exit(1); } - -/// Struct for safe in-place replacement. -/// -/// This struct allows easily replacing struct fields that provide `self -> Self` methods in-place, -/// without having to deal with constantly unwrapping the underlying [`Option`]. -struct Replaceable(Option); - -impl Replaceable { - pub fn new(inner: T) -> Self { - Self(Some(inner)) - } - - /// Replace the contents of the container. - pub fn replace_with T>(&mut self, f: F) { - self.0 = self.0.take().map(f); - } - - /// Get immutable access to the wrapped value. - pub fn get(&self) -> &T { - self.0.as_ref().unwrap() - } - - /// Get mutable access to the wrapped value. - pub fn get_mut(&mut self) -> &mut T { - self.0.as_mut().unwrap() - } -} - -impl Deref for Replaceable { - type Target = T; - - fn deref(&self) -> &Self::Target { - self.get() - } -} - -impl DerefMut for Replaceable { - fn deref_mut(&mut self) -> &mut Self::Target { - self.get_mut() - } -} diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index 853e3f5e..e480e305 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -13,20 +13,20 @@ use std::rc::Rc; use std::time::{Duration, Instant}; use std::{env, f32, mem}; -use glutin::dpi::PhysicalSize; -use glutin::event::{ - ElementState, Event as GlutinEvent, Ime, ModifiersState, MouseButton, StartCause, WindowEvent, -}; -use glutin::event_loop::{ - ControlFlow, DeviceEventFilter, EventLoop, EventLoopProxy, EventLoopWindowTarget, -}; -use glutin::platform::run_return::EventLoopExtRunReturn; -#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] -use glutin::platform::unix::EventLoopWindowTargetExtUnix; -use glutin::window::WindowId; use log::{debug, error, info, warn}; #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] use wayland_client::{Display as WaylandDisplay, EventQueue}; +use winit::dpi::PhysicalSize; +use winit::event::{ + ElementState, Event as WinitEvent, Ime, ModifiersState, MouseButton, StartCause, WindowEvent, +}; +use winit::event_loop::{ + ControlFlow, DeviceEventFilter, EventLoop, EventLoopProxy, EventLoopWindowTarget, +}; +use winit::platform::run_return::EventLoopExtRunReturn; +#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] +use winit::platform::unix::EventLoopWindowTargetExtUnix; +use winit::window::WindowId; use crossfont::{self, Size}; @@ -81,9 +81,9 @@ impl Event { } } -impl From for GlutinEvent<'_, Event> { +impl From for WinitEvent<'_, Event> { fn from(event: Event) -> Self { - GlutinEvent::UserEvent(event) + WinitEvent::UserEvent(event) } } @@ -1080,10 +1080,10 @@ impl Mouse { } impl input::Processor> { - /// Handle events from glutin. - pub fn handle_event(&mut self, event: GlutinEvent<'_, Event>) { + /// Handle events from winit. + pub fn handle_event(&mut self, event: WinitEvent<'_, Event>) { match event { - GlutinEvent::UserEvent(Event { payload, .. }) => match payload { + WinitEvent::UserEvent(Event { payload, .. }) => match payload { EventType::ScaleFactorChanged(scale_factor, (width, height)) => { let display_update_pending = &mut self.ctx.display.pending_update; @@ -1171,8 +1171,8 @@ impl input::Processor> { EventType::IpcConfig(_) => (), EventType::ConfigReload(_) | EventType::CreateWindow(_) => (), }, - GlutinEvent::RedrawRequested(_) => *self.ctx.dirty = true, - GlutinEvent::WindowEvent { event, .. } => { + WinitEvent::RedrawRequested(_) => *self.ctx.dirty = true, + WinitEvent::WindowEvent { event, .. } => { match event { WindowEvent::CloseRequested => self.ctx.terminal.exit(), WindowEvent::Resized(size) => { @@ -1279,13 +1279,13 @@ impl input::Processor> { | WindowEvent::Moved(_) => (), } }, - GlutinEvent::Suspended { .. } - | GlutinEvent::NewEvents { .. } - | GlutinEvent::DeviceEvent { .. } - | GlutinEvent::MainEventsCleared - | GlutinEvent::RedrawEventsCleared - | GlutinEvent::Resumed - | GlutinEvent::LoopDestroyed => (), + WinitEvent::Suspended { .. } + | WinitEvent::NewEvents { .. } + | WinitEvent::DeviceEvent { .. } + | WinitEvent::MainEventsCleared + | WinitEvent::RedrawEventsCleared + | WinitEvent::Resumed + | WinitEvent::LoopDestroyed => (), } } } @@ -1327,6 +1327,30 @@ impl Processor { } } + /// Create initial window and load GL platform. + /// + /// This will initialize the OpenGL Api and pick a config that + /// will be used for the rest of the windows. + pub fn create_initial_window( + &mut self, + event_loop: &EventLoopWindowTarget, + proxy: EventLoopProxy, + options: WindowOptions, + ) -> Result<(), Box> { + let window_context = WindowContext::initial( + event_loop, + proxy, + self.config.clone(), + options, + #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] + self.wayland_event_queue.as_ref(), + )?; + + self.windows.insert(window_context.id(), window_context); + + Ok(()) + } + /// Create a new terminal window. pub fn create_window( &mut self, @@ -1334,14 +1358,16 @@ impl Processor { proxy: EventLoopProxy, options: WindowOptions, ) -> Result<(), Box> { - let window_context = WindowContext::new( - self.config.clone(), - &options, + let window = self.windows.iter().next().as_ref().unwrap().1; + let window_context = window.additional( event_loop, proxy, + self.config.clone(), + options, #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] self.wayland_event_queue.as_ref(), )?; + self.windows.insert(window_context.id(), window_context); Ok(()) } @@ -1369,7 +1395,7 @@ impl Processor { let exit_code = event_loop.run_return(move |event, event_loop, control_flow| { if self.config.debug.print_events { - info!("glutin event: {:?}", event); + info!("winit event: {:?}", event); } // Ignore all events we do not care about. @@ -1379,7 +1405,7 @@ impl Processor { match event { // The event loop just got initialized. Create a window. - GlutinEvent::Resumed => { + WinitEvent::Resumed => { // Creating window inside event loop is required for platforms like macOS to // properly initialize state, like tab management. Othwerwise the first window // won't handle tabs. @@ -1388,9 +1414,11 @@ impl Processor { None => return, }; - if let Err(err) = - self.create_window(event_loop, proxy.clone(), initial_window_options) - { + if let Err(err) = self.create_initial_window( + event_loop, + proxy.clone(), + initial_window_options, + ) { // Log the error right away since we can't return it. eprintln!("Error: {}", err); *control_flow = ControlFlow::ExitWithCode(1); @@ -1400,7 +1428,7 @@ impl Processor { info!("Initialisation complete"); }, // Check for shutdown. - GlutinEvent::UserEvent(Event { + WinitEvent::UserEvent(Event { window_id: Some(window_id), payload: EventType::Terminal(TerminalEvent::Exit), }) => { @@ -1424,7 +1452,7 @@ impl Processor { } }, // Process all pending events. - GlutinEvent::RedrawEventsCleared => { + WinitEvent::RedrawEventsCleared => { *control_flow = match scheduler.update() { Some(instant) => ControlFlow::WaitUntil(instant), None => ControlFlow::Wait, @@ -1445,14 +1473,12 @@ impl Processor { &proxy, &mut clipboard, &mut scheduler, - GlutinEvent::RedrawEventsCleared, + WinitEvent::RedrawEventsCleared, ); } }, // Process config update. - GlutinEvent::UserEvent(Event { - payload: EventType::ConfigReload(path), .. - }) => { + WinitEvent::UserEvent(Event { payload: EventType::ConfigReload(path), .. }) => { // Clear config logs from message bar for all terminals. for window_context in self.windows.values_mut() { if !window_context.message_buffer.is_empty() { @@ -1472,7 +1498,7 @@ impl Processor { }, // Process IPC config update. #[cfg(unix)] - GlutinEvent::UserEvent(Event { + WinitEvent::UserEvent(Event { payload: EventType::IpcConfig(ipc_config), window_id, }) => { @@ -1485,14 +1511,14 @@ impl Processor { } }, // Create a new terminal window. - GlutinEvent::UserEvent(Event { + WinitEvent::UserEvent(Event { payload: EventType::CreateWindow(options), .. }) => { // XXX Ensure that no context is current when creating a new window, otherwise // it may lock the backing buffer of the surface of current context when asking // e.g. EGL on Wayland to create a new context. for window_context in self.windows.values_mut() { - window_context.display.window.make_not_current(); + window_context.display.make_not_current(); } if let Err(err) = self.create_window(event_loop, proxy.clone(), options) { @@ -1500,7 +1526,7 @@ impl Processor { } }, // Process events affecting all windows. - GlutinEvent::UserEvent(event @ Event { window_id: None, .. }) => { + WinitEvent::UserEvent(event @ Event { window_id: None, .. }) => { for window_context in self.windows.values_mut() { window_context.handle_event( event_loop, @@ -1512,9 +1538,9 @@ impl Processor { } }, // Process window-specific events. - GlutinEvent::WindowEvent { window_id, .. } - | GlutinEvent::UserEvent(Event { window_id: Some(window_id), .. }) - | GlutinEvent::RedrawRequested(window_id) => { + WinitEvent::WindowEvent { window_id, .. } + | WinitEvent::UserEvent(Event { window_id: Some(window_id), .. }) + | WinitEvent::RedrawRequested(window_id) => { if let Some(window_context) = self.windows.get_mut(&window_id) { window_context.handle_event( event_loop, @@ -1537,10 +1563,10 @@ impl Processor { } /// Check if an event is irrelevant and can be skipped. - fn skip_event(event: &GlutinEvent<'_, Event>) -> bool { + fn skip_event(event: &WinitEvent<'_, Event>) -> bool { match event { - GlutinEvent::NewEvents(StartCause::Init) => false, - GlutinEvent::WindowEvent { event, .. } => matches!( + WinitEvent::NewEvents(StartCause::Init) => false, + WinitEvent::WindowEvent { event, .. } => matches!( event, WindowEvent::KeyboardInput { is_synthetic: true, .. } | WindowEvent::TouchpadPressure { .. } @@ -1552,10 +1578,10 @@ impl Processor { | WindowEvent::Touch(_) | WindowEvent::Moved(_) ), - GlutinEvent::Suspended { .. } - | GlutinEvent::NewEvents { .. } - | GlutinEvent::MainEventsCleared - | GlutinEvent::LoopDestroyed => true, + WinitEvent::Suspended { .. } + | WinitEvent::NewEvents { .. } + | WinitEvent::MainEventsCleared + | WinitEvent::LoopDestroyed => true, _ => false, } } diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs index 6dbb72cf..c8d948ff 100644 --- a/alacritty/src/input.rs +++ b/alacritty/src/input.rs @@ -1,4 +1,4 @@ -//! Handle input from glutin. +//! Handle input from winit. //! //! Certain key combinations should send some escape sequence back to the PTY. //! In order to figure that out, state about which modifier keys are pressed @@ -12,14 +12,14 @@ use std::fmt::Debug; use std::marker::PhantomData; use std::time::{Duration, Instant}; -use glutin::dpi::PhysicalPosition; -use glutin::event::{ +use winit::dpi::PhysicalPosition; +use winit::event::{ ElementState, KeyboardInput, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase, }; -use glutin::event_loop::EventLoopWindowTarget; +use winit::event_loop::EventLoopWindowTarget; #[cfg(target_os = "macos")] -use glutin::platform::macos::EventLoopWindowTargetExtMacOS; -use glutin::window::CursorIcon; +use winit::platform::macos::EventLoopWindowTargetExtMacOS; +use winit::window::CursorIcon; use alacritty_terminal::ansi::{ClearMode, Handler}; use alacritty_terminal::event::EventListener; @@ -51,7 +51,7 @@ const MIN_SELECTION_SCROLLING_HEIGHT: f64 = 5.; /// Number of pixels for increasing the selection scrolling speed factor by one. const SELECTION_SCROLLING_STEP: f64 = 20.; -/// Processes input from glutin. +/// Processes input from winit. /// /// An escape sequence may be emitted in case specific keys or key combinations /// are activated. @@ -990,8 +990,8 @@ impl> Processor { mod tests { use super::*; - use glutin::event::{DeviceId, Event as GlutinEvent, VirtualKeyCode, WindowEvent}; - use glutin::window::WindowId; + use winit::event::{DeviceId, Event as WinitEvent, VirtualKeyCode, WindowEvent}; + use winit::window::WindowId; use alacritty_terminal::event::Event as TerminalEvent; @@ -1158,8 +1158,8 @@ mod tests { let mut processor = Processor::new(context); - let event: GlutinEvent::<'_, TerminalEvent> = $input; - if let GlutinEvent::WindowEvent { + let event: WinitEvent::<'_, TerminalEvent> = $input; + if let WinitEvent::WindowEvent { event: WindowEvent::MouseInput { state, button, @@ -1199,7 +1199,7 @@ mod tests { name: single_click, initial_state: ClickState::None, initial_button: MouseButton::Other(0), - input: GlutinEvent::WindowEvent { + input: WinitEvent::WindowEvent { event: WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Left, @@ -1215,7 +1215,7 @@ mod tests { name: single_right_click, initial_state: ClickState::None, initial_button: MouseButton::Other(0), - input: GlutinEvent::WindowEvent { + input: WinitEvent::WindowEvent { event: WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Right, @@ -1231,7 +1231,7 @@ mod tests { name: single_middle_click, initial_state: ClickState::None, initial_button: MouseButton::Other(0), - input: GlutinEvent::WindowEvent { + input: WinitEvent::WindowEvent { event: WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Middle, @@ -1247,7 +1247,7 @@ mod tests { name: double_click, initial_state: ClickState::Click, initial_button: MouseButton::Left, - input: GlutinEvent::WindowEvent { + input: WinitEvent::WindowEvent { event: WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Left, @@ -1263,7 +1263,7 @@ mod tests { name: triple_click, initial_state: ClickState::DoubleClick, initial_button: MouseButton::Left, - input: GlutinEvent::WindowEvent { + input: WinitEvent::WindowEvent { event: WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Left, @@ -1279,7 +1279,7 @@ mod tests { name: multi_click_separate_buttons, initial_state: ClickState::DoubleClick, initial_button: MouseButton::Left, - input: GlutinEvent::WindowEvent { + input: WinitEvent::WindowEvent { event: WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Right, diff --git a/alacritty/src/ipc.rs b/alacritty/src/ipc.rs index 368015a9..1cb7a1c8 100644 --- a/alacritty/src/ipc.rs +++ b/alacritty/src/ipc.rs @@ -6,9 +6,9 @@ use std::os::unix::net::{UnixListener, UnixStream}; use std::path::PathBuf; use std::{env, fs, process}; -use glutin::event_loop::EventLoopProxy; -use glutin::window::WindowId; use log::warn; +use winit::event_loop::EventLoopProxy; +use winit::window::WindowId; use alacritty_terminal::thread; diff --git a/alacritty/src/logging.rs b/alacritty/src/logging.rs index 113e96ff..846ab1c5 100644 --- a/alacritty/src/logging.rs +++ b/alacritty/src/logging.rs @@ -12,8 +12,8 @@ use std::sync::{Arc, Mutex}; use std::time::Instant; use std::{env, process}; -use glutin::event_loop::EventLoopProxy; use log::{self, Level, LevelFilter}; +use winit::event_loop::EventLoopProxy; use alacritty_terminal::config::LOG_TARGET_CONFIG; diff --git a/alacritty/src/main.rs b/alacritty/src/main.rs index 569ed30e..3514b84e 100644 --- a/alacritty/src/main.rs +++ b/alacritty/src/main.rs @@ -20,12 +20,12 @@ use std::fs; use std::io::{self, Write}; use std::path::PathBuf; -use glutin::event_loop::EventLoopBuilder as GlutinEventLoopBuilder; -#[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] -use glutin::platform::unix::EventLoopWindowTargetExtUnix; use log::info; #[cfg(windows)] use windows_sys::Win32::System::Console::{AttachConsole, FreeConsole, ATTACH_PARENT_PROCESS}; +use winit::event_loop::EventLoopBuilder as WinitEventLoopBuilder; +#[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] +use winit::platform::unix::EventLoopWindowTargetExtUnix; use alacritty_terminal::tty; @@ -124,8 +124,8 @@ impl Drop for TemporaryFiles { /// Creates a window, the terminal state, PTY, I/O event loop, input processor, /// config change monitor, and runs the main display loop. fn alacritty(options: Options) -> Result<(), Box> { - // Setup glutin event loop. - let window_event_loop = GlutinEventLoopBuilder::::with_user_event().build(); + // Setup winit event loop. + let window_event_loop = WinitEventLoopBuilder::::with_user_event().build(); // Initialize the logger as soon as possible as to capture output from other subsystems. let log_file = logging::initialize(&options, window_event_loop.create_proxy()) diff --git a/alacritty/src/renderer/mod.rs b/alacritty/src/renderer/mod.rs index 6ee8815c..d8c6fb6d 100644 --- a/alacritty/src/renderer/mod.rs +++ b/alacritty/src/renderer/mod.rs @@ -1,8 +1,11 @@ use std::collections::HashSet; -use std::ffi::CStr; +use std::ffi::{CStr, CString}; use std::fmt; +use std::sync::atomic::{AtomicBool, Ordering}; use crossfont::Metrics; +use glutin::context::PossiblyCurrentContext; +use glutin::display::{GetGlDisplay, GlDisplay}; use log::info; use once_cell::sync::OnceCell; @@ -16,6 +19,7 @@ use crate::gl; use crate::renderer::rects::{RectRenderer, RenderRect}; use crate::renderer::shader::ShaderError; +pub mod platform; pub mod rects; mod shader; mod text; @@ -33,6 +37,9 @@ macro_rules! cstr { } pub(crate) use cstr; +/// Whether the OpenGL functions have been loaded. +pub static GL_FUNS_LOADED: AtomicBool = AtomicBool::new(false); + #[derive(Debug)] pub enum Error { /// Shader error. @@ -80,7 +87,17 @@ impl Renderer { /// /// This will automatically pick between the GLES2 and GLSL3 renderer based on the GPU's /// supported OpenGL version. - pub fn new() -> Result { + pub fn new(context: &PossiblyCurrentContext) -> Result { + // We need to load OpenGL functions once per instance, but only after we make our context + // current due to WGL limitations. + if !GL_FUNS_LOADED.swap(true, Ordering::Relaxed) { + let gl_display = context.display(); + gl::load_with(|symbol| { + let symbol = CString::new(symbol).unwrap(); + gl_display.get_proc_address(symbol.as_c_str()).cast() + }); + } + let (version, renderer) = unsafe { let renderer = CStr::from_ptr(gl::GetString(gl::RENDERER) as *mut _); let version = CStr::from_ptr(gl::GetString(gl::SHADING_LANGUAGE_VERSION) as *mut _); diff --git a/alacritty/src/renderer/platform.rs b/alacritty/src/renderer/platform.rs new file mode 100644 index 00000000..0f55d941 --- /dev/null +++ b/alacritty/src/renderer/platform.rs @@ -0,0 +1,116 @@ +//! The graphics platform that is used by the renderer. + +use std::num::NonZeroU32; + +use glutin::config::{ColorBufferType, Config, ConfigTemplateBuilder, GetGlConfig}; +use glutin::context::{ + ContextApi, ContextAttributesBuilder, GlProfile, NotCurrentContext, Version, +}; +use glutin::display::{Display, DisplayApiPreference, GetGlDisplay}; +use glutin::error::Result as GlutinResult; +use glutin::prelude::*; +use glutin::surface::{Surface, SurfaceAttributesBuilder, WindowSurface}; + +use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; +use winit::dpi::PhysicalSize; +#[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] +use winit::platform::unix; + +/// Create the GL display. +pub fn create_gl_display( + raw_display_handle: RawDisplayHandle, + _raw_window_handle: Option, +) -> GlutinResult { + #[cfg(target_os = "macos")] + let preference = DisplayApiPreference::Cgl; + + #[cfg(windows)] + let preference = DisplayApiPreference::Wgl(Some(_raw_window_handle.unwrap())); + + #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] + let preference = DisplayApiPreference::GlxThenEgl(Box::new(unix::register_xlib_error_hook)); + + #[cfg(all(not(feature = "x11"), not(any(target_os = "macos", windows))))] + let preference = DisplayApiPreference::Egl; + + let display = unsafe { Display::new(raw_display_handle, preference)? }; + log::info!("Using {}", { display.version_string() }); + Ok(display) +} + +pub fn pick_gl_config( + gl_display: &Display, + raw_window_handle: Option, +) -> Result { + let mut default_config = ConfigTemplateBuilder::new().with_transparency(true); + + if let Some(raw_window_handle) = raw_window_handle { + default_config = default_config.compatible_with_native_window(raw_window_handle); + } + + let config_10bit = default_config + .clone() + .with_buffer_type(ColorBufferType::Rgb { r_size: 10, g_size: 10, b_size: 10 }) + .with_alpha_size(2); + + let configs = [ + default_config.clone(), + config_10bit.clone(), + default_config.with_transparency(false), + config_10bit.with_transparency(false), + ]; + + for config in configs { + let gl_config = unsafe { + gl_display.find_configs(config.build()).ok().and_then(|mut configs| configs.next()) + }; + + if let Some(gl_config) = gl_config { + return Ok(gl_config); + } + } + + Err(String::from("failed to find suitable GL configuration.")) +} + +pub fn create_gl_context( + gl_display: &Display, + gl_config: &Config, + raw_window_handle: Option, +) -> GlutinResult { + let context_attributes = ContextAttributesBuilder::new() + .with_context_api(ContextApi::OpenGl(Some(Version::new(3, 3)))) + .build(raw_window_handle); + + unsafe { + if let Ok(gl_context) = gl_display.create_context(gl_config, &context_attributes) { + Ok(gl_context) + } else { + let context_attributes = ContextAttributesBuilder::new() + .with_profile(GlProfile::Compatibility) + .with_context_api(ContextApi::OpenGl(Some(Version::new(2, 1)))) + .build(raw_window_handle); + gl_display.create_context(gl_config, &context_attributes) + } + } +} + +pub fn create_gl_surface( + gl_context: &NotCurrentContext, + size: PhysicalSize, + raw_window_handle: RawWindowHandle, +) -> GlutinResult> { + // Get the display and the config used to create that context. + let gl_display = gl_context.display(); + let gl_config = gl_context.config(); + + let surface_attributes = + SurfaceAttributesBuilder::::new().with_srgb(Some(false)).build( + raw_window_handle, + NonZeroU32::new(size.width).unwrap(), + NonZeroU32::new(size.height).unwrap(), + ); + + // Create the GL surface to draw into. + unsafe { gl_display.create_window_surface(&gl_config, &surface_attributes) } +} diff --git a/alacritty/src/scheduler.rs b/alacritty/src/scheduler.rs index 08388b94..3a26322f 100644 --- a/alacritty/src/scheduler.rs +++ b/alacritty/src/scheduler.rs @@ -3,8 +3,8 @@ use std::collections::VecDeque; use std::time::{Duration, Instant}; -use glutin::event_loop::EventLoopProxy; -use glutin::window::WindowId; +use winit::event_loop::EventLoopProxy; +use winit::window::WindowId; use crate::event::Event; @@ -80,7 +80,7 @@ impl Scheduler { .timers .iter() .position(|timer| timer.deadline > deadline) - .unwrap_or_else(|| self.timers.len()); + .unwrap_or(self.timers.len()); // Set the automatic event repeat rate. let interval = if repeat { Some(interval) } else { None }; diff --git a/alacritty/src/window_context.rs b/alacritty/src/window_context.rs index 45adf8b8..833586ea 100644 --- a/alacritty/src/window_context.rs +++ b/alacritty/src/window_context.rs @@ -12,13 +12,19 @@ use std::sync::atomic::Ordering; use std::sync::Arc; use crossfont::Size; -use glutin::event::{Event as GlutinEvent, ModifiersState, WindowEvent}; -use glutin::event_loop::{EventLoopProxy, EventLoopWindowTarget}; -use glutin::window::WindowId; +use glutin::config::GetGlConfig; +use glutin::context::NotCurrentContext; +use glutin::display::GetGlDisplay; +#[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] +use glutin::platform::x11::X11GlConfigExt; use log::{error, info}; +use raw_window_handle::HasRawDisplayHandle; use serde_json as json; #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] use wayland_client::EventQueue; +use winit::event::{Event as WinitEvent, ModifiersState, WindowEvent}; +use winit::event_loop::{EventLoopProxy, EventLoopWindowTarget}; +use winit::window::WindowId; use alacritty_config::SerdeReplace; use alacritty_terminal::event::Event as TerminalEvent; @@ -35,18 +41,19 @@ use crate::cli::IpcConfig; use crate::cli::WindowOptions; use crate::clipboard::Clipboard; use crate::config::UiConfig; +use crate::display::window::Window; use crate::display::Display; use crate::event::{ActionContext, Event, EventProxy, EventType, Mouse, SearchState}; -use crate::input; use crate::logging::LOG_TARGET_IPC_CONFIG; use crate::message_bar::MessageBuffer; use crate::scheduler::Scheduler; +use crate::{input, renderer}; /// Event context for one individual Alacritty window. pub struct WindowContext { pub message_buffer: MessageBuffer, pub display: Display, - event_queue: Vec>, + event_queue: Vec>, terminal: Arc>>, cursor_blink_timed_out: bool, modifiers: ModifiersState, @@ -68,32 +75,109 @@ pub struct WindowContext { } impl WindowContext { - /// Create a new terminal window context. - pub fn new( - config: Rc, - options: &WindowOptions, - window_event_loop: &EventLoopWindowTarget, + /// Create initial window context that dous bootstrapping the graphics Api we're going to use. + pub fn initial( + event_loop: &EventLoopWindowTarget, proxy: EventLoopProxy, + config: Rc, + options: WindowOptions, #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] wayland_event_queue: Option<&EventQueue>, + ) -> Result> { + let raw_display_handle = event_loop.raw_display_handle(); + + let mut identity = config.window.identity.clone(); + options.window_identity.override_identity_config(&mut identity); + + // Windows has different order of GL platform initialization compared to any other platform; + // it requires the window first. + #[cfg(windows)] + let window = Window::new(event_loop, &config, &identity)?; + #[cfg(windows)] + let raw_window_handle = Some(window.raw_window_handle()); + + #[cfg(not(windows))] + let raw_window_handle = None; + + let gl_display = + renderer::platform::create_gl_display(raw_display_handle, raw_window_handle)?; + let gl_config = renderer::platform::pick_gl_config(&gl_display, raw_window_handle)?; + + #[cfg(not(windows))] + let window = Window::new( + event_loop, + &config, + &identity, + #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] + wayland_event_queue, + #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] + gl_config.x11_visual(), + )?; + + // Create context. + let gl_context = + renderer::platform::create_gl_context(&gl_display, &gl_config, raw_window_handle)?; + + Self::new(window, gl_context, config, options, proxy) + } + + /// Create additional context with the graphics platform other windows are using. + pub fn additional( + &self, + event_loop: &EventLoopWindowTarget, + proxy: EventLoopProxy, + config: Rc, + options: WindowOptions, + #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] + wayland_event_queue: Option<&EventQueue>, + ) -> Result> { + // Get any window and take its GL config and display to build a new context. + let (gl_display, gl_config) = { + let gl_context = self.display.gl_context(); + (gl_context.display(), gl_context.config()) + }; + + let mut identity = config.window.identity.clone(); + options.window_identity.override_identity_config(&mut identity); + + let window = Window::new( + event_loop, + &config, + &identity, + #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] + wayland_event_queue, + #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] + gl_config.x11_visual(), + )?; + + // Create context. + let raw_window_handle = window.raw_window_handle(); + let gl_context = renderer::platform::create_gl_context( + &gl_display, + &gl_config, + Some(raw_window_handle), + )?; + + Self::new(window, gl_context, config, options, proxy) + } + + /// Create a new terminal window context. + fn new( + window: Window, + context: NotCurrentContext, + config: Rc, + options: WindowOptions, + proxy: EventLoopProxy, ) -> Result> { let mut pty_config = config.terminal_config.pty_config.clone(); options.terminal_options.override_pty_config(&mut pty_config); - let mut identity = config.window.identity.clone(); let preserve_title = options.window_identity.title.is_some(); - options.window_identity.override_identity_config(&mut identity); // Create a display. // // The display manages a window and can draw the terminal. - let display = Display::new( - &config, - window_event_loop, - &identity, - #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] - wayland_event_queue, - )?; + let display = Display::new(window, context, &config)?; info!( "PTY dimensions: {:?} x {:?}", @@ -307,17 +391,17 @@ impl WindowContext { event_proxy: &EventLoopProxy, clipboard: &mut Clipboard, scheduler: &mut Scheduler, - event: GlutinEvent<'_, Event>, + event: WinitEvent<'_, Event>, ) { match event { // Skip further event handling with no staged updates. - GlutinEvent::RedrawEventsCleared if self.event_queue.is_empty() && !self.dirty => { + WinitEvent::RedrawEventsCleared if self.event_queue.is_empty() && !self.dirty => { return; }, // Continue to process all pending events. - GlutinEvent::RedrawEventsCleared => (), + WinitEvent::RedrawEventsCleared => (), // Remap scale_factor change event to remove the lifetime. - GlutinEvent::WindowEvent { + WinitEvent::WindowEvent { event: WindowEvent::ScaleFactorChanged { scale_factor, new_inner_size }, window_id, } => { @@ -396,7 +480,7 @@ impl WindowContext { // Skip rendering on Wayland until we get frame event from compositor. #[cfg(not(any(target_os = "macos", windows)))] - if !self.display.is_x11 && !self.display.window.should_draw.load(Ordering::Relaxed) { + if self.display.is_wayland && !self.display.window.should_draw.load(Ordering::Relaxed) { return; } diff --git a/alacritty_config/Cargo.toml b/alacritty_config/Cargo.toml index dde2d9ac..c2e25f4f 100644 --- a/alacritty_config/Cargo.toml +++ b/alacritty_config/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT/Apache-2.0" description = "Alacritty configuration abstractions" homepage = "https://github.com/alacritty/alacritty" edition = "2021" -rust-version = "1.57.0" +rust-version = "1.60.0" [dependencies] log = { version = "0.4.17", features = ["serde"] } diff --git a/alacritty_config_derive/Cargo.toml b/alacritty_config_derive/Cargo.toml index f01899b5..38de4467 100644 --- a/alacritty_config_derive/Cargo.toml +++ b/alacritty_config_derive/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT/Apache-2.0" description = "Failure resistant deserialization derive" homepage = "https://github.com/alacritty/alacritty" edition = "2021" -rust-version = "1.57.0" +rust-version = "1.60.0" [lib] proc-macro = true diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml index 90fee671..9f3455a8 100644 --- a/alacritty_terminal/Cargo.toml +++ b/alacritty_terminal/Cargo.toml @@ -7,7 +7,7 @@ description = "Library for writing terminal emulators" readme = "../README.md" homepage = "https://github.com/alacritty/alacritty" edition = "2021" -rust-version = "1.57.0" +rust-version = "1.60.0" [dependencies.alacritty_config_derive] path = "../alacritty_config_derive"