inspector: render basic key inspector

This commit is contained in:
Mitchell Hashimoto
2023-10-23 14:46:10 -07:00
parent 534ecb2d80
commit 00ed6069f6
5 changed files with 180 additions and 26 deletions

View File

@ -11,14 +11,21 @@ const CircBuf = @import("circ_buf.zig").CircBuf;
const Surface = @import("Surface.zig");
const input = @import("input.zig");
const terminal = @import("terminal/main.zig");
const inspector = @import("inspector/main.zig");
/// The window names. These are used with docking so we need to have access.
const window_cell = "Cell";
const window_modes = "Modes";
const window_keyboard = "Keyboard";
const window_screen = "Screen";
const window_size = "Surface Info";
const window_imgui_demo = "Dear ImGui Demo";
/// Unique ID system. This is used to generate unique IDs for Dear ImGui
/// widgets. Overflow to reset to 0 is fine. IDs should still be prefixed
/// by type to avoid collisions but its never going to happen.
next_id: usize = 123456789,
/// The surface that we're inspecting.
surface: *Surface,
@ -41,7 +48,7 @@ mouse: struct {
cell: CellInspect = .{ .idle = {} },
/// The list of keyboard events
key_events: CircBuf(KeyEvent, undefined),
key_events: inspector.key.EventRing,
const CellInspect = union(enum) {
/// Idle, no cell inspection is requested
@ -68,23 +75,6 @@ const CellInspect = union(enum) {
}
};
pub const KeyEvent = struct {
/// The input event.
event: input.KeyEvent,
/// The binding that was triggered as a result of this event.
binding: ?input.Binding.Action = null,
/// The data sent to the pty as a result of this keyboard event.
/// This is allocated using the inspector allocator.
pty: []const u8 = "",
pub fn deinit(self: *const KeyEvent, alloc: Allocator) void {
if (self.event.utf8.len > 0) alloc.free(self.event.utf8);
if (self.pty.len > 0) alloc.free(self.pty);
}
};
/// Setup the ImGui state. This requires an ImGui context to be set.
pub fn setup() void {
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
@ -120,7 +110,7 @@ pub fn setup() void {
}
pub fn init(surface: *Surface) !Inspector {
var key_buf = try CircBuf(KeyEvent, undefined).init(surface.alloc, 2);
var key_buf = try inspector.key.EventRing.init(surface.alloc, 2);
errdefer key_buf.deinit(surface.alloc);
return .{
@ -138,15 +128,18 @@ pub fn deinit(self: *Inspector) void {
}
/// Record a keyboard event.
pub fn recordKeyEvent(self: *Inspector, ev: KeyEvent) !void {
const max_capacity = 1024;
pub fn recordKeyEvent(self: *Inspector, ev: inspector.key.Event) !void {
const max_capacity = 50;
self.key_events.append(ev) catch |err| switch (err) {
error.OutOfMemory => if (self.key_events.capacity() < max_capacity) {
// We're out of memory, but we can allocate to our capacity.
const new_capacity = @min(self.key_events.capacity() * 2, max_capacity);
try self.key_events.resize(self.surface.alloc, new_capacity);
try self.key_events.append(ev);
} else return err,
} else {
self.key_events.deleteOldest(1);
try self.key_events.append(ev);
},
else => return err,
};
@ -168,6 +161,7 @@ pub fn render(self: *Inspector) void {
defer self.surface.renderer_state.mutex.unlock();
self.renderScreenWindow();
self.renderModesWindow();
self.renderKeyboardWindow();
self.renderCellWindow();
self.renderSizeWindow();
}
@ -217,6 +211,7 @@ fn setupLayout(self: *Inspector, dock_id_main: cimgui.c.ImGuiID) void {
cimgui.c.igDockBuilderDockWindow(window_cell, dock_id.left);
cimgui.c.igDockBuilderDockWindow(window_modes, dock_id.left);
cimgui.c.igDockBuilderDockWindow(window_keyboard, dock_id.left);
cimgui.c.igDockBuilderDockWindow(window_screen, dock_id.left);
cimgui.c.igDockBuilderDockWindow(window_imgui_demo, dock_id.left);
cimgui.c.igDockBuilderDockWindow(window_size, dock_id.right);
@ -915,3 +910,84 @@ fn renderCellWindow(self: *Inspector) void {
cimgui.c.igTextDisabled("(Any styles not shown are not currently set)");
}
fn renderKeyboardWindow(self: *Inspector) void {
// Start our window. If we're collapsed we do nothing.
defer cimgui.c.igEnd();
if (!cimgui.c.igBegin(
window_keyboard,
null,
cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing,
)) return;
list: {
if (self.key_events.empty()) {
cimgui.c.igText("No recorded key events. Press a key with the " ++
"terminal focused to record it.");
break :list;
}
_ = cimgui.c.igBeginTable(
"table_key_events",
1,
//cimgui.c.ImGuiTableFlags_ScrollY |
cimgui.c.ImGuiTableFlags_RowBg |
cimgui.c.ImGuiTableFlags_Borders,
.{ .x = 0, .y = 0 },
0,
);
defer cimgui.c.igEndTable();
var it = self.key_events.iterator(.reverse);
while (it.next()) |ev| {
// Need to push an ID so that our selectable is unique.
cimgui.c.igPushID_Ptr(ev);
defer cimgui.c.igPopID();
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
_ = cimgui.c.igTableSetColumnIndex(0);
var buf: [1024]u8 = undefined;
const label = ev.label(&buf) catch "Key Event";
_ = cimgui.c.igSelectable_BoolPtr(
label.ptr,
&ev.imgui_state.selected,
cimgui.c.ImGuiSelectableFlags_None,
.{ .x = 0, .y = 0 },
);
if (!ev.imgui_state.selected) continue;
_ = cimgui.c.igBeginTable(
"##event",
2,
cimgui.c.ImGuiTableFlags_None,
.{ .x = 0, .y = 0 },
0,
);
defer cimgui.c.igEndTable();
{
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Action");
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("%s", @tagName(ev.event.action).ptr);
}
{
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Key");
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("%s", @tagName(ev.event.key).ptr);
}
{
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);
_ = cimgui.c.igTableSetColumnIndex(0);
cimgui.c.igText("Physical Key");
_ = cimgui.c.igTableSetColumnIndex(1);
cimgui.c.igText("%s", @tagName(ev.event.physical_key).ptr);
}
}
} // table
}

View File

@ -34,6 +34,7 @@ const configpkg = @import("config.zig");
const input = @import("input.zig");
const App = @import("App.zig");
const internal_os = @import("os/main.zig");
const inspector = @import("inspector/main.zig");
const log = std.log.scoped(.surface);
@ -598,7 +599,7 @@ pub fn activateInspector(self: *Surface) !void {
/// Deactivate the inspector and stop collecting any information.
pub fn deactivateInspector(self: *Surface) void {
const inspector = self.inspector orelse return;
const insp = self.inspector orelse return;
// Remove the inspector from the render state
{
@ -613,8 +614,8 @@ pub fn deactivateInspector(self: *Surface) void {
_ = self.io_thread.mailbox.push(.{ .inspector = false }, .{ .forever = {} });
// Deinit the inspector
inspector.deinit();
self.alloc.destroy(inspector);
insp.deinit();
self.alloc.destroy(insp);
self.inspector = null;
}
@ -1005,7 +1006,7 @@ pub fn keyCallback(
// log.debug("keyCallback event={}", .{event});
// Setup our inspector event if we have an inspector.
var insp_ev: ?Inspector.KeyEvent = if (self.inspector != null) ev: {
var insp_ev: ?inspector.key.Event = if (self.inspector != null) ev: {
var copy = event;
copy.utf8 = "";
if (event.utf8.len > 0) copy.utf8 = try self.alloc.dupe(u8, event.utf8);

71
src/inspector/key.zig Normal file
View File

@ -0,0 +1,71 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const input = @import("../input.zig");
const CircBuf = @import("../circ_buf.zig").CircBuf;
/// Circular buffer of key events.
pub const EventRing = CircBuf(Event, undefined);
/// Represents a recorded keyboard event.
pub const Event = struct {
/// The input event.
event: input.KeyEvent,
/// The binding that was triggered as a result of this event.
binding: ?input.Binding.Action = null,
/// The data sent to the pty as a result of this keyboard event.
/// This is allocated using the inspector allocator.
pty: []const u8 = "",
/// State for the inspector GUI. Do not set this unless you're the inspector.
imgui_state: struct {
selected: bool = false,
} = .{},
pub fn init(alloc: Allocator, event: input.KeyEvent) !Event {
var copy = event;
copy.utf8 = "";
if (event.utf8.len > 0) copy.utf8 = try alloc.dupe(u8, event.utf8);
return .{ .event = copy };
}
pub fn deinit(self: *const Event, alloc: Allocator) void {
if (self.event.utf8.len > 0) alloc.free(self.event.utf8);
if (self.pty.len > 0) alloc.free(self.pty);
}
/// Returns a label that can be used for this event. This is null-terminated
/// so it can be easily used with C APIs.
pub fn label(self: *const Event, buf: []u8) ![:0]const u8 {
var buf_stream = std.io.fixedBufferStream(buf);
const writer = buf_stream.writer();
switch (self.event.action) {
.press => try writer.writeAll("Press: "),
.release => try writer.writeAll("Release: "),
.repeat => try writer.writeAll("Repeat: "),
}
if (self.event.mods.shift) try writer.writeAll("Shift+");
if (self.event.mods.ctrl) try writer.writeAll("Ctrl+");
if (self.event.mods.alt) try writer.writeAll("Alt+");
if (self.event.mods.super) try writer.writeAll("Super+");
try writer.writeAll(@tagName(self.event.key));
// Null-terminator
try writer.writeByte(0);
return buf[0..(buf_stream.getWritten().len - 1) :0];
}
};
test "event string" {
const testing = std.testing;
const alloc = testing.allocator;
var event = try Event.init(alloc, .{ .key = .a });
defer event.deinit(alloc);
var buf: [1024]u8 = undefined;
try testing.expectEqualStrings("Press: a", try event.label(&buf));
}

5
src/inspector/main.zig Normal file
View File

@ -0,0 +1,5 @@
pub const key = @import("key.zig");
test {
@import("std").testing.refAllDecls(@This());
}

View File

@ -295,6 +295,7 @@ test {
// Libraries
_ = @import("segmented_pool.zig");
_ = @import("inspector/main.zig");
_ = @import("terminal/main.zig");
_ = @import("terminfo/main.zig");