Merge 152bca3313eafb43a93d3669d53db75a9b9275e9 into 5f49ffad6a2dae1af3e186a5715d9553bbf94eb0

This commit is contained in:
Jeffrey C. Ollie
2024-11-20 12:24:01 -03:00
committed by GitHub
11 changed files with 226 additions and 3 deletions

3
media/README.md Normal file
View File

@ -0,0 +1,3 @@
These files are copied from the xdg-sound-theme, found at:
https://gitlab.freedesktop.org/xdg/xdg-sound-theme

BIN
media/bell.oga Normal file

Binary file not shown.

BIN
media/message.oga Normal file

Binary file not shown.

View File

@ -51,6 +51,7 @@
pandoc, pandoc,
hyperfine, hyperfine,
typos, typos,
gst_all_1,
}: let }: let
# See package.nix. Keep in sync. # See package.nix. Keep in sync.
rpathLibs = rpathLibs =
@ -153,6 +154,9 @@ in
libadwaita libadwaita
gtk4 gtk4
glib glib
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 # This should be set onto the rpath of the ghostty binary if you want

View File

@ -17,6 +17,7 @@
glib, glib,
gtk4, gtk4,
libadwaita, libadwaita,
gst_all_1,
wrapGAppsHook4, wrapGAppsHook4,
gsettings-desktop-schemas, gsettings-desktop-schemas,
git, git,
@ -51,6 +52,7 @@
../conformance ../conformance
../images ../images
../include ../include
../media
../pkg ../pkg
../src ../src
../vendor ../vendor
@ -144,6 +146,10 @@ in
libadwaita libadwaita
gtk4 gtk4
glib glib
gst_all_1.gstreamer
gst_all_1.gst-plugins-base
gst_all_1.gst-plugins-good
gsettings-desktop-schemas gsettings-desktop-schemas
]; ];
@ -177,6 +183,10 @@ in
mv "$out/share/ghostty/shell-integration" "$shell_integration/shell-integration" mv "$out/share/ghostty/shell-integration" "$shell_integration/shell-integration"
ln -sf "$shell_integration/shell-integration" "$out/share/ghostty/shell-integration" ln -sf "$shell_integration/shell-integration" "$out/share/ghostty/shell-integration"
echo "$shell_integration" >> "$out/nix-support/propagated-user-env-packages" echo "$shell_integration" >> "$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"
''; '';
postFixup = '' postFixup = ''

View File

@ -901,6 +901,8 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
.present_surface => try self.presentSurface(), .present_surface => try self.presentSurface(),
.password_input => |v| try self.passwordInput(v), .password_input => |v| try self.passwordInput(v),
.bell => try self.bell(),
} }
} }
@ -4472,3 +4474,9 @@ fn presentSurface(self: *Surface) !void {
{}, {},
); );
} }
fn bell(self: *Surface) !void {
if (@hasDecl(apprt.Surface, "bell")) {
try self.rt_surface.bell();
} else log.warn("runtime doesn't support bell", .{});
}

View File

