mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-25 13:16:11 +03:00
apprt/gtk-ng: surface input
This commit is contained in:
@ -53,6 +53,19 @@ pub fn getCursorPos(self: *const Self) !apprt.CursorPos {
|
|||||||
return .{ .x = 0, .y = 0 };
|
return .{ .x = 0, .y = 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn supportsClipboard(
|
||||||
|
self: *const Self,
|
||||||
|
clipboard_type: apprt.Clipboard,
|
||||||
|
) bool {
|
||||||
|
_ = self;
|
||||||
|
return switch (clipboard_type) {
|
||||||
|
.standard,
|
||||||
|
.selection,
|
||||||
|
.primary,
|
||||||
|
=> true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clipboardRequest(
|
pub fn clipboardRequest(
|
||||||
self: *Self,
|
self: *Self,
|
||||||
clipboard_type: apprt.Clipboard,
|
clipboard_type: apprt.Clipboard,
|
||||||
|
@ -2,10 +2,12 @@ const std = @import("std");
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const adw = @import("adw");
|
const adw = @import("adw");
|
||||||
const gdk = @import("gdk");
|
const gdk = @import("gdk");
|
||||||
|
const glib = @import("glib");
|
||||||
const gobject = @import("gobject");
|
const gobject = @import("gobject");
|
||||||
const gtk = @import("gtk");
|
const gtk = @import("gtk");
|
||||||
|
|
||||||
const apprt = @import("../../../apprt.zig");
|
const apprt = @import("../../../apprt.zig");
|
||||||
|
const input = @import("../../../input.zig");
|
||||||
const internal_os = @import("../../../os/main.zig");
|
const internal_os = @import("../../../os/main.zig");
|
||||||
const renderer = @import("../../../renderer.zig");
|
const renderer = @import("../../../renderer.zig");
|
||||||
const CoreSurface = @import("../../../Surface.zig");
|
const CoreSurface = @import("../../../Surface.zig");
|
||||||
@ -58,10 +60,10 @@ pub const Surface = extern struct {
|
|||||||
|
|
||||||
/// The GLAarea that renders the actual surface. This is a binding
|
/// The GLAarea that renders the actual surface. This is a binding
|
||||||
/// to the template so it doesn't have to be unrefed manually.
|
/// to the template so it doesn't have to be unrefed manually.
|
||||||
gl_area: *gtk.GLArea,
|
gl_area: *gtk.GLArea = undefined,
|
||||||
|
|
||||||
/// The apprt Surface.
|
/// The apprt Surface.
|
||||||
rt_surface: ApprtSurface,
|
rt_surface: ApprtSurface = undefined,
|
||||||
|
|
||||||
/// The core surface backing this GTK surface. This starts out
|
/// The core surface backing this GTK surface. This starts out
|
||||||
/// null because it can't be initialized until there is an available
|
/// null because it can't be initialized until there is an available
|
||||||
@ -77,6 +79,13 @@ pub const Surface = extern struct {
|
|||||||
/// Cached metrics for libghostty callbacks
|
/// Cached metrics for libghostty callbacks
|
||||||
size: apprt.SurfaceSize,
|
size: apprt.SurfaceSize,
|
||||||
|
|
||||||
|
/// Various input method state. All related to key input.
|
||||||
|
in_keyevent: IMKeyEvent = .false,
|
||||||
|
im_context: ?*gtk.IMMulticontext = null,
|
||||||
|
im_composing: bool = false,
|
||||||
|
im_buf: [128]u8 = undefined,
|
||||||
|
im_len: u7 = 0,
|
||||||
|
|
||||||
pub var offset: c_int = 0;
|
pub var offset: c_int = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -103,6 +112,54 @@ pub const Surface = extern struct {
|
|||||||
priv.gl_area.queueRender();
|
priv.gl_area.queueRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Key press event (press or release).
|
||||||
|
///
|
||||||
|
/// At a high level, we want to construct an `input.KeyEvent` and
|
||||||
|
/// pass that to `keyCallback`. At a low level, this is more complicated
|
||||||
|
/// than it appears because we need to construct all of this information
|
||||||
|
/// and its not given to us.
|
||||||
|
///
|
||||||
|
/// For all events, we run the GdkEvent through the input method context.
|
||||||
|
/// This allows the input method to capture the event and trigger
|
||||||
|
/// callbacks such as preedit, commit, etc.
|
||||||
|
///
|
||||||
|
/// There are a couple important aspects to the prior paragraph: we must
|
||||||
|
/// send ALL events through the input method context. This is because
|
||||||
|
/// input methods use both key press and key release events to determine
|
||||||
|
/// the state of the input method. For example, fcitx uses key release
|
||||||
|
/// events on modifiers (i.e. ctrl+shift) to switch the input method.
|
||||||
|
///
|
||||||
|
/// We set some state to note we're in a key event (self.in_keyevent)
|
||||||
|
/// because some of the input method callbacks change behavior based on
|
||||||
|
/// this state. For example, we don't want to send character events
|
||||||
|
/// like "a" via the input "commit" event if we're actively processing
|
||||||
|
/// a keypress because we'd lose access to the keycode information.
|
||||||
|
/// However, a "commit" event may still happen outside of a keypress
|
||||||
|
/// event from e.g. a tablet or on-screen keyboard.
|
||||||
|
///
|
||||||
|
/// Finally, we take all of the information in order to determine if we have
|
||||||
|
/// a unicode character or if we have to map the keyval to a code to
|
||||||
|
/// get the underlying logical key, etc.
|
||||||
|
///
|
||||||
|
/// Then we can emit the keyCallback.
|
||||||
|
pub fn keyEvent(
|
||||||
|
self: *Surface,
|
||||||
|
action: input.Action,
|
||||||
|
ec_key: *gtk.EventControllerKey,
|
||||||
|
keyval: c_uint,
|
||||||
|
keycode: c_uint,
|
||||||
|
gtk_mods: gdk.ModifierType,
|
||||||
|
) bool {
|
||||||
|
log.warn("keyEvent action={}", .{action});
|
||||||
|
|
||||||
|
_ = self;
|
||||||
|
_ = ec_key;
|
||||||
|
_ = keyval;
|
||||||
|
_ = keycode;
|
||||||
|
_ = gtk_mods;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
// Libghostty Callbacks
|
// Libghostty Callbacks
|
||||||
|
|
||||||
@ -224,6 +281,83 @@ pub const Surface = extern struct {
|
|||||||
priv.config = app.getConfig();
|
priv.config = app.getConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const self_widget = self.as(gtk.Widget);
|
||||||
|
|
||||||
|
// Setup our event controllers to get input events
|
||||||
|
const ec_key = gtk.EventControllerKey.new();
|
||||||
|
errdefer ec_key.unref();
|
||||||
|
self_widget.addController(ec_key.as(gtk.EventController));
|
||||||
|
errdefer self_widget.removeController(ec_key.as(gtk.EventController));
|
||||||
|
_ = gtk.EventControllerKey.signals.key_pressed.connect(
|
||||||
|
ec_key,
|
||||||
|
*Self,
|
||||||
|
ecKeyPressed,
|
||||||
|
self,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
_ = gtk.EventControllerKey.signals.key_released.connect(
|
||||||
|
ec_key,
|
||||||
|
*Self,
|
||||||
|
ecKeyReleased,
|
||||||
|
self,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Focus controller will tell us about focus enter/exit events
|
||||||
|
const ec_focus = gtk.EventControllerFocus.new();
|
||||||
|
errdefer ec_focus.unref();
|
||||||
|
self_widget.addController(ec_focus.as(gtk.EventController));
|
||||||
|
errdefer self_widget.removeController(ec_focus.as(gtk.EventController));
|
||||||
|
_ = gtk.EventControllerFocus.signals.enter.connect(
|
||||||
|
ec_focus,
|
||||||
|
*Self,
|
||||||
|
ecFocusEnter,
|
||||||
|
self,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
_ = gtk.EventControllerFocus.signals.leave.connect(
|
||||||
|
ec_focus,
|
||||||
|
*Self,
|
||||||
|
ecFocusLeave,
|
||||||
|
self,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Setup our input method state
|
||||||
|
const im_context = gtk.IMMulticontext.new();
|
||||||
|
priv.im_context = im_context;
|
||||||
|
priv.in_keyevent = .false;
|
||||||
|
priv.im_composing = false;
|
||||||
|
priv.im_len = 0;
|
||||||
|
_ = gtk.IMContext.signals.preedit_start.connect(
|
||||||
|
im_context,
|
||||||
|
*Self,
|
||||||
|
imPreeditStart,
|
||||||
|
self,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
_ = gtk.IMContext.signals.preedit_changed.connect(
|
||||||
|
im_context,
|
||||||
|
*Self,
|
||||||
|
imPreeditChanged,
|
||||||
|
self,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
_ = gtk.IMContext.signals.preedit_end.connect(
|
||||||
|
im_context,
|
||||||
|
*Self,
|
||||||
|
imPreeditEnd,
|
||||||
|
self,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
_ = gtk.IMContext.signals.commit.connect(
|
||||||
|
im_context,
|
||||||
|
*Self,
|
||||||
|
imCommit,
|
||||||
|
self,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
|
||||||
// Initialize our GLArea. We could do a lot of this in
|
// Initialize our GLArea. We could do a lot of this in
|
||||||
// the Blueprint file but I think its cleaner to separate
|
// the Blueprint file but I think its cleaner to separate
|
||||||
// the "UI" part of the blueprint file from the internal logic/config
|
// the "UI" part of the blueprint file from the internal logic/config
|
||||||
@ -272,6 +406,10 @@ pub const Surface = extern struct {
|
|||||||
v.unref();
|
v.unref();
|
||||||
priv.config = null;
|
priv.config = null;
|
||||||
}
|
}
|
||||||
|
if (priv.im_context) |v| {
|
||||||
|
v.unref();
|
||||||
|
priv.im_context = null;
|
||||||
|
}
|
||||||
|
|
||||||
gtk.Widget.disposeTemplate(
|
gtk.Widget.disposeTemplate(
|
||||||
self.as(gtk.Widget),
|
self.as(gtk.Widget),
|
||||||
@ -287,8 +425,6 @@ pub const Surface = extern struct {
|
|||||||
fn finalize(self: *Self) callconv(.C) void {
|
fn finalize(self: *Self) callconv(.C) void {
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
if (priv.core_surface) |v| {
|
if (priv.core_surface) |v| {
|
||||||
priv.core_surface = null;
|
|
||||||
|
|
||||||
// Remove ourselves from the list of known surfaces in the app.
|
// Remove ourselves from the list of known surfaces in the app.
|
||||||
// We do this before deinit in case a callback triggers
|
// We do this before deinit in case a callback triggers
|
||||||
// searching for this surface.
|
// searching for this surface.
|
||||||
@ -298,6 +434,8 @@ pub const Surface = extern struct {
|
|||||||
v.deinit();
|
v.deinit();
|
||||||
const alloc = Application.default().allocator();
|
const alloc = Application.default().allocator();
|
||||||
alloc.destroy(v);
|
alloc.destroy(v);
|
||||||
|
|
||||||
|
priv.core_surface = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
gobject.Object.virtual_methods.finalize.call(
|
gobject.Object.virtual_methods.finalize.call(
|
||||||
@ -309,16 +447,235 @@ pub const Surface = extern struct {
|
|||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
// Signal Handlers
|
// Signal Handlers
|
||||||
|
|
||||||
|
fn ecKeyPressed(
|
||||||
|
ec_key: *gtk.EventControllerKey,
|
||||||
|
keyval: c_uint,
|
||||||
|
keycode: c_uint,
|
||||||
|
gtk_mods: gdk.ModifierType,
|
||||||
|
self: *Self,
|
||||||
|
) callconv(.c) c_int {
|
||||||
|
return @intFromBool(self.keyEvent(
|
||||||
|
.press,
|
||||||
|
ec_key,
|
||||||
|
keyval,
|
||||||
|
keycode,
|
||||||
|
gtk_mods,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ecKeyReleased(
|
||||||
|
ec_key: *gtk.EventControllerKey,
|
||||||
|
keyval: c_uint,
|
||||||
|
keycode: c_uint,
|
||||||
|
state: gdk.ModifierType,
|
||||||
|
self: *Self,
|
||||||
|
) callconv(.c) void {
|
||||||
|
_ = self.keyEvent(
|
||||||
|
.release,
|
||||||
|
ec_key,
|
||||||
|
keyval,
|
||||||
|
keycode,
|
||||||
|
state,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ecFocusEnter(_: *gtk.EventControllerFocus, self: *Self) callconv(.c) void {
|
||||||
|
const priv = self.private();
|
||||||
|
|
||||||
|
if (priv.im_context) |im_context| {
|
||||||
|
im_context.as(gtk.IMContext).focusIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priv.core_surface) |surface| {
|
||||||
|
surface.focusCallback(true) catch |err| {
|
||||||
|
log.warn("error in focus callback err={}", .{err});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ecFocusLeave(_: *gtk.EventControllerFocus, self: *Self) callconv(.c) void {
|
||||||
|
const priv = self.private();
|
||||||
|
|
||||||
|
if (priv.im_context) |im_context| {
|
||||||
|
im_context.as(gtk.IMContext).focusOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priv.core_surface) |surface| {
|
||||||
|
surface.focusCallback(false) catch |err| {
|
||||||
|
log.warn("error in focus callback err={}", .{err});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn imPreeditStart(
|
||||||
|
_: *gtk.IMMulticontext,
|
||||||
|
self: *Self,
|
||||||
|
) callconv(.c) void {
|
||||||
|
// log.warn("GTKIM: preedit start", .{});
|
||||||
|
|
||||||
|
// Start our composing state for the input method and reset our
|
||||||
|
// input buffer to empty.
|
||||||
|
const priv = self.private();
|
||||||
|
priv.im_composing = true;
|
||||||
|
priv.im_len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn imPreeditChanged(
|
||||||
|
ctx: *gtk.IMMulticontext,
|
||||||
|
self: *Self,
|
||||||
|
) callconv(.c) void {
|
||||||
|
const priv = self.private();
|
||||||
|
|
||||||
|
// Any preedit change should mark that we're composing. Its possible this
|
||||||
|
// is false using fcitx5-hangul and typing "dkssud<space>" ("안녕"). The
|
||||||
|
// second "s" results in a "commit" for "안" which sets composing to false,
|
||||||
|
// but then immediately sends a preedit change for the next symbol. With
|
||||||
|
// composing set to false we won't commit this text. Therefore, we must
|
||||||
|
// ensure it is set here.
|
||||||
|
priv.im_composing = true;
|
||||||
|
|
||||||
|
// We can't set our preedit on our surface unless we're realized.
|
||||||
|
// We do this now because we want to still keep our input method
|
||||||
|
// state coherent.
|
||||||
|
const surface = priv.core_surface orelse return;
|
||||||
|
|
||||||
|
// Get our pre-edit string that we'll use to show the user.
|
||||||
|
var buf: [*:0]u8 = undefined;
|
||||||
|
ctx.as(gtk.IMContext).getPreeditString(
|
||||||
|
&buf,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
defer glib.free(buf);
|
||||||
|
const str = std.mem.sliceTo(buf, 0);
|
||||||
|
|
||||||
|
// Update our preedit state in Ghostty core
|
||||||
|
// log.warn("GTKIM: preedit change str={s}", .{str});
|
||||||
|
surface.preeditCallback(str) catch |err| {
|
||||||
|
log.warn(
|
||||||
|
"error in preedit callback err={}",
|
||||||
|
.{err},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn imPreeditEnd(
|
||||||
|
_: *gtk.IMMulticontext,
|
||||||
|
self: *Self,
|
||||||
|
) callconv(.c) void {
|
||||||
|
// log.warn("GTKIM: preedit end", .{});
|
||||||
|
|
||||||
|
// End our composing state for GTK, allowing us to commit the text.
|
||||||
|
const priv = self.private();
|
||||||
|
priv.im_composing = false;
|
||||||
|
|
||||||
|
// End our preedit state in Ghostty core
|
||||||
|
const surface = priv.core_surface orelse return;
|
||||||
|
surface.preeditCallback(null) catch |err| {
|
||||||
|
log.warn("error in preedit callback err={}", .{err});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn imCommit(
|
||||||
|
_: *gtk.IMMulticontext,
|
||||||
|
bytes: [*:0]u8,
|
||||||
|
self: *Self,
|
||||||
|
) callconv(.c) void {
|
||||||
|
const priv = self.private();
|
||||||
|
const str = std.mem.sliceTo(bytes, 0);
|
||||||
|
|
||||||
|
// log.debug("GTKIM: input commit composing={} keyevent={} str={s}", .{
|
||||||
|
// self.im_composing,
|
||||||
|
// self.in_keyevent,
|
||||||
|
// str,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// We need to handle commit specially if we're in a key event.
|
||||||
|
// Specifically, GTK will send us a commit event for basic key
|
||||||
|
// encodings like "a" (on a US layout keyboard). We don't want
|
||||||
|
// to treat this as IME committed text because we want to associate
|
||||||
|
// it with a key event (i.e. "a" key press).
|
||||||
|
switch (priv.in_keyevent) {
|
||||||
|
// If we're not in a key event then this commit is from
|
||||||
|
// some other source (i.e. on-screen keyboard, tablet, etc.)
|
||||||
|
// and we want to commit the text to the core surface.
|
||||||
|
.false => {},
|
||||||
|
|
||||||
|
// If we're in a composing state and in a key event then this
|
||||||
|
// key event is resulting in a commit of multiple keypresses
|
||||||
|
// and we don't want to encode it alongside the keypress.
|
||||||
|
.composing => {},
|
||||||
|
|
||||||
|
// If we're not composing then this commit is just a normal
|
||||||
|
// key encoding and we want our key event to handle it so
|
||||||
|
// that Ghostty can be aware of the key event alongside
|
||||||
|
// the text.
|
||||||
|
.not_composing => {
|
||||||
|
if (str.len > priv.im_buf.len) {
|
||||||
|
log.warn("not enough buffer space for input method commit", .{});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy our committed text to the buffer
|
||||||
|
@memcpy(priv.im_buf[0..str.len], str);
|
||||||
|
priv.im_len = @intCast(str.len);
|
||||||
|
|
||||||
|
// log.debug("input commit len={}", .{priv.im_len});
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we reach this point from above it means we're composing OR
|
||||||
|
// not in a keypress. In either case, we want to commit the text
|
||||||
|
// given to us because that's what GTK is asking us to do. If we're
|
||||||
|
// not in a keypress it means that this commit came via a non-keyboard
|
||||||
|
// event (i.e. on-screen keyboard, tablet of some kind, etc.).
|
||||||
|
|
||||||
|
// Committing ends composing state
|
||||||
|
priv.im_composing = false;
|
||||||
|
|
||||||
|
// We can't set our preedit on our surface unless we're realized.
|
||||||
|
// We do this now because we want to still keep our input method
|
||||||
|
// state coherent.
|
||||||
|
if (priv.core_surface) |surface| {
|
||||||
|
// End our preedit state. Well-behaved input methods do this for us
|
||||||
|
// by triggering a preedit-end event but some do not (ibus 1.5.29).
|
||||||
|
surface.preeditCallback(null) catch |err| {
|
||||||
|
log.warn("error in preedit callback err={}", .{err});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send the text to the core surface, associated with no key (an
|
||||||
|
// invalid key, which should produce no PTY encoding).
|
||||||
|
_ = surface.keyCallback(.{
|
||||||
|
.action = .press,
|
||||||
|
.key = .unidentified,
|
||||||
|
.mods = .{},
|
||||||
|
.consumed_mods = .{},
|
||||||
|
.composing = false,
|
||||||
|
.utf8 = str,
|
||||||
|
}) catch |err| {
|
||||||
|
log.warn("error in key callback err={}", .{err});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn glareaRealize(
|
fn glareaRealize(
|
||||||
_: *gtk.GLArea,
|
_: *gtk.GLArea,
|
||||||
self: *Self,
|
self: *Self,
|
||||||
) callconv(.c) void {
|
) callconv(.c) void {
|
||||||
log.debug("realize", .{});
|
log.debug("realize", .{});
|
||||||
|
|
||||||
|
// Setup our core surface
|
||||||
self.realizeSurface() catch |err| {
|
self.realizeSurface() catch |err| {
|
||||||
log.warn("surface failed to realize err={}", .{err});
|
log.warn("surface failed to realize err={}", .{err});
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Setup our input method. We do this here because this will
|
||||||
|
// create a strong reference back to ourself and we want to be
|
||||||
|
// able to release that in unrealize.
|
||||||
|
if (self.private().im_context) |im_context| {
|
||||||
|
im_context.as(gtk.IMContext).setClientWidget(self.as(gtk.Widget));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn glareaUnrealize(
|
fn glareaUnrealize(
|
||||||
@ -327,30 +684,34 @@ pub const Surface = extern struct {
|
|||||||
) callconv(.c) void {
|
) callconv(.c) void {
|
||||||
log.debug("unrealize", .{});
|
log.debug("unrealize", .{});
|
||||||
|
|
||||||
// Get our surface. If we don't have one, there's no work we
|
// Notify our core surface
|
||||||
// need to do here.
|
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
const surface = priv.core_surface orelse return;
|
if (priv.core_surface) |surface| {
|
||||||
|
// There is no guarantee that our GLArea context is current
|
||||||
|
// when unrealize is emitted, so we need to make it current.
|
||||||
|
gl_area.makeCurrent();
|
||||||
|
if (gl_area.getError()) |err| {
|
||||||
|
// I don't know a scenario this can happen, but it means
|
||||||
|
// we probably leaked memory because displayUnrealized
|
||||||
|
// below frees resources that aren't specifically OpenGL
|
||||||
|
// related. I didn't make the OpenGL renderer handle this
|
||||||
|
// scenario because I don't know if its even possible
|
||||||
|
// under valid circumstances, so let's log.
|
||||||
|
log.warn(
|
||||||
|
"gl_area_make_current failed in unrealize msg={s}",
|
||||||
|
.{err.f_message orelse "(no message)"},
|
||||||
|
);
|
||||||
|
log.warn("OpenGL resources and memory likely leaked", .{});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// There is no guarantee that our GLArea context is current
|
surface.renderer.displayUnrealized();
|
||||||
// when unrealize is emitted, so we need to make it current.
|
|
||||||
gl_area.makeCurrent();
|
|
||||||
if (gl_area.getError()) |err| {
|
|
||||||
// I don't know a scenario this can happen, but it means
|
|
||||||
// we probably leaked memory because displayUnrealized
|
|
||||||
// below frees resources that aren't specifically OpenGL
|
|
||||||
// related. I didn't make the OpenGL renderer handle this
|
|
||||||
// scenario because I don't know if its even possible
|
|
||||||
// under valid circumstances, so let's log.
|
|
||||||
log.warn(
|
|
||||||
"gl_area_make_current failed in unrealize msg={s}",
|
|
||||||
.{err.f_message orelse "(no message)"},
|
|
||||||
);
|
|
||||||
log.warn("OpenGL resources and memory likely leaked", .{});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
surface.renderer.displayUnrealized();
|
// Unset our input method
|
||||||
|
if (priv.im_context) |im_context| {
|
||||||
|
im_context.as(gtk.IMContext).setClientWidget(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn glareaRender(
|
fn glareaRender(
|
||||||
@ -375,7 +736,7 @@ pub const Surface = extern struct {
|
|||||||
gl_area: *gtk.GLArea,
|
gl_area: *gtk.GLArea,
|
||||||
width: c_int,
|
width: c_int,
|
||||||
height: c_int,
|
height: c_int,
|
||||||
self: *Surface,
|
self: *Self,
|
||||||
) callconv(.c) void {
|
) callconv(.c) void {
|
||||||
// Some debug output to help understand what GTK is telling us.
|
// Some debug output to help understand what GTK is telling us.
|
||||||
{
|
{
|
||||||
@ -519,3 +880,17 @@ pub const Surface = extern struct {
|
|||||||
pub const bindTemplateChildPrivate = C.Class.bindTemplateChildPrivate;
|
pub const bindTemplateChildPrivate = C.Class.bindTemplateChildPrivate;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The state of the key event while we're doing IM composition.
|
||||||
|
/// See gtkKeyPressed for detailed descriptions.
|
||||||
|
pub const IMKeyEvent = enum {
|
||||||
|
/// Not in a key event.
|
||||||
|
false,
|
||||||
|
|
||||||
|
/// In a key event but im_composing was either true or false
|
||||||
|
/// prior to the calling IME processing. This is important to
|
||||||
|
/// work around different input methods calling commit and
|
||||||
|
/// preedit end in a different order.
|
||||||
|
composing,
|
||||||
|
not_composing,
|
||||||
|
};
|
||||||
|
@ -2,15 +2,12 @@ using Gtk 4.0;
|
|||||||
using Adw 1;
|
using Adw 1;
|
||||||
|
|
||||||
template $GhosttySurface: Adw.Bin {
|
template $GhosttySurface: Adw.Bin {
|
||||||
// A box isn't strictly necessary right now but there will be more
|
Overlay {
|
||||||
// stuff here in the future. There's still a lot to do with surfaces.
|
|
||||||
Box {
|
|
||||||
orientation: vertical;
|
|
||||||
hexpand: true;
|
|
||||||
|
|
||||||
GLArea gl_area {
|
GLArea gl_area {
|
||||||
hexpand: true;
|
hexpand: true;
|
||||||
vexpand: true;
|
vexpand: true;
|
||||||
|
focusable: true;
|
||||||
|
focus-on-click: true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user