gtk: move audio playback into separate file, enabling reuse (#11588)

This commit is contained in:
Leah Amelia Chen
2026-03-17 04:42:51 +00:00
committed by GitHub
2 changed files with 105 additions and 57 deletions

View File

@@ -35,6 +35,7 @@ const TitleDialog = @import("title_dialog.zig").TitleDialog;
const Window = @import("window.zig").Window;
const InspectorWindow = @import("inspector_window.zig").InspectorWindow;
const i18n = @import("../../../os/i18n.zig");
const media = @import("../media.zig");
const log = std.log.scoped(.gtk_ghostty_surface);
@@ -2457,34 +2458,8 @@ pub const Surface = extern struct {
1.0,
);
assert(std.fs.path.isAbsolute(path));
const media_file = gtk.MediaFile.newForFilename(path);
// If the audio file is marked as required, we'll emit an error if
// there was a problem playing it. Otherwise there will be silence.
if (required) {
_ = gobject.Object.signals.notify.connect(
media_file,
?*anyopaque,
mediaFileError,
null,
.{ .detail = "error" },
);
}
// Watch for the "ended" signal so that we can clean up after
// ourselves.
_ = gobject.Object.signals.notify.connect(
media_file,
?*anyopaque,
mediaFileEnded,
null,
.{ .detail = "ended" },
);
const media_stream = media_file.as(gtk.MediaStream);
media_stream.setVolume(volume);
media_stream.play();
const media_file = media.fromFilename(path) orelse break :audio;
media.playMediaFile(media_file, volume, required);
}
}
@@ -3481,35 +3456,6 @@ pub const Surface = extern struct {
right.setVisible(0);
}
fn mediaFileError(
media_file: *gtk.MediaFile,
_: *gobject.ParamSpec,
_: ?*anyopaque,
) callconv(.c) void {
const path = path: {
const file = media_file.getFile() orelse break :path null;
break :path file.getPath();
};
defer if (path) |p| glib.free(p);
const media_stream = media_file.as(gtk.MediaStream);
const err = media_stream.getError() orelse return;
log.warn("error playing bell from {s}: {s} {d} {s}", .{
path orelse "<<unknown>>",
glib.quarkToString(err.f_domain),
err.f_code,
err.f_message orelse "",
});
}
fn mediaFileEnded(
media_file: *gtk.MediaFile,
_: *gobject.ParamSpec,
_: ?*anyopaque,
) callconv(.c) void {
media_file.unref();
}
fn titleDialogSet(
_: *TitleDialog,
title_ptr: [*:0]const u8,

102
src/apprt/gtk/media.zig Normal file
View File

@@ -0,0 +1,102 @@
const std = @import("std");
const assert = @import("../../quirks.zig").inlineAssert;
const log = std.log.scoped(.gtk_media);
const gio = @import("gio");
const glib = @import("glib");
const gobject = @import("gobject");
const gtk = @import("gtk");
pub fn fromFilename(path: [:0]const u8) ?*gtk.MediaFile {
assert(std.fs.path.isAbsolute(path));
std.fs.accessAbsolute(path, .{ .mode = .read_only }) catch |err| {
log.warn("unable to access {s}: {t}", .{ path, err });
return null;
};
return gtk.MediaFile.newForFilename(path);
}
pub fn fromResource(path: [:0]const u8) ?*gtk.MediaFile {
assert(std.fs.path.isAbsolute(path));
var gerr: ?*glib.Error = null;
const found = gio.resourcesGetInfo(path, .{}, null, null, &gerr);
if (gerr) |err| {
defer err.free();
log.warn(
"failed to find resource {s}: {s} {d} {s}",
.{
path,
glib.quarkToString(err.f_domain),
err.f_code,
err.f_message orelse "(no message)",
},
);
return null;
}
if (found == 0) {
log.warn("failed to find resource {s}", .{path});
return null;
}
return gtk.MediaFile.newForResource(path);
}
pub fn playMediaFile(media_file: *gtk.MediaFile, volume: f64, required: bool) void {
// If the audio file is marked as required, we'll emit an error if
// there was a problem playing it. Otherwise there will be silence.
if (required) {
_ = gobject.Object.signals.notify.connect(
media_file,
?*anyopaque,
mediaFileError,
null,
.{ .detail = "error" },
);
}
// Watch for the "ended" signal so that we can clean up after
// ourselves.
_ = gobject.Object.signals.notify.connect(
media_file,
?*anyopaque,
mediaFileEnded,
null,
.{ .detail = "ended" },
);
const media_stream = media_file.as(gtk.MediaStream);
media_stream.setVolume(volume);
media_stream.play();
}
fn mediaFileError(
media_file: *gtk.MediaFile,
_: *gobject.ParamSpec,
_: ?*anyopaque,
) callconv(.c) void {
const path = path: {
const file = media_file.getFile() orelse break :path null;
break :path file.getPath();
};
defer if (path) |p| glib.free(p);
const media_stream = media_file.as(gtk.MediaStream);
const err = media_stream.getError() orelse return;
log.warn("error playing sound from {s}: {s} {d} {s}", .{
path orelse "<<unknown>>",
glib.quarkToString(err.f_domain),
err.f_code,
err.f_message orelse "",
});
}
fn mediaFileEnded(
media_file: *gtk.MediaFile,
_: *gobject.ParamSpec,
_: ?*anyopaque,
) callconv(.c) void {
media_file.unref();
}