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.
This commit is contained in:
Kirill Chibisov 2022-11-03 19:37:54 +03:00 committed by GitHub
parent 578e08486d
commit 0e418bc2f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 776 additions and 624 deletions

View File

@ -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

View File

@ -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

View File

@ -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: |

View File

@ -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`

View File

@ -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

93
Cargo.lock generated
View File

@ -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"

View File

@ -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 = []

View File

@ -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<usize>;

View File

@ -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;

View File

@ -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::{

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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};

View File

@ -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<renderer::Error> for Error {
}
}
impl From<glutin::ContextError> for Error {
fn from(val: glutin::ContextError) -> Self {
impl From<glutin::error::Error> 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<HintMatch>,
/// Hint highlighted by the vi mode cursor.
pub vi_highlighted_hint: Option<HintMatch>,
#[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<Point>,
is_damage_supported: bool,
renderer: ManuallyDrop<Renderer>,
surface: ManuallyDrop<Surface<WindowSurface>>,
context: ManuallyDrop<Replaceable<PossiblyCurrentContext>>,
debug_damage: bool,
damage_rects: Vec<DamageRect>,
next_frame_damage_rects: Vec<DamageRect>,
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<Preedit>,
}
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<Preedit>) {
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<usize>,
/// The cursor offset from the end of the preedit in char width.
cursor_end_offset: Option<usize>,
}
impl Preedit {
pub fn new(text: String, cursor_byte_offset: Option<usize>) -> 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<E>(
pub fn new(
window: Window,
gl_context: NotCurrentContext,
config: &UiConfig,
event_loop: &EventLoopWindowTarget<E>,
identity: &Identity,
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
wayland_event_queue: Option<&EventQueue>,
) -> Result<Display, Error> {
#[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<Preedit>,
}
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<Preedit>) {
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<usize>,
/// The cursor offset from the end of the preedit in char width.
cursor_end_offset: Option<usize>,
}
impl Preedit {
pub fn new(text: String, cursor_byte_offset: Option<usize>) -> 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<T>(Option<T>);
impl<T> Replaceable<T> {
pub fn new(inner: T) -> Self {
Self(Some(inner))
}
/// Replace the contents of the container.
pub fn replace_with<F: FnMut(T) -> 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<T> Deref for Replaceable<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.get()
}
}
impl<T> DerefMut for Replaceable<T> {
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<u32> {
let padding = config.window.padding(scale_factor);

View File

@ -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<T> = std::result::Result<T, Error>;
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<glutin::CreationError> for Error {
fn from(val: glutin::CreationError) -> Self {
Error::ContextCreation(val)
}
}
impl From<glutin::ContextError> for Error {
fn from(val: glutin::ContextError) -> Self {
Error::Context(val)
impl From<winit::error::OsError> for Error {
fn from(val: winit::error::OsError) -> Self {
Error::WindowCreation(val)
}
}
@ -128,34 +102,6 @@ impl From<crossfont::Error> for Error {
}
}
fn create_gl_window<E>(
mut window: WindowBuilder,
event_loop: &EventLoopWindowTarget<E>,
flags: GlContextFlags,
vsync: bool,
dimensions: Option<PhysicalSize<u32>>,
) -> Result<WindowedContext<PossiblyCurrent>> {
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<WindowedContext<PossiblyCurrent>>,
current_mouse_cursor: CursorIcon,
mouse_visible: bool,
}
@ -187,81 +134,65 @@ impl Window {
event_loop: &EventLoopWindowTarget<E>,
config: &UiConfig,
identity: &Identity,
size: Option<PhysicalSize<u32>>,
#[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<X11VisualInfo>,
) -> Result<Window> {
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::<i32>::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<WlSurface> = 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<u32>) {
self.window().set_inner_size(size);
self.window.set_inner_size(size);
}
#[inline]
pub fn inner_size(&self) -> PhysicalSize<u32> {
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<u32>) {
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<T>(Option<T>);
impl<T> Replaceable<T> {
pub fn new(inner: T) -> Self {
Self(Some(inner))
}
/// Replace the contents of the container.
pub fn replace_with<F: FnMut(T) -> 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<T> Deref for Replaceable<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.get()
}
}
impl<T> DerefMut for Replaceable<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.get_mut()
}
}

View File

@ -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<Event> for GlutinEvent<'_, Event> {
impl From<Event> for WinitEvent<'_, Event> {
fn from(event: Event) -> Self {
GlutinEvent::UserEvent(event)
WinitEvent::UserEvent(event)
}
}
@ -1080,10 +1080,10 @@ impl Mouse {
}
impl input::Processor<EventProxy, ActionContext<'_, Notifier, EventProxy>> {
/// 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<EventProxy, ActionContext<'_, Notifier, EventProxy>> {
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<EventProxy, ActionContext<'_, Notifier, EventProxy>> {
| 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<Event>,
proxy: EventLoopProxy<Event>,
options: WindowOptions,
) -> Result<(), Box<dyn Error>> {
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<Event>,
options: WindowOptions,
) -> Result<(), Box<dyn Error>> {
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,
}
}

View File

@ -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<T: EventListener, A: ActionContext<T>> Processor<T, A> {
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,

View File

@ -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;

View File

@ -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;

View File

@ -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<dyn Error>> {
// Setup glutin event loop.
let window_event_loop = GlutinEventLoopBuilder::<Event>::with_user_event().build();
// Setup winit event loop.
let window_event_loop = WinitEventLoopBuilder::<Event>::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())

View File

@ -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<Self, Error> {
pub fn new(context: &PossiblyCurrentContext) -> Result<Self, Error> {
// 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 _);

View File

@ -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<RawWindowHandle>,
) -> GlutinResult<Display> {
#[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<RawWindowHandle>,
) -> Result<Config, String> {
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<RawWindowHandle>,
) -> GlutinResult<NotCurrentContext> {
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<u32>,
raw_window_handle: RawWindowHandle,
) -> GlutinResult<Surface<WindowSurface>> {
// 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::<WindowSurface>::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) }
}

View File

@ -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 };

View File

@ -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<GlutinEvent<'static, Event>>,
event_queue: Vec<WinitEvent<'static, Event>>,
terminal: Arc<FairMutex<Term<EventProxy>>>,
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<UiConfig>,
options: &WindowOptions,
window_event_loop: &EventLoopWindowTarget<Event>,
/// Create initial window context that dous bootstrapping the graphics Api we're going to use.
pub fn initial(
event_loop: &EventLoopWindowTarget<Event>,
proxy: EventLoopProxy<Event>,
config: Rc<UiConfig>,
options: WindowOptions,
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
wayland_event_queue: Option<&EventQueue>,
) -> Result<Self, Box<dyn Error>> {
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<Event>,
proxy: EventLoopProxy<Event>,
config: Rc<UiConfig>,
options: WindowOptions,
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
wayland_event_queue: Option<&EventQueue>,
) -> Result<Self, Box<dyn Error>> {
// 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<UiConfig>,
options: WindowOptions,
proxy: EventLoopProxy<Event>,
) -> Result<Self, Box<dyn Error>> {
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<Event>,
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;
}

View File

@ -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"] }

View File

@ -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

View File

@ -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"