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),
|
.quit_timer => self.quitTimer(value),
|
||||||
.prompt_title => try self.promptTitle(target),
|
.prompt_title => try self.promptTitle(target),
|
||||||
.toggle_quick_terminal => return try self.toggleQuickTerminal(),
|
.toggle_quick_terminal => return try self.toggleQuickTerminal(),
|
||||||
|
.secure_input => self.setSecureInput(target, value),
|
||||||
|
|
||||||
// Unimplemented
|
// Unimplemented
|
||||||
.close_all_windows,
|
.close_all_windows,
|
||||||
.toggle_visibility,
|
.toggle_visibility,
|
||||||
.cell_size,
|
.cell_size,
|
||||||
.secure_input,
|
|
||||||
.key_sequence,
|
.key_sequence,
|
||||||
.render_inspector,
|
.render_inspector,
|
||||||
.renderer_health,
|
.renderer_health,
|
||||||
@ -1409,6 +1409,15 @@ fn newWindow(self: *App, parent_: ?*CoreSurface) !void {
|
|||||||
window.present();
|
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 {
|
fn quit(self: *App) void {
|
||||||
// If we're already not running, do nothing.
|
// If we're already not running, do nothing.
|
||||||
if (!self.running) return;
|
if (!self.running) return;
|
||||||
|
@ -25,12 +25,16 @@ dialog: *DialogType,
|
|||||||
data: [:0]u8,
|
data: [:0]u8,
|
||||||
core_surface: *CoreSurface,
|
core_surface: *CoreSurface,
|
||||||
pending_req: apprt.ClipboardRequest,
|
pending_req: apprt.ClipboardRequest,
|
||||||
|
content_revealer: *gtk.Revealer,
|
||||||
|
reveal_button_widget: *gtk.Widget,
|
||||||
|
hide_button_widget: *gtk.Widget,
|
||||||
|
|
||||||
pub fn create(
|
pub fn create(
|
||||||
app: *App,
|
app: *App,
|
||||||
data: []const u8,
|
data: []const u8,
|
||||||
core_surface: *CoreSurface,
|
core_surface: *CoreSurface,
|
||||||
request: apprt.ClipboardRequest,
|
request: apprt.ClipboardRequest,
|
||||||
|
is_secure_input: bool,
|
||||||
) !void {
|
) !void {
|
||||||
if (app.clipboard_confirmation_window != null) return error.WindowAlreadyExists;
|
if (app.clipboard_confirmation_window != null) return error.WindowAlreadyExists;
|
||||||
|
|
||||||
@ -43,6 +47,7 @@ pub fn create(
|
|||||||
data,
|
data,
|
||||||
core_surface,
|
core_surface,
|
||||||
request,
|
request,
|
||||||
|
is_secure_input,
|
||||||
);
|
);
|
||||||
|
|
||||||
app.clipboard_confirmation_window = self;
|
app.clipboard_confirmation_window = self;
|
||||||
@ -62,6 +67,7 @@ fn init(
|
|||||||
data: []const u8,
|
data: []const u8,
|
||||||
core_surface: *CoreSurface,
|
core_surface: *CoreSurface,
|
||||||
request: apprt.ClipboardRequest,
|
request: apprt.ClipboardRequest,
|
||||||
|
is_secure_input: bool,
|
||||||
) !void {
|
) !void {
|
||||||
var builder = switch (DialogType) {
|
var builder = switch (DialogType) {
|
||||||
adw.AlertDialog => switch (request) {
|
adw.AlertDialog => switch (request) {
|
||||||
@ -79,6 +85,9 @@ fn init(
|
|||||||
defer builder.deinit();
|
defer builder.deinit();
|
||||||
|
|
||||||
const dialog = builder.getObject(DialogType, "clipboard_confirmation_window").?;
|
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);
|
const copy = try app.core_app.alloc.dupeZ(u8, data);
|
||||||
errdefer app.core_app.alloc.free(copy);
|
errdefer app.core_app.alloc.free(copy);
|
||||||
@ -88,15 +97,24 @@ fn init(
|
|||||||
.data = copy,
|
.data = copy,
|
||||||
.core_surface = core_surface,
|
.core_surface = core_surface,
|
||||||
.pending_req = request,
|
.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 text_view = builder.getObject(gtk.TextView, "text_view").?;
|
||||||
|
|
||||||
const buffer = gtk.TextBuffer.new(null);
|
const buffer = gtk.TextBuffer.new(null);
|
||||||
errdefer buffer.unref();
|
errdefer buffer.unref();
|
||||||
buffer.insertAtCursor(copy.ptr, @intCast(copy.len));
|
buffer.insertAtCursor(copy.ptr, @intCast(copy.len));
|
||||||
text_view.setBuffer(buffer);
|
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) {
|
switch (DialogType) {
|
||||||
adw.AlertDialog => {
|
adw.AlertDialog => {
|
||||||
const parent: ?*gtk.Widget = widget: {
|
const parent: ?*gtk.Widget = widget: {
|
||||||
@ -152,3 +170,15 @@ fn gtkResponse(_: *DialogType, response: [*:0]u8, self: *ClipboardConfirmation)
|
|||||||
}
|
}
|
||||||
self.destroy();
|
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
|
/// True when we have a precision scroll in progress
|
||||||
precision_scroll: bool = false,
|
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.
|
/// The state of the key event while we're doing IM composition.
|
||||||
/// See gtkKeyPressed for detailed descriptions.
|
/// See gtkKeyPressed for detailed descriptions.
|
||||||
pub const IMKeyEvent = enum {
|
pub const IMKeyEvent = enum {
|
||||||
@ -1162,6 +1165,7 @@ pub fn setClipboardString(
|
|||||||
val,
|
val,
|
||||||
&self.core_surface,
|
&self.core_surface,
|
||||||
.{ .osc_52_write = clipboard_type },
|
.{ .osc_52_write = clipboard_type },
|
||||||
|
self.is_secure_input,
|
||||||
) catch |window_err| {
|
) catch |window_err| {
|
||||||
log.err("failed to create clipboard confirmation window err={}", .{window_err});
|
log.err("failed to create clipboard confirmation window err={}", .{window_err});
|
||||||
};
|
};
|
||||||
@ -1205,12 +1209,7 @@ fn gtkClipboardRead(
|
|||||||
error.UnauthorizedPaste,
|
error.UnauthorizedPaste,
|
||||||
=> {
|
=> {
|
||||||
// Create a dialog and ask the user if they want to paste anyway.
|
// Create a dialog and ask the user if they want to paste anyway.
|
||||||
ClipboardConfirmationWindow.create(
|
ClipboardConfirmationWindow.create(self.app, str, &self.core_surface, req.state, self.is_secure_input) catch |window_err| {
|
||||||
self.app,
|
|
||||||
str,
|
|
||||||
&self.core_surface,
|
|
||||||
req.state,
|
|
||||||
) catch |window_err| {
|
|
||||||
log.err("failed to create clipboard confirmation window err={}", .{window_err});
|
log.err("failed to create clipboard confirmation window err={}", .{window_err});
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@ -2225,12 +2224,7 @@ fn doPaste(self: *Surface, data: [:0]const u8) void {
|
|||||||
error.UnsafePaste,
|
error.UnsafePaste,
|
||||||
error.UnauthorizedPaste,
|
error.UnauthorizedPaste,
|
||||||
=> {
|
=> {
|
||||||
ClipboardConfirmationWindow.create(
|
ClipboardConfirmationWindow.create(self.app, data, &self.core_surface, .paste, self.is_secure_input) catch |window_err| {
|
||||||
self.app,
|
|
||||||
data,
|
|
||||||
&self.core_surface,
|
|
||||||
.paste,
|
|
||||||
) catch |window_err| {
|
|
||||||
log.err("failed to create clipboard confirmation window err={}", .{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";
|
default-response: "cancel";
|
||||||
close-response: "cancel";
|
close-response: "cancel";
|
||||||
|
|
||||||
extra-child: ScrolledWindow {
|
extra-child: Box {
|
||||||
width-request: 500;
|
orientation: vertical;
|
||||||
height-request: 250;
|
spacing: 12;
|
||||||
|
|
||||||
TextView text_view {
|
Button reveal_button {
|
||||||
cursor-visible: false;
|
visible: false;
|
||||||
editable: false;
|
|
||||||
monospace: true;
|
styles [
|
||||||
top-margin: 8;
|
"destructive-action"
|
||||||
left-margin: 8;
|
]
|
||||||
bottom-margin: 8;
|
|
||||||
right-margin: 8;
|
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";
|
default-response: "cancel";
|
||||||
close-response: "cancel";
|
close-response: "cancel";
|
||||||
|
|
||||||
extra-child: ScrolledWindow {
|
extra-child: Box {
|
||||||
width-request: 500;
|
orientation: vertical;
|
||||||
height-request: 250;
|
spacing: 12;
|
||||||
|
|
||||||
TextView text_view {
|
Button reveal_button {
|
||||||
cursor-visible: false;
|
visible: false;
|
||||||
editable: false;
|
|
||||||
monospace: true;
|
styles [
|
||||||
top-margin: 8;
|
"destructive-action"
|
||||||
left-margin: 8;
|
]
|
||||||
bottom-margin: 8;
|
|
||||||
right-margin: 8;
|
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";
|
default-response: "cancel";
|
||||||
close-response: "cancel";
|
close-response: "cancel";
|
||||||
|
|
||||||
extra-child: ScrolledWindow {
|
extra-child: Box {
|
||||||
width-request: 500;
|
orientation: vertical;
|
||||||
height-request: 250;
|
spacing: 12;
|
||||||
|
|
||||||
TextView text_view {
|
Button reveal_button {
|
||||||
cursor-visible: false;
|
visible: false;
|
||||||
editable: false;
|
|
||||||
monospace: true;
|
styles [
|
||||||
top-margin: 8;
|
"destructive-action"
|
||||||
left-margin: 8;
|
]
|
||||||
bottom-margin: 8;
|
|
||||||
right-margin: 8;
|
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