mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
renderer mailbox, focus message
This commit is contained in:
@ -394,7 +394,6 @@ pub fn create(alloc: Allocator, loop: libuv.Loop, config: *const Config) !*Windo
|
|||||||
.renderer_thread = render_thread,
|
.renderer_thread = render_thread,
|
||||||
.renderer_state = .{
|
.renderer_state = .{
|
||||||
.mutex = mutex,
|
.mutex = mutex,
|
||||||
.focused = true,
|
|
||||||
.resize_screen = screen_size,
|
.resize_screen = screen_size,
|
||||||
.cursor = .{
|
.cursor = .{
|
||||||
.style = .blinking_block,
|
.style = .blinking_block,
|
||||||
@ -917,6 +916,11 @@ fn focusCallback(window: glfw.Window, focused: bool) void {
|
|||||||
|
|
||||||
const win = window.getUserPointer(Window) orelse return;
|
const win = window.getUserPointer(Window) orelse return;
|
||||||
|
|
||||||
|
// Notify our render thread of the new state
|
||||||
|
_ = win.renderer_thread.mailbox.push(.{
|
||||||
|
.focus = focused,
|
||||||
|
}, .{ .forever = {} });
|
||||||
|
|
||||||
// We have to schedule a render because no matter what we're changing
|
// We have to schedule a render because no matter what we're changing
|
||||||
// the cursor. If we're focused its reappearing, if we're not then
|
// the cursor. If we're focused its reappearing, if we're not then
|
||||||
// its changing to hollow and not blinking.
|
// its changing to hollow and not blinking.
|
||||||
@ -926,11 +930,6 @@ fn focusCallback(window: glfw.Window, focused: bool) void {
|
|||||||
win.terminal_cursor.startTimer() catch unreachable
|
win.terminal_cursor.startTimer() catch unreachable
|
||||||
else
|
else
|
||||||
win.terminal_cursor.stopTimer() catch unreachable;
|
win.terminal_cursor.stopTimer() catch unreachable;
|
||||||
|
|
||||||
// We are modifying renderer state from here on out
|
|
||||||
win.renderer_state.mutex.lock();
|
|
||||||
defer win.renderer_state.mutex.unlock();
|
|
||||||
win.renderer_state.focused = focused;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn refreshCallback(window: glfw.Window) void {
|
fn refreshCallback(window: glfw.Window) void {
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
pub usingnamespace @import("renderer/cursor.zig");
|
pub usingnamespace @import("renderer/cursor.zig");
|
||||||
|
pub usingnamespace @import("renderer/message.zig");
|
||||||
pub usingnamespace @import("renderer/size.zig");
|
pub usingnamespace @import("renderer/size.zig");
|
||||||
pub const Metal = @import("renderer/Metal.zig");
|
pub const Metal = @import("renderer/Metal.zig");
|
||||||
pub const OpenGL = @import("renderer/OpenGL.zig");
|
pub const OpenGL = @import("renderer/OpenGL.zig");
|
||||||
|
@ -33,6 +33,9 @@ alloc: std.mem.Allocator,
|
|||||||
/// Current cell dimensions for this grid.
|
/// Current cell dimensions for this grid.
|
||||||
cell_size: renderer.CellSize,
|
cell_size: renderer.CellSize,
|
||||||
|
|
||||||
|
/// True if the window is focused
|
||||||
|
focused: bool,
|
||||||
|
|
||||||
/// Whether the cursor is visible or not. This is used to control cursor
|
/// Whether the cursor is visible or not. This is used to control cursor
|
||||||
/// blinking.
|
/// blinking.
|
||||||
cursor_visible: bool,
|
cursor_visible: bool,
|
||||||
@ -205,6 +208,7 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal {
|
|||||||
.cell_size = .{ .width = metrics.cell_width, .height = metrics.cell_height },
|
.cell_size = .{ .width = metrics.cell_width, .height = metrics.cell_height },
|
||||||
.background = .{ .r = 0, .g = 0, .b = 0 },
|
.background = .{ .r = 0, .g = 0, .b = 0 },
|
||||||
.foreground = .{ .r = 255, .g = 255, .b = 255 },
|
.foreground = .{ .r = 255, .g = 255, .b = 255 },
|
||||||
|
.focused = true,
|
||||||
.cursor_visible = true,
|
.cursor_visible = true,
|
||||||
.cursor_style = .box,
|
.cursor_style = .box,
|
||||||
|
|
||||||
@ -290,6 +294,11 @@ pub fn threadExit(self: *const Metal) void {
|
|||||||
// Metal requires no per-thread state.
|
// Metal requires no per-thread state.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Callback when the focus changes for the terminal this is rendering.
|
||||||
|
pub fn setFocus(self: *Metal, focus: bool) !void {
|
||||||
|
self.focused = focus;
|
||||||
|
}
|
||||||
|
|
||||||
/// The primary render callback that is completely thread-safe.
|
/// The primary render callback that is completely thread-safe.
|
||||||
pub fn render(
|
pub fn render(
|
||||||
self: *Metal,
|
self: *Metal,
|
||||||
@ -315,7 +324,7 @@ pub fn render(
|
|||||||
defer state.resize_screen = null;
|
defer state.resize_screen = null;
|
||||||
|
|
||||||
// Setup our cursor state
|
// Setup our cursor state
|
||||||
if (state.focused) {
|
if (self.focused) {
|
||||||
self.cursor_visible = state.cursor.visible and !state.cursor.blink;
|
self.cursor_visible = state.cursor.visible and !state.cursor.blink;
|
||||||
self.cursor_style = renderer.CursorStyle.fromTerminal(state.cursor.style) orelse .box;
|
self.cursor_style = renderer.CursorStyle.fromTerminal(state.cursor.style) orelse .box;
|
||||||
} else {
|
} else {
|
||||||
|
@ -74,6 +74,9 @@ foreground: terminal.color.RGB,
|
|||||||
/// Default background color
|
/// Default background color
|
||||||
background: terminal.color.RGB,
|
background: terminal.color.RGB,
|
||||||
|
|
||||||
|
/// True if the window is focused
|
||||||
|
focused: bool,
|
||||||
|
|
||||||
/// The raw structure that maps directly to the buffer sent to the vertex shader.
|
/// The raw structure that maps directly to the buffer sent to the vertex shader.
|
||||||
/// This must be "extern" so that the field order is not reordered by the
|
/// This must be "extern" so that the field order is not reordered by the
|
||||||
/// Zig compiler.
|
/// Zig compiler.
|
||||||
@ -292,6 +295,7 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !OpenGL {
|
|||||||
.cursor_style = .box,
|
.cursor_style = .box,
|
||||||
.background = .{ .r = 0, .g = 0, .b = 0 },
|
.background = .{ .r = 0, .g = 0, .b = 0 },
|
||||||
.foreground = .{ .r = 255, .g = 255, .b = 255 },
|
.foreground = .{ .r = 255, .g = 255, .b = 255 },
|
||||||
|
.focused = true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -432,6 +436,11 @@ pub fn threadExit(self: *const OpenGL) void {
|
|||||||
glfw.makeContextCurrent(null) catch {};
|
glfw.makeContextCurrent(null) catch {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Callback when the focus changes for the terminal this is rendering.
|
||||||
|
pub fn setFocus(self: *OpenGL, focus: bool) !void {
|
||||||
|
self.focused = focus;
|
||||||
|
}
|
||||||
|
|
||||||
/// The primary render callback that is completely thread-safe.
|
/// The primary render callback that is completely thread-safe.
|
||||||
pub fn render(
|
pub fn render(
|
||||||
self: *OpenGL,
|
self: *OpenGL,
|
||||||
@ -455,7 +464,7 @@ pub fn render(
|
|||||||
defer state.resize_screen = null;
|
defer state.resize_screen = null;
|
||||||
|
|
||||||
// Setup our cursor state
|
// Setup our cursor state
|
||||||
if (state.focused) {
|
if (self.focused) {
|
||||||
self.cursor_visible = state.cursor.visible and !state.cursor.blink;
|
self.cursor_visible = state.cursor.visible and !state.cursor.blink;
|
||||||
self.cursor_style = renderer.CursorStyle.fromTerminal(state.cursor.style) orelse .box;
|
self.cursor_style = renderer.CursorStyle.fromTerminal(state.cursor.style) orelse .box;
|
||||||
} else {
|
} else {
|
||||||
|
@ -12,9 +12,6 @@ const renderer = @import("../renderer.zig");
|
|||||||
/// state (i.e. the terminal, devmode, etc. values).
|
/// state (i.e. the terminal, devmode, etc. values).
|
||||||
mutex: *std.Thread.Mutex,
|
mutex: *std.Thread.Mutex,
|
||||||
|
|
||||||
/// True if the window is focused
|
|
||||||
focused: bool,
|
|
||||||
|
|
||||||
/// A new screen size if the screen was resized.
|
/// A new screen size if the screen was resized.
|
||||||
resize_screen: ?renderer.ScreenSize,
|
resize_screen: ?renderer.ScreenSize,
|
||||||
|
|
||||||
|
@ -7,11 +7,16 @@ const builtin = @import("builtin");
|
|||||||
const glfw = @import("glfw");
|
const glfw = @import("glfw");
|
||||||
const libuv = @import("libuv");
|
const libuv = @import("libuv");
|
||||||
const renderer = @import("../renderer.zig");
|
const renderer = @import("../renderer.zig");
|
||||||
const gl = @import("../opengl.zig");
|
const BlockingQueue = @import("../blocking_queue.zig").BlockingQueue;
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const log = std.log.scoped(.renderer_thread);
|
const log = std.log.scoped(.renderer_thread);
|
||||||
|
|
||||||
|
/// The type used for sending messages to the IO thread. For now this is
|
||||||
|
/// hardcoded with a capacity. We can make this a comptime parameter in
|
||||||
|
/// the future if we want it configurable.
|
||||||
|
const Mailbox = BlockingQueue(renderer.Message, 64);
|
||||||
|
|
||||||
/// The main event loop for the application. The user data of this loop
|
/// The main event loop for the application. The user data of this loop
|
||||||
/// is always the allocator used to create the loop. This is a convenience
|
/// is always the allocator used to create the loop. This is a convenience
|
||||||
/// so that users of the loop always have an allocator.
|
/// so that users of the loop always have an allocator.
|
||||||
@ -36,6 +41,10 @@ renderer: *renderer.Renderer,
|
|||||||
/// Pointer to the shared state that is used to generate the final render.
|
/// Pointer to the shared state that is used to generate the final render.
|
||||||
state: *renderer.State,
|
state: *renderer.State,
|
||||||
|
|
||||||
|
/// The mailbox that can be used to send this thread messages. Note
|
||||||
|
/// this is a blocking queue so if it is full you will get errors (or block).
|
||||||
|
mailbox: *Mailbox,
|
||||||
|
|
||||||
/// Initialize the thread. This does not START the thread. This only sets
|
/// Initialize the thread. This does not START the thread. This only sets
|
||||||
/// up all the internal state necessary prior to starting the thread. It
|
/// up all the internal state necessary prior to starting the thread. It
|
||||||
/// is up to the caller to start the thread with the threadMain entrypoint.
|
/// is up to the caller to start the thread with the threadMain entrypoint.
|
||||||
@ -83,6 +92,10 @@ pub fn init(
|
|||||||
}
|
}
|
||||||
}).callback);
|
}).callback);
|
||||||
|
|
||||||
|
// The mailbox for messaging this thread
|
||||||
|
var mailbox = try Mailbox.create(alloc);
|
||||||
|
errdefer mailbox.destroy(alloc);
|
||||||
|
|
||||||
return Thread{
|
return Thread{
|
||||||
.loop = loop,
|
.loop = loop,
|
||||||
.wakeup = wakeup_h,
|
.wakeup = wakeup_h,
|
||||||
@ -91,6 +104,7 @@ pub fn init(
|
|||||||
.window = window,
|
.window = window,
|
||||||
.renderer = renderer_impl,
|
.renderer = renderer_impl,
|
||||||
.state = state,
|
.state = state,
|
||||||
|
.mailbox = mailbox,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,6 +141,9 @@ pub fn deinit(self: *Thread) void {
|
|||||||
_ = self.loop.run(.default) catch |err|
|
_ = self.loop.run(.default) catch |err|
|
||||||
log.err("error finalizing event loop: {}", .{err});
|
log.err("error finalizing event loop: {}", .{err});
|
||||||
|
|
||||||
|
// Nothing can possibly access the mailbox anymore, destroy it.
|
||||||
|
self.mailbox.destroy(alloc);
|
||||||
|
|
||||||
// Dealloc our allocator copy
|
// Dealloc our allocator copy
|
||||||
alloc.destroy(alloc_ptr);
|
alloc.destroy(alloc_ptr);
|
||||||
|
|
||||||
@ -164,6 +181,23 @@ fn threadMain_(self: *Thread) !void {
|
|||||||
_ = try self.loop.run(.default);
|
_ = try self.loop.run(.default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Drain the mailbox.
|
||||||
|
fn drainMailbox(self: *Thread) !void {
|
||||||
|
// This holds the mailbox lock for the duration of the drain. The
|
||||||
|
// expectation is that all our message handlers will be non-blocking
|
||||||
|
// ENOUGH to not mess up throughput on producers.
|
||||||
|
|
||||||
|
var drain = self.mailbox.drain();
|
||||||
|
defer drain.deinit();
|
||||||
|
|
||||||
|
while (drain.next()) |message| {
|
||||||
|
log.debug("mailbox message={}", .{message});
|
||||||
|
switch (message) {
|
||||||
|
.focus => |v| try self.renderer.setFocus(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn wakeupCallback(h: *libuv.Async) void {
|
fn wakeupCallback(h: *libuv.Async) void {
|
||||||
const t = h.getData(Thread) orelse {
|
const t = h.getData(Thread) orelse {
|
||||||
// This shouldn't happen so we log it.
|
// This shouldn't happen so we log it.
|
||||||
@ -171,6 +205,11 @@ fn wakeupCallback(h: *libuv.Async) void {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// When we wake up, we check the mailbox. Mailbox producers should
|
||||||
|
// wake up our thread after publishing.
|
||||||
|
t.drainMailbox() catch |err|
|
||||||
|
log.err("error draining mailbox err={}", .{err});
|
||||||
|
|
||||||
// If the timer is already active then we don't have to do anything.
|
// If the timer is already active then we don't have to do anything.
|
||||||
const active = t.render_h.isActive() catch true;
|
const active = t.render_h.isActive() catch true;
|
||||||
if (active) return;
|
if (active) return;
|
||||||
|
11
src/renderer/message.zig
Normal file
11
src/renderer/message.zig
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
/// The messages that can be sent to a renderer thread.
|
||||||
|
pub const Message = union(enum) {
|
||||||
|
/// A change in state in the window focus that this renderer is
|
||||||
|
/// rendering within. This is only sent when a change is detected so
|
||||||
|
/// the renderer is expected to handle all of these.
|
||||||
|
focus: bool,
|
||||||
|
};
|
Reference in New Issue
Block a user