gtk: implement sensitive content reveal mechanism when showing paste confirmation in secure input mode

This commit is contained in:
Maciej Bartczak
2025-03-02 17:38:47 +01:00
parent ee8ae196ee
commit 1f695c2646
6 changed files with 174 additions and 49 deletions

View File

@ -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;

View File

@ -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));
}

View File

@ -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,
}
}

View File

@ -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;
}
}
}
};
}

View File

@ -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;
}
}
}
};
}

View File

@ -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;
}
}
}
};
}