github raphamorim/rio v0.3.11

latest release: nightly
one day ago
  • Fix wayland freezing.
  • Windows: native MessageBoxW close-confirmation dialog when confirm-before-quit = true (default), mirroring macOS's applicationShouldTerminate NSAlert in rio-window/src/platform_impl/macos/app_delegate.rs. Replaces rio's in-window GPU-rendered confirmation modal on Windows. New EventLoopRunner.confirm_before_quit: Cell<bool> set via the same public EventLoop::set_confirm_before_quit that macOS already uses (cfg gate widened to any(target_os = "macos", target_os = "windows")). The WM_CLOSE handler in event_loop.rs checks the flag and vsync_state.window_count() <= 1 (only the last window prompts — matches macOS's app-wide quit semantics) and on confirm-No swallows the message so the window stays open. rioterm's WindowEvent::CloseRequested handler now follows the same fast path on Windows as on macOS — routes.remove(&window_id) + maybe-exit() — since the user already confirmed at the OS layer.
  • Windows render loop now matches the macOS / CVDisplayLink model exactly: Window::request_redraw sets a per-window Arc<AtomicBool> dirty flag instead of immediately calling RedrawWindow(RDW_INTERNALPAINT). The DwmFlush worker thread (rio-window/src/platform_impl/windows/event_loop/vsync.rs) is the single source of frame timing — per composition cycle it iterates the window registry and, for each window where dirty || should_present_after_input (1 s post-input window, identical to macOS), calls RedrawWindow(.., RDW_INVALIDATE). Existing WM_PAINTRedrawRequested path is unchanged. Plumbing: new vsync::VSyncSharedState (Arc<RwLock<HashMap<HWND, Arc<AtomicBool>>>> registry + Mutex<Instant> for last input) lives on ActiveEventLoop, threaded into WindowData and the Window struct so input handlers, request_redraw, and the worker all share it. Window::Drop unregisters before DESTROY_MSG_ID. Replaces the earlier "always invalidate per vsync" zed mirror — that approach worked but didn't compose with the app's own request_redraw, which made scroll feel locked to DWM tick. Now scroll-driven renders queue immediately into the next vsync (≤16 ms wait) and idle frames are skipped entirely. Net behaviour matches macOS one-to-one.
  • Windows: window.blur = true now actually blurs (was a no-op stub). Implementation calls DwmSetWindowAttribute with DWMWA_SYSTEMBACKDROP_TYPE = DWMSBT_TRANSIENTWINDOW (Acrylic) when blur is on, DWMSBT_NONE when off — uses the existing set_system_backdrop plumbing in rio-window/src/platform_impl/windows/window.rs. Wired both at window creation (via attributes.blur in on_create) and at runtime via the cross-platform Window::set_blur. Requires Windows 11 22H2+; older builds silently no-op (matches the previous behaviour).
  • Windows: vsync-driven render loop with 1-second post-input sustain (matches macOS / Linux / zed behaviour). All inside rio-window — frontends still just respond to RedrawRequested. New worker thread (platform_impl/windows/event_loop/vsync.rs) calls DwmFlush() per composition cycle; when DWM is disabled, the monitor is asleep, or the call returns under the 1 ms threshold (RDP / occluded), it falls back to thread::sleep at the queried DwmGetCompositionTimingInfo.qpcRefreshPeriod interval, with a rateRefresh numerator/denominator fallback for spuriously low values and a 16.6 ms (60 Hz) default if both queries fail. Same heuristic zed uses in gpui_windows/src/vsync.rs. Each tick PostMessageW's a registered custom message (Winit::VsyncTick) to the existing thread_msg_target; the handler in thread_event_target_callback enumerates thread windows via EnumThreadWindows, filters with IsWindowVisible, and posts RedrawWindow(hwnd, RDW_INTERNALPAINT) per visible window so the existing WM_PAINT path emits RedrawRequested unchanged. The fan-out is gated on a new EventLoopRunner.should_present_after_input() (1 s window after the last input event) — same field/method names as the macOS / Wayland / X11 implementations. mark_input_received is hooked into public_window_callback_inner for WM_KEY{DOWN,UP}, WM_SYSKEY{DOWN,UP}, WM_MOUSE{MOVE,WHEEL,HWHEEL}, all four button down/up pairs, WM_TOUCH, and WM_POINTER{DOWN,UPDATE,UP}. EventLoop::drop now joins the worker thread (via a stub()-and-mem::replace swap) before destroying thread_msg_target so the worker can never PostMessageW to a freed HWND. New windows-sys feature Win32_System_Performance for QueryPerformanceFrequency. build.rs no longer panics when cross-compiling between host and target OS (#[cfg(target_os = ...)] branches now also check CARGO_CFG_TARGET_OS), so cargo check --target x86_64-pc-windows-gnu actually works from a macOS workstation.
  • Linux: vsync-driven render loop with 1-second post-input sustain (matches macOS / zed behaviour). Two changes inside rio-window (no rioterm churn — frontends still just respond to RedrawRequested). Wayland (platform_impl/linux/wayland/event_loop/mod.rs): the wl_callback::Done handler now unconditionally re-arms the next wl_surface.frame() and drives an auto-loop, modelled on zed's gpui_linux/src/linux/wayland/window.rs:572-587. Compositors stop delivering Done for occluded windows so the loop pauses naturally without an explicit visibility check. X11 (platform_impl/linux/x11/mod.rs): a new calloop::timer::Timer source reschedules at the primary monitor's xrandr-derived refresh rate (monitor::mode_refresh_rate_millihertz, fallback 60 Hz); each tick sets EventLoopState.vsync_pending and the main pump fans the flag out to every visible window via the existing redraw_sender channel. Mirrors zed's gpui_linux/src/linux/x11/client.rs:1934-1971. Both paths gate the synthetic RedrawRequested on a new should_present_after_input() (1 s window after the last input event) — same field/method names as the existing macOS implementation in platform_impl/macos/window_delegate.rs:139,997. mark_input_received is hooked into the Wayland KeyboardHandler and PointerHandler and into the X11 KeyPress/KeyRelease + XInput2 ButtonPress/Release/Motion dispatch sites in event_processor.rs. Net effect: ProMotion / 144 Hz / 240 Hz monitors now stay at peak refresh during typing/scroll on Wayland and X11; idle terminals don't burn extra CPU because the gate falls back to the existing dirty-driven path outside the 1 s window.
  • Full kitty graphics protocol Unicode-placeholder support (kitten icat --unicode-placeholder and any tmux/yazi/etc. that uses U+10EEEE placement cells). Wire-protocol bugs fixed: (1) the APC parser stored U=1 into a private cmd.virtual_placement field that was never propagated to PlacementRequest, so place_graphic always fell through to the direct-overlay branch; (2) place_virtual_graphic was auto-writing U+10EEEE cells to the grid, racing kitty's own writes (per the spec the application emits the cells, the terminal only stores metadata); (3) icat uses a=T,U=1 (combined transmit-and-display) which goes through kitty_transmit_and_display, not place_graphic — that handler now also routes virtual placements correctly and pushes pixel data to pending_images (the regular overlay path did this implicitly via place_kitty_overlay); (4) the diacritics table had 299 entries instead of the canonical 297 (two stray entries \u{06EA} and \u{06ED} from earlier work), shifting every index past the divergence point so image_id_high decoded as the wrong byte (e.g. 0x5C stored, 0x5E decoded). Replaced with the 297 entries from kitty/gen/rowcolumn-diacritics.txt. Shader bug fixed: image.metal interpreted source_rect.zw as size but the new code passes it as end-coords; switched to mix(.xy, .zw, corner) (only worked before because every overlay used the full-image default [0,0,1,1]). Renderer side, modeled cell-by-cell on ghostty's graphics_unicode.zig: new Row.kitty_virtual_placeholder per-row dirty flag set in Crosswords::input and checked by the scan loop, so we only walk rows that contain a placeholder (page.zig:1953-1958); new IncompletePlacement + can_append / append / complete implements kitty's diacritic continuation rules — a cell with missing row/col/high-byte diacritics inherits from the previous cell on the row and the col field can be omitted to mean "auto-increment from prev" (graphics_unicode.zig:407-535); the renderer now walks each visible row left-to-right, builds runs of consecutive cells that belong to the same (image_id, placement_id, image_row) with sequential image columns, and pushes ONE GraphicOverlay per run instead of one per placement (matches ghostty's PlacementIterator); per-run aspect-preserving fit, centering, and source-rect clipping factored into kitty_virtual::compute_run_geometry — handles partial visibility (placement scrolled half off-screen renders only the visible image slice), runs that fall entirely in the centering padding (returns None), and runs that straddle the padding boundary (clips both the screen rect and the source rect so the rendered slice exactly covers the fitted-image area). Placeholder cells render with style.background_color = None so the per-cell bg quad doesn't cover the BelowText image. GraphicOverlay gained a source_rect: [f32; 4] field (default [0,0,1,1] via FULL_SOURCE_RECT) wired through to the existing ImageInstance.source_rect. Tests cover: the 297-entry diacritic table, IncompletePlacement::from_cell across all input shapes (Indexed fg, Spec fg + 3rd diacritic, underline=placement_id, missing diacritics, Named fg → 0), all can_append cases (row inherit, col inherit, sequential col, col jump, row mismatch, image-id mismatch, image-id-high inherit), a 3-cell run with only the first cell carrying diacritics, parser-level U=1 propagation, the metadata-only contract of place_virtual_graphic, an end-to-end pass that feeds icat's exact wire format and asserts both the resulting grid cells AND the per-row dirty flag, and compute_run_geometry across exact-fit / image-taller-than-grid (horizontal centering + left-padding cull) / image-wider-than-grid (vertical centering + top-row cull) / partial-visibility-scrolled-off-top / origin-offset / zero-sized-image cases.
  • macOS Metal renderer: triple-buffered pipeline modeled on zed's gpui_macos::InstanceBufferPool. CAMetalLayer.maximumDrawableCount is now 3 (was 2). The six per-frame buffers (text vertices, quad instances, kitty/sixel image instances, bg-image instance, bg-fill instance, Globals uniform) collapsed into a single pooled metal::Buffer per frame: text/quad/image data bump-allocates from the pool buffer with 256-byte aligned offsets, and Globals (transform + input_colorspace, ~80 B) goes through set_vertex_bytes / set_fragment_bytes so no buffer is needed for it at all. The pool starts at 2 MiB, doubles on overflow up to a 256 MiB cap (matches zed). On overflow we end-encoding, drop the never-committed command buffer, grow the pool, and retry the frame from scratch — old smaller buffers in flight get rejected by release after grow and dropped naturally, no "all 3 slots must grow together" coordination. command_buffer.add_completed_handler releases the buffer back to the Arc<Mutex<InstanceBufferPool>> on the GPU completion thread. Net effect: CPU can stay up to 3 frames ahead of the GPU without racing the previously single StorageModeShared buffers (the old code was relying on luck — every frame shared one allocation), and ProMotion 120 Hz is now reachable without dropped frames during heavy scenes. MetalRenderer::resize is gone (no more uniform buffer to refresh on resize); Renderer::render_metal now owns command-buffer / encoder / drawable / commit, so Sugarloaf::render_metal is just a one-liner that hands bg_color and the MetalContext over.
  • Fix Nerd Font glyphs from an extras family (e.g. extras = [{family="JetBrainsMono Nerd Font Mono"}]) overflowing into the next grid cell. FontLibraryData::load was loading every extras/symbol-map font with is_emoji = true, which made font_cache::resolve_with clamp every glyph from that font to width = 2.0; the terminal grid still budgeted one cell per PUA codepoint, so the layout advanced 2× per glyph and the next character painted on top of the right half. Extras and symbol-map fonts now load with is_emoji = false, and FontData::from_data / from_slice auto-promote to is_emoji = true when the SFNT carries a color table (COLR, CBDT, CBLC, or sbix) — mirroring ghostty's FT_HAS_COLOR() / CoreText SBIX check, so a real emoji font in extras still gets the wide-cell / color-atlas treatment without needing a config flag.
  • Removed the dedicated fonts.emoji config slot. Rio now relies entirely on fonts.extras plus the color-table auto-detection above: drop extras = [{family="Apple Color Emoji"}] (or any color family) and it's picked up as emoji; otherwise the bundled Twemoji continues to serve as the default color-emoji fallback. User [fonts.emoji] sections in existing configs are silently ignored instead of erroring.
  • PUA / Nerd Font glyphs with an adjacent blank cell now fill the full 2-cell constraint crisply instead of rendering half-sized or blurry. pua_constraint_width in the rioterm renderer still picks 1 or 2 cells based on neighbour content; the compositor's fit pass (sugarloaf/src/renderer/compositor.rs) dropped the .min(1.0) cap so the glyph can scale up to the 2-cell slot — and to avoid stretching a bitmap atlas entry (which is blurry), GlyphCacheSession::get_at_size(id, size) now rasterizes the glyph fresh at font_size × cells when cells > 1. Cache key (id, size) already disambiguates the two rasterizations, so 1-cell and 2-cell lookups for the same codepoint coexist. Fixes JetBrainsMono NF Mono (patched to ~1-cell advance) showing as half-sized inside a 2-cell slot next to Cascadia NF whose glyphs render ~2 cells wide natively.
  • Fix vertical positioning of constraint-fit PUA glyphs. The compositor was placing the scaled glyph at baseline − entry.top × scale, which drifts upward as the scale grows because the top-bearing scales faster than the descent-bearing. Switched to cell-centered placement (topline + (line_height − sh) / 2.0), matching ghostty's .align_vertical = .center1 choice for isSymbol(cp) — symbols aren't baseline-anchored the way text characters are.
  • macOS Metal renderer: wide-gamut pipeline with ghostty-compatible native alpha blending. CAMetalLayer uses plain BGRA8Unorm tagged with kCGColorSpaceDisplayP3 (earlier iterations used BGRA8Unorm_sRGB, which makes Metal do linear-light blending — physically correct but visibly brighter on AA edges and translucent overlays than ghostty's and Terminal.app's default). The three pipeline color attachments moved back to BGRA8Unorm to match. Fragment shaders (renderer.metal, image.metal) now srgb_to_linear → sRGB-to-P3 matrix → linear_to_srgb before returning, emitting gamma-encoded DisplayP3 bytes that the drawable stores verbatim and the compositor displays directly. MTLClearColor goes through the same encode on the Rust side (sugarloaf::prepare_output_rgb_f64) so the first cleared pixel matches a shader-drawn quad of the same theme colour. Net effect: same saturation as before (P3 gamut preserved), same glyph weight as ghostty alpha-blending = native / Terminal.app (no more AA-edge brightening). The metal crate doesn't wrap CAMetalLayer.colorspace yet, so the setter goes through msg_send!; the CGColorSpace handle is mem::forget-ed to guarantee the pointer stays live for the layer's lifetime, and a post-set read-back logs a warning if the colorspace didn't take.
  • Breaking (macOS): [window] colorspace default flipped from display-p3 to srgb. The semantics also changed to match ghostty's window-colorspace: the setting now describes how Rio interprets input color bytes (hex values in config, ANSI direct-color sequences), not which colorspace the Metal surface uses. The Metal surface is always DisplayP3 regardless of this setting. With srgb (the new default), Rio applies a Bradford-adapted sRGB D65 → DisplayP3 D65 matrix (in linear light, after srgb_to_linear) so #ff0000 renders as sRGB-standard red — matching ghostty and every other app. Users who preferred the previous more-saturated P3-interpreted look can pin it with colorspace = "display-p3". The matrix runs in the fragment shader (prepare_output_rgb in renderer.metal / image.metal) driven by a new input_colorspace: u8 field on the Globals uniform, and at the Rust boundary for the MTLClearColor (sugarloaf::prepare_output_rgb_f64) so the first HW-cleared pixel lands in the same colorspace as shader-emitted pixels. MetalRenderer::new takes the colorspace at construction and writes it into every uniform upload; the Metal encoder now binds the uniform buffer to fragment slot 1 (in addition to the existing vertex slot 1) so fs_main / image_fs_main can read the flag.
  • New OpenCommandPalette key-binding action (default Cmd+Shift+P on macOS, Ctrl+Shift+P elsewhere), user-remappable via opencommandpalette in [bindings]. Replaces the hardcoded Cmd/Ctrl+Shift+P branch that used to sit in screen::process_key_event ahead of the binding system, so the palette shortcut now composes with mode-gating like every other action.
  • New List Fonts command palette entry: selecting it keeps the palette open and swaps the list for the host system's font families (via font-kit's SystemSource), fuzzy-filterable. Enter copies the selected family to the clipboard and closes the palette. Each font row shows a hand-drawn (rect-only) rounded copy icon on the right to advertise the action.
  • Command palette scrollbar now reuses the terminal scrollbar's look and behaviour through shared primitives in renderer::scrollbar (opacity_from_last_scroll, compute_thumb, draw_thumb) — 6 px wide, [0.6, 0.6, 0.6, 0.5] gray, no rounded corners, 2 s visibility + 300 ms fade after the last scroll event. Only appears when the user has actually scrolled and resets on palette close / query change.
  • Sugarloaf::font_family_names() + FontLibrary::family_names() for enumerating the host font catalog.
  • Rio Attributes re-exported at the sugarloaf crate root so downstream callers don't have to reach into font_introspector.

Don't miss a new rio release

NewReleases is sending notifications on new releases.