From 04f0bcaf54ed373128ca0f84ee8fcdd8e52bce23 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Mon, 4 May 2020 02:29:11 +0300 Subject: [PATCH] Use frame callbacks instead of vsync on Wayland Instead of blocking on vsync, Alacritty now requests a notification from wayland about when the next frame should be rendered. this helps with input latency, since it gives alacritty more time to process events before a redraw. it also prevents alacritty from drawing unless the compositor tells it to do so. Fixes #2851. --- CHANGELOG.md | 1 + Cargo.lock | 62 ++++++++++++++++ alacritty/Cargo.toml | 1 + alacritty/src/display.rs | 154 ++++++++++++++++++++++++++------------- alacritty/src/event.rs | 65 +++++++++++++---- alacritty/src/window.rs | 67 ++++++++++++++--- 6 files changed, 275 insertions(+), 75 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c99e07b..eda674f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fallback to `LC_CTYPE=UTF-8` on macOS without valid system locale - Resize lag on launch under some X11 wms - Increased input latency due to vsync behavior on X11 +- Freeze when application is invisible on Wayland ## 0.4.2 diff --git a/Cargo.lock b/Cargo.lock index 1904799..bb7bae2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,7 @@ dependencies = [ "time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "urlocator 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-client 0.26.3 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "x11-dl 2.18.5 (registry+https://github.com/rust-lang/crates.io-index)", "xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1195,6 +1196,11 @@ dependencies = [ "objc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "once_cell" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "openssl" version = "0.10.29" @@ -1532,6 +1538,11 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "scopeguard" version = "1.1.0" @@ -1949,6 +1960,21 @@ dependencies = [ "wayland-sys 0.23.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "wayland-client" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "downcast-rs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", + "scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-commons 0.26.3 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-scanner 0.26.3 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-sys 0.26.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "wayland-commons" version = "0.23.6" @@ -1958,6 +1984,17 @@ dependencies = [ "wayland-sys 0.23.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "wayland-commons" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", + "once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wayland-sys 0.26.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "wayland-protocols" version = "0.23.6" @@ -1979,6 +2016,16 @@ dependencies = [ "xml-rs 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "wayland-scanner" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "xml-rs 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "wayland-sys" version = "0.23.6" @@ -1988,6 +2035,15 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "wayland-sys" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "dlib 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "which" version = "3.1.1" @@ -2295,6 +2351,7 @@ dependencies = [ "checksum objc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" "checksum objc-foundation 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" "checksum objc_id 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +"checksum once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" "checksum openssl 0.10.29 (registry+https://github.com/rust-lang/crates.io-index)" = "cee6d85f4cb4c4f59a6a85d5b68a233d280c82e29e822913b9c8b129fbf20bdd" "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" "checksum openssl-sys 0.9.55 (registry+https://github.com/rust-lang/crates.io-index)" = "7717097d810a0f2e2323f9e5d11e71608355e24828410b55b9d4f18aa5f9a5d8" @@ -2337,6 +2394,7 @@ dependencies = [ "checksum ryu 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" "checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" "checksum schannel 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "039c25b130bd8c1321ee2d7de7fde2659fa9c2744e4bb29711cfc852ea53cd19" +"checksum scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" "checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" "checksum security-framework 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "572dfa3a0785509e7a44b5b4bebcf94d41ba34e9ed9eb9df722545c3b3c4144a" "checksum security-framework-sys 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8ddb15a5fec93b7021b8a9e96009c5d8d51c15673569f7c0f6b7204e5b7b404f" @@ -2389,10 +2447,14 @@ dependencies = [ "checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" "checksum wayland-client 0.23.6 (registry+https://github.com/rust-lang/crates.io-index)" = "af1080ebe0efabcf12aef2132152f616038f2d7dcbbccf7b2d8c5270fe14bcda" +"checksum wayland-client 0.26.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6eda8c06906f6572f840930588ce28f021485bc9679af0191aeb753ad6c7b46b" "checksum wayland-commons 0.23.6 (registry+https://github.com/rust-lang/crates.io-index)" = "bb66b0d1a27c39bbce712b6372131c6e25149f03ffb0cd017cf8f7de8d66dbdb" +"checksum wayland-commons 0.26.3 (registry+https://github.com/rust-lang/crates.io-index)" = "951aaed76c01185bff7ec68426769c94e88b36113dfe73f8e2356feaa5f09c1f" "checksum wayland-protocols 0.23.6 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc286643656742777d55dc8e70d144fa4699e426ca8e9d4ef454f4bf15ffcf9" "checksum wayland-scanner 0.23.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93b02247366f395b9258054f964fe293ddd019c3237afba9be2ccbe9e1651c3d" +"checksum wayland-scanner 0.26.3 (registry+https://github.com/rust-lang/crates.io-index)" = "21ae379761543b2f41ce88010d0f4ee489218c8e8cfa42dd8f070fdb263aa971" "checksum wayland-sys 0.23.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d94e89a86e6d6d7c7c9b19ebf48a03afaac4af6bc22ae570e9a24124b75358f4" +"checksum wayland-sys 0.26.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fab1f1d2ab35838c5fcbbeebee42cf15721c5fdc1add1f13d0e70e219b94e013" "checksum which 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml index 97a69a7..5e390fa 100644 --- a/alacritty/Cargo.toml +++ b/alacritty/Cargo.toml @@ -41,6 +41,7 @@ dirs = "2.0.2" [target.'cfg(not(any(target_os="windows", target_os="macos")))'.dependencies] x11-dl = "2" +wayland-client = { version = "0.26", features = ["dlopen"] } [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.7", features = ["impl-default", "wincon"]} diff --git a/alacritty/src/display.rs b/alacritty/src/display.rs index 980b0ea..aabd263 100644 --- a/alacritty/src/display.rs +++ b/alacritty/src/display.rs @@ -16,6 +16,8 @@ //! GPU drawing. use std::f64; use std::fmt::{self, Formatter}; +#[cfg(not(any(target_os = "macos", windows)))] +use std::sync::atomic::Ordering; use std::time::Instant; use glutin::dpi::{PhysicalPosition, PhysicalSize}; @@ -26,6 +28,8 @@ use glutin::platform::unix::EventLoopWindowTargetExtUnix; use glutin::window::CursorIcon; use log::{debug, info}; use parking_lot::MutexGuard; +#[cfg(not(any(target_os = "macos", windows)))] +use wayland_client::{Display as WaylandDisplay, EventQueue}; use font::{self, Rasterize}; @@ -47,16 +51,16 @@ use crate::window::{self, Window}; #[derive(Debug)] pub enum Error { - /// Error with window management + /// Error with window management. Window(window::Error), - /// Error dealing with fonts + /// Error dealing with fonts. Font(font::Error), - /// Error in renderer + /// Error in renderer. Render(renderer::Error), - /// Error during buffer swap + /// Error during buffer swap. ContextError(glutin::ContextError), } @@ -106,7 +110,7 @@ impl From for Error { } } -/// The display wraps a window, font rasterizer, and GPU renderer +/// The display wraps a window, font rasterizer, and GPU renderer. pub struct Display { pub size_info: SizeInfo, pub window: Window, @@ -115,6 +119,9 @@ pub struct Display { /// Currently highlighted URL. pub highlighted_url: Option, + #[cfg(not(any(target_os = "macos", windows)))] + pub wayland_event_queue: Option, + renderer: QuadRenderer, glyph_cache: GlyphCache, meter: Meter, @@ -124,11 +131,11 @@ pub struct Display { impl Display { pub fn new(config: &Config, event_loop: &EventLoop) -> Result { - // Guess DPR based on first monitor + // Guess DPR based on first monitor. let estimated_dpr = event_loop.available_monitors().next().map(|m| m.scale_factor()).unwrap_or(1.); - // Guess the target window dimensions + // Guess the target window dimensions. let metrics = GlyphCache::static_metrics(config.font.clone(), estimated_dpr)?; let (cell_width, cell_height) = compute_cell_size(config, &metrics); let dimensions = @@ -138,19 +145,37 @@ impl Display { debug!("Estimated Cell Size: {} x {}", cell_width, cell_height); debug!("Estimated Dimensions: {:?}", dimensions); - // Create the window where Alacritty will be displayed + #[cfg(not(any(target_os = "macos", windows)))] + let mut wayland_event_queue = None; + + // Initialize Wayland event queue, to handle Wayland callbacks. + #[cfg(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()); + } + } + + // Create the window where Alacritty will be displayed. let size = dimensions.map(|(width, height)| PhysicalSize::new(width, height)); - // Spawn window - let mut window = Window::new(event_loop, &config, size)?; + // Spawn window. + let mut window = Window::new( + event_loop, + &config, + size, + #[cfg(not(any(target_os = "macos", windows)))] + wayland_event_queue.as_ref(), + )?; let dpr = window.scale_factor(); info!("Device pixel ratio: {}", dpr); - // get window properties for initializing the other subsystems + // get window properties for initializing the other subsystems. let viewport_size = window.inner_size(); - // Create renderer + // Create renderer. let mut renderer = QuadRenderer::new()?; let (glyph_cache, cell_width, cell_height) = @@ -169,7 +194,7 @@ impl Display { window.set_inner_size(PhysicalSize::new(width, height)); } } else if config.window.dynamic_padding { - // Make sure additional padding is spread evenly + // Make sure additional padding is spread evenly. padding_x = dynamic_padding(padding_x, viewport_size.width as f32, cell_width); padding_y = dynamic_padding(padding_y, viewport_size.height as f32, cell_height); } @@ -180,7 +205,7 @@ impl Display { info!("Cell Size: {} x {}", cell_width, cell_height); info!("Padding: {} x {}", padding_x, padding_y); - // Create new size with at least one column and row + // Create new size with at least one column and row. let size_info = SizeInfo { dpr, width: (viewport_size.width as f32).max(cell_width + 2. * padding_x), @@ -191,10 +216,10 @@ impl Display { padding_y, }; - // Update OpenGL projection + // Update OpenGL projection. renderer.resize(&size_info); - // Clear screen + // Call `clear` before showing the window, to make sure the surface is initialized. let background_color = config.colors.primary.background; renderer.with_api(&config, &size_info, |api| { api.clear(background_color); @@ -203,12 +228,10 @@ impl Display { #[cfg(not(any(target_os = "macos", windows)))] let is_x11 = event_loop.is_x11(); - // We should call `clear` when window is offscreen, so when `window.show()` happens it - // would be with background color instead of uninitialized surface. #[cfg(not(any(target_os = "macos", windows)))] { // On Wayland we can safely ignore this call, since the window isn't visible until you - // actually draw something into it. + // actually draw something into it and commit those changes. if is_x11 { window.swap_buffers(); renderer.with_api(&config, &size_info, |api| { @@ -219,10 +242,10 @@ impl Display { window.set_visible(true); - // Set window position + // Set window position. // // TODO: replace `set_position` with `with_position` once available - // Upstream issue: https://github.com/rust-windowing/winit/issues/806 + // Upstream issue: https://github.com/rust-windowing/winit/issues/806. if let Some(position) = config.window.position { window.set_outer_position(PhysicalPosition::from((position.x, position.y))); } @@ -247,6 +270,8 @@ impl Display { highlighted_url: None, #[cfg(not(any(target_os = "macos", windows)))] is_x11, + #[cfg(not(any(target_os = "macos", windows)))] + wayland_event_queue, }) } @@ -258,7 +283,7 @@ impl Display { let font = config.font.clone(); let rasterizer = font::Rasterizer::new(dpr as f32, config.font.use_thin_strokes())?; - // Initialize glyph cache + // Initialize glyph cache. let glyph_cache = { info!("Initializing glyph cache..."); let init_start = Instant::now(); @@ -281,7 +306,7 @@ impl Display { Ok((glyph_cache, cw, ch)) } - /// Update font size and cell dimensions + /// Update font size and cell dimensions. fn update_glyph_cache(&mut self, config: &Config, font: Font) { let size_info = &mut self.size_info; let cache = &mut self.glyph_cache; @@ -290,7 +315,7 @@ impl Display { let _ = cache.update_font_size(font, size_info.dpr, &mut api); }); - // Update cell size + // Update cell size. let (cell_width, cell_height) = compute_cell_size(config, &self.glyph_cache.font_metrics()); size_info.cell_width = cell_width; size_info.cell_height = cell_height; @@ -313,7 +338,7 @@ impl Display { config: &Config, update_pending: DisplayUpdate, ) { - // Update font size and cell dimensions + // Update font size and cell dimensions. if let Some(font) = update_pending.font { self.update_glyph_cache(config, font); } else if update_pending.cursor { @@ -323,18 +348,18 @@ impl Display { let cell_width = self.size_info.cell_width; let cell_height = self.size_info.cell_height; - // Recalculate padding + // Recalculate padding. let mut padding_x = f32::from(config.window.padding.x) * self.size_info.dpr as f32; let mut padding_y = f32::from(config.window.padding.y) * self.size_info.dpr as f32; - // Update the window dimensions + // Update the window dimensions. if let Some(size) = update_pending.dimensions { - // Ensure we have at least one column and row + // Ensure we have at least one column and row. self.size_info.width = (size.width as f32).max(cell_width + 2. * padding_x); self.size_info.height = (size.height as f32).max(cell_height + 2. * padding_y); } - // Distribute excess padding equally on all sides + // Distribute excess padding equally on all sides. if config.window.dynamic_padding { padding_x = dynamic_padding(padding_x, self.size_info.width, cell_width); padding_y = dynamic_padding(padding_y, self.size_info.height, cell_height); @@ -345,29 +370,29 @@ impl Display { let mut pty_size = self.size_info; - // Subtract message bar lines from pty size + // Subtract message bar lines from pty size. if let Some(message) = message_buffer.message() { let lines = message.text(&self.size_info).len(); pty_size.height -= pty_size.cell_height * lines as f32; } - // Resize PTY + // Resize PTY. pty_resize_handle.on_resize(&pty_size); - // Resize terminal + // Resize terminal. terminal.resize(&pty_size); - // Resize renderer + // Resize renderer. let physical = PhysicalSize::new(self.size_info.width as u32, self.size_info.height as u32); self.window.resize(physical); self.renderer.resize(&self.size_info); } - /// Draw the screen + /// Draw the screen. /// /// A reference to Term whose state is being drawn must be provided. /// - /// This call may block if vsync is enabled + /// This call may block if vsync is enabled. pub fn draw( &mut self, terminal: MutexGuard<'_, Term>, @@ -393,11 +418,11 @@ impl Display { None }; - // Update IME position + // Update IME position. #[cfg(not(windows))] self.window.update_ime_position(&terminal, &self.size_info); - // Drop terminal as early as possible to free lock + // Drop terminal as early as possible to free lock. drop(terminal); self.renderer.with_api(&config, &size_info, |api| { @@ -407,20 +432,20 @@ impl Display { let mut lines = RenderLines::new(); let mut urls = Urls::new(); - // Draw grid + // Draw grid. { let _sampler = self.meter.sampler(); self.renderer.with_api(&config, &size_info, |mut api| { - // Iterate over all non-empty cells in the grid + // Iterate over all non-empty cells in the grid. for cell in grid_cells { - // Update URL underlines + // Update URL underlines. urls.update(size_info.cols().0, cell); - // Update underline/strikeout + // Update underline/strikeout. lines.update(cell); - // Draw the cell + // Draw the cell. api.render_cell(cell, glyph_cache); } }); @@ -428,7 +453,7 @@ impl Display { let mut rects = lines.rects(&metrics, &size_info); - // Update visible URLs + // Update visible URLs. self.urls = urls; if let Some(url) = self.urls.highlighted(config, mouse, mods, mouse_mode, selection) { rects.append(&mut url.rects(&metrics, &size_info)); @@ -446,14 +471,14 @@ impl Display { } } - // Highlight URLs at the vi mode cursor position + // Highlight URLs at the vi mode cursor position. if let Some(vi_mode_cursor) = vi_mode_cursor { if let Some(url) = self.urls.find_at(vi_mode_cursor.point) { rects.append(&mut url.rects(&metrics, &size_info)); } } - // Push visual bell after url/underline/strikeout rects + // Push visual bell after url/underline/strikeout rects. if visual_bell_intensity != 0. { let visual_bell_rect = RenderRect::new( 0., @@ -469,19 +494,19 @@ impl Display { if let Some(message) = message_buffer.message() { let text = message.text(&size_info); - // Create a new rectangle for the background + // Create a new rectangle for the background. let start_line = size_info.lines().0 - text.len(); let y = size_info.cell_height.mul_add(start_line as f32, size_info.padding_y); let message_bar_rect = RenderRect::new(0., y, size_info.width, size_info.height - y, message.color(), 1.); - // Push message_bar in the end, so it'll be above all other content + // Push message_bar in the end, so it'll be above all other content. rects.push(message_bar_rect); - // Draw rectangles + // Draw rectangles. self.renderer.draw_rects(&size_info, rects); - // Relay messages to the user + // Relay messages to the user. let mut offset = 1; for message_text in text.iter().rev() { self.renderer.with_api(&config, &size_info, |mut api| { @@ -495,11 +520,11 @@ impl Display { offset += 1; } } else { - // Draw rectangles + // Draw rectangles. self.renderer.draw_rects(&size_info, rects); } - // Draw render timer + // Draw render timer. if config.render_timer() { let timing = format!("{:.3} usec", self.meter.average()); let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 }; @@ -508,6 +533,11 @@ impl Display { }); } + // Frame event should be requested before swaping buffers, since it requires surface + // `commit`, which is done by swap buffers under the hood. + #[cfg(not(any(target_os = "macos", windows)))] + self.request_frame(&self.window); + self.window.swap_buffers(); #[cfg(not(any(target_os = "macos", windows)))] @@ -522,9 +552,29 @@ impl Display { } } } + + /// Requst a new frame for a window on Wayland. + #[inline] + #[cfg(not(any(target_os = "macos", windows)))] + fn request_frame(&self, window: &Window) { + let surface = match window.wayland_surface() { + Some(surface) => surface, + None => return, + }; + + let should_draw = self.window.should_draw.clone(); + + // Mark that window was drawn. + should_draw.store(false, Ordering::Relaxed); + + // Request a new frame. + surface.frame().quick_assign(move |_, _, _| { + should_draw.store(true, Ordering::Relaxed); + }); + } } -/// Calculate padding to spread it evenly around the terminal content +/// Calculate padding to spread it evenly around the terminal content. #[inline] fn dynamic_padding(padding: f32, dimension: f32, cell_dimension: f32) -> f32 { padding + ((dimension - 2. * padding) % cell_dimension) / 2. diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index 3f2e3f7..94c9a73 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -8,6 +8,8 @@ use std::fs::File; use std::io::Write; use std::mem; use std::path::PathBuf; +#[cfg(not(any(target_os = "macos", windows)))] +use std::sync::atomic::Ordering; use std::sync::Arc; use std::time::Instant; @@ -358,6 +360,7 @@ pub struct Processor { message_buffer: MessageBuffer, display: Display, font_size: Size, + event_queue: Vec>, } impl Processor { @@ -381,41 +384,65 @@ impl Processor { config, message_buffer, display, + event_queue: Vec::new(), } } + /// Return `true` if `event_queue` is empty, `false` otherwise. + #[inline] + #[cfg(not(any(target_os = "macos", windows)))] + fn event_queue_empty(&mut self) -> bool { + let wayland_event_queue = match self.display.wayland_event_queue.as_mut() { + Some(wayland_event_queue) => wayland_event_queue, + // Since frame callbacks do not exist on X11, just check for event queue. + None => return self.event_queue.is_empty(), + }; + + // Check for pending frame callbacks on Wayland. + let events_dispatched = wayland_event_queue + .dispatch_pending(&mut (), |_, _, _| {}) + .expect("failed to dispatch event queue"); + + self.event_queue.is_empty() && events_dispatched == 0 + } + + /// Return `true` if `event_queue` is empty, `false` otherwise. + #[inline] + #[cfg(any(target_os = "macos", windows))] + fn event_queue_empty(&mut self) -> bool { + self.event_queue.is_empty() + } + /// Run the event loop. pub fn run(&mut self, terminal: Arc>>, mut event_loop: EventLoop) where T: EventListener, { - let mut event_queue = Vec::new(); - event_loop.run_return(|event, event_loop, control_flow| { if self.config.debug.print_events { info!("glutin event: {:?}", event); } - // Ignore all events we do not care about + // Ignore all events we do not care about. if Self::skip_event(&event) { return; } match event { - // Check for shutdown + // Check for shutdown. GlutinEvent::UserEvent(Event::Exit) => { *control_flow = ControlFlow::Exit; return; }, - // Process events + // Process events. GlutinEvent::RedrawEventsCleared => { *control_flow = ControlFlow::Wait; - if event_queue.is_empty() { + if self.event_queue_empty() { return; } }, - // Remap DPR change event to remove lifetime + // Remap DPR change event to remove lifetime. GlutinEvent::WindowEvent { event: WindowEvent::ScaleFactorChanged { scale_factor, new_inner_size }, .. @@ -423,14 +450,14 @@ impl Processor { *control_flow = ControlFlow::Poll; let size = (new_inner_size.width, new_inner_size.height); let event = GlutinEvent::UserEvent(Event::DPRChanged(scale_factor, size)); - event_queue.push(event); + self.event_queue.push(event); return; }, // Transmute to extend lifetime, which exists only for `ScaleFactorChanged` event. // Since we remap that event to remove the lifetime, this is safe. event => unsafe { *control_flow = ControlFlow::Poll; - event_queue.push(mem::transmute(event)); + self.event_queue.push(mem::transmute(event)); return; }, } @@ -457,11 +484,11 @@ impl Processor { }; let mut processor = input::Processor::new(context, &self.display.highlighted_url); - for event in event_queue.drain(..) { + for event in self.event_queue.drain(..) { Processor::handle_event(event, &mut processor); } - // Process DisplayUpdate events + // Process DisplayUpdate events. if !display_update_pending.is_empty() { self.display.handle_update( &mut terminal, @@ -472,15 +499,25 @@ impl Processor { ); } + #[cfg(not(any(target_os = "macos", windows)))] + { + // Skip rendering on Wayland until we get frame event from compositor. + if event_loop.is_wayland() + && !self.display.window.should_draw.load(Ordering::Relaxed) + { + return; + } + } + if terminal.dirty { terminal.dirty = false; - // Request immediate re-draw if visual bell animation is not finished yet + // Request immediate re-draw if visual bell animation is not finished yet. if !terminal.visual_bell.completed() { - event_queue.push(GlutinEvent::UserEvent(Event::Wakeup)); + self.event_queue.push(GlutinEvent::UserEvent(Event::Wakeup)); } - // Redraw screen + // Redraw screen. self.display.draw( terminal, &self.message_buffer, diff --git a/alacritty/src/window.rs b/alacritty/src/window.rs index 9083fa3..dfb8517 100644 --- a/alacritty/src/window.rs +++ b/alacritty/src/window.rs @@ -17,6 +17,10 @@ use std::ffi::c_void; use std::fmt::{self, Display, Formatter}; #[cfg(not(any(target_os = "macos", windows)))] use std::os::raw::c_ulong; +#[cfg(not(any(target_os = "macos", windows)))] +use std::sync::atomic::AtomicBool; +#[cfg(not(any(target_os = "macos", windows)))] +use std::sync::Arc; use glutin::dpi::{PhysicalPosition, PhysicalSize}; use glutin::event_loop::EventLoop; @@ -51,6 +55,12 @@ use crate::gl; #[cfg(not(any(target_os = "macos", windows)))] use crate::wayland_theme::AlacrittyWaylandTheme; +#[cfg(not(any(target_os = "macos", windows)))] +use wayland_client::{Attached, EventQueue, Proxy}; + +#[cfg(not(any(target_os = "macos", windows)))] +use wayland_client::protocol::wl_surface::WlSurface; + // It's required to be in this directory due to the `windows.rc` file #[cfg(not(any(target_os = "macos", windows)))] static WINDOW_ICON: &[u8] = include_bytes!("../../extra/windows/alacritty.ico"); @@ -117,6 +127,7 @@ fn create_gl_window( mut window: WindowBuilder, event_loop: &EventLoop, srgb: bool, + vsync: bool, dimensions: Option>, ) -> Result> { if let Some(dimensions) = dimensions { @@ -125,7 +136,7 @@ fn create_gl_window( let windowed_context = ContextBuilder::new() .with_srgb(srgb) - .with_vsync(true) + .with_vsync(vsync) .with_hardware_acceleration(None) .build_windowed(window, event_loop)?; @@ -135,10 +146,18 @@ fn create_gl_window( Ok(windowed_context) } -/// A window which can be used for displaying the terminal +/// A window which can be used for displaying the terminal. /// -/// Wraps the underlying windowing library to provide a stable API in Alacritty +/// Wraps the underlying windowing library to provide a stable API in Alacritty. pub struct Window { + /// Flag tracking frame redraw requests from Wayland compositor. + #[cfg(not(any(target_os = "macos", windows)))] + pub should_draw: Arc, + + /// Attached Wayland surface to request new frame events. + #[cfg(not(any(target_os = "macos", windows)))] + pub wayland_surface: Option>, + windowed_context: WindowedContext, current_mouse_cursor: CursorIcon, mouse_visible: bool, @@ -152,33 +171,58 @@ impl Window { event_loop: &EventLoop, config: &Config, size: Option>, + #[cfg(not(any(target_os = "macos", windows)))] wayland_event_queue: Option<&EventQueue>, ) -> Result { let window_builder = Window::get_platform_window(&config.window.title, &config.window); - let windowed_context = - create_gl_window(window_builder.clone(), &event_loop, false, size) - .or_else(|_| create_gl_window(window_builder, &event_loop, true, size))?; - // Text cursor + // Disable vsync on Wayland. + #[cfg(not(any(target_os = "macos", windows)))] + let vsync = !event_loop.is_wayland(); + #[cfg(any(target_os = "macos", windows))] + let vsync = true; + + let windowed_context = + create_gl_window(window_builder.clone(), &event_loop, false, vsync, size) + .or_else(|_| create_gl_window(window_builder, &event_loop, true, vsync, size))?; + + // Text cursor. let current_mouse_cursor = CursorIcon::Text; windowed_context.window().set_cursor_icon(current_mouse_cursor); // 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 _); - // On X11, embed the window inside another if the parent ID has been set + #[cfg(not(any(target_os = "macos", windows)))] + let mut wayland_surface = None; + #[cfg(not(any(target_os = "macos", windows)))] { if event_loop.is_x11() { + // 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); } } else { + // Apply client side decorations theme. let theme = AlacrittyWaylandTheme::new(&config.colors); windowed_context.window().set_wayland_theme(theme); + + // Attach surface to Alacritty's internal wayland queue to handle frame callbacks. + let surface = windowed_context.window().wayland_surface().unwrap(); + let proxy: Proxy = unsafe { Proxy::from_c_ptr(surface as _) }; + wayland_surface = Some(proxy.attach(wayland_event_queue.as_ref().unwrap().token())); } } - Ok(Self { current_mouse_cursor, mouse_visible: true, windowed_context }) + Ok(Self { + current_mouse_cursor, + mouse_visible: true, + windowed_context, + #[cfg(not(any(target_os = "macos", windows)))] + should_draw: Arc::new(AtomicBool::new(true)), + #[cfg(not(any(target_os = "macos", windows)))] + wayland_surface, + }) } pub fn set_inner_size(&mut self, size: PhysicalSize) { @@ -363,6 +407,11 @@ impl Window { self.window().wayland_display() } + #[cfg(not(any(target_os = "macos", target_os = "windows")))] + pub fn wayland_surface(&self) -> Option<&Attached> { + self.wayland_surface.as_ref() + } + #[cfg(not(any(target_os = "macos", target_os = "windows")))] pub fn set_wayland_theme(&mut self, colors: &Colors) { self.window().set_wayland_theme(AlacrittyWaylandTheme::new(colors));