From b4404b691d6baff9e98a5a4e33ce70d22f15942f Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sat, 11 May 2024 15:43:55 -0500 Subject: [PATCH 1/5] suppress identical desktop notifications --- src/Surface.zig | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/Surface.zig b/src/Surface.zig index 07db137fa..03003619c 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3486,8 +3486,33 @@ fn completeClipboardReadOSC52( self.io_thread.wakeup.notify() catch {}; } +const hash_algorithm = std.crypto.hash.sha2.Sha224; +var last_notification_time: ?std.time.Instant = null; +var last_notification_digest = [_]u8{0} ** hash_algorithm.digest_length; + fn showDesktopNotification(self: *Surface, title: [:0]const u8, body: [:0]const u8) !void { if (@hasDecl(apprt.Surface, "showDesktopNotification")) { + const now = try std.time.Instant.now(); + + var new_notification_digest: [hash_algorithm.digest_length]u8 = undefined; + + var hash = hash_algorithm.init(.{}); + hash.update(title); + hash.update(body); + hash.final(&new_notification_digest); + + if (std.mem.eql(u8, &last_notification_digest, &new_notification_digest)) { + if (last_notification_time) |last| { + if (now.since(last) < 5 * std.time.ns_per_s) { + log.warn("suppressing identical notification", .{}); + return; + } + } + } + + last_notification_time = now; + @memcpy(&last_notification_digest, &new_notification_digest); + try self.rt_surface.showDesktopNotification(title, body); } else log.warn("runtime doesn't support desktop notifications", .{}); } From 085c964be795f0d969dc4f8fbf597deddf3548cd Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sat, 11 May 2024 15:52:47 -0500 Subject: [PATCH 2/5] add strict rate limit --- src/Surface.zig | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index 03003619c..04e36df6d 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3496,13 +3496,22 @@ fn showDesktopNotification(self: *Surface, title: [:0]const u8, body: [:0]const var new_notification_digest: [hash_algorithm.digest_length]u8 = undefined; - var hash = hash_algorithm.init(.{}); - hash.update(title); - hash.update(body); - hash.final(&new_notification_digest); + if (last_notification_time) |last| { + if (now.since(last) < 1 * std.time.ns_per_s) { + log.warn("rate limiting desktop notifications", .{}); + return; + } + } - if (std.mem.eql(u8, &last_notification_digest, &new_notification_digest)) { - if (last_notification_time) |last| { + { + var hash = hash_algorithm.init(.{}); + hash.update(title); + hash.update(body); + hash.final(&new_notification_digest); + } + + if (last_notification_time) |last| { + if (std.mem.eql(u8, &last_notification_digest, &new_notification_digest)) { if (now.since(last) < 5 * std.time.ns_per_s) { log.warn("suppressing identical notification", .{}); return; From a89f817b9e812904286dfd3ba93bfd44136f26be Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sat, 11 May 2024 15:59:57 -0500 Subject: [PATCH 3/5] adjust log message --- src/Surface.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Surface.zig b/src/Surface.zig index 04e36df6d..8ab678744 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3513,7 +3513,7 @@ fn showDesktopNotification(self: *Surface, title: [:0]const u8, body: [:0]const if (last_notification_time) |last| { if (std.mem.eql(u8, &last_notification_digest, &new_notification_digest)) { if (now.since(last) < 5 * std.time.ns_per_s) { - log.warn("suppressing identical notification", .{}); + log.warn("suppressing identical desktop notification", .{}); return; } } From 7c893881c349203fbc0e987e55988eaf887848a9 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Fri, 17 May 2024 17:13:43 -0500 Subject: [PATCH 4/5] Address review comments 1. Switch to using Wyhash instead of a cryptographic hash. 2. Move global variables to App struct. --- src/App.zig | 6 ++++++ src/Surface.zig | 33 +++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/App.zig b/src/App.zig index 41a7887b2..314d0b25b 100644 --- a/src/App.zig +++ b/src/App.zig @@ -45,6 +45,12 @@ quit: bool, /// same font configuration. font_grid_set: font.SharedGridSet, +// Used to rate limit desktop notifications. Some platforms (notably macOS) will +// run out of resources if desktop notifications are sent too fast and the OS +// will kill Ghostty. +last_notification_time: ?std.time.Instant = null, +last_notification_digest: u64 = 0, + /// Initialize the main app instance. This creates the main window, sets /// up the renderer state, compiles the shaders, etc. This is the primary /// "startup" logic. diff --git a/src/Surface.zig b/src/Surface.zig index 8ab678744..28e702d7a 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3486,32 +3486,37 @@ fn completeClipboardReadOSC52( self.io_thread.wakeup.notify() catch {}; } -const hash_algorithm = std.crypto.hash.sha2.Sha224; -var last_notification_time: ?std.time.Instant = null; -var last_notification_digest = [_]u8{0} ** hash_algorithm.digest_length; +// Wyhash is used to hash the contents of the desktop notification to limit how +// fast identical notifications can be sent sequentially. +const hash_algorithm = std.hash.Wyhash; +// This seed for Wyhash was literally chosen at random. The actual seed (AFAIK) +// shouldn't matter as long as it stays constant. +const hash_seed = 0xb8179c65b93cc558; fn showDesktopNotification(self: *Surface, title: [:0]const u8, body: [:0]const u8) !void { if (@hasDecl(apprt.Surface, "showDesktopNotification")) { const now = try std.time.Instant.now(); - var new_notification_digest: [hash_algorithm.digest_length]u8 = undefined; - - if (last_notification_time) |last| { + // Set a limit of one desktop notification per second so that the OS + // doesn't kill us when we run out of resources. + if (self.app.last_notification_time) |last| { if (now.since(last) < 1 * std.time.ns_per_s) { log.warn("rate limiting desktop notifications", .{}); return; } } - { - var hash = hash_algorithm.init(.{}); + const new_notification_digest = d: { + var hash = hash_algorithm.init(hash_seed); hash.update(title); hash.update(body); - hash.final(&new_notification_digest); - } + break :d hash.final(); + }; - if (last_notification_time) |last| { - if (std.mem.eql(u8, &last_notification_digest, &new_notification_digest)) { + // Set a limit of one notification per five seconds for desktop + // notifications with identical content. + if (self.app.last_notification_time) |last| { + if (self.app.last_notification_digest == new_notification_digest) { if (now.since(last) < 5 * std.time.ns_per_s) { log.warn("suppressing identical desktop notification", .{}); return; @@ -3519,8 +3524,8 @@ fn showDesktopNotification(self: *Surface, title: [:0]const u8, body: [:0]const } } - last_notification_time = now; - @memcpy(&last_notification_digest, &new_notification_digest); + self.app.last_notification_time = now; + self.app.last_notification_digest = new_notification_digest; try self.rt_surface.showDesktopNotification(title, body); } else log.warn("runtime doesn't support desktop notifications", .{}); From 98b05ffd09aaa19a0ce759a6d5d58b840c56af08 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 26 May 2024 20:49:00 -0700 Subject: [PATCH 5/5] core: nitpick some var names --- src/Surface.zig | 73 ++++++++++++++++++++++++------------------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index 28e702d7a..b7b2588e6 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3486,49 +3486,48 @@ fn completeClipboardReadOSC52( self.io_thread.wakeup.notify() catch {}; } -// Wyhash is used to hash the contents of the desktop notification to limit how -// fast identical notifications can be sent sequentially. -const hash_algorithm = std.hash.Wyhash; -// This seed for Wyhash was literally chosen at random. The actual seed (AFAIK) -// shouldn't matter as long as it stays constant. -const hash_seed = 0xb8179c65b93cc558; - fn showDesktopNotification(self: *Surface, title: [:0]const u8, body: [:0]const u8) !void { - if (@hasDecl(apprt.Surface, "showDesktopNotification")) { - const now = try std.time.Instant.now(); + if (comptime !@hasDecl(apprt.Surface, "showDesktopNotification")) { + log.warn("runtime doesn't support desktop notifications", .{}); + return; + } - // Set a limit of one desktop notification per second so that the OS - // doesn't kill us when we run out of resources. - if (self.app.last_notification_time) |last| { - if (now.since(last) < 1 * std.time.ns_per_s) { - log.warn("rate limiting desktop notifications", .{}); + // Wyhash is used to hash the contents of the desktop notification to limit + // how fast identical notifications can be sent sequentially. + const hash_algorithm = std.hash.Wyhash; + + const now = try std.time.Instant.now(); + + // Set a limit of one desktop notification per second so that the OS + // doesn't kill us when we run out of resources. + if (self.app.last_notification_time) |last| { + if (now.since(last) < 1 * std.time.ns_per_s) { + log.warn("rate limiting desktop notifications", .{}); + return; + } + } + + const new_digest = d: { + var hash = hash_algorithm.init(0); + hash.update(title); + hash.update(body); + break :d hash.final(); + }; + + // Set a limit of one notification per five seconds for desktop + // notifications with identical content. + if (self.app.last_notification_time) |last| { + if (self.app.last_notification_digest == new_digest) { + if (now.since(last) < 5 * std.time.ns_per_s) { + log.warn("suppressing identical desktop notification", .{}); return; } } + } - const new_notification_digest = d: { - var hash = hash_algorithm.init(hash_seed); - hash.update(title); - hash.update(body); - break :d hash.final(); - }; - - // Set a limit of one notification per five seconds for desktop - // notifications with identical content. - if (self.app.last_notification_time) |last| { - if (self.app.last_notification_digest == new_notification_digest) { - if (now.since(last) < 5 * std.time.ns_per_s) { - log.warn("suppressing identical desktop notification", .{}); - return; - } - } - } - - self.app.last_notification_time = now; - self.app.last_notification_digest = new_notification_digest; - - try self.rt_surface.showDesktopNotification(title, body); - } else log.warn("runtime doesn't support desktop notifications", .{}); + self.app.last_notification_time = now; + self.app.last_notification_digest = new_digest; + try self.rt_surface.showDesktopNotification(title, body); } pub const face_ttf = @embedFile("font/res/JetBrainsMono-Regular.ttf");