mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-24 16:11:43 +00:00
GTK: move audio bell processing to the application
This fixes #13647 by using at most one GStreamer thread per application. This was previously addressed in #12815 which used at most one GStreamer thread per surface. Originally discussed in #12808.
This commit is contained in:
@@ -45,6 +45,7 @@ const CloseConfirmationDialog = @import("close_confirmation_dialog.zig").CloseCo
|
||||
const ConfigErrorsDialog = @import("config_errors_dialog.zig").ConfigErrorsDialog;
|
||||
const GlobalShortcuts = @import("global_shortcuts.zig").GlobalShortcuts;
|
||||
const OpenURI = @import("../portal.zig").OpenURI;
|
||||
const media = @import("../media.zig");
|
||||
|
||||
const log = std.log.scoped(.gtk_ghostty_application);
|
||||
|
||||
@@ -223,6 +224,12 @@ pub const Application = extern struct {
|
||||
|
||||
open_uri: OpenURI = undefined,
|
||||
|
||||
// The audio bell's MediaFile, reused across bells so we don't leak a
|
||||
// GStreamer pipeline (and its GL threads) on every ring. Built lazily
|
||||
// on the first audio bell and rebuilt when `bell-audio-path` changes;
|
||||
// unref'd on dispose. See ringBell and media.zig.
|
||||
bell_media: ?*gtk.MediaFile = null,
|
||||
|
||||
pub var offset: c_int = 0;
|
||||
};
|
||||
|
||||
@@ -1478,6 +1485,7 @@ pub const Application = extern struct {
|
||||
.init("quit", actionQuit, null),
|
||||
.init("reload-config", actionReloadConfig, null),
|
||||
.init("toggle-quick-terminal", actionToggleQuickTerminal, null),
|
||||
.init("ring-bell", actionRingBell, null),
|
||||
};
|
||||
|
||||
ext.actions.add(Self, self, &actions);
|
||||
@@ -1549,6 +1557,11 @@ pub const Application = extern struct {
|
||||
priv.signal_source = null;
|
||||
}
|
||||
|
||||
if (priv.bell_media) |v| {
|
||||
v.unref();
|
||||
priv.bell_media = null;
|
||||
}
|
||||
|
||||
gobject.Object.virtual_methods.dispose.call(
|
||||
Class.parent,
|
||||
self.as(Parent),
|
||||
@@ -1948,6 +1961,40 @@ pub const Application = extern struct {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn actionRingBell(
|
||||
_: *gio.SimpleAction,
|
||||
_: ?*glib.Variant,
|
||||
self: *Self,
|
||||
) callconv(.c) void {
|
||||
const priv: *Private = self.private();
|
||||
const config = priv.config.get();
|
||||
|
||||
// Do our sound
|
||||
if (config.@"bell-features".audio) audio: {
|
||||
const config_path = config.@"bell-audio-path" orelse break :audio;
|
||||
const path, const required = switch (config_path) {
|
||||
.optional => |path| .{ path, false },
|
||||
.required => |path| .{ path, true },
|
||||
};
|
||||
|
||||
const volume = std.math.clamp(
|
||||
config.@"bell-audio-volume",
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
|
||||
// Reuse one MediaFile per application (rebuilt only when the path
|
||||
// changes) so each bell replays the same pipeline instead of
|
||||
// leaking a fresh one. Assign unconditionally: bellMediaFile frees
|
||||
// any stale MediaFile and returns the current slot value (possibly
|
||||
// null if the path is now inaccessible), so priv.bell_media never
|
||||
// dangles.
|
||||
priv.bell_media = media.bellMediaFile(priv.bell_media, path, required);
|
||||
const media_file = priv.bell_media orelse break :audio;
|
||||
media.playBell(media_file, volume);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
// Boilerplate/Noise
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ const Window = @import("window.zig").Window;
|
||||
const InspectorWindow = @import("inspector_window.zig").InspectorWindow;
|
||||
const SplitTree = @import("split_tree.zig").SplitTree;
|
||||
const i18n = @import("../../../os/i18n.zig");
|
||||
const media = @import("../media.zig");
|
||||
const global = @import("../../../global.zig");
|
||||
const gtk_version = @import("../gtk_version.zig");
|
||||
|
||||
@@ -677,12 +676,6 @@ pub const Surface = extern struct {
|
||||
// false by a parent widget.
|
||||
bell_ringing: bool = false,
|
||||
|
||||
// The audio bell's MediaFile, reused across bells so we don't leak a
|
||||
// GStreamer pipeline (and its GL threads) on every ring. Built lazily
|
||||
// on the first audio bell and rebuilt when `bell-audio-path` changes;
|
||||
// unref'd on dispose. See ringBell and media.zig.
|
||||
bell_media: ?*gtk.MediaFile = null,
|
||||
|
||||
/// True if this surface is in an error state. This is currently
|
||||
/// a simple boolean with no additional information on WHAT the
|
||||
/// error state is, because we don't yet need it or use it. For now,
|
||||
@@ -1880,11 +1873,6 @@ pub const Surface = extern struct {
|
||||
priv.config = null;
|
||||
}
|
||||
|
||||
if (priv.bell_media) |v| {
|
||||
v.unref();
|
||||
priv.bell_media = null;
|
||||
}
|
||||
|
||||
if (priv.vadj_signal_group) |group| {
|
||||
group.setTarget(null);
|
||||
group.as(gobject.Object).unref();
|
||||
@@ -2544,8 +2532,6 @@ pub const Surface = extern struct {
|
||||
/// Handle bell features that need to happen every time a BEL is received
|
||||
/// Currently this is audio and system but this could change in the future.
|
||||
fn ringBell(self: *Self) void {
|
||||
const priv = self.private();
|
||||
|
||||
// Emit the signal
|
||||
signals.bell.impl.emit(
|
||||
self,
|
||||
@@ -2557,33 +2543,7 @@ pub const Surface = extern struct {
|
||||
// Activate actions if they exist
|
||||
_ = self.as(gtk.Widget).activateAction("tab.ring-bell", null);
|
||||
_ = self.as(gtk.Widget).activateAction("win.ring-bell", null);
|
||||
|
||||
const config = if (priv.config) |c| c.get() else return;
|
||||
|
||||
// Do our sound
|
||||
if (config.@"bell-features".audio) audio: {
|
||||
const config_path = config.@"bell-audio-path" orelse break :audio;
|
||||
const path, const required = switch (config_path) {
|
||||
.optional => |path| .{ path, false },
|
||||
.required => |path| .{ path, true },
|
||||
};
|
||||
|
||||
const volume = std.math.clamp(
|
||||
config.@"bell-audio-volume",
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
|
||||
// Reuse one MediaFile per surface (rebuilt only when the path
|
||||
// changes) so each bell replays the same pipeline instead of
|
||||
// leaking a fresh one. Assign unconditionally: bellMediaFile frees
|
||||
// any stale MediaFile and returns the current slot value (possibly
|
||||
// null if the path is now inaccessible), so priv.bell_media never
|
||||
// dangles.
|
||||
priv.bell_media = media.bellMediaFile(priv.bell_media, path, required);
|
||||
const media_file = priv.bell_media orelse break :audio;
|
||||
media.playBell(media_file, volume);
|
||||
}
|
||||
_ = self.as(gtk.Widget).activateAction("app.ring-bell", null);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user