From 8d0c3c7b7c4b4aaba59d5562fe43701d2d9e566a Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Thu, 23 Jan 2025 13:57:36 -0600 Subject: [PATCH] gtk: implement custom audio for bell --- nix/build-support/build-inputs.nix | 3 ++ nix/devShell.nix | 4 ++ nix/package.nix | 4 ++ src/apprt/gtk/Surface.zig | 65 ++++++++++++++++++++++++++++++ src/config/Config.zig | 30 ++++++++++++-- 5 files changed, 102 insertions(+), 4 deletions(-) diff --git a/nix/build-support/build-inputs.nix b/nix/build-support/build-inputs.nix index 5886cfe30..7c9258675 100644 --- a/nix/build-support/build-inputs.nix +++ b/nix/build-support/build-inputs.nix @@ -28,6 +28,9 @@ pkgs.glib pkgs.gobject-introspection pkgs.gsettings-desktop-schemas + pkgs.gst_all_1.gst-plugins-base + pkgs.gst_all_1.gst-plugins-good + pkgs.gst_all_1.gstreamer pkgs.gtk4 pkgs.libadwaita ] diff --git a/nix/devShell.nix b/nix/devShell.nix index 498102ef4..b87c23dd1 100644 --- a/nix/devShell.nix +++ b/nix/devShell.nix @@ -35,6 +35,7 @@ gtk4, gtk4-layer-shell, gobject-introspection, + gst_all_1, libadwaita, blueprint-compiler, gettext, @@ -166,6 +167,9 @@ in wayland wayland-scanner wayland-protocols + gst_all_1.gstreamer + gst_all_1.gst-plugins-base + gst_all_1.gst-plugins-good ]; # This should be set onto the rpath of the ghostty binary if you want diff --git a/nix/package.nix b/nix/package.nix index 9368b2cde..a39f5b835 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -127,6 +127,10 @@ in mv $out/share/vim/vimfiles "$vim" ln -sf "$vim" "$out/share/vim/vimfiles" echo "$vim" >> "$out/nix-support/propagated-user-env-packages" + + echo "gst_all_1.gstreamer" >> "$out/nix-support/propagated-user-env-packages" + echo "gst_all_1.gst-plugins-base" >> "$out/nix-support/propagated-user-env-packages" + echo "gst_all_1.gst-plugins-good" >> "$out/nix-support/propagated-user-env-packages" ''; meta = { diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 0a9f644b7..e47316ac3 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -2405,6 +2405,47 @@ pub fn ringBell(self: *Surface) !void { surface.beep(); } + if (features.audio) audio: { + // Play a user-specified audio file. + + const pathname, const optional = switch (self.app.config.@"bell-audio-path" orelse break :audio) { + .optional => |path| .{ path, true }, + .required => |path| .{ path, false }, + }; + + const volume: f64 = @min( + @max( + 0.0, + self.app.config.@"bell-audio-volume", + ), + 1.0, + ); + + std.debug.assert(std.fs.path.isAbsolute(pathname)); + const media_file = gtk.MediaFile.newForFilename(pathname); + + if (!optional) { + _ = gobject.Object.signals.notify.connect( + media_file, + ?*anyopaque, + gtkStreamError, + null, + .{ .detail = "error" }, + ); + } + _ = gobject.Object.signals.notify.connect( + media_file, + ?*anyopaque, + gtkStreamEnded, + null, + .{ .detail = "ended" }, + ); + + const media_stream = media_file.as(gtk.MediaStream); + media_stream.setVolume(volume); + media_stream.play(); + } + // Mark tab as needing attention if (self.container.tab()) |tab| tab: { const page = window.notebook.getTabPage(tab) orelse break :tab; @@ -2413,3 +2454,27 @@ pub fn ringBell(self: *Surface) !void { if (page.getSelected() == 0) page.setNeedsAttention(@intFromBool(true)); } } + +/// Handle a stream that is in an error state. +fn gtkStreamError(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 "", + }); +} + +/// Stream is finished, release the memory. +fn gtkStreamEnded(media_file: *gtk.MediaFile, _: *gobject.ParamSpec, _: ?*anyopaque) callconv(.c) void { + media_file.unref(); +} diff --git a/src/config/Config.zig b/src/config/Config.zig index ca330f8f6..b51f053cd 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1890,8 +1890,10 @@ keybind: Keybinds = .{}, /// open terminals. @"custom-shader-animation": CustomShaderAnimation = .true, -/// The list of enabled features that are activated after encountering -/// a bell character. +/// Bell features to enable if bell support is available in your runtime. Not +/// all features are available on all runtimes. The format of this is a list of +/// features to enable separated by commas. If you prefix a feature with `no-` +/// then it is disabled. If you omit a feature, its default value is used. /// /// Valid values are: /// @@ -1901,17 +1903,36 @@ keybind: Keybinds = .{}, /// This could result in an audiovisual effect, a notification, or something /// else entirely. Changing these effects require altering system settings: /// for instance under the "Sound > Alert Sound" setting in GNOME, -/// or the "Accessibility > System Bell" settings in KDE Plasma. +/// or the "Accessibility > System Bell" settings in KDE Plasma. (GTK only) /// -/// On macOS this has no affect. +/// * `audio` +/// +/// Play a custom sound. (GTK only) +/// +/// Example: `audio`, `no-audio`, `system`, `no-system`: /// /// On macOS, if the app is unfocused, it will bounce the app icon in the dock /// once. Additionally, the title of the window with the alerted terminal /// surface will contain a bell emoji (🔔) until the terminal is focused /// or a key is pressed. These are not currently configurable since they're /// considered unobtrusive. +/// +/// By default, no bell features are enabled. @"bell-features": BellFeatures = .{}, +/// If `audio` is an enabled bell feature, this is a path to an audio file. If +/// the path is not absolute, it is considered relative to the directory of the +/// configuration file that it is referenced from, or from the current working +/// directory if this is used as a CLI flag. The path may be prefixed with `~/` +/// to reference the user's home directory. (GTK only) +@"bell-audio-path": ?Path = null, + +/// If `audio` is an enabled bell feature, this is the volume to play the audio +/// file at (relative to the system volume). This is a floating point number +/// ranging from 0.0 (silence) to 1.0 (as loud as possible). The default is 0.5. +/// (GTK only) +@"bell-audio-volume": f64 = 0.5, + /// Control the in-app notifications that Ghostty shows. /// /// On Linux (GTK), in-app notifications show up as toasts. Toasts appear @@ -5765,6 +5786,7 @@ pub const AppNotifications = packed struct { /// See bell-features pub const BellFeatures = packed struct { system: bool = false, + audio: bool = false, }; /// See mouse-shift-capture