mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-20 10:46:07 +03:00
apprt/gtk-ng: introduce a basic surface
This commit is contained in:
@ -31,6 +31,7 @@ pub const icon_sizes: []const comptime_int = &.{ 16, 32, 128, 256, 512, 1024 };
|
||||
/// These will be asserted to exist at runtime.
|
||||
pub const blueprints: []const Blueprint = &.{
|
||||
.{ .major = 1, .minor = 2, .name = "config-errors-dialog" },
|
||||
.{ .major = 1, .minor = 2, .name = "surface" },
|
||||
.{ .major = 1, .minor = 5, .name = "config-errors-dialog" },
|
||||
.{ .major = 1, .minor = 5, .name = "window" },
|
||||
};
|
||||
|
@ -3,10 +3,12 @@
|
||||
|
||||
const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
pub const Application = @import("class/application.zig").Application;
|
||||
pub const Window = @import("class/window.zig").Window;
|
||||
pub const Config = @import("class/config.zig").Config;
|
||||
pub const Surface = @import("class/surface.zig").Surface;
|
||||
|
||||
/// Unrefs the given GObject on the next event loop tick.
|
||||
///
|
||||
@ -60,6 +62,30 @@ pub fn Common(
|
||||
);
|
||||
}
|
||||
}).private else {};
|
||||
|
||||
/// Common class functions.
|
||||
pub const Class = struct {
|
||||
pub fn as(class: *Self.Class, comptime T: type) *T {
|
||||
return gobject.ext.as(T, class);
|
||||
}
|
||||
|
||||
/// Bind a template child to a private entry in the class.
|
||||
pub const bindTemplateChildPrivate = if (Private) |P| (struct {
|
||||
pub fn bindTemplateChildPrivate(
|
||||
class: *Self.Class,
|
||||
comptime name: [:0]const u8,
|
||||
comptime options: gtk.ext.BindTemplateChildOptions,
|
||||
) void {
|
||||
gtk.ext.impl_helpers.bindTemplateChildPrivate(
|
||||
class,
|
||||
name,
|
||||
P,
|
||||
P.offset,
|
||||
options,
|
||||
);
|
||||
}
|
||||
}).bindTemplateChildPrivate else {};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ const configpkg = @import("../../../config.zig");
|
||||
const internal_os = @import("../../../os/main.zig");
|
||||
const xev = @import("../../../global.zig").xev;
|
||||
const CoreConfig = configpkg.Config;
|
||||
const CoreSurface = @import("../../../Surface.zig");
|
||||
|
||||
const adw_version = @import("../adw_version.zig");
|
||||
const gtk_version = @import("../gtk_version.zig");
|
||||
@ -58,6 +59,25 @@ pub const Application = extern struct {
|
||||
.private = .{ .Type = Private, .offset = &Private.offset },
|
||||
});
|
||||
|
||||
pub const properties = struct {
|
||||
pub const config = struct {
|
||||
pub const name = "config";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
"config",
|
||||
Self,
|
||||
?*Config,
|
||||
.{
|
||||
.nick = "Config",
|
||||
.blurb = "The current active configuration for the application.",
|
||||
.default = null,
|
||||
.accessor = .{
|
||||
.getter = Self.getPropConfig,
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
const Private = struct {
|
||||
/// The apprt App. This is annoying that we need this it'd be
|
||||
/// nicer to just make THIS the apprt app but the current libghostty
|
||||
@ -88,6 +108,17 @@ pub const Application = extern struct {
|
||||
pub var offset: c_int = 0;
|
||||
};
|
||||
|
||||
/// Get this application as the default, allowing access to its
|
||||
/// properties globally.
|
||||
///
|
||||
/// This asserts that there is a default application and that the
|
||||
/// default application is a GhosttyApplication. The program would have
|
||||
/// to be in a very bad state for this to be violated.
|
||||
pub fn default() *Self {
|
||||
const app = gio.Application.getDefault().?;
|
||||
return gobject.ext.cast(Self, app).?;
|
||||
}
|
||||
|
||||
/// Creates a new Application instance.
|
||||
///
|
||||
/// This does a lot more work than a typical class instantiation,
|
||||
@ -121,14 +152,14 @@ pub const Application = extern struct {
|
||||
// the error in the diagnostics so it can be shown to the user.
|
||||
// We can still load a default which only fails for OOM, allowing
|
||||
// us to startup.
|
||||
var default: CoreConfig = try .default(alloc);
|
||||
errdefer default.deinit();
|
||||
try default.addDiagnosticFmt(
|
||||
var def: CoreConfig = try .default(alloc);
|
||||
errdefer def.deinit();
|
||||
try def.addDiagnosticFmt(
|
||||
"error loading user configuration: {}",
|
||||
.{err},
|
||||
);
|
||||
|
||||
break :err default;
|
||||
break :err def;
|
||||
};
|
||||
defer config.deinit();
|
||||
|
||||
@ -223,6 +254,13 @@ pub const Application = extern struct {
|
||||
if (priv.transient_cgroup_base) |base| alloc.free(base);
|
||||
}
|
||||
|
||||
/// The global allocator that all other classes should use by
|
||||
/// calling `Application.default().allocator()`. Zig code should prefer
|
||||
/// this wherever possible so we get leak detection in debug/tests.
|
||||
pub fn allocator(self: *Self) std.mem.Allocator {
|
||||
return self.private().core_app.alloc;
|
||||
}
|
||||
|
||||
/// Run the application. This is a replacement for `gio.Application.run`
|
||||
/// because we want more tight control over our event loop so we can
|
||||
/// integrate it with libghostty.
|
||||
@ -332,9 +370,16 @@ pub const Application = extern struct {
|
||||
value.config,
|
||||
),
|
||||
|
||||
.new_window => try Action.newWindow(
|
||||
self,
|
||||
switch (target) {
|
||||
.app => null,
|
||||
.surface => |v| v,
|
||||
},
|
||||
),
|
||||
|
||||
// Unimplemented
|
||||
.quit,
|
||||
.new_window,
|
||||
.close_window,
|
||||
.toggle_maximize,
|
||||
.toggle_fullscreen,
|
||||
@ -410,6 +455,27 @@ pub const Application = extern struct {
|
||||
try priv.core_app.updateConfig(priv.rt_app, &config);
|
||||
}
|
||||
|
||||
/// Returns the configuration for this application.
|
||||
///
|
||||
/// The reference count is increased.
|
||||
pub fn getConfig(self: *Self) *Config {
|
||||
var value = gobject.ext.Value.zero;
|
||||
gobject.Object.getProperty(
|
||||
self.as(gobject.Object),
|
||||
properties.config.name,
|
||||
&value,
|
||||
);
|
||||
|
||||
const obj = value.getObject().?;
|
||||
return gobject.ext.cast(Config, obj).?;
|
||||
}
|
||||
|
||||
fn getPropConfig(self: *Self) *Config {
|
||||
// Property return must not increase reference count since
|
||||
// the gobject getter handles this automatically.
|
||||
return self.private().config;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Virtual Methods
|
||||
|
||||
@ -421,6 +487,9 @@ pub const Application = extern struct {
|
||||
self.as(Parent),
|
||||
);
|
||||
|
||||
// Set ourselves as the default application.
|
||||
gio.Application.setDefault(self.as(gio.Application));
|
||||
|
||||
// Setup our event loop
|
||||
self.startupXev();
|
||||
|
||||
@ -581,14 +650,17 @@ pub const Application = extern struct {
|
||||
fn activate(self: *Self) callconv(.C) void {
|
||||
log.debug("activate", .{});
|
||||
|
||||
// Queue a new window
|
||||
const priv = self.private();
|
||||
_ = priv.core_app.mailbox.push(.{
|
||||
.new_window = .{},
|
||||
}, .{ .forever = {} });
|
||||
|
||||
// Call the parent activate method.
|
||||
gio.Application.virtual_methods.activate.call(
|
||||
Class.parent,
|
||||
self.as(Parent),
|
||||
);
|
||||
|
||||
// const win = Window.new(self);
|
||||
// gtk.Window.present(win.as(gtk.Window));
|
||||
}
|
||||
|
||||
fn dispose(self: *Self) callconv(.C) void {
|
||||
@ -697,10 +769,6 @@ pub const Application = extern struct {
|
||||
//----------------------------------------------------------------
|
||||
// Boilerplate/Noise
|
||||
|
||||
fn allocator(self: *Self) std.mem.Allocator {
|
||||
return self.private().core_app.alloc;
|
||||
}
|
||||
|
||||
const C = Common(Self, Private);
|
||||
pub const as = C.as;
|
||||
pub const ref = C.ref;
|
||||
@ -729,6 +797,11 @@ pub const Application = extern struct {
|
||||
}
|
||||
}
|
||||
|
||||
// Properties
|
||||
gobject.ext.registerProperties(class, &.{
|
||||
properties.config.impl,
|
||||
});
|
||||
|
||||
// Virtual methods
|
||||
gio.Application.virtual_methods.activate.implement(class, &activate);
|
||||
gio.Application.virtual_methods.startup.implement(class, &startup);
|
||||
@ -765,6 +838,16 @@ const Action = struct {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn newWindow(
|
||||
self: *Application,
|
||||
parent: ?*CoreSurface,
|
||||
) !void {
|
||||
_ = parent;
|
||||
|
||||
const win = Window.new(self);
|
||||
gtk.Window.present(win.as(gtk.Window));
|
||||
}
|
||||
};
|
||||
|
||||
/// This sets various GTK-related environment variables as necessary
|
||||
|
166
src/apprt/gtk-ng/class/surface.zig
Normal file
166
src/apprt/gtk-ng/class/surface.zig
Normal file
@ -0,0 +1,166 @@
|
||||
const std = @import("std");
|
||||
const adw = @import("adw");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const renderer = @import("../../../renderer.zig");
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
const adw_version = @import("../adw_version.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
const Application = @import("application.zig").Application;
|
||||
const Config = @import("config.zig").Config;
|
||||
|
||||
const log = std.log.scoped(.gtk_ghostty_surface);
|
||||
|
||||
pub const Surface = extern struct {
|
||||
const Self = @This();
|
||||
parent_instance: Parent,
|
||||
pub const Parent = adw.Bin;
|
||||
pub const getGObjectType = gobject.ext.defineClass(Self, .{
|
||||
.name = "GhosttySurface",
|
||||
.instanceInit = &init,
|
||||
.classInit = &Class.init,
|
||||
.parent_class = &Class.parent,
|
||||
.private = .{ .Type = Private, .offset = &Private.offset },
|
||||
});
|
||||
|
||||
pub const properties = struct {
|
||||
pub const config = struct {
|
||||
pub const name = "config";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
?*Config,
|
||||
.{
|
||||
.nick = "Config",
|
||||
.blurb = "The configuration that this surface is using.",
|
||||
.default = null,
|
||||
.accessor = gobject.ext.privateFieldAccessor(
|
||||
Self,
|
||||
Private,
|
||||
&Private.offset,
|
||||
"config",
|
||||
),
|
||||
},
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
const Private = struct {
|
||||
/// The configuration that this surface is using.
|
||||
config: ?*Config = null,
|
||||
|
||||
/// The GLAarea that renders the actual surface. This is a binding
|
||||
/// to the template so it doesn't have to be unrefed manually.
|
||||
gl_area: *gtk.GLArea,
|
||||
|
||||
pub var offset: c_int = 0;
|
||||
};
|
||||
|
||||
pub fn new() *Self {
|
||||
return gobject.ext.newInstance(Self, .{});
|
||||
}
|
||||
|
||||
fn init(self: *Self, _: *Class) callconv(.C) void {
|
||||
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
||||
|
||||
const priv = self.private();
|
||||
|
||||
// If our configuration is null then we get the configuration
|
||||
// from the application.
|
||||
if (priv.config == null) {
|
||||
const app = Application.default();
|
||||
priv.config = app.getConfig();
|
||||
}
|
||||
|
||||
// Initialize our GLArea. We could do a lot of this in
|
||||
// the Blueprint file but I think its cleaner to separate
|
||||
// the "UI" part of the blueprint file from the internal logic/config
|
||||
// part.
|
||||
const gl_area = priv.gl_area;
|
||||
gl_area.setRequiredVersion(
|
||||
renderer.OpenGL.MIN_VERSION_MAJOR,
|
||||
renderer.OpenGL.MIN_VERSION_MINOR,
|
||||
);
|
||||
gl_area.setHasStencilBuffer(0);
|
||||
gl_area.setHasDepthBuffer(0);
|
||||
gl_area.setUseEs(0);
|
||||
}
|
||||
|
||||
fn dispose(self: *Self) callconv(.C) void {
|
||||
const priv = self.private();
|
||||
if (priv.config) |v| {
|
||||
v.unref();
|
||||
priv.config = null;
|
||||
}
|
||||
|
||||
gtk.Widget.disposeTemplate(
|
||||
self.as(gtk.Widget),
|
||||
getGObjectType(),
|
||||
);
|
||||
|
||||
gobject.Object.virtual_methods.dispose.call(
|
||||
Class.parent,
|
||||
self.as(Parent),
|
||||
);
|
||||
}
|
||||
|
||||
fn realize(self: *Self) callconv(.C) void {
|
||||
log.debug("realize", .{});
|
||||
|
||||
// Call the parent class's realize method.
|
||||
gtk.Widget.virtual_methods.realize.call(
|
||||
Class.parent,
|
||||
self.as(Parent),
|
||||
);
|
||||
}
|
||||
|
||||
fn unrealize(self: *Self) callconv(.C) void {
|
||||
log.debug("unrealize", .{});
|
||||
|
||||
// Call the parent class's unrealize method.
|
||||
gtk.Widget.virtual_methods.unrealize.call(
|
||||
Class.parent,
|
||||
self.as(Parent),
|
||||
);
|
||||
}
|
||||
|
||||
const C = Common(Self, Private);
|
||||
pub const as = C.as;
|
||||
pub const ref = C.ref;
|
||||
pub const unref = C.unref;
|
||||
const private = C.private;
|
||||
|
||||
pub const Class = extern struct {
|
||||
parent_class: Parent.Class,
|
||||
var parent: *Parent.Class = undefined;
|
||||
pub const Instance = Self;
|
||||
|
||||
fn init(class: *Class) callconv(.C) void {
|
||||
gtk.Widget.Class.setTemplateFromResource(
|
||||
class.as(gtk.Widget.Class),
|
||||
comptime gresource.blueprint(.{
|
||||
.major = 1,
|
||||
.minor = 2,
|
||||
.name = "surface",
|
||||
}),
|
||||
);
|
||||
|
||||
// Bindings
|
||||
class.bindTemplateChildPrivate("gl_area", .{});
|
||||
|
||||
// Properties
|
||||
gobject.ext.registerProperties(class, &.{
|
||||
properties.config.impl,
|
||||
});
|
||||
|
||||
// Virtual methods
|
||||
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
||||
gtk.Widget.virtual_methods.realize.implement(class, &realize);
|
||||
gtk.Widget.virtual_methods.unrealize.implement(class, &unrealize);
|
||||
}
|
||||
|
||||
pub const as = C.Class.as;
|
||||
pub const bindTemplateChildPrivate = C.Class.bindTemplateChildPrivate;
|
||||
};
|
||||
};
|
@ -6,6 +6,7 @@ const gtk = @import("gtk");
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
const Application = @import("application.zig").Application;
|
||||
const Surface = @import("surface.zig").Surface;
|
||||
|
||||
const log = std.log.scoped(.gtk_ghostty_window);
|
||||
|
||||
@ -58,6 +59,8 @@ pub const Window = extern struct {
|
||||
pub const Instance = Self;
|
||||
|
||||
fn init(class: *Class) callconv(.C) void {
|
||||
gobject.ext.ensureType(Surface);
|
||||
|
||||
gtk.Widget.Class.setTemplateFromResource(
|
||||
class.as(gtk.Widget.Class),
|
||||
comptime gresource.blueprint(.{
|
||||
|
18
src/apprt/gtk-ng/ui/1.2/surface.blp
Normal file
18
src/apprt/gtk-ng/ui/1.2/surface.blp
Normal file
@ -0,0 +1,18 @@
|
||||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $GhosttySurface: Adw.Bin {
|
||||
Box {
|
||||
orientation: vertical;
|
||||
hexpand: true;
|
||||
|
||||
Label {
|
||||
label: "Hello";
|
||||
}
|
||||
|
||||
GLArea gl_area {
|
||||
hexpand: true;
|
||||
vexpand: true;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,5 @@ using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $GhosttyWindow: Adw.ApplicationWindow {
|
||||
content: Label {
|
||||
label: "Hello, Ghostty!";
|
||||
};
|
||||
content: $GhosttySurface {};
|
||||
}
|
||||
|
@ -13,6 +13,39 @@
|
||||
# You must gracefully exit Ghostty (do not SIGINT) by closing all windows
|
||||
# and quitting. Otherwise, we leave a number of GTK resources around.
|
||||
|
||||
{
|
||||
GSK Renderer GPU Stuff
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: possible
|
||||
...
|
||||
fun:gsk_gpu_image_toggle_ref_texture
|
||||
fun:gsk_gl_image_new_for_texture
|
||||
fun:gsk_gl_frame_upload_texture
|
||||
fun:gsk_gpu_frame_do_upload_texture
|
||||
fun:gsk_gpu_lookup_texture
|
||||
...
|
||||
fun:gsk_gpu_node_processor_add_first_node
|
||||
fun:gsk_gpu_node_processor_process
|
||||
fun:gsk_gpu_frame_render
|
||||
fun:gsk_gpu_renderer_render
|
||||
fun:gsk_renderer_render
|
||||
fun:gtk_widget_render
|
||||
fun:surface_render
|
||||
...
|
||||
}
|
||||
|
||||
{
|
||||
GTK Shader Selector
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: possible
|
||||
...
|
||||
fun:_ZL29si_init_shader_selector_asyncPvS_i
|
||||
fun:util_queue_thread_func
|
||||
fun:impl_thrd_routine
|
||||
fun:start_thread
|
||||
fun:clone
|
||||
}
|
||||
|
||||
# Weird gtk_tooltip_init leak I can't figure out
|
||||
{
|
||||
Non-builder tooltip create
|
||||
|
Reference in New Issue
Block a user