diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index 4b31c43d5..8ff8e55e1 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -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 "<>", - 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, diff --git a/src/apprt/gtk/media.zig b/src/apprt/gtk/media.zig new file mode 100644 index 000000000..1015c933f --- /dev/null +++ b/src/apprt/gtk/media.zig @@ -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 "<>", + 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(); +}