macos: implement audio bell support with bell-audio-path

Extends the macOS bell implementation to support the `audio` bell
feature by playing a user-specified audio file via NSSound.

Previously, macOS only supported the `system` feature (NSSound.beep()).
This change adds support for:
- `audio` bell feature: plays the file at `bell-audio-path` using
  NSSound, respecting the `bell-audio-volume` setting
- Adds `cval()` to the `Path` type so it can be returned via the C API

Also removes the "(GTK only)" restriction from `bell-audio-path` and
`bell-audio-volume` documentation, as these options now work on macOS.

Example config:
  bell-features = audio
  bell-audio-path = /System/Library/Sounds/Glass.aiff
  bell-audio-volume = 0.8
pull/11154/head
Alaa Ali 2026-03-03 23:00:50 +01:00
parent ee4c6f88c5
commit b215291914
4 changed files with 35 additions and 2 deletions

View File

@ -777,6 +777,14 @@ class AppDelegate: NSObject,
NSSound.beep()
}
if ghostty.config.bellFeatures.contains(.audio) {
if let path = ghostty.config.bellAudioPath,
let sound = NSSound(contentsOfFile: path, byReference: false) {
sound.volume = ghostty.config.bellAudioVolume
sound.play()
}
}
if ghostty.config.bellFeatures.contains(.attention) {
// Bounce the dock icon if we're not focused.
NSApp.requestUserAttention(.informationalRequest)

View File

@ -134,6 +134,24 @@ extension Ghostty {
return .init(rawValue: v)
}
var bellAudioPath: String? {
guard let config = self.config else { return nil }
var v: UnsafePointer<Int8>?
let key = "bell-audio-path"
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return nil }
guard let ptr = v else { return nil }
let path = String(cString: ptr)
return path.isEmpty ? nil : path
}
var bellAudioVolume: Float {
guard let config = self.config else { return 0.5 }
var v: Double = 0.5
let key = "bell-audio-volume"
_ = ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8)))
return Float(v)
}
var notifyOnCommandFinish: NotifyOnCommandFinish {
guard let config = self.config else { return .never }
var v: UnsafePointer<Int8>?

View File

@ -3087,7 +3087,7 @@ keybind: Keybinds = .{},
/// 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)
/// to reference the user's home directory.
///
/// Available since: 1.2.0
@"bell-audio-path": ?Path = null,
@ -3095,7 +3095,6 @@ keybind: Keybinds = .{},
/// 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)
///
/// Available since: 1.2.0
@"bell-audio-volume": f64 = 0.5,

View File

@ -32,6 +32,14 @@ pub const Path = union(enum) {
return std.meta.eql(self, other);
}
/// Returns the path as a C-compatible null-terminated string pointer.
pub fn cval(self: Path) [*:0]const u8 {
return switch (self) {
.optional => |path| path.ptr,
.required => |path| path.ptr,
};
}
/// Parse the input and return a Path. A leading `?` indicates that the path
/// is _optional_ and an error should not be logged or displayed to the user
/// if that path does not exist. Otherwise the path is required and an error