@ -1160,6 +1160,77 @@ fn showContextMenu(self: *Surface, x: f32, y: f32) void {
c.gtk_popover_popup(@ptrCast(@alignCast(window.context_menu))); c.gtk_popover_popup(@ptrCast(@alignCast(window.context_menu)));
} }
pub fn bell(self: *Surface) !void {
if (self.app.config.@"bell-features".audio) audio: {
const stream = switch (self.app.config.@"bell-audio") {
.bell => c.gtk_media_file_new_for_resource("/com/mitchellh/ghostty/media/bell.oga"),
.message => c.gtk_media_file_new_for_resource("/com/mitchellh/ghostty/media/message.oga"),
.custom => |filename| stream: {
var arena = std.heap.ArenaAllocator.init(self.app.core_app.alloc);
defer arena.deinit();
const alloc = arena.allocator();
const pathname = pathname: {
if (std.fs.path.isAbsolute(filename))
break :pathname try alloc.dupeZ(u8, filename)
else
break :pathname try std.fs.path.joinZ(alloc, &.{
try internal_os.xdg.config(
alloc,
.{ .subdir = "ghostty/media" },
),
filename,
});
};
std.fs.accessAbsoluteZ(pathname, .{ .mode = .read_only }) catch {
log.warn("unable to find sound file: {s}", .{filename});
break :audio;
};
break :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);
}
if (self.app.config.@"bell-features".visual) {
log.warn("visual bell is not supported", .{});
}
if (self.app.config.@"bell-features".notification) {
log.warn("notification bell is not supported", .{});
}
if (self.app.config.@"bell-features".title) {
log.warn("title bell is not supported", .{});
}
if (self.app.config.@"bell-features".command) {
log.warn("command bell is not supported", .{});
}
}
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 { fn gtkRealize(area: *c.GtkGLArea, ud: ?*anyopaque) callconv(.C) void {
log.debug("gl surface realized", .{}); log.debug("gl surface realized", .{});

View File

@ -50,6 +50,10 @@ const icons = [_]struct {
}; };
pub const gresource_xml = comptimeGenerateGResourceXML(); pub const gresource_xml = comptimeGenerateGResourceXML();
const media = [_][]const u8{
"media/bell.oga",
"media/message.oga",
};
fn comptimeGenerateGResourceXML() []const u8 { fn comptimeGenerateGResourceXML() []const u8 {
comptime { comptime {
@ -97,6 +101,23 @@ fn writeGResourceXML(writer: anytype) !void {
} }
try writer.writeAll( try writer.writeAll(
\\ </gresource> \\ </gresource>
\\
);
try writer.writeAll(
\\ <gresource prefix="/com/mitchellh/ghostty/media">
\\
);
for (media) |pathname| {
try writer.print(
" <file alias=\"{s}\">{s}</file>\n",
.{ std.fs.path.basename(pathname), pathname },
);
}
try writer.writeAll(
\\ </gresource>
\\
);
try writer.writeAll(
\\</gresources> \\</gresources>
\\ \\
); );

View File

@ -79,6 +79,9 @@ pub const Message = union(enum) {
/// The terminal has reported a change in the working directory. /// The terminal has reported a change in the working directory.
pwd_change: WriteReq, pwd_change: WriteReq,
/// Bell
bell: void,
pub const ReportTitleStyle = enum { pub const ReportTitleStyle = enum {
csi_21_t, csi_21_t,

View File

@ -1736,6 +1736,51 @@ term: []const u8 = "xterm-ghostty",
/// Changing this value at runtime works after a small delay. /// Changing this value at runtime works after a small delay.
@"auto-update": AutoUpdate = .check, @"auto-update": AutoUpdate = .check,
/// 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:
///
/// * `audio` - Play an audible sound. (GTK only).
///
/// * `visual` - Flashes a visual indication in the surface that triggered
/// the bell. (Currently not implemented.)
///
/// * `notification` - Displays a desktop notification. (Currently not
/// implemented.)
///
/// * `title` - Will add a visual indicator to the window/tab title.
/// (Currently not implemented.)
///
/// * `command` - Will run a command (e.g. for haptic feedback or flashing a
/// physical light). (Currently not implemented.)
///
/// Example: `audio`, `no-audio`, `visual`, `no-visual`, `notification`, `no-notification`
///
/// By default, no bell features are enabled.
@"bell-features": BellFeatures = .{},
/// If `audio` is an enabled bell feature, this determines whether to use an
/// internal audio file or whether to use a custom file on disk.
///
/// * `bell` - A simple bell sound.
///
/// * `message` - Another bell sound.
///
/// * `custom:<filename>` - The filename of an audio file to play as the bell.
/// If the filename is not an absolute pathname the directory `~/.config/
/// ghostty/media` will be searched for the file.
///
/// The default value is `bell`
@"bell-audio": BellAudio = .{ .bell = {} },
/// If `command` is an enabled bell feature, the command to be run. By default,
/// this value is unset and no command will run.
@"bell-command": ?[:0]const u8 = null,
/// This is set by the CLI parser for deinit. /// This is set by the CLI parser for deinit.
_arena: ?ArenaAllocator = null, _arena: ?ArenaAllocator = null,
@ -4955,3 +5000,62 @@ test "test entryFormatter" {
try p.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try p.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
try std.testing.expectEqualStrings("a = 584y 49w 23h 34m 33s 709ms 551µs 615ns\n", buf.items); try std.testing.expectEqualStrings("a = 584y 49w 23h 34m 33s 709ms 551µs 615ns\n", buf.items);
} }
/// Bell features
pub const BellFeatures = packed struct {
audio: bool = false,
visual: bool = false,
notification: bool = false,
title: bool = false,
command: bool = false,
};
pub const BellAudio = union(enum) {
bell: void,
message: void,
custom: [:0]const u8,
pub fn formatEntry(self: BellAudio, formatter: anytype) !void {
switch (self) {
.bell, .message => try formatter.formatEntry([]const u8, @tagName(self)),
.custom => |filename| {
var buf: [std.fs.max_path_bytes + 7]u8 = undefined;
try formatter.formatEntry(
[]const u8,
std.fmt.bufPrint(
&buf,
"custom:{s}",
.{filename},
) catch return error.OutOfMemory,
);
},
}
}
test "test formatEntry 1" {
var buf = std.ArrayList(u8).init(std.testing.allocator);
defer buf.deinit();
var b: BellAudio = .{ .bell = {} };
try b.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
try std.testing.expectEqualStrings("a = bell\n", buf.items);
}
test "test formatEntry 2" {
var buf = std.ArrayList(u8).init(std.testing.allocator);
defer buf.deinit();
var b: BellAudio = .{ .message = {} };
try b.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
try std.testing.expectEqualStrings("a = message\n", buf.items);
}
test "test formatEntry 3" {
var buf = std.ArrayList(u8).init(std.testing.allocator);
defer buf.deinit();
var b: BellAudio = .{ .custom = "custom.oga" };
try b.formatEntry(formatterpkg.entryFormatter("a", buf.writer()));
try std.testing.expectEqualStrings("a = custom:custom.oga\n", buf.items);
}
};

View File

@ -322,9 +322,8 @@ pub const StreamHandler = struct {
try self.terminal.printRepeat(count); try self.terminal.printRepeat(count);
} }
pub fn bell(self: StreamHandler) !void { pub fn bell(self: *StreamHandler) !void {
_ = self; self.surfaceMessageWriter(.{ .bell = {} });
log.info("BELL", .{});
} }
pub fn backspace(self: *StreamHandler) !void { pub fn backspace(self: *StreamHandler) !void {