diff options
Diffstat (limited to 'alacritty/src/display')
-rw-r--r-- | alacritty/src/display/damage.rs | 24 | ||||
-rw-r--r-- | alacritty/src/display/hint.rs | 2 | ||||
-rw-r--r-- | alacritty/src/display/mod.rs | 451 | ||||
-rw-r--r-- | alacritty/src/display/window.rs | 310 |
4 files changed, 352 insertions, 435 deletions
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<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(), )?; - // Create renderer. - let mut renderer = Renderer::new()?; - - let scale_factor = window.scale_factor; - info!("Display scale factor: {}", scale_factor); + // Make the context current. + let context = gl_context.make_current(&surface)?; - // 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) - }; + // Create renderer. + 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); 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<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)), @@ -272,25 +203,30 @@ 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() - } -} |