mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-18 17:56:09 +03:00
Merge pull request #2301 from ghostty-org/actiondecl
Purge @hasDecl from apprt APIs
This commit is contained in:
@ -549,6 +549,9 @@ extension Ghostty {
|
||||
}
|
||||
|
||||
static func setPasswordInput(_ userdata: UnsafeMutableRawPointer?, value: Bool) {
|
||||
// We don't currently allow global password input being set from this.
|
||||
guard let userdata else { return }
|
||||
|
||||
let surfaceView = self.surfaceUserdata(from: userdata)
|
||||
guard let appState = self.appState(fromView: surfaceView) else { return }
|
||||
guard appState.config.autoSecureInput else { return }
|
||||
|
55
src/App.zig
55
src/App.zig
@ -138,7 +138,13 @@ pub fn addSurface(self: *App, rt_surface: *apprt.Surface) !void {
|
||||
// Since we have non-zero surfaces, we can cancel the quit timer.
|
||||
// It is up to the apprt if there is a quit timer at all and if it
|
||||
// should be canceled.
|
||||
if (@hasDecl(apprt.App, "cancelQuitTimer")) rt_surface.app.cancelQuitTimer();
|
||||
rt_surface.app.performAction(
|
||||
.{ .surface = &rt_surface.core_surface },
|
||||
.quit_timer,
|
||||
.stop,
|
||||
) catch |err| {
|
||||
log.warn("error stopping quit timer err={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
/// Delete the surface from the known surface list. This will NOT call the
|
||||
@ -166,8 +172,13 @@ pub fn deleteSurface(self: *App, rt_surface: *apprt.Surface) void {
|
||||
|
||||
// If we have no surfaces, we can start the quit timer. It is up to the
|
||||
// apprt to determine if this is necessary.
|
||||
if (@hasDecl(apprt.App, "startQuitTimer") and
|
||||
self.surfaces.items.len == 0) rt_surface.app.startQuitTimer();
|
||||
if (self.surfaces.items.len == 0) rt_surface.app.performAction(
|
||||
.{ .surface = &rt_surface.core_surface },
|
||||
.quit_timer,
|
||||
.start,
|
||||
) catch |err| {
|
||||
log.warn("error starting quit timer err={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
/// The last focused surface. This is only valid while on the main thread
|
||||
@ -194,7 +205,7 @@ fn drainMailbox(self: *App, rt_app: *apprt.App) !void {
|
||||
log.debug("mailbox message={s}", .{@tagName(message)});
|
||||
switch (message) {
|
||||
.reload_config => try self.reloadConfig(rt_app),
|
||||
.open_config => try self.openConfig(rt_app),
|
||||
.open_config => try self.performAction(rt_app, .open_config),
|
||||
.new_window => |msg| try self.newWindow(rt_app, msg),
|
||||
.close => |surface| try self.closeSurface(surface),
|
||||
.quit => try self.setQuit(),
|
||||
@ -205,12 +216,6 @@ fn drainMailbox(self: *App, rt_app: *apprt.App) !void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn openConfig(self: *App, rt_app: *apprt.App) !void {
|
||||
_ = self;
|
||||
log.debug("opening configuration", .{});
|
||||
try rt_app.openConfig();
|
||||
}
|
||||
|
||||
pub fn reloadConfig(self: *App, rt_app: *apprt.App) !void {
|
||||
log.debug("reloading configuration", .{});
|
||||
if (try rt_app.reloadConfig()) |new| {
|
||||
@ -241,19 +246,17 @@ fn redrawInspector(self: *App, rt_app: *apprt.App, surface: *apprt.Surface) !voi
|
||||
|
||||
/// Create a new window
|
||||
pub fn newWindow(self: *App, rt_app: *apprt.App, msg: Message.NewWindow) !void {
|
||||
if (!@hasDecl(apprt.App, "newWindow")) {
|
||||
log.warn("newWindow is not supported by this runtime", .{});
|
||||
return;
|
||||
}
|
||||
const target: apprt.Target = target: {
|
||||
const parent = msg.parent orelse break :target .app;
|
||||
if (self.hasSurface(parent)) break :target .{ .surface = parent };
|
||||
break :target .app;
|
||||
};
|
||||
|
||||
const parent = if (msg.parent) |parent| parent: {
|
||||
break :parent if (self.hasSurface(parent))
|
||||
parent
|
||||
else
|
||||
null;
|
||||
} else null;
|
||||
|
||||
try rt_app.newWindow(parent);
|
||||
try rt_app.performAction(
|
||||
target,
|
||||
.new_window,
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
/// Start quitting
|
||||
@ -318,13 +321,9 @@ pub fn performAction(
|
||||
.ignore => {},
|
||||
.quit => try self.setQuit(),
|
||||
.new_window => try self.newWindow(rt_app, .{ .parent = null }),
|
||||
.open_config => try self.openConfig(rt_app),
|
||||
.open_config => try rt_app.performAction(.app, .open_config, {}),
|
||||
.reload_config => try self.reloadConfig(rt_app),
|
||||
.close_all_windows => {
|
||||
if (@hasDecl(apprt.App, "closeAllWindows")) {
|
||||
rt_app.closeAllWindows();
|
||||
} else log.warn("runtime doesn't implement closeAllWindows", .{});
|
||||
},
|
||||
.close_all_windows => try rt_app.performAction(.app, .close_all_windows, {}),
|
||||
}
|
||||
}
|
||||
|
||||
|
245
src/Surface.zig
245
src/Surface.zig
@ -747,11 +747,7 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
|
||||
},
|
||||
|
||||
.report_title => |style| {
|
||||
const title: ?[:0]const u8 = title: {
|
||||
if (!@hasDecl(apprt.runtime.Surface, "getTitle")) break :title null;
|
||||
break :title self.rt_surface.getTitle();
|
||||
};
|
||||
|
||||
const title: ?[:0]const u8 = self.rt_surface.getTitle();
|
||||
const data = switch (style) {
|
||||
.csi_21_t => try std.fmt.allocPrint(
|
||||
self.alloc,
|
||||
@ -838,9 +834,16 @@ fn passwordInput(self: *Surface, v: bool) !void {
|
||||
}
|
||||
|
||||
// Notify our apprt so it can do whatever it wants.
|
||||
if (@hasDecl(apprt.Surface, "setPasswordInput")) {
|
||||
self.rt_surface.setPasswordInput(v);
|
||||
}
|
||||
self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.secure_input,
|
||||
if (v) .on else .off,
|
||||
) catch |err| {
|
||||
// We ignore this error because we don't want to fail this
|
||||
// entire operation just because the apprt failed to set
|
||||
// the secure input state.
|
||||
log.warn("apprt failed to set secure input state err={}", .{err});
|
||||
};
|
||||
|
||||
try self.queueRender();
|
||||
}
|
||||
@ -894,7 +897,6 @@ fn modsChanged(self: *Surface, mods: input.Mods) void {
|
||||
/// Called when our renderer health state changes.
|
||||
fn updateRendererHealth(self: *Surface, health: renderer.Health) void {
|
||||
log.warn("renderer health status change status={}", .{health});
|
||||
if (!@hasDecl(apprt.runtime.Surface, "updateRendererHealth")) return;
|
||||
self.rt_surface.updateRendererHealth(health);
|
||||
}
|
||||
|
||||
@ -1151,10 +1153,8 @@ fn setSelection(self: *Surface, sel_: ?terminal.Selection) !void {
|
||||
|
||||
// Check if our runtime supports the selection clipboard at all.
|
||||
// We can save a lot of work if it doesn't.
|
||||
if (@hasDecl(apprt.runtime.Surface, "supportsClipboard")) {
|
||||
if (!self.rt_surface.supportsClipboard(clipboard)) {
|
||||
return;
|
||||
}
|
||||
if (!self.rt_surface.supportsClipboard(clipboard)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const buf = self.io.terminal.screen.selectionString(self.alloc, .{
|
||||
@ -3656,113 +3656,99 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
v,
|
||||
),
|
||||
|
||||
.new_tab => {
|
||||
if (@hasDecl(apprt.Surface, "newTab")) {
|
||||
try self.rt_surface.newTab();
|
||||
} else log.warn("runtime doesn't implement newTab", .{});
|
||||
},
|
||||
.new_tab => try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.new_tab,
|
||||
{},
|
||||
),
|
||||
|
||||
.previous_tab => {
|
||||
if (@hasDecl(apprt.Surface, "hasTabs")) {
|
||||
if (!self.rt_surface.hasTabs()) {
|
||||
log.debug("surface has no tabs, ignoring previous_tab binding", .{});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
inline .previous_tab,
|
||||
.next_tab,
|
||||
.last_tab,
|
||||
.goto_tab,
|
||||
=> |v, tag| try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.goto_tab,
|
||||
switch (tag) {
|
||||
.previous_tab => .previous,
|
||||
.next_tab => .next,
|
||||
.last_tab => .last,
|
||||
.goto_tab => @enumFromInt(v),
|
||||
else => comptime unreachable,
|
||||
},
|
||||
),
|
||||
|
||||
if (@hasDecl(apprt.Surface, "gotoTab")) {
|
||||
self.rt_surface.gotoTab(.previous);
|
||||
} else log.warn("runtime doesn't implement gotoTab", .{});
|
||||
},
|
||||
.new_split => |direction| try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.new_split,
|
||||
switch (direction) {
|
||||
.right => .right,
|
||||
.down => .down,
|
||||
.auto => if (self.screen_size.width > self.screen_size.height)
|
||||
.right
|
||||
else
|
||||
.down,
|
||||
},
|
||||
),
|
||||
|
||||
.next_tab => {
|
||||
if (@hasDecl(apprt.Surface, "hasTabs")) {
|
||||
if (!self.rt_surface.hasTabs()) {
|
||||
log.debug("surface has no tabs, ignoring next_tab binding", .{});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
.goto_split => |direction| try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.goto_split,
|
||||
switch (direction) {
|
||||
inline else => |tag| @field(
|
||||
apprt.action.GotoSplit,
|
||||
@tagName(tag),
|
||||
),
|
||||
},
|
||||
),
|
||||
|
||||
if (@hasDecl(apprt.Surface, "gotoTab")) {
|
||||
self.rt_surface.gotoTab(.next);
|
||||
} else log.warn("runtime doesn't implement gotoTab", .{});
|
||||
},
|
||||
.resize_split => |value| try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.resize_split,
|
||||
.{
|
||||
.amount = value[1],
|
||||
.direction = switch (value[0]) {
|
||||
inline else => |tag| @field(
|
||||
apprt.action.ResizeSplit.Direction,
|
||||
@tagName(tag),
|
||||
),
|
||||
},
|
||||
},
|
||||
),
|
||||
|
||||
.last_tab => {
|
||||
if (@hasDecl(apprt.Surface, "hasTabs")) {
|
||||
if (!self.rt_surface.hasTabs()) {
|
||||
log.debug("surface has no tabs, ignoring last_tab binding", .{});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
.equalize_splits => try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.equalize_splits,
|
||||
{},
|
||||
),
|
||||
|
||||
if (@hasDecl(apprt.Surface, "gotoTab")) {
|
||||
self.rt_surface.gotoTab(.last);
|
||||
} else log.warn("runtime doesn't implement gotoTab", .{});
|
||||
},
|
||||
.toggle_split_zoom => try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.toggle_split_zoom,
|
||||
{},
|
||||
),
|
||||
|
||||
.goto_tab => |n| {
|
||||
if (@hasDecl(apprt.Surface, "gotoTab")) {
|
||||
self.rt_surface.gotoTab(@enumFromInt(n));
|
||||
} else log.warn("runtime doesn't implement gotoTab", .{});
|
||||
},
|
||||
.toggle_fullscreen => try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.toggle_fullscreen,
|
||||
switch (self.config.macos_non_native_fullscreen) {
|
||||
.false => .native,
|
||||
.true => .macos_non_native,
|
||||
.@"visible-menu" => .macos_non_native_visible_menu,
|
||||
},
|
||||
),
|
||||
|
||||
.new_split => |direction| {
|
||||
if (@hasDecl(apprt.Surface, "newSplit")) {
|
||||
try self.rt_surface.newSplit(switch (direction) {
|
||||
.right => .right,
|
||||
.down => .down,
|
||||
.auto => if (self.screen_size.width > self.screen_size.height)
|
||||
.right
|
||||
else
|
||||
.down,
|
||||
});
|
||||
} else log.warn("runtime doesn't implement newSplit", .{});
|
||||
},
|
||||
.toggle_window_decorations => try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.toggle_window_decorations,
|
||||
{},
|
||||
),
|
||||
|
||||
.goto_split => |direction| {
|
||||
if (@hasDecl(apprt.Surface, "gotoSplit")) {
|
||||
self.rt_surface.gotoSplit(direction);
|
||||
} else log.warn("runtime doesn't implement gotoSplit", .{});
|
||||
},
|
||||
|
||||
.resize_split => |param| {
|
||||
if (@hasDecl(apprt.Surface, "resizeSplit")) {
|
||||
const direction = param[0];
|
||||
const amount = param[1];
|
||||
self.rt_surface.resizeSplit(direction, amount);
|
||||
} else log.warn("runtime doesn't implement resizeSplit", .{});
|
||||
},
|
||||
|
||||
.equalize_splits => {
|
||||
if (@hasDecl(apprt.Surface, "equalizeSplits")) {
|
||||
self.rt_surface.equalizeSplits();
|
||||
} else log.warn("runtime doesn't implement equalizeSplits", .{});
|
||||
},
|
||||
|
||||
.toggle_split_zoom => {
|
||||
if (@hasDecl(apprt.Surface, "toggleSplitZoom")) {
|
||||
self.rt_surface.toggleSplitZoom();
|
||||
} else log.warn("runtime doesn't implement toggleSplitZoom", .{});
|
||||
},
|
||||
|
||||
.toggle_fullscreen => {
|
||||
if (@hasDecl(apprt.Surface, "toggleFullscreen")) {
|
||||
self.rt_surface.toggleFullscreen(self.config.macos_non_native_fullscreen);
|
||||
} else log.warn("runtime doesn't implement toggleFullscreen", .{});
|
||||
},
|
||||
|
||||
.toggle_window_decorations => {
|
||||
if (@hasDecl(apprt.Surface, "toggleWindowDecorations")) {
|
||||
self.rt_surface.toggleWindowDecorations();
|
||||
} else log.warn("runtime doesn't implement toggleWindowDecorations", .{});
|
||||
},
|
||||
|
||||
.toggle_secure_input => {
|
||||
if (@hasDecl(apprt.Surface, "toggleSecureInput")) {
|
||||
self.rt_surface.toggleSecureInput();
|
||||
} else log.warn("runtime doesn't implement toggleSecureInput", .{});
|
||||
},
|
||||
.toggle_secure_input => try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.secure_input,
|
||||
.toggle,
|
||||
),
|
||||
|
||||
.select_all => {
|
||||
const sel = self.io.terminal.screen.selectAll();
|
||||
@ -3772,11 +3758,16 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
}
|
||||
},
|
||||
|
||||
.inspector => |mode| {
|
||||
if (@hasDecl(apprt.Surface, "controlInspector")) {
|
||||
self.rt_surface.controlInspector(mode);
|
||||
} else log.warn("runtime doesn't implement controlInspector", .{});
|
||||
},
|
||||
.inspector => |mode| try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.inspector,
|
||||
switch (mode) {
|
||||
inline else => |tag| @field(
|
||||
apprt.action.Inspector,
|
||||
@tagName(tag),
|
||||
),
|
||||
},
|
||||
),
|
||||
|
||||
.close_surface => self.close(),
|
||||
|
||||
@ -4163,11 +4154,6 @@ fn completeClipboardReadOSC52(
|
||||
}
|
||||
|
||||
fn showDesktopNotification(self: *Surface, title: [:0]const u8, body: [:0]const u8) !void {
|
||||
if (comptime !@hasDecl(apprt.Surface, "showDesktopNotification")) {
|
||||
log.warn("runtime doesn't support desktop notifications", .{});
|
||||
return;
|
||||
}
|
||||
|
||||
// Wyhash is used to hash the contents of the desktop notification to limit
|
||||
// how fast identical notifications can be sent sequentially.
|
||||
const hash_algorithm = std.hash.Wyhash;
|
||||
@ -4203,7 +4189,14 @@ fn showDesktopNotification(self: *Surface, title: [:0]const u8, body: [:0]const
|
||||
|
||||
self.app.last_notification_time = now;
|
||||
self.app.last_notification_digest = new_digest;
|
||||
try self.rt_surface.showDesktopNotification(title, body);
|
||||
try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.desktop_notification,
|
||||
.{
|
||||
.title = title,
|
||||
.body = body,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn crashThreadState(self: *Surface) crash.sentry.ThreadState {
|
||||
@ -4216,9 +4209,11 @@ fn crashThreadState(self: *Surface) crash.sentry.ThreadState {
|
||||
/// Tell the surface to present itself to the user. This may involve raising the
|
||||
/// window and switching tabs.
|
||||
fn presentSurface(self: *Surface) !void {
|
||||
if (@hasDecl(apprt.Surface, "presentSurface")) {
|
||||
self.rt_surface.presentSurface();
|
||||
} else log.warn("runtime doesn't support presentSurface", .{});
|
||||
try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.present_terminal,
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
pub const face_ttf = @embedFile("font/res/JetBrainsMono-Regular.ttf");
|
||||
|
@ -14,6 +14,7 @@ const build_config = @import("build_config.zig");
|
||||
|
||||
const structs = @import("apprt/structs.zig");
|
||||
|
||||
pub const action = @import("apprt/action.zig");
|
||||
pub const glfw = @import("apprt/glfw.zig");
|
||||
pub const gtk = @import("apprt/gtk.zig");
|
||||
pub const none = @import("apprt/none.zig");
|
||||
@ -21,6 +22,9 @@ pub const browser = @import("apprt/browser.zig");
|
||||
pub const embedded = @import("apprt/embedded.zig");
|
||||
pub const surface = @import("apprt/surface.zig");
|
||||
|
||||
pub const Action = action.Action;
|
||||
pub const Target = action.Target;
|
||||
|
||||
pub const ContentScale = structs.ContentScale;
|
||||
pub const Clipboard = structs.Clipboard;
|
||||
pub const ClipboardRequest = structs.ClipboardRequest;
|
||||
@ -28,10 +32,8 @@ pub const ClipboardRequestType = structs.ClipboardRequestType;
|
||||
pub const ColorScheme = structs.ColorScheme;
|
||||
pub const CursorPos = structs.CursorPos;
|
||||
pub const DesktopNotification = structs.DesktopNotification;
|
||||
pub const GotoTab = structs.GotoTab;
|
||||
pub const IMEPos = structs.IMEPos;
|
||||
pub const Selection = structs.Selection;
|
||||
pub const SplitDirection = structs.SplitDirection;
|
||||
pub const SurfaceSize = structs.SurfaceSize;
|
||||
|
||||
/// The implementation to use for the app runtime. This is comptime chosen
|
||||
@ -84,4 +86,6 @@ pub const Runtime = enum {
|
||||
test {
|
||||
_ = Runtime;
|
||||
_ = runtime;
|
||||
_ = action;
|
||||
_ = structs;
|
||||
}
|
||||
|
177
src/apprt/action.zig
Normal file
177
src/apprt/action.zig
Normal file
@ -0,0 +1,177 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const CoreSurface = @import("../Surface.zig");
|
||||
|
||||
/// The target for an action. This is generally the thing that had focus
|
||||
/// while the action was made but the concept of "focus" is not guaranteed
|
||||
/// since actions can also be triggered by timers, scripts, etc.
|
||||
pub const Target = union(enum) {
|
||||
app,
|
||||
surface: *CoreSurface,
|
||||
};
|
||||
|
||||
/// The possible actions an apprt has to react to. Actions are one-way
|
||||
/// messages that are sent to the app runtime to trigger some behavior.
|
||||
///
|
||||
/// Actions are very often key binding actions but can also be triggered
|
||||
/// by lifecycle events. For example, the `quit_timer` action is not bindable.
|
||||
///
|
||||
/// Importantly, actions are generally OPTIONAL to implement by an apprt.
|
||||
/// Required functionality is called directly on the runtime structure so
|
||||
/// there is a compiler error if an action is not implemented.
|
||||
pub const Action = union(enum) {
|
||||
/// Open a new window. The target determines whether properties such
|
||||
/// as font size should be inherited.
|
||||
new_window,
|
||||
|
||||
/// Open a new tab. If the target is a surface it should be opened in
|
||||
/// the same window as the surface. If the target is the app then
|
||||
/// the tab should be opened in a new window.
|
||||
new_tab,
|
||||
|
||||
/// Create a new split. The value determines the location of the split
|
||||
/// relative to the target.
|
||||
new_split: SplitDirection,
|
||||
|
||||
/// Close all open windows.
|
||||
close_all_windows,
|
||||
|
||||
/// Toggle fullscreen mode.
|
||||
toggle_fullscreen: Fullscreen,
|
||||
|
||||
/// Toggle whether window directions are shown.
|
||||
toggle_window_decorations,
|
||||
|
||||
/// Jump to a specific tab. Must handle the scenario that the tab
|
||||
/// value is invalid.
|
||||
goto_tab: GotoTab,
|
||||
|
||||
/// Jump to a specific split.
|
||||
goto_split: GotoSplit,
|
||||
|
||||
/// Resize the split in the given direction.
|
||||
resize_split: ResizeSplit,
|
||||
|
||||
/// Equalize all the splits in the target window.
|
||||
equalize_splits,
|
||||
|
||||
/// Toggle whether a split is zoomed or not. A zoomed split is resized
|
||||
/// to take up the entire window.
|
||||
toggle_split_zoom,
|
||||
|
||||
/// Present the target terminal whether its a tab, split, or window.
|
||||
present_terminal,
|
||||
|
||||
/// Control whether the inspector is shown or hidden.
|
||||
inspector: Inspector,
|
||||
|
||||
/// Show a desktop notification.
|
||||
desktop_notification: DesktopNotification,
|
||||
|
||||
/// Open the Ghostty configuration. This is platform-specific about
|
||||
/// what it means; it can mean opening a dedicated UI or just opening
|
||||
/// a file in a text editor.
|
||||
open_config,
|
||||
|
||||
/// Called when there are no more surfaces and the app should quit
|
||||
/// after the configured delay. This can be cancelled by sending
|
||||
/// another quit_timer action with "stop". Multiple "starts" shouldn't
|
||||
/// happen and can be ignored or cause a restart it isn't that important.
|
||||
quit_timer: QuitTimer,
|
||||
|
||||
/// Set the secure input functionality on or off. "Secure input" means
|
||||
/// that the user is currently at some sort of prompt where they may be
|
||||
/// entering a password or other sensitive information. This can be used
|
||||
/// by the app runtime to change the appearance of the cursor, setup
|
||||
/// system APIs to not log the input, etc.
|
||||
secure_input: SecureInput,
|
||||
|
||||
/// The enum of keys in the tagged union.
|
||||
pub const Key = @typeInfo(Action).Union.tag_type.?;
|
||||
|
||||
/// Returns the value type for the given key.
|
||||
pub fn Value(comptime key: Key) type {
|
||||
inline for (@typeInfo(Action).Union.fields) |field| {
|
||||
const field_key = @field(Key, field.name);
|
||||
if (field_key == key) return field.type;
|
||||
}
|
||||
|
||||
unreachable;
|
||||
}
|
||||
};
|
||||
|
||||
// This is made extern (c_int) to make interop easier with our embedded
|
||||
// runtime. The small size cost doesn't make a difference in our union.
|
||||
pub const SplitDirection = enum(c_int) {
|
||||
right,
|
||||
down,
|
||||
};
|
||||
|
||||
// This is made extern (c_int) to make interop easier with our embedded
|
||||
// runtime. The small size cost doesn't make a difference in our union.
|
||||
pub const GotoSplit = enum(c_int) {
|
||||
previous,
|
||||
next,
|
||||
|
||||
top,
|
||||
left,
|
||||
bottom,
|
||||
right,
|
||||
};
|
||||
|
||||
/// The amount to resize the split by and the direction to resize it in.
|
||||
pub const ResizeSplit = struct {
|
||||
amount: u16,
|
||||
direction: Direction,
|
||||
|
||||
pub const Direction = enum(c_int) {
|
||||
up,
|
||||
down,
|
||||
left,
|
||||
right,
|
||||
};
|
||||
};
|
||||
|
||||
/// The tab to jump to. This is non-exhaustive so that integer values represent
|
||||
/// the index (zero-based) of the tab to jump to. Negative values are special
|
||||
/// values.
|
||||
pub const GotoTab = enum(c_int) {
|
||||
previous = -1,
|
||||
next = -2,
|
||||
last = -3,
|
||||
_,
|
||||
};
|
||||
|
||||
/// The fullscreen mode to toggle to if we're moving to fullscreen.
|
||||
pub const Fullscreen = enum(c_int) {
|
||||
native,
|
||||
|
||||
/// macOS has a non-native fullscreen mode that is more like a maximized
|
||||
/// window. This is much faster to enter and exit than the native mode.
|
||||
macos_non_native,
|
||||
macos_non_native_visible_menu,
|
||||
};
|
||||
|
||||
pub const SecureInput = enum(c_int) {
|
||||
on,
|
||||
off,
|
||||
toggle,
|
||||
};
|
||||
|
||||
/// The inspector mode to toggle to if we're toggling the inspector.
|
||||
pub const Inspector = enum(c_int) {
|
||||
toggle,
|
||||
show,
|
||||
hide,
|
||||
};
|
||||
|
||||
pub const QuitTimer = enum(c_int) {
|
||||
start,
|
||||
stop,
|
||||
};
|
||||
|
||||
/// The desktop notification to show.
|
||||
pub const DesktopNotification = struct {
|
||||
title: [:0]const u8,
|
||||
body: [:0]const u8,
|
||||
};
|
@ -81,9 +81,10 @@ pub const App = struct {
|
||||
|
||||
/// Create a new split view. If the embedder doesn't support split
|
||||
/// views then this can be null.
|
||||
new_split: ?*const fn (SurfaceUD, apprt.SplitDirection, apprt.Surface.Options) callconv(.C) void = null,
|
||||
new_split: ?*const fn (SurfaceUD, apprt.action.SplitDirection, apprt.Surface.Options) callconv(.C) void = null,
|
||||
|
||||
/// New tab with options.
|
||||
/// New tab with options. The surface may be null if there is no target
|
||||
/// surface in which case the apprt is expected to create a new window.
|
||||
new_tab: ?*const fn (SurfaceUD, apprt.Surface.Options) callconv(.C) void = null,
|
||||
|
||||
/// New window with options. The surface may be null if there is no
|
||||
@ -91,16 +92,16 @@ pub const App = struct {
|
||||
new_window: ?*const fn (SurfaceUD, apprt.Surface.Options) callconv(.C) void = null,
|
||||
|
||||
/// Control the inspector visibility
|
||||
control_inspector: ?*const fn (SurfaceUD, input.InspectorMode) callconv(.C) void = null,
|
||||
control_inspector: ?*const fn (SurfaceUD, apprt.action.Inspector) callconv(.C) void = null,
|
||||
|
||||
/// Close the current surface given by this function.
|
||||
close_surface: ?*const fn (SurfaceUD, bool) callconv(.C) void = null,
|
||||
|
||||
/// Focus the previous/next split (if any).
|
||||
focus_split: ?*const fn (SurfaceUD, input.SplitFocusDirection) callconv(.C) void = null,
|
||||
focus_split: ?*const fn (SurfaceUD, apprt.action.GotoSplit) callconv(.C) void = null,
|
||||
|
||||
/// Resize the current split.
|
||||
resize_split: ?*const fn (SurfaceUD, input.SplitResizeDirection, u16) callconv(.C) void = null,
|
||||
resize_split: ?*const fn (SurfaceUD, apprt.action.ResizeSplit.Direction, u16) callconv(.C) void = null,
|
||||
|
||||
/// Equalize all splits in the current window
|
||||
equalize_splits: ?*const fn (SurfaceUD) callconv(.C) void = null,
|
||||
@ -109,7 +110,7 @@ pub const App = struct {
|
||||
toggle_split_zoom: ?*const fn (SurfaceUD) callconv(.C) void = null,
|
||||
|
||||
/// Goto tab
|
||||
goto_tab: ?*const fn (SurfaceUD, apprt.GotoTab) callconv(.C) void = null,
|
||||
goto_tab: ?*const fn (SurfaceUD, apprt.action.GotoTab) callconv(.C) void = null,
|
||||
|
||||
/// Toggle fullscreen for current window.
|
||||
toggle_fullscreen: ?*const fn (SurfaceUD, configpkg.NonNativeFullscreen) callconv(.C) void = null,
|
||||
@ -124,7 +125,8 @@ pub const App = struct {
|
||||
/// Called when the cell size changes.
|
||||
set_cell_size: ?*const fn (SurfaceUD, u32, u32) callconv(.C) void = null,
|
||||
|
||||
/// Show a desktop notification to the user.
|
||||
/// Show a desktop notification to the user. The surface may be null
|
||||
/// if the notification is global.
|
||||
show_desktop_notification: ?*const fn (SurfaceUD, [*:0]const u8, [*:0]const u8) void = null,
|
||||
|
||||
/// Called when the health of the renderer changes.
|
||||
@ -138,6 +140,8 @@ pub const App = struct {
|
||||
/// Notifies that a password input has been started for the given
|
||||
/// surface. The apprt can use this to modify UI, enable features
|
||||
/// such as macOS secure input, etc.
|
||||
///
|
||||
/// The surface userdata will be null if a surface isn't focused.
|
||||
set_password_input: ?*const fn (SurfaceUD, bool) callconv(.C) void = null,
|
||||
|
||||
/// Toggle secure input for the application.
|
||||
@ -440,10 +444,6 @@ pub const App = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn openConfig(self: *App) !void {
|
||||
try configpkg.edit.open(self.core_app.alloc);
|
||||
}
|
||||
|
||||
pub fn reloadConfig(self: *App) !?*const Config {
|
||||
// Reload
|
||||
if (self.opts.reload_config(self.opts.userdata)) |new| {
|
||||
@ -492,7 +492,7 @@ pub const App = struct {
|
||||
surface.queueInspectorRender();
|
||||
}
|
||||
|
||||
pub fn newWindow(self: *App, parent: ?*CoreSurface) !void {
|
||||
fn newWindow(self: *App, parent: ?*CoreSurface) !void {
|
||||
// If we have a parent, the surface logic handles it.
|
||||
if (parent) |surface| {
|
||||
try surface.rt_surface.newWindow();
|
||||
@ -507,6 +507,232 @@ pub const App = struct {
|
||||
|
||||
func(null, .{});
|
||||
}
|
||||
|
||||
fn toggleFullscreen(
|
||||
self: *App,
|
||||
target: apprt.Target,
|
||||
fullscreen: apprt.action.Fullscreen,
|
||||
) void {
|
||||
const func = self.opts.toggle_fullscreen orelse {
|
||||
log.info("runtime embedder does not toggle_fullscreen", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
switch (target) {
|
||||
.app => {},
|
||||
.surface => |v| func(
|
||||
v.rt_surface.userdata,
|
||||
switch (fullscreen) {
|
||||
.native => .false,
|
||||
.macos_non_native => .true,
|
||||
.macos_non_native_visible_menu => .@"visible-menu",
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn newTab(self: *const App, target: apprt.Target) void {
|
||||
const func = self.opts.new_tab orelse {
|
||||
log.info("runtime embedder does not support new_tab", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
switch (target) {
|
||||
.app => func(null, .{}),
|
||||
.surface => |v| func(
|
||||
v.rt_surface.userdata,
|
||||
v.rt_surface.newSurfaceOptions(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn gotoTab(self: *App, target: apprt.Target, tab: apprt.action.GotoTab) void {
|
||||
const func = self.opts.goto_tab orelse {
|
||||
log.info("runtime embedder does not support goto_tab", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
switch (target) {
|
||||
.app => {},
|
||||
.surface => |v| func(v.rt_surface.userdata, tab),
|
||||
}
|
||||
}
|
||||
|
||||
fn newSplit(
|
||||
self: *const App,
|
||||
target: apprt.Target,
|
||||
direction: apprt.action.SplitDirection,
|
||||
) void {
|
||||
const func = self.opts.new_split orelse {
|
||||
log.info("runtime embedder does not support splits", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
switch (target) {
|
||||
.app => func(null, direction, .{}),
|
||||
.surface => |v| func(
|
||||
v.rt_surface.userdata,
|
||||
direction,
|
||||
v.rt_surface.newSurfaceOptions(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn gotoSplit(
|
||||
self: *const App,
|
||||
target: apprt.Target,
|
||||
direction: apprt.action.GotoSplit,
|
||||
) void {
|
||||
const func = self.opts.focus_split orelse {
|
||||
log.info("runtime embedder does not support focus split", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
switch (target) {
|
||||
.app => {},
|
||||
.surface => |v| func(v.rt_surface.userdata, direction),
|
||||
}
|
||||
}
|
||||
|
||||
fn resizeSplit(
|
||||
self: *const App,
|
||||
target: apprt.Target,
|
||||
resize: apprt.action.ResizeSplit,
|
||||
) void {
|
||||
const func = self.opts.resize_split orelse {
|
||||
log.info("runtime embedder does not support resize split", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
switch (target) {
|
||||
.app => {},
|
||||
.surface => |v| func(
|
||||
v.rt_surface.userdata,
|
||||
resize.direction,
|
||||
resize.amount,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn equalizeSplits(self: *const App, target: apprt.Target) void {
|
||||
const func = self.opts.equalize_splits orelse {
|
||||
log.info("runtime embedder does not support equalize splits", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
switch (target) {
|
||||
.app => func(null),
|
||||
.surface => |v| func(v.rt_surface.userdata),
|
||||
}
|
||||
}
|
||||
|
||||
fn toggleSplitZoom(self: *const App, target: apprt.Target) void {
|
||||
const func = self.opts.toggle_split_zoom orelse {
|
||||
log.info("runtime embedder does not support split zoom", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
switch (target) {
|
||||
.app => func(null),
|
||||
.surface => |v| func(v.rt_surface.userdata),
|
||||
}
|
||||
}
|
||||
|
||||
fn controlInspector(
|
||||
self: *const App,
|
||||
target: apprt.Target,
|
||||
value: apprt.action.Inspector,
|
||||
) void {
|
||||
const func = self.opts.control_inspector orelse {
|
||||
log.info("runtime embedder does not support the terminal inspector", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
switch (target) {
|
||||
.app => {},
|
||||
.surface => |v| func(v.rt_surface.userdata, value),
|
||||
}
|
||||
}
|
||||
|
||||
fn showDesktopNotification(
|
||||
self: *const App,
|
||||
target: apprt.Target,
|
||||
notification: apprt.action.DesktopNotification,
|
||||
) void {
|
||||
const func = self.opts.show_desktop_notification orelse {
|
||||
log.info("runtime embedder does not support show_desktop_notification", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
func(switch (target) {
|
||||
.app => null,
|
||||
.surface => |v| v.rt_surface.userdata,
|
||||
}, notification.title, notification.body);
|
||||
}
|
||||
|
||||
fn setPasswordInput(self: *App, target: apprt.Target, v: apprt.action.SecureInput) void {
|
||||
switch (v) {
|
||||
inline .on, .off => |tag| {
|
||||
const func = self.opts.set_password_input orelse {
|
||||
log.info("runtime embedder does not support set_password_input", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
func(switch (target) {
|
||||
.app => null,
|
||||
.surface => |surface| surface.rt_surface.userdata,
|
||||
}, switch (tag) {
|
||||
.on => true,
|
||||
.off => false,
|
||||
else => comptime unreachable,
|
||||
});
|
||||
},
|
||||
|
||||
.toggle => {
|
||||
const func = self.opts.toggle_secure_input orelse {
|
||||
log.info("runtime embedder does not support toggle_secure_input", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
func();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform a given action.
|
||||
pub fn performAction(
|
||||
self: *App,
|
||||
target: apprt.Target,
|
||||
comptime action: apprt.Action.Key,
|
||||
value: apprt.Action.Value(action),
|
||||
) !void {
|
||||
switch (action) {
|
||||
.new_window => _ = try self.newWindow(switch (target) {
|
||||
.app => null,
|
||||
.surface => |v| v,
|
||||
}),
|
||||
.toggle_fullscreen => self.toggleFullscreen(target, value),
|
||||
|
||||
.new_tab => self.newTab(target),
|
||||
.goto_tab => self.gotoTab(target, value),
|
||||
.new_split => self.newSplit(target, value),
|
||||
.resize_split => self.resizeSplit(target, value),
|
||||
.equalize_splits => self.equalizeSplits(target),
|
||||
.toggle_split_zoom => self.toggleSplitZoom(target),
|
||||
.goto_split => self.gotoSplit(target, value),
|
||||
.open_config => try configpkg.edit.open(self.core_app.alloc),
|
||||
.inspector => self.controlInspector(target, value),
|
||||
.desktop_notification => self.showDesktopNotification(target, value),
|
||||
.secure_input => self.setPasswordInput(target, value),
|
||||
|
||||
// Unimplemented
|
||||
.present_terminal,
|
||||
.close_all_windows,
|
||||
.toggle_window_decorations,
|
||||
.quit_timer,
|
||||
=> log.warn("unimplemented action={}", .{action}),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Platform-specific configuration for libghostty.
|
||||
@ -723,25 +949,6 @@ pub const Surface = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn controlInspector(self: *const Surface, mode: input.InspectorMode) void {
|
||||
const func = self.app.opts.control_inspector orelse {
|
||||
log.info("runtime embedder does not support the terminal inspector", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
func(self.userdata, mode);
|
||||
}
|
||||
|
||||
pub fn newSplit(self: *const Surface, direction: apprt.SplitDirection) !void {
|
||||
const func = self.app.opts.new_split orelse {
|
||||
log.info("runtime embedder does not support splits", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
const options = self.newSurfaceOptions();
|
||||
func(self.userdata, direction, options);
|
||||
}
|
||||
|
||||
pub fn close(self: *const Surface, process_alive: bool) void {
|
||||
const func = self.app.opts.close_surface orelse {
|
||||
log.info("runtime embedder does not support closing a surface", .{});
|
||||
@ -751,42 +958,6 @@ pub const Surface = struct {
|
||||
func(self.userdata, process_alive);
|
||||
}
|
||||
|
||||
pub fn gotoSplit(self: *const Surface, direction: input.SplitFocusDirection) void {
|
||||
const func = self.app.opts.focus_split orelse {
|
||||
log.info("runtime embedder does not support focus split", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
func(self.userdata, direction);
|
||||
}
|
||||
|
||||
pub fn resizeSplit(self: *const Surface, direction: input.SplitResizeDirection, amount: u16) void {
|
||||
const func = self.app.opts.resize_split orelse {
|
||||
log.info("runtime embedder does not support resize split", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
func(self.userdata, direction, amount);
|
||||
}
|
||||
|
||||
pub fn equalizeSplits(self: *const Surface) void {
|
||||
const func = self.app.opts.equalize_splits orelse {
|
||||
log.info("runtime embedder does not support equalize splits", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
func(self.userdata);
|
||||
}
|
||||
|
||||
pub fn toggleSplitZoom(self: *const Surface) void {
|
||||
const func = self.app.opts.toggle_split_zoom orelse {
|
||||
log.info("runtime embedder does not support split zoom", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
func(self.userdata);
|
||||
}
|
||||
|
||||
pub fn getContentScale(self: *const Surface) !apprt.ContentScale {
|
||||
return self.content_scale;
|
||||
}
|
||||
@ -1065,53 +1236,7 @@ pub const Surface = struct {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn gotoTab(self: *Surface, tab: apprt.GotoTab) void {
|
||||
const func = self.app.opts.goto_tab orelse {
|
||||
log.info("runtime embedder does not goto_tab", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
func(self.userdata, tab);
|
||||
}
|
||||
|
||||
pub fn toggleFullscreen(self: *Surface, nonNativeFullscreen: configpkg.NonNativeFullscreen) void {
|
||||
const func = self.app.opts.toggle_fullscreen orelse {
|
||||
log.info("runtime embedder does not toggle_fullscreen", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
func(self.userdata, nonNativeFullscreen);
|
||||
}
|
||||
|
||||
pub fn toggleSecureInput(self: *Surface) void {
|
||||
const func = self.app.opts.toggle_secure_input orelse {
|
||||
log.info("runtime embedder does not toggle_secure_input", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
func();
|
||||
}
|
||||
|
||||
pub fn setPasswordInput(self: *Surface, v: bool) void {
|
||||
const func = self.app.opts.set_password_input orelse {
|
||||
log.info("runtime embedder does not set_password_input", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
func(self.userdata, v);
|
||||
}
|
||||
|
||||
pub fn newTab(self: *const Surface) !void {
|
||||
const func = self.app.opts.new_tab orelse {
|
||||
log.info("runtime embedder does not support new_tab", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
const options = self.newSurfaceOptions();
|
||||
func(self.userdata, options);
|
||||
}
|
||||
|
||||
pub fn newWindow(self: *const Surface) !void {
|
||||
fn newWindow(self: *const Surface) !void {
|
||||
const func = self.app.opts.new_window orelse {
|
||||
log.info("runtime embedder does not support new_window", .{});
|
||||
return;
|
||||
@ -1166,20 +1291,6 @@ pub const Surface = struct {
|
||||
return .{ .x = pos.x * scale.x, .y = pos.y * scale.y };
|
||||
}
|
||||
|
||||
/// Show a desktop notification.
|
||||
pub fn showDesktopNotification(
|
||||
self: *const Surface,
|
||||
title: [:0]const u8,
|
||||
body: [:0]const u8,
|
||||
) !void {
|
||||
const func = self.app.opts.show_desktop_notification orelse {
|
||||
log.info("runtime embedder does not support show_desktop_notification", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
func(self.userdata, title, body);
|
||||
}
|
||||
|
||||
/// Update the health of the renderer.
|
||||
pub fn updateRendererHealth(self: *const Surface, health: renderer.Health) void {
|
||||
const func = self.app.opts.update_renderer_health orelse {
|
||||
@ -1573,7 +1684,7 @@ pub const CAPI = struct {
|
||||
|
||||
/// Open the configuration.
|
||||
export fn ghostty_app_open_config(v: *App) void {
|
||||
_ = v.core_app.openConfig(v) catch |err| {
|
||||
v.performAction(.app, .open_config, {}) catch |err| {
|
||||
log.err("error reloading config err={}", .{err});
|
||||
return;
|
||||
};
|
||||
@ -1864,26 +1975,61 @@ pub const CAPI = struct {
|
||||
}
|
||||
|
||||
/// Request that the surface split in the given direction.
|
||||
export fn ghostty_surface_split(ptr: *Surface, direction: apprt.SplitDirection) void {
|
||||
ptr.newSplit(direction) catch {};
|
||||
export fn ghostty_surface_split(ptr: *Surface, direction: apprt.action.SplitDirection) void {
|
||||
ptr.app.performAction(
|
||||
.{ .surface = &ptr.core_surface },
|
||||
.new_split,
|
||||
direction,
|
||||
) catch |err| {
|
||||
log.err("error creating new split err={}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
/// Focus on the next split (if any).
|
||||
export fn ghostty_surface_split_focus(ptr: *Surface, direction: input.SplitFocusDirection) void {
|
||||
ptr.gotoSplit(direction);
|
||||
export fn ghostty_surface_split_focus(
|
||||
ptr: *Surface,
|
||||
direction: apprt.action.GotoSplit,
|
||||
) void {
|
||||
ptr.app.performAction(
|
||||
.{ .surface = &ptr.core_surface },
|
||||
.goto_split,
|
||||
direction,
|
||||
) catch |err| {
|
||||
log.err("error creating new split err={}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
/// Resize the current split by moving the split divider in the given
|
||||
/// direction. `direction` specifies which direction the split divider will
|
||||
/// move relative to the focused split. `amount` is a fractional value
|
||||
/// between 0 and 1 that specifies by how much the divider will move.
|
||||
export fn ghostty_surface_split_resize(ptr: *Surface, direction: input.SplitResizeDirection, amount: u16) void {
|
||||
ptr.resizeSplit(direction, amount);
|
||||
export fn ghostty_surface_split_resize(
|
||||
ptr: *Surface,
|
||||
direction: apprt.action.ResizeSplit.Direction,
|
||||
amount: u16,
|
||||
) void {
|
||||
ptr.app.performAction(
|
||||
.{ .surface = &ptr.core_surface },
|
||||
.resize_split,
|
||||
.{ .direction = direction, .amount = amount },
|
||||
) catch |err| {
|
||||
log.err("error resizing split err={}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
/// Equalize the size of all splits in the current window.
|
||||
export fn ghostty_surface_split_equalize(ptr: *Surface) void {
|
||||
ptr.equalizeSplits();
|
||||
ptr.app.performAction(
|
||||
.{ .surface = &ptr.core_surface },
|
||||
.equalize_splits,
|
||||
{},
|
||||
) catch |err| {
|
||||
log.err("error equalizing splits err={}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
/// Invoke an action on the surface.
|
||||
|
@ -127,9 +127,46 @@ pub const App = struct {
|
||||
glfw.postEmptyEvent();
|
||||
}
|
||||
|
||||
/// Open the configuration in the system editor.
|
||||
pub fn openConfig(self: *App) !void {
|
||||
try configpkg.edit.open(self.app.alloc);
|
||||
/// Perform a given action.
|
||||
pub fn performAction(
|
||||
self: *App,
|
||||
target: apprt.Target,
|
||||
comptime action: apprt.Action.Key,
|
||||
value: apprt.Action.Value(action),
|
||||
) !void {
|
||||
_ = value;
|
||||
|
||||
switch (action) {
|
||||
.new_window => _ = try self.newSurface(switch (target) {
|
||||
.app => null,
|
||||
.surface => |v| v,
|
||||
}),
|
||||
|
||||
.new_tab => try self.newTab(switch (target) {
|
||||
.app => null,
|
||||
.surface => |v| v,
|
||||
}),
|
||||
|
||||
.toggle_fullscreen => self.toggleFullscreen(target),
|
||||
|
||||
.open_config => try configpkg.edit.open(self.app.alloc),
|
||||
|
||||
// Unimplemented
|
||||
.new_split,
|
||||
.goto_split,
|
||||
.resize_split,
|
||||
.equalize_splits,
|
||||
.toggle_split_zoom,
|
||||
.present_terminal,
|
||||
.close_all_windows,
|
||||
.toggle_window_decorations,
|
||||
.goto_tab,
|
||||
.inspector,
|
||||
.quit_timer,
|
||||
.secure_input,
|
||||
.desktop_notification,
|
||||
=> log.info("unimplemented action={}", .{action}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reload the configuration. This should return the new configuration.
|
||||
@ -150,8 +187,12 @@ pub const App = struct {
|
||||
}
|
||||
|
||||
/// Toggle the window to fullscreen mode.
|
||||
pub fn toggleFullscreen(self: *App, surface: *Surface) void {
|
||||
fn toggleFullscreen(self: *App, target: apprt.Target) void {
|
||||
_ = self;
|
||||
const surface: *Surface = switch (target) {
|
||||
.app => return,
|
||||
.surface => |v| v.rt_surface,
|
||||
};
|
||||
const win = surface.window;
|
||||
|
||||
if (surface.isFullscreen()) {
|
||||
@ -195,18 +236,18 @@ pub const App = struct {
|
||||
win.setMonitor(monitor, 0, 0, video_mode.getWidth(), video_mode.getHeight(), 0);
|
||||
}
|
||||
|
||||
/// Create a new window for the app.
|
||||
pub fn newWindow(self: *App, parent_: ?*CoreSurface) !void {
|
||||
_ = try self.newSurface(parent_);
|
||||
}
|
||||
|
||||
/// Create a new tab in the parent surface.
|
||||
fn newTab(self: *App, parent: *CoreSurface) !void {
|
||||
fn newTab(self: *App, parent_: ?*CoreSurface) !void {
|
||||
if (!Darwin.enabled) {
|
||||
log.warn("tabbing is not supported on this platform", .{});
|
||||
return;
|
||||
}
|
||||
|
||||
const parent = parent_ orelse {
|
||||
_ = try self.newSurface(null);
|
||||
return;
|
||||
};
|
||||
|
||||
// Create the new window
|
||||
const window = try self.newSurface(parent);
|
||||
|
||||
@ -370,7 +411,6 @@ pub const Surface = struct {
|
||||
/// Initialize the surface into the given self pointer. This gives a
|
||||
/// stable pointer to the destination that can be used for callbacks.
|
||||
pub fn init(self: *Surface, app: *App) !void {
|
||||
|
||||
// Create our window
|
||||
const win = glfw.Window.create(
|
||||
640,
|
||||
@ -525,20 +565,11 @@ pub const Surface = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new tab in the window containing this surface.
|
||||
pub fn newTab(self: *Surface) !void {
|
||||
try self.app.newTab(&self.core_surface);
|
||||
}
|
||||
|
||||
/// Checks if the glfw window is in fullscreen.
|
||||
pub fn isFullscreen(self: *Surface) bool {
|
||||
return self.window.getMonitor() != null;
|
||||
}
|
||||
|
||||
pub fn toggleFullscreen(self: *Surface, _: Config.NonNativeFullscreen) void {
|
||||
self.app.toggleFullscreen(self);
|
||||
}
|
||||
|
||||
/// Close this surface.
|
||||
pub fn close(self: *Surface, processActive: bool) void {
|
||||
_ = processActive;
|
||||
@ -683,6 +714,23 @@ pub const Surface = struct {
|
||||
self.window.setInputModeCursor(if (visible) .normal else .hidden);
|
||||
}
|
||||
|
||||
pub fn updateRendererHealth(self: *const Surface, health: renderer.Health) void {
|
||||
// We don't support this in GLFW.
|
||||
_ = self;
|
||||
_ = health;
|
||||
}
|
||||
|
||||
pub fn supportsClipboard(
|
||||
self: *const Surface,
|
||||
clipboard_type: apprt.Clipboard,
|
||||
) bool {
|
||||
_ = self;
|
||||
return switch (clipboard_type) {
|
||||
.standard => true,
|
||||
.selection, .primary => comptime builtin.os.tag == .linux,
|
||||
};
|
||||
}
|
||||
|
||||
/// Start an async clipboard request.
|
||||
pub fn clipboardRequest(
|
||||
self: *Surface,
|
||||
|
@ -27,6 +27,7 @@ const Surface = @import("Surface.zig");
|
||||
const Window = @import("Window.zig");
|
||||
const ConfigErrorsWindow = @import("ConfigErrorsWindow.zig");
|
||||
const ClipboardConfirmationWindow = @import("ClipboardConfirmationWindow.zig");
|
||||
const Split = @import("Split.zig");
|
||||
const c = @import("c.zig").c;
|
||||
const inspector = @import("inspector.zig");
|
||||
const key = @import("key.zig");
|
||||
@ -341,9 +342,249 @@ pub fn terminate(self: *App) void {
|
||||
self.config.deinit();
|
||||
}
|
||||
|
||||
/// Open the configuration in the system editor.
|
||||
pub fn openConfig(self: *App) !void {
|
||||
try configpkg.edit.open(self.core_app.alloc);
|
||||
/// Perform a given action.
|
||||
pub fn performAction(
|
||||
self: *App,
|
||||
target: apprt.Target,
|
||||
comptime action: apprt.Action.Key,
|
||||
value: apprt.Action.Value(action),
|
||||
) !void {
|
||||
switch (action) {
|
||||
.new_window => _ = try self.newWindow(switch (target) {
|
||||
.app => null,
|
||||
.surface => |v| v,
|
||||
}),
|
||||
.toggle_fullscreen => self.toggleFullscreen(target, value),
|
||||
|
||||
.new_tab => try self.newTab(target),
|
||||
.goto_tab => self.gotoTab(target, value),
|
||||
.new_split => try self.newSplit(target, value),
|
||||
.resize_split => self.resizeSplit(target, value),
|
||||
.equalize_splits => self.equalizeSplits(target),
|
||||
.goto_split => self.gotoSplit(target, value),
|
||||
.open_config => try configpkg.edit.open(self.core_app.alloc),
|
||||
.inspector => self.controlInspector(target, value),
|
||||
.desktop_notification => self.showDesktopNotification(target, value),
|
||||
.present_terminal => self.presentTerminal(target),
|
||||
.toggle_window_decorations => self.toggleWindowDecorations(target),
|
||||
.quit_timer => self.quitTimer(value),
|
||||
|
||||
// Unimplemented
|
||||
.close_all_windows,
|
||||
.toggle_split_zoom,
|
||||
.secure_input,
|
||||
=> log.warn("unimplemented action={}", .{action}),
|
||||
}
|
||||
}
|
||||
|
||||
fn newTab(_: *App, target: apprt.Target) !void {
|
||||
switch (target) {
|
||||
.app => {},
|
||||
.surface => |v| {
|
||||
const window = v.rt_surface.container.window() orelse {
|
||||
log.info(
|
||||
"new_tab invalid for container={s}",
|
||||
.{@tagName(v.rt_surface.container)},
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
try window.newTab(v);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn gotoTab(_: *App, target: apprt.Target, tab: apprt.action.GotoTab) void {
|
||||
switch (target) {
|
||||
.app => {},
|
||||
.surface => |v| {
|
||||
const window = v.rt_surface.container.window() orelse {
|
||||
log.info(
|
||||
"gotoTab invalid for container={s}",
|
||||
.{@tagName(v.rt_surface.container)},
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
switch (tab) {
|
||||
.previous => window.gotoPreviousTab(v.rt_surface),
|
||||
.next => window.gotoNextTab(v.rt_surface),
|
||||
.last => window.gotoLastTab(),
|
||||
else => window.gotoTab(@intCast(@intFromEnum(tab))),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn newSplit(
|
||||
self: *App,
|
||||
target: apprt.Target,
|
||||
direction: apprt.action.SplitDirection,
|
||||
) !void {
|
||||
switch (target) {
|
||||
.app => {},
|
||||
.surface => |v| {
|
||||
const alloc = self.core_app.alloc;
|
||||
_ = try Split.create(alloc, v.rt_surface, direction);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn equalizeSplits(_: *App, target: apprt.Target) void {
|
||||
switch (target) {
|
||||
.app => {},
|
||||
.surface => |v| {
|
||||
const tab = v.rt_surface.container.tab() orelse return;
|
||||
const top_split = switch (tab.elem) {
|
||||
.split => |s| s,
|
||||
else => return,
|
||||
};
|
||||
_ = top_split.equalize();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn gotoSplit(
|
||||
_: *const App,
|
||||
target: apprt.Target,
|
||||
direction: apprt.action.GotoSplit,
|
||||
) void {
|
||||
switch (target) {
|
||||
.app => {},
|
||||
.surface => |v| {
|
||||
const s = v.rt_surface.container.split() orelse return;
|
||||
const map = s.directionMap(switch (v.rt_surface.container) {
|
||||
.split_tl => .top_left,
|
||||
.split_br => .bottom_right,
|
||||
.none, .tab_ => unreachable,
|
||||
});
|
||||
const surface_ = map.get(direction) orelse return;
|
||||
if (surface_) |surface| surface.grabFocus();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn resizeSplit(
|
||||
_: *const App,
|
||||
target: apprt.Target,
|
||||
resize: apprt.action.ResizeSplit,
|
||||
) void {
|
||||
switch (target) {
|
||||
.app => {},
|
||||
.surface => |v| {
|
||||
const s = v.rt_surface.container.firstSplitWithOrientation(
|
||||
Split.Orientation.fromResizeDirection(resize.direction),
|
||||
) orelse return;
|
||||
s.moveDivider(resize.direction, resize.amount);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn presentTerminal(
|
||||
_: *const App,
|
||||
target: apprt.Target,
|
||||
) void {
|
||||
switch (target) {
|
||||
.app => {},
|
||||
.surface => |v| v.rt_surface.present(),
|
||||
}
|
||||
}
|
||||
|
||||
fn controlInspector(
|
||||
_: *const App,
|
||||
target: apprt.Target,
|
||||
mode: apprt.action.Inspector,
|
||||
) void {
|
||||
const surface: *Surface = switch (target) {
|
||||
.app => return,
|
||||
.surface => |v| v.rt_surface,
|
||||
};
|
||||
|
||||
surface.controlInspector(mode);
|
||||
}
|
||||
|
||||
fn toggleFullscreen(
|
||||
_: *App,
|
||||
target: apprt.Target,
|
||||
_: apprt.action.Fullscreen,
|
||||
) void {
|
||||
switch (target) {
|
||||
.app => {},
|
||||
.surface => |v| {
|
||||
const window = v.rt_surface.container.window() orelse {
|
||||
log.info(
|
||||
"toggleFullscreen invalid for container={s}",
|
||||
.{@tagName(v.rt_surface.container)},
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
window.toggleFullscreen();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn toggleWindowDecorations(
|
||||
_: *App,
|
||||
target: apprt.Target,
|
||||
) void {
|
||||
switch (target) {
|
||||
.app => {},
|
||||
.surface => |v| {
|
||||
const window = v.rt_surface.container.window() orelse {
|
||||
log.info(
|
||||
"toggleFullscreen invalid for container={s}",
|
||||
.{@tagName(v.rt_surface.container)},
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
window.toggleWindowDecorations();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn quitTimer(self: *App, mode: apprt.action.QuitTimer) void {
|
||||
switch (mode) {
|
||||
.start => self.startQuitTimer(),
|
||||
.stop => self.stopQuitTimer(),
|
||||
}
|
||||
}
|
||||
|
||||
fn showDesktopNotification(
|
||||
self: *App,
|
||||
target: apprt.Target,
|
||||
n: apprt.action.DesktopNotification,
|
||||
) void {
|
||||
// Set a default title if we don't already have one
|
||||
const t = switch (n.title.len) {
|
||||
0 => "Ghostty",
|
||||
else => n.title,
|
||||
};
|
||||
|
||||
const notification = c.g_notification_new(t.ptr);
|
||||
defer c.g_object_unref(notification);
|
||||
c.g_notification_set_body(notification, n.body.ptr);
|
||||
|
||||
const icon = c.g_themed_icon_new("com.mitchellh.ghostty");
|
||||
defer c.g_object_unref(icon);
|
||||
c.g_notification_set_icon(notification, icon);
|
||||
|
||||
const pointer = c.g_variant_new_uint64(switch (target) {
|
||||
.app => 0,
|
||||
.surface => |v| @intFromPtr(v),
|
||||
});
|
||||
c.g_notification_set_default_action_and_target_value(
|
||||
notification,
|
||||
"app.present-surface",
|
||||
pointer,
|
||||
);
|
||||
|
||||
const g_app: *c.GApplication = @ptrCast(self.app);
|
||||
|
||||
// We set the notification ID to the body content. If the content is the
|
||||
// same, this notification may replace a previous notification
|
||||
c.g_application_send_notification(g_app, n.body.ptr, notification);
|
||||
}
|
||||
|
||||
/// Reload the configuration. This should return the new configuration.
|
||||
@ -565,9 +806,9 @@ pub fn gtkQuitTimerExpired(ud: ?*anyopaque) callconv(.C) c.gboolean {
|
||||
}
|
||||
|
||||
/// This will get called when there are no more open surfaces.
|
||||
pub fn startQuitTimer(self: *App) void {
|
||||
fn startQuitTimer(self: *App) void {
|
||||
// Cancel any previous timer.
|
||||
self.cancelQuitTimer();
|
||||
self.stopQuitTimer();
|
||||
|
||||
// This is a no-op unless we are configured to quit after last window is closed.
|
||||
if (!self.config.@"quit-after-last-window-closed") return;
|
||||
@ -582,7 +823,7 @@ pub fn startQuitTimer(self: *App) void {
|
||||
}
|
||||
|
||||
/// This will get called when a new surface gets opened.
|
||||
pub fn cancelQuitTimer(self: *App) void {
|
||||
fn stopQuitTimer(self: *App) void {
|
||||
switch (self.quit_timer) {
|
||||
.off => {},
|
||||
.expired => self.quit_timer = .{ .off = {} },
|
||||
@ -608,7 +849,7 @@ pub fn redrawInspector(self: *App, surface: *Surface) void {
|
||||
}
|
||||
|
||||
/// Called by CoreApp to create a new window with a new surface.
|
||||
pub fn newWindow(self: *App, parent_: ?*CoreSurface) !void {
|
||||
fn newWindow(self: *App, parent_: ?*CoreSurface) !void {
|
||||
const alloc = self.core_app.alloc;
|
||||
|
||||
// Allocate a fixed pointer for our window. We try to minimize
|
||||
@ -857,8 +1098,12 @@ fn gtkActionPresentSurface(
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert that u64 to pointer to a core surface.
|
||||
const surface: *CoreSurface = @ptrFromInt(c.g_variant_get_uint64(parameter));
|
||||
// Convert that u64 to pointer to a core surface. A value of zero
|
||||
// means that there was no target surface for the notification so
|
||||
// we dont' focus any surface.
|
||||
const ptr_int: u64 = c.g_variant_get_uint64(parameter);
|
||||
if (ptr_int == 0) return;
|
||||
const surface: *CoreSurface = @ptrFromInt(ptr_int);
|
||||
|
||||
// Send a message through the core app mailbox rather than presenting the
|
||||
// surface directly so that it can validate that the surface pointer is
|
||||
|
@ -7,7 +7,6 @@ const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
const apprt = @import("../../apprt.zig");
|
||||
const font = @import("../../font/main.zig");
|
||||
const input = @import("../../input.zig");
|
||||
const CoreSurface = @import("../../Surface.zig");
|
||||
|
||||
const Surface = @import("Surface.zig");
|
||||
@ -21,14 +20,14 @@ pub const Orientation = enum {
|
||||
horizontal,
|
||||
vertical,
|
||||
|
||||
pub fn fromDirection(direction: apprt.SplitDirection) Orientation {
|
||||
pub fn fromDirection(direction: apprt.action.SplitDirection) Orientation {
|
||||
return switch (direction) {
|
||||
.right => .horizontal,
|
||||
.down => .vertical,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn fromResizeDirection(direction: input.SplitResizeDirection) Orientation {
|
||||
pub fn fromResizeDirection(direction: apprt.action.ResizeSplit.Direction) Orientation {
|
||||
return switch (direction) {
|
||||
.up, .down => .vertical,
|
||||
.left, .right => .horizontal,
|
||||
@ -58,7 +57,7 @@ bottom_right: Surface.Container.Elem,
|
||||
pub fn create(
|
||||
alloc: Allocator,
|
||||
sibling: *Surface,
|
||||
direction: apprt.SplitDirection,
|
||||
direction: apprt.action.SplitDirection,
|
||||
) !*Split {
|
||||
var split = try alloc.create(Split);
|
||||
errdefer alloc.destroy(split);
|
||||
@ -69,7 +68,7 @@ pub fn create(
|
||||
pub fn init(
|
||||
self: *Split,
|
||||
sibling: *Surface,
|
||||
direction: apprt.SplitDirection,
|
||||
direction: apprt.action.SplitDirection,
|
||||
) !void {
|
||||
// Create the new child surface for the other direction.
|
||||
const alloc = sibling.app.core_app.alloc;
|
||||
@ -164,7 +163,11 @@ fn removeChild(
|
||||
}
|
||||
|
||||
/// Move the divider in the given direction by the given amount.
|
||||
pub fn moveDivider(self: *Split, direction: input.SplitResizeDirection, amount: u16) void {
|
||||
pub fn moveDivider(
|
||||
self: *Split,
|
||||
direction: apprt.action.ResizeSplit.Direction,
|
||||
amount: u16,
|
||||
) void {
|
||||
const min_pos = 10;
|
||||
|
||||
const pos = c.gtk_paned_get_position(self.paned);
|
||||
@ -263,7 +266,7 @@ fn updateChildren(self: *const Split) void {
|
||||
|
||||
/// A mapping of direction to the element (if any) in that direction.
|
||||
pub const DirectionMap = std.EnumMap(
|
||||
input.SplitFocusDirection,
|
||||
apprt.action.GotoSplit,
|
||||
?*Surface,
|
||||
);
|
||||
|
||||
|
@ -9,6 +9,7 @@ const configpkg = @import("../../config.zig");
|
||||
const apprt = @import("../../apprt.zig");
|
||||
const font = @import("../../font/main.zig");
|
||||
const input = @import("../../input.zig");
|
||||
const renderer = @import("../../renderer.zig");
|
||||
const terminal = @import("../../terminal/main.zig");
|
||||
const CoreSurface = @import("../../Surface.zig");
|
||||
const internal_os = @import("../../os/main.zig");
|
||||
@ -688,7 +689,10 @@ pub fn close(self: *Surface, processActive: bool) void {
|
||||
c.gtk_widget_show(alert);
|
||||
}
|
||||
|
||||
pub fn controlInspector(self: *Surface, mode: input.InspectorMode) void {
|
||||
pub fn controlInspector(
|
||||
self: *Surface,
|
||||
mode: apprt.action.Inspector,
|
||||
) void {
|
||||
const show = switch (mode) {
|
||||
.toggle => self.inspector == null,
|
||||
.show => true,
|
||||
@ -715,30 +719,6 @@ pub fn controlInspector(self: *Surface, mode: input.InspectorMode) void {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toggleFullscreen(self: *Surface, mac_non_native: configpkg.NonNativeFullscreen) void {
|
||||
const window = self.container.window() orelse {
|
||||
log.info(
|
||||
"toggleFullscreen invalid for container={s}",
|
||||
.{@tagName(self.container)},
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
window.toggleFullscreen(mac_non_native);
|
||||
}
|
||||
|
||||
pub fn toggleWindowDecorations(self: *Surface) void {
|
||||
const window = self.container.window() orelse {
|
||||
log.info(
|
||||
"toggleWindowDecorations invalid for container={s}",
|
||||
.{@tagName(self.container)},
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
window.toggleWindowDecorations();
|
||||
}
|
||||
|
||||
pub fn getTitleLabel(self: *Surface) ?*c.GtkWidget {
|
||||
switch (self.title) {
|
||||
.none => return null,
|
||||
@ -749,69 +729,6 @@ pub fn getTitleLabel(self: *Surface) ?*c.GtkWidget {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn newSplit(self: *Surface, direction: apprt.SplitDirection) !void {
|
||||
const alloc = self.app.core_app.alloc;
|
||||
_ = try Split.create(alloc, self, direction);
|
||||
}
|
||||
|
||||
pub fn gotoSplit(self: *const Surface, direction: input.SplitFocusDirection) void {
|
||||
const s = self.container.split() orelse return;
|
||||
const map = s.directionMap(switch (self.container) {
|
||||
.split_tl => .top_left,
|
||||
.split_br => .bottom_right,
|
||||
.none, .tab_ => unreachable,
|
||||
});
|
||||
const surface_ = map.get(direction) orelse return;
|
||||
if (surface_) |surface| surface.grabFocus();
|
||||
}
|
||||
|
||||
pub fn resizeSplit(self: *const Surface, direction: input.SplitResizeDirection, amount: u16) void {
|
||||
const s = self.container.firstSplitWithOrientation(
|
||||
Split.Orientation.fromResizeDirection(direction),
|
||||
) orelse return;
|
||||
s.moveDivider(direction, amount);
|
||||
}
|
||||
|
||||
pub fn equalizeSplits(self: *const Surface) void {
|
||||
const tab = self.container.tab() orelse return;
|
||||
const top_split = switch (tab.elem) {
|
||||
.split => |s| s,
|
||||
else => return,
|
||||
};
|
||||
_ = top_split.equalize();
|
||||
}
|
||||
|
||||
pub fn newTab(self: *Surface) !void {
|
||||
const window = self.container.window() orelse {
|
||||
log.info("surface cannot create new tab when not attached to a window", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
try window.newTab(&self.core_surface);
|
||||
}
|
||||
|
||||
pub fn hasTabs(self: *const Surface) bool {
|
||||
const window = self.container.window() orelse return false;
|
||||
return window.hasTabs();
|
||||
}
|
||||
|
||||
pub fn gotoTab(self: *Surface, tab: apprt.GotoTab) void {
|
||||
const window = self.container.window() orelse {
|
||||
log.info(
|
||||
"gotoTab invalid for container={s}",
|
||||
.{@tagName(self.container)},
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
switch (tab) {
|
||||
.previous => window.gotoPreviousTab(self),
|
||||
.next => window.gotoNextTab(self),
|
||||
.last => window.gotoLastTab(),
|
||||
else => window.gotoTab(@intCast(@intFromEnum(tab))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setShouldClose(self: *Surface) void {
|
||||
_ = self;
|
||||
}
|
||||
@ -1026,6 +943,19 @@ pub fn mouseOverLink(self: *Surface, uri_: ?[]const u8) void {
|
||||
self.url_widget = URLWidget.init(self, uriZ);
|
||||
}
|
||||
|
||||
pub fn supportsClipboard(
|
||||
self: *const Surface,
|
||||
clipboard_type: apprt.Clipboard,
|
||||
) bool {
|
||||
_ = self;
|
||||
return switch (clipboard_type) {
|
||||
.standard,
|
||||
.selection,
|
||||
.primary,
|
||||
=> true,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn clipboardRequest(
|
||||
self: *Surface,
|
||||
clipboard_type: apprt.Clipboard,
|
||||
@ -1980,7 +1910,7 @@ fn translateMods(state: c.GdkModifierType) input.Mods {
|
||||
return mods;
|
||||
}
|
||||
|
||||
pub fn presentSurface(self: *Surface) void {
|
||||
pub fn present(self: *Surface) void {
|
||||
if (self.container.window()) |window| {
|
||||
if (self.container.tab()) |tab| {
|
||||
if (window.notebook.getTabPosition(tab)) |position|
|
||||
@ -1988,5 +1918,12 @@ pub fn presentSurface(self: *Surface) void {
|
||||
}
|
||||
c.gtk_window_present(window.window);
|
||||
}
|
||||
|
||||
self.grabFocus();
|
||||
}
|
||||
|
||||
pub fn updateRendererHealth(self: *const Surface, health: renderer.Health) void {
|
||||
// We don't support this in GTK.
|
||||
_ = self;
|
||||
_ = health;
|
||||
}
|
||||
|
@ -421,11 +421,6 @@ pub fn closeTab(self: *Window, tab: *Tab) void {
|
||||
self.notebook.closeTab(tab);
|
||||
}
|
||||
|
||||
/// Returns true if this window has any tabs.
|
||||
pub fn hasTabs(self: *const Window) bool {
|
||||
return self.notebook.nPages() > 0;
|
||||
}
|
||||
|
||||
/// Go to the previous tab for a surface.
|
||||
pub fn gotoPreviousTab(self: *Window, surface: *Surface) void {
|
||||
const tab = surface.container.tab() orelse {
|
||||
@ -464,7 +459,7 @@ pub fn gotoTab(self: *Window, n: usize) void {
|
||||
}
|
||||
|
||||
/// Toggle fullscreen for this window.
|
||||
pub fn toggleFullscreen(self: *Window, _: configpkg.NonNativeFullscreen) void {
|
||||
pub fn toggleFullscreen(self: *Window) void {
|
||||
const is_fullscreen = c.gtk_window_is_fullscreen(self.window);
|
||||
if (is_fullscreen == 0) {
|
||||
c.gtk_window_fullscreen(self.window);
|
||||
|
@ -62,23 +62,6 @@ pub const DesktopNotification = struct {
|
||||
body: []const u8,
|
||||
};
|
||||
|
||||
/// The tab to jump to. This is non-exhaustive so that integer values represent
|
||||
/// the index (zero-based) of the tab to jump to. Negative values are special
|
||||
/// values.
|
||||
pub const GotoTab = enum(c_int) {
|
||||
previous = -1,
|
||||
next = -2,
|
||||
last = -3,
|
||||
_,
|
||||
};
|
||||
|
||||
// This is made extern (c_int) to make interop easier with our embedded
|
||||
// runtime. The small size cost doesn't make a difference in our union.
|
||||
pub const SplitDirection = enum(c_int) {
|
||||
right,
|
||||
down,
|
||||
};
|
||||
|
||||
/// The color scheme in use (light vs dark).
|
||||
pub const ColorScheme = enum(u2) {
|
||||
light = 0,
|
||||
|
@ -410,8 +410,7 @@ pub const Action = union(enum) {
|
||||
// Note: we don't support top or left yet
|
||||
};
|
||||
|
||||
// Extern because it is used in the embedded runtime ABI.
|
||||
pub const SplitFocusDirection = enum(c_int) {
|
||||
pub const SplitFocusDirection = enum {
|
||||
previous,
|
||||
next,
|
||||
|
||||
@ -421,8 +420,7 @@ pub const Action = union(enum) {
|
||||
right,
|
||||
};
|
||||
|
||||
// Extern because it is used in the embedded runtime ABI.
|
||||
pub const SplitResizeDirection = enum(c_int) {
|
||||
pub const SplitResizeDirection = enum {
|
||||
up,
|
||||
down,
|
||||
left,
|
||||
@ -440,7 +438,7 @@ pub const Action = union(enum) {
|
||||
};
|
||||
|
||||
// Extern because it is used in the embedded runtime ABI.
|
||||
pub const InspectorMode = enum(c_int) {
|
||||
pub const InspectorMode = enum {
|
||||
toggle,
|
||||
show,
|
||||
hide,
|
||||
|
Reference in New Issue
Block a user