diff --git a/changelog.md b/changelog.md index 399854c..c58ae46 100644 --- a/changelog.md +++ b/changelog.md @@ -8,8 +8,9 @@ res/config.ini contains all of the available config options and their default va + border\_fg has been introduced to change the color of the borders. + term\_restore\_cursor\_cmd should restore the cursor to it's usual state. -+ sleep\_key and sleep\_cmd. + log\_path is used to store ly.log and ly.log.old for debugging purposes (pretty much nothing is logged currently). ++ enable\_vi\_mode to enable vi keybindings. ++ sleep\_key and sleep\_cmd. Note: sleep\_cmd is unset by default, meaning it's hidden and has no effect. diff --git a/res/config.ini b/res/config.ini index 1e03cad..9d8fcd6 100644 --- a/res/config.ini +++ b/res/config.ini @@ -16,6 +16,9 @@ asterisk = * # Erase password input on failure clear_password = false +# Enable vi keybindings +vi_mode = false + # The `fg` and `bg` color settings take a digit 0-8 corresponding to: #define TB_DEFAULT 0x00 #define TB_BLACK 0x01 diff --git a/src/animations/Matrix.zig b/src/animations/Matrix.zig index e9eedbe..9c62ee0 100644 --- a/src/animations/Matrix.zig +++ b/src/animations/Matrix.zig @@ -120,7 +120,7 @@ pub fn draw(self: *Matrix) void { // Head's down offscreen if (i > buf_height) { self.dots[buf_width * tail + j].value = ' '; - continue :height_it; + break :height_it; } dot = &self.dots[buf_width * i + j]; } diff --git a/src/auth.zig b/src/auth.zig index 44cdf3d..08dc5c5 100644 --- a/src/auth.zig +++ b/src/auth.zig @@ -355,7 +355,7 @@ fn xauth(display_name: [:0]u8, shell: [*:0]const u8, pw_dir: [*:0]const u8, xaut const pid = std.c.fork(); if (pid == 0) { - var cmd_buffer = std.mem.zeroes([1024]u8); + var cmd_buffer: [1024]u8 = undefined; const cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} add {s} . $({s})", .{ xauth_cmd, display_name, mcookie_cmd }); _ = interop.execl(shell, shell, "-c", cmd_str.ptr, @as([*c]const u8, 0)); std.os.exit(0); @@ -366,7 +366,7 @@ fn xauth(display_name: [:0]u8, shell: [*:0]const u8, pw_dir: [*:0]const u8, xaut } fn executeWaylandCmd(shell: [*:0]const u8, wayland_cmd: []const u8, desktop_cmd: []const u8) !void { - var cmd_buffer = std.mem.zeroes([1024]u8); + var cmd_buffer: [1024]u8 = undefined; const cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} {s}", .{ wayland_cmd, desktop_cmd }); _ = interop.execl(shell, shell, "-c", cmd_str.ptr, @as([*c]const u8, 0)); @@ -380,7 +380,7 @@ fn executeX11Cmd(shell: [*:0]const u8, pw_dir: [*:0]const u8, config: Config, de const pid = std.c.fork(); if (pid == 0) { - var cmd_buffer = std.mem.zeroes([1024]u8); + var cmd_buffer: [1024]u8 = undefined; const cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ config.x_cmd, display_name, vt }); _ = interop.execl(shell, shell, "-c", cmd_str.ptr, @as([*c]const u8, 0)); std.os.exit(0); @@ -405,7 +405,7 @@ fn executeX11Cmd(shell: [*:0]const u8, pw_dir: [*:0]const u8, config: Config, de const xorg_pid = std.c.fork(); if (xorg_pid == 0) { - var cmd_buffer = std.mem.zeroes([1024]u8); + var cmd_buffer: [1024]u8 = undefined; const cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} {s}", .{ config.x_cmd_setup, desktop_cmd }); _ = interop.execl(shell, shell, "-c", cmd_str.ptr, @as([*c]const u8, 0)); std.os.exit(0); diff --git a/src/config/Config.zig b/src/config/Config.zig index 452151f..268c43d 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -43,6 +43,7 @@ sleep_key: []const u8 = "F3", term_reset_cmd: []const u8 = "/usr/bin/tput reset", term_restore_cursor_cmd: []const u8 = "/usr/bin/tput cnorm", tty: u8 = 2, +vi_mode: bool = false, wayland_cmd: []const u8 = build_options.data_directory ++ "/wsetup.sh", waylandsessions: []const u8 = "/usr/share/wayland-sessions", x_cmd: []const u8 = "/usr/bin/X", diff --git a/src/config/Lang.zig b/src/config/Lang.zig index 07795a2..4d3a934 100644 --- a/src/config/Lang.zig +++ b/src/config/Lang.zig @@ -36,8 +36,10 @@ err_user_init: []const u8 = "failed to initialize user", err_user_uid: []const u8 = "failed to set user UID", err_xsessions_dir: []const u8 = "failed to find sessions folder", err_xsessions_open: []const u8 = "failed to open sessions folder", +insert: []const u8 = "Insert", login: []const u8 = "login:", logout: []const u8 = "logged out", +normal: []const u8 = "Normal", numlock: []const u8 = "numlock", password: []const u8 = "password:", restart: []const u8 = "reboot", @@ -45,4 +47,4 @@ shell: []const u8 = "shell", shutdown: []const u8 = "shutdown", sleep: []const u8 = "sleep", wayland: []const u8 = "wayland", -xinitrc: []const u8 = "xinitrc" +xinitrc: []const u8 = "xinitrc", diff --git a/src/enums.zig b/src/enums.zig index 4cfa564..d62673b 100644 --- a/src/enums.zig +++ b/src/enums.zig @@ -16,3 +16,8 @@ pub const Input = enum { login, password, }; + +pub const ViMode = enum { + normal, + insert, +}; diff --git a/src/main.zig b/src/main.zig index bf02dbd..a23ac93 100644 --- a/src/main.zig +++ b/src/main.zig @@ -15,6 +15,7 @@ const ini = @import("config/ini.zig"); const Lang = @import("config/Lang.zig"); const Save = @import("config/Save.zig"); const LogFile = @import("logger/LogFile.zig"); +const ViMode = @import("enums.zig").ViMode; const Ini = ini.Ini; const termbox = interop.termbox; @@ -150,6 +151,7 @@ pub fn main() !void { defer password.deinit(); var active_input = config.default_input; + var vi_mode: ViMode = if (config.vi_mode) .normal else .insert; // Load last saved username and desktop selection, if any if (config.load) { @@ -178,11 +180,11 @@ pub fn main() !void { password.position(coordinates.x, coordinates.y + 6, coordinates.visible_length); switch (active_input) { - .session => desktop.handle(null), - .login => login.handle(null) catch { + .session => desktop.handle(null, vi_mode), + .login => login.handle(null, vi_mode) catch { info_line = lang.err_alloc; }, - .password => password.handle(null) catch { + .password => password.handle(null, vi_mode) catch { info_line = lang.err_alloc; }, } @@ -280,11 +282,11 @@ pub fn main() !void { // If the user entered a wrong password 10 times in a row, play a cascade animation, else update normally if (auth_fails < 10) { switch (active_input) { - .session => desktop.handle(null), - .login => login.handle(null) catch { + .session => desktop.handle(null, vi_mode), + .login => login.handle(null, vi_mode) catch { info_line = lang.err_alloc; }, - .password => password.handle(null) catch { + .password => password.handle(null, vi_mode) catch { info_line = lang.err_alloc; }, } @@ -373,6 +375,11 @@ pub fn main() !void { } } + if (config.vi_mode) { + const label_txt = if (vi_mode == .normal) lang.normal else lang.insert; + buffer.drawLabel(label_txt, buffer.box_x, buffer.box_y - 1); + } + draw_lock_state: { const lock_state = interop.getLockState(allocator, config.console_dev) catch |err| { if (err == error.CannotOpenConsoleDev) { @@ -440,15 +447,22 @@ pub fn main() !void { if (event_error < 0 or event.type != termbox.TB_EVENT_KEY) continue; switch (event.key) { - termbox.TB_KEY_F1, termbox.TB_KEY_F2, termbox.TB_KEY_F3, termbox.TB_KEY_F4, termbox.TB_KEY_F5, termbox.TB_KEY_F6, termbox.TB_KEY_F7, termbox.TB_KEY_F8, termbox.TB_KEY_F9, termbox.TB_KEY_F10, termbox.TB_KEY_F11, termbox.TB_KEY_F12 => { - if (0xFFFF - event.key + 1 == shutdown_key) { + termbox.TB_KEY_ESC => { + if (config.vi_mode and vi_mode != .normal) { + vi_mode = .normal; + update = true; + } + }, + termbox.TB_KEY_F12...termbox.TB_KEY_F1 => { + const pressed_key = 0xFFFF - event.key + 1; + if (pressed_key == shutdown_key) { shutdown = true; run = false; - } else if (0xFFFF - event.key + 1 == restart_key) { + } else if (pressed_key == restart_key) { restart = true; run = false; - } else if (0xFFFF - event.key + 1 == sleep_key and config.sleep_cmd != null) { - const pid = std.c.fork(); + } else if (pressed_key == sleep_key and config.sleep_cmd != null) { + const pid = try std.os.fork(); if (pid == 0) { std.process.execv(allocator, &[_][]const u8{ "/bin/sh", "-c", config.sleep_cmd.? }) catch {}; } @@ -524,12 +538,39 @@ pub fn main() !void { } }, else => { + if (vi_mode == .normal) { + switch (event.ch) { + 'k' => { + active_input = switch (active_input) { + .session, .login => .session, + .password => .login, + }; + update = true; + continue; + }, + 'j' => { + active_input = switch (active_input) { + .session => .login, + .login, .password => .password, + }; + update = true; + continue; + }, + 'i' => { + vi_mode = .insert; + update = true; + continue; + }, + else => {}, + } + } + switch (active_input) { - .session => desktop.handle(&event), - .login => login.handle(&event) catch { + .session => desktop.handle(&event, vi_mode), + .login => login.handle(&event, vi_mode) catch { info_line = lang.err_alloc; }, - .password => password.handle(&event) catch { + .password => password.handle(&event, vi_mode) catch { info_line = lang.err_alloc; }, } diff --git a/src/tui/components/Desktop.zig b/src/tui/components/Desktop.zig index 0290565..2e340de 100644 --- a/src/tui/components/Desktop.zig +++ b/src/tui/components/Desktop.zig @@ -9,6 +9,7 @@ const Allocator = std.mem.Allocator; const EnvironmentList = std.ArrayList(Environment); const DisplayServer = enums.DisplayServer; +const ViMode = enums.ViMode; const termbox = interop.termbox; @@ -115,14 +116,22 @@ pub fn crawl(self: *Desktop, path: []const u8, display_server: DisplayServer) !v } } -pub fn handle(self: *Desktop, maybe_event: ?*termbox.tb_event) void { +pub fn handle(self: *Desktop, maybe_event: ?*termbox.tb_event, vi_mode: ViMode) void { if (maybe_event) |event| blk: { if (event.type != termbox.TB_EVENT_KEY) break :blk; switch (event.key) { termbox.TB_KEY_ARROW_LEFT, termbox.TB_KEY_CTRL_H => self.goLeft(), termbox.TB_KEY_ARROW_RIGHT, termbox.TB_KEY_CTRL_L => self.goRight(), - else => {}, + else => { + if (vi_mode == .normal) { + switch (event.ch) { + 'h' => self.goLeft(), + 'l' => self.goRight(), + else => {}, + } + } + }, } } diff --git a/src/tui/components/Text.zig b/src/tui/components/Text.zig index 304f37b..6cb0221 100644 --- a/src/tui/components/Text.zig +++ b/src/tui/components/Text.zig @@ -2,6 +2,7 @@ const std = @import("std"); const interop = @import("../../interop.zig"); const TerminalBuffer = @import("../TerminalBuffer.zig"); const utils = @import("../utils.zig"); +const ViMode = @import("../../enums.zig").ViMode; const Allocator = std.mem.Allocator; const DynamicString = std.ArrayList(u8); @@ -46,7 +47,7 @@ pub fn position(self: *Text, x: u64, y: u64, visible_length: u64) void { self.visible_length = visible_length; } -pub fn handle(self: *Text, maybe_event: ?*termbox.tb_event) !void { +pub fn handle(self: *Text, maybe_event: ?*termbox.tb_event, vi_mode: ViMode) !void { if (maybe_event) |event| blk: { if (event.type != termbox.TB_EVENT_KEY) break :blk; @@ -54,9 +55,27 @@ pub fn handle(self: *Text, maybe_event: ?*termbox.tb_event) !void { termbox.TB_KEY_ARROW_LEFT => self.goLeft(), termbox.TB_KEY_ARROW_RIGHT => self.goRight(), termbox.TB_KEY_DELETE => self.delete(), - termbox.TB_KEY_BACKSPACE, termbox.TB_KEY_BACKSPACE2 => self.backspace(), + termbox.TB_KEY_BACKSPACE, termbox.TB_KEY_BACKSPACE2 => { + if (vi_mode == .insert) { + self.backspace(); + } else { + self.goLeft(); + } + }, termbox.TB_KEY_SPACE => try self.write(' '), - else => if (event.ch > 31 and event.ch < 127) try self.write(@intCast(event.ch)), + else => { + if (event.ch > 31 and event.ch < 127) { + if (vi_mode == .insert) { + try self.write(@intCast(event.ch)); + } else { + switch (event.ch) { + 'h' => self.goLeft(), + 'l' => self.goRight(), + else => {}, + } + } + } + }, } }