add basic functionality

This commit is contained in:
David Rubin
2023-11-03 10:58:56 -07:00
parent 9e7493c4d4
commit 44073e4c98
4 changed files with 256 additions and 6 deletions

View File

@ -2177,7 +2177,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
// If you split it across two then the shell can interpret it
// as two literals.
var buf: [128]u8 = undefined;
const full_data = try std.fmt.bufPrint(&buf, "\x1b{s}{s}", .{if(action==.csi)"["else"", data});
const full_data = try std.fmt.bufPrint(&buf, "\x1b{s}{s}", .{ if (action == .csi) "[" else "", data });
_ = self.io_thread.mailbox.push(try termio.Message.writeReq(
self.alloc,
full_data,
@ -2457,9 +2457,10 @@ pub fn completeClipboardRequest(
self: *Surface,
req: apprt.ClipboardRequest,
data: []const u8,
force: bool, // Dialog has been shown, and ignoring unsafe pastes.
) !void {
switch (req) {
.paste => try self.completeClipboardPaste(data),
.paste => try self.completeClipboardPaste(data, force),
.osc_52 => |kind| try self.completeClipboardReadOSC52(data, kind),
}
}
@ -2485,9 +2486,24 @@ fn startClipboardRequest(
try self.rt_surface.clipboardRequest(loc, req);
}
fn completeClipboardPaste(self: *Surface, data: []const u8) !void {
fn sanatizeClipboardPaste(data: []const u8) !void {
// Split into lines.
var lines = std.mem.splitSequence(u8, data, "\n");
// If there's only one line no need to proceed.
if (std.mem.eql(u8, lines.next().?, data)) return;
// Warning popup.
return error.UnsafePaste;
}
fn completeClipboardPaste(self: *Surface, data: []const u8, force: bool) !void {
if (data.len == 0) return;
if (!force) {
try sanatizeClipboardPaste(data);
}
const bracketed = bracketed: {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();

View File

@ -24,6 +24,7 @@ const build_options = @import("build_options");
const Surface = @import("Surface.zig");
const Window = @import("Window.zig");
const ConfigErrorsWindow = @import("ConfigErrorsWindow.zig");
const UnsafePasteWindow = @import("UnsafePasteWindow.zig");
const c = @import("c.zig");
const inspector = @import("inspector.zig");
const key = @import("key.zig");
@ -47,6 +48,9 @@ menu: ?*c.GMenu = null,
/// The configuration errors window, if it is currently open.
config_errors_window: ?*ConfigErrorsWindow = null,
/// The unsafe paste window, if it is currently open.
unsafe_paste_window: ?*UnsafePasteWindow = null,
/// This is set to false when the main loop should exit.
running: bool = true,

View File

@ -17,6 +17,8 @@ const inspector = @import("inspector.zig");
const gtk_key = @import("key.zig");
const c = @import("c.zig");
const UnsafePasteWindow = @import("UnsafePasteWindow.zig");
const log = std.log.scoped(.gtk);
/// This is detected by the OpenGL renderer to move to a single-threaded
@ -546,9 +548,22 @@ fn gtkClipboardRead(
return;
}
defer c.g_free(cstr);
const str = std.mem.sliceTo(cstr, 0);
self.core_surface.completeClipboardRequest(req.state, str) catch |err| {
self.core_surface.completeClipboardRequest(req.state, str, false) catch |err| {
if (err == error.UnsafePaste) {
// Create a dialog and ask the user if they want to paste anyway.
UnsafePasteWindow.create(
self.app,
str,
self.core_surface,
req.state,
) catch |window_err| {
log.err("failed to create unsafe paste window err={}", .{window_err});
};
return;
}
log.err("failed to complete clipboard request err={}", .{err});
};
}
@ -559,7 +574,6 @@ fn getClipboard(widget: *c.GtkWidget, clipboard: apprt.Clipboard) ?*c.GdkClipboa
.selection => c.gtk_widget_get_primary_clipboard(widget),
};
}
pub fn getCursorPos(self: *const Surface) !apprt.CursorPos {
return self.cursor_pos;
}

View File

@ -0,0 +1,216 @@
/// Configuration errors window.
const UnsafePaste = @This();
const std = @import("std");
const Allocator = std.mem.Allocator;
const CoreSurface = @import("../../Surface.zig");
const ClipboardRequest = @import("../structs.zig").ClipboardRequest;
const App = @import("App.zig");
const View = @import("View.zig");
const c = @import("c.zig");
const log = std.log.scoped(.gtk);
app: *App,
window: *c.GtkWindow,
view: PrimaryView,
data: []u8,
core_surface: CoreSurface,
pending_req: ClipboardRequest,
pub fn create(
app: *App,
data: []const u8,
core_surface: CoreSurface,
request: ClipboardRequest,
) !void {
if (app.unsafe_paste_window != null) return error.WindowAlreadyExists;
const alloc = app.core_app.alloc;
const self = try alloc.create(UnsafePaste);
errdefer alloc.destroy(self);
try self.init(
app,
data,
core_surface,
request,
);
app.unsafe_paste_window = self;
}
/// Not public because this should be called by the GTK lifecycle.
fn destroy(self: *UnsafePaste) void {
const alloc = self.app.core_app.alloc;
self.app.config_errors_window = null;
alloc.destroy(self);
}
fn init(
self: *UnsafePaste,
app: *App,
data: []const u8,
core_surface: CoreSurface,
request: ClipboardRequest,
) !void {
// Create the window
const window = c.gtk_window_new();
const gtk_window: *c.GtkWindow = @ptrCast(window);
errdefer c.gtk_window_destroy(gtk_window);
c.gtk_window_set_title(gtk_window, "Unsafe Paste!");
c.gtk_window_set_default_size(gtk_window, 600, 400);
c.gtk_window_set_resizable(gtk_window, 0);
_ = c.g_signal_connect_data(
window,
"destroy",
c.G_CALLBACK(&gtkDestroy),
self,
null,
c.G_CONNECT_DEFAULT,
);
// Set some state
self.* = .{
.app = app,
.window = gtk_window,
.view = undefined,
.data = try app.core_app.alloc.dupe(u8, data),
.core_surface = core_surface,
.pending_req = request,
};
// Show the window
const view = try PrimaryView.init(self, data);
self.view = view;
c.gtk_window_set_child(@ptrCast(window), view.root);
c.gtk_widget_show(window);
}
fn gtkDestroy(_: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void {
const self = userdataSelf(ud.?);
self.destroy();
}
fn userdataSelf(ud: *anyopaque) *UnsafePaste {
return @ptrCast(@alignCast(ud));
}
const PrimaryView = struct {
root: *c.GtkWidget,
text: *c.GtkTextView,
pub fn init(root: *UnsafePaste, data: []const u8) !PrimaryView {
// All our widgets
const label = c.gtk_label_new(
\\ Pasting this text into the terminal may be dangerous as
\\ unintended commands may be be executed.
);
const buf = unsafeBuffer(data);
defer c.g_object_unref(buf);
const buttons = try ButtonsView.init(root);
const text_scroll = c.gtk_scrolled_window_new();
errdefer c.g_object_unref(text_scroll);
const text = c.gtk_text_view_new_with_buffer(buf);
errdefer c.g_object_unref(text);
c.gtk_scrolled_window_set_child(@ptrCast(text_scroll), text);
// Create our view
const view = try View.init(&.{
.{ .name = "label", .widget = label },
.{ .name = "text", .widget = text_scroll },
.{ .name = "buttons", .widget = buttons.root },
}, &vfl);
errdefer view.deinit();
// We can do additional settings once the layout is setup
c.gtk_label_set_wrap(@ptrCast(label), 1);
c.gtk_text_view_set_editable(@ptrCast(text), 0);
c.gtk_text_view_set_cursor_visible(@ptrCast(text), 0);
c.gtk_text_view_set_top_margin(@ptrCast(text), 8);
c.gtk_text_view_set_bottom_margin(@ptrCast(text), 8);
c.gtk_text_view_set_left_margin(@ptrCast(text), 8);
c.gtk_text_view_set_right_margin(@ptrCast(text), 8);
return .{ .root = view.root, .text = @ptrCast(text) };
}
/// Returns the GtkTextBuffer for the data that was unsafe.
fn unsafeBuffer(data: []const u8) *c.GtkTextBuffer {
const buf = c.gtk_text_buffer_new(null);
errdefer c.g_object_unref(buf);
c.gtk_text_buffer_insert_at_cursor(buf, data.ptr, @intCast(data.len));
return buf;
}
const vfl = [_][*:0]const u8{
"H:|-8-[label]-8-|",
"H:|[text]|",
"H:|[buttons]|",
"V:|[label(<=80)][text(>=100)]-[buttons]-|",
};
};
const ButtonsView = struct {
root: *c.GtkWidget,
pub fn init(root: *UnsafePaste) !ButtonsView {
const cancel_button = c.gtk_button_new_with_label("Cancel");
errdefer c.g_object_unref(cancel_button);
const paste_button = c.gtk_button_new_with_label("Paste");
errdefer c.g_object_unref(paste_button);
// Create our view
const view = try View.init(&.{
.{ .name = "cancel", .widget = cancel_button },
.{ .name = "paste", .widget = paste_button },
}, &vfl);
// Signals
_ = c.g_signal_connect_data(
cancel_button,
"clicked",
c.G_CALLBACK(&gtkCancelClick),
root,
null,
c.G_CONNECT_DEFAULT,
);
_ = c.g_signal_connect_data(
paste_button,
"clicked",
c.G_CALLBACK(&gtkPasteClick),
root,
null,
c.G_CONNECT_DEFAULT,
);
return .{ .root = view.root };
}
fn gtkCancelClick(_: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void {
const self: *UnsafePaste = @ptrCast(@alignCast(ud));
c.gtk_window_destroy(@ptrCast(self.window));
self.app.unsafe_paste_window = null;
}
fn gtkPasteClick(_: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void {
const self: *UnsafePaste = @ptrCast(@alignCast(ud));
// Requeue the paste, this time forcing it.
self.core_surface.completeClipboardRequest(self.pending_req, self.data, true) catch |err| {
std.log.err("Failed to requeue clipboard request: {}", .{err});
};
c.gtk_window_destroy(@ptrCast(self.window));
self.app.unsafe_paste_window = null;
}
const vfl = [_][*:0]const u8{
"H:[cancel]-8-[paste]-8-|",
};
};