Skip to content

Commit

Permalink
gtk: implement audio bell
Browse files Browse the repository at this point in the history
  • Loading branch information
jcollie committed Feb 1, 2025
1 parent c5508e7 commit aaacbb6
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 3 deletions.
1 change: 1 addition & 0 deletions include/ghostty.h
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,7 @@ typedef enum {
GHOSTTY_ACTION_COLOR_CHANGE,
GHOSTTY_ACTION_RELOAD_CONFIG,
GHOSTTY_ACTION_CONFIG_CHANGE,
GHOSTTY_ACTION_BELL,
} ghostty_action_tag_e;

typedef union {
Expand Down
14 changes: 14 additions & 0 deletions src/Surface.zig
Original file line number Diff line number Diff line change
Expand Up @@ -943,6 +943,8 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
.present_surface => try self.presentSurface(),

.password_input => |v| try self.passwordInput(v),

.bell => self.bell(),
}
}

Expand Down Expand Up @@ -4694,3 +4696,15 @@ fn presentSurface(self: *Surface) !void {
{},
);
}

fn bell(self: *Surface) void {
self.rt_app.performAction(
.{ .surface = self },
.bell,
{},
) catch |err| {
// We ignore this error because we don't want to fail this entire
// operation just because the apprt failed to activate the bell.
log.warn("apprt failed to activate the bell err={}", .{err});
};
}
4 changes: 4 additions & 0 deletions src/apprt/action.zig
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,9 @@ pub const Action = union(Key) {
/// for changes.
config_change: ConfigChange,

/// The system has received a request to activate the bell.
bell: void,

/// Sync with: ghostty_action_tag_e
pub const Key = enum(c_int) {
quit,
Expand Down Expand Up @@ -266,6 +269,7 @@ pub const Action = union(Key) {
color_change,
reload_config,
config_change,
bell,
};

/// Sync with: ghostty_action_u
Expand Down
1 change: 1 addition & 0 deletions src/apprt/glfw.zig
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ pub const App = struct {
.pwd,
.config_change,
.toggle_maximize,
.bell,
=> log.info("unimplemented action={}", .{action}),
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/apprt/gtk/App.zig
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@ pub fn performAction(
.toggle_split_zoom => self.toggleSplitZoom(target),
.toggle_window_decorations => self.toggleWindowDecorations(target),
.quit_timer => self.quitTimer(value),
.bell => self.bell(target),

// Unimplemented
.close_all_windows,
Expand Down Expand Up @@ -999,6 +1000,16 @@ pub fn reloadConfig(
self.config = config;
}

fn bell(
_: *App,
target: apprt.Target,
) void {
switch (target) {
.app => {},
.surface => |surface| surface.rt_surface.bell(),
}
}

/// Call this anytime the configuration changes.
fn syncConfigChanges(self: *App) !void {
try self.updateConfigErrors();
Expand Down
76 changes: 76 additions & 0 deletions src/apprt/gtk/Surface.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1279,6 +1279,82 @@ fn showContextMenu(self: *Surface, x: f32, y: f32) void {
c.gtk_popover_popup(@ptrCast(@alignCast(window.context_menu)));
}

pub fn bell(self: *Surface) void {
inline for (std.meta.fields(configpkg.Config.BellFeatures.Features)) |field| {
const feature = std.meta.stringToEnum(configpkg.Config.BellFeatures.Features, field.name) orelse unreachable;
const enabled = @field(self.app.config.@"bell-features", field.name);
if (enabled) {
switch (feature) {
.system => system: {
const native = c.gtk_widget_get_native(@ptrCast(@alignCast(self.overlay))) orelse break :system;
const surface = c.gtk_native_get_surface(native) orelse break :system;
c.gdk_surface_beep(surface);
},
.audio => audio: {
var arena = std.heap.ArenaAllocator.init(self.app.core_app.alloc);
defer arena.deinit();
const alloc = arena.allocator();
const filename = self.app.config.@"bell-audio" orelse break :audio;
const pathname = pathname: {
if (std.fs.path.isAbsolute(filename))
break :pathname alloc.dupeZ(u8, filename) catch |err| {
log.warn("unable to allocate space for bell audio pathname: {}", .{err});
break :audio;
}
else
break :pathname std.fs.path.joinZ(alloc, &.{
internal_os.xdg.config(alloc, .{ .subdir = "ghostty/media" }) catch |err| {
log.warn("unable to determine media config subdir: {}", .{err});
break :audio;
},
filename,
}) catch |err| {
log.warn("unable to allocate space for bell audio pathname: {}", .{err});
break :audio;
};
};
std.fs.accessAbsoluteZ(pathname, .{ .mode = .read_only }) catch {
log.warn("unable to find sound file: {s}", .{filename});
break :audio;
};
const stream = c.gtk_media_file_new_for_filename(pathname);
_ = c.g_signal_connect_data(
stream,
"notify::error",
c.G_CALLBACK(&gtkStreamError),
stream,
null,
c.G_CONNECT_DEFAULT,
);
_ = c.g_signal_connect_data(
stream,
"notify::ended",
c.G_CALLBACK(&gtkStreamEnded),
stream,
null,
c.G_CONNECT_DEFAULT,
);
c.gtk_media_stream_set_volume(stream, 1.0);
c.gtk_media_stream_play(stream);
},
// inline else => {
// log.warn("bell feature '{s}' is not supported", .{field.name});
// },
}
}
}
}

fn gtkStreamError(stream: ?*c.GObject) callconv(.C) void {
const err = c.gtk_media_stream_get_error(@ptrCast(stream));
if (err) |e|
log.err("error playing bell: {s} {d} {s}", .{ c.g_quark_to_string(e.*.domain), e.*.code, e.*.message });
}

fn gtkStreamEnded(stream: ?*c.GObject) callconv(.C) void {
c.g_object_unref(stream);
}

fn gtkRealize(area: *c.GtkGLArea, ud: ?*anyopaque) callconv(.C) void {
log.debug("gl surface realized", .{});

Expand Down
3 changes: 3 additions & 0 deletions src/apprt/surface.zig
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ pub const Message = union(enum) {
/// The terminal has reported a change in the working directory.
pwd_change: WriteReq,

/// Bell
bell: void,

pub const ReportTitleStyle = enum {
csi_21_t,

Expand Down
32 changes: 32 additions & 0 deletions src/config/Config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2247,6 +2247,30 @@ term: []const u8 = "xterm-ghostty",
/// This only works on macOS since only macOS has an auto-update feature.
@"auto-update-channel": ?build_config.ReleaseChannel = null,

/// Bell features to enable if bell support is available in your runtime. 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, so you must explicitly disable features you don't
/// want.
///
/// Available features:
///
/// * `system` - Use a system function to play an audible sound. This differs
/// from the `audio` feature in that the sound played is not customizable
/// from within Ghostty. Your system may allow for the sound to be
/// customized externally to Ghostty.
/// * `audio` - Play a custom sound. (GTK only).
///
/// Example: `audio`, `no-audio`, `system`, `no-system`:
///
/// 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 `media`
/// subdirectory of the Ghostty configuration directory.
@"bell-audio": ?[:0]const u8 = null,

/// This is set by the CLI parser for deinit.
_arena: ?ArenaAllocator = null,

Expand Down Expand Up @@ -6881,3 +6905,11 @@ test "theme specifying light/dark sets theme usage in conditional state" {
try testing.expect(cfg._conditional_set.contains(.theme));
}
}

/// Bell features
pub const BellFeatures = packed struct {
system: bool = false,
audio: bool = false,

pub const Features = std.meta.FieldEnum(@This());
};
5 changes: 2 additions & 3 deletions src/termio/stream_handler.zig
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,8 @@ pub const StreamHandler = struct {
try self.terminal.printRepeat(count);
}

pub fn bell(self: StreamHandler) !void {
_ = self;
log.info("BELL", .{});
pub fn bell(self: *StreamHandler) !void {
self.surfaceMessageWriter(.{ .bell = {} });
}

pub fn backspace(self: *StreamHandler) !void {
Expand Down

0 comments on commit aaacbb6

Please sign in to comment.