@@ -26,6 +26,10 @@ const inspector = @import("inspector.zig");
26
26
const gtk_key = @import ("key.zig" );
27
27
const c = @import ("c.zig" ).c ;
28
28
29
+ const gtk = @import ("gtk" );
30
+ const gobject = @import ("gobject" );
31
+ const glib = @import ("glib" );
32
+
29
33
const log = std .log .scoped (.gtk_surface );
30
34
31
35
/// This is detected by the OpenGL renderer to move to a single-threaded
@@ -1295,6 +1299,81 @@ fn showContextMenu(self: *Surface, x: f32, y: f32) void {
1295
1299
c .gtk_popover_popup (@ptrCast (@alignCast (window .context_menu )));
1296
1300
}
1297
1301
1302
+ pub fn bell (self : * Surface ) void {
1303
+ inline for (std .meta .fields (configpkg .Config .BellFeatures .Features )) | field | {
1304
+ const feature = std .meta .stringToEnum (configpkg .Config .BellFeatures .Features , field .name ) orelse unreachable ;
1305
+ const enabled = @field (self .app .config .@"bell-features" , field .name );
1306
+ if (enabled ) {
1307
+ switch (feature ) {
1308
+ .system = > system : {
1309
+ const native = gtk .Widget .getNative (@ptrCast (@alignCast (self .overlay ))) orelse break :system ;
1310
+ const surface = native .getSurface () orelse break :system ;
1311
+ surface .beep ();
1312
+ },
1313
+ .audio = > audio : {
1314
+ var arena = std .heap .ArenaAllocator .init (self .app .core_app .alloc );
1315
+ defer arena .deinit ();
1316
+ const alloc = arena .allocator ();
1317
+ const filename = self .app .config .@"bell-audio" orelse break :audio ;
1318
+ const pathname = pathname : {
1319
+ if (std .fs .path .isAbsolute (filename ))
1320
+ break :pathname alloc .dupeZ (u8 , filename ) catch | err | {
1321
+ log .warn ("unable to allocate space for bell audio pathname: {}" , .{err });
1322
+ break :audio ;
1323
+ }
1324
+ else
1325
+ break :pathname std .fs .path .joinZ (alloc , &.{
1326
+ internal_os .xdg .config (alloc , .{ .subdir = "ghostty/media" }) catch | err | {
1327
+ log .warn ("unable to determine media config subdir: {}" , .{err });
1328
+ break :audio ;
1329
+ },
1330
+ filename ,
1331
+ }) catch | err | {
1332
+ log .warn ("unable to allocate space for bell audio pathname: {}" , .{err });
1333
+ break :audio ;
1334
+ };
1335
+ };
1336
+ std .fs .accessAbsoluteZ (pathname , .{ .mode = .read_only }) catch {
1337
+ log .warn ("unable to find sound file: {s}" , .{filename });
1338
+ break :audio ;
1339
+ };
1340
+
1341
+ const file = gtk .MediaFile .newForFilename (pathname );
1342
+ const stream = file .as (gtk .MediaStream );
1343
+
1344
+ _ = gobject .Object .signals .notify .connect (
1345
+ stream ,
1346
+ ? * anyopaque ,
1347
+ gtkStreamError ,
1348
+ null ,
1349
+ .{ .detail = "error" },
1350
+ );
1351
+ _ = gobject .Object .signals .notify .connect (
1352
+ stream ,
1353
+ ? * anyopaque ,
1354
+ gtkStreamEnded ,
1355
+ null ,
1356
+ .{ .detail = "ended" },
1357
+ );
1358
+
1359
+ stream .setVolume (1.0 );
1360
+ stream .play ();
1361
+ },
1362
+ }
1363
+ }
1364
+ }
1365
+ }
1366
+
1367
+ fn gtkStreamError (stream : * gtk.MediaStream , _ : * gobject.ParamSpec , _ : ? * anyopaque ) callconv (.C ) void {
1368
+ const err = stream .getError ();
1369
+ if (err ) | e |
1370
+ log .err ("error playing bell: {s} {d} {s}" , .{ glib .quarkToString (e .f_domain ), e .f_code , e .f_message orelse "" });
1371
+ }
1372
+
1373
+ fn gtkStreamEnded (stream : * gtk.MediaStream , _ : * gobject.ParamSpec , _ : ? * anyopaque ) callconv (.C ) void {
1374
+ stream .unref ();
1375
+ }
1376
+
1298
1377
fn gtkRealize (area : * c.GtkGLArea , ud : ? * anyopaque ) callconv (.C ) void {
1299
1378
log .debug ("gl surface realized" , .{});
1300
1379
0 commit comments