From 1f695c2646fc7ca3cbe43c77918d8b5c0cb47fa3 Mon Sep 17 00:00:00 2001 From: Maciej Bartczak <39600846+maciekbartczak@users.noreply.github.com> Date: Sun, 2 Mar 2025 17:38:47 +0100 Subject: [PATCH] gtk: implement sensitive content reveal mechanism when showing paste confirmation in secure input mode --- src/apprt/gtk/App.zig | 11 +++- src/apprt/gtk/ClipboardConfirmationWindow.zig | 32 ++++++++++- src/apprt/gtk/Surface.zig | 26 ++++----- src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp | 50 +++++++++++++---- src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp | 50 +++++++++++++---- src/apprt/gtk/ui/1.5/ccw-paste.blp | 54 ++++++++++++++----- 6 files changed, 174 insertions(+), 49 deletions(-) diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 50b15f6a3..6266d4c30 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -502,12 +502,12 @@ pub fn performAction( .quit_timer => self.quitTimer(value), .prompt_title => try self.promptTitle(target), .toggle_quick_terminal => return try self.toggleQuickTerminal(), + .secure_input => self.setSecureInput(target, value), // Unimplemented .close_all_windows, .toggle_visibility, .cell_size, - .secure_input, .key_sequence, .render_inspector, .renderer_health, @@ -1409,6 +1409,15 @@ fn newWindow(self: *App, parent_: ?*CoreSurface) !void { window.present(); } +fn setSecureInput(_: *App, target: apprt.Target, value: apprt.action.SecureInput) void { + switch (target) { + .app => {}, + .surface => |surface| { + surface.rt_surface.setSecureInput(value); + }, + } +} + fn quit(self: *App) void { // If we're already not running, do nothing. if (!self.running) return; diff --git a/src/apprt/gtk/ClipboardConfirmationWindow.zig b/src/apprt/gtk/ClipboardConfirmationWindow.zig index 632f64fa3..ff20342fe 100644 --- a/src/apprt/gtk/ClipboardConfirmationWindow.zig +++ b/src/apprt/gtk/ClipboardConfirmationWindow.zig @@ -25,12 +25,16 @@ dialog: *DialogType, data: [:0]u8, core_surface: *CoreSurface, pending_req: apprt.ClipboardRequest, +content_revealer: *gtk.Revealer, +reveal_button_widget: *gtk.Widget, +hide_button_widget: *gtk.Widget, pub fn create( app: *App, data: []const u8, core_surface: *CoreSurface, request: apprt.ClipboardRequest, + is_secure_input: bool, ) !void { if (app.clipboard_confirmation_window != null) return error.WindowAlreadyExists; @@ -43,6 +47,7 @@ pub fn create( data, core_surface, request, + is_secure_input, ); app.clipboard_confirmation_window = self; @@ -62,6 +67,7 @@ fn init( data: []const u8, core_surface: *CoreSurface, request: apprt.ClipboardRequest, + is_secure_input: bool, ) !void { var builder = switch (DialogType) { adw.AlertDialog => switch (request) { @@ -79,6 +85,9 @@ fn init( defer builder.deinit(); const dialog = builder.getObject(DialogType, "clipboard_confirmation_window").?; + const content_revealer = builder.getObject(gtk.Revealer, "content_revealer").?; + const reveal_button: *gtk.Button = builder.getObject(gtk.Button, "reveal_button").?; + const hide_button: *gtk.Button = builder.getObject(gtk.Button, "hide_button").?; const copy = try app.core_app.alloc.dupeZ(u8, data); errdefer app.core_app.alloc.free(copy); @@ -88,15 +97,24 @@ fn init( .data = copy, .core_surface = core_surface, .pending_req = request, + .content_revealer = content_revealer, + .reveal_button_widget = gobject.ext.cast(gtk.Widget, reveal_button).?, + .hide_button_widget = gobject.ext.cast(gtk.Widget, hide_button).?, }; const text_view = builder.getObject(gtk.TextView, "text_view").?; - const buffer = gtk.TextBuffer.new(null); errdefer buffer.unref(); buffer.insertAtCursor(copy.ptr, @intCast(copy.len)); text_view.setBuffer(buffer); + if (is_secure_input) { + content_revealer.setRevealChild(@intFromBool(false)); + self.reveal_button_widget.setVisible(@intFromBool(true)); + _ = gtk.Button.signals.clicked.connect(reveal_button, *ClipboardConfirmation, gtkRevealButtonClicked, self, .{}); + _ = gtk.Button.signals.clicked.connect(hide_button, *ClipboardConfirmation, gtkHideButtonClicked, self, .{}); + } + switch (DialogType) { adw.AlertDialog => { const parent: ?*gtk.Widget = widget: { @@ -152,3 +170,15 @@ fn gtkResponse(_: *DialogType, response: [*:0]u8, self: *ClipboardConfirmation) } self.destroy(); } + +fn gtkRevealButtonClicked(_: *gtk.Button, self: *ClipboardConfirmation) callconv(.C) void { + self.content_revealer.setRevealChild(@intFromBool(true)); + self.hide_button_widget.setVisible(@intFromBool(true)); + self.reveal_button_widget.setVisible(@intFromBool(false)); +} + +fn gtkHideButtonClicked(_: *gtk.Button, self: *ClipboardConfirmation) callconv(.C) void { + self.content_revealer.setRevealChild(@intFromBool(false)); + self.hide_button_widget.setVisible(@intFromBool(false)); + self.reveal_button_widget.setVisible(@intFromBool(true)); +} diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index ba2e4d244..47b0867ba 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -307,6 +307,9 @@ context_menu: Menu(Surface, "context_menu", false), /// True when we have a precision scroll in progress precision_scroll: bool = false, +/// Flag indicating whether the surface is in secure input mode. +is_secure_input: bool = false, + /// The state of the key event while we're doing IM composition. /// See gtkKeyPressed for detailed descriptions. pub const IMKeyEvent = enum { @@ -1162,6 +1165,7 @@ pub fn setClipboardString( val, &self.core_surface, .{ .osc_52_write = clipboard_type }, + self.is_secure_input, ) catch |window_err| { log.err("failed to create clipboard confirmation window err={}", .{window_err}); }; @@ -1205,12 +1209,7 @@ fn gtkClipboardRead( error.UnauthorizedPaste, => { // Create a dialog and ask the user if they want to paste anyway. - ClipboardConfirmationWindow.create( - self.app, - str, - &self.core_surface, - req.state, - ) catch |window_err| { + ClipboardConfirmationWindow.create(self.app, str, &self.core_surface, req.state, self.is_secure_input) catch |window_err| { log.err("failed to create clipboard confirmation window err={}", .{window_err}); }; return; @@ -2225,12 +2224,7 @@ fn doPaste(self: *Surface, data: [:0]const u8) void { error.UnsafePaste, error.UnauthorizedPaste, => { - ClipboardConfirmationWindow.create( - self.app, - data, - &self.core_surface, - .paste, - ) catch |window_err| { + ClipboardConfirmationWindow.create(self.app, data, &self.core_surface, .paste, self.is_secure_input) catch |window_err| { log.err("failed to create clipboard confirmation window err={}", .{window_err}); }; }, @@ -2321,3 +2315,11 @@ fn gtkPromptTitleResponse(source_object: ?*gobject.Object, result: *gio.AsyncRes } } } + +pub fn setSecureInput(self: *Surface, value: apprt.action.SecureInput) void { + switch (value) { + .on => self.is_secure_input = true, + .off => self.is_secure_input = false, + .toggle => self.is_secure_input = !self.is_secure_input, + } +} diff --git a/src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp b/src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp index 3319bc597..d894059da 100644 --- a/src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp +++ b/src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp @@ -14,18 +14,46 @@ Adw.AlertDialog clipboard_confirmation_window { default-response: "cancel"; close-response: "cancel"; - extra-child: ScrolledWindow { - width-request: 500; - height-request: 250; + extra-child: Box { + orientation: vertical; + spacing: 12; - TextView text_view { - cursor-visible: false; - editable: false; - monospace: true; - top-margin: 8; - left-margin: 8; - bottom-margin: 8; - right-margin: 8; + Button reveal_button { + visible: false; + + styles [ + "destructive-action" + ] + + Adw.ButtonContent { + label: _("Reveal Potentially Sensitive Content"); + icon-name: "dialog-warning-symbolic"; + } + } + + Button hide_button { + visible: false; + label: _("Hide Potentially Sensitive Content"); + } + + Revealer content_revealer { + reveal-child: true; + transition-type: slide_down; + + ScrolledWindow { + width-request: 500; + height-request: 250; + + TextView text_view { + cursor-visible: false; + editable: false; + monospace: true; + top-margin: 8; + left-margin: 8; + bottom-margin: 8; + right-margin: 8; + } + } } }; } diff --git a/src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp b/src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp index 8372f0ccb..a51a9cc2c 100644 --- a/src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp +++ b/src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp @@ -14,18 +14,46 @@ Adw.AlertDialog clipboard_confirmation_window { default-response: "cancel"; close-response: "cancel"; - extra-child: ScrolledWindow { - width-request: 500; - height-request: 250; + extra-child: Box { + orientation: vertical; + spacing: 12; - TextView text_view { - cursor-visible: false; - editable: false; - monospace: true; - top-margin: 8; - left-margin: 8; - bottom-margin: 8; - right-margin: 8; + Button reveal_button { + visible: false; + + styles [ + "destructive-action" + ] + + Adw.ButtonContent { + label: _("Reveal Potentially Sensitive Content"); + icon-name: "dialog-warning-symbolic"; + } + } + + Button hide_button { + visible: false; + label: _("Hide Potentially Sensitive Content"); + } + + Revealer content_revealer { + reveal-child: true; + transition-type: slide_down; + + ScrolledWindow { + width-request: 500; + height-request: 250; + + TextView text_view { + cursor-visible: false; + editable: false; + monospace: true; + top-margin: 8; + left-margin: 8; + bottom-margin: 8; + right-margin: 8; + } + } } }; } diff --git a/src/apprt/gtk/ui/1.5/ccw-paste.blp b/src/apprt/gtk/ui/1.5/ccw-paste.blp index 57aaabfd1..771c99058 100644 --- a/src/apprt/gtk/ui/1.5/ccw-paste.blp +++ b/src/apprt/gtk/ui/1.5/ccw-paste.blp @@ -10,22 +10,50 @@ Adw.AlertDialog clipboard_confirmation_window { cancel: _("Cancel") suggested, ok: _("Paste") destructive ] - + default-response: "cancel"; close-response: "cancel"; - - extra-child: ScrolledWindow { - width-request: 500; - height-request: 250; - TextView text_view { - cursor-visible: false; - editable: false; - monospace: true; - top-margin: 8; - left-margin: 8; - bottom-margin: 8; - right-margin: 8; + extra-child: Box { + orientation: vertical; + spacing: 12; + + Button reveal_button { + visible: false; + + styles [ + "destructive-action" + ] + + Adw.ButtonContent { + label: _("Reveal Potentially Sensitive Content"); + icon-name: "dialog-warning-symbolic"; + } + } + + Button hide_button { + visible: false; + label: _("Hide Potentially Sensitive Content"); + } + + Revealer content_revealer { + reveal-child: true; + transition-type: slide_down; + + ScrolledWindow { + width-request: 500; + height-request: 250; + + TextView text_view { + cursor-visible: false; + editable: false; + monospace: true; + top-margin: 8; + left-margin: 8; + bottom-margin: 8; + right-margin: 8; + } + } } }; }