diff --git a/src/app.zig b/src/app.zig index 9fa9333..d09f56d 100644 --- a/src/app.zig +++ b/src/app.zig @@ -7,6 +7,7 @@ const mergeTaggedUnions = event.mergeTaggedUnions; const isTaggedUnion = event.isTaggedUnion; const key = @import("key.zig"); +const Mouse = @import("mouse.zig").Mouse; const Key = key.Key; const Size = @import("size.zig").Size; const Queue = @import("queue.zig").Queue; @@ -95,6 +96,7 @@ pub fn App(comptime E: type) type { try terminal.saveScreen(); try terminal.enterAltScreen(); try terminal.hideCursor(); + try terminal.enableMouseSupport(); // send initial size afterwards const size = terminal.getTerminalSize(); @@ -104,6 +106,7 @@ pub fn App(comptime E: type) type { pub fn interrupt(this: *@This()) !void { this.quit_event.set(); + try terminal.disableMouseSupport(); try terminal.exitAltScreen(); try terminal.restoreScreen(); if (this.thread) |thread| { @@ -115,9 +118,10 @@ pub fn App(comptime E: type) type { pub fn stop(this: *@This()) !void { try this.interrupt(); if (this.termios) |*termios| { - try terminal.disableRawMode(termios); + try terminal.disableMouseSupport(); try terminal.showCursor(); try terminal.exitAltScreen(); + try terminal.disableRawMode(termios); try terminal.restoreScreen(); } this.termios = null; @@ -171,6 +175,7 @@ pub fn App(comptime E: type) type { // FIX: I still think that there is a race condition (I'm just waiting 'long' enough) this.quit_event.timedWait(20 * std.time.ns_per_ms) catch { const read_bytes = try terminal.read(buf[0..]); + // TODO: `break` should not terminate the reading of the user inputs, but instead only the received faulty input! // escape key presses if (buf[0] == 0x1b and read_bytes > 1) { switch (buf[1]) { @@ -267,7 +272,47 @@ pub fn App(comptime E: type) type { }, 'I' => this.postEvent(.{ .focus = true }), 'O' => this.postEvent(.{ .focus = false }), - // 'M', 'm' => return parseMouse(sequence), // TODO: parse mouse inputs + 'M', 'm' => { + std.debug.assert(sequence.len >= 4); + if (sequence[2] != '<') break; + + const delim1 = std.mem.indexOfScalarPos(u8, sequence, 3, ';') orelse break; + const button_mask = std.fmt.parseUnsigned(u16, sequence[3..delim1], 10) catch break; + const delim2 = std.mem.indexOfScalarPos(u8, sequence, delim1 + 1, ';') orelse break; + const px = std.fmt.parseUnsigned(u16, sequence[delim1 + 1 .. delim2], 10) catch break; + const py = std.fmt.parseUnsigned(u16, sequence[delim2 + 1 .. sequence.len - 1], 10) catch break; + + const mouse_bits = packed struct { + const motion: u8 = 0b00100000; + const buttons: u8 = 0b11000011; + const shift: u8 = 0b00000100; + const alt: u8 = 0b00001000; + const ctrl: u8 = 0b00010000; + }; + + const button: Mouse.Button = @enumFromInt(button_mask & mouse_bits.buttons); + const motion = button_mask & mouse_bits.motion > 0; + // const shift = button_mask & mouse_bits.shift > 0; + // const alt = button_mask & mouse_bits.alt > 0; + // const ctrl = button_mask & mouse_bits.ctrl > 0; + + const mouse = Mouse{ + .button = button, + .col = px -| 1, + .row = py -| 1, + .kind = blk: { + if (motion and button != Mouse.Button.none) { + break :blk .drag; + } + if (motion and button == Mouse.Button.none) { + break :blk .motion; + } + if (sequence[sequence.len - 1] == 'm') break :blk .release; + break :blk .press; + }, + }; + this.postEvent(.{ .mouse = mouse }); + }, 'c' => { // Primary DA (CSI ? Pm c) }, diff --git a/src/event.zig b/src/event.zig index 2dc20db..c80b314 100644 --- a/src/event.zig +++ b/src/event.zig @@ -3,6 +3,7 @@ const std = @import("std"); const terminal = @import("terminal.zig"); +const Mouse = @import("mouse.zig").Mouse; const Size = @import("size.zig").Size; const Key = @import("key.zig").Key; @@ -23,6 +24,8 @@ pub const SystemEvent = union(enum) { resize: Size, /// Input key event received from the user key: Key, + /// Mouse input event + mouse: Mouse, /// Focus event for mouse interaction /// TODO: this should instead be a union with a `Size` to derive which container / element the focus meant for focus: bool, diff --git a/src/key.zig b/src/key.zig index 033843d..2d85dba 100644 --- a/src/key.zig +++ b/src/key.zig @@ -1,4 +1,6 @@ //! Keybindings and Modifiers for user input detection and selection. + +// TODO: rename this module to 'input' and include the mouse.zig contents as well! const std = @import("std"); pub const Key = packed struct { diff --git a/src/mouse.zig b/src/mouse.zig new file mode 100644 index 0000000..121289e --- /dev/null +++ b/src/mouse.zig @@ -0,0 +1,29 @@ +/// Mouse input detection. +pub const Mouse = packed struct { + col: u16, + row: u16, + button: Button, + kind: Kind, + + pub const Button = enum(u8) { + left, + middle, + right, + none, + wheel_up = 64, + wheel_down = 65, + wheel_right = 66, + wheel_left = 67, + button_8 = 128, + button_9 = 129, + button_10 = 130, + button_11 = 131, + }; + + pub const Kind = enum(u2) { + press, + release, + motion, + drag, + }; +};