mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
gtk: implement sensitive content reveal mechanism when showing paste confirmation in secure input mode
This commit is contained in:
@ -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;
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
Reference in New Issue
Block a user