mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-24 12:46:10 +03:00
262 lines
8.3 KiB
Zig
262 lines
8.3 KiB
Zig
//! Application runtime for the embedded version of Ghostty. The embedded
|
|
//! version is when Ghostty is embedded within a parent host application,
|
|
//! rather than owning the application lifecycle itself. This is used for
|
|
//! example for the macOS build of Ghostty so that we can use a native
|
|
//! Swift+XCode-based application.
|
|
|
|
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const assert = std.debug.assert;
|
|
const Allocator = std.mem.Allocator;
|
|
const objc = @import("objc");
|
|
const apprt = @import("../apprt.zig");
|
|
const input = @import("../input.zig");
|
|
const CoreApp = @import("../App.zig");
|
|
const CoreSurface = @import("../Surface.zig");
|
|
|
|
const log = std.log.scoped(.embedded_window);
|
|
|
|
pub const App = struct {
|
|
/// Because we only expect the embedding API to be used in embedded
|
|
/// environments, the options are extern so that we can expose it
|
|
/// directly to a C callconv and not pay for any translation costs.
|
|
///
|
|
/// C type: ghostty_runtime_config_s
|
|
pub const Options = extern struct {
|
|
/// These are just aliases to make the function signatures below
|
|
/// more obvious what values will be sent.
|
|
const AppUD = ?*anyopaque;
|
|
const SurfaceUD = ?*anyopaque;
|
|
|
|
/// Userdata that is passed to all the callbacks.
|
|
userdata: AppUD = null,
|
|
|
|
/// Callback called to wakeup the event loop. This should trigger
|
|
/// a full tick of the app loop.
|
|
wakeup: *const fn (AppUD) callconv(.C) void,
|
|
|
|
/// Called to set the title of the window.
|
|
set_title: *const fn (SurfaceUD, [*]const u8) callconv(.C) void,
|
|
|
|
/// Read the clipboard value. The return value must be preserved
|
|
/// by the host until the next call.
|
|
read_clipboard: *const fn (SurfaceUD) callconv(.C) [*:0]const u8,
|
|
|
|
/// Write the clipboard value.
|
|
write_clipboard: *const fn (SurfaceUD, [*:0]const u8) callconv(.C) void,
|
|
};
|
|
|
|
core_app: *CoreApp,
|
|
opts: Options,
|
|
|
|
pub fn init(core_app: *CoreApp, opts: Options) !App {
|
|
return .{ .core_app = core_app, .opts = opts };
|
|
}
|
|
|
|
pub fn terminate(self: App) void {
|
|
_ = self;
|
|
}
|
|
|
|
pub fn wakeup(self: App) !void {
|
|
self.opts.wakeup(self.opts.userdata);
|
|
}
|
|
|
|
pub fn wait(self: App) !void {
|
|
_ = self;
|
|
}
|
|
};
|
|
|
|
pub const Surface = struct {
|
|
nsview: objc.Object,
|
|
core_surface: *CoreSurface,
|
|
content_scale: apprt.ContentScale,
|
|
size: apprt.SurfaceSize,
|
|
cursor_pos: apprt.CursorPos,
|
|
opts: Options,
|
|
|
|
pub const Options = extern struct {
|
|
/// Userdata passed to some of the callbacks.
|
|
userdata: ?*anyopaque = null,
|
|
|
|
/// The pointer to the backing NSView for the surface.
|
|
nsview: *anyopaque = undefined,
|
|
|
|
/// The scale factor of the screen.
|
|
scale_factor: f64 = 1,
|
|
};
|
|
|
|
pub fn init(self: *Surface, app: *App, opts: Options) !void {
|
|
self.* = .{
|
|
.core_surface = undefined,
|
|
.nsview = objc.Object.fromId(opts.nsview),
|
|
.content_scale = .{
|
|
.x = @floatCast(f32, opts.scale_factor),
|
|
.y = @floatCast(f32, opts.scale_factor),
|
|
},
|
|
.size = .{ .width = 800, .height = 600 },
|
|
.cursor_pos = .{ .x = 0, .y = 0 },
|
|
.opts = opts,
|
|
};
|
|
|
|
// Add ourselves to the list of surfaces on the app.
|
|
try app.app.addSurface(self);
|
|
errdefer app.app.deleteSurface(self);
|
|
|
|
// Initialize our surface right away. We're given a view that is
|
|
// ready to use.
|
|
try self.core_surface.init(app.app, app.app.config, self);
|
|
errdefer self.core_surface.deinit();
|
|
}
|
|
|
|
pub fn deinit(self: *Surface) void {
|
|
// Remove ourselves from the list of known surfaces in the app.
|
|
self.core_surface.app.deleteSurface(self);
|
|
|
|
// Clean up our core surface so that all the rendering and IO stop.
|
|
self.core_surface.deinit();
|
|
}
|
|
|
|
pub fn getContentScale(self: *const Surface) !apprt.ContentScale {
|
|
return self.content_scale;
|
|
}
|
|
|
|
pub fn getSize(self: *const Surface) !apprt.SurfaceSize {
|
|
return self.size;
|
|
}
|
|
|
|
pub fn setSizeLimits(self: *Surface, min: apprt.SurfaceSize, max_: ?apprt.SurfaceSize) !void {
|
|
_ = self;
|
|
_ = min;
|
|
_ = max_;
|
|
}
|
|
|
|
pub fn setTitle(self: *Surface, slice: [:0]const u8) !void {
|
|
self.core_surface.app.runtime.opts.set_title(
|
|
self.opts.userdata,
|
|
slice.ptr,
|
|
);
|
|
}
|
|
|
|
pub fn getClipboardString(self: *const Surface) ![:0]const u8 {
|
|
const ptr = self.core_surface.app.runtime.opts.read_clipboard(self.opts.userdata);
|
|
return std.mem.sliceTo(ptr, 0);
|
|
}
|
|
|
|
pub fn setClipboardString(self: *const Surface, val: [:0]const u8) !void {
|
|
self.core_surface.app.runtime.opts.write_clipboard(self.opts.userdata, val.ptr);
|
|
}
|
|
|
|
pub fn setShouldClose(self: *Surface) void {
|
|
_ = self;
|
|
}
|
|
|
|
pub fn shouldClose(self: *const Surface) bool {
|
|
_ = self;
|
|
return false;
|
|
}
|
|
|
|
pub fn getCursorPos(self: *const Surface) !apprt.CursorPos {
|
|
return self.cursor_pos;
|
|
}
|
|
|
|
pub fn refresh(self: *Surface) void {
|
|
self.core_surface.refreshCallback() catch |err| {
|
|
log.err("error in refresh callback err={}", .{err});
|
|
return;
|
|
};
|
|
}
|
|
|
|
pub fn updateContentScale(self: *Surface, x: f64, y: f64) void {
|
|
self.content_scale = .{
|
|
.x = @floatCast(f32, x),
|
|
.y = @floatCast(f32, y),
|
|
};
|
|
}
|
|
|
|
pub fn updateSize(self: *Surface, width: u32, height: u32) void {
|
|
self.size = .{
|
|
.width = width,
|
|
.height = height,
|
|
};
|
|
|
|
// Call the primary callback.
|
|
self.core_surface.sizeCallback(self.size) catch |err| {
|
|
log.err("error in size callback err={}", .{err});
|
|
return;
|
|
};
|
|
}
|
|
|
|
pub fn mouseButtonCallback(
|
|
self: *const Surface,
|
|
action: input.MouseButtonState,
|
|
button: input.MouseButton,
|
|
mods: input.Mods,
|
|
) void {
|
|
self.core_surface.mouseButtonCallback(action, button, mods) catch |err| {
|
|
log.err("error in mouse button callback err={}", .{err});
|
|
return;
|
|
};
|
|
}
|
|
|
|
pub fn scrollCallback(self: *const Surface, xoff: f64, yoff: f64) void {
|
|
self.core_surface.scrollCallback(xoff, yoff) catch |err| {
|
|
log.err("error in scroll callback err={}", .{err});
|
|
return;
|
|
};
|
|
}
|
|
|
|
pub fn cursorPosCallback(self: *Surface, x: f64, y: f64) void {
|
|
// Convert our unscaled x/y to scaled.
|
|
self.cursor_pos = self.core_surface.window.cursorPosToPixels(.{
|
|
.x = @floatCast(f32, x),
|
|
.y = @floatCast(f32, y),
|
|
}) catch |err| {
|
|
log.err(
|
|
"error converting cursor pos to scaled pixels in cursor pos callback err={}",
|
|
.{err},
|
|
);
|
|
return;
|
|
};
|
|
|
|
self.core_surface.cursorPosCallback(self.cursor_pos) catch |err| {
|
|
log.err("error in cursor pos callback err={}", .{err});
|
|
return;
|
|
};
|
|
}
|
|
|
|
pub fn keyCallback(
|
|
self: *const Surface,
|
|
action: input.Action,
|
|
key: input.Key,
|
|
mods: input.Mods,
|
|
) void {
|
|
// log.warn("key action={} key={} mods={}", .{ action, key, mods });
|
|
self.core_surface.keyCallback(action, key, mods) catch |err| {
|
|
log.err("error in key callback err={}", .{err});
|
|
return;
|
|
};
|
|
}
|
|
|
|
pub fn charCallback(self: *const Surface, cp_: u32) void {
|
|
const cp = std.math.cast(u21, cp_) orelse return;
|
|
self.core_surface.charCallback(cp) catch |err| {
|
|
log.err("error in char callback err={}", .{err});
|
|
return;
|
|
};
|
|
}
|
|
|
|
pub fn focusCallback(self: *const Surface, focused: bool) void {
|
|
self.core_surface.focusCallback(focused) catch |err| {
|
|
log.err("error in focus callback err={}", .{err});
|
|
return;
|
|
};
|
|
}
|
|
|
|
/// The cursor position from the host directly is in screen coordinates but
|
|
/// all our interface works in pixels.
|
|
fn cursorPosToPixels(self: *const Surface, pos: apprt.CursorPos) !apprt.CursorPos {
|
|
const scale = try self.getContentScale();
|
|
return .{ .x = pos.x * scale.x, .y = pos.y * scale.y };
|
|
}
|
|
};
|