diff options
author | Christian Duerr <contact@christianduerr.com> | 2021-10-23 07:16:47 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-23 07:16:47 +0000 |
commit | 1df7dc5171abfe1eab3e95be964f61c5876198f1 (patch) | |
tree | 315ceaa093b86b8e875512825302f38e32f697a4 /alacritty/src/display | |
parent | d8a98f88295e59d6518ae780a9857c033a83161c (diff) | |
download | r-alacritty-1df7dc5171abfe1eab3e95be964f61c5876198f1.tar.gz r-alacritty-1df7dc5171abfe1eab3e95be964f61c5876198f1.tar.bz2 r-alacritty-1df7dc5171abfe1eab3e95be964f61c5876198f1.zip |
Add multi-window support
Previously Alacritty would always initialize only a single terminal
emulator window feeding into the winit event loop, however some
platforms like macOS expect all windows to be spawned by the same
process and this "daemon-mode" can also come with the advantage of
increased memory efficiency.
The event loop has been restructured to handle all window-specific
events only by the event processing context with the associated window
id. This makes it possible to add new terminal windows at any time using
the WindowContext::new function call.
Some preliminary tests have shown that for empty terminals, this reduces
the cost of additional terminal emulators from ~100M to ~6M. However at
this point the robustness of the daemon against issues with individual
terminals has not been refined, making the reliability of this system
questionable.
New windows can be created either by using the new `CreateNewWindow`
action, or with the `alacritty msg create-window` subcommand. The
subcommand sends a message to an IPC socket which Alacritty listens on,
its location can be found in the `ALACRITTY_SOCKET` environment
variable.
Fixes #607.
Diffstat (limited to 'alacritty/src/display')
-rw-r--r-- | alacritty/src/display/mod.rs | 64 | ||||
-rw-r--r-- | alacritty/src/display/window.rs | 68 |
2 files changed, 100 insertions, 32 deletions
diff --git a/alacritty/src/display/mod.rs b/alacritty/src/display/mod.rs index 946c27f9..a942a88d 100644 --- a/alacritty/src/display/mod.rs +++ b/alacritty/src/display/mod.rs @@ -3,15 +3,15 @@ use std::cmp::min; use std::convert::TryFrom; -use std::f64; use std::fmt::{self, Formatter}; #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] use std::sync::atomic::Ordering; use std::time::Instant; +use std::{f64, mem}; use glutin::dpi::{PhysicalPosition, PhysicalSize}; use glutin::event::ModifiersState; -use glutin::event_loop::EventLoop; +use glutin::event_loop::EventLoopWindowTarget; #[cfg(not(any(target_os = "macos", windows)))] use glutin::platform::unix::EventLoopWindowTargetExtUnix; use glutin::window::CursorIcon; @@ -19,7 +19,7 @@ use log::{debug, info}; use parking_lot::MutexGuard; use unicode_width::UnicodeWidthChar; #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] -use wayland_client::{Display as WaylandDisplay, EventQueue}; +use wayland_client::EventQueue; use crossfont::{self, Rasterize, Rasterizer}; @@ -178,9 +178,6 @@ pub struct Display { /// Hint highlighted by the vi mode cursor. pub vi_highlighted_hint: Option<HintMatch>, - #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] - pub wayland_event_queue: Option<EventQueue>, - #[cfg(not(any(target_os = "macos", windows)))] pub is_x11: bool, @@ -195,13 +192,21 @@ pub struct Display { /// State of the keyboard hints. pub hint_state: HintState, + /// Unprocessed display updates. + pub pending_update: DisplayUpdate, + renderer: QuadRenderer, glyph_cache: GlyphCache, meter: Meter, } impl Display { - pub fn new<E>(config: &Config, event_loop: &EventLoop<E>) -> Result<Display, Error> { + pub fn new<E>( + config: &Config, + event_loop: &EventLoopWindowTarget<E>, + #[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))))] @@ -229,23 +234,13 @@ impl Display { debug!("Estimated window size: {:?}", estimated_size); debug!("Estimated cell size: {} x {}", cell_width, cell_height); - #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] - let mut wayland_event_queue = None; - - // Initialize Wayland event queue, to handle Wayland callbacks. - #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] - if let Some(display) = event_loop.wayland_display() { - let display = unsafe { WaylandDisplay::from_external_display(display as _) }; - wayland_event_queue = Some(display.create_event_queue()); - } - // Spawn the Alacritty window. let mut window = Window::new( event_loop, config, estimated_size, #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] - wayland_event_queue.as_ref(), + wayland_event_queue, )?; info!("Device pixel ratio: {}", window.dpr); @@ -344,11 +339,10 @@ impl Display { vi_highlighted_hint: None, #[cfg(not(any(target_os = "macos", windows)))] is_x11, - #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] - wayland_event_queue, cursor_hidden: false, visual_bell: VisualBell::from(&config.ui_config.bell), colors: List::from(&config.ui_config.colors), + pending_update: Default::default(), }) } @@ -414,26 +408,30 @@ impl Display { message_buffer: &MessageBuffer, search_active: bool, config: &Config, - update_pending: DisplayUpdate, ) where T: EventListener, { + let pending_update = mem::take(&mut self.pending_update); + let (mut cell_width, mut cell_height) = (self.size_info.cell_width(), self.size_info.cell_height()); + // Ensure we're modifying the correct OpenGL context. + self.window.make_current(); + // Update font size and cell dimensions. - if let Some(font) = update_pending.font() { + if let Some(font) = pending_update.font() { let cell_dimensions = self.update_glyph_cache(config, font); cell_width = cell_dimensions.0; cell_height = cell_dimensions.1; info!("Cell size: {} x {}", cell_width, cell_height); - } else if update_pending.cursor_dirty() { + } else if pending_update.cursor_dirty() { self.clear_glyph_cache(); } let (mut width, mut height) = (self.size_info.width(), self.size_info.height()); - if let Some(dimensions) = update_pending.dimensions() { + if let Some(dimensions) = pending_update.dimensions() { width = dimensions.width as f32; height = dimensions.height as f32; } @@ -463,8 +461,7 @@ impl Display { terminal.resize(self.size_info); // Resize renderer. - let physical = - PhysicalSize::new(self.size_info.width() as u32, self.size_info.height() as u32); + let physical = PhysicalSize::new(self.size_info.width() as _, self.size_info.height() as _); self.window.resize(physical); self.renderer.resize(&self.size_info); @@ -505,6 +502,9 @@ impl Display { // Drop terminal as early as possible to free lock. drop(terminal); + // Make sure this window's OpenGL context is active. + self.window.make_current(); + self.renderer.with_api(&config.ui_config, &size_info, |api| { api.clear(background_color); }); @@ -515,6 +515,10 @@ impl Display { { let _sampler = self.meter.sampler(); + // Ensure macOS hasn't reset our viewport. + #[cfg(target_os = "macos")] + self.renderer.set_viewport(&size_info); + let glyph_cache = &mut self.glyph_cache; let highlighted_hint = &self.highlighted_hint; let vi_highlighted_hint = &self.vi_highlighted_hint; @@ -819,6 +823,14 @@ 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() + } +} + /// Convert a terminal point to a viewport relative point. pub fn point_to_viewport(display_offset: usize, point: Point) -> Option<Point<usize>> { let viewport_line = point.line.0 + display_offset as i32; diff --git a/alacritty/src/display/window.rs b/alacritty/src/display/window.rs index 12416700..16932dc4 100644 --- a/alacritty/src/display/window.rs +++ b/alacritty/src/display/window.rs @@ -29,11 +29,12 @@ use { }; use std::fmt::{self, Display, Formatter}; +use std::ops::{Deref, DerefMut}; #[cfg(target_os = "macos")] use cocoa::base::{id, NO, YES}; use glutin::dpi::{PhysicalPosition, PhysicalSize}; -use glutin::event_loop::EventLoop; +use glutin::event_loop::EventLoopWindowTarget; #[cfg(target_os = "macos")] use glutin::platform::macos::{WindowBuilderExtMacOS, WindowExtMacOS}; #[cfg(windows)] @@ -124,7 +125,7 @@ impl From<crossfont::Error> for Error { fn create_gl_window<E>( mut window: WindowBuilder, - event_loop: &EventLoop<E>, + event_loop: &EventLoopWindowTarget<E>, srgb: bool, vsync: bool, dimensions: Option<PhysicalSize<u32>>, @@ -160,7 +161,7 @@ pub struct Window { /// Cached DPR for quickly scaling pixel sizes. pub dpr: f64, - windowed_context: WindowedContext<PossiblyCurrent>, + windowed_context: Replaceable<WindowedContext<PossiblyCurrent>>, current_mouse_cursor: CursorIcon, mouse_visible: bool, } @@ -170,7 +171,7 @@ impl Window { /// /// This creates a window and fully initializes a window. pub fn new<E>( - event_loop: &EventLoop<E>, + event_loop: &EventLoopWindowTarget<E>, config: &Config, size: Option<PhysicalSize<u32>>, #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] @@ -232,7 +233,7 @@ impl Window { Ok(Self { current_mouse_cursor, mouse_visible: true, - windowed_context, + windowed_context: Replaceable::new(windowed_context), #[cfg(not(any(target_os = "macos", windows)))] should_draw: Arc::new(AtomicBool::new(true)), #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] @@ -241,10 +242,12 @@ impl Window { }) } + #[inline] pub fn set_inner_size(&mut self, size: PhysicalSize<u32>) { self.window().set_inner_size(size); } + #[inline] pub fn inner_size(&self) -> PhysicalSize<u32> { self.window().inner_size() } @@ -261,6 +264,11 @@ impl Window { } #[inline] + pub fn request_redraw(&self) { + 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; @@ -374,7 +382,7 @@ impl Window { None } - pub fn window_id(&self) -> WindowId { + pub fn id(&self) -> WindowId { self.window().id() } @@ -436,6 +444,13 @@ impl Window { self.windowed_context.resize(size); } + 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") }); + } + } + /// Disable macOS window shadows. /// /// This prevents rendering artifacts from showing up when the window is transparent. @@ -496,3 +511,44 @@ 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() + } +} |