From 291e0d836be33412a5f1c12ae5504cc5bb8624ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=82=B7=E3=83=A5?= <120780645+Kawaii-Ash@users.noreply.github.com> Date: Tue, 2 Jul 2024 08:52:38 +0000 Subject: [PATCH] Various bug fixes and small features (#606) * Fix stray cursor, integer overflows and other bugs * check for getenvlist error, shorten code * fix cascade, set info_line before auth, make code clearer and a bug fix * Add option to turn on numlock at startup * Fix setting numlock * Update build.zig * Custom info text * Shift+Tab for previous input * update changelog and res/config * Some fixes * update build.zig * update build.zig again * Fix xauth command for some shells and fix building in ReleaseSafe * Use git describe to get dev version str * revert change to getLockState (it broke the doom animation) * add new ly error messages. Only try to make path for pam/exe during install when dest_directory is defined + print warning on error. * add warning message for workaround --- build.zig | 287 +- changelog.md | 5 + dep/termbox_next/.gitignore | 7 - dep/termbox_next/license | 19 - dep/termbox_next/readme.md | 57 - dep/termbox_next/src/demo/keyboard.c | 827 ----- dep/termbox_next/src/demo/makefile | 30 - dep/termbox_next/src/demo/output.c | 156 - dep/termbox_next/src/demo/paint.c | 183 -- dep/termbox_next/src/demo/truecolor.c | 69 - dep/termbox_next/src/input.c | 319 -- dep/termbox_next/src/memstream.c | 36 - dep/termbox_next/src/memstream.h | 20 - dep/termbox_next/src/ringbuffer.c | 195 -- dep/termbox_next/src/ringbuffer.h | 26 - dep/termbox_next/src/term.c | 412 --- dep/termbox_next/src/term.h | 38 - dep/termbox_next/src/termbox.c | 885 ----- dep/termbox_next/src/termbox.h | 307 -- dep/termbox_next/src/utf8.c | 106 - dep/termbox_next/tools/astylerc | 27 - dep/termbox_next/tools/collect_terminfo.py | 108 - include/termbox2.h | 3448 ++++++++++++++++++++ res/config.ini | 9 + res/lang/en.ini | 5 + src/animations/Doom.zig | 2 +- src/animations/Matrix.zig | 6 +- src/auth.zig | 104 +- src/bigclock.zig | 30 +- src/config/Config.zig | 5 +- src/config/Lang.zig | 5 + src/interop.zig | 17 +- src/main.zig | 126 +- src/tui/TerminalBuffer.zig | 43 +- src/tui/components/Desktop.zig | 6 +- src/tui/components/InfoLine.zig | 22 + src/tui/components/Text.zig | 11 +- src/tui/utils.zig | 6 +- 38 files changed, 3898 insertions(+), 4066 deletions(-) delete mode 100644 dep/termbox_next/.gitignore delete mode 100644 dep/termbox_next/license delete mode 100644 dep/termbox_next/readme.md delete mode 100644 dep/termbox_next/src/demo/keyboard.c delete mode 100644 dep/termbox_next/src/demo/makefile delete mode 100644 dep/termbox_next/src/demo/output.c delete mode 100644 dep/termbox_next/src/demo/paint.c delete mode 100644 dep/termbox_next/src/demo/truecolor.c delete mode 100644 dep/termbox_next/src/input.c delete mode 100644 dep/termbox_next/src/memstream.c delete mode 100644 dep/termbox_next/src/memstream.h delete mode 100644 dep/termbox_next/src/ringbuffer.c delete mode 100644 dep/termbox_next/src/ringbuffer.h delete mode 100644 dep/termbox_next/src/term.c delete mode 100644 dep/termbox_next/src/term.h delete mode 100644 dep/termbox_next/src/termbox.c delete mode 100644 dep/termbox_next/src/termbox.h delete mode 100644 dep/termbox_next/src/utf8.c delete mode 100644 dep/termbox_next/tools/astylerc delete mode 100755 dep/termbox_next/tools/collect_terminfo.py create mode 100644 include/termbox2.h diff --git a/build.zig b/build.zig index 13c5140c..65c06a26 100644 --- a/build.zig +++ b/build.zig @@ -1,32 +1,26 @@ const std = @import("std"); -const ly_version = std.SemanticVersion{ .major = 1, .minor = 0, .patch = 0, .build = "dev" }; +const ly_version = std.SemanticVersion{ .major = 1, .minor = 0, .patch = 0 }; +var dest_directory: []const u8 = undefined; +var data_directory: []const u8 = undefined; +var exe_name: []const u8 = undefined; -pub fn build(b: *std.Build) void { - const data_directory = b.option([]const u8, "data_directory", "Specify a default data directory (default is /etc/ly)"); +pub fn build(b: *std.Build) !void { + dest_directory = b.option([]const u8, "dest_directory", "Specify a dest directory for installation") orelse ""; + data_directory = b.option([]const u8, "data_directory", "Specify a default data directory (default is /etc/ly)") orelse "/etc/ly"; + data_directory = try std.fs.path.join(b.allocator, &[_][]const u8{ dest_directory, data_directory }); + exe_name = b.option([]const u8, "name", "Specify installed executable file name (default is ly)") orelse "ly"; const build_options = b.addOptions(); - build_options.addOption([]const u8, "data_directory", data_directory orelse "/etc/ly"); - const version_str = b.fmt("{d}.{d}.{d}-{s}", .{ ly_version.major, ly_version.minor, ly_version.patch, ly_version.build.? }); + build_options.addOption([]const u8, "data_directory", data_directory); + + const version_str = try getVersionStr(b, "ly", ly_version); build_options.addOption([]const u8, "version", version_str); const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - const c_args = [_][]const u8{ - "-std=c99", - "-pedantic", - "-g", - "-Wall", - "-Wextra", - "-Werror=vla", - "-Wno-unused-parameter", - "-D_DEFAULT_SOURCE", - "-D_POSIX_C_SOURCE=200809L", - "-D_XOPEN_SOURCE", - }; - const exe = b.addExecutable(.{ .name = "ly", .root_source_file = .{ .path = "src/main.zig" }, @@ -42,18 +36,24 @@ pub fn build(b: *std.Build) void { const clap = b.dependency("clap", .{ .target = target, .optimize = optimize }); exe.root_module.addImport("clap", clap.module("clap")); + exe.addIncludePath(.{ .path = "include" }); exe.linkSystemLibrary("pam"); exe.linkSystemLibrary("xcb"); exe.linkLibC(); - exe.addIncludePath(.{ .path = "dep/termbox_next/src" }); + // HACK: Only fails with ReleaseSafe, so we'll override it. + const translate_c = b.addTranslateC(.{ + .root_source_file = .{ .path = "include/termbox2.h" }, + .target = target, + .optimize = if (optimize == .ReleaseSafe) .ReleaseFast else optimize, + }); + translate_c.defineCMacroRaw("TB_IMPL"); + const termbox2 = translate_c.addModule("termbox2"); + exe.root_module.addImport("termbox2", termbox2); - exe.addCSourceFile(.{ .file = .{ .path = "dep/termbox_next/src/input.c" }, .flags = &c_args }); - exe.addCSourceFile(.{ .file = .{ .path = "dep/termbox_next/src/memstream.c" }, .flags = &c_args }); - exe.addCSourceFile(.{ .file = .{ .path = "dep/termbox_next/src/ringbuffer.c" }, .flags = &c_args }); - exe.addCSourceFile(.{ .file = .{ .path = "dep/termbox_next/src/term.c" }, .flags = &c_args }); - exe.addCSourceFile(.{ .file = .{ .path = "dep/termbox_next/src/termbox.c" }, .flags = &c_args }); - exe.addCSourceFile(.{ .file = .{ .path = "dep/termbox_next/src/utf8.c" }, .flags = &c_args }); + if (optimize == .ReleaseSafe) { + std.debug.print("warn: termbox2 module is being built in ReleaseFast due to a bug.\n", .{}); + } b.installArtifact(exe); @@ -67,120 +67,108 @@ pub fn build(b: *std.Build) void { run_step.dependOn(&run_cmd.step); const installexe_step = b.step("installexe", "Install Ly"); - installexe_step.makeFn = installexe; + installexe_step.makeFn = ExeInstaller(true).make; installexe_step.dependOn(b.getInstallStep()); const installnoconf_step = b.step("installnoconf", "Install Ly without its configuration file"); - installnoconf_step.makeFn = installnoconf; + installnoconf_step.makeFn = ExeInstaller(false).make; installnoconf_step.dependOn(b.getInstallStep()); const installsystemd_step = b.step("installsystemd", "Install the Ly systemd service"); - installsystemd_step.makeFn = installsystemd; + installsystemd_step.makeFn = ServiceInstaller(.Systemd).make; installsystemd_step.dependOn(installexe_step); const installopenrc_step = b.step("installopenrc", "Install the Ly openrc service"); - installopenrc_step.makeFn = installopenrc; + installopenrc_step.makeFn = ServiceInstaller(.Openrc).make; installopenrc_step.dependOn(installexe_step); const installrunit_step = b.step("installrunit", "Install the Ly runit service"); - installrunit_step.makeFn = installrunit; + installrunit_step.makeFn = ServiceInstaller(.Runit).make; installrunit_step.dependOn(installexe_step); const uninstallall_step = b.step("uninstallall", "Uninstall Ly and all services"); uninstallall_step.makeFn = uninstallall; } -fn installexe(self: *std.Build.Step, progress: *std.Progress.Node) !void { - _ = progress; - _ = self; - - try install_ly(true); -} - -fn installnoconf(self: *std.Build.Step, progress: *std.Progress.Node) !void { - _ = progress; - _ = self; - - try install_ly(false); -} - -fn installsystemd(self: *std.Build.Step, progress: *std.Progress.Node) !void { - _ = progress; - _ = self; - - var service_dir = std.fs.openDirAbsolute("/usr/lib/systemd/system", .{}) catch unreachable; - defer service_dir.close(); - - try std.fs.cwd().copyFile("res/ly.service", service_dir, "ly.service", .{ .override_mode = 644 }); -} - -fn installopenrc(self: *std.Build.Step, progress: *std.Progress.Node) !void { - _ = progress; - _ = self; - - var service_dir = std.fs.openDirAbsolute("/etc/init.d", .{}) catch unreachable; - defer service_dir.close(); - - try std.fs.cwd().copyFile("res/ly-openrc", service_dir, "ly", .{ .override_mode = 755 }); -} - -fn installrunit(self: *std.Build.Step, progress: *std.Progress.Node) !void { - _ = progress; - _ = self; - - var service_dir = std.fs.openDirAbsolute("/etc/sv", .{}) catch unreachable; - defer service_dir.close(); - - std.fs.makeDirAbsolute("/etc/sv/ly") catch { - std.debug.print("warn: /etc/sv/ly already exists as a directory.\n", .{}); +pub fn ExeInstaller(install_conf: bool) type { + return struct { + pub fn make(step: *std.Build.Step, progress: *std.Progress.Node) !void { + _ = progress; + try install_ly(step.owner.allocator, install_conf); + } }; - - var ly_service_dir = std.fs.openDirAbsolute("/etc/sv/ly", .{}) catch unreachable; - defer ly_service_dir.close(); - - try std.fs.cwd().copyFile("res/ly-runit-service/conf", ly_service_dir, "conf", .{}); - try std.fs.cwd().copyFile("res/ly-runit-service/finish", ly_service_dir, "finish", .{}); - try std.fs.cwd().copyFile("res/ly-runit-service/run", ly_service_dir, "run", .{}); } -fn uninstallall(self: *std.Build.Step, progress: *std.Progress.Node) !void { - _ = progress; - _ = self; - - try std.fs.deleteTreeAbsolute("/etc/ly"); - try std.fs.deleteFileAbsolute("/usr/bin/ly"); - try std.fs.deleteFileAbsolute("/etc/pam.d/ly"); - std.fs.deleteFileAbsolute("/usr/lib/systemd/system/ly.service") catch { - std.debug.print("warn: systemd service not found.\n", .{}); - }; - std.fs.deleteFileAbsolute("/etc/init.d/ly") catch { - std.debug.print("warn: openrc service not found.\n", .{}); - }; - std.fs.deleteTreeAbsolute("/etc/sv/ly") catch { - std.debug.print("warn: runit service not found.\n", .{}); +const InitSystem = enum { + Systemd, + Openrc, + Runit, +}; +pub fn ServiceInstaller(comptime init_system: InitSystem) type { + return struct { + pub fn make(step: *std.Build.Step, progress: *std.Progress.Node) !void { + _ = progress; + const allocator = step.owner.allocator; + switch (init_system) { + .Openrc => { + const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/etc/init.d" }); + std.fs.cwd().makePath(service_path) catch {}; + var service_dir = std.fs.openDirAbsolute(service_path, .{}) catch unreachable; + defer service_dir.close(); + + try std.fs.cwd().copyFile("res/ly-openrc", service_dir, exe_name, .{ .override_mode = 755 }); + }, + .Runit => { + const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/etc/sv/ly" }); + std.fs.cwd().makePath(service_path) catch {}; + var service_dir = std.fs.openDirAbsolute(service_path, .{}) catch unreachable; + defer service_dir.close(); + + try std.fs.cwd().copyFile("res/ly-runit-service/conf", service_dir, "conf", .{}); + try std.fs.cwd().copyFile("res/ly-runit-service/finish", service_dir, "finish", .{}); + try std.fs.cwd().copyFile("res/ly-runit-service/run", service_dir, "run", .{}); + }, + .Systemd => { + const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/usr/lib/systemd/system" }); + std.fs.cwd().makePath(service_path) catch {}; + var service_dir = std.fs.openDirAbsolute(service_path, .{}) catch unreachable; + defer service_dir.close(); + + try std.fs.cwd().copyFile("res/ly.service", service_dir, "ly.service", .{ .override_mode = 644 }); + }, + } + } }; } -fn install_ly(install_config: bool) !void { - std.fs.makeDirAbsolute("/etc/ly") catch { - std.debug.print("warn: /etc/ly already exists as a directory.\n", .{}); +fn install_ly(allocator: std.mem.Allocator, install_config: bool) !void { + std.fs.cwd().makePath(data_directory) catch { + std.debug.print("warn: {s} already exists as a directory.\n", .{data_directory}); }; - std.fs.makeDirAbsolute("/etc/ly/lang") catch { - std.debug.print("warn: /etc/ly/lang already exists as a directory.\n", .{}); + const lang_path = try std.fs.path.join(allocator, &[_][]const u8{ data_directory, "/lang" }); + std.fs.cwd().makePath(lang_path) catch { + std.debug.print("warn: {s} already exists as a directory.\n", .{data_directory}); }; var current_dir = std.fs.cwd(); { - var executable_dir = std.fs.openDirAbsolute("/usr/bin", .{}) catch unreachable; + const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/usr/bin" }); + if (!std.mem.eql(u8, dest_directory, "")) { + std.fs.cwd().makePath(exe_path) catch { + std.debug.print("warn: {s} already exists as a directory.\n", .{exe_path}); + }; + } + + var executable_dir = std.fs.openDirAbsolute(exe_path, .{}) catch unreachable; defer executable_dir.close(); - try current_dir.copyFile("zig-out/bin/ly", executable_dir, "ly", .{}); + try current_dir.copyFile("zig-out/bin/ly", executable_dir, exe_name, .{}); } { - var config_dir = std.fs.openDirAbsolute("/etc/ly", .{}) catch unreachable; + var config_dir = std.fs.openDirAbsolute(data_directory, .{}) catch unreachable; defer config_dir.close(); if (install_config) { @@ -191,7 +179,7 @@ fn install_ly(install_config: bool) !void { } { - var lang_dir = std.fs.openDirAbsolute("/etc/ly/lang", .{}) catch unreachable; + var lang_dir = std.fs.openDirAbsolute(lang_path, .{}) catch unreachable; defer lang_dir.close(); try current_dir.copyFile("res/lang/cat.ini", lang_dir, "cat.ini", .{}); @@ -213,9 +201,98 @@ fn install_ly(install_config: bool) !void { } { - var pam_dir = std.fs.openDirAbsolute("/etc/pam.d", .{}) catch unreachable; + const pam_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/etc/pam.d" }); + if (!std.mem.eql(u8, dest_directory, "")) { + std.fs.cwd().makePath(pam_path) catch { + std.debug.print("warn: {s} already exists as a directory.\n", .{pam_path}); + }; + } + + var pam_dir = std.fs.openDirAbsolute(pam_path, .{}) catch unreachable; defer pam_dir.close(); try current_dir.copyFile("res/pam.d/ly", pam_dir, "ly", .{ .override_mode = 644 }); } } + +pub fn uninstallall(step: *std.Build.Step, progress: *std.Progress.Node) !void { + _ = progress; + try std.fs.deleteTreeAbsolute(data_directory); + const allocator = step.owner.allocator; + + const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/usr/bin/", exe_name }); + try std.fs.deleteFileAbsolute(exe_path); + + const pam_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/etc/pam.d/ly" }); + try std.fs.deleteFileAbsolute(pam_path); + + const systemd_service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/usr/lib/systemd/system/ly.service" }); + std.fs.deleteFileAbsolute(systemd_service_path) catch { + std.debug.print("warn: systemd service not found.\n", .{}); + }; + + const openrc_service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/etc/init.d/ly" }); + std.fs.deleteFileAbsolute(openrc_service_path) catch { + std.debug.print("warn: openrc service not found.\n", .{}); + }; + + const runit_service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/etc/sv/ly" }); + std.fs.deleteTreeAbsolute(runit_service_path) catch { + std.debug.print("warn: runit service not found.\n", .{}); + }; +} + +fn getVersionStr(b: *std.Build, name: []const u8, version: std.SemanticVersion) ![]const u8 { + const version_str = b.fmt("{d}.{d}.{d}", .{ version.major, version.minor, version.patch }); + + var status: u8 = undefined; + const git_describe_raw = b.runAllowFail(&[_][]const u8{ + "git", + "-C", + b.build_root.path orelse ".", + "describe", + "--match", + "*.*.*", + "--tags", + }, &status, .Ignore) catch { + return version_str; + }; + var git_describe = std.mem.trim(u8, git_describe_raw, " \n\r"); + git_describe = std.mem.trimLeft(u8, git_describe, "v"); + + switch (std.mem.count(u8, git_describe, "-")) { + 0 => { + if (!std.mem.eql(u8, version_str, git_describe)) { + std.debug.print("{s} version '{s}' does not match git tag: '{s}'\n", .{ name, version_str, git_describe }); + std.process.exit(1); + } + return version_str; + }, + 2 => { + // Untagged development build (e.g. 0.10.0-dev.2025+ecf0050a9). + var it = std.mem.splitScalar(u8, git_describe, '-'); + const tagged_ancestor = std.mem.trimLeft(u8, it.first(), "v"); + const commit_height = it.next().?; + const commit_id = it.next().?; + + const ancestor_ver = try std.SemanticVersion.parse(tagged_ancestor); + if (version.order(ancestor_ver) != .gt) { + std.debug.print("{s} version '{}' must be greater than tagged ancestor '{}'\n", .{ name, version, ancestor_ver }); + std.process.exit(1); + } + + // Check that the commit hash is prefixed with a 'g' (a Git convention). + if (commit_id.len < 1 or commit_id[0] != 'g') { + std.debug.print("Unexpected `git describe` output: {s}\n", .{git_describe}); + return version_str; + } + + // The version is reformatted in accordance with the https://semver.org specification. + return b.fmt("{s}-dev.{s}+{s}", .{ version_str, commit_height, commit_id[1..] }); + }, + else => { + std.debug.print("Unexpected `git describe` output: {s}\n", .{git_describe}); + return version_str; + }, + } +} diff --git a/changelog.md b/changelog.md index 2a1be490..b7abd617 100644 --- a/changelog.md +++ b/changelog.md @@ -10,6 +10,9 @@ res/config.ini contains all of the available config options and their default va + `term_restore_cursor_cmd` should restore the cursor to it's usual state. + `vi_mode` to enable vi keybindings. + `sleep_key` and `sleep_cmd`. ++ `numlock` to set numlock on startup. ++ `initial_info_text` allows changing the initial text on the info line. ++ `box_title` to display a title at the top of the main box Note: `sleep_cmd` is unset by default, meaning it's hidden and has no effect. @@ -45,3 +48,5 @@ session_index = 0 + Non .desktop files are now ignored in sessions directory. + PAM auth is now done in a child process. (Fixes some issues with logging out and back in). + When ly receives SIGTERM, the terminal is now cleared and existing child processes are cleaned up. ++ Shift+Tab now focuses previous input. ++ Display text in the info line when authenticating. diff --git a/dep/termbox_next/.gitignore b/dep/termbox_next/.gitignore deleted file mode 100644 index afcccdee..00000000 --- a/dep/termbox_next/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -bin -obj -src/demo/*.o -src/demo/keyboard -src/demo/output -src/demo/paint -src/demo/truecolor diff --git a/dep/termbox_next/license b/dep/termbox_next/license deleted file mode 100644 index e9bb4eac..00000000 --- a/dep/termbox_next/license +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (C) 2010-2013 nsf - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/dep/termbox_next/readme.md b/dep/termbox_next/readme.md deleted file mode 100644 index d20e68df..00000000 --- a/dep/termbox_next/readme.md +++ /dev/null @@ -1,57 +0,0 @@ -# Termbox -[Termbox](https://github.com/nsf/termbox) -was a promising Text User Interface (TUI) library. -Unfortunately, its original author -[changed his mind](https://github.com/nsf/termbox/issues/37#issuecomment-261075481) -about consoles and despite the -[community's efforts](https://github.com/nsf/termbox/pull/104#issuecomment-300308156) -to keep the library's development going, preferred to let it die. Before it happened, -[some people](https://wiki.musl-libc.org/alternatives.html) -already noticed the robustness of the initial architecture -[became compromised](https://github.com/nsf/termbox/commit/66c3f91b14e24510319bce6b5cc2fecf8cf5abff#commitcomment-3790714) -in a nonsensical refactoring frenzy. Now, the author refuses to merge features -like true-color support, invoking some -[spurious correlations](https://github.com/nsf/termbox/pull/104#issuecomment-300292223) -we will discuss no further. - -## The new Termbox-next -This fork was made to restore the codebase to its original quality (before -[66c3f91](https://github.com/nsf/termbox/commit/66c3f91b14e24510319bce6b5cc2fecf8cf5abff)) -while providing all the functionnalities of the current implementation. -This was achieved by branching at -[a2e217f](https://github.com/nsf/termbox/commit/a2e217f0fb97e6bbd589136ea3945f9d5a123d81) -and cherry-picking all the commits up to -[d63b83a](https://github.com/nsf/termbox/commit/d63b83af04e0fd6da836bb8f37e5cec72a1dc95a) -if they weren't harmful. - -## Changes -A lot of things changed during the process: - - *waf*, the original build system, was completely removed from the - project and replaced by make. - - anything related to python was removed as well - -## Getting started -Termbox's interface only consists of 12 functions: -``` -tb_init() // initialization -tb_shutdown() // shutdown - -tb_width() // width of the terminal screen -tb_height() // height of the terminal screen - -tb_clear() // clear buffer -tb_present() // sync internal buffer with terminal - -tb_put_cell() -tb_change_cell() -tb_blit() // drawing functions - -tb_select_input_mode() // change input mode -tb_peek_event() // peek a keyboard event -tb_poll_event() // wait for a keyboard event -``` -See src/termbox.h header file for full detail. - -## TL;DR -`make` to build a static version of the lib under bin/termbox.a -`cd src/demo && make` to build the example programs in src/demo/ diff --git a/dep/termbox_next/src/demo/keyboard.c b/dep/termbox_next/src/demo/keyboard.c deleted file mode 100644 index 3c25c5aa..00000000 --- a/dep/termbox_next/src/demo/keyboard.c +++ /dev/null @@ -1,827 +0,0 @@ -#include -#include -#include -#include -#include "termbox.h" - -struct key -{ - unsigned char x; - unsigned char y; - uint32_t ch; -}; - -#define STOP {0,0,0} -struct key K_ESC[] = {{1, 1, 'E'}, {2, 1, 'S'}, {3, 1, 'C'}, STOP}; -struct key K_F1[] = {{6, 1, 'F'}, {7, 1, '1'}, STOP}; -struct key K_F2[] = {{9, 1, 'F'}, {10, 1, '2'}, STOP}; -struct key K_F3[] = {{12, 1, 'F'}, {13, 1, '3'}, STOP}; -struct key K_F4[] = {{15, 1, 'F'}, {16, 1, '4'}, STOP}; -struct key K_F5[] = {{19, 1, 'F'}, {20, 1, '5'}, STOP}; -struct key K_F6[] = {{22, 1, 'F'}, {23, 1, '6'}, STOP}; -struct key K_F7[] = {{25, 1, 'F'}, {26, 1, '7'}, STOP}; -struct key K_F8[] = {{28, 1, 'F'}, {29, 1, '8'}, STOP}; -struct key K_F9[] = {{33, 1, 'F'}, {34, 1, '9'}, STOP}; -struct key K_F10[] = {{36, 1, 'F'}, {37, 1, '1'}, {38, 1, '0'}, STOP}; -struct key K_F11[] = {{40, 1, 'F'}, {41, 1, '1'}, {42, 1, '1'}, STOP}; -struct key K_F12[] = {{44, 1, 'F'}, {45, 1, '1'}, {46, 1, '2'}, STOP}; -struct key K_PRN[] = {{50, 1, 'P'}, {51, 1, 'R'}, {52, 1, 'N'}, STOP}; -struct key K_SCR[] = {{54, 1, 'S'}, {55, 1, 'C'}, {56, 1, 'R'}, STOP}; -struct key K_BRK[] = {{58, 1, 'B'}, {59, 1, 'R'}, {60, 1, 'K'}, STOP}; -struct key K_LED1[] = {{66, 1, '-'}, STOP}; -struct key K_LED2[] = {{70, 1, '-'}, STOP}; -struct key K_LED3[] = {{74, 1, '-'}, STOP}; - -struct key K_TILDE[] = {{1, 4, '`'}, STOP}; -struct key K_TILDE_SHIFT[] = {{1, 4, '~'}, STOP}; -struct key K_1[] = {{4, 4, '1'}, STOP}; -struct key K_1_SHIFT[] = {{4, 4, '!'}, STOP}; -struct key K_2[] = {{7, 4, '2'}, STOP}; -struct key K_2_SHIFT[] = {{7, 4, '@'}, STOP}; -struct key K_3[] = {{10, 4, '3'}, STOP}; -struct key K_3_SHIFT[] = {{10, 4, '#'}, STOP}; -struct key K_4[] = {{13, 4, '4'}, STOP}; -struct key K_4_SHIFT[] = {{13, 4, '$'}, STOP}; -struct key K_5[] = {{16, 4, '5'}, STOP}; -struct key K_5_SHIFT[] = {{16, 4, '%'}, STOP}; -struct key K_6[] = {{19, 4, '6'}, STOP}; -struct key K_6_SHIFT[] = {{19, 4, '^'}, STOP}; -struct key K_7[] = {{22, 4, '7'}, STOP}; -struct key K_7_SHIFT[] = {{22, 4, '&'}, STOP}; -struct key K_8[] = {{25, 4, '8'}, STOP}; -struct key K_8_SHIFT[] = {{25, 4, '*'}, STOP}; -struct key K_9[] = {{28, 4, '9'}, STOP}; -struct key K_9_SHIFT[] = {{28, 4, '('}, STOP}; -struct key K_0[] = {{31, 4, '0'}, STOP}; -struct key K_0_SHIFT[] = {{31, 4, ')'}, STOP}; -struct key K_MINUS[] = {{34, 4, '-'}, STOP}; -struct key K_MINUS_SHIFT[] = {{34, 4, '_'}, STOP}; -struct key K_EQUALS[] = {{37, 4, '='}, STOP}; -struct key K_EQUALS_SHIFT[] = {{37, 4, '+'}, STOP}; -struct key K_BACKSLASH[] = {{40, 4, '\\'}, STOP}; -struct key K_BACKSLASH_SHIFT[] = {{40, 4, '|'}, STOP}; -struct key K_BACKSPACE[] = {{44, 4, 0x2190}, {45, 4, 0x2500}, {46, 4, 0x2500}, STOP}; -struct key K_INS[] = {{50, 4, 'I'}, {51, 4, 'N'}, {52, 4, 'S'}, STOP}; -struct key K_HOM[] = {{54, 4, 'H'}, {55, 4, 'O'}, {56, 4, 'M'}, STOP}; -struct key K_PGU[] = {{58, 4, 'P'}, {59, 4, 'G'}, {60, 4, 'U'}, STOP}; -struct key K_K_NUMLOCK[] = {{65, 4, 'N'}, STOP}; -struct key K_K_SLASH[] = {{68, 4, '/'}, STOP}; -struct key K_K_STAR[] = {{71, 4, '*'}, STOP}; -struct key K_K_MINUS[] = {{74, 4, '-'}, STOP}; - -struct key K_TAB[] = {{1, 6, 'T'}, {2, 6, 'A'}, {3, 6, 'B'}, STOP}; -struct key K_q[] = {{6, 6, 'q'}, STOP}; -struct key K_Q[] = {{6, 6, 'Q'}, STOP}; -struct key K_w[] = {{9, 6, 'w'}, STOP}; -struct key K_W[] = {{9, 6, 'W'}, STOP}; -struct key K_e[] = {{12, 6, 'e'}, STOP}; -struct key K_E[] = {{12, 6, 'E'}, STOP}; -struct key K_r[] = {{15, 6, 'r'}, STOP}; -struct key K_R[] = {{15, 6, 'R'}, STOP}; -struct key K_t[] = {{18, 6, 't'}, STOP}; -struct key K_T[] = {{18, 6, 'T'}, STOP}; -struct key K_y[] = {{21, 6, 'y'}, STOP}; -struct key K_Y[] = {{21, 6, 'Y'}, STOP}; -struct key K_u[] = {{24, 6, 'u'}, STOP}; -struct key K_U[] = {{24, 6, 'U'}, STOP}; -struct key K_i[] = {{27, 6, 'i'}, STOP}; -struct key K_I[] = {{27, 6, 'I'}, STOP}; -struct key K_o[] = {{30, 6, 'o'}, STOP}; -struct key K_O[] = {{30, 6, 'O'}, STOP}; -struct key K_p[] = {{33, 6, 'p'}, STOP}; -struct key K_P[] = {{33, 6, 'P'}, STOP}; -struct key K_LSQB[] = {{36, 6, '['}, STOP}; -struct key K_LCUB[] = {{36, 6, '{'}, STOP}; -struct key K_RSQB[] = {{39, 6, ']'}, STOP}; -struct key K_RCUB[] = {{39, 6, '}'}, STOP}; -struct key K_ENTER[] = -{ - {43, 6, 0x2591}, {44, 6, 0x2591}, {45, 6, 0x2591}, {46, 6, 0x2591}, - {43, 7, 0x2591}, {44, 7, 0x2591}, {45, 7, 0x21B5}, {46, 7, 0x2591}, - {41, 8, 0x2591}, {42, 8, 0x2591}, {43, 8, 0x2591}, {44, 8, 0x2591}, - {45, 8, 0x2591}, {46, 8, 0x2591}, STOP -}; -struct key K_DEL[] = {{50, 6, 'D'}, {51, 6, 'E'}, {52, 6, 'L'}, STOP}; -struct key K_END[] = {{54, 6, 'E'}, {55, 6, 'N'}, {56, 6, 'D'}, STOP}; -struct key K_PGD[] = {{58, 6, 'P'}, {59, 6, 'G'}, {60, 6, 'D'}, STOP}; -struct key K_K_7[] = {{65, 6, '7'}, STOP}; -struct key K_K_8[] = {{68, 6, '8'}, STOP}; -struct key K_K_9[] = {{71, 6, '9'}, STOP}; -struct key K_K_PLUS[] = {{74, 6, ' '}, {74, 7, '+'}, {74, 8, ' '}, STOP}; - -struct key K_CAPS[] = {{1, 8, 'C'}, {2, 8, 'A'}, {3, 8, 'P'}, {4, 8, 'S'}, STOP}; -struct key K_a[] = {{7, 8, 'a'}, STOP}; -struct key K_A[] = {{7, 8, 'A'}, STOP}; -struct key K_s[] = {{10, 8, 's'}, STOP}; -struct key K_S[] = {{10, 8, 'S'}, STOP}; -struct key K_d[] = {{13, 8, 'd'}, STOP}; -struct key K_D[] = {{13, 8, 'D'}, STOP}; -struct key K_f[] = {{16, 8, 'f'}, STOP}; -struct key K_F[] = {{16, 8, 'F'}, STOP}; -struct key K_g[] = {{19, 8, 'g'}, STOP}; -struct key K_G[] = {{19, 8, 'G'}, STOP}; -struct key K_h[] = {{22, 8, 'h'}, STOP}; -struct key K_H[] = {{22, 8, 'H'}, STOP}; -struct key K_j[] = {{25, 8, 'j'}, STOP}; -struct key K_J[] = {{25, 8, 'J'}, STOP}; -struct key K_k[] = {{28, 8, 'k'}, STOP}; -struct key K_K[] = {{28, 8, 'K'}, STOP}; -struct key K_l[] = {{31, 8, 'l'}, STOP}; -struct key K_L[] = {{31, 8, 'L'}, STOP}; -struct key K_SEMICOLON[] = {{34, 8, ';'}, STOP}; -struct key K_PARENTHESIS[] = {{34, 8, ':'}, STOP}; -struct key K_QUOTE[] = {{37, 8, '\''}, STOP}; -struct key K_DOUBLEQUOTE[] = {{37, 8, '"'}, STOP}; -struct key K_K_4[] = {{65, 8, '4'}, STOP}; -struct key K_K_5[] = {{68, 8, '5'}, STOP}; -struct key K_K_6[] = {{71, 8, '6'}, STOP}; - -struct key K_LSHIFT[] = {{1, 10, 'S'}, {2, 10, 'H'}, {3, 10, 'I'}, {4, 10, 'F'}, {5, 10, 'T'}, STOP}; -struct key K_z[] = {{9, 10, 'z'}, STOP}; -struct key K_Z[] = {{9, 10, 'Z'}, STOP}; -struct key K_x[] = {{12, 10, 'x'}, STOP}; -struct key K_X[] = {{12, 10, 'X'}, STOP}; -struct key K_c[] = {{15, 10, 'c'}, STOP}; -struct key K_C[] = {{15, 10, 'C'}, STOP}; -struct key K_v[] = {{18, 10, 'v'}, STOP}; -struct key K_V[] = {{18, 10, 'V'}, STOP}; -struct key K_b[] = {{21, 10, 'b'}, STOP}; -struct key K_B[] = {{21, 10, 'B'}, STOP}; -struct key K_n[] = {{24, 10, 'n'}, STOP}; -struct key K_N[] = {{24, 10, 'N'}, STOP}; -struct key K_m[] = {{27, 10, 'm'}, STOP}; -struct key K_M[] = {{27, 10, 'M'}, STOP}; -struct key K_COMMA[] = {{30, 10, ','}, STOP}; -struct key K_LANB[] = {{30, 10, '<'}, STOP}; -struct key K_PERIOD[] = {{33, 10, '.'}, STOP}; -struct key K_RANB[] = {{33, 10, '>'}, STOP}; -struct key K_SLASH[] = {{36, 10, '/'}, STOP}; -struct key K_QUESTION[] = {{36, 10, '?'}, STOP}; -struct key K_RSHIFT[] = {{42, 10, 'S'}, {43, 10, 'H'}, {44, 10, 'I'}, {45, 10, 'F'}, {46, 10, 'T'}, STOP}; -struct key K_ARROW_UP[] = {{54, 10, '('}, {55, 10, 0x2191}, {56, 10, ')'}, STOP}; -struct key K_K_1[] = {{65, 10, '1'}, STOP}; -struct key K_K_2[] = {{68, 10, '2'}, STOP}; -struct key K_K_3[] = {{71, 10, '3'}, STOP}; -struct key K_K_ENTER[] = {{74, 10, 0x2591}, {74, 11, 0x2591}, {74, 12, 0x2591}, STOP}; - -struct key K_LCTRL[] = {{1, 12, 'C'}, {2, 12, 'T'}, {3, 12, 'R'}, {4, 12, 'L'}, STOP}; -struct key K_LWIN[] = {{6, 12, 'W'}, {7, 12, 'I'}, {8, 12, 'N'}, STOP}; -struct key K_LALT[] = {{10, 12, 'A'}, {11, 12, 'L'}, {12, 12, 'T'}, STOP}; -struct key K_SPACE[] = -{ - {14, 12, ' '}, {15, 12, ' '}, {16, 12, ' '}, {17, 12, ' '}, {18, 12, ' '}, - {19, 12, 'S'}, {20, 12, 'P'}, {21, 12, 'A'}, {22, 12, 'C'}, {23, 12, 'E'}, - {24, 12, ' '}, {25, 12, ' '}, {26, 12, ' '}, {27, 12, ' '}, {28, 12, ' '}, - STOP -}; -struct key K_RALT[] = {{30, 12, 'A'}, {31, 12, 'L'}, {32, 12, 'T'}, STOP}; -struct key K_RWIN[] = {{34, 12, 'W'}, {35, 12, 'I'}, {36, 12, 'N'}, STOP}; -struct key K_RPROP[] = {{38, 12, 'P'}, {39, 12, 'R'}, {40, 12, 'O'}, {41, 12, 'P'}, STOP}; -struct key K_RCTRL[] = {{43, 12, 'C'}, {44, 12, 'T'}, {45, 12, 'R'}, {46, 12, 'L'}, STOP}; -struct key K_ARROW_LEFT[] = {{50, 12, '('}, {51, 12, 0x2190}, {52, 12, ')'}, STOP}; -struct key K_ARROW_DOWN[] = {{54, 12, '('}, {55, 12, 0x2193}, {56, 12, ')'}, STOP}; -struct key K_ARROW_RIGHT[] = {{58, 12, '('}, {59, 12, 0x2192}, {60, 12, ')'}, STOP}; -struct key K_K_0[] = {{65, 12, ' '}, {66, 12, '0'}, {67, 12, ' '}, {68, 12, ' '}, STOP}; -struct key K_K_PERIOD[] = {{71, 12, '.'}, STOP}; - -struct combo -{ - struct key* keys[6]; -}; - -struct combo combos[] = -{ - {{K_TILDE, K_2, K_LCTRL, K_RCTRL, 0}}, - {{K_A, K_LCTRL, K_RCTRL, 0}}, - {{K_B, K_LCTRL, K_RCTRL, 0}}, - {{K_C, K_LCTRL, K_RCTRL, 0}}, - {{K_D, K_LCTRL, K_RCTRL, 0}}, - {{K_E, K_LCTRL, K_RCTRL, 0}}, - {{K_F, K_LCTRL, K_RCTRL, 0}}, - {{K_G, K_LCTRL, K_RCTRL, 0}}, - {{K_H, K_BACKSPACE, K_LCTRL, K_RCTRL, 0}}, - {{K_I, K_TAB, K_LCTRL, K_RCTRL, 0}}, - {{K_J, K_LCTRL, K_RCTRL, 0}}, - {{K_K, K_LCTRL, K_RCTRL, 0}}, - {{K_L, K_LCTRL, K_RCTRL, 0}}, - {{K_M, K_ENTER, K_K_ENTER, K_LCTRL, K_RCTRL, 0}}, - {{K_N, K_LCTRL, K_RCTRL, 0}}, - {{K_O, K_LCTRL, K_RCTRL, 0}}, - {{K_P, K_LCTRL, K_RCTRL, 0}}, - {{K_Q, K_LCTRL, K_RCTRL, 0}}, - {{K_R, K_LCTRL, K_RCTRL, 0}}, - {{K_S, K_LCTRL, K_RCTRL, 0}}, - {{K_T, K_LCTRL, K_RCTRL, 0}}, - {{K_U, K_LCTRL, K_RCTRL, 0}}, - {{K_V, K_LCTRL, K_RCTRL, 0}}, - {{K_W, K_LCTRL, K_RCTRL, 0}}, - {{K_X, K_LCTRL, K_RCTRL, 0}}, - {{K_Y, K_LCTRL, K_RCTRL, 0}}, - {{K_Z, K_LCTRL, K_RCTRL, 0}}, - {{K_LSQB, K_ESC, K_3, K_LCTRL, K_RCTRL, 0}}, - {{K_4, K_BACKSLASH, K_LCTRL, K_RCTRL, 0}}, - {{K_RSQB, K_5, K_LCTRL, K_RCTRL, 0}}, - {{K_6, K_LCTRL, K_RCTRL, 0}}, - {{K_7, K_SLASH, K_MINUS_SHIFT, K_LCTRL, K_RCTRL, 0}}, - {{K_SPACE, 0}}, - {{K_1_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, - {{K_DOUBLEQUOTE, K_LSHIFT, K_RSHIFT, 0}}, - {{K_3_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, - {{K_4_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, - {{K_5_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, - {{K_7_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, - {{K_QUOTE, 0}}, - {{K_9_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, - {{K_0_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, - {{K_8_SHIFT, K_K_STAR, K_LSHIFT, K_RSHIFT, 0}}, - {{K_EQUALS_SHIFT, K_K_PLUS, K_LSHIFT, K_RSHIFT, 0}}, - {{K_COMMA, 0}}, - {{K_MINUS, K_K_MINUS, 0}}, - {{K_PERIOD, K_K_PERIOD, 0}}, - {{K_SLASH, K_K_SLASH, 0}}, - {{K_0, K_K_0, 0}}, - {{K_1, K_K_1, 0}}, - {{K_2, K_K_2, 0}}, - {{K_3, K_K_3, 0}}, - {{K_4, K_K_4, 0}}, - {{K_5, K_K_5, 0}}, - {{K_6, K_K_6, 0}}, - {{K_7, K_K_7, 0}}, - {{K_8, K_K_8, 0}}, - {{K_9, K_K_9, 0}}, - {{K_PARENTHESIS, K_LSHIFT, K_RSHIFT, 0}}, - {{K_SEMICOLON, 0}}, - {{K_LANB, K_LSHIFT, K_RSHIFT, 0}}, - {{K_EQUALS, 0}}, - {{K_RANB, K_LSHIFT, K_RSHIFT, 0}}, - {{K_QUESTION, K_LSHIFT, K_RSHIFT, 0}}, - {{K_2_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, - {{K_A, K_LSHIFT, K_RSHIFT, 0}}, - {{K_B, K_LSHIFT, K_RSHIFT, 0}}, - {{K_C, K_LSHIFT, K_RSHIFT, 0}}, - {{K_D, K_LSHIFT, K_RSHIFT, 0}}, - {{K_E, K_LSHIFT, K_RSHIFT, 0}}, - {{K_F, K_LSHIFT, K_RSHIFT, 0}}, - {{K_G, K_LSHIFT, K_RSHIFT, 0}}, - {{K_H, K_LSHIFT, K_RSHIFT, 0}}, - {{K_I, K_LSHIFT, K_RSHIFT, 0}}, - {{K_J, K_LSHIFT, K_RSHIFT, 0}}, - {{K_K, K_LSHIFT, K_RSHIFT, 0}}, - {{K_L, K_LSHIFT, K_RSHIFT, 0}}, - {{K_M, K_LSHIFT, K_RSHIFT, 0}}, - {{K_N, K_LSHIFT, K_RSHIFT, 0}}, - {{K_O, K_LSHIFT, K_RSHIFT, 0}}, - {{K_P, K_LSHIFT, K_RSHIFT, 0}}, - {{K_Q, K_LSHIFT, K_RSHIFT, 0}}, - {{K_R, K_LSHIFT, K_RSHIFT, 0}}, - {{K_S, K_LSHIFT, K_RSHIFT, 0}}, - {{K_T, K_LSHIFT, K_RSHIFT, 0}}, - {{K_U, K_LSHIFT, K_RSHIFT, 0}}, - {{K_V, K_LSHIFT, K_RSHIFT, 0}}, - {{K_W, K_LSHIFT, K_RSHIFT, 0}}, - {{K_X, K_LSHIFT, K_RSHIFT, 0}}, - {{K_Y, K_LSHIFT, K_RSHIFT, 0}}, - {{K_Z, K_LSHIFT, K_RSHIFT, 0}}, - {{K_LSQB, 0}}, - {{K_BACKSLASH, 0}}, - {{K_RSQB, 0}}, - {{K_6_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, - {{K_MINUS_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, - {{K_TILDE, 0}}, - {{K_a, 0}}, - {{K_b, 0}}, - {{K_c, 0}}, - {{K_d, 0}}, - {{K_e, 0}}, - {{K_f, 0}}, - {{K_g, 0}}, - {{K_h, 0}}, - {{K_i, 0}}, - {{K_j, 0}}, - {{K_k, 0}}, - {{K_l, 0}}, - {{K_m, 0}}, - {{K_n, 0}}, - {{K_o, 0}}, - {{K_p, 0}}, - {{K_q, 0}}, - {{K_r, 0}}, - {{K_s, 0}}, - {{K_t, 0}}, - {{K_u, 0}}, - {{K_v, 0}}, - {{K_w, 0}}, - {{K_x, 0}}, - {{K_y, 0}}, - {{K_z, 0}}, - {{K_LCUB, K_LSHIFT, K_RSHIFT, 0}}, - {{K_BACKSLASH_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, - {{K_RCUB, K_LSHIFT, K_RSHIFT, 0}}, - {{K_TILDE_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, - {{K_8, K_BACKSPACE, K_LCTRL, K_RCTRL, 0}} -}; - -struct combo func_combos[] = -{ - {{K_F1, 0}}, - {{K_F2, 0}}, - {{K_F3, 0}}, - {{K_F4, 0}}, - {{K_F5, 0}}, - {{K_F6, 0}}, - {{K_F7, 0}}, - {{K_F8, 0}}, - {{K_F9, 0}}, - {{K_F10, 0}}, - {{K_F11, 0}}, - {{K_F12, 0}}, - {{K_INS, 0}}, - {{K_DEL, 0}}, - {{K_HOM, 0}}, - {{K_END, 0}}, - {{K_PGU, 0}}, - {{K_PGD, 0}}, - {{K_ARROW_UP, 0}}, - {{K_ARROW_DOWN, 0}}, - {{K_ARROW_LEFT, 0}}, - {{K_ARROW_RIGHT, 0}} -}; - -void print_tb(const char* str, int x, int y, uint32_t fg, uint32_t bg) -{ - while (*str) - { - uint32_t uni; - str += utf8_char_to_unicode(&uni, str); - tb_change_cell(x, y, uni, fg, bg); - x++; - } -} - -void printf_tb(int x, int y, uint32_t fg, uint32_t bg, const char* fmt, ...) -{ - char buf[4096]; - va_list vl; - va_start(vl, fmt); - vsnprintf(buf, sizeof(buf), fmt, vl); - va_end(vl); - print_tb(buf, x, y, fg, bg); -} - -void draw_key(struct key* k, uint32_t fg, uint32_t bg) -{ - while (k->x) - { - tb_change_cell(k->x + 2, k->y + 4, k->ch, fg, bg); - k++; - } -} - -void draw_keyboard() -{ - int i; - tb_change_cell(0, 0, 0x250C, TB_WHITE, TB_DEFAULT); - tb_change_cell(79, 0, 0x2510, TB_WHITE, TB_DEFAULT); - tb_change_cell(0, 23, 0x2514, TB_WHITE, TB_DEFAULT); - tb_change_cell(79, 23, 0x2518, TB_WHITE, TB_DEFAULT); - - for (i = 1; i < 79; ++i) - { - tb_change_cell(i, 0, 0x2500, TB_WHITE, TB_DEFAULT); - tb_change_cell(i, 23, 0x2500, TB_WHITE, TB_DEFAULT); - tb_change_cell(i, 17, 0x2500, TB_WHITE, TB_DEFAULT); - tb_change_cell(i, 4, 0x2500, TB_WHITE, TB_DEFAULT); - } - - for (i = 1; i < 23; ++i) - { - tb_change_cell(0, i, 0x2502, TB_WHITE, TB_DEFAULT); - tb_change_cell(79, i, 0x2502, TB_WHITE, TB_DEFAULT); - } - - tb_change_cell(0, 17, 0x251C, TB_WHITE, TB_DEFAULT); - tb_change_cell(79, 17, 0x2524, TB_WHITE, TB_DEFAULT); - tb_change_cell(0, 4, 0x251C, TB_WHITE, TB_DEFAULT); - tb_change_cell(79, 4, 0x2524, TB_WHITE, TB_DEFAULT); - - for (i = 5; i < 17; ++i) - { - tb_change_cell(1, i, 0x2588, TB_YELLOW, TB_YELLOW); - tb_change_cell(78, i, 0x2588, TB_YELLOW, TB_YELLOW); - } - - draw_key(K_ESC, TB_WHITE, TB_BLUE); - draw_key(K_F1, TB_WHITE, TB_BLUE); - draw_key(K_F2, TB_WHITE, TB_BLUE); - draw_key(K_F3, TB_WHITE, TB_BLUE); - draw_key(K_F4, TB_WHITE, TB_BLUE); - draw_key(K_F5, TB_WHITE, TB_BLUE); - draw_key(K_F6, TB_WHITE, TB_BLUE); - draw_key(K_F7, TB_WHITE, TB_BLUE); - draw_key(K_F8, TB_WHITE, TB_BLUE); - draw_key(K_F9, TB_WHITE, TB_BLUE); - draw_key(K_F10, TB_WHITE, TB_BLUE); - draw_key(K_F11, TB_WHITE, TB_BLUE); - draw_key(K_F12, TB_WHITE, TB_BLUE); - draw_key(K_PRN, TB_WHITE, TB_BLUE); - draw_key(K_SCR, TB_WHITE, TB_BLUE); - draw_key(K_BRK, TB_WHITE, TB_BLUE); - draw_key(K_LED1, TB_WHITE, TB_BLUE); - draw_key(K_LED2, TB_WHITE, TB_BLUE); - draw_key(K_LED3, TB_WHITE, TB_BLUE); - - draw_key(K_TILDE, TB_WHITE, TB_BLUE); - draw_key(K_1, TB_WHITE, TB_BLUE); - draw_key(K_2, TB_WHITE, TB_BLUE); - draw_key(K_3, TB_WHITE, TB_BLUE); - draw_key(K_4, TB_WHITE, TB_BLUE); - draw_key(K_5, TB_WHITE, TB_BLUE); - draw_key(K_6, TB_WHITE, TB_BLUE); - draw_key(K_7, TB_WHITE, TB_BLUE); - draw_key(K_8, TB_WHITE, TB_BLUE); - draw_key(K_9, TB_WHITE, TB_BLUE); - draw_key(K_0, TB_WHITE, TB_BLUE); - draw_key(K_MINUS, TB_WHITE, TB_BLUE); - draw_key(K_EQUALS, TB_WHITE, TB_BLUE); - draw_key(K_BACKSLASH, TB_WHITE, TB_BLUE); - draw_key(K_BACKSPACE, TB_WHITE, TB_BLUE); - draw_key(K_INS, TB_WHITE, TB_BLUE); - draw_key(K_HOM, TB_WHITE, TB_BLUE); - draw_key(K_PGU, TB_WHITE, TB_BLUE); - draw_key(K_K_NUMLOCK, TB_WHITE, TB_BLUE); - draw_key(K_K_SLASH, TB_WHITE, TB_BLUE); - draw_key(K_K_STAR, TB_WHITE, TB_BLUE); - draw_key(K_K_MINUS, TB_WHITE, TB_BLUE); - - draw_key(K_TAB, TB_WHITE, TB_BLUE); - draw_key(K_q, TB_WHITE, TB_BLUE); - draw_key(K_w, TB_WHITE, TB_BLUE); - draw_key(K_e, TB_WHITE, TB_BLUE); - draw_key(K_r, TB_WHITE, TB_BLUE); - draw_key(K_t, TB_WHITE, TB_BLUE); - draw_key(K_y, TB_WHITE, TB_BLUE); - draw_key(K_u, TB_WHITE, TB_BLUE); - draw_key(K_i, TB_WHITE, TB_BLUE); - draw_key(K_o, TB_WHITE, TB_BLUE); - draw_key(K_p, TB_WHITE, TB_BLUE); - draw_key(K_LSQB, TB_WHITE, TB_BLUE); - draw_key(K_RSQB, TB_WHITE, TB_BLUE); - draw_key(K_ENTER, TB_WHITE, TB_BLUE); - draw_key(K_DEL, TB_WHITE, TB_BLUE); - draw_key(K_END, TB_WHITE, TB_BLUE); - draw_key(K_PGD, TB_WHITE, TB_BLUE); - draw_key(K_K_7, TB_WHITE, TB_BLUE); - draw_key(K_K_8, TB_WHITE, TB_BLUE); - draw_key(K_K_9, TB_WHITE, TB_BLUE); - draw_key(K_K_PLUS, TB_WHITE, TB_BLUE); - - draw_key(K_CAPS, TB_WHITE, TB_BLUE); - draw_key(K_a, TB_WHITE, TB_BLUE); - draw_key(K_s, TB_WHITE, TB_BLUE); - draw_key(K_d, TB_WHITE, TB_BLUE); - draw_key(K_f, TB_WHITE, TB_BLUE); - draw_key(K_g, TB_WHITE, TB_BLUE); - draw_key(K_h, TB_WHITE, TB_BLUE); - draw_key(K_j, TB_WHITE, TB_BLUE); - draw_key(K_k, TB_WHITE, TB_BLUE); - draw_key(K_l, TB_WHITE, TB_BLUE); - draw_key(K_SEMICOLON, TB_WHITE, TB_BLUE); - draw_key(K_QUOTE, TB_WHITE, TB_BLUE); - draw_key(K_K_4, TB_WHITE, TB_BLUE); - draw_key(K_K_5, TB_WHITE, TB_BLUE); - draw_key(K_K_6, TB_WHITE, TB_BLUE); - - draw_key(K_LSHIFT, TB_WHITE, TB_BLUE); - draw_key(K_z, TB_WHITE, TB_BLUE); - draw_key(K_x, TB_WHITE, TB_BLUE); - draw_key(K_c, TB_WHITE, TB_BLUE); - draw_key(K_v, TB_WHITE, TB_BLUE); - draw_key(K_b, TB_WHITE, TB_BLUE); - draw_key(K_n, TB_WHITE, TB_BLUE); - draw_key(K_m, TB_WHITE, TB_BLUE); - draw_key(K_COMMA, TB_WHITE, TB_BLUE); - draw_key(K_PERIOD, TB_WHITE, TB_BLUE); - draw_key(K_SLASH, TB_WHITE, TB_BLUE); - draw_key(K_RSHIFT, TB_WHITE, TB_BLUE); - draw_key(K_ARROW_UP, TB_WHITE, TB_BLUE); - draw_key(K_K_1, TB_WHITE, TB_BLUE); - draw_key(K_K_2, TB_WHITE, TB_BLUE); - draw_key(K_K_3, TB_WHITE, TB_BLUE); - draw_key(K_K_ENTER, TB_WHITE, TB_BLUE); - - draw_key(K_LCTRL, TB_WHITE, TB_BLUE); - draw_key(K_LWIN, TB_WHITE, TB_BLUE); - draw_key(K_LALT, TB_WHITE, TB_BLUE); - draw_key(K_SPACE, TB_WHITE, TB_BLUE); - draw_key(K_RCTRL, TB_WHITE, TB_BLUE); - draw_key(K_RPROP, TB_WHITE, TB_BLUE); - draw_key(K_RWIN, TB_WHITE, TB_BLUE); - draw_key(K_RALT, TB_WHITE, TB_BLUE); - draw_key(K_ARROW_LEFT, TB_WHITE, TB_BLUE); - draw_key(K_ARROW_DOWN, TB_WHITE, TB_BLUE); - draw_key(K_ARROW_RIGHT, TB_WHITE, TB_BLUE); - draw_key(K_K_0, TB_WHITE, TB_BLUE); - draw_key(K_K_PERIOD, TB_WHITE, TB_BLUE); - - printf_tb(33, 1, TB_MAGENTA | TB_BOLD, TB_DEFAULT, "Keyboard demo!"); - printf_tb(21, 2, TB_MAGENTA, TB_DEFAULT, - "(press CTRL+X and then CTRL+Q to exit)"); - printf_tb(15, 3, TB_MAGENTA, TB_DEFAULT, - "(press CTRL+X and then CTRL+C to change input mode)"); - - int inputmode = tb_select_input_mode(0); - char inputmode_str[64]; - - if (inputmode & TB_INPUT_ESC) - { - sprintf(inputmode_str, "TB_INPUT_ESC"); - } - - if (inputmode & TB_INPUT_ALT) - { - sprintf(inputmode_str, "TB_INPUT_ALT"); - } - - if (inputmode & TB_INPUT_MOUSE) - { - sprintf(inputmode_str + 12, " | TB_INPUT_MOUSE"); - } - - printf_tb(3, 18, TB_WHITE, TB_DEFAULT, "Input mode: %s", inputmode_str); -} - -const char* funckeymap(int k) -{ - static const char* fcmap[] = - { - "CTRL+2, CTRL+~", - "CTRL+A", - "CTRL+B", - "CTRL+C", - "CTRL+D", - "CTRL+E", - "CTRL+F", - "CTRL+G", - "CTRL+H, BACKSPACE", - "CTRL+I, TAB", - "CTRL+J", - "CTRL+K", - "CTRL+L", - "CTRL+M, ENTER", - "CTRL+N", - "CTRL+O", - "CTRL+P", - "CTRL+Q", - "CTRL+R", - "CTRL+S", - "CTRL+T", - "CTRL+U", - "CTRL+V", - "CTRL+W", - "CTRL+X", - "CTRL+Y", - "CTRL+Z", - "CTRL+3, ESC, CTRL+[", - "CTRL+4, CTRL+\\", - "CTRL+5, CTRL+]", - "CTRL+6", - "CTRL+7, CTRL+/, CTRL+_", - "SPACE" - }; - static const char* fkmap[] = - { - "F1", - "F2", - "F3", - "F4", - "F5", - "F6", - "F7", - "F8", - "F9", - "F10", - "F11", - "F12", - "INSERT", - "DELETE", - "HOME", - "END", - "PGUP", - "PGDN", - "ARROW UP", - "ARROW DOWN", - "ARROW LEFT", - "ARROW RIGHT" - }; - - if (k == TB_KEY_CTRL_8) - { - return "CTRL+8, BACKSPACE 2"; // 0x7F - } - else if (k >= TB_KEY_ARROW_RIGHT && k <= 0xFFFF) - { - return fkmap[0xFFFF - k]; - } - else if (k <= TB_KEY_SPACE) - { - return fcmap[k]; - } - - return "UNKNOWN"; -} - -void pretty_print_press(struct tb_event* ev) -{ - char buf[7]; - buf[utf8_unicode_to_char(buf, ev->ch)] = '\0'; - printf_tb(3, 19, TB_WHITE, TB_DEFAULT, "Key: "); - printf_tb(8, 19, TB_YELLOW, TB_DEFAULT, "decimal: %d", ev->key); - printf_tb(8, 20, TB_GREEN, TB_DEFAULT, "hex: 0x%X", ev->key); - printf_tb(8, 21, TB_CYAN, TB_DEFAULT, "octal: 0%o", ev->key); - printf_tb(8, 22, TB_RED, TB_DEFAULT, "string: %s", funckeymap(ev->key)); - - printf_tb(54, 19, TB_WHITE, TB_DEFAULT, "Char: "); - printf_tb(60, 19, TB_YELLOW, TB_DEFAULT, "decimal: %d", ev->ch); - printf_tb(60, 20, TB_GREEN, TB_DEFAULT, "hex: 0x%X", ev->ch); - printf_tb(60, 21, TB_CYAN, TB_DEFAULT, "octal: 0%o", ev->ch); - printf_tb(60, 22, TB_RED, TB_DEFAULT, "string: %s", buf); - - printf_tb(54, 18, TB_WHITE, TB_DEFAULT, "Modifier: %s", - (ev->mod) ? "TB_MOD_ALT" : "none"); - -} - -void pretty_print_resize(struct tb_event* ev) -{ - printf_tb(3, 19, TB_WHITE, TB_DEFAULT, "Resize event: %d x %d", ev->w, ev->h); -} - -int counter = 0; - -void pretty_print_mouse(struct tb_event* ev) -{ - printf_tb(3, 19, TB_WHITE, TB_DEFAULT, "Mouse event: %d x %d", ev->x, ev->y); - char* btn = ""; - - switch (ev->key) - { - case TB_KEY_MOUSE_LEFT: - btn = "MouseLeft: %d"; - break; - - case TB_KEY_MOUSE_MIDDLE: - btn = "MouseMiddle: %d"; - break; - - case TB_KEY_MOUSE_RIGHT: - btn = "MouseRight: %d"; - break; - - case TB_KEY_MOUSE_WHEEL_UP: - btn = "MouseWheelUp: %d"; - break; - - case TB_KEY_MOUSE_WHEEL_DOWN: - btn = "MouseWheelDown: %d"; - break; - - case TB_KEY_MOUSE_RELEASE: - btn = "MouseRelease: %d"; - } - - counter++; - printf_tb(43, 19, TB_WHITE, TB_DEFAULT, "Key: "); - printf_tb(48, 19, TB_YELLOW, TB_DEFAULT, btn, counter); -} - -void dispatch_press(struct tb_event* ev) -{ - if (ev->mod & TB_MOD_ALT) - { - draw_key(K_LALT, TB_WHITE, TB_RED); - draw_key(K_RALT, TB_WHITE, TB_RED); - } - - struct combo* k = 0; - - if (ev->key >= TB_KEY_ARROW_RIGHT) - { - k = &func_combos[0xFFFF - ev->key]; - } - else if (ev->ch < 128) - { - if (ev->ch == 0 && ev->key < 128) - { - k = &combos[ev->key]; - } - else - { - k = &combos[ev->ch]; - } - } - - if (!k) - { - return; - } - - struct key** keys = k->keys; - - while (*keys) - { - draw_key(*keys, TB_WHITE, TB_RED); - keys++; - } -} - -int main(int argc, char** argv) -{ - (void) argc; - (void) argv; - int ret; - - ret = tb_init(); - - if (ret) - { - fprintf(stderr, "tb_init() failed with error code %d\n", ret); - return 1; - } - - tb_select_input_mode(TB_INPUT_ESC | TB_INPUT_MOUSE); - struct tb_event ev; - - tb_clear(); - draw_keyboard(); - tb_present(); - int inputmode = 0; - int ctrlxpressed = 0; - - while (tb_poll_event(&ev)) - { - switch (ev.type) - { - case TB_EVENT_KEY: - if (ev.key == TB_KEY_CTRL_Q && ctrlxpressed) - { - tb_shutdown(); - return 0; - } - - if (ev.key == TB_KEY_CTRL_C && ctrlxpressed) - { - static int chmap[] = - { - TB_INPUT_ESC | TB_INPUT_MOUSE, // 101 - TB_INPUT_ALT | TB_INPUT_MOUSE, // 110 - TB_INPUT_ESC, // 001 - TB_INPUT_ALT, // 010 - }; - inputmode++; - - if (inputmode >= 4) - { - inputmode = 0; - } - - tb_select_input_mode(chmap[inputmode]); - } - - if (ev.key == TB_KEY_CTRL_X) - { - ctrlxpressed = 1; - } - else - { - ctrlxpressed = 0; - } - - tb_clear(); - draw_keyboard(); - dispatch_press(&ev); - pretty_print_press(&ev); - tb_present(); - break; - - case TB_EVENT_RESIZE: - tb_clear(); - draw_keyboard(); - pretty_print_resize(&ev); - tb_present(); - break; - - case TB_EVENT_MOUSE: - tb_clear(); - draw_keyboard(); - pretty_print_mouse(&ev); - tb_present(); - break; - - default: - break; - } - } - - tb_shutdown(); - return 0; -} diff --git a/dep/termbox_next/src/demo/makefile b/dep/termbox_next/src/demo/makefile deleted file mode 100644 index 74ec8ddc..00000000 --- a/dep/termbox_next/src/demo/makefile +++ /dev/null @@ -1,30 +0,0 @@ -CC=gcc -FLAGS=-std=c99 -pedantic -Wall -Werror -g -static -INCL=-I../ -BIND=../../bin - -%.o:%.c - @echo "building source object $@" - @$(CC) $(INCL) $(FLAGS) -c -o $@ $< - -all:keyboard output paint truecolor - -keyboard:keyboard.o - @echo "compiling $@" - @$(CC) $(INCL) $(FLAGS) -o $@ $@.o $(BIND)/termbox.a - -output:output.o - @echo "compiling $@" - @$(CC) $(INCL) $(FLAGS) -o $@ $@.o $(BIND)/termbox.a - -paint:paint.o - @echo "compiling $@" - @$(CC) $(INCL) $(FLAGS) -o $@ $@.o $(BIND)/termbox.a - -truecolor:truecolor.o - @echo "compiling $@" - @$(CC) $(INCL) $(FLAGS) -o $@ $@.o $(BIND)/termbox.a - -clean: - @echo "cleaning workspace" - @rm -rf *.o keyboard output paint truecolor diff --git a/dep/termbox_next/src/demo/output.c b/dep/termbox_next/src/demo/output.c deleted file mode 100644 index 447975ec..00000000 --- a/dep/termbox_next/src/demo/output.c +++ /dev/null @@ -1,156 +0,0 @@ -#include -#include -#include "../termbox.h" - -static const char chars[] = "nnnnnnnnnbbbbbbbbbuuuuuuuuuBBBBBBBBB"; - -static const uint32_t all_attrs[] = -{ - 0, - TB_BOLD, - TB_UNDERLINE, - TB_BOLD | TB_UNDERLINE, -}; - -static int next_char(int current) -{ - current++; - - if (!chars[current]) - { - current = 0; - } - - return current; -} - -static void draw_line(int x, int y, uint32_t bg) -{ - int a, c; - int current_char = 0; - - for (a = 0; a < 4; a++) - { - for (c = TB_DEFAULT; c <= TB_WHITE; c++) - { - uint32_t fg = all_attrs[a] | c; - tb_change_cell(x, y, chars[current_char], fg, bg); - current_char = next_char(current_char); - x++; - } - } -} - -static void print_combinations_table(int sx, int sy, const uint32_t* attrs, - int attrs_n) -{ - int i, c; - - for (i = 0; i < attrs_n; i++) - { - for (c = TB_DEFAULT; c <= TB_WHITE; c++) - { - uint32_t bg = attrs[i] | c; - draw_line(sx, sy, bg); - sy++; - } - } -} - -static void draw_all() -{ - tb_clear(); - - tb_select_output_mode(TB_OUTPUT_NORMAL); - static const uint32_t col1[] = {0, TB_BOLD}; - static const uint32_t col2[] = {TB_REVERSE}; - print_combinations_table(1, 1, col1, 2); - print_combinations_table(2 + strlen(chars), 1, col2, 1); - tb_present(); - - tb_select_output_mode(TB_OUTPUT_GRAYSCALE); - int c, x, y; - - for (x = 0, y = 23; x < 24; ++x) - { - tb_change_cell(x, y, '@', x, 0); - tb_change_cell(x + 25, y, ' ', 0, x); - } - - tb_present(); - - tb_select_output_mode(TB_OUTPUT_216); - y++; - - for (c = 0, x = 0; c < 216; ++c, ++x) - { - if (!(x % 24)) - { - x = 0; - ++y; - } - - tb_change_cell(x, y, '@', c, 0); - tb_change_cell(x + 25, y, ' ', 0, c); - } - - tb_present(); - - tb_select_output_mode(TB_OUTPUT_256); - y++; - - for (c = 0, x = 0; c < 256; ++c, ++x) - { - if (!(x % 24)) - { - x = 0; - ++y; - } - - tb_change_cell(x, y, '+', c | ((y & 1) ? TB_UNDERLINE : 0), 0); - tb_change_cell(x + 25, y, ' ', 0, c); - } - - tb_present(); -} - -int main(int argc, char** argv) -{ - (void)argc; - (void)argv; - int ret = tb_init(); - - if (ret) - { - fprintf(stderr, "tb_init() failed with error code %d\n", ret); - return 1; - } - - draw_all(); - - struct tb_event ev; - - while (tb_poll_event(&ev)) - { - switch (ev.type) - { - case TB_EVENT_KEY: - switch (ev.key) - { - case TB_KEY_ESC: - goto done; - break; - } - - break; - - case TB_EVENT_RESIZE: - draw_all(); - break; - } - } - -done: - tb_shutdown(); - return 0; -} diff --git a/dep/termbox_next/src/demo/paint.c b/dep/termbox_next/src/demo/paint.c deleted file mode 100644 index 1f68c2dd..00000000 --- a/dep/termbox_next/src/demo/paint.c +++ /dev/null @@ -1,183 +0,0 @@ -#include "../termbox.h" -#include -#include -#include - -static int curCol = 0; -static int curRune = 0; -static struct tb_cell* backbuf; -static int bbw = 0, bbh = 0; - -static const uint32_t runes[] = -{ - 0x20, // ' ' - 0x2591, // '░' - 0x2592, // '▒' - 0x2593, // '▓' - 0x2588, // '█' -}; - -#define len(a) (sizeof(a)/sizeof(a[0])) - -static const uint32_t colors[] = -{ - TB_BLACK, - TB_RED, - TB_GREEN, - TB_YELLOW, - TB_BLUE, - TB_MAGENTA, - TB_CYAN, - TB_WHITE, -}; - -void updateAndDrawButtons(int* current, int x, int y, int mx, int my, int n, - void (*attrFunc)(int, uint32_t*, uint32_t*, uint32_t*)) -{ - int lx = x; - int ly = y; - - for (int i = 0; i < n; i++) - { - if (lx <= mx && mx <= lx + 3 && ly <= my && my <= ly + 1) - { - *current = i; - } - - uint32_t r; - uint32_t fg, bg; - (*attrFunc)(i, &r, &fg, &bg); - tb_change_cell(lx + 0, ly + 0, r, fg, bg); - tb_change_cell(lx + 1, ly + 0, r, fg, bg); - tb_change_cell(lx + 2, ly + 0, r, fg, bg); - tb_change_cell(lx + 3, ly + 0, r, fg, bg); - tb_change_cell(lx + 0, ly + 1, r, fg, bg); - tb_change_cell(lx + 1, ly + 1, r, fg, bg); - tb_change_cell(lx + 2, ly + 1, r, fg, bg); - tb_change_cell(lx + 3, ly + 1, r, fg, bg); - lx += 4; - } - - lx = x; - ly = y; - - for (int i = 0; i < n; i++) - { - if (*current == i) - { - uint32_t fg = TB_RED | TB_BOLD; - uint32_t bg = TB_DEFAULT; - tb_change_cell(lx + 0, ly + 2, '^', fg, bg); - tb_change_cell(lx + 1, ly + 2, '^', fg, bg); - tb_change_cell(lx + 2, ly + 2, '^', fg, bg); - tb_change_cell(lx + 3, ly + 2, '^', fg, bg); - } - - lx += 4; - } -} - -void runeAttrFunc(int i, uint32_t* r, uint32_t* fg, uint32_t* bg) -{ - *r = runes[i]; - *fg = TB_DEFAULT; - *bg = TB_DEFAULT; -} - -void colorAttrFunc(int i, uint32_t* r, uint32_t* fg, uint32_t* bg) -{ - *r = ' '; - *fg = TB_DEFAULT; - *bg = colors[i]; -} - -void updateAndRedrawAll(int mx, int my) -{ - tb_clear(); - - if (mx != -1 && my != -1) - { - backbuf[bbw * my + mx].ch = runes[curRune]; - backbuf[bbw * my + mx].fg = colors[curCol]; - } - - memcpy(tb_cell_buffer(), backbuf, sizeof(struct tb_cell)*bbw * bbh); - int h = tb_height(); - updateAndDrawButtons(&curRune, 0, 0, mx, my, len(runes), runeAttrFunc); - updateAndDrawButtons(&curCol, 0, h - 3, mx, my, len(colors), colorAttrFunc); - tb_present(); -} - -void reallocBackBuffer(int w, int h) -{ - bbw = w; - bbh = h; - - if (backbuf) - { - free(backbuf); - } - - backbuf = calloc(sizeof(struct tb_cell), w * h); -} - -int main(int argv, char** argc) -{ - (void)argc; - (void)argv; - int code = tb_init(); - - if (code < 0) - { - fprintf(stderr, "termbox init failed, code: %d\n", code); - return -1; - } - - tb_select_input_mode(TB_INPUT_ESC | TB_INPUT_MOUSE); - int w = tb_width(); - int h = tb_height(); - reallocBackBuffer(w, h); - updateAndRedrawAll(-1, -1); - - for (;;) - { - struct tb_event ev; - int mx = -1; - int my = -1; - int t = tb_poll_event(&ev); - - if (t == -1) - { - tb_shutdown(); - fprintf(stderr, "termbox poll event error\n"); - return -1; - } - - switch (t) - { - case TB_EVENT_KEY: - if (ev.key == TB_KEY_ESC) - { - tb_shutdown(); - return 0; - } - - break; - - case TB_EVENT_MOUSE: - if (ev.key == TB_KEY_MOUSE_LEFT) - { - mx = ev.x; - my = ev.y; - } - - break; - - case TB_EVENT_RESIZE: - reallocBackBuffer(ev.w, ev.h); - break; - } - - updateAndRedrawAll(mx, my); - } -} diff --git a/dep/termbox_next/src/demo/truecolor.c b/dep/termbox_next/src/demo/truecolor.c deleted file mode 100644 index 33609fdb..00000000 --- a/dep/termbox_next/src/demo/truecolor.c +++ /dev/null @@ -1,69 +0,0 @@ -#include "termbox.h" - -int main() -{ - tb_init(); - tb_select_output_mode(TB_OUTPUT_TRUECOLOR); - int w = tb_width(); - int h = tb_height(); - uint32_t bg = 0x000000, fg = 0x000000; - tb_clear(); - int z = 0; - - for (int y = 1; y < h; y++) - { - for (int x = 1; x < w; x++) - { - uint32_t ch; - utf8_char_to_unicode(&ch, "x"); - fg = 0; - - if (z % 2 == 0) - { - fg |= TB_BOLD; - } - - if (z % 3 == 0) - { - fg |= TB_UNDERLINE; - } - - if (z % 5 == 0) - { - fg |= TB_REVERSE; - } - - tb_change_cell(x, y, ch, fg, bg); - bg += 0x000101; - z++; - } - - bg += 0x080000; - - if (bg > 0xFFFFFF) - { - bg = 0; - } - } - - tb_present(); - - while (1) - { - struct tb_event ev; - int t = tb_poll_event(&ev); - - if (t == -1) - { - break; - } - - if (t == TB_EVENT_KEY) - { - break; - } - } - - tb_shutdown(); - return 0; -} diff --git a/dep/termbox_next/src/input.c b/dep/termbox_next/src/input.c deleted file mode 100644 index 2d3f84d3..00000000 --- a/dep/termbox_next/src/input.c +++ /dev/null @@ -1,319 +0,0 @@ -#include -#include -#include -#include -#include - -#include "term.h" - -#define BUFFER_SIZE_MAX 16 - -// if s1 starts with s2 returns 1, else 0 -static int starts_with(const char* s1, const char* s2) -{ - // nice huh? - while (*s2) - { - if (*s1++ != *s2++) - { - return 0; - } - } - - return 1; -} - -static int parse_mouse_event(struct tb_event* event, const char* buf, int len) -{ - if ((len >= 6) && starts_with(buf, "\033[M")) - { - // X10 mouse encoding, the simplest one - // \033 [ M Cb Cx Cy - int b = buf[3] - 32; - - switch (b & 3) - { - case 0: - if ((b & 64) != 0) - { - event->key = TB_KEY_MOUSE_WHEEL_UP; - } - else - { - event->key = TB_KEY_MOUSE_LEFT; - } - - break; - - case 1: - if ((b & 64) != 0) - { - event->key = TB_KEY_MOUSE_WHEEL_DOWN; - } - else - { - event->key = TB_KEY_MOUSE_MIDDLE; - } - - break; - - case 2: - event->key = TB_KEY_MOUSE_RIGHT; - break; - - case 3: - event->key = TB_KEY_MOUSE_RELEASE; - break; - - default: - return -6; - } - - event->type = TB_EVENT_MOUSE; // TB_EVENT_KEY by default - - if ((b & 32) != 0) - { - event->mod |= TB_MOD_MOTION; - } - - // the coord is 1,1 for upper left - event->x = (uint8_t)buf[4] - 1 - 32; - event->y = (uint8_t)buf[5] - 1 - 32; - return 6; - } - else if (starts_with(buf, "\033[<") || starts_with(buf, "\033[")) - { - // xterm 1006 extended mode or urxvt 1015 extended mode - // xterm: \033 [ < Cb ; Cx ; Cy (M or m) - // urxvt: \033 [ Cb ; Cx ; Cy M - int i, mi = -1, starti = -1; - int isM, isU, s1 = -1, s2 = -1; - int n1 = 0, n2 = 0, n3 = 0; - - for (i = 0; i < len; i++) - { - // We search the first (s1) and the last (s2) ';' - if (buf[i] == ';') - { - if (s1 == -1) - { - s1 = i; - } - - s2 = i; - } - - // We search for the first 'm' or 'M' - if ((buf[i] == 'm' || buf[i] == 'M') && mi == -1) - { - mi = i; - break; - } - } - - if (mi == -1) - { - return 0; - } - - // whether it's a capital M or not - isM = (buf[mi] == 'M'); - - if (buf[2] == '<') - { - isU = 0; - starti = 3; - } - else - { - isU = 1; - starti = 2; - } - - if (s1 == -1 || s2 == -1 || s1 == s2) - { - return 0; - } - - n1 = strtoul(&buf[starti], NULL, 10); - n2 = strtoul(&buf[s1 + 1], NULL, 10); - n3 = strtoul(&buf[s2 + 1], NULL, 10); - - if (isU) - { - n1 -= 32; - } - - switch (n1 & 3) - { - case 0: - if ((n1 & 64) != 0) - { - event->key = TB_KEY_MOUSE_WHEEL_UP; - } - else - { - event->key = TB_KEY_MOUSE_LEFT; - } - - break; - - case 1: - if ((n1 & 64) != 0) - { - event->key = TB_KEY_MOUSE_WHEEL_DOWN; - } - else - { - event->key = TB_KEY_MOUSE_MIDDLE; - } - - break; - - case 2: - event->key = TB_KEY_MOUSE_RIGHT; - break; - - case 3: - event->key = TB_KEY_MOUSE_RELEASE; - break; - - default: - return mi + 1; - } - - if (!isM) - { - // on xterm mouse release is signaled by lowercase m - event->key = TB_KEY_MOUSE_RELEASE; - } - - event->type = TB_EVENT_MOUSE; // TB_EVENT_KEY by default - - if ((n1 & 32) != 0) - { - event->mod |= TB_MOD_MOTION; - } - - event->x = (uint8_t)n2 - 1; - event->y = (uint8_t)n3 - 1; - return mi + 1; - } - - return 0; -} - -// convert escape sequence to event, and return consumed bytes on success (failure == 0) -static int parse_escape_seq(struct tb_event* event, const char* buf, int len) -{ - int mouse_parsed = parse_mouse_event(event, buf, len); - - if (mouse_parsed != 0) - { - return mouse_parsed; - } - - // it's pretty simple here, find 'starts_with' match and return success, else return failure - int i; - - for (i = 0; keys[i]; i++) - { - if (starts_with(buf, keys[i])) - { - event->ch = 0; - event->key = 0xFFFF - i; - return strlen(keys[i]); - } - } - - return 0; -} - -bool extract_event(struct tb_event* event, struct ringbuffer* inbuf, - int inputmode) -{ - char buf[BUFFER_SIZE_MAX + 1]; - int nbytes = ringbuffer_data_size(inbuf); - - if (nbytes > BUFFER_SIZE_MAX) - { - nbytes = BUFFER_SIZE_MAX; - } - - if (nbytes == 0) - { - return false; - } - - ringbuffer_read(inbuf, buf, nbytes); - buf[nbytes] = '\0'; - - if (buf[0] == '\033') - { - int n = parse_escape_seq(event, buf, nbytes); - - if (n != 0) - { - bool success = true; - - if (n < 0) - { - success = false; - n = -n; - } - - ringbuffer_pop(inbuf, 0, n); - return success; - } - else - { - // it's not escape sequence, then it's ALT or ESC, check inputmode - if (inputmode & TB_INPUT_ESC) - { - // if we're in escape mode, fill ESC event, pop buffer, return success - event->ch = 0; - event->key = TB_KEY_ESC; - event->mod = 0; - ringbuffer_pop(inbuf, 0, 1); - return true; - } - else if (inputmode & TB_INPUT_ALT) - { - // if we're in alt mode, set ALT modifier to event and redo parsing - event->mod = TB_MOD_ALT; - ringbuffer_pop(inbuf, 0, 1); - return extract_event(event, inbuf, inputmode); - } - - assert(!"never got here"); - } - } - - // if we're here, this is not an escape sequence and not an alt sequence - // so, it's a FUNCTIONAL KEY or a UNICODE character - - // first of all check if it's a functional key*/ - if ((unsigned char)buf[0] <= TB_KEY_SPACE || - (unsigned char)buf[0] == TB_KEY_BACKSPACE2) - { - // fill event, pop buffer, return success - event->ch = 0; - event->key = (uint16_t)buf[0]; - ringbuffer_pop(inbuf, 0, 1); - return true; - } - - // feh... we got utf8 here - - // check if there is all bytes - if (nbytes >= utf8_char_length(buf[0])) - { - // everything ok, fill event, pop buffer, return success - utf8_char_to_unicode(&event->ch, buf); - event->key = 0; - ringbuffer_pop(inbuf, 0, utf8_char_length(buf[0])); - return true; - } - - return false; -} diff --git a/dep/termbox_next/src/memstream.c b/dep/termbox_next/src/memstream.c deleted file mode 100644 index d218b546..00000000 --- a/dep/termbox_next/src/memstream.c +++ /dev/null @@ -1,36 +0,0 @@ -#include -#include -#include -#include "memstream.h" - -void memstream_init(struct memstream* s, int fd, void* buffer, size_t len) -{ - s->file = fd; - s->data = buffer; - s->pos = 0; - s->capa = len; -} - -void memstream_flush(struct memstream* s) -{ - write(s->file, s->data, s->pos); - s->pos = 0; -} - -void memstream_write(struct memstream* s, void* source, size_t len) -{ - unsigned char* data = source; - - if (s->pos + len > s->capa) - { - memstream_flush(s); - } - - memcpy(s->data + s->pos, data, len); - s->pos += len; -} - -void memstream_puts(struct memstream* s, const char* str) -{ - memstream_write(s, (void*) str, strlen(str)); -} diff --git a/dep/termbox_next/src/memstream.h b/dep/termbox_next/src/memstream.h deleted file mode 100644 index c5d864ac..00000000 --- a/dep/termbox_next/src/memstream.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef H_MEMSTREAM -#define H_MEMSTREAM - -#include -#include - -struct memstream -{ - size_t pos; - size_t capa; - int file; - unsigned char* data; -}; - -void memstream_init(struct memstream* s, int fd, void* buffer, size_t len); -void memstream_flush(struct memstream* s); -void memstream_write(struct memstream* s, void* source, size_t len); -void memstream_puts(struct memstream* s, const char* str); - -#endif diff --git a/dep/termbox_next/src/ringbuffer.c b/dep/termbox_next/src/ringbuffer.c deleted file mode 100644 index a8de0f65..00000000 --- a/dep/termbox_next/src/ringbuffer.c +++ /dev/null @@ -1,195 +0,0 @@ -#include "ringbuffer.h" -#include -#include -#include -#include // for ptrdiff_t - -int init_ringbuffer(struct ringbuffer* r, size_t size) -{ - r->buf = (char*)malloc(size); - - if (!r->buf) - { - return ERINGBUFFER_ALLOC_FAIL; - } - - r->size = size; - clear_ringbuffer(r); - - return 0; -} - -void free_ringbuffer(struct ringbuffer* r) -{ - free(r->buf); -} - -void clear_ringbuffer(struct ringbuffer* r) -{ - r->begin = 0; - r->end = 0; -} - -size_t ringbuffer_free_space(struct ringbuffer* r) -{ - if (r->begin == 0 && r->end == 0) - { - return r->size; - } - - if (r->begin < r->end) - { - return r->size - (r->end - r->begin) - 1; - } - else - { - return r->begin - r->end - 1; - } -} - -size_t ringbuffer_data_size(struct ringbuffer* r) -{ - if (r->begin == 0 && r->end == 0) - { - return 0; - } - - if (r->begin <= r->end) - { - return r->end - r->begin + 1; - } - else - { - return r->size - (r->begin - r->end) + 1; - } -} - - -void ringbuffer_push(struct ringbuffer* r, const void* data, size_t size) -{ - if (ringbuffer_free_space(r) < size) - { - return; - } - - if (r->begin == 0 && r->end == 0) - { - memcpy(r->buf, data, size); - r->begin = r->buf; - r->end = r->buf + size - 1; - return; - } - - r->end++; - - if (r->begin < r->end) - { - if ((size_t)(r->buf + (ptrdiff_t)r->size - r->begin) >= size) - { - // we can fit without cut - memcpy(r->end, data, size); - r->end += size - 1; - } - else - { - // make a cut - size_t s = r->buf + r->size - r->end; - memcpy(r->end, data, s); - size -= s; - memcpy(r->buf, (char*)data + s, size); - r->end = r->buf + size - 1; - } - } - else - { - memcpy(r->end, data, size); - r->end += size - 1; - } -} - -void ringbuffer_pop(struct ringbuffer* r, void* data, size_t size) -{ - if (ringbuffer_data_size(r) < size) - { - return; - } - - int need_clear = 0; - - if (ringbuffer_data_size(r) == size) - { - need_clear = 1; - } - - if (r->begin < r->end) - { - if (data) - { - memcpy(data, r->begin, size); - } - - r->begin += size; - } - else - { - if ((size_t)(r->buf + (ptrdiff_t)r->size - r->begin) >= size) - { - if (data) - { - memcpy(data, r->begin, size); - } - - r->begin += size; - } - else - { - size_t s = r->buf + r->size - r->begin; - - if (data) - { - memcpy(data, r->begin, s); - } - - size -= s; - - if (data) - { - memcpy((char*)data + s, r->buf, size); - } - - r->begin = r->buf + size; - } - } - - if (need_clear) - { - clear_ringbuffer(r); - } -} - -void ringbuffer_read(struct ringbuffer* r, void* data, size_t size) -{ - if (ringbuffer_data_size(r) < size) - { - return; - } - - if (r->begin < r->end) - { - memcpy(data, r->begin, size); - } - else - { - if ((size_t)(r->buf + (ptrdiff_t)r->size - r->begin) >= size) - { - memcpy(data, r->begin, size); - } - else - { - size_t s = r->buf + r->size - r->begin; - memcpy(data, r->begin, s); - size -= s; - memcpy((char*)data + s, r->buf, size); - } - } -} diff --git a/dep/termbox_next/src/ringbuffer.h b/dep/termbox_next/src/ringbuffer.h deleted file mode 100644 index 9a8b0d66..00000000 --- a/dep/termbox_next/src/ringbuffer.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef H_RINGBUFFER -#define H_RINGBUFFER - -#include - -#define ERINGBUFFER_ALLOC_FAIL -1 - -struct ringbuffer -{ - char* buf; - size_t size; - - char* begin; - char* end; -}; - -int init_ringbuffer(struct ringbuffer* r, size_t size); -void free_ringbuffer(struct ringbuffer* r); -void clear_ringbuffer(struct ringbuffer* r); -size_t ringbuffer_free_space(struct ringbuffer* r); -size_t ringbuffer_data_size(struct ringbuffer* r); -void ringbuffer_push(struct ringbuffer* r, const void* data, size_t size); -void ringbuffer_pop(struct ringbuffer* r, void* data, size_t size); -void ringbuffer_read(struct ringbuffer* r, void* data, size_t size); - -#endif diff --git a/dep/termbox_next/src/term.c b/dep/termbox_next/src/term.c deleted file mode 100644 index c94f3941..00000000 --- a/dep/termbox_next/src/term.c +++ /dev/null @@ -1,412 +0,0 @@ -#include -#include -#include -#include -#include - -#include "term.h" -#define ENTER_MOUSE_SEQ "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h" -#define EXIT_MOUSE_SEQ "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l" - -#define EUNSUPPORTED_TERM -1 - -// rxvt-256color -static const char* rxvt_256color_keys[] = -{ - "\033[11~", "\033[12~", "\033[13~", "\033[14~", "\033[15~", "\033[17~", - "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", - "\033[2~", "\033[3~", "\033[7~", "\033[8~", "\033[5~", "\033[6~", - "\033[A", "\033[B", "\033[D", "\033[C", NULL -}; -static const char* rxvt_256color_funcs[] = -{ - "\0337\033[?47h", "\033[2J\033[?47l\0338", "\033[?25h", "\033[?25l", - "\033[H\033[2J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", - "\033=", "\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ, -}; - -// Eterm -static const char* eterm_keys[] = -{ - "\033[11~", "\033[12~", "\033[13~", "\033[14~", "\033[15~", "\033[17~", - "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", - "\033[2~", "\033[3~", "\033[7~", "\033[8~", "\033[5~", "\033[6~", - "\033[A", "\033[B", "\033[D", "\033[C", NULL -}; -static const char* eterm_funcs[] = -{ - "\0337\033[?47h", "\033[2J\033[?47l\0338", "\033[?25h", "\033[?25l", - "\033[H\033[2J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", - "", "", "", "", -}; - -// screen -static const char* screen_keys[] = -{ - "\033OP", "\033OQ", "\033OR", "\033OS", "\033[15~", "\033[17~", - "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", - "\033[2~", "\033[3~", "\033[1~", "\033[4~", "\033[5~", "\033[6~", - "\033OA", "\033OB", "\033OD", "\033OC", NULL -}; -static const char* screen_funcs[] = -{ - "\033[?1049h", "\033[?1049l", "\033[34h\033[?25h", "\033[?25l", - "\033[H\033[J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", - "\033[?1h\033=", "\033[?1l\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ, -}; - -// rxvt-unicode -static const char* rxvt_unicode_keys[] = -{ - "\033[11~", "\033[12~", "\033[13~", "\033[14~", "\033[15~", "\033[17~", - "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", - "\033[2~", "\033[3~", "\033[7~", "\033[8~", "\033[5~", "\033[6~", - "\033[A", "\033[B", "\033[D", "\033[C", NULL -}; -static const char* rxvt_unicode_funcs[] = -{ - "\033[?1049h", "\033[r\033[?1049l", "\033[?25h", "\033[?25l", - "\033[H\033[2J", "\033[m\033(B", "\033[4m", "\033[1m", "\033[5m", - "\033[7m", "\033=", "\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ, -}; - -// linux -static const char* linux_keys[] = -{ - "\033[[A", "\033[[B", "\033[[C", "\033[[D", "\033[[E", "\033[17~", - "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", - "\033[2~", "\033[3~", "\033[1~", "\033[4~", "\033[5~", "\033[6~", - "\033[A", "\033[B", "\033[D", "\033[C", NULL -}; -static const char* linux_funcs[] = -{ - "", "", "\033[?25h\033[?0c", "\033[?25l\033[?1c", "\033[H\033[J", - "\033[0;10m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "", "", "", "", -}; - -// xterm -static const char* xterm_keys[] = -{ - "\033OP", "\033OQ", "\033OR", "\033OS", "\033[15~", "\033[17~", "\033[18~", - "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", "\033[2~", - "\033[3~", "\033OH", "\033OF", "\033[5~", "\033[6~", "\033OA", "\033OB", - "\033OD", "\033OC", NULL -}; -static const char* xterm_funcs[] = -{ - "\033[?1049h", "\033[?1049l", "\033[?12l\033[?25h", "\033[?25l", - "\033[H\033[2J", "\033(B\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", - "\033[?1h\033=", "\033[?1l\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ, -}; - -struct term -{ - const char* name; - const char** keys; - const char** funcs; -}; - -static struct term terms[] = -{ - {"rxvt-256color", rxvt_256color_keys, rxvt_256color_funcs}, - {"Eterm", eterm_keys, eterm_funcs}, - {"screen", screen_keys, screen_funcs}, - {"rxvt-unicode", rxvt_unicode_keys, rxvt_unicode_funcs}, - {"linux", linux_keys, linux_funcs}, - {"xterm", xterm_keys, xterm_funcs}, - {0, 0, 0}, -}; - -static int init_from_terminfo = 0; -const char** keys; -const char** funcs; - -static int try_compatible(const char* term, const char* name, - const char** tkeys, const char** tfuncs) -{ - if (strstr(term, name)) - { - keys = tkeys; - funcs = tfuncs; - return 0; - } - - return EUNSUPPORTED_TERM; -} - -static int init_term_builtin(void) -{ - int i; - const char* term = getenv("TERM"); - - if (term) - { - for (i = 0; terms[i].name; i++) - { - if (!strcmp(terms[i].name, term)) - { - keys = terms[i].keys; - funcs = terms[i].funcs; - return 0; - } - } - - // let's do some heuristic, maybe it's a compatible terminal - if (try_compatible(term, "xterm", xterm_keys, xterm_funcs) == 0) - { - return 0; - } - - if (try_compatible(term, "rxvt", rxvt_unicode_keys, rxvt_unicode_funcs) == 0) - { - return 0; - } - - if (try_compatible(term, "linux", linux_keys, linux_funcs) == 0) - { - return 0; - } - - if (try_compatible(term, "Eterm", eterm_keys, eterm_funcs) == 0) - { - return 0; - } - - if (try_compatible(term, "screen", screen_keys, screen_funcs) == 0) - { - return 0; - } - - // let's assume that 'cygwin' is xterm compatible - if (try_compatible(term, "cygwin", xterm_keys, xterm_funcs) == 0) - { - return 0; - } - } - - return EUNSUPPORTED_TERM; -} - -// terminfo -static char* read_file(const char* file) -{ - FILE* f = fopen(file, "rb"); - - if (!f) - { - return 0; - } - - struct stat st; - - if (fstat(fileno(f), &st) != 0) - { - fclose(f); - return 0; - } - - char* data = malloc(st.st_size); - - if (!data) - { - fclose(f); - return 0; - } - - if (fread(data, 1, st.st_size, f) != (size_t)st.st_size) - { - fclose(f); - free(data); - return 0; - } - - fclose(f); - return data; -} - -static char* terminfo_try_path(const char* path, const char* term) -{ - char tmp[4096]; - snprintf(tmp, sizeof(tmp), "%s/%c/%s", path, term[0], term); - tmp[sizeof(tmp) - 1] = '\0'; - char* data = read_file(tmp); - - if (data) - { - return data; - } - - // fallback to darwin specific dirs structure - snprintf(tmp, sizeof(tmp), "%s/%x/%s", path, term[0], term); - tmp[sizeof(tmp) - 1] = '\0'; - return read_file(tmp); -} - -static char* load_terminfo(void) -{ - char tmp[4096]; - const char* term = getenv("TERM"); - - if (!term) - { - return 0; - } - - // if TERMINFO is set, no other directory should be searched - const char* terminfo = getenv("TERMINFO"); - - if (terminfo) - { - return terminfo_try_path(terminfo, term); - } - - // next, consider ~/.terminfo - const char* home = getenv("HOME"); - - if (home) - { - snprintf(tmp, sizeof(tmp), "%s/.terminfo", home); - tmp[sizeof(tmp) - 1] = '\0'; - char* data = terminfo_try_path(tmp, term); - - if (data) - { - return data; - } - } - - // next, TERMINFO_DIRS - const char* dirs = getenv("TERMINFO_DIRS"); - - if (dirs) - { - snprintf(tmp, sizeof(tmp), "%s", dirs); - tmp[sizeof(tmp) - 1] = '\0'; - char* dir = strtok(tmp, ":"); - - while (dir) - { - const char* cdir = dir; - - if (strcmp(cdir, "") == 0) - { - cdir = "/usr/share/terminfo"; - } - - char* data = terminfo_try_path(cdir, term); - - if (data) - { - return data; - } - - dir = strtok(0, ":"); - } - } - - // fallback to /usr/share/terminfo - return terminfo_try_path("/usr/share/terminfo", term); -} - -#define TI_MAGIC 0432 -#define TI_ALT_MAGIC 542 -#define TI_HEADER_LENGTH 12 -#define TB_KEYS_NUM 22 - -static const char* terminfo_copy_string(char* data, int str, int table) -{ - const int16_t off = *(int16_t*)(data + str); - const char* src = data + table + off; - int len = strlen(src); - char* dst = malloc(len + 1); - strcpy(dst, src); - return dst; -} - -const int16_t ti_funcs[] = -{ - 28, 40, 16, 13, 5, 39, 36, 27, 26, 34, 89, 88, -}; - -const int16_t ti_keys[] = -{ - // apparently not a typo; 67 is F10 for whatever reason - 66, 68, 69, 70, 71, 72, 73, 74, 75, 67, 216, 217, 77, 59, 76, 164, 82, - 81, 87, 61, 79, 83, -}; - -int init_term(void) -{ - int i; - char* data = load_terminfo(); - - if (!data) - { - init_from_terminfo = 0; - return init_term_builtin(); - } - - int16_t* header = (int16_t*)data; - - const int number_sec_len = header[0] == TI_ALT_MAGIC ? 4 : 2; - - if ((header[1] + header[2]) % 2) - { - // old quirk to align everything on word boundaries - header[2] += 1; - } - - const int str_offset = TI_HEADER_LENGTH + - header[1] + header[2] + number_sec_len * header[3]; - const int table_offset = str_offset + 2 * header[4]; - - keys = malloc(sizeof(const char*) * (TB_KEYS_NUM + 1)); - - for (i = 0; i < TB_KEYS_NUM; i++) - { - keys[i] = terminfo_copy_string(data, - str_offset + 2 * ti_keys[i], table_offset); - } - - keys[i] = NULL; - - funcs = malloc(sizeof(const char*) * T_FUNCS_NUM); - - // the last two entries are reserved for mouse. because the table offset is - // not there, the two entries have to fill in manually - for (i = 0; i < T_FUNCS_NUM - 2; i++) - { - funcs[i] = terminfo_copy_string(data, - str_offset + 2 * ti_funcs[i], table_offset); - } - - funcs[T_FUNCS_NUM - 2] = ENTER_MOUSE_SEQ; - funcs[T_FUNCS_NUM - 1] = EXIT_MOUSE_SEQ; - init_from_terminfo = 1; - free(data); - return 0; -} - -void shutdown_term(void) -{ - if (init_from_terminfo) - { - int i; - - for (i = 0; i < TB_KEYS_NUM; i++) - { - free((void*)keys[i]); - } - - // the last two entries are reserved for mouse. because the table offset - // is not there, the two entries have to fill in manually and do not - // need to be freed. - for (i = 0; i < T_FUNCS_NUM - 2; i++) - { - free((void*)funcs[i]); - } - - free(keys); - free(funcs); - } -} diff --git a/dep/termbox_next/src/term.h b/dep/termbox_next/src/term.h deleted file mode 100644 index 8f4d93d4..00000000 --- a/dep/termbox_next/src/term.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef H_TERM -#define H_TERM - -#include "termbox.h" -#include "ringbuffer.h" -#include - -#define EUNSUPPORTED_TERM -1 - -enum -{ - T_ENTER_CA, - T_EXIT_CA, - T_SHOW_CURSOR, - T_HIDE_CURSOR, - T_CLEAR_SCREEN, - T_SGR0, - T_UNDERLINE, - T_BOLD, - T_BLINK, - T_REVERSE, - T_ENTER_KEYPAD, - T_EXIT_KEYPAD, - T_ENTER_MOUSE, - T_EXIT_MOUSE, - T_FUNCS_NUM, -}; - -extern const char** keys; -extern const char** funcs; - -// true on success, false on failure -bool extract_event(struct tb_event* event, struct ringbuffer* inbuf, - int inputmode); -int init_term(void); -void shutdown_term(void); - -#endif diff --git a/dep/termbox_next/src/termbox.c b/dep/termbox_next/src/termbox.c deleted file mode 100644 index 82e30fc2..00000000 --- a/dep/termbox_next/src/termbox.c +++ /dev/null @@ -1,885 +0,0 @@ -#include "term.h" -#include "termbox.h" -#include "memstream.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct cellbuf -{ - int width; - int height; - struct tb_cell* cells; -}; - -#define CELL(buf, x, y) (buf)->cells[(y) * (buf)->width + (x)] -#define IS_CURSOR_HIDDEN(cx, cy) (cx == -1 || cy == -1) -#define LAST_COORD_INIT -1 - -static struct termios orig_tios; - -static struct cellbuf back_buffer; -static struct cellbuf front_buffer; -static unsigned char write_buffer_data[32 * 1024]; -static struct memstream write_buffer; - -static int termw = -1; -static int termh = -1; - -static int inputmode = TB_INPUT_ESC; -static int outputmode = TB_OUTPUT_NORMAL; - -static struct ringbuffer inbuf; - -static int out; -static FILE* in; - -static int out_fileno; -static int in_fileno; - -static int winch_fds[2]; - -static int lastx = LAST_COORD_INIT; -static int lasty = LAST_COORD_INIT; -static int cursor_x = -1; -static int cursor_y = -1; - -static uint32_t background = TB_DEFAULT; -static uint32_t foreground = TB_DEFAULT; - -static void write_cursor(int x, int y); -static void write_sgr(uint32_t fg, uint32_t bg); - -static void cellbuf_init(struct cellbuf* buf, int width, int height); -static void cellbuf_resize(struct cellbuf* buf, int width, int height); -static void cellbuf_clear(struct cellbuf* buf); -static void cellbuf_free(struct cellbuf* buf); - -static void update_size(void); -static void update_term_size(void); -static void send_attr(uint32_t fg, uint32_t bg); -static void send_char(int x, int y, uint32_t c); -static void send_clear(void); -static void sigwinch_handler(int xxx); -static int wait_fill_event(struct tb_event* event, struct timeval* timeout); - -// may happen in a different thread -static volatile int buffer_size_change_request; - -int tb_init_file(const char* name) -{ - out = open(name, O_WRONLY); - in = fopen(name, "r"); - - if (out == -1 || !in) - { - if (out != -1) - { - close(out); - } - - if (in) - { - fclose(in); - } - - return TB_EFAILED_TO_OPEN_TTY; - } - - out_fileno = out; - in_fileno = fileno(in); - - if (init_term() < 0) - { - close(out); - fclose(in); - - return TB_EUNSUPPORTED_TERMINAL; - } - - if (pipe(winch_fds) < 0) - { - close(out); - fclose(in); - - return TB_EPIPE_TRAP_ERROR; - } - - struct sigaction sa; - - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = sigwinch_handler; - sa.sa_flags = 0; - sigaction(SIGWINCH, &sa, 0); - tcgetattr(out_fileno, &orig_tios); - - struct termios tios; - - memcpy(&tios, &orig_tios, sizeof(tios)); - tios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP - | INLCR | IGNCR | ICRNL | IXON); - tios.c_oflag &= ~OPOST; - tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); - tios.c_cflag &= ~(CSIZE | PARENB); - tios.c_cflag |= CS8; - tios.c_cc[VMIN] = 0; - tios.c_cc[VTIME] = 0; - tcsetattr(out_fileno, TCSAFLUSH, &tios); - - memstream_init(&write_buffer, out_fileno, write_buffer_data, - sizeof(write_buffer_data)); - memstream_puts(&write_buffer, funcs[T_ENTER_CA]); - memstream_puts(&write_buffer, funcs[T_ENTER_KEYPAD]); - memstream_puts(&write_buffer, funcs[T_HIDE_CURSOR]); - send_clear(); - - update_term_size(); - cellbuf_init(&back_buffer, termw, termh); - cellbuf_init(&front_buffer, termw, termh); - cellbuf_clear(&back_buffer); - cellbuf_clear(&front_buffer); - init_ringbuffer(&inbuf, 4096); - return 0; -} - -int tb_init(void) -{ - return tb_init_file("/dev/tty"); -} - -void tb_shutdown(void) -{ - if (termw == -1) - { - fputs("tb_shutdown() should not be called twice.", stderr); - abort(); - } - - memstream_puts(&write_buffer, funcs[T_SHOW_CURSOR]); - memstream_puts(&write_buffer, funcs[T_SGR0]); - memstream_puts(&write_buffer, funcs[T_CLEAR_SCREEN]); - memstream_puts(&write_buffer, funcs[T_EXIT_CA]); - memstream_puts(&write_buffer, funcs[T_EXIT_KEYPAD]); - memstream_puts(&write_buffer, funcs[T_EXIT_MOUSE]); - memstream_flush(&write_buffer); - tcsetattr(out_fileno, TCSAFLUSH, &orig_tios); - - shutdown_term(); - close(out); - fclose(in); - close(winch_fds[0]); - close(winch_fds[1]); - - cellbuf_free(&back_buffer); - cellbuf_free(&front_buffer); - free_ringbuffer(&inbuf); - termw = termh = -1; -} - -void tb_present(void) -{ - int x, y, w, i; - struct tb_cell* back, *front; - - // invalidate cursor position - lastx = LAST_COORD_INIT; - lasty = LAST_COORD_INIT; - - if (buffer_size_change_request) - { - update_size(); - buffer_size_change_request = 0; - } - - for (y = 0; y < front_buffer.height; ++y) - { - for (x = 0; x < front_buffer.width;) - { - back = &CELL(&back_buffer, x, y); - front = &CELL(&front_buffer, x, y); - w = wcwidth(back->ch); - - if (w < 1) - { - w = 1; - } - - if (memcmp(back, front, sizeof(struct tb_cell)) == 0) - { - x += w; - continue; - } - - memcpy(front, back, sizeof(struct tb_cell)); - send_attr(back->fg, back->bg); - - if (w > 1 && x >= front_buffer.width - (w - 1)) - { - // Not enough room for wide ch, so send spaces - for (i = x; i < front_buffer.width; ++i) - { - send_char(i, y, ' '); - } - } - else - { - send_char(x, y, back->ch); - - for (i = 1; i < w; ++i) - { - front = &CELL(&front_buffer, x + i, y); - front->ch = 0; - front->fg = back->fg; - front->bg = back->bg; - } - } - - x += w; - } - } - - if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y)) - { - write_cursor(cursor_x, cursor_y); - } - - memstream_flush(&write_buffer); -} - -void tb_set_cursor(int cx, int cy) -{ - if (IS_CURSOR_HIDDEN(cursor_x, cursor_y) && !IS_CURSOR_HIDDEN(cx, cy)) - { - memstream_puts(&write_buffer, funcs[T_SHOW_CURSOR]); - } - - if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y) && IS_CURSOR_HIDDEN(cx, cy)) - { - memstream_puts(&write_buffer, funcs[T_HIDE_CURSOR]); - } - - cursor_x = cx; - cursor_y = cy; - - if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y)) - { - write_cursor(cursor_x, cursor_y); - } -} - -void tb_put_cell(int x, int y, const struct tb_cell* cell) -{ - if ((unsigned)x >= (unsigned)back_buffer.width) - { - return; - } - - if ((unsigned)y >= (unsigned)back_buffer.height) - { - return; - } - - CELL(&back_buffer, x, y) = *cell; -} - -void tb_change_cell(int x, int y, uint32_t ch, uint32_t fg, uint32_t bg) -{ - struct tb_cell c = {ch, fg, bg}; - tb_put_cell(x, y, &c); -} - -void tb_blit(int x, int y, int w, int h, const struct tb_cell* cells) -{ - if (x + w < 0 || x >= back_buffer.width) - { - return; - } - - if (y + h < 0 || y >= back_buffer.height) - { - return; - } - - int xo = 0, yo = 0, ww = w, hh = h; - - if (x < 0) - { - xo = -x; - ww -= xo; - x = 0; - } - - if (y < 0) - { - yo = -y; - hh -= yo; - y = 0; - } - - if (ww > back_buffer.width - x) - { - ww = back_buffer.width - x; - } - - if (hh > back_buffer.height - y) - { - hh = back_buffer.height - y; - } - - int sy; - struct tb_cell* dst = &CELL(&back_buffer, x, y); - const struct tb_cell* src = cells + yo * w + xo; - size_t size = sizeof(struct tb_cell) * ww; - - for (sy = 0; sy < hh; ++sy) - { - memcpy(dst, src, size); - dst += back_buffer.width; - src += w; - } -} - -struct tb_cell* tb_cell_buffer(void) -{ - return back_buffer.cells; -} - -int tb_poll_event(struct tb_event* event) -{ - return wait_fill_event(event, 0); -} - -int tb_peek_event(struct tb_event* event, int timeout) -{ - struct timeval tv; - tv.tv_sec = timeout / 1000; - tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000; - return wait_fill_event(event, &tv); -} - -int tb_width(void) -{ - return termw; -} - -int tb_height(void) -{ - return termh; -} - -void tb_clear(void) -{ - if (buffer_size_change_request) - { - update_size(); - buffer_size_change_request = 0; - } - - cellbuf_clear(&back_buffer); -} - -int tb_select_input_mode(int mode) -{ - if (mode) - { - if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == 0) - { - mode |= TB_INPUT_ESC; - } - - // technically termbox can handle that, but let's be nice - // and show here what mode is actually used - if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == (TB_INPUT_ESC | TB_INPUT_ALT)) - { - mode &= ~TB_INPUT_ALT; - } - - inputmode = mode; - - if (mode & TB_INPUT_MOUSE) - { - memstream_puts(&write_buffer, funcs[T_ENTER_MOUSE]); - memstream_flush(&write_buffer); - } - else - { - memstream_puts(&write_buffer, funcs[T_EXIT_MOUSE]); - memstream_flush(&write_buffer); - } - } - - return inputmode; -} - -int tb_select_output_mode(int mode) -{ - if (mode) - { - outputmode = mode; - } - - return outputmode; -} - -void tb_set_clear_attributes(uint32_t fg, uint32_t bg) -{ - foreground = fg; - background = bg; -} - -static unsigned convertnum(uint32_t num, char* buf) -{ - unsigned i, l = 0; - int ch; - - do - { - buf[l++] = '0' + (num % 10); - num /= 10; - } - while (num); - - for (i = 0; i < l / 2; i++) - { - ch = buf[i]; - buf[i] = buf[l - 1 - i]; - buf[l - 1 - i] = ch; - } - - return l; -} - -#define WRITE_LITERAL(X) memstream_write(&write_buffer, (X), sizeof(X) -1) -#define WRITE_INT(X) memstream_write(&write_buffer, buf, convertnum((X), buf)) - -static void write_cursor(int x, int y) -{ - char buf[32]; - WRITE_LITERAL("\033["); - WRITE_INT(y + 1); - WRITE_LITERAL(";"); - WRITE_INT(x + 1); - WRITE_LITERAL("H"); -} - -static void write_sgr(uint32_t fg, uint32_t bg) -{ - char buf[32]; - - if (outputmode != TB_OUTPUT_TRUECOLOR && fg == TB_DEFAULT && bg == TB_DEFAULT) - { - return; - } - - switch (outputmode) - { - case TB_OUTPUT_TRUECOLOR: - WRITE_LITERAL("\033[38;2;"); - WRITE_INT(fg >> 16 & 0xFF); - WRITE_LITERAL(";"); - WRITE_INT(fg >> 8 & 0xFF); - WRITE_LITERAL(";"); - WRITE_INT(fg & 0xFF); - WRITE_LITERAL(";48;2;"); - WRITE_INT(bg >> 16 & 0xFF); - WRITE_LITERAL(";"); - WRITE_INT(bg >> 8 & 0xFF); - WRITE_LITERAL(";"); - WRITE_INT(bg & 0xFF); - WRITE_LITERAL("m"); - break; - - case TB_OUTPUT_256: - case TB_OUTPUT_216: - case TB_OUTPUT_GRAYSCALE: - WRITE_LITERAL("\033["); - - if (fg != TB_DEFAULT) - { - WRITE_LITERAL("38;5;"); - WRITE_INT(fg); - - if (bg != TB_DEFAULT) - { - WRITE_LITERAL(";"); - } - } - - if (bg != TB_DEFAULT) - { - WRITE_LITERAL("48;5;"); - WRITE_INT(bg); - } - - WRITE_LITERAL("m"); - break; - - case TB_OUTPUT_NORMAL: - default: - WRITE_LITERAL("\033["); - - if (fg != TB_DEFAULT) - { - WRITE_LITERAL("3"); - WRITE_INT(fg - 1); - - if (bg != TB_DEFAULT) - { - WRITE_LITERAL(";"); - } - } - - if (bg != TB_DEFAULT) - { - WRITE_LITERAL("4"); - WRITE_INT(bg - 1); - } - - WRITE_LITERAL("m"); - break; - } -} - -static void cellbuf_init(struct cellbuf* buf, int width, int height) -{ - buf->cells = (struct tb_cell*)malloc(sizeof(struct tb_cell) * width * height); - assert(buf->cells); - buf->width = width; - buf->height = height; -} - -static void cellbuf_resize(struct cellbuf* buf, int width, int height) -{ - if (buf->width == width && buf->height == height) - { - return; - } - - int oldw = buf->width; - int oldh = buf->height; - struct tb_cell* oldcells = buf->cells; - - cellbuf_init(buf, width, height); - cellbuf_clear(buf); - - int minw = (width < oldw) ? width : oldw; - int minh = (height < oldh) ? height : oldh; - int i; - - for (i = 0; i < minh; ++i) - { - struct tb_cell* csrc = oldcells + (i * oldw); - struct tb_cell* cdst = buf->cells + (i * width); - memcpy(cdst, csrc, sizeof(struct tb_cell) * minw); - } - - free(oldcells); -} - -static void cellbuf_clear(struct cellbuf* buf) -{ - int i; - int ncells = buf->width * buf->height; - - for (i = 0; i < ncells; ++i) - { - buf->cells[i].ch = ' '; - buf->cells[i].fg = foreground; - buf->cells[i].bg = background; - } -} - -static void cellbuf_free(struct cellbuf* buf) -{ - free(buf->cells); -} - -static void get_term_size(int* w, int* h) -{ - struct winsize sz; - memset(&sz, 0, sizeof(sz)); - - ioctl(out_fileno, TIOCGWINSZ, &sz); - - if (w) - { - *w = sz.ws_col; - } - - if (h) - { - *h = sz.ws_row; - } -} - -static void send_attr(uint32_t fg, uint32_t bg) -{ -#define LAST_ATTR_INIT 0xFFFFFFFF - static uint32_t lastfg = LAST_ATTR_INIT, lastbg = LAST_ATTR_INIT; - - if (fg != lastfg || bg != lastbg) - { - memstream_puts(&write_buffer, funcs[T_SGR0]); - uint32_t fgcol; - uint32_t bgcol; - - switch (outputmode) - { - case TB_OUTPUT_TRUECOLOR: - fgcol = fg; - bgcol = bg; - break; - - case TB_OUTPUT_256: - fgcol = fg & 0xFF; - bgcol = bg & 0xFF; - break; - - case TB_OUTPUT_216: - fgcol = fg & 0xFF; - - if (fgcol > 215) - { - fgcol = 7; - } - - bgcol = bg & 0xFF; - - if (bgcol > 215) - { - bgcol = 0; - } - - fgcol += 0x10; - bgcol += 0x10; - break; - - case TB_OUTPUT_GRAYSCALE: - fgcol = fg & 0xFF; - - if (fgcol > 23) - { - fgcol = 23; - } - - bgcol = bg & 0xFF; - - if (bgcol > 23) - { - bgcol = 0; - } - - fgcol += 0xe8; - bgcol += 0xe8; - break; - - case TB_OUTPUT_NORMAL: - default: - fgcol = fg & 0x0F; - bgcol = bg & 0x0F; - } - - if (fg & TB_BOLD) - { - memstream_puts(&write_buffer, funcs[T_BOLD]); - } - - if (bg & TB_BOLD) - { - memstream_puts(&write_buffer, funcs[T_BLINK]); - } - - if (fg & TB_UNDERLINE) - { - memstream_puts(&write_buffer, funcs[T_UNDERLINE]); - } - - if ((fg & TB_REVERSE) || (bg & TB_REVERSE)) - { - memstream_puts(&write_buffer, funcs[T_REVERSE]); - } - - write_sgr(fgcol, bgcol); - - lastfg = fg; - lastbg = bg; - } -} - -static void send_char(int x, int y, uint32_t c) -{ - char buf[7]; - int bw = utf8_unicode_to_char(buf, c); - buf[bw] = '\0'; - - if (x - 1 != lastx || y != lasty) - { - write_cursor(x, y); - } - - lastx = x; - lasty = y; - - if (!c) - { - buf[0] = ' '; // replace 0 with whitespace - } - - memstream_puts(&write_buffer, buf); -} - -static void send_clear(void) -{ - send_attr(foreground, background); - memstream_puts(&write_buffer, funcs[T_CLEAR_SCREEN]); - - if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y)) - { - write_cursor(cursor_x, cursor_y); - } - - memstream_flush(&write_buffer); - - // we need to invalidate cursor position too and these two vars are - // used only for simple cursor positioning optimization, cursor - // actually may be in the correct place, but we simply discard - // optimization once and it gives us simple solution for the case when - // cursor moved - lastx = LAST_COORD_INIT; - lasty = LAST_COORD_INIT; -} - -static void sigwinch_handler(int xxx) -{ - (void) xxx; - const int zzz = 1; - write(winch_fds[1], &zzz, sizeof(int)); -} - -static void update_size(void) -{ - update_term_size(); - cellbuf_resize(&back_buffer, termw, termh); - cellbuf_resize(&front_buffer, termw, termh); - cellbuf_clear(&front_buffer); - send_clear(); -} - -static void update_term_size(void) -{ - struct winsize sz; - memset(&sz, 0, sizeof(sz)); - - ioctl(out_fileno, TIOCGWINSZ, &sz); - - termw = sz.ws_col; - termh = sz.ws_row; -} - -static int wait_fill_event(struct tb_event* event, struct timeval* timeout) -{ -#define ENOUGH_DATA_FOR_INPUT_PARSING 128 - int result; - char buf[ENOUGH_DATA_FOR_INPUT_PARSING]; - fd_set events; - memset(event, 0, sizeof(struct tb_event)); - - // try to extract event from input buffer, return on success - event->type = TB_EVENT_KEY; - - if (extract_event(event, &inbuf, inputmode)) - { - return event->type; - } - - // it looks like input buffer is incomplete, let's try the short path - size_t r = fread(buf, 1, ENOUGH_DATA_FOR_INPUT_PARSING, in); - - if (r < ENOUGH_DATA_FOR_INPUT_PARSING && feof(in)) - { - clearerr(in); - } - - if (r > 0) - { - if (ringbuffer_free_space(&inbuf) < r) - { - return -1; - } - - ringbuffer_push(&inbuf, buf, r); - - if (extract_event(event, &inbuf, inputmode)) - { - return event->type; - } - } - - // no stuff in FILE's internal buffer, block in select - while (1) - { - FD_ZERO(&events); - FD_SET(in_fileno, &events); - FD_SET(winch_fds[0], &events); - int maxfd = (winch_fds[0] > in_fileno) ? winch_fds[0] : in_fileno; - result = select(maxfd + 1, &events, 0, 0, timeout); - - if (!result) - { - return 0; - } - - if (FD_ISSET(in_fileno, &events)) - { - event->type = TB_EVENT_KEY; - size_t r = fread(buf, 1, ENOUGH_DATA_FOR_INPUT_PARSING, in); - - if (r < ENOUGH_DATA_FOR_INPUT_PARSING && feof(in)) - { - clearerr(in); - } - - if (r == 0) - { - continue; - } - - // if there is no free space in input buffer, return error - if (ringbuffer_free_space(&inbuf) < r) - { - return -1; - } - - // fill buffer - ringbuffer_push(&inbuf, buf, r); - - if (extract_event(event, &inbuf, inputmode)) - { - return event->type; - } - } - - if (FD_ISSET(winch_fds[0], &events)) - { - event->type = TB_EVENT_RESIZE; - int zzz = 0; - read(winch_fds[0], &zzz, sizeof(int)); - buffer_size_change_request = 1; - get_term_size(&event->w, &event->h); - return TB_EVENT_RESIZE; - } - } -} diff --git a/dep/termbox_next/src/termbox.h b/dep/termbox_next/src/termbox.h deleted file mode 100644 index c3cbcb63..00000000 --- a/dep/termbox_next/src/termbox.h +++ /dev/null @@ -1,307 +0,0 @@ -#ifndef H_TERMBOX -#define H_TERMBOX -#include - -// shared objects -#if __GNUC__ >= 4 - #define SO_IMPORT __attribute__((visibility("default"))) -#else - #define SO_IMPORT -#endif - -// c++ -#ifdef __cplusplus -extern "C" { -#endif - -// Key constants. See also struct tb_event's key field. -// These are a safe subset of terminfo keys, which exist on all popular -// terminals. Termbox uses only them to stay truly portable. -#define TB_KEY_F1 (0xFFFF-0) -#define TB_KEY_F2 (0xFFFF-1) -#define TB_KEY_F3 (0xFFFF-2) -#define TB_KEY_F4 (0xFFFF-3) -#define TB_KEY_F5 (0xFFFF-4) -#define TB_KEY_F6 (0xFFFF-5) -#define TB_KEY_F7 (0xFFFF-6) -#define TB_KEY_F8 (0xFFFF-7) -#define TB_KEY_F9 (0xFFFF-8) -#define TB_KEY_F10 (0xFFFF-9) -#define TB_KEY_F11 (0xFFFF-10) -#define TB_KEY_F12 (0xFFFF-11) -#define TB_KEY_INSERT (0xFFFF-12) -#define TB_KEY_DELETE (0xFFFF-13) -#define TB_KEY_HOME (0xFFFF-14) -#define TB_KEY_END (0xFFFF-15) -#define TB_KEY_PGUP (0xFFFF-16) -#define TB_KEY_PGDN (0xFFFF-17) -#define TB_KEY_ARROW_UP (0xFFFF-18) -#define TB_KEY_ARROW_DOWN (0xFFFF-19) -#define TB_KEY_ARROW_LEFT (0xFFFF-20) -#define TB_KEY_ARROW_RIGHT (0xFFFF-21) -#define TB_KEY_MOUSE_LEFT (0xFFFF-22) -#define TB_KEY_MOUSE_RIGHT (0xFFFF-23) -#define TB_KEY_MOUSE_MIDDLE (0xFFFF-24) -#define TB_KEY_MOUSE_RELEASE (0xFFFF-25) -#define TB_KEY_MOUSE_WHEEL_UP (0xFFFF-26) -#define TB_KEY_MOUSE_WHEEL_DOWN (0xFFFF-27) - -// These are all ASCII code points below SPACE character and a BACKSPACE key. -#define TB_KEY_CTRL_TILDE 0x00 -#define TB_KEY_CTRL_2 0x00 // clash with 'CTRL_TILDE' -#define TB_KEY_CTRL_A 0x01 -#define TB_KEY_CTRL_B 0x02 -#define TB_KEY_CTRL_C 0x03 -#define TB_KEY_CTRL_D 0x04 -#define TB_KEY_CTRL_E 0x05 -#define TB_KEY_CTRL_F 0x06 -#define TB_KEY_CTRL_G 0x07 -#define TB_KEY_BACKSPACE 0x08 -#define TB_KEY_CTRL_H 0x08 // clash with 'CTRL_BACKSPACE' -#define TB_KEY_TAB 0x09 -#define TB_KEY_CTRL_I 0x09 // clash with 'TAB' -#define TB_KEY_CTRL_J 0x0A -#define TB_KEY_CTRL_K 0x0B -#define TB_KEY_CTRL_L 0x0C -#define TB_KEY_ENTER 0x0D -#define TB_KEY_CTRL_M 0x0D // clash with 'ENTER' -#define TB_KEY_CTRL_N 0x0E -#define TB_KEY_CTRL_O 0x0F -#define TB_KEY_CTRL_P 0x10 -#define TB_KEY_CTRL_Q 0x11 -#define TB_KEY_CTRL_R 0x12 -#define TB_KEY_CTRL_S 0x13 -#define TB_KEY_CTRL_T 0x14 -#define TB_KEY_CTRL_U 0x15 -#define TB_KEY_CTRL_V 0x16 -#define TB_KEY_CTRL_W 0x17 -#define TB_KEY_CTRL_X 0x18 -#define TB_KEY_CTRL_Y 0x19 -#define TB_KEY_CTRL_Z 0x1A -#define TB_KEY_ESC 0x1B -#define TB_KEY_CTRL_LSQ_BRACKET 0x1B // clash with 'ESC' -#define TB_KEY_CTRL_3 0x1B // clash with 'ESC' -#define TB_KEY_CTRL_4 0x1C -#define TB_KEY_CTRL_BACKSLASH 0x1C // clash with 'CTRL_4' -#define TB_KEY_CTRL_5 0x1D -#define TB_KEY_CTRL_RSQ_BRACKET 0x1D // clash with 'CTRL_5' -#define TB_KEY_CTRL_6 0x1E -#define TB_KEY_CTRL_7 0x1F -#define TB_KEY_CTRL_SLASH 0x1F // clash with 'CTRL_7' -#define TB_KEY_CTRL_UNDERSCORE 0x1F // clash with 'CTRL_7' -#define TB_KEY_SPACE 0x20 -#define TB_KEY_BACKSPACE2 0x7F -#define TB_KEY_CTRL_8 0x7F // clash with 'BACKSPACE2' - -// These are non-existing ones. -// #define TB_KEY_CTRL_1 clash with '1' -// #define TB_KEY_CTRL_9 clash with '9' -// #define TB_KEY_CTRL_0 clash with '0' - -// Alt modifier constant, see tb_event.mod field and tb_select_input_mode function. -// Mouse-motion modifier -#define TB_MOD_ALT 0x01 -#define TB_MOD_MOTION 0x02 - -// Colors (see struct tb_cell's fg and bg fields). -#define TB_DEFAULT 0x00 -#define TB_BLACK 0x01 -#define TB_RED 0x02 -#define TB_GREEN 0x03 -#define TB_YELLOW 0x04 -#define TB_BLUE 0x05 -#define TB_MAGENTA 0x06 -#define TB_CYAN 0x07 -#define TB_WHITE 0x08 - -// Attributes, it is possible to use multiple attributes by combining them -// using bitwise OR ('|'). Although, colors cannot be combined. But you can -// combine attributes and a single color. See also struct tb_cell's fg and bg -// fields. -#define TB_BOLD 0x01000000 -#define TB_UNDERLINE 0x02000000 -#define TB_REVERSE 0x04000000 - -// A cell, single conceptual entity on the terminal screen. The terminal screen -// is basically a 2d array of cells. It has the following fields: -// - 'ch' is a unicode character -// - 'fg' foreground color and attributes -// - 'bg' background color and attributes -struct tb_cell -{ - uint32_t ch; - uint32_t fg; - uint32_t bg; -}; - -#define TB_EVENT_KEY 1 -#define TB_EVENT_RESIZE 2 -#define TB_EVENT_MOUSE 3 - -// An event, single interaction from the user. The 'mod' and 'ch' fields are -// valid if 'type' is TB_EVENT_KEY. The 'w' and 'h' fields are valid if 'type' -// is TB_EVENT_RESIZE. The 'x' and 'y' fields are valid if 'type' is -// TB_EVENT_MOUSE. The 'key' field is valid if 'type' is either TB_EVENT_KEY -// or TB_EVENT_MOUSE. The fields 'key' and 'ch' are mutually exclusive; only -// one of them can be non-zero at a time. -struct tb_event -{ - uint8_t type; - uint8_t mod; // modifiers to either 'key' or 'ch' below - uint16_t key; // one of the TB_KEY_* constants - uint32_t ch; // unicode character - int32_t w; - int32_t h; - int32_t x; - int32_t y; -}; - -// Error codes returned by tb_init(). All of them are self-explanatory, except -// the pipe trap error. Termbox uses unix pipes in order to deliver a message -// from a signal handler (SIGWINCH) to the main event reading loop. Honestly in -// most cases you should just check the returned code as < 0. -#define TB_EUNSUPPORTED_TERMINAL -1 -#define TB_EFAILED_TO_OPEN_TTY -2 -#define TB_EPIPE_TRAP_ERROR -3 - -// Initializes the termbox library. This function should be called before any -// other functions. Function tb_init is same as tb_init_file("/dev/tty"). After successful initialization, the library must be -// finalized using the tb_shutdown() function. -SO_IMPORT int tb_init(void); -SO_IMPORT int tb_init_file(const char* name); -SO_IMPORT void tb_shutdown(void); - -// Returns the size of the internal back buffer (which is the same as -// terminal's window size in characters). The internal buffer can be resized -// after tb_clear() or tb_present() function calls. Both dimensions have an -// unspecified negative value when called before tb_init() or after -// tb_shutdown(). -SO_IMPORT int tb_width(void); -SO_IMPORT int tb_height(void); - -// Clears the internal back buffer using TB_DEFAULT color or the -// color/attributes set by tb_set_clear_attributes() function. -SO_IMPORT void tb_clear(void); -SO_IMPORT void tb_set_clear_attributes(uint32_t fg, uint32_t bg); - -// Synchronizes the internal back buffer with the terminal. -SO_IMPORT void tb_present(void); - -#define TB_HIDE_CURSOR -1 - -// Sets the position of the cursor. Upper-left character is (0, 0). If you pass -// TB_HIDE_CURSOR as both coordinates, then the cursor will be hidden. Cursor -// is hidden by default. -SO_IMPORT void tb_set_cursor(int cx, int cy); - -// Changes cell's parameters in the internal back buffer at the specified -// position. -SO_IMPORT void tb_put_cell(int x, int y, const struct tb_cell* cell); -SO_IMPORT void tb_change_cell(int x, int y, uint32_t ch, uint32_t fg, - uint32_t bg); - -// Copies the buffer from 'cells' at the specified position, assuming the -// buffer is a two-dimensional array of size ('w' x 'h'), represented as a -// one-dimensional buffer containing lines of cells starting from the top. -// (DEPRECATED: use tb_cell_buffer() instead and copy memory on your own) -SO_IMPORT void tb_blit(int x, int y, int w, int h, const struct tb_cell* cells); - -// Returns a pointer to internal cell back buffer. You can get its dimensions -// using tb_width() and tb_height() functions. The pointer stays valid as long -// as no tb_clear() and tb_present() calls are made. The buffer is -// one-dimensional buffer containing lines of cells starting from the top. -SO_IMPORT struct tb_cell* tb_cell_buffer(void); - -#define TB_INPUT_CURRENT 0 // 000 -#define TB_INPUT_ESC 1 // 001 -#define TB_INPUT_ALT 2 // 010 -#define TB_INPUT_MOUSE 4 // 100 - -// Sets the termbox input mode. Termbox has two input modes: -// 1. Esc input mode. -// When ESC sequence is in the buffer and it doesn't match any known -// ESC sequence => ESC means TB_KEY_ESC. -// 2. Alt input mode. -// When ESC sequence is in the buffer and it doesn't match any known -// sequence => ESC enables TB_MOD_ALT modifier for the next keyboard event. -// -// You can also apply TB_INPUT_MOUSE via bitwise OR operation to either of the -// modes (e.g. TB_INPUT_ESC | TB_INPUT_MOUSE). If none of the main two modes -// were set, but the mouse mode was, TB_INPUT_ESC mode is used. If for some -// reason you've decided to use (TB_INPUT_ESC | TB_INPUT_ALT) combination, it -// will behave as if only TB_INPUT_ESC was selected. -// -// If 'mode' is TB_INPUT_CURRENT, it returns the current input mode. -// -// Default termbox input mode is TB_INPUT_ESC. -SO_IMPORT int tb_select_input_mode(int mode); - -#define TB_OUTPUT_CURRENT 0 -#define TB_OUTPUT_NORMAL 1 -#define TB_OUTPUT_256 2 -#define TB_OUTPUT_216 3 -#define TB_OUTPUT_GRAYSCALE 4 -#define TB_OUTPUT_TRUECOLOR 5 - -// Sets the termbox output mode. Termbox has three output options: -// 1. TB_OUTPUT_NORMAL => [1..8] -// This mode provides 8 different colors: -// black, red, green, yellow, blue, magenta, cyan, white -// Shortcut: TB_BLACK, TB_RED, ... -// Attributes: TB_BOLD, TB_UNDERLINE, TB_REVERSE -// -// Example usage: -// tb_change_cell(x, y, '@', TB_BLACK | TB_BOLD, TB_RED); -// -// 2. TB_OUTPUT_256 => [0..256] -// In this mode you can leverage the 256 terminal mode: -// 0x00 - 0x07: the 8 colors as in TB_OUTPUT_NORMAL -// 0x08 - 0x0f: TB_* | TB_BOLD -// 0x10 - 0xe7: 216 different colors -// 0xe8 - 0xff: 24 different shades of grey -// -// Example usage: -// tb_change_cell(x, y, '@', 184, 240); -// tb_change_cell(x, y, '@', 0xb8, 0xf0); -// -// 3. TB_OUTPUT_216 => [0..216] -// This mode supports the 3rd range of the 256 mode only. -// But you don't need to provide an offset. -// -// 4. TB_OUTPUT_GRAYSCALE => [0..23] -// This mode supports the 4th range of the 256 mode only. -// But you dont need to provide an offset. -// -// 5. TB_OUTPUT_TRUECOLOR => [0x000000..0xFFFFFF] -// This mode supports 24-bit true color. Format is 0xRRGGBB. -// -// Execute build/src/demo/output to see its impact on your terminal. -// -// If 'mode' is TB_OUTPUT_CURRENT, it returns the current output mode. -// -// Default termbox output mode is TB_OUTPUT_NORMAL. -SO_IMPORT int tb_select_output_mode(int mode); - -// Wait for an event up to 'timeout' milliseconds and fill the 'event' -// structure with it, when the event is available. Returns the type of the -// event (one of TB_EVENT_* constants) or -1 if there was an error or 0 in case -// there were no event during 'timeout' period. -SO_IMPORT int tb_peek_event(struct tb_event* event, int timeout); - -// Wait for an event forever and fill the 'event' structure with it, when the -// event is available. Returns the type of the event (one of TB_EVENT_ -// constants) or -1 if there was an error. -SO_IMPORT int tb_poll_event(struct tb_event* event); - -// Utility utf8 functions. -#define TB_EOF -1 -SO_IMPORT int utf8_char_length(char c); -SO_IMPORT int utf8_char_to_unicode(uint32_t* out, const char* c); -SO_IMPORT int utf8_unicode_to_char(char* out, uint32_t c); - -// c++ -#ifdef __cplusplus -} -#endif - -#endif diff --git a/dep/termbox_next/src/utf8.c b/dep/termbox_next/src/utf8.c deleted file mode 100644 index 43efd7f2..00000000 --- a/dep/termbox_next/src/utf8.c +++ /dev/null @@ -1,106 +0,0 @@ -#include "termbox.h" - -static const unsigned char utf8_length[256] = -{ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1 -}; - -static const unsigned char utf8_mask[6] = -{ - 0x7F, - 0x1F, - 0x0F, - 0x07, - 0x03, - 0x01 -}; - -int utf8_char_length(char c) -{ - return utf8_length[(unsigned char)c]; -} - -int utf8_char_to_unicode(uint32_t* out, const char* c) -{ - if (*c == 0) - { - return TB_EOF; - } - - int i; - unsigned char len = utf8_char_length(*c); - unsigned char mask = utf8_mask[len - 1]; - uint32_t result = c[0] & mask; - - for (i = 1; i < len; ++i) - { - result <<= 6; - result |= c[i] & 0x3f; - } - - *out = result; - return (int)len; -} - -int utf8_unicode_to_char(char* out, uint32_t c) -{ - int len = 0; - int first; - int i; - - if (c < 0x80) - { - first = 0; - len = 1; - } - else if (c < 0x800) - { - first = 0xc0; - len = 2; - } - else if (c < 0x10000) - { - first = 0xe0; - len = 3; - } - else if (c < 0x200000) - { - first = 0xf0; - len = 4; - } - else if (c < 0x4000000) - { - first = 0xf8; - len = 5; - } - else - { - first = 0xfc; - len = 6; - } - - for (i = len - 1; i > 0; --i) - { - out[i] = (c & 0x3f) | 0x80; - c >>= 6; - } - - out[0] = c | first; - - return len; -} diff --git a/dep/termbox_next/tools/astylerc b/dep/termbox_next/tools/astylerc deleted file mode 100644 index a296bc37..00000000 --- a/dep/termbox_next/tools/astylerc +++ /dev/null @@ -1,27 +0,0 @@ ---style=break ---indent=force-tab=4 ---indent-classes ---indent-switches ---indent-namespaces ---indent-after-parens ---indent-continuation=1 ---indent-preproc-block ---indent-preproc-define ---indent-preproc-cond ---indent-col1-comments ---min-conditional-indent=0 ---max-continuation-indent=40 ---break-blocks ---pad-oper ---pad-comma ---pad-header ---unpad-paren ---align-pointer=type ---align-reference=type ---break-one-line-headers ---add-braces ---attach-return-type ---attach-return-type-decl ---remove-comment-prefix ---max-code-length=80 ---mode=c diff --git a/dep/termbox_next/tools/collect_terminfo.py b/dep/termbox_next/tools/collect_terminfo.py deleted file mode 100755 index 596c3c46..00000000 --- a/dep/termbox_next/tools/collect_terminfo.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env python - -import sys, os, subprocess - -def escaped(s): - return s.replace("\033", "\\033") - -def tput(term, name): - try: - return subprocess.check_output(['tput', '-T%s' % term, name]).decode() - except subprocess.CalledProcessError as e: - return e.output.decode() - - -def w(s): - if s == None: - return - sys.stdout.write(s) - -terminals = { - 'xterm' : 'xterm', - 'rxvt-256color' : 'rxvt_256color', - 'rxvt-unicode' : 'rxvt_unicode', - 'linux' : 'linux', - 'Eterm' : 'eterm', - 'screen' : 'screen' -} - -keys = [ - "F1", "kf1", - "F2", "kf2", - "F3", "kf3", - "F4", "kf4", - "F5", "kf5", - "F6", "kf6", - "F7", "kf7", - "F8", "kf8", - "F9", "kf9", - "F10", "kf10", - "F11", "kf11", - "F12", "kf12", - "INSERT", "kich1", - "DELETE", "kdch1", - "HOME", "khome", - "END", "kend", - "PGUP", "kpp", - "PGDN", "knp", - "KEY_UP", "kcuu1", - "KEY_DOWN", "kcud1", - "KEY_LEFT", "kcub1", - "KEY_RIGHT", "kcuf1" -] - -funcs = [ - "T_ENTER_CA", "smcup", - "T_EXIT_CA", "rmcup", - "T_SHOW_CURSOR", "cnorm", - "T_HIDE_CURSOR", "civis", - "T_CLEAR_SCREEN", "clear", - "T_SGR0", "sgr0", - "T_UNDERLINE", "smul", - "T_BOLD", "bold", - "T_BLINK", "blink", - "T_REVERSE", "rev", - "T_ENTER_KEYPAD", "smkx", - "T_EXIT_KEYPAD", "rmkx" -] - -def iter_pairs(iterable): - iterable = iter(iterable) - while True: - yield (next(iterable), next(iterable)) - -def do_term(term, nick): - w("// %s\n" % term) - w("static const char *%s_keys[] = {\n\t" % nick) - for k, v in iter_pairs(keys): - w('"') - w(escaped(tput(term, v))) - w('",') - w(" 0\n};\n") - w("static const char *%s_funcs[] = {\n\t" % nick) - for k,v in iter_pairs(funcs): - w('"') - if v == "sgr": - w("\\033[3%d;4%dm") - elif v == "cup": - w("\\033[%d;%dH") - else: - w(escaped(tput(term, v))) - w('", ') - w("\n};\n\n") - -def do_terms(d): - w("static struct term {\n") - w("\tconst char *name;\n") - w("\tconst char **keys;\n") - w("\tconst char **funcs;\n") - w("} terms[] = {\n") - for k, v in d.items(): - w('\t{"%s", %s_keys, %s_funcs},\n' % (k, v, v)) - w("\t{0, 0, 0},\n") - w("};\n") - -for k,v in terminals.items(): - do_term(k, v) - -do_terms(terminals) diff --git a/include/termbox2.h b/include/termbox2.h new file mode 100644 index 00000000..4f1088d0 --- /dev/null +++ b/include/termbox2.h @@ -0,0 +1,3448 @@ +/* +MIT License + +Copyright (c) 2010-2020 nsf + 2015-2024 Adam Saponara + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef __TERMBOX_H +#define __TERMBOX_H + +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE +#endif + +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef PATH_MAX +#define TB_PATH_MAX PATH_MAX +#else +#define TB_PATH_MAX 4096 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// __ffi_start + +#define TB_VERSION_STR "2.5.0-dev" + +/* The following compile-time options are supported: + * + * TB_OPT_ATTR_W: Integer width of fg and bg attributes. Valid values + * (assuming system support) are 16, 32, and 64. (See + * uintattr_t). 32 or 64 enables output mode + * TB_OUTPUT_TRUECOLOR. 64 enables additional style + * attributes. (See tb_set_output_mode.) Larger values + * consume more memory in exchange for more features. + * Defaults to 16. + * + * TB_OPT_EGC: If set, enable extended grapheme cluster support + * (tb_extend_cell, tb_set_cell_ex). Consumes more memory. + * Defaults off. + * + * TB_OPT_PRINTF_BUF: Write buffer size for printf operations. Represents the + * largest string that can be sent in one call to tb_print* + * and tb_send* functions. Defaults to 4096. + * + * TB_OPT_READ_BUF: Read buffer size for tty reads. Defaults to 64. + * + * TB_OPT_TRUECOLOR: Deprecated. Sets TB_OPT_ATTR_W to 32 if not already set. + */ + +#if defined(TB_LIB_OPTS) || 0 // __tb_lib_opts +// Ensure consistent compile-time options when using as a shared library +#undef TB_OPT_ATTR_W +#undef TB_OPT_EGC +#undef TB_OPT_PRINTF_BUF +#undef TB_OPT_READ_BUF +#define TB_OPT_ATTR_W 64 +#define TB_OPT_EGC +#endif + +// Ensure sane TB_OPT_ATTR_W (16, 32, or 64) +#if defined TB_OPT_ATTR_W && TB_OPT_ATTR_W == 16 +#elif defined TB_OPT_ATTR_W && TB_OPT_ATTR_W == 32 +#elif defined TB_OPT_ATTR_W && TB_OPT_ATTR_W == 64 +#else +#undef TB_OPT_ATTR_W +#if defined TB_OPT_TRUECOLOR // Back-compat for old flag +#define TB_OPT_ATTR_W 32 +#else +#define TB_OPT_ATTR_W 16 +#endif +#endif + +/* ASCII key constants (tb_event.key) */ +#define TB_KEY_CTRL_TILDE 0x00 +#define TB_KEY_CTRL_2 0x00 /* clash with 'CTRL_TILDE' */ +#define TB_KEY_CTRL_A 0x01 +#define TB_KEY_CTRL_B 0x02 +#define TB_KEY_CTRL_C 0x03 +#define TB_KEY_CTRL_D 0x04 +#define TB_KEY_CTRL_E 0x05 +#define TB_KEY_CTRL_F 0x06 +#define TB_KEY_CTRL_G 0x07 +#define TB_KEY_BACKSPACE 0x08 +#define TB_KEY_CTRL_H 0x08 /* clash with 'CTRL_BACKSPACE' */ +#define TB_KEY_TAB 0x09 +#define TB_KEY_CTRL_I 0x09 /* clash with 'TAB' */ +#define TB_KEY_CTRL_J 0x0a +#define TB_KEY_CTRL_K 0x0b +#define TB_KEY_CTRL_L 0x0c +#define TB_KEY_ENTER 0x0d +#define TB_KEY_CTRL_M 0x0d /* clash with 'ENTER' */ +#define TB_KEY_CTRL_N 0x0e +#define TB_KEY_CTRL_O 0x0f +#define TB_KEY_CTRL_P 0x10 +#define TB_KEY_CTRL_Q 0x11 +#define TB_KEY_CTRL_R 0x12 +#define TB_KEY_CTRL_S 0x13 +#define TB_KEY_CTRL_T 0x14 +#define TB_KEY_CTRL_U 0x15 +#define TB_KEY_CTRL_V 0x16 +#define TB_KEY_CTRL_W 0x17 +#define TB_KEY_CTRL_X 0x18 +#define TB_KEY_CTRL_Y 0x19 +#define TB_KEY_CTRL_Z 0x1a +#define TB_KEY_ESC 0x1b +#define TB_KEY_CTRL_LSQ_BRACKET 0x1b /* clash with 'ESC' */ +#define TB_KEY_CTRL_3 0x1b /* clash with 'ESC' */ +#define TB_KEY_CTRL_4 0x1c +#define TB_KEY_CTRL_BACKSLASH 0x1c /* clash with 'CTRL_4' */ +#define TB_KEY_CTRL_5 0x1d +#define TB_KEY_CTRL_RSQ_BRACKET 0x1d /* clash with 'CTRL_5' */ +#define TB_KEY_CTRL_6 0x1e +#define TB_KEY_CTRL_7 0x1f +#define TB_KEY_CTRL_SLASH 0x1f /* clash with 'CTRL_7' */ +#define TB_KEY_CTRL_UNDERSCORE 0x1f /* clash with 'CTRL_7' */ +#define TB_KEY_SPACE 0x20 +#define TB_KEY_BACKSPACE2 0x7f +#define TB_KEY_CTRL_8 0x7f /* clash with 'BACKSPACE2' */ + +#define tb_key_i(i) 0xffff - (i) +/* Terminal-dependent key constants (tb_event.key) and terminfo capabilities */ +/* BEGIN codegen h */ +/* Produced by ./codegen.sh on Thu, 13 Jul 2023 05:46:13 +0000 */ +#define TB_KEY_F1 (0xffff - 0) +#define TB_KEY_F2 (0xffff - 1) +#define TB_KEY_F3 (0xffff - 2) +#define TB_KEY_F4 (0xffff - 3) +#define TB_KEY_F5 (0xffff - 4) +#define TB_KEY_F6 (0xffff - 5) +#define TB_KEY_F7 (0xffff - 6) +#define TB_KEY_F8 (0xffff - 7) +#define TB_KEY_F9 (0xffff - 8) +#define TB_KEY_F10 (0xffff - 9) +#define TB_KEY_F11 (0xffff - 10) +#define TB_KEY_F12 (0xffff - 11) +#define TB_KEY_INSERT (0xffff - 12) +#define TB_KEY_DELETE (0xffff - 13) +#define TB_KEY_HOME (0xffff - 14) +#define TB_KEY_END (0xffff - 15) +#define TB_KEY_PGUP (0xffff - 16) +#define TB_KEY_PGDN (0xffff - 17) +#define TB_KEY_ARROW_UP (0xffff - 18) +#define TB_KEY_ARROW_DOWN (0xffff - 19) +#define TB_KEY_ARROW_LEFT (0xffff - 20) +#define TB_KEY_ARROW_RIGHT (0xffff - 21) +#define TB_KEY_BACK_TAB (0xffff - 22) +#define TB_KEY_MOUSE_LEFT (0xffff - 23) +#define TB_KEY_MOUSE_RIGHT (0xffff - 24) +#define TB_KEY_MOUSE_MIDDLE (0xffff - 25) +#define TB_KEY_MOUSE_RELEASE (0xffff - 26) +#define TB_KEY_MOUSE_WHEEL_UP (0xffff - 27) +#define TB_KEY_MOUSE_WHEEL_DOWN (0xffff - 28) + +#define TB_CAP_F1 0 +#define TB_CAP_F2 1 +#define TB_CAP_F3 2 +#define TB_CAP_F4 3 +#define TB_CAP_F5 4 +#define TB_CAP_F6 5 +#define TB_CAP_F7 6 +#define TB_CAP_F8 7 +#define TB_CAP_F9 8 +#define TB_CAP_F10 9 +#define TB_CAP_F11 10 +#define TB_CAP_F12 11 +#define TB_CAP_INSERT 12 +#define TB_CAP_DELETE 13 +#define TB_CAP_HOME 14 +#define TB_CAP_END 15 +#define TB_CAP_PGUP 16 +#define TB_CAP_PGDN 17 +#define TB_CAP_ARROW_UP 18 +#define TB_CAP_ARROW_DOWN 19 +#define TB_CAP_ARROW_LEFT 20 +#define TB_CAP_ARROW_RIGHT 21 +#define TB_CAP_BACK_TAB 22 +#define TB_CAP__COUNT_KEYS 23 +#define TB_CAP_ENTER_CA 23 +#define TB_CAP_EXIT_CA 24 +#define TB_CAP_SHOW_CURSOR 25 +#define TB_CAP_HIDE_CURSOR 26 +#define TB_CAP_CLEAR_SCREEN 27 +#define TB_CAP_SGR0 28 +#define TB_CAP_UNDERLINE 29 +#define TB_CAP_BOLD 30 +#define TB_CAP_BLINK 31 +#define TB_CAP_ITALIC 32 +#define TB_CAP_REVERSE 33 +#define TB_CAP_ENTER_KEYPAD 34 +#define TB_CAP_EXIT_KEYPAD 35 +#define TB_CAP_DIM 36 +#define TB_CAP_INVISIBLE 37 +#define TB_CAP__COUNT 38 +/* END codegen h */ + +/* Some hard-coded caps */ +#define TB_HARDCAP_ENTER_MOUSE "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h" +#define TB_HARDCAP_EXIT_MOUSE "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l" +#define TB_HARDCAP_STRIKEOUT "\x1b[9m" +#define TB_HARDCAP_UNDERLINE_2 "\x1b[21m" +#define TB_HARDCAP_OVERLINE "\x1b[53m" + +/* Colors (numeric) and attributes (bitwise) (tb_cell.fg, tb_cell.bg) */ +#define TB_DEFAULT 0x0000 +#define TB_BLACK 0x0001 +#define TB_RED 0x0002 +#define TB_GREEN 0x0003 +#define TB_YELLOW 0x0004 +#define TB_BLUE 0x0005 +#define TB_MAGENTA 0x0006 +#define TB_CYAN 0x0007 +#define TB_WHITE 0x0008 + +#if TB_OPT_ATTR_W == 16 +#define TB_BOLD 0x0100 +#define TB_UNDERLINE 0x0200 +#define TB_REVERSE 0x0400 +#define TB_ITALIC 0x0800 +#define TB_BLINK 0x1000 +#define TB_HI_BLACK 0x2000 +#define TB_BRIGHT 0x4000 +#define TB_DIM 0x8000 +#define TB_256_BLACK TB_HI_BLACK // TB_256_BLACK is deprecated +#else // 32 or 64 +#define TB_BOLD 0x01000000 +#define TB_UNDERLINE 0x02000000 +#define TB_REVERSE 0x04000000 +#define TB_ITALIC 0x08000000 +#define TB_BLINK 0x10000000 +#define TB_HI_BLACK 0x20000000 +#define TB_BRIGHT 0x40000000 +#define TB_DIM 0x80000000 +#define TB_TRUECOLOR_BOLD TB_BOLD // TB_TRUECOLOR_* is deprecated +#define TB_TRUECOLOR_UNDERLINE TB_UNDERLINE +#define TB_TRUECOLOR_REVERSE TB_REVERSE +#define TB_TRUECOLOR_ITALIC TB_ITALIC +#define TB_TRUECOLOR_BLINK TB_BLINK +#define TB_TRUECOLOR_BLACK TB_HI_BLACK +#endif + +#if TB_OPT_ATTR_W == 64 +#define TB_STRIKEOUT 0x0000000100000000 +#define TB_UNDERLINE_2 0x0000000200000000 +#define TB_OVERLINE 0x0000000400000000 +#define TB_INVISIBLE 0x0000000800000000 +#endif + +/* Event types (tb_event.type) */ +#define TB_EVENT_KEY 1 +#define TB_EVENT_RESIZE 2 +#define TB_EVENT_MOUSE 3 + +/* Key modifiers (bitwise) (tb_event.mod) */ +#define TB_MOD_ALT 1 +#define TB_MOD_CTRL 2 +#define TB_MOD_SHIFT 4 +#define TB_MOD_MOTION 8 + +/* Input modes (bitwise) (tb_set_input_mode) */ +#define TB_INPUT_CURRENT 0 +#define TB_INPUT_ESC 1 +#define TB_INPUT_ALT 2 +#define TB_INPUT_MOUSE 4 + +/* Output modes (tb_set_output_mode) */ +#define TB_OUTPUT_CURRENT 0 +#define TB_OUTPUT_NORMAL 1 +#define TB_OUTPUT_256 2 +#define TB_OUTPUT_216 3 +#define TB_OUTPUT_GRAYSCALE 4 +#if TB_OPT_ATTR_W >= 32 +#define TB_OUTPUT_TRUECOLOR 5 +#endif + +/* Common function return values unless otherwise noted. + * + * Library behavior is undefined after receiving TB_ERR_MEM. Callers may + * attempt reinitializing by freeing memory, invoking tb_shutdown, then + * tb_init. + */ +#define TB_OK 0 +#define TB_ERR -1 +#define TB_ERR_NEED_MORE -2 +#define TB_ERR_INIT_ALREADY -3 +#define TB_ERR_INIT_OPEN -4 +#define TB_ERR_MEM -5 +#define TB_ERR_NO_EVENT -6 +#define TB_ERR_NO_TERM -7 +#define TB_ERR_NOT_INIT -8 +#define TB_ERR_OUT_OF_BOUNDS -9 +#define TB_ERR_READ -10 +#define TB_ERR_RESIZE_IOCTL -11 +#define TB_ERR_RESIZE_PIPE -12 +#define TB_ERR_RESIZE_SIGACTION -13 +#define TB_ERR_POLL -14 +#define TB_ERR_TCGETATTR -15 +#define TB_ERR_TCSETATTR -16 +#define TB_ERR_UNSUPPORTED_TERM -17 +#define TB_ERR_RESIZE_WRITE -18 +#define TB_ERR_RESIZE_POLL -19 +#define TB_ERR_RESIZE_READ -20 +#define TB_ERR_RESIZE_SSCANF -21 +#define TB_ERR_CAP_COLLISION -22 + +#define TB_ERR_SELECT TB_ERR_POLL +#define TB_ERR_RESIZE_SELECT TB_ERR_RESIZE_POLL + +/* Function types to be used with tb_set_func() */ +#define TB_FUNC_EXTRACT_PRE 0 +#define TB_FUNC_EXTRACT_POST 1 + +/* Define this to set the size of the buffer used in tb_printf() + * and tb_sendf() + */ +#ifndef TB_OPT_PRINTF_BUF +#define TB_OPT_PRINTF_BUF 4096 +#endif + +/* Define this to set the size of the read buffer used when reading + * from the tty + */ +#ifndef TB_OPT_READ_BUF +#define TB_OPT_READ_BUF 64 +#endif + +/* Define this for limited back compat with termbox v1 */ +#ifdef TB_OPT_V1_COMPAT +#define tb_change_cell tb_set_cell +#define tb_put_cell(x, y, c) tb_set_cell((x), (y), (c)->ch, (c)->fg, (c)->bg) +#define tb_set_clear_attributes tb_set_clear_attrs +#define tb_select_input_mode tb_set_input_mode +#define tb_select_output_mode tb_set_output_mode +#endif + +/* Define these to swap in a different allocator */ +#ifndef tb_malloc +#define tb_malloc malloc +#define tb_realloc realloc +#define tb_free free +#endif + +#if TB_OPT_ATTR_W == 64 +typedef uint64_t uintattr_t; +#elif TB_OPT_ATTR_W == 32 +typedef uint32_t uintattr_t; +#else // 16 +typedef uint16_t uintattr_t; +#endif + +/* The terminal screen is represented as 2d array of cells. The structure is + * optimized for dealing with single-width (wcwidth()==1) Unicode codepoints, + * however some support for grapheme clusters (e.g., combining diacritical + * marks) and wide codepoints (e.g., Hiragana) is provided through ech, nech, + * cech via tb_set_cell_ex(). ech is only valid when nech>0, otherwise ch is + * used. + * + * For non-single-width codepoints, given N=wcwidth(ch)/wcswidth(ech): + * + * when N==0: termbox forces a single-width cell. Callers should avoid this + * if aiming to render text accurately. + * + * when N>1: termbox zeroes out the following N-1 cells and skips sending + * them to the tty. So, e.g., if the caller sets x=0,y=0 to an N==2 + * codepoint, the caller's next set should be at x=2,y=0. Anything + * set at x=1,y=0 will be ignored. If there are not enough columns + * remaining on the line to render N width, spaces are sent + * instead. + * + * See tb_present() for implementation. + */ +struct tb_cell { + uint32_t ch; /* a Unicode codepoint */ + uintattr_t fg; /* bitwise foreground attributes */ + uintattr_t bg; /* bitwise background attributes */ +#ifdef TB_OPT_EGC + uint32_t *ech; /* a grapheme cluster of Unicode codepoints, 0-terminated */ + size_t nech; /* num elements in ech, 0 means use ch instead of ech */ + size_t cech; /* num elements allocated for ech */ +#endif +}; + +/* An incoming event from the tty. + * + * Given the event type, the following fields are relevant: + * + * when TB_EVENT_KEY: (key XOR ch, one will be zero), mod. Note there is + * overlap between TB_MOD_CTRL and TB_KEY_CTRL_*. + * TB_MOD_CTRL and TB_MOD_SHIFT are only set as + * modifiers to TB_KEY_ARROW_*. + * + * when TB_EVENT_RESIZE: w, h + * + * when TB_EVENT_MOUSE: key (TB_KEY_MOUSE_*), x, y + */ +struct tb_event { + uint8_t type; /* one of TB_EVENT_* constants */ + uint8_t mod; /* bitwise TB_MOD_* constants */ + uint16_t key; /* one of TB_KEY_* constants */ + uint32_t ch; /* a Unicode codepoint */ + int32_t w; /* resize width */ + int32_t h; /* resize height */ + int32_t x; /* mouse x */ + int32_t y; /* mouse y */ +}; + +/* Initializes the termbox library. This function should be called before any + * other functions. tb_init() is equivalent to tb_init_file("/dev/tty"). After + * successful initialization, the library must be finalized using the + * tb_shutdown() function. + */ +int tb_init(void); +int tb_init_file(const char *path); +int tb_init_fd(int ttyfd); +int tb_init_rwfd(int rfd, int wfd); +int tb_shutdown(void); + +/* Returns the size of the internal back buffer (which is the same as terminal's + * window size in rows and columns). The internal buffer can be resized after + * tb_clear() or tb_present() function calls. Both dimensions have an + * unspecified negative value when called before tb_init() or after + * tb_shutdown(). + */ +int tb_width(void); +int tb_height(void); + +/* Clears the internal back buffer using TB_DEFAULT color or the + * color/attributes set by tb_set_clear_attrs() function. + */ +int tb_clear(void); +int tb_set_clear_attrs(uintattr_t fg, uintattr_t bg); + +/* Synchronizes the internal back buffer with the terminal by writing to tty. */ +int tb_present(void); + +/* Clears the internal front buffer effectively forcing a complete re-render of + * the back buffer to the tty. It is not necessary to call this under normal + * circumstances. */ +int tb_invalidate(void); + +/* Sets the position of the cursor. Upper-left character is (0, 0). */ +int tb_set_cursor(int cx, int cy); +int tb_hide_cursor(void); + +/* Set cell contents in the internal back buffer at the specified position. + * + * Use tb_set_cell_ex() for rendering grapheme clusters (e.g., combining + * diacritical marks). + * + * Function tb_set_cell(x, y, ch, fg, bg) is equivalent to + * tb_set_cell_ex(x, y, &ch, 1, fg, bg). + * + * Function tb_extend_cell() is a shortcut for appending 1 codepoint to + * cell->ech. + */ +int tb_set_cell(int x, int y, uint32_t ch, uintattr_t fg, uintattr_t bg); +int tb_set_cell_ex(int x, int y, uint32_t *ch, size_t nch, uintattr_t fg, + uintattr_t bg); +int tb_extend_cell(int x, int y, uint32_t ch); + +/* Sets the input mode. Termbox has two input modes: + * + * 1. TB_INPUT_ESC + * When escape (\x1b) is in the buffer and there's no match for an escape + * sequence, a key event for TB_KEY_ESC is returned. + * + * 2. TB_INPUT_ALT + * When escape (\x1b) is in the buffer and there's no match for an escape + * sequence, the next keyboard event is returned with a TB_MOD_ALT modifier. + * + * You can also apply TB_INPUT_MOUSE via bitwise OR operation to either of the + * modes (e.g., TB_INPUT_ESC | TB_INPUT_MOUSE) to receive TB_EVENT_MOUSE events. + * If none of the main two modes were set, but the mouse mode was, TB_INPUT_ESC + * mode is used. If for some reason you've decided to use + * (TB_INPUT_ESC | TB_INPUT_ALT) combination, it will behave as if only + * TB_INPUT_ESC was selected. + * + * If mode is TB_INPUT_CURRENT, the function returns the current input mode. + * + * The default input mode is TB_INPUT_ESC. + */ +int tb_set_input_mode(int mode); + +/* Sets the termbox output mode. Termbox has multiple output modes: + * + * 1. TB_OUTPUT_NORMAL => [0..8] + * + * This mode provides 8 different colors: + * TB_BLACK, TB_RED, TB_GREEN, TB_YELLOW, + * TB_BLUE, TB_MAGENTA, TB_CYAN, TB_WHITE + * + * Plus TB_DEFAULT which skips sending a color code (i.e., uses the + * terminal's default color). + * + * Colors (including TB_DEFAULT) may be bitwise OR'd with attributes: + * TB_BOLD, TB_UNDERLINE, TB_REVERSE, TB_ITALIC, TB_BLINK, TB_BRIGHT, + * TB_DIM + * + * The following style attributes are also available if compiled with + * TB_OPT_ATTR_W set to 64: + * TB_STRIKEOUT, TB_UNDERLINE_2, TB_OVERLINE, TB_INVISIBLE + * + * As in all modes, the value 0 is interpreted as TB_DEFAULT for + * convenience. + * + * Some notes: TB_REVERSE can be applied as either fg or bg attributes for + * the same effect. TB_BRIGHT can be applied to either fg or bg. The rest of + * the attributes apply to fg only and are ignored as bg attributes. + * + * Example usage: + * tb_set_cell(x, y, '@', TB_BLACK | TB_BOLD, TB_RED); + * + * 2. TB_OUTPUT_256 => [0..255] + TB_HI_BLACK + * + * In this mode you get 256 distinct colors (plus default): + * 0x00 (1): TB_DEFAULT + * TB_HI_BLACK (1): TB_BLACK in TB_OUTPUT_NORMAL + * 0x01..0x07 (7): the next 7 colors as in TB_OUTPUT_NORMAL + * 0x08..0x0f (8): bright versions of the above + * 0x10..0xe7 (216): 216 different colors + * 0xe8..0xff (24): 24 different shades of gray + * + * All TB_* style attributes except TB_BRIGHT may be bitwise OR'd as in + * TB_OUTPUT_NORMAL. + * + * Note TB_HI_BLACK must be used for black, as 0x00 represents default. + * + * 3. TB_OUTPUT_216 => [0..216] + * + * This mode supports the 216-color range of TB_OUTPUT_256 only, but you + * don't need to provide an offset: + * 0x00 (1): TB_DEFAULT + * 0x01..0xd8 (216): 216 different colors + * + * 4. TB_OUTPUT_GRAYSCALE => [0..24] + * + * This mode supports the 24-color range of TB_OUTPUT_256 only, but you + * don't need to provide an offset: + * 0x00 (1): TB_DEFAULT + * 0x01..0x18 (24): 24 different shades of gray + * + * 5. TB_OUTPUT_TRUECOLOR => [0x000000..0xffffff] + TB_HI_BLACK + * + * This mode provides 24-bit color on supported terminals. The format is + * 0xRRGGBB. + * + * All TB_* style attributes except TB_BRIGHT may be bitwise OR'd as in + * TB_OUTPUT_NORMAL. + * + * Note TB_HI_BLACK must be used for black, as 0x000000 represents default. + * + * If mode is TB_OUTPUT_CURRENT, the function returns the current output mode. + * + * The default output mode is TB_OUTPUT_NORMAL. + * + * To use the terminal default color (i.e., to not send an escape code), pass + * TB_DEFAULT. For convenience, the value 0 is interpreted as TB_DEFAULT in + * all modes. + * + * Note, cell attributes persist after switching output modes. Any translation + * between, for example, TB_OUTPUT_NORMAL's TB_RED and TB_OUTPUT_TRUECOLOR's + * 0xff0000 must be performed by the caller. Also note that cells previously + * rendered in one mode may persist unchanged until the front buffer is cleared + * (such as after a resize event) at which point it will be re-interpreted and + * flushed according to the current mode. Callers may invoke tb_invalidate if + * it is desirable to immediately re-interpret and flush the entire screen + * according to the current mode. + * + * Note, not all terminals support all output modes, especially beyond + * TB_OUTPUT_NORMAL. There is also no very reliable way to determine color + * support dynamically. If portability is desired, callers are recommended to + * use TB_OUTPUT_NORMAL or make output mode end-user configurable. The same + * advice applies to style attributes. + */ +int tb_set_output_mode(int mode); + +/* Wait for an event up to timeout_ms milliseconds and fill the event structure + * with it. If no event is available within the timeout period, TB_ERR_NO_EVENT + * is returned. On a resize event, the underlying select(2) call may be + * interrupted, yielding a return code of TB_ERR_POLL. In this case, you may + * check errno via tb_last_errno(). If it's EINTR, you can safely ignore that + * and call tb_peek_event() again. + */ +int tb_peek_event(struct tb_event *event, int timeout_ms); + +/* Same as tb_peek_event except no timeout. */ +int tb_poll_event(struct tb_event *event); + +/* Internal termbox FDs that can be used with poll() / select(). Must call + * tb_poll_event() / tb_peek_event() if activity is detected. */ +int tb_get_fds(int *ttyfd, int *resizefd); + +/* Print and printf functions. Specify param out_w to determine width of printed + * string. Incomplete trailing UTF-8 byte sequences are replaced with U+FFFD. + * For finer control, use tb_set_cell(). + */ +int tb_print(int x, int y, uintattr_t fg, uintattr_t bg, const char *str); +int tb_printf(int x, int y, uintattr_t fg, uintattr_t bg, const char *fmt, ...); +int tb_print_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, + const char *str); +int tb_printf_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, + const char *fmt, ...); + +/* Send raw bytes to terminal. */ +int tb_send(const char *buf, size_t nbuf); +int tb_sendf(const char *fmt, ...); + +/* Set custom functions. fn_type is one of TB_FUNC_* constants, fn is a + * compatible function pointer, or NULL to clear. + * + * TB_FUNC_EXTRACT_PRE: + * If specified, invoke this function BEFORE termbox tries to extract any + * escape sequences from the input buffer. + * + * TB_FUNC_EXTRACT_POST: + * If specified, invoke this function AFTER termbox tries (and fails) to + * extract any escape sequences from the input buffer. + */ +int tb_set_func(int fn_type, int (*fn)(struct tb_event *, size_t *)); + +/* Return byte length of codepoint given first byte of UTF-8 sequence (1-6). */ +int tb_utf8_char_length(char c); + +/* Convert UTF-8 null-terminated byte sequence to UTF-32 codepoint. + * + * If `c` is an empty C string, return 0. `out` is left unchanged. + * + * If a null byte is encountered in the middle of the codepoint, return a + * negative number indicating how many bytes were processed. `out` is left + * unchanged. + * + * Otherwise, return byte length of codepoint (1-6). + */ +int tb_utf8_char_to_unicode(uint32_t *out, const char *c); + +/* Convert UTF-32 codepoint to UTF-8 null-terminated byte sequence. + * + * `out` must be char[7] or greater. Return byte length of codepoint (1-6). + */ +int tb_utf8_unicode_to_char(char *out, uint32_t c); + +/* Library utility functions */ +int tb_last_errno(void); +const char *tb_strerror(int err); +struct tb_cell *tb_cell_buffer(void); +int tb_has_truecolor(void); +int tb_has_egc(void); +int tb_attr_width(void); +const char *tb_version(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __TERMBOX_H */ + +#ifdef TB_IMPL + +#define if_err_return(rv, expr) \ + if (((rv) = (expr)) != TB_OK) return (rv) +#define if_err_break(rv, expr) \ + if (((rv) = (expr)) != TB_OK) break +#define if_ok_return(rv, expr) \ + if (((rv) = (expr)) == TB_OK) return (rv) +#define if_ok_or_need_more_return(rv, expr) \ + if (((rv) = (expr)) == TB_OK || (rv) == TB_ERR_NEED_MORE) return (rv) + +#define send_literal(rv, a) \ + if_err_return((rv), bytebuf_nputs(&global.out, (a), sizeof(a) - 1)) + +#define send_num(rv, nbuf, n) \ + if_err_return((rv), \ + bytebuf_nputs(&global.out, (nbuf), convert_num((n), (nbuf)))) + +#define snprintf_or_return(rv, str, sz, fmt, ...) \ + do { \ + (rv) = snprintf((str), (sz), (fmt), __VA_ARGS__); \ + if ((rv) < 0 || (rv) >= (int)(sz)) return TB_ERR; \ + } while (0) + +#define if_not_init_return() \ + if (!global.initialized) return TB_ERR_NOT_INIT + +struct bytebuf_t { + char *buf; + size_t len; + size_t cap; +}; + +struct cellbuf_t { + int width; + int height; + struct tb_cell *cells; +}; + +struct cap_trie_t { + char c; + struct cap_trie_t *children; + size_t nchildren; + int is_leaf; + uint16_t key; + uint8_t mod; +}; + +struct tb_global_t { + int ttyfd; + int rfd; + int wfd; + int ttyfd_open; + int resize_pipefd[2]; + int width; + int height; + int cursor_x; + int cursor_y; + int last_x; + int last_y; + uintattr_t fg; + uintattr_t bg; + uintattr_t last_fg; + uintattr_t last_bg; + int input_mode; + int output_mode; + char *terminfo; + size_t nterminfo; + const char *caps[TB_CAP__COUNT]; + struct cap_trie_t cap_trie; + struct bytebuf_t in; + struct bytebuf_t out; + struct cellbuf_t back; + struct cellbuf_t front; + struct termios orig_tios; + int has_orig_tios; + int last_errno; + int initialized; + int (*fn_extract_esc_pre)(struct tb_event *, size_t *); + int (*fn_extract_esc_post)(struct tb_event *, size_t *); + char errbuf[1024]; +}; + +static struct tb_global_t global = {0}; + +/* BEGIN codegen c */ +/* Produced by ./codegen.sh on Thu, 13 Jul 2023 05:46:13 +0000 */ + +static const int16_t terminfo_cap_indexes[] = { + 66, // kf1 (TB_CAP_F1) + 68, // kf2 (TB_CAP_F2) + 69, // kf3 (TB_CAP_F3) + 70, // kf4 (TB_CAP_F4) + 71, // kf5 (TB_CAP_F5) + 72, // kf6 (TB_CAP_F6) + 73, // kf7 (TB_CAP_F7) + 74, // kf8 (TB_CAP_F8) + 75, // kf9 (TB_CAP_F9) + 67, // kf10 (TB_CAP_F10) + 216, // kf11 (TB_CAP_F11) + 217, // kf12 (TB_CAP_F12) + 77, // kich1 (TB_CAP_INSERT) + 59, // kdch1 (TB_CAP_DELETE) + 76, // khome (TB_CAP_HOME) + 164, // kend (TB_CAP_END) + 82, // kpp (TB_CAP_PGUP) + 81, // knp (TB_CAP_PGDN) + 87, // kcuu1 (TB_CAP_ARROW_UP) + 61, // kcud1 (TB_CAP_ARROW_DOWN) + 79, // kcub1 (TB_CAP_ARROW_LEFT) + 83, // kcuf1 (TB_CAP_ARROW_RIGHT) + 148, // kcbt (TB_CAP_BACK_TAB) + 28, // smcup (TB_CAP_ENTER_CA) + 40, // rmcup (TB_CAP_EXIT_CA) + 16, // cnorm (TB_CAP_SHOW_CURSOR) + 13, // civis (TB_CAP_HIDE_CURSOR) + 5, // clear (TB_CAP_CLEAR_SCREEN) + 39, // sgr0 (TB_CAP_SGR0) + 36, // smul (TB_CAP_UNDERLINE) + 27, // bold (TB_CAP_BOLD) + 26, // blink (TB_CAP_BLINK) + 311, // sitm (TB_CAP_ITALIC) + 34, // rev (TB_CAP_REVERSE) + 89, // smkx (TB_CAP_ENTER_KEYPAD) + 88, // rmkx (TB_CAP_EXIT_KEYPAD) + 30, // dim (TB_CAP_DIM) + 32, // invis (TB_CAP_INVISIBLE) +}; + +// xterm +static const char *xterm_caps[] = { + "\033OP", // kf1 (TB_CAP_F1) + "\033OQ", // kf2 (TB_CAP_F2) + "\033OR", // kf3 (TB_CAP_F3) + "\033OS", // kf4 (TB_CAP_F4) + "\033[15~", // kf5 (TB_CAP_F5) + "\033[17~", // kf6 (TB_CAP_F6) + "\033[18~", // kf7 (TB_CAP_F7) + "\033[19~", // kf8 (TB_CAP_F8) + "\033[20~", // kf9 (TB_CAP_F9) + "\033[21~", // kf10 (TB_CAP_F10) + "\033[23~", // kf11 (TB_CAP_F11) + "\033[24~", // kf12 (TB_CAP_F12) + "\033[2~", // kich1 (TB_CAP_INSERT) + "\033[3~", // kdch1 (TB_CAP_DELETE) + "\033OH", // khome (TB_CAP_HOME) + "\033OF", // kend (TB_CAP_END) + "\033[5~", // kpp (TB_CAP_PGUP) + "\033[6~", // knp (TB_CAP_PGDN) + "\033OA", // kcuu1 (TB_CAP_ARROW_UP) + "\033OB", // kcud1 (TB_CAP_ARROW_DOWN) + "\033OD", // kcub1 (TB_CAP_ARROW_LEFT) + "\033OC", // kcuf1 (TB_CAP_ARROW_RIGHT) + "\033[Z", // kcbt (TB_CAP_BACK_TAB) + "\033[?1049h\033[22;0;0t", // smcup (TB_CAP_ENTER_CA) + "\033[?1049l\033[23;0;0t", // rmcup (TB_CAP_EXIT_CA) + "\033[?12l\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) + "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) + "\033[H\033[2J", // clear (TB_CAP_CLEAR_SCREEN) + "\033(B\033[m", // sgr0 (TB_CAP_SGR0) + "\033[4m", // smul (TB_CAP_UNDERLINE) + "\033[1m", // bold (TB_CAP_BOLD) + "\033[5m", // blink (TB_CAP_BLINK) + "\033[3m", // sitm (TB_CAP_ITALIC) + "\033[7m", // rev (TB_CAP_REVERSE) + "\033[?1h\033=", // smkx (TB_CAP_ENTER_KEYPAD) + "\033[?1l\033>", // rmkx (TB_CAP_EXIT_KEYPAD) + "\033[2m", // dim (TB_CAP_DIM) + "\033[8m", // invis (TB_CAP_INVISIBLE) +}; + +// linux +static const char *linux_caps[] = { + "\033[[A", // kf1 (TB_CAP_F1) + "\033[[B", // kf2 (TB_CAP_F2) + "\033[[C", // kf3 (TB_CAP_F3) + "\033[[D", // kf4 (TB_CAP_F4) + "\033[[E", // kf5 (TB_CAP_F5) + "\033[17~", // kf6 (TB_CAP_F6) + "\033[18~", // kf7 (TB_CAP_F7) + "\033[19~", // kf8 (TB_CAP_F8) + "\033[20~", // kf9 (TB_CAP_F9) + "\033[21~", // kf10 (TB_CAP_F10) + "\033[23~", // kf11 (TB_CAP_F11) + "\033[24~", // kf12 (TB_CAP_F12) + "\033[2~", // kich1 (TB_CAP_INSERT) + "\033[3~", // kdch1 (TB_CAP_DELETE) + "\033[1~", // khome (TB_CAP_HOME) + "\033[4~", // kend (TB_CAP_END) + "\033[5~", // kpp (TB_CAP_PGUP) + "\033[6~", // knp (TB_CAP_PGDN) + "\033[A", // kcuu1 (TB_CAP_ARROW_UP) + "\033[B", // kcud1 (TB_CAP_ARROW_DOWN) + "\033[D", // kcub1 (TB_CAP_ARROW_LEFT) + "\033[C", // kcuf1 (TB_CAP_ARROW_RIGHT) + "\033\011", // kcbt (TB_CAP_BACK_TAB) + "", // smcup (TB_CAP_ENTER_CA) + "", // rmcup (TB_CAP_EXIT_CA) + "\033[?25h\033[?0c", // cnorm (TB_CAP_SHOW_CURSOR) + "\033[?25l\033[?1c", // civis (TB_CAP_HIDE_CURSOR) + "\033[H\033[J", // clear (TB_CAP_CLEAR_SCREEN) + "\033[m\017", // sgr0 (TB_CAP_SGR0) + "\033[4m", // smul (TB_CAP_UNDERLINE) + "\033[1m", // bold (TB_CAP_BOLD) + "\033[5m", // blink (TB_CAP_BLINK) + "", // sitm (TB_CAP_ITALIC) + "\033[7m", // rev (TB_CAP_REVERSE) + "", // smkx (TB_CAP_ENTER_KEYPAD) + "", // rmkx (TB_CAP_EXIT_KEYPAD) + "\033[2m", // dim (TB_CAP_DIM) + "", // invis (TB_CAP_INVISIBLE) +}; + +// screen +static const char *screen_caps[] = { + "\033OP", // kf1 (TB_CAP_F1) + "\033OQ", // kf2 (TB_CAP_F2) + "\033OR", // kf3 (TB_CAP_F3) + "\033OS", // kf4 (TB_CAP_F4) + "\033[15~", // kf5 (TB_CAP_F5) + "\033[17~", // kf6 (TB_CAP_F6) + "\033[18~", // kf7 (TB_CAP_F7) + "\033[19~", // kf8 (TB_CAP_F8) + "\033[20~", // kf9 (TB_CAP_F9) + "\033[21~", // kf10 (TB_CAP_F10) + "\033[23~", // kf11 (TB_CAP_F11) + "\033[24~", // kf12 (TB_CAP_F12) + "\033[2~", // kich1 (TB_CAP_INSERT) + "\033[3~", // kdch1 (TB_CAP_DELETE) + "\033[1~", // khome (TB_CAP_HOME) + "\033[4~", // kend (TB_CAP_END) + "\033[5~", // kpp (TB_CAP_PGUP) + "\033[6~", // knp (TB_CAP_PGDN) + "\033OA", // kcuu1 (TB_CAP_ARROW_UP) + "\033OB", // kcud1 (TB_CAP_ARROW_DOWN) + "\033OD", // kcub1 (TB_CAP_ARROW_LEFT) + "\033OC", // kcuf1 (TB_CAP_ARROW_RIGHT) + "\033[Z", // kcbt (TB_CAP_BACK_TAB) + "\033[?1049h", // smcup (TB_CAP_ENTER_CA) + "\033[?1049l", // rmcup (TB_CAP_EXIT_CA) + "\033[34h\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) + "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) + "\033[H\033[J", // clear (TB_CAP_CLEAR_SCREEN) + "\033[m\017", // sgr0 (TB_CAP_SGR0) + "\033[4m", // smul (TB_CAP_UNDERLINE) + "\033[1m", // bold (TB_CAP_BOLD) + "\033[5m", // blink (TB_CAP_BLINK) + "", // sitm (TB_CAP_ITALIC) + "\033[7m", // rev (TB_CAP_REVERSE) + "\033[?1h\033=", // smkx (TB_CAP_ENTER_KEYPAD) + "\033[?1l\033>", // rmkx (TB_CAP_EXIT_KEYPAD) + "\033[2m", // dim (TB_CAP_DIM) + "", // invis (TB_CAP_INVISIBLE) +}; + +// rxvt-256color +static const char *rxvt_256color_caps[] = { + "\033[11~", // kf1 (TB_CAP_F1) + "\033[12~", // kf2 (TB_CAP_F2) + "\033[13~", // kf3 (TB_CAP_F3) + "\033[14~", // kf4 (TB_CAP_F4) + "\033[15~", // kf5 (TB_CAP_F5) + "\033[17~", // kf6 (TB_CAP_F6) + "\033[18~", // kf7 (TB_CAP_F7) + "\033[19~", // kf8 (TB_CAP_F8) + "\033[20~", // kf9 (TB_CAP_F9) + "\033[21~", // kf10 (TB_CAP_F10) + "\033[23~", // kf11 (TB_CAP_F11) + "\033[24~", // kf12 (TB_CAP_F12) + "\033[2~", // kich1 (TB_CAP_INSERT) + "\033[3~", // kdch1 (TB_CAP_DELETE) + "\033[7~", // khome (TB_CAP_HOME) + "\033[8~", // kend (TB_CAP_END) + "\033[5~", // kpp (TB_CAP_PGUP) + "\033[6~", // knp (TB_CAP_PGDN) + "\033[A", // kcuu1 (TB_CAP_ARROW_UP) + "\033[B", // kcud1 (TB_CAP_ARROW_DOWN) + "\033[D", // kcub1 (TB_CAP_ARROW_LEFT) + "\033[C", // kcuf1 (TB_CAP_ARROW_RIGHT) + "\033[Z", // kcbt (TB_CAP_BACK_TAB) + "\0337\033[?47h", // smcup (TB_CAP_ENTER_CA) + "\033[2J\033[?47l\0338", // rmcup (TB_CAP_EXIT_CA) + "\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) + "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) + "\033[H\033[2J", // clear (TB_CAP_CLEAR_SCREEN) + "\033[m\017", // sgr0 (TB_CAP_SGR0) + "\033[4m", // smul (TB_CAP_UNDERLINE) + "\033[1m", // bold (TB_CAP_BOLD) + "\033[5m", // blink (TB_CAP_BLINK) + "", // sitm (TB_CAP_ITALIC) + "\033[7m", // rev (TB_CAP_REVERSE) + "\033=", // smkx (TB_CAP_ENTER_KEYPAD) + "\033>", // rmkx (TB_CAP_EXIT_KEYPAD) + "", // dim (TB_CAP_DIM) + "", // invis (TB_CAP_INVISIBLE) +}; + +// rxvt-unicode +static const char *rxvt_unicode_caps[] = { + "\033[11~", // kf1 (TB_CAP_F1) + "\033[12~", // kf2 (TB_CAP_F2) + "\033[13~", // kf3 (TB_CAP_F3) + "\033[14~", // kf4 (TB_CAP_F4) + "\033[15~", // kf5 (TB_CAP_F5) + "\033[17~", // kf6 (TB_CAP_F6) + "\033[18~", // kf7 (TB_CAP_F7) + "\033[19~", // kf8 (TB_CAP_F8) + "\033[20~", // kf9 (TB_CAP_F9) + "\033[21~", // kf10 (TB_CAP_F10) + "\033[23~", // kf11 (TB_CAP_F11) + "\033[24~", // kf12 (TB_CAP_F12) + "\033[2~", // kich1 (TB_CAP_INSERT) + "\033[3~", // kdch1 (TB_CAP_DELETE) + "\033[7~", // khome (TB_CAP_HOME) + "\033[8~", // kend (TB_CAP_END) + "\033[5~", // kpp (TB_CAP_PGUP) + "\033[6~", // knp (TB_CAP_PGDN) + "\033[A", // kcuu1 (TB_CAP_ARROW_UP) + "\033[B", // kcud1 (TB_CAP_ARROW_DOWN) + "\033[D", // kcub1 (TB_CAP_ARROW_LEFT) + "\033[C", // kcuf1 (TB_CAP_ARROW_RIGHT) + "\033[Z", // kcbt (TB_CAP_BACK_TAB) + "\033[?1049h", // smcup (TB_CAP_ENTER_CA) + "\033[r\033[?1049l", // rmcup (TB_CAP_EXIT_CA) + "\033[?12l\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) + "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) + "\033[H\033[2J", // clear (TB_CAP_CLEAR_SCREEN) + "\033[m\033(B", // sgr0 (TB_CAP_SGR0) + "\033[4m", // smul (TB_CAP_UNDERLINE) + "\033[1m", // bold (TB_CAP_BOLD) + "\033[5m", // blink (TB_CAP_BLINK) + "\033[3m", // sitm (TB_CAP_ITALIC) + "\033[7m", // rev (TB_CAP_REVERSE) + "\033=", // smkx (TB_CAP_ENTER_KEYPAD) + "\033>", // rmkx (TB_CAP_EXIT_KEYPAD) + "", // dim (TB_CAP_DIM) + "", // invis (TB_CAP_INVISIBLE) +}; + +// Eterm +static const char *eterm_caps[] = { + "\033[11~", // kf1 (TB_CAP_F1) + "\033[12~", // kf2 (TB_CAP_F2) + "\033[13~", // kf3 (TB_CAP_F3) + "\033[14~", // kf4 (TB_CAP_F4) + "\033[15~", // kf5 (TB_CAP_F5) + "\033[17~", // kf6 (TB_CAP_F6) + "\033[18~", // kf7 (TB_CAP_F7) + "\033[19~", // kf8 (TB_CAP_F8) + "\033[20~", // kf9 (TB_CAP_F9) + "\033[21~", // kf10 (TB_CAP_F10) + "\033[23~", // kf11 (TB_CAP_F11) + "\033[24~", // kf12 (TB_CAP_F12) + "\033[2~", // kich1 (TB_CAP_INSERT) + "\033[3~", // kdch1 (TB_CAP_DELETE) + "\033[7~", // khome (TB_CAP_HOME) + "\033[8~", // kend (TB_CAP_END) + "\033[5~", // kpp (TB_CAP_PGUP) + "\033[6~", // knp (TB_CAP_PGDN) + "\033[A", // kcuu1 (TB_CAP_ARROW_UP) + "\033[B", // kcud1 (TB_CAP_ARROW_DOWN) + "\033[D", // kcub1 (TB_CAP_ARROW_LEFT) + "\033[C", // kcuf1 (TB_CAP_ARROW_RIGHT) + "", // kcbt (TB_CAP_BACK_TAB) + "\0337\033[?47h", // smcup (TB_CAP_ENTER_CA) + "\033[2J\033[?47l\0338", // rmcup (TB_CAP_EXIT_CA) + "\033[?25h", // cnorm (TB_CAP_SHOW_CURSOR) + "\033[?25l", // civis (TB_CAP_HIDE_CURSOR) + "\033[H\033[2J", // clear (TB_CAP_CLEAR_SCREEN) + "\033[m\017", // sgr0 (TB_CAP_SGR0) + "\033[4m", // smul (TB_CAP_UNDERLINE) + "\033[1m", // bold (TB_CAP_BOLD) + "\033[5m", // blink (TB_CAP_BLINK) + "", // sitm (TB_CAP_ITALIC) + "\033[7m", // rev (TB_CAP_REVERSE) + "", // smkx (TB_CAP_ENTER_KEYPAD) + "", // rmkx (TB_CAP_EXIT_KEYPAD) + "", // dim (TB_CAP_DIM) + "", // invis (TB_CAP_INVISIBLE) +}; + +static struct { + const char *name; + const char **caps; + const char *alias; +} builtin_terms[] = { + {"xterm", xterm_caps, "" }, + {"linux", linux_caps, "" }, + {"screen", screen_caps, "tmux"}, + {"rxvt-256color", rxvt_256color_caps, "" }, + {"rxvt-unicode", rxvt_unicode_caps, "rxvt"}, + {"Eterm", eterm_caps, "" }, + {NULL, NULL, NULL }, +}; + +/* END codegen c */ + +static struct { + const char *cap; + const uint16_t key; + const uint8_t mod; +} builtin_mod_caps[] = { + // xterm arrows + {"\x1b[1;2A", TB_KEY_ARROW_UP, TB_MOD_SHIFT }, + {"\x1b[1;3A", TB_KEY_ARROW_UP, TB_MOD_ALT }, + {"\x1b[1;4A", TB_KEY_ARROW_UP, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[1;5A", TB_KEY_ARROW_UP, TB_MOD_CTRL }, + {"\x1b[1;6A", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[1;7A", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[1;8A", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[1;2B", TB_KEY_ARROW_DOWN, TB_MOD_SHIFT }, + {"\x1b[1;3B", TB_KEY_ARROW_DOWN, TB_MOD_ALT }, + {"\x1b[1;4B", TB_KEY_ARROW_DOWN, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[1;5B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL }, + {"\x1b[1;6B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[1;7B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[1;8B", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[1;2C", TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT }, + {"\x1b[1;3C", TB_KEY_ARROW_RIGHT, TB_MOD_ALT }, + {"\x1b[1;4C", TB_KEY_ARROW_RIGHT, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[1;5C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL }, + {"\x1b[1;6C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[1;7C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[1;8C", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[1;2D", TB_KEY_ARROW_LEFT, TB_MOD_SHIFT }, + {"\x1b[1;3D", TB_KEY_ARROW_LEFT, TB_MOD_ALT }, + {"\x1b[1;4D", TB_KEY_ARROW_LEFT, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[1;5D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL }, + {"\x1b[1;6D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[1;7D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[1;8D", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + // xterm keys + {"\x1b[1;2H", TB_KEY_HOME, TB_MOD_SHIFT }, + {"\x1b[1;3H", TB_KEY_HOME, TB_MOD_ALT }, + {"\x1b[1;4H", TB_KEY_HOME, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[1;5H", TB_KEY_HOME, TB_MOD_CTRL }, + {"\x1b[1;6H", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[1;7H", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[1;8H", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[1;2F", TB_KEY_END, TB_MOD_SHIFT }, + {"\x1b[1;3F", TB_KEY_END, TB_MOD_ALT }, + {"\x1b[1;4F", TB_KEY_END, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[1;5F", TB_KEY_END, TB_MOD_CTRL }, + {"\x1b[1;6F", TB_KEY_END, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[1;7F", TB_KEY_END, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[1;8F", TB_KEY_END, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[2;2~", TB_KEY_INSERT, TB_MOD_SHIFT }, + {"\x1b[2;3~", TB_KEY_INSERT, TB_MOD_ALT }, + {"\x1b[2;4~", TB_KEY_INSERT, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[2;5~", TB_KEY_INSERT, TB_MOD_CTRL }, + {"\x1b[2;6~", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[2;7~", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[2;8~", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[3;2~", TB_KEY_DELETE, TB_MOD_SHIFT }, + {"\x1b[3;3~", TB_KEY_DELETE, TB_MOD_ALT }, + {"\x1b[3;4~", TB_KEY_DELETE, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[3;5~", TB_KEY_DELETE, TB_MOD_CTRL }, + {"\x1b[3;6~", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[3;7~", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[3;8~", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[5;2~", TB_KEY_PGUP, TB_MOD_SHIFT }, + {"\x1b[5;3~", TB_KEY_PGUP, TB_MOD_ALT }, + {"\x1b[5;4~", TB_KEY_PGUP, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[5;5~", TB_KEY_PGUP, TB_MOD_CTRL }, + {"\x1b[5;6~", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[5;7~", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[5;8~", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[6;2~", TB_KEY_PGDN, TB_MOD_SHIFT }, + {"\x1b[6;3~", TB_KEY_PGDN, TB_MOD_ALT }, + {"\x1b[6;4~", TB_KEY_PGDN, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[6;5~", TB_KEY_PGDN, TB_MOD_CTRL }, + {"\x1b[6;6~", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[6;7~", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[6;8~", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[1;2P", TB_KEY_F1, TB_MOD_SHIFT }, + {"\x1b[1;3P", TB_KEY_F1, TB_MOD_ALT }, + {"\x1b[1;4P", TB_KEY_F1, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[1;5P", TB_KEY_F1, TB_MOD_CTRL }, + {"\x1b[1;6P", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[1;7P", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[1;8P", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[1;2Q", TB_KEY_F2, TB_MOD_SHIFT }, + {"\x1b[1;3Q", TB_KEY_F2, TB_MOD_ALT }, + {"\x1b[1;4Q", TB_KEY_F2, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[1;5Q", TB_KEY_F2, TB_MOD_CTRL }, + {"\x1b[1;6Q", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[1;7Q", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[1;8Q", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[1;2R", TB_KEY_F3, TB_MOD_SHIFT }, + {"\x1b[1;3R", TB_KEY_F3, TB_MOD_ALT }, + {"\x1b[1;4R", TB_KEY_F3, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[1;5R", TB_KEY_F3, TB_MOD_CTRL }, + {"\x1b[1;6R", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[1;7R", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[1;8R", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[1;2S", TB_KEY_F4, TB_MOD_SHIFT }, + {"\x1b[1;3S", TB_KEY_F4, TB_MOD_ALT }, + {"\x1b[1;4S", TB_KEY_F4, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[1;5S", TB_KEY_F4, TB_MOD_CTRL }, + {"\x1b[1;6S", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[1;7S", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[1;8S", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[15;2~", TB_KEY_F5, TB_MOD_SHIFT }, + {"\x1b[15;3~", TB_KEY_F5, TB_MOD_ALT }, + {"\x1b[15;4~", TB_KEY_F5, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[15;5~", TB_KEY_F5, TB_MOD_CTRL }, + {"\x1b[15;6~", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[15;7~", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[15;8~", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[17;2~", TB_KEY_F6, TB_MOD_SHIFT }, + {"\x1b[17;3~", TB_KEY_F6, TB_MOD_ALT }, + {"\x1b[17;4~", TB_KEY_F6, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[17;5~", TB_KEY_F6, TB_MOD_CTRL }, + {"\x1b[17;6~", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[17;7~", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[17;8~", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[18;2~", TB_KEY_F7, TB_MOD_SHIFT }, + {"\x1b[18;3~", TB_KEY_F7, TB_MOD_ALT }, + {"\x1b[18;4~", TB_KEY_F7, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[18;5~", TB_KEY_F7, TB_MOD_CTRL }, + {"\x1b[18;6~", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[18;7~", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[18;8~", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[19;2~", TB_KEY_F8, TB_MOD_SHIFT }, + {"\x1b[19;3~", TB_KEY_F8, TB_MOD_ALT }, + {"\x1b[19;4~", TB_KEY_F8, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[19;5~", TB_KEY_F8, TB_MOD_CTRL }, + {"\x1b[19;6~", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[19;7~", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[19;8~", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[20;2~", TB_KEY_F9, TB_MOD_SHIFT }, + {"\x1b[20;3~", TB_KEY_F9, TB_MOD_ALT }, + {"\x1b[20;4~", TB_KEY_F9, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[20;5~", TB_KEY_F9, TB_MOD_CTRL }, + {"\x1b[20;6~", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[20;7~", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[20;8~", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[21;2~", TB_KEY_F10, TB_MOD_SHIFT }, + {"\x1b[21;3~", TB_KEY_F10, TB_MOD_ALT }, + {"\x1b[21;4~", TB_KEY_F10, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[21;5~", TB_KEY_F10, TB_MOD_CTRL }, + {"\x1b[21;6~", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[21;7~", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[21;8~", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[23;2~", TB_KEY_F11, TB_MOD_SHIFT }, + {"\x1b[23;3~", TB_KEY_F11, TB_MOD_ALT }, + {"\x1b[23;4~", TB_KEY_F11, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[23;5~", TB_KEY_F11, TB_MOD_CTRL }, + {"\x1b[23;6~", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[23;7~", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[23;8~", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b[24;2~", TB_KEY_F12, TB_MOD_SHIFT }, + {"\x1b[24;3~", TB_KEY_F12, TB_MOD_ALT }, + {"\x1b[24;4~", TB_KEY_F12, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[24;5~", TB_KEY_F12, TB_MOD_CTRL }, + {"\x1b[24;6~", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[24;7~", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b[24;8~", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + // rxvt arrows + {"\x1b[a", TB_KEY_ARROW_UP, TB_MOD_SHIFT }, + {"\x1b\x1b[A", TB_KEY_ARROW_UP, TB_MOD_ALT }, + {"\x1b\x1b[a", TB_KEY_ARROW_UP, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1bOa", TB_KEY_ARROW_UP, TB_MOD_CTRL }, + {"\x1b\x1bOa", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT }, + + {"\x1b[b", TB_KEY_ARROW_DOWN, TB_MOD_SHIFT }, + {"\x1b\x1b[B", TB_KEY_ARROW_DOWN, TB_MOD_ALT }, + {"\x1b\x1b[b", TB_KEY_ARROW_DOWN, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1bOb", TB_KEY_ARROW_DOWN, TB_MOD_CTRL }, + {"\x1b\x1bOb", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT }, + + {"\x1b[c", TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT }, + {"\x1b\x1b[C", TB_KEY_ARROW_RIGHT, TB_MOD_ALT }, + {"\x1b\x1b[c", TB_KEY_ARROW_RIGHT, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1bOc", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL }, + {"\x1b\x1bOc", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT }, + + {"\x1b[d", TB_KEY_ARROW_LEFT, TB_MOD_SHIFT }, + {"\x1b\x1b[D", TB_KEY_ARROW_LEFT, TB_MOD_ALT }, + {"\x1b\x1b[d", TB_KEY_ARROW_LEFT, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1bOd", TB_KEY_ARROW_LEFT, TB_MOD_CTRL }, + {"\x1b\x1bOd", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT }, + + // rxvt keys + {"\x1b[7$", TB_KEY_HOME, TB_MOD_SHIFT }, + {"\x1b\x1b[7~", TB_KEY_HOME, TB_MOD_ALT }, + {"\x1b\x1b[7$", TB_KEY_HOME, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[7^", TB_KEY_HOME, TB_MOD_CTRL }, + {"\x1b[7@", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b\x1b[7^", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[7@", TB_KEY_HOME, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + + {"\x1b\x1b[8~", TB_KEY_END, TB_MOD_ALT }, + {"\x1b\x1b[8$", TB_KEY_END, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[8^", TB_KEY_END, TB_MOD_CTRL }, + {"\x1b\x1b[8^", TB_KEY_END, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[8@", TB_KEY_END, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[8@", TB_KEY_END, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[8$", TB_KEY_END, TB_MOD_SHIFT }, + + {"\x1b\x1b[2~", TB_KEY_INSERT, TB_MOD_ALT }, + {"\x1b\x1b[2$", TB_KEY_INSERT, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[2^", TB_KEY_INSERT, TB_MOD_CTRL }, + {"\x1b\x1b[2^", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[2@", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[2@", TB_KEY_INSERT, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[2$", TB_KEY_INSERT, TB_MOD_SHIFT }, + + {"\x1b\x1b[3~", TB_KEY_DELETE, TB_MOD_ALT }, + {"\x1b\x1b[3$", TB_KEY_DELETE, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[3^", TB_KEY_DELETE, TB_MOD_CTRL }, + {"\x1b\x1b[3^", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[3@", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[3@", TB_KEY_DELETE, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[3$", TB_KEY_DELETE, TB_MOD_SHIFT }, + + {"\x1b\x1b[5~", TB_KEY_PGUP, TB_MOD_ALT }, + {"\x1b\x1b[5$", TB_KEY_PGUP, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[5^", TB_KEY_PGUP, TB_MOD_CTRL }, + {"\x1b\x1b[5^", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[5@", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[5@", TB_KEY_PGUP, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[5$", TB_KEY_PGUP, TB_MOD_SHIFT }, + + {"\x1b\x1b[6~", TB_KEY_PGDN, TB_MOD_ALT }, + {"\x1b\x1b[6$", TB_KEY_PGDN, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[6^", TB_KEY_PGDN, TB_MOD_CTRL }, + {"\x1b\x1b[6^", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[6@", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[6@", TB_KEY_PGDN, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[6$", TB_KEY_PGDN, TB_MOD_SHIFT }, + + {"\x1b\x1b[11~", TB_KEY_F1, TB_MOD_ALT }, + {"\x1b\x1b[23~", TB_KEY_F1, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[11^", TB_KEY_F1, TB_MOD_CTRL }, + {"\x1b\x1b[11^", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[23^", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[23^", TB_KEY_F1, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[23~", TB_KEY_F1, TB_MOD_SHIFT }, + + {"\x1b\x1b[12~", TB_KEY_F2, TB_MOD_ALT }, + {"\x1b\x1b[24~", TB_KEY_F2, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[12^", TB_KEY_F2, TB_MOD_CTRL }, + {"\x1b\x1b[12^", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[24^", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[24^", TB_KEY_F2, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[24~", TB_KEY_F2, TB_MOD_SHIFT }, + + {"\x1b\x1b[13~", TB_KEY_F3, TB_MOD_ALT }, + {"\x1b\x1b[25~", TB_KEY_F3, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[13^", TB_KEY_F3, TB_MOD_CTRL }, + {"\x1b\x1b[13^", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[25^", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[25^", TB_KEY_F3, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[25~", TB_KEY_F3, TB_MOD_SHIFT }, + + {"\x1b\x1b[14~", TB_KEY_F4, TB_MOD_ALT }, + {"\x1b\x1b[26~", TB_KEY_F4, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[14^", TB_KEY_F4, TB_MOD_CTRL }, + {"\x1b\x1b[14^", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[26^", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[26^", TB_KEY_F4, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[26~", TB_KEY_F4, TB_MOD_SHIFT }, + + {"\x1b\x1b[15~", TB_KEY_F5, TB_MOD_ALT }, + {"\x1b\x1b[28~", TB_KEY_F5, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[15^", TB_KEY_F5, TB_MOD_CTRL }, + {"\x1b\x1b[15^", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[28^", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[28^", TB_KEY_F5, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[28~", TB_KEY_F5, TB_MOD_SHIFT }, + + {"\x1b\x1b[17~", TB_KEY_F6, TB_MOD_ALT }, + {"\x1b\x1b[29~", TB_KEY_F6, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[17^", TB_KEY_F6, TB_MOD_CTRL }, + {"\x1b\x1b[17^", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[29^", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[29^", TB_KEY_F6, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[29~", TB_KEY_F6, TB_MOD_SHIFT }, + + {"\x1b\x1b[18~", TB_KEY_F7, TB_MOD_ALT }, + {"\x1b\x1b[31~", TB_KEY_F7, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[18^", TB_KEY_F7, TB_MOD_CTRL }, + {"\x1b\x1b[18^", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[31^", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[31^", TB_KEY_F7, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[31~", TB_KEY_F7, TB_MOD_SHIFT }, + + {"\x1b\x1b[19~", TB_KEY_F8, TB_MOD_ALT }, + {"\x1b\x1b[32~", TB_KEY_F8, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[19^", TB_KEY_F8, TB_MOD_CTRL }, + {"\x1b\x1b[19^", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[32^", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[32^", TB_KEY_F8, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[32~", TB_KEY_F8, TB_MOD_SHIFT }, + + {"\x1b\x1b[20~", TB_KEY_F9, TB_MOD_ALT }, + {"\x1b\x1b[33~", TB_KEY_F9, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[20^", TB_KEY_F9, TB_MOD_CTRL }, + {"\x1b\x1b[20^", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[33^", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[33^", TB_KEY_F9, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[33~", TB_KEY_F9, TB_MOD_SHIFT }, + + {"\x1b\x1b[21~", TB_KEY_F10, TB_MOD_ALT }, + {"\x1b\x1b[34~", TB_KEY_F10, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[21^", TB_KEY_F10, TB_MOD_CTRL }, + {"\x1b\x1b[21^", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[34^", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[34^", TB_KEY_F10, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[34~", TB_KEY_F10, TB_MOD_SHIFT }, + + {"\x1b\x1b[23~", TB_KEY_F11, TB_MOD_ALT }, + {"\x1b\x1b[23$", TB_KEY_F11, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[23^", TB_KEY_F11, TB_MOD_CTRL }, + {"\x1b\x1b[23^", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[23@", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[23@", TB_KEY_F11, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[23$", TB_KEY_F11, TB_MOD_SHIFT }, + + {"\x1b\x1b[24~", TB_KEY_F12, TB_MOD_ALT }, + {"\x1b\x1b[24$", TB_KEY_F12, TB_MOD_ALT | TB_MOD_SHIFT }, + {"\x1b[24^", TB_KEY_F12, TB_MOD_CTRL }, + {"\x1b\x1b[24^", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1b\x1b[24@", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_ALT | TB_MOD_SHIFT}, + {"\x1b[24@", TB_KEY_F12, TB_MOD_CTRL | TB_MOD_SHIFT }, + {"\x1b[24$", TB_KEY_F12, TB_MOD_SHIFT }, + + // linux console/putty arrows + {"\x1b[A", TB_KEY_ARROW_UP, TB_MOD_SHIFT }, + {"\x1b[B", TB_KEY_ARROW_DOWN, TB_MOD_SHIFT }, + {"\x1b[C", TB_KEY_ARROW_RIGHT, TB_MOD_SHIFT }, + {"\x1b[D", TB_KEY_ARROW_LEFT, TB_MOD_SHIFT }, + + // more putty arrows + {"\x1bOA", TB_KEY_ARROW_UP, TB_MOD_CTRL }, + {"\x1b\x1bOA", TB_KEY_ARROW_UP, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1bOB", TB_KEY_ARROW_DOWN, TB_MOD_CTRL }, + {"\x1b\x1bOB", TB_KEY_ARROW_DOWN, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1bOC", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL }, + {"\x1b\x1bOC", TB_KEY_ARROW_RIGHT, TB_MOD_CTRL | TB_MOD_ALT }, + {"\x1bOD", TB_KEY_ARROW_LEFT, TB_MOD_CTRL }, + {"\x1b\x1bOD", TB_KEY_ARROW_LEFT, TB_MOD_CTRL | TB_MOD_ALT }, + + {NULL, 0, 0 }, +}; + +static const unsigned char utf8_length[256] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1}; + +static const unsigned char utf8_mask[6] = {0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01}; + +static int tb_reset(void); +static int tb_printf_inner(int x, int y, uintattr_t fg, uintattr_t bg, + size_t *out_w, const char *fmt, va_list vl); +static int init_term_attrs(void); +static int init_term_caps(void); +static int init_cap_trie(void); +static int cap_trie_add(const char *cap, uint16_t key, uint8_t mod); +static int cap_trie_find(const char *buf, size_t nbuf, struct cap_trie_t **last, + size_t *depth); +static int cap_trie_deinit(struct cap_trie_t *node); +static int init_resize_handler(void); +static int send_init_escape_codes(void); +static int send_clear(void); +static int update_term_size(void); +static int update_term_size_via_esc(void); +static int init_cellbuf(void); +static int tb_deinit(void); +static int load_terminfo(void); +static int load_terminfo_from_path(const char *path, const char *term); +static int read_terminfo_path(const char *path); +static int parse_terminfo_caps(void); +static int load_builtin_caps(void); +static const char *get_terminfo_string(int16_t str_offsets_pos, + int16_t str_offsets_len, int16_t str_table_pos, int16_t str_table_len, + int16_t str_index); +static int wait_event(struct tb_event *event, int timeout); +static int extract_event(struct tb_event *event); +static int extract_esc(struct tb_event *event); +static int extract_esc_user(struct tb_event *event, int is_post); +static int extract_esc_cap(struct tb_event *event); +static int extract_esc_mouse(struct tb_event *event); +static int resize_cellbufs(void); +static void handle_resize(int sig); +static int send_attr(uintattr_t fg, uintattr_t bg); +static int send_sgr(uint32_t fg, uint32_t bg, int fg_is_default, + int bg_is_default); +static int send_cursor_if(int x, int y); +static int send_char(int x, int y, uint32_t ch); +static int send_cluster(int x, int y, uint32_t *ch, size_t nch); +static int convert_num(uint32_t num, char *buf); +static int cell_cmp(struct tb_cell *a, struct tb_cell *b); +static int cell_copy(struct tb_cell *dst, struct tb_cell *src); +static int cell_set(struct tb_cell *cell, uint32_t *ch, size_t nch, + uintattr_t fg, uintattr_t bg); +static int cell_reserve_ech(struct tb_cell *cell, size_t n); +static int cell_free(struct tb_cell *cell); +static int cellbuf_init(struct cellbuf_t *c, int w, int h); +static int cellbuf_free(struct cellbuf_t *c); +static int cellbuf_clear(struct cellbuf_t *c); +static int cellbuf_get(struct cellbuf_t *c, int x, int y, struct tb_cell **out); +static int cellbuf_resize(struct cellbuf_t *c, int w, int h); +static int bytebuf_puts(struct bytebuf_t *b, const char *str); +static int bytebuf_nputs(struct bytebuf_t *b, const char *str, size_t nstr); +static int bytebuf_shift(struct bytebuf_t *b, size_t n); +static int bytebuf_flush(struct bytebuf_t *b, int fd); +static int bytebuf_reserve(struct bytebuf_t *b, size_t sz); +static int bytebuf_free(struct bytebuf_t *b); + +int tb_init(void) { + return tb_init_file("/dev/tty"); +} + +int tb_init_file(const char *path) { + if (global.initialized) { + return TB_ERR_INIT_ALREADY; + } + int ttyfd = open(path, O_RDWR); + if (ttyfd < 0) { + global.last_errno = errno; + return TB_ERR_INIT_OPEN; + } + global.ttyfd_open = 1; + return tb_init_fd(ttyfd); +} + +int tb_init_fd(int ttyfd) { + return tb_init_rwfd(ttyfd, ttyfd); +} + +int tb_init_rwfd(int rfd, int wfd) { + int rv; + + tb_reset(); + global.ttyfd = rfd == wfd && isatty(rfd) ? rfd : -1; + global.rfd = rfd; + global.wfd = wfd; + + do { + if_err_break(rv, init_term_attrs()); + if_err_break(rv, init_term_caps()); + if_err_break(rv, init_cap_trie()); + if_err_break(rv, init_resize_handler()); + if_err_break(rv, send_init_escape_codes()); + if_err_break(rv, send_clear()); + if_err_break(rv, update_term_size()); + if_err_break(rv, init_cellbuf()); + global.initialized = 1; + } while (0); + + if (rv != TB_OK) { + tb_deinit(); + } + + return rv; +} + +int tb_shutdown(void) { + if_not_init_return(); + tb_deinit(); + return TB_OK; +} + +int tb_width(void) { + if_not_init_return(); + return global.width; +} + +int tb_height(void) { + if_not_init_return(); + return global.height; +} + +int tb_clear(void) { + if_not_init_return(); + return cellbuf_clear(&global.back); +} + +int tb_set_clear_attrs(uintattr_t fg, uintattr_t bg) { + if_not_init_return(); + global.fg = fg; + global.bg = bg; + return TB_OK; +} + +int tb_present(void) { + if_not_init_return(); + + int rv; + + // TODO Assert global.back.(width,height) == global.front.(width,height) + + global.last_x = -1; + global.last_y = -1; + + int x, y, i; + for (y = 0; y < global.front.height; y++) { + for (x = 0; x < global.front.width;) { + struct tb_cell *back, *front; + if_err_return(rv, cellbuf_get(&global.back, x, y, &back)); + if_err_return(rv, cellbuf_get(&global.front, x, y, &front)); + + int w; + { +#ifdef TB_OPT_EGC + if (back->nech > 0) + w = wcswidth((wchar_t *)back->ech, back->nech); + else +#endif + /* wcwidth() simply returns -1 on overflow of wchar_t */ + w = wcwidth((wchar_t)back->ch); + } + if (w < 1) { + w = 1; + } + + if (cell_cmp(back, front) != 0) { + cell_copy(front, back); + + send_attr(back->fg, back->bg); + if (w > 1 && x >= global.front.width - (w - 1)) { + for (i = x; i < global.front.width; i++) { + send_char(i, y, ' '); + } + } else { + { +#ifdef TB_OPT_EGC + if (back->nech > 0) + send_cluster(x, y, back->ech, back->nech); + else +#endif + send_char(x, y, back->ch); + } + for (i = 1; i < w; i++) { + struct tb_cell *front_wide; + if_err_return(rv, + cellbuf_get(&global.front, x + i, y, &front_wide)); + if_err_return(rv, + cell_set(front_wide, 0, 1, back->fg, back->bg)); + } + } + } + x += w; + } + } + + if_err_return(rv, send_cursor_if(global.cursor_x, global.cursor_y)); + if_err_return(rv, bytebuf_flush(&global.out, global.wfd)); + + return TB_OK; +} + +int tb_invalidate(void) { + int rv; + if_not_init_return(); + if_err_return(rv, resize_cellbufs()); + return TB_OK; +} + +int tb_set_cursor(int cx, int cy) { + if_not_init_return(); + int rv; + if (cx < 0) cx = 0; + if (cy < 0) cy = 0; + if (global.cursor_x == -1) { + if_err_return(rv, + bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR])); + } + if_err_return(rv, send_cursor_if(cx, cy)); + global.cursor_x = cx; + global.cursor_y = cy; + return TB_OK; +} + +int tb_hide_cursor(void) { + if_not_init_return(); + int rv; + if (global.cursor_x >= 0) { + if_err_return(rv, + bytebuf_puts(&global.out, global.caps[TB_CAP_HIDE_CURSOR])); + } + global.cursor_x = -1; + global.cursor_y = -1; + return TB_OK; +} + +int tb_set_cell(int x, int y, uint32_t ch, uintattr_t fg, uintattr_t bg) { + return tb_set_cell_ex(x, y, &ch, 1, fg, bg); +} + +int tb_set_cell_ex(int x, int y, uint32_t *ch, size_t nch, uintattr_t fg, + uintattr_t bg) { + if_not_init_return(); + int rv; + struct tb_cell *cell; + if_err_return(rv, cellbuf_get(&global.back, x, y, &cell)); + if_err_return(rv, cell_set(cell, ch, nch, fg, bg)); + return TB_OK; +} + +int tb_extend_cell(int x, int y, uint32_t ch) { + if_not_init_return(); +#ifdef TB_OPT_EGC + int rv; + struct tb_cell *cell; + size_t nech; + if_err_return(rv, cellbuf_get(&global.back, x, y, &cell)); + if (cell->nech > 0) { // append to ech + nech = cell->nech + 1; + if_err_return(rv, cell_reserve_ech(cell, nech)); + cell->ech[nech - 1] = ch; + } else { // make new ech + nech = 2; + if_err_return(rv, cell_reserve_ech(cell, nech)); + cell->ech[0] = cell->ch; + cell->ech[1] = ch; + } + cell->ech[nech] = '\0'; + cell->nech = nech; + return TB_OK; +#else + (void)x; + (void)y; + (void)ch; + return TB_ERR; +#endif +} + +int tb_set_input_mode(int mode) { + if_not_init_return(); + if (mode == TB_INPUT_CURRENT) { + return global.input_mode; + } + + if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == 0) { + mode |= TB_INPUT_ESC; + } + + if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == (TB_INPUT_ESC | TB_INPUT_ALT)) + { + mode &= ~TB_INPUT_ALT; + } + + if (mode & TB_INPUT_MOUSE) { + bytebuf_puts(&global.out, TB_HARDCAP_ENTER_MOUSE); + bytebuf_flush(&global.out, global.wfd); + } else { + bytebuf_puts(&global.out, TB_HARDCAP_EXIT_MOUSE); + bytebuf_flush(&global.out, global.wfd); + } + + global.input_mode = mode; + return TB_OK; +} + +int tb_set_output_mode(int mode) { + if_not_init_return(); + switch (mode) { + case TB_OUTPUT_CURRENT: + return global.output_mode; + case TB_OUTPUT_NORMAL: + case TB_OUTPUT_256: + case TB_OUTPUT_216: + case TB_OUTPUT_GRAYSCALE: +#if TB_OPT_ATTR_W >= 32 + case TB_OUTPUT_TRUECOLOR: +#endif + global.last_fg = ~global.fg; + global.last_bg = ~global.bg; + global.output_mode = mode; + return TB_OK; + } + return TB_ERR; +} + +int tb_peek_event(struct tb_event *event, int timeout_ms) { + if_not_init_return(); + return wait_event(event, timeout_ms); +} + +int tb_poll_event(struct tb_event *event) { + if_not_init_return(); + return wait_event(event, -1); +} + +int tb_get_fds(int *ttyfd, int *resizefd) { + if_not_init_return(); + + *ttyfd = global.rfd; + *resizefd = global.resize_pipefd[0]; + + return TB_OK; +} + +int tb_print(int x, int y, uintattr_t fg, uintattr_t bg, const char *str) { + return tb_print_ex(x, y, fg, bg, NULL, str); +} + +int tb_print_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, + const char *str) { + int rv; + uint32_t uni; + int w, ix = x; + if (out_w) { + *out_w = 0; + } + while (*str) { + rv = tb_utf8_char_to_unicode(&uni, str); + if (rv < 0) { + uni = 0xfffd; // replace invalid UTF-8 char with U+FFFD + str += rv * -1; + } else if (rv > 0) { + str += rv; + } else { + break; // shouldn't get here + } + w = wcwidth((wchar_t)uni); + if (w < 0) w = 1; + if (w == 0 && x > ix) { + if_err_return(rv, tb_extend_cell(x - 1, y, uni)); + } else { + if_err_return(rv, tb_set_cell(x, y, uni, fg, bg)); + } + x += w; + if (out_w) { + *out_w += w; + } + } + return TB_OK; +} + +int tb_printf(int x, int y, uintattr_t fg, uintattr_t bg, const char *fmt, + ...) { + int rv; + va_list vl; + va_start(vl, fmt); + rv = tb_printf_inner(x, y, fg, bg, NULL, fmt, vl); + va_end(vl); + return rv; +} + +int tb_printf_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, + const char *fmt, ...) { + int rv; + va_list vl; + va_start(vl, fmt); + rv = tb_printf_inner(x, y, fg, bg, out_w, fmt, vl); + va_end(vl); + return rv; +} + +int tb_send(const char *buf, size_t nbuf) { + return bytebuf_nputs(&global.out, buf, nbuf); +} + +int tb_sendf(const char *fmt, ...) { + int rv; + char buf[TB_OPT_PRINTF_BUF]; + va_list vl; + va_start(vl, fmt); + rv = vsnprintf(buf, sizeof(buf), fmt, vl); + va_end(vl); + if (rv < 0 || rv >= (int)sizeof(buf)) { + return TB_ERR; + } + return tb_send(buf, (size_t)rv); +} + +int tb_set_func(int fn_type, int (*fn)(struct tb_event *, size_t *)) { + switch (fn_type) { + case TB_FUNC_EXTRACT_PRE: + global.fn_extract_esc_pre = fn; + return TB_OK; + case TB_FUNC_EXTRACT_POST: + global.fn_extract_esc_post = fn; + return TB_OK; + } + return TB_ERR; +} + +struct tb_cell *tb_cell_buffer(void) { + if (!global.initialized) return NULL; + return global.back.cells; +} + +int tb_utf8_char_length(char c) { + return utf8_length[(unsigned char)c]; +} + +int tb_utf8_char_to_unicode(uint32_t *out, const char *c) { + if (*c == '\0') return 0; + + int i; + unsigned char len = tb_utf8_char_length(*c); + unsigned char mask = utf8_mask[len - 1]; + uint32_t result = c[0] & mask; + for (i = 1; i < len && c[i] != '\0'; ++i) { + result <<= 6; + result |= c[i] & 0x3f; + } + + if (i != len) return i * -1; + + *out = result; + return (int)len; +} + +int tb_utf8_unicode_to_char(char *out, uint32_t c) { + int len = 0; + int first; + int i; + + if (c < 0x80) { + first = 0; + len = 1; + } else if (c < 0x800) { + first = 0xc0; + len = 2; + } else if (c < 0x10000) { + first = 0xe0; + len = 3; + } else if (c < 0x200000) { + first = 0xf0; + len = 4; + } else if (c < 0x4000000) { + first = 0xf8; + len = 5; + } else { + first = 0xfc; + len = 6; + } + + for (i = len - 1; i > 0; --i) { + out[i] = (c & 0x3f) | 0x80; + c >>= 6; + } + out[0] = c | first; + out[len] = '\0'; + + return len; +} + +int tb_last_errno(void) { + return global.last_errno; +} + +const char *tb_strerror(int err) { + switch (err) { + case TB_OK: + return "Success"; + case TB_ERR_NEED_MORE: + return "Not enough input"; + case TB_ERR_INIT_ALREADY: + return "Termbox initialized already"; + case TB_ERR_MEM: + return "Out of memory"; + case TB_ERR_NO_EVENT: + return "No event"; + case TB_ERR_NO_TERM: + return "No TERM in environment"; + case TB_ERR_NOT_INIT: + return "Termbox not initialized"; + case TB_ERR_OUT_OF_BOUNDS: + return "Out of bounds"; + case TB_ERR_UNSUPPORTED_TERM: + return "Unsupported terminal"; + case TB_ERR_CAP_COLLISION: + return "Termcaps collision"; + case TB_ERR_RESIZE_SSCANF: + return "Terminal width/height not received by sscanf() after " + "resize"; + case TB_ERR: + case TB_ERR_INIT_OPEN: + case TB_ERR_READ: + case TB_ERR_RESIZE_IOCTL: + case TB_ERR_RESIZE_PIPE: + case TB_ERR_RESIZE_SIGACTION: + case TB_ERR_POLL: + case TB_ERR_TCGETATTR: + case TB_ERR_TCSETATTR: + case TB_ERR_RESIZE_WRITE: + case TB_ERR_RESIZE_POLL: + case TB_ERR_RESIZE_READ: + default: + strerror_r(global.last_errno, global.errbuf, sizeof(global.errbuf)); + return (const char *)global.errbuf; + } +} + +int tb_has_truecolor(void) { +#if TB_OPT_ATTR_W >= 32 + return 1; +#else + return 0; +#endif +} + +int tb_has_egc(void) { +#ifdef TB_OPT_EGC + return 1; +#else + return 0; +#endif +} + +int tb_attr_width(void) { + return TB_OPT_ATTR_W; +} + +const char *tb_version(void) { + return TB_VERSION_STR; +} + +static int tb_reset(void) { + int ttyfd_open = global.ttyfd_open; + memset(&global, 0, sizeof(global)); + global.ttyfd = -1; + global.rfd = -1; + global.wfd = -1; + global.ttyfd_open = ttyfd_open; + global.resize_pipefd[0] = -1; + global.resize_pipefd[1] = -1; + global.width = -1; + global.height = -1; + global.cursor_x = -1; + global.cursor_y = -1; + global.last_x = -1; + global.last_y = -1; + global.fg = TB_DEFAULT; + global.bg = TB_DEFAULT; + global.last_fg = ~global.fg; + global.last_bg = ~global.bg; + global.input_mode = TB_INPUT_ESC; + global.output_mode = TB_OUTPUT_NORMAL; + return TB_OK; +} + +static int init_term_attrs(void) { + if (global.ttyfd < 0) { + return TB_OK; + } + + if (tcgetattr(global.ttyfd, &global.orig_tios) != 0) { + global.last_errno = errno; + return TB_ERR_TCGETATTR; + } + + struct termios tios; + memcpy(&tios, &global.orig_tios, sizeof(tios)); + global.has_orig_tios = 1; + + cfmakeraw(&tios); + tios.c_cc[VMIN] = 1; + tios.c_cc[VTIME] = 0; + + if (tcsetattr(global.ttyfd, TCSAFLUSH, &tios) != 0) { + global.last_errno = errno; + return TB_ERR_TCSETATTR; + } + + return TB_OK; +} + +int tb_printf_inner(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w, + const char *fmt, va_list vl) { + int rv; + char buf[TB_OPT_PRINTF_BUF]; + rv = vsnprintf(buf, sizeof(buf), fmt, vl); + if (rv < 0 || rv >= (int)sizeof(buf)) { + return TB_ERR; + } + return tb_print_ex(x, y, fg, bg, out_w, buf); +} + +static int init_term_caps(void) { + if (load_terminfo() == TB_OK) { + return parse_terminfo_caps(); + } + return load_builtin_caps(); +} + +static int init_cap_trie(void) { + int rv, i; + + // Add caps from terminfo or built-in + // + // Collisions are expected as some terminfo entries have dupes. (For + // example, att605-pc collides on TB_CAP_F4 and TB_CAP_DELETE.) First cap + // in TB_CAP_* index order will win. + // + // TODO Reorder TB_CAP_* so more critical caps come first. + for (i = 0; i < TB_CAP__COUNT_KEYS; i++) { + rv = cap_trie_add(global.caps[i], tb_key_i(i), 0); + if (rv != TB_OK && rv != TB_ERR_CAP_COLLISION) return rv; + } + + // Add built-in mod caps + // + // Collisions are OK here as well. This can happen if global.caps collides + // with builtin_mod_caps. It is desirable to give precedence to global.caps + // here. + for (i = 0; builtin_mod_caps[i].cap != NULL; i++) { + rv = cap_trie_add(builtin_mod_caps[i].cap, builtin_mod_caps[i].key, + builtin_mod_caps[i].mod); + if (rv != TB_OK && rv != TB_ERR_CAP_COLLISION) return rv; + } + + return TB_OK; +} + +static int cap_trie_add(const char *cap, uint16_t key, uint8_t mod) { + struct cap_trie_t *next, *node = &global.cap_trie; + size_t i, j; + + if (!cap || strlen(cap) <= 0) return TB_OK; // Nothing to do for empty caps + + for (i = 0; cap[i] != '\0'; i++) { + char c = cap[i]; + next = NULL; + + // Check if c is already a child of node + for (j = 0; j < node->nchildren; j++) { + if (node->children[j].c == c) { + next = &node->children[j]; + break; + } + } + if (!next) { + // We need to add a new child to node + node->nchildren += 1; + node->children = + tb_realloc(node->children, sizeof(*node) * node->nchildren); + if (!node->children) { + return TB_ERR_MEM; + } + next = &node->children[node->nchildren - 1]; + memset(next, 0, sizeof(*next)); + next->c = c; + } + + // Continue + node = next; + } + + if (node->is_leaf) { + // Already a leaf here + return TB_ERR_CAP_COLLISION; + } + + node->is_leaf = 1; + node->key = key; + node->mod = mod; + return TB_OK; +} + +static int cap_trie_find(const char *buf, size_t nbuf, struct cap_trie_t **last, + size_t *depth) { + struct cap_trie_t *next, *node = &global.cap_trie; + size_t i, j; + *last = node; + *depth = 0; + for (i = 0; i < nbuf; i++) { + char c = buf[i]; + next = NULL; + + // Find c in node.children + for (j = 0; j < node->nchildren; j++) { + if (node->children[j].c == c) { + next = &node->children[j]; + break; + } + } + if (!next) { + // Not found + return TB_OK; + } + node = next; + *last = node; + *depth += 1; + if (node->is_leaf && node->nchildren < 1) { + break; + } + } + return TB_OK; +} + +static int cap_trie_deinit(struct cap_trie_t *node) { + size_t j; + for (j = 0; j < node->nchildren; j++) { + cap_trie_deinit(&node->children[j]); + } + if (node->children) { + tb_free(node->children); + } + memset(node, 0, sizeof(*node)); + return TB_OK; +} + +static int init_resize_handler(void) { + if (pipe(global.resize_pipefd) != 0) { + global.last_errno = errno; + return TB_ERR_RESIZE_PIPE; + } + + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handle_resize; + if (sigaction(SIGWINCH, &sa, NULL) != 0) { + global.last_errno = errno; + return TB_ERR_RESIZE_SIGACTION; + } + + return TB_OK; +} + +static int send_init_escape_codes(void) { + int rv; + if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_ENTER_CA])); + if_err_return(rv, + bytebuf_puts(&global.out, global.caps[TB_CAP_ENTER_KEYPAD])); + if_err_return(rv, + bytebuf_puts(&global.out, global.caps[TB_CAP_HIDE_CURSOR])); + return TB_OK; +} + +static int send_clear(void) { + int rv; + + if_err_return(rv, send_attr(global.fg, global.bg)); + if_err_return(rv, + bytebuf_puts(&global.out, global.caps[TB_CAP_CLEAR_SCREEN])); + + if_err_return(rv, send_cursor_if(global.cursor_x, global.cursor_y)); + if_err_return(rv, bytebuf_flush(&global.out, global.wfd)); + + global.last_x = -1; + global.last_y = -1; + + return TB_OK; +} + +static int update_term_size(void) { + int rv, ioctl_errno; + + if (global.ttyfd < 0) { + return TB_OK; + } + + struct winsize sz; + memset(&sz, 0, sizeof(sz)); + + // Try ioctl TIOCGWINSZ + if (ioctl(global.ttyfd, TIOCGWINSZ, &sz) == 0) { + global.width = sz.ws_col; + global.height = sz.ws_row; + return TB_OK; + } + ioctl_errno = errno; + + // Try >cursor(9999,9999), >u7, = 0) { + bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]); + bytebuf_puts(&global.out, global.caps[TB_CAP_SGR0]); + bytebuf_puts(&global.out, global.caps[TB_CAP_CLEAR_SCREEN]); + bytebuf_puts(&global.out, global.caps[TB_CAP_EXIT_CA]); + bytebuf_puts(&global.out, global.caps[TB_CAP_EXIT_KEYPAD]); + bytebuf_puts(&global.out, TB_HARDCAP_EXIT_MOUSE); + bytebuf_flush(&global.out, global.wfd); + } + if (global.ttyfd >= 0) { + if (global.has_orig_tios) { + tcsetattr(global.ttyfd, TCSAFLUSH, &global.orig_tios); + } + if (global.ttyfd_open) { + close(global.ttyfd); + global.ttyfd_open = 0; + } + } + + sigaction(SIGWINCH, &(struct sigaction){.sa_handler = SIG_DFL}, NULL); + if (global.resize_pipefd[0] >= 0) close(global.resize_pipefd[0]); + if (global.resize_pipefd[1] >= 0) close(global.resize_pipefd[1]); + + cellbuf_free(&global.back); + cellbuf_free(&global.front); + bytebuf_free(&global.in); + bytebuf_free(&global.out); + + if (global.terminfo) tb_free(global.terminfo); + + cap_trie_deinit(&global.cap_trie); + + tb_reset(); + return TB_OK; +} + +static int load_terminfo(void) { + int rv; + char tmp[TB_PATH_MAX]; + + // See terminfo(5) "Fetching Compiled Descriptions" for a description of + // this behavior. Some of these paths are compile-time ncurses options, so + // best guesses are used here. + const char *term = getenv("TERM"); + if (!term) { + return TB_ERR; + } + + // If TERMINFO is set, try that directory and stop + const char *terminfo = getenv("TERMINFO"); + if (terminfo) { + return load_terminfo_from_path(terminfo, term); + } + + // Next try ~/.terminfo + const char *home = getenv("HOME"); + if (home) { + snprintf_or_return(rv, tmp, sizeof(tmp), "%s/.terminfo", home); + if_ok_return(rv, load_terminfo_from_path(tmp, term)); + } + + // Next try TERMINFO_DIRS + // + // Note, empty entries are supposed to be interpretted as the "compiled-in + // default", which is of course system-dependent. Previously /etc/terminfo + // was used here. Let's skip empty entries altogether rather than give + // precedence to a guess, and check common paths after this loop. + const char *dirs = getenv("TERMINFO_DIRS"); + if (dirs) { + snprintf_or_return(rv, tmp, sizeof(tmp), "%s", dirs); + char *dir = strtok(tmp, ":"); + while (dir) { + const char *cdir = dir; + if (*cdir != '\0') { + if_ok_return(rv, load_terminfo_from_path(cdir, term)); + } + dir = strtok(NULL, ":"); + } + } + +#ifdef TB_TERMINFO_DIR + if_ok_return(rv, load_terminfo_from_path(TB_TERMINFO_DIR, term)); +#endif + if_ok_return(rv, load_terminfo_from_path("/usr/local/etc/terminfo", term)); + if_ok_return(rv, + load_terminfo_from_path("/usr/local/share/terminfo", term)); + if_ok_return(rv, load_terminfo_from_path("/usr/local/lib/terminfo", term)); + if_ok_return(rv, load_terminfo_from_path("/etc/terminfo", term)); + if_ok_return(rv, load_terminfo_from_path("/usr/share/terminfo", term)); + if_ok_return(rv, load_terminfo_from_path("/usr/lib/terminfo", term)); + if_ok_return(rv, load_terminfo_from_path("/usr/share/lib/terminfo", term)); + if_ok_return(rv, load_terminfo_from_path("/lib/terminfo", term)); + + return TB_ERR; +} + +static int load_terminfo_from_path(const char *path, const char *term) { + int rv; + char tmp[TB_PATH_MAX]; + + // Look for term at this terminfo location, e.g., /x/xterm + snprintf_or_return(rv, tmp, sizeof(tmp), "%s/%c/%s", path, term[0], term); + if_ok_return(rv, read_terminfo_path(tmp)); + +#ifdef __APPLE__ + // Try the Darwin equivalent path, e.g., /78/xterm + snprintf_or_return(rv, tmp, sizeof(tmp), "%s/%x/%s", path, term[0], term); + return read_terminfo_path(tmp); +#endif + + return TB_ERR; +} + +static int read_terminfo_path(const char *path) { + FILE *fp = fopen(path, "rb"); + if (!fp) { + return TB_ERR; + } + + struct stat st; + if (fstat(fileno(fp), &st) != 0) { + fclose(fp); + return TB_ERR; + } + + size_t fsize = st.st_size; + char *data = tb_malloc(fsize); + if (!data) { + fclose(fp); + return TB_ERR; + } + + if (fread(data, 1, fsize, fp) != fsize) { + fclose(fp); + tb_free(data); + return TB_ERR; + } + + global.terminfo = data; + global.nterminfo = fsize; + + fclose(fp); + return TB_OK; +} + +static int parse_terminfo_caps(void) { + // See term(5) "LEGACY STORAGE FORMAT" and "EXTENDED STORAGE FORMAT" for a + // description of this behavior. + + // Ensure there's at least a header's worth of data + if (global.nterminfo < 6) { + return TB_ERR; + } + + int16_t *header = (int16_t *)global.terminfo; + // header[0] the magic number (octal 0432 or 01036) + // header[1] the size, in bytes, of the names section + // header[2] the number of bytes in the boolean section + // header[3] the number of short integers in the numbers section + // header[4] the number of offsets (short integers) in the strings section + // header[5] the size, in bytes, of the string table + + // Legacy ints are 16-bit, extended ints are 32-bit + const int bytes_per_int = header[0] == 01036 ? 4 // 32-bit + : 2; // 16-bit + + // > Between the boolean section and the number section, a null byte will be + // > inserted, if necessary, to ensure that the number section begins on an + // > even byte + const int align_offset = (header[1] + header[2]) % 2 != 0 ? 1 : 0; + + const int pos_str_offsets = + (6 * sizeof(int16_t)) // header (12 bytes) + + header[1] // length of names section + + header[2] // length of boolean section + + align_offset + + (header[3] * bytes_per_int); // length of numbers section + + const int pos_str_table = + pos_str_offsets + + (header[4] * sizeof(int16_t)); // length of string offsets table + + // Load caps + int i; + for (i = 0; i < TB_CAP__COUNT; i++) { + const char *cap = get_terminfo_string(pos_str_offsets, header[4], + pos_str_table, header[5], terminfo_cap_indexes[i]); + if (!cap) { + // Something is not right + return TB_ERR; + } + global.caps[i] = cap; + } + + return TB_OK; +} + +static int load_builtin_caps(void) { + int i, j; + const char *term = getenv("TERM"); + + if (!term) { + return TB_ERR_NO_TERM; + } + + // Check for exact TERM match + for (i = 0; builtin_terms[i].name != NULL; i++) { + if (strcmp(term, builtin_terms[i].name) == 0) { + for (j = 0; j < TB_CAP__COUNT; j++) { + global.caps[j] = builtin_terms[i].caps[j]; + } + return TB_OK; + } + } + + // Check for partial TERM or alias match + for (i = 0; builtin_terms[i].name != NULL; i++) { + if (strstr(term, builtin_terms[i].name) != NULL || + (*(builtin_terms[i].alias) != '\0' && + strstr(term, builtin_terms[i].alias) != NULL)) + { + for (j = 0; j < TB_CAP__COUNT; j++) { + global.caps[j] = builtin_terms[i].caps[j]; + } + return TB_OK; + } + } + + return TB_ERR_UNSUPPORTED_TERM; +} + +static const char *get_terminfo_string(int16_t str_offsets_pos, + int16_t str_offsets_len, int16_t str_table_pos, int16_t str_table_len, + int16_t str_index) { + const int str_byte_index = (int)str_index * (int)sizeof(int16_t); + if (str_byte_index >= (int)str_offsets_len * (int)sizeof(int16_t)) { + // An offset beyond the table indicates absent + // See `convert_strings` in tinfo `read_entry.c` + return ""; + } + const int16_t *str_offset = + (int16_t *)(global.terminfo + (int)str_offsets_pos + str_byte_index); + if ((char *)str_offset >= global.terminfo + global.nterminfo) { + // str_offset points beyond end of entry + // Truncated/corrupt terminfo entry? + return NULL; + } + if (*str_offset < 0 || *str_offset >= str_table_len) { + // A negative offset indicates absent + // An offset beyond the table indicates absent + // See `convert_strings` in tinfo `read_entry.c` + return ""; + } + if (((size_t)((int)str_table_pos + (int)*str_offset)) >= global.nterminfo) { + // string points beyond end of entry + // Truncated/corrupt terminfo entry? + return NULL; + } + return ( + const char *)(global.terminfo + (int)str_table_pos + (int)*str_offset); +} + +static int wait_event(struct tb_event *event, int timeout) { + int rv; + char buf[TB_OPT_READ_BUF]; + + memset(event, 0, sizeof(*event)); + if_ok_return(rv, extract_event(event)); + + fd_set fds; + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000; + + do { + FD_ZERO(&fds); + FD_SET(global.rfd, &fds); + FD_SET(global.resize_pipefd[0], &fds); + + int maxfd = global.resize_pipefd[0] > global.rfd + ? global.resize_pipefd[0] + : global.rfd; + + int select_rv = + select(maxfd + 1, &fds, NULL, NULL, (timeout < 0) ? NULL : &tv); + + if (select_rv < 0) { + // Let EINTR/EAGAIN bubble up + global.last_errno = errno; + return TB_ERR_POLL; + } else if (select_rv == 0) { + return TB_ERR_NO_EVENT; + } + + int tty_has_events = (FD_ISSET(global.rfd, &fds)); + int resize_has_events = (FD_ISSET(global.resize_pipefd[0], &fds)); + + if (tty_has_events) { + ssize_t read_rv = read(global.rfd, buf, sizeof(buf)); + if (read_rv < 0) { + global.last_errno = errno; + return TB_ERR_READ; + } else if (read_rv > 0) { + bytebuf_nputs(&global.in, buf, read_rv); + } + } + + if (resize_has_events) { + int ignore = 0; + read(global.resize_pipefd[0], &ignore, sizeof(ignore)); + // TODO Harden against errors encountered mid-resize + if_err_return(rv, update_term_size()); + if_err_return(rv, resize_cellbufs()); + event->type = TB_EVENT_RESIZE; + event->w = global.width; + event->h = global.height; + return TB_OK; + } + + memset(event, 0, sizeof(*event)); + if_ok_return(rv, extract_event(event)); + } while (timeout == -1); + + return rv; +} + +static int extract_event(struct tb_event *event) { + int rv; + struct bytebuf_t *in = &global.in; + + if (in->len == 0) { + return TB_ERR; + } + + if (in->buf[0] == '\x1b') { + // Escape sequence? + // In TB_INPUT_ESC, skip if the buffer is a single escape char + if (!((global.input_mode & TB_INPUT_ESC) && in->len == 1)) { + if_ok_or_need_more_return(rv, extract_esc(event)); + } + + // Escape key? + if (global.input_mode & TB_INPUT_ESC) { + event->type = TB_EVENT_KEY; + event->ch = 0; + event->key = TB_KEY_ESC; + event->mod = 0; + bytebuf_shift(in, 1); + return TB_OK; + } + + // Recurse for alt key + event->mod |= TB_MOD_ALT; + bytebuf_shift(in, 1); + return extract_event(event); + } + + // ASCII control key? + if ((uint16_t)in->buf[0] < TB_KEY_SPACE || in->buf[0] == TB_KEY_BACKSPACE2) + { + event->type = TB_EVENT_KEY; + event->ch = 0; + event->key = (uint16_t)in->buf[0]; + event->mod |= TB_MOD_CTRL; + bytebuf_shift(in, 1); + return TB_OK; + } + + // UTF-8? + if (in->len >= (size_t)tb_utf8_char_length(in->buf[0])) { + event->type = TB_EVENT_KEY; + tb_utf8_char_to_unicode(&event->ch, in->buf); + event->key = 0; + bytebuf_shift(in, tb_utf8_char_length(in->buf[0])); + return TB_OK; + } + + // Need more input + return TB_ERR; +} + +static int extract_esc(struct tb_event *event) { + int rv; + if_ok_or_need_more_return(rv, extract_esc_user(event, 0)); + if_ok_or_need_more_return(rv, extract_esc_cap(event)); + if_ok_or_need_more_return(rv, extract_esc_mouse(event)); + if_ok_or_need_more_return(rv, extract_esc_user(event, 1)); + return TB_ERR; +} + +static int extract_esc_user(struct tb_event *event, int is_post) { + int rv; + size_t consumed = 0; + struct bytebuf_t *in = &global.in; + int (*fn)(struct tb_event *, size_t *); + + fn = is_post ? global.fn_extract_esc_post : global.fn_extract_esc_pre; + + if (!fn) { + return TB_ERR; + } + + rv = fn(event, &consumed); + if (rv == TB_OK) { + bytebuf_shift(in, consumed); + } + + if_ok_or_need_more_return(rv, rv); + return TB_ERR; +} + +static int extract_esc_cap(struct tb_event *event) { + int rv; + struct bytebuf_t *in = &global.in; + struct cap_trie_t *node; + size_t depth; + + if_err_return(rv, cap_trie_find(in->buf, in->len, &node, &depth)); + if (node->is_leaf) { + // Found a leaf node + event->type = TB_EVENT_KEY; + event->ch = 0; + event->key = node->key; + event->mod = node->mod; + bytebuf_shift(in, depth); + return TB_OK; + } else if (node->nchildren > 0 && in->len <= depth) { + // Found a branch node (not enough input) + return TB_ERR_NEED_MORE; + } + + return TB_ERR; +} + +static int extract_esc_mouse(struct tb_event *event) { + struct bytebuf_t *in = &global.in; + + enum type { TYPE_VT200 = 0, TYPE_1006, TYPE_1015, TYPE_MAX }; + + const char *cmp[TYPE_MAX] = {// + // X10 mouse encoding, the simplest one + // \x1b [ M Cb Cx Cy + [TYPE_VT200] = "\x1b[M", + // xterm 1006 extended mode or urxvt 1015 extended mode + // xterm: \x1b [ < Cb ; Cx ; Cy (M or m) + [TYPE_1006] = "\x1b[<", + // urxvt: \x1b [ Cb ; Cx ; Cy M + [TYPE_1015] = "\x1b["}; + + enum type type = 0; + int ret = TB_ERR; + + // Unrolled at compile-time (probably) + for (; type < TYPE_MAX; type++) { + size_t size = strlen(cmp[type]); + + if (in->len >= size && (strncmp(cmp[type], in->buf, size)) == 0) { + break; + } + } + + if (type == TYPE_MAX) { + ret = TB_ERR; // No match + return ret; + } + + size_t buf_shift = 0; + + switch (type) { + case TYPE_VT200: + if (in->len >= 6) { + int b = in->buf[3] - 0x20; + int fail = 0; + + switch (b & 3) { + case 0: + event->key = ((b & 64) != 0) ? TB_KEY_MOUSE_WHEEL_UP + : TB_KEY_MOUSE_LEFT; + break; + case 1: + event->key = ((b & 64) != 0) ? TB_KEY_MOUSE_WHEEL_DOWN + : TB_KEY_MOUSE_MIDDLE; + break; + case 2: + event->key = TB_KEY_MOUSE_RIGHT; + break; + case 3: + event->key = TB_KEY_MOUSE_RELEASE; + break; + default: + ret = TB_ERR; + fail = 1; + break; + } + + if (!fail) { + if ((b & 32) != 0) { + event->mod |= TB_MOD_MOTION; + } + + // the coord is 1,1 for upper left + event->x = ((uint8_t)in->buf[4]) - 0x21; + event->y = ((uint8_t)in->buf[5]) - 0x21; + + ret = TB_OK; + } + + buf_shift = 6; + } + break; + case TYPE_1006: + // fallthrough + case TYPE_1015: { + size_t index_fail = (size_t)-1; + + enum { + FIRST_M = 0, + FIRST_SEMICOLON, + LAST_SEMICOLON, + FIRST_LAST_MAX + }; + + size_t indices[FIRST_LAST_MAX] = {index_fail, index_fail, + index_fail}; + int m_is_capital = 0; + + for (size_t i = 0; i < in->len; i++) { + if (in->buf[i] == ';') { + if (indices[FIRST_SEMICOLON] == index_fail) { + indices[FIRST_SEMICOLON] = i; + } else { + indices[LAST_SEMICOLON] = i; + } + } else if (indices[FIRST_M] == index_fail) { + if (in->buf[i] == 'm' || in->buf[i] == 'M') { + m_is_capital = (in->buf[i] == 'M'); + indices[FIRST_M] = i; + } + } + } + + if (indices[FIRST_M] == index_fail || + indices[FIRST_SEMICOLON] == index_fail || + indices[LAST_SEMICOLON] == index_fail) + { + ret = TB_ERR; + } else { + int start = (type == TYPE_1015 ? 2 : 3); + + unsigned n1 = strtoul(&in->buf[start], NULL, 10); + unsigned n2 = + strtoul(&in->buf[indices[FIRST_SEMICOLON] + 1], NULL, 10); + unsigned n3 = + strtoul(&in->buf[indices[LAST_SEMICOLON] + 1], NULL, 10); + + if (type == TYPE_1015) { + n1 -= 0x20; + } + + int fail = 0; + + switch (n1 & 3) { + case 0: + event->key = ((n1 & 64) != 0) ? TB_KEY_MOUSE_WHEEL_UP + : TB_KEY_MOUSE_LEFT; + break; + case 1: + event->key = ((n1 & 64) != 0) ? TB_KEY_MOUSE_WHEEL_DOWN + : TB_KEY_MOUSE_MIDDLE; + break; + case 2: + event->key = TB_KEY_MOUSE_RIGHT; + break; + case 3: + event->key = TB_KEY_MOUSE_RELEASE; + break; + default: + ret = TB_ERR; + fail = 1; + break; + } + + buf_shift = in->len; + + if (!fail) { + if (!m_is_capital) { + // on xterm mouse release is signaled by lowercase m + event->key = TB_KEY_MOUSE_RELEASE; + } + + if ((n1 & 32) != 0) { + event->mod |= TB_MOD_MOTION; + } + + event->x = ((uint8_t)n2) - 1; + event->y = ((uint8_t)n3) - 1; + + ret = TB_OK; + } + } + } break; + case TYPE_MAX: + ret = TB_ERR; + } + + if (buf_shift > 0) { + bytebuf_shift(in, buf_shift); + } + + if (ret == TB_OK) { + event->type = TB_EVENT_MOUSE; + } + + return ret; +} + +static int resize_cellbufs(void) { + int rv; + if_err_return(rv, + cellbuf_resize(&global.back, global.width, global.height)); + if_err_return(rv, + cellbuf_resize(&global.front, global.width, global.height)); + if_err_return(rv, cellbuf_clear(&global.front)); + if_err_return(rv, send_clear()); + return TB_OK; +} + +static void handle_resize(int sig) { + int errno_copy = errno; + write(global.resize_pipefd[1], &sig, sizeof(sig)); + errno = errno_copy; +} + +static int send_attr(uintattr_t fg, uintattr_t bg) { + int rv; + + if (fg == global.last_fg && bg == global.last_bg) { + return TB_OK; + } + + if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_SGR0])); + + uint32_t cfg, cbg; + switch (global.output_mode) { + default: + case TB_OUTPUT_NORMAL: + // The minus 1 below is because our colors are 1-indexed starting + // from black. Black is represented by a 30, 40, 90, or 100 for fg, + // bg, bright fg, or bright bg respectively. Red is 31, 41, 91, + // 101, etc. + cfg = (fg & TB_BRIGHT ? 90 : 30) + (fg & 0x0f) - 1; + cbg = (bg & TB_BRIGHT ? 100 : 40) + (bg & 0x0f) - 1; + break; + + case TB_OUTPUT_256: + cfg = fg & 0xff; + cbg = bg & 0xff; + if (fg & TB_HI_BLACK) cfg = 0; + if (bg & TB_HI_BLACK) cbg = 0; + break; + + case TB_OUTPUT_216: + cfg = fg & 0xff; + cbg = bg & 0xff; + if (cfg > 216) cfg = 216; + if (cbg > 216) cbg = 216; + cfg += 0x0f; + cbg += 0x0f; + break; + + case TB_OUTPUT_GRAYSCALE: + cfg = fg & 0xff; + cbg = bg & 0xff; + if (cfg > 24) cfg = 24; + if (cbg > 24) cbg = 24; + cfg += 0xe7; + cbg += 0xe7; + break; + +#if TB_OPT_ATTR_W >= 32 + case TB_OUTPUT_TRUECOLOR: + cfg = fg & 0xffffff; + cbg = bg & 0xffffff; + if (fg & TB_HI_BLACK) cfg = 0; + if (bg & TB_HI_BLACK) cbg = 0; + break; +#endif + } + + if (fg & TB_BOLD) + if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_BOLD])); + + if (fg & TB_BLINK) + if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_BLINK])); + + if (fg & TB_UNDERLINE) + if_err_return(rv, + bytebuf_puts(&global.out, global.caps[TB_CAP_UNDERLINE])); + + if (fg & TB_ITALIC) + if_err_return(rv, + bytebuf_puts(&global.out, global.caps[TB_CAP_ITALIC])); + + if (fg & TB_DIM) + if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_DIM])); + +#if TB_OPT_ATTR_W == 64 + if (fg & TB_STRIKEOUT) + if_err_return(rv, bytebuf_puts(&global.out, TB_HARDCAP_STRIKEOUT)); + + if (fg & TB_UNDERLINE_2) + if_err_return(rv, bytebuf_puts(&global.out, TB_HARDCAP_UNDERLINE_2)); + + if (fg & TB_OVERLINE) + if_err_return(rv, bytebuf_puts(&global.out, TB_HARDCAP_OVERLINE)); + + if (fg & TB_INVISIBLE) + if_err_return(rv, + bytebuf_puts(&global.out, global.caps[TB_CAP_INVISIBLE])); +#endif + + if ((fg & TB_REVERSE) || (bg & TB_REVERSE)) + if_err_return(rv, + bytebuf_puts(&global.out, global.caps[TB_CAP_REVERSE])); + + int fg_is_default = (fg & 0xff) == 0; + int bg_is_default = (bg & 0xff) == 0; + if (global.output_mode == TB_OUTPUT_256) { + if (fg & TB_HI_BLACK) fg_is_default = 0; + if (bg & TB_HI_BLACK) bg_is_default = 0; + } +#if TB_OPT_ATTR_W >= 32 + if (global.output_mode == TB_OUTPUT_TRUECOLOR) { + fg_is_default = ((fg & 0xffffff) == 0) && ((fg & TB_HI_BLACK) == 0); + bg_is_default = ((bg & 0xffffff) == 0) && ((bg & TB_HI_BLACK) == 0); + } +#endif + + if_err_return(rv, send_sgr(cfg, cbg, fg_is_default, bg_is_default)); + + global.last_fg = fg; + global.last_bg = bg; + + return TB_OK; +} + +static int send_sgr(uint32_t cfg, uint32_t cbg, int fg_is_default, + int bg_is_default) { + int rv; + char nbuf[32]; + + if (fg_is_default && bg_is_default) { + return TB_OK; + } + + switch (global.output_mode) { + default: + case TB_OUTPUT_NORMAL: + send_literal(rv, "\x1b["); + if (!fg_is_default) { + send_num(rv, nbuf, cfg); + if (!bg_is_default) { + send_literal(rv, ";"); + } + } + if (!bg_is_default) { + send_num(rv, nbuf, cbg); + } + send_literal(rv, "m"); + break; + + case TB_OUTPUT_256: + case TB_OUTPUT_216: + case TB_OUTPUT_GRAYSCALE: + send_literal(rv, "\x1b["); + if (!fg_is_default) { + send_literal(rv, "38;5;"); + send_num(rv, nbuf, cfg); + if (!bg_is_default) { + send_literal(rv, ";"); + } + } + if (!bg_is_default) { + send_literal(rv, "48;5;"); + send_num(rv, nbuf, cbg); + } + send_literal(rv, "m"); + break; + +#if TB_OPT_ATTR_W >= 32 + case TB_OUTPUT_TRUECOLOR: + send_literal(rv, "\x1b["); + if (!fg_is_default) { + send_literal(rv, "38;2;"); + send_num(rv, nbuf, (cfg >> 16) & 0xff); + send_literal(rv, ";"); + send_num(rv, nbuf, (cfg >> 8) & 0xff); + send_literal(rv, ";"); + send_num(rv, nbuf, cfg & 0xff); + if (!bg_is_default) { + send_literal(rv, ";"); + } + } + if (!bg_is_default) { + send_literal(rv, "48;2;"); + send_num(rv, nbuf, (cbg >> 16) & 0xff); + send_literal(rv, ";"); + send_num(rv, nbuf, (cbg >> 8) & 0xff); + send_literal(rv, ";"); + send_num(rv, nbuf, cbg & 0xff); + } + send_literal(rv, "m"); + break; +#endif + } + return TB_OK; +} + +static int send_cursor_if(int x, int y) { + int rv; + char nbuf[32]; + if (x < 0 || y < 0) { + return TB_OK; + } + send_literal(rv, "\x1b["); + send_num(rv, nbuf, y + 1); + send_literal(rv, ";"); + send_num(rv, nbuf, x + 1); + send_literal(rv, "H"); + return TB_OK; +} + +static int send_char(int x, int y, uint32_t ch) { + return send_cluster(x, y, &ch, 1); +} + +static int send_cluster(int x, int y, uint32_t *ch, size_t nch) { + int rv; + char chu8[8]; + + if (global.last_x != x - 1 || global.last_y != y) { + if_err_return(rv, send_cursor_if(x, y)); + } + global.last_x = x; + global.last_y = y; + + int i; + for (i = 0; i < (int)nch; i++) { + uint32_t ch32 = *(ch + i); + int chu8_len; + if (ch32 == 0) { // replace null with space (from termbox 19dbee5) + chu8_len = 1; + chu8[0] = ' '; + } else { + chu8_len = tb_utf8_unicode_to_char(chu8, ch32); + } + if_err_return(rv, bytebuf_nputs(&global.out, chu8, (size_t)chu8_len)); + } + + return TB_OK; +} + +static int convert_num(uint32_t num, char *buf) { + int i, l = 0; + char ch; + do { + /* '0' = 48; 48 + num%10 < 58 < MAX_8bitCHAR */ + buf[l++] = (char)('0' + (num % 10)); + num /= 10; + } while (num); + for (i = 0; i < l / 2; i++) { + ch = buf[i]; + buf[i] = buf[l - 1 - i]; + buf[l - 1 - i] = ch; + } + return l; +} + +static int cell_cmp(struct tb_cell *a, struct tb_cell *b) { + if (a->ch != b->ch || a->fg != b->fg || a->bg != b->bg) { + return 1; + } +#ifdef TB_OPT_EGC + if (a->nech != b->nech) { + return 1; + } else if (a->nech > 0) { // a->nech == b->nech + return memcmp(a->ech, b->ech, a->nech); + } +#endif + return 0; +} + +static int cell_copy(struct tb_cell *dst, struct tb_cell *src) { +#ifdef TB_OPT_EGC + if (src->nech > 0) { + return cell_set(dst, src->ech, src->nech, src->fg, src->bg); + } +#endif + return cell_set(dst, &src->ch, 1, src->fg, src->bg); +} + +static int cell_set(struct tb_cell *cell, uint32_t *ch, size_t nch, + uintattr_t fg, uintattr_t bg) { + cell->ch = ch ? *ch : 0; + cell->fg = fg; + cell->bg = bg; +#ifdef TB_OPT_EGC + if (nch <= 1) { + cell->nech = 0; + } else { + int rv; + if_err_return(rv, cell_reserve_ech(cell, nch + 1)); + memcpy(cell->ech, ch, sizeof(ch) * nch); + cell->ech[nch] = '\0'; + cell->nech = nch; + } +#else + (void)nch; + (void)cell_reserve_ech; +#endif + return TB_OK; +} + +static int cell_reserve_ech(struct tb_cell *cell, size_t n) { +#ifdef TB_OPT_EGC + if (cell->cech >= n) { + return TB_OK; + } + if (!(cell->ech = tb_realloc(cell->ech, n * sizeof(cell->ch)))) { + return TB_ERR_MEM; + } + cell->cech = n; + return TB_OK; +#else + (void)cell; + (void)n; + return TB_ERR; +#endif +} + +static int cell_free(struct tb_cell *cell) { +#ifdef TB_OPT_EGC + if (cell->ech) { + tb_free(cell->ech); + } +#endif + memset(cell, 0, sizeof(*cell)); + return TB_OK; +} + +static int cellbuf_init(struct cellbuf_t *c, int w, int h) { + c->cells = tb_malloc(sizeof(struct tb_cell) * w * h); + if (!c->cells) { + return TB_ERR_MEM; + } + memset(c->cells, 0, sizeof(struct tb_cell) * w * h); + c->width = w; + c->height = h; + return TB_OK; +} + +static int cellbuf_free(struct cellbuf_t *c) { + if (c->cells) { + int i; + for (i = 0; i < c->width * c->height; i++) { + cell_free(&c->cells[i]); + } + tb_free(c->cells); + } + memset(c, 0, sizeof(*c)); + return TB_OK; +} + +static int cellbuf_clear(struct cellbuf_t *c) { + int rv, i; + uint32_t space = (uint32_t)' '; + for (i = 0; i < c->width * c->height; i++) { + if_err_return(rv, + cell_set(&c->cells[i], &space, 1, global.fg, global.bg)); + } + return TB_OK; +} + +static int cellbuf_get(struct cellbuf_t *c, int x, int y, + struct tb_cell **out) { + if (x < 0 || x >= c->width || y < 0 || y >= c->height) { + *out = NULL; + return TB_ERR_OUT_OF_BOUNDS; + } + *out = &c->cells[(y * c->width) + x]; + return TB_OK; +} + +static int cellbuf_resize(struct cellbuf_t *c, int w, int h) { + int rv; + + int ow = c->width; + int oh = c->height; + + if (ow == w && oh == h) { + return TB_OK; + } + + w = w < 1 ? 1 : w; + h = h < 1 ? 1 : h; + + int minw = (w < ow) ? w : ow; + int minh = (h < oh) ? h : oh; + + struct tb_cell *prev = c->cells; + + if_err_return(rv, cellbuf_init(c, w, h)); + if_err_return(rv, cellbuf_clear(c)); + + int x, y; + for (x = 0; x < minw; x++) { + for (y = 0; y < minh; y++) { + struct tb_cell *src, *dst; + src = &prev[(y * ow) + x]; + if_err_return(rv, cellbuf_get(c, x, y, &dst)); + if_err_return(rv, cell_copy(dst, src)); + } + } + + tb_free(prev); + + return TB_OK; +} + +static int bytebuf_puts(struct bytebuf_t *b, const char *str) { + if (!str || strlen(str) <= 0) return TB_OK; // Nothing to do for empty caps + return bytebuf_nputs(b, str, (size_t)strlen(str)); +} + +static int bytebuf_nputs(struct bytebuf_t *b, const char *str, size_t nstr) { + int rv; + if_err_return(rv, bytebuf_reserve(b, b->len + nstr + 1)); + memcpy(b->buf + b->len, str, nstr); + b->len += nstr; + b->buf[b->len] = '\0'; + return TB_OK; +} + +static int bytebuf_shift(struct bytebuf_t *b, size_t n) { + if (n > b->len) { + n = b->len; + } + size_t nmove = b->len - n; + memmove(b->buf, b->buf + n, nmove); + b->len -= n; + return TB_OK; +} + +static int bytebuf_flush(struct bytebuf_t *b, int fd) { + if (b->len <= 0) { + return TB_OK; + } + ssize_t write_rv = write(fd, b->buf, b->len); + if (write_rv < 0 || (size_t)write_rv != b->len) { + // Note, errno will be 0 on partial write + global.last_errno = errno; + return TB_ERR; + } + b->len = 0; + return TB_OK; +} + +static int bytebuf_reserve(struct bytebuf_t *b, size_t sz) { + if (b->cap >= sz) { + return TB_OK; + } + size_t newcap = b->cap > 0 ? b->cap : 1; + while (newcap < sz) { + newcap *= 2; + } + char *newbuf; + if (b->buf) { + newbuf = tb_realloc(b->buf, newcap); + } else { + newbuf = tb_malloc(newcap); + } + if (!newbuf) { + return TB_ERR_MEM; + } + b->buf = newbuf; + b->cap = newcap; + return TB_OK; +} + +static int bytebuf_free(struct bytebuf_t *b) { + if (b->buf) { + tb_free(b->buf); + } + memset(b, 0, sizeof(*b)); + return TB_OK; +} + +#endif /* TB_IMPL */ diff --git a/res/config.ini b/res/config.ini index 65c6baa7..68a39bbd 100644 --- a/res/config.ini +++ b/res/config.ini @@ -47,6 +47,12 @@ fg = 8 # Border color border_fg = 8 +# Title to show at the top of the main box +box_title = null + +# Initial text to show on the info line (Defaults to hostname) +initial_info_text = null + # Blank main box background # Setting to false will make it transparent blank_box = true @@ -119,6 +125,9 @@ path = /sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin # Event timeout in milliseconds min_refresh_delta = 5 +# Set numlock on/off at startup +numlock = false + # Service name (set to ly to use the provided pam config file) service_name = ly diff --git a/res/lang/en.ini b/res/lang/en.ini index 766dc864..d49882aa 100644 --- a/res/lang/en.ini +++ b/res/lang/en.ini @@ -1,3 +1,4 @@ +authenticating = authenticating... capslock = capslock err_alloc = failed memory allocation err_bounds = out-of-bounds index @@ -5,7 +6,9 @@ err_chdir = failed to open home folder err_console_dev = failed to access console err_dgn_oob = log message err_domain = invalid domain +err_envlist = failed to get envlist err_hostname = failed to get hostname +err_mcookie = mcookie command failed err_mlock = failed to lock password memory err_null = null pointer err_pam = pam transaction failed @@ -33,6 +36,8 @@ err_unknown = an unknown error occurred err_user_gid = failed to set user GID err_user_init = failed to initialize user err_user_uid = failed to set user UID +err_xauth = xauth command failed +err_xcb_conn = xcb connection failed err_xsessions_dir = failed to find sessions folder err_xsessions_open = failed to open sessions folder insert = insert diff --git a/src/animations/Doom.zig b/src/animations/Doom.zig index b4d435ec..15831658 100644 --- a/src/animations/Doom.zig +++ b/src/animations/Doom.zig @@ -56,7 +56,7 @@ pub fn draw(self: Doom) void { const source = y * self.terminal_buffer.width + x; const random = (self.terminal_buffer.random.int(u16) % 7) & 3; - var dest = source - random + 1; + var dest = (source - @min(source, random)) + 1; if (self.terminal_buffer.width > dest) dest = 0 else dest -= self.terminal_buffer.width; const buffer_source = self.buffer[source]; diff --git a/src/animations/Matrix.zig b/src/animations/Matrix.zig index fbacc412..5956eb87 100644 --- a/src/animations/Matrix.zig +++ b/src/animations/Matrix.zig @@ -145,15 +145,15 @@ pub fn draw(self: *Matrix) void { var y: u64 = 1; while (y <= self.terminal_buffer.height) : (y += 1) { const dot = self.dots[buf_width * y + x]; - var fg: u32 = @intCast(termbox.TB_GREEN); + var fg: u16 = @intCast(termbox.TB_GREEN); if (dot.value == -1 or dot.value == ' ') { - termbox.tb_change_cell(@intCast(x), @intCast(y - 1), ' ', fg, termbox.TB_DEFAULT); + _ = termbox.tb_set_cell(@intCast(x), @intCast(y - 1), ' ', fg, termbox.TB_DEFAULT); continue; } if (dot.is_head) fg = @intCast(termbox.TB_WHITE | termbox.TB_BOLD); - termbox.tb_change_cell(@intCast(x), @intCast(y - 1), @intCast(dot.value), fg, termbox.TB_DEFAULT); + _ = termbox.tb_set_cell(@intCast(x), @intCast(y - 1), @intCast(dot.value), fg, termbox.TB_DEFAULT); } } } diff --git a/src/auth.zig b/src/auth.zig index 4b061a5e..dbbdfda0 100644 --- a/src/auth.zig +++ b/src/auth.zig @@ -20,10 +20,9 @@ pub fn sessionSignalHandler(i: c_int) callconv(.C) void { if (child_pid > 0) _ = std.c.kill(child_pid, i); } -pub fn authenticate(config: Config, desktop: Desktop, login: [:0]const u8, password: [:0]const u8) !void { +pub fn authenticate(config: Config, current_environment: Desktop.Environment, login: [:0]const u8, password: [:0]const u8) !void { var tty_buffer: [2]u8 = undefined; const tty_str = try std.fmt.bufPrintZ(&tty_buffer, "{d}", .{config.tty}); - const current_environment = desktop.environments.items[desktop.current]; // Set the XDG environment variables setXdgSessionEnv(current_environment.display_server); @@ -40,6 +39,7 @@ pub fn authenticate(config: Config, desktop: Desktop, login: [:0]const u8, passw var status = interop.pam.pam_start(config.service_name.ptr, null, &conv, &handle); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); + defer _ = interop.pam.pam_end(handle, status); // Do the PAM routine status = interop.pam.pam_authenticate(handle, 0); @@ -50,9 +50,11 @@ pub fn authenticate(config: Config, desktop: Desktop, login: [:0]const u8, passw status = interop.pam.pam_setcred(handle, interop.pam.PAM_ESTABLISH_CRED); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); + defer status = interop.pam.pam_setcred(handle, interop.pam.PAM_DELETE_CRED); status = interop.pam.pam_open_session(handle, 0); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); + defer status = interop.pam.pam_close_session(handle, 0); var pwd: *interop.passwd = undefined; { @@ -81,17 +83,25 @@ pub fn authenticate(config: Config, desktop: Desktop, login: [:0]const u8, passw std.process.exit(0); } - var entry: Utmp = std.mem.zeroes(Utmp); - addUtmpEntry(&entry, pwd.pw_name, child_pid) catch {}; + var entry = std.mem.zeroes(Utmp); - // If we receive SIGTERM, forward it to child_pid - const act = std.posix.Sigaction{ - .handler = .{ .handler = &sessionSignalHandler }, - .mask = std.posix.empty_sigset, - .flags = 0, - }; - try std.posix.sigaction(std.posix.SIG.TERM, &act, null); + { + // If an error occurs here, we can send SIGTERM to the session + errdefer cleanup: { + _ = std.posix.kill(child_pid, std.posix.SIG.TERM) catch break :cleanup; + _ = std.posix.waitpid(child_pid, 0); + } + + // If we receive SIGTERM, forward it to child_pid + const act = std.posix.Sigaction{ + .handler = .{ .handler = &sessionSignalHandler }, + .mask = std.posix.empty_sigset, + .flags = 0, + }; + try std.posix.sigaction(std.posix.SIG.TERM, &act, null); + try addUtmpEntry(&entry, pwd.pw_name, child_pid); + } // Wait for the session to stop _ = std.posix.waitpid(child_pid, 0); @@ -99,16 +109,6 @@ pub fn authenticate(config: Config, desktop: Desktop, login: [:0]const u8, passw try resetTerminal(pwd.pw_shell, config.term_reset_cmd); - // Close the PAM session - status = interop.pam.pam_close_session(handle, 0); - if (status != 0) return pamDiagnose(status); - - status = interop.pam.pam_setcred(handle, interop.pam.PAM_DELETE_CRED); - if (status != 0) return pamDiagnose(status); - - status = interop.pam.pam_end(handle, status); - if (status != 0) return pamDiagnose(status); - if (shared_err.readError()) |err| return err; } @@ -118,8 +118,7 @@ fn startSession( handle: ?*interop.pam.pam_handle, current_environment: Desktop.Environment, ) !void { - var status: c_int = 0; - status = interop.initgroups(pwd.pw_name, pwd.pw_gid); + const status = interop.initgroups(pwd.pw_name, pwd.pw_gid); if (status != 0) return error.GroupInitializationFailed; std.posix.setgid(pwd.pw_gid) catch return error.SetUserGidFailed; @@ -129,15 +128,11 @@ fn startSession( try initEnv(pwd, config.path); // Set the PAM variables - const pam_env_vars = interop.pam.pam_getenvlist(handle); + const pam_env_vars: ?[*:null]?[*:0]u8 = interop.pam.pam_getenvlist(handle); + if (pam_env_vars == null) return error.GetEnvListFailed; - var index: usize = 0; - while (true) : (index += 1) { - const pam_env_var = pam_env_vars[index]; - if (pam_env_var == null) break; - - _ = interop.putenv(pam_env_var); - } + const env_list = std.mem.span(pam_env_vars.?); + for (env_list) |env_var| _ = interop.putenv(env_var.?); // Execute what the user requested std.posix.chdirZ(pwd.pw_dir) catch return error.ChangeDirectoryFailed; @@ -156,9 +151,6 @@ fn startSession( } fn initEnv(pwd: *interop.passwd, path_env: ?[:0]const u8) !void { - const term_env = std.posix.getenv("TERM"); - - if (term_env) |term| _ = interop.setenv("TERM", term, 1); _ = interop.setenv("HOME", pwd.pw_dir, 1); _ = interop.setenv("PWD", pwd.pw_dir, 1); _ = interop.setenv("SHELL", pwd.pw_shell, 1); @@ -207,7 +199,7 @@ fn loginConv( // Initialise allocated memory to 0 // This ensures memory can be freed by pam on success - for (response) |*r| r.* = std.mem.zeroes(interop.pam.pam_response); + @memset(response, std.mem.zeroes(interop.pam.pam_response)); var username: ?[:0]u8 = null; var password: ?[:0]u8 = null; @@ -335,7 +327,36 @@ fn createXauthFile(pwd: [:0]const u8) ![:0]const u8 { return xauthority; } -fn xauth(display_name: [:0]u8, shell: [*:0]const u8, pw_dir: [*:0]const u8, xauth_cmd: []const u8, mcookie_cmd: []const u8) !void { +pub fn mcookie(cmd: [:0]const u8) ![32]u8 { + const pipe = try std.posix.pipe(); + defer std.posix.close(pipe[1]); + + const output = std.fs.File{ .handle = pipe[0] }; + defer output.close(); + + const pid = try std.posix.fork(); + if (pid == 0) { + std.posix.close(pipe[0]); + + std.posix.dup2(pipe[1], std.posix.STDOUT_FILENO) catch std.process.exit(1); + std.posix.close(pipe[1]); + + const args = [_:null]?[*:0]u8{}; + std.posix.execveZ(cmd.ptr, &args, std.c.environ) catch {}; + std.process.exit(1); + } + + const result = std.posix.waitpid(pid, 0); + + if (result.status != 0) return error.McookieFailed; + + var buf: [32]u8 = undefined; + const len = try output.read(&buf); + if (len != 32) return error.McookieFailed; + return buf; +} + +fn xauth(display_name: [:0]u8, shell: [*:0]const u8, pw_dir: [*:0]const u8, xauth_cmd: []const u8, mcookie_cmd: [:0]const u8) !void { var pwd_buf: [100]u8 = undefined; const pwd = try std.fmt.bufPrintZ(&pwd_buf, "{s}", .{pw_dir}); @@ -343,16 +364,19 @@ fn xauth(display_name: [:0]u8, shell: [*:0]const u8, pw_dir: [*:0]const u8, xaut _ = interop.setenv("XAUTHORITY", xauthority, 1); _ = interop.setenv("DISPLAY", display_name, 1); + const mcookie_output = try mcookie(mcookie_cmd); + const pid = try std.posix.fork(); if (pid == 0) { var cmd_buffer: [1024]u8 = undefined; - const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} add {s} . $({s})", .{ xauth_cmd, display_name, mcookie_cmd }) catch std.process.exit(1); + const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} add {s} . {s}", .{ xauth_cmd, display_name, mcookie_output }) catch std.process.exit(1); const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str }; std.posix.execveZ(shell, &args, std.c.environ) catch {}; std.process.exit(1); } - _ = std.posix.waitpid(pid, 0); + const status = std.posix.waitpid(pid, 0); + if (status.status != 0) return error.XauthFailed; } fn executeShellCmd(shell: [*:0]const u8) !void { @@ -388,7 +412,7 @@ fn executeX11Cmd(shell: [*:0]const u8, pw_dir: [*:0]const u8, config: Config, de xcb = interop.xcb.xcb_connect(null, null); ok = interop.xcb.xcb_connection_has_error(xcb); std.posix.kill(pid, 0) catch |e| { - if (e == error.ProcessNotFound and ok != 0) return; + if (e == error.ProcessNotFound and ok != 0) return error.XcbConnectionFailed; }; } @@ -428,7 +452,7 @@ fn addUtmpEntry(entry: *Utmp, username: [*:0]const u8, pid: c_int) !void { entry.ut_pid = pid; var buf: [4096]u8 = undefined; - const ttyname = try std.os.getFdPath(0, &buf); + const ttyname = try std.os.getFdPath(std.posix.STDIN_FILENO, &buf); var ttyname_buf: [32]u8 = undefined; _ = try std.fmt.bufPrintZ(&ttyname_buf, "{s}", .{ttyname["/dev/".len..]}); diff --git a/src/bigclock.zig b/src/bigclock.zig index 46d157c1..00f79391 100644 --- a/src/bigclock.zig +++ b/src/bigclock.zig @@ -8,89 +8,89 @@ const termbox = interop.termbox; const X: u32 = if (builtin.os.tag == .linux or builtin.os.tag.isBSD()) 0x2593 else '#'; const O: u32 = 0; -pub const WIDTH: u64 = 5; -pub const HEIGHT: u64 = 5; +pub const WIDTH = 5; +pub const HEIGHT = 5; pub const SIZE = WIDTH * HEIGHT; // zig fmt: off -const ZERO = [_]u32{ +const ZERO = [_]u21{ X,X,X,X,X, X,X,O,X,X, X,X,O,X,X, X,X,O,X,X, X,X,X,X,X, }; -const ONE = [_]u32{ +const ONE = [_]u21{ O,O,O,X,X, O,O,O,X,X, O,O,O,X,X, O,O,O,X,X, O,O,O,X,X, }; -const TWO = [_]u32{ +const TWO = [_]u21{ X,X,X,X,X, O,O,O,X,X, X,X,X,X,X, X,X,O,O,O, X,X,X,X,X, }; -const THREE = [_]u32{ +const THREE = [_]u21{ X,X,X,X,X, O,O,O,X,X, X,X,X,X,X, O,O,O,X,X, X,X,X,X,X, }; -const FOUR = [_]u32{ +const FOUR = [_]u21{ X,X,O,X,X, X,X,O,X,X, X,X,X,X,X, O,O,O,X,X, O,O,O,X,X, }; -const FIVE = [_]u32{ +const FIVE = [_]u21{ X,X,X,X,X, X,X,O,O,O, X,X,X,X,X, O,O,O,X,X, X,X,X,X,X, }; -const SIX = [_]u32{ +const SIX = [_]u21{ X,X,X,X,X, X,X,O,O,O, X,X,X,X,X, X,X,O,X,X, X,X,X,X,X, }; -const SEVEN = [_]u32{ +const SEVEN = [_]u21{ X,X,X,X,X, O,O,O,X,X, O,O,O,X,X, O,O,O,X,X, O,O,O,X,X, }; -const EIGHT = [_]u32{ +const EIGHT = [_]u21{ X,X,X,X,X, X,X,O,X,X, X,X,X,X,X, X,X,O,X,X, X,X,X,X,X, }; -const NINE = [_]u32{ +const NINE = [_]u21{ X,X,X,X,X, X,X,O,X,X, X,X,X,X,X, O,O,O,X,X, X,X,X,X,X, }; -const S = [_]u32{ +const S = [_]u21{ O,O,O,O,O, O,O,X,O,O, O,O,O,O,O, O,O,X,O,O, O,O,O,O,O, }; -const E = [_]u32{ +const E = [_]u21{ O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, @@ -122,7 +122,7 @@ pub fn alphaBlit(buffer: [*]termbox.tb_cell, x: u64, y: u64, tb_width: u64, tb_h } } -fn toBigNumber(char: u8) []const u32 { +fn toBigNumber(char: u8) []const u21 { return switch (char) { '0' => &ZERO, '1' => &ONE, diff --git a/src/config/Config.zig b/src/config/Config.zig index b5fdeda3..bddda19e 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -10,6 +10,7 @@ bg: u8 = 0, bigclock: bool = false, blank_box: bool = true, border_fg: u8 = 8, +box_title: ?[]const u8 = null, clear_password: bool = false, clock: ?[:0]const u8 = null, console_dev: [:0]const u8 = "/dev/console", @@ -17,6 +18,7 @@ default_input: Input = .login, fg: u8 = 8, hide_borders: bool = false, hide_key_hints: bool = false, +initial_info_text: ?[]const u8 = null, input_len: u8 = 34, lang: []const u8 = "en", load: bool = true, @@ -25,8 +27,9 @@ margin_box_v: u8 = 1, max_desktop_len: u8 = 100, max_login_len: u8 = 255, max_password_len: u8 = 255, -mcookie_cmd: []const u8 = "/usr/bin/mcookie", +mcookie_cmd: [:0]const u8 = "/usr/bin/mcookie", min_refresh_delta: u16 = 5, +numlock: bool = false, path: ?[:0]const u8 = "/sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin", restart_cmd: []const u8 = "/sbin/shutdown -r now", restart_key: []const u8 = "F2", diff --git a/src/config/Lang.zig b/src/config/Lang.zig index 0333e0b2..8c4051ca 100644 --- a/src/config/Lang.zig +++ b/src/config/Lang.zig @@ -1,3 +1,4 @@ +authenticating: []const u8 = "authenticating...", capslock: []const u8 = "capslock", err_alloc: []const u8 = "failed memory allocation", err_bounds: []const u8 = "out-of-bounds index", @@ -5,7 +6,9 @@ err_chdir: []const u8 = "failed to open home folder", err_console_dev: []const u8 = "failed to access console", err_dgn_oob: []const u8 = "log message", err_domain: []const u8 = "invalid domain", +err_envlist: []const u8 = "failed to get envlist", err_hostname: []const u8 = "failed to get hostname", +err_mcookie: []const u8 = "mcookie command failed", err_mlock: []const u8 = "failed to lock password memory", err_null: []const u8 = "null pointer", err_pam: []const u8 = "pam transaction failed", @@ -33,6 +36,8 @@ err_unknown: []const u8 = "an unknown error occurred", err_user_gid: []const u8 = "failed to set user GID", err_user_init: []const u8 = "failed to initialize user", err_user_uid: []const u8 = "failed to set user UID", +err_xauth: []const u8 = "xauth command failed", +err_xcb_conn: []const u8 = "xcb connection failed", err_xsessions_dir: []const u8 = "failed to find sessions folder", err_xsessions_open: []const u8 = "failed to open sessions folder", insert: []const u8 = "insert", diff --git a/src/interop.zig b/src/interop.zig index a053747f..14ce55af 100644 --- a/src/interop.zig +++ b/src/interop.zig @@ -2,9 +2,7 @@ const std = @import("std"); const builtin = @import("builtin"); const Allocator = std.mem.Allocator; -pub const termbox = @cImport({ - @cInclude("termbox.h"); -}); +pub const termbox = @import("termbox2"); pub const pam = @cImport({ @cInclude("security/pam_appl.h"); @@ -48,7 +46,9 @@ pub const VT_ACTIVATE: c_int = 0x5606; pub const VT_WAITACTIVE: c_int = 0x5607; pub const KDGETLED: c_int = 0x4B31; +pub const KDSETLED: c_int = 0x4B32; pub const KDGKBLED: c_int = 0x4B64; +pub const KDSKBLED: c_int = 0x4B65; pub const LED_NUM: c_int = 0x02; pub const LED_CAP: c_int = 0x04; @@ -106,3 +106,14 @@ pub fn getLockState(console_dev: [:0]const u8) !struct { .capslock = capslock, }; } + +pub fn setNumlock(val: bool) !void { + var led: c_char = undefined; + _ = std.c.ioctl(0, KDGKBLED, &led); + + const numlock = (led & K_NUMLOCK) != 0; + if (numlock != val) { + const status = std.c.ioctl(std.posix.STDIN_FILENO, KDSKBLED, led ^ K_NUMLOCK); + if (status != 0) return error.FailedToSetNumlock; + } +} diff --git a/src/main.zig b/src/main.zig index bff277be..6171361f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -33,7 +33,7 @@ pub fn signalHandler(i: c_int) callconv(.C) void { _ = std.c.waitpid(session_pid, &status, 0); } - termbox.tb_shutdown(); + _ = termbox.tb_shutdown(); std.c.exit(i); } @@ -127,8 +127,12 @@ pub fn main() !void { } } - // Initialize information line with host name - get_host_name: { + interop.setNumlock(config.numlock) catch {}; + + if (config.initial_info_text) |text| { + try info_line.setText(text); + } else get_host_name: { + // Initialize information line with host name var name_buf: [std.posix.HOST_NAME_MAX]u8 = undefined; const hostname = std.posix.gethostname(&name_buf) catch { try info_line.setText(lang.err_hostname); @@ -139,7 +143,7 @@ pub fn main() !void { // Initialize termbox _ = termbox.tb_init(); - defer termbox.tb_shutdown(); + defer _ = termbox.tb_shutdown(); const act = std.posix.Sigaction{ .handler = .{ .handler = &signalHandler }, @@ -148,8 +152,8 @@ pub fn main() !void { }; try std.posix.sigaction(std.posix.SIG.TERM, &act, null); - _ = termbox.tb_select_output_mode(termbox.TB_OUTPUT_NORMAL); - termbox.tb_clear(); + _ = termbox.tb_set_output_mode(termbox.TB_OUTPUT_NORMAL); + _ = termbox.tb_clear(); // Needed to reset termbox after auth const tb_termios = try std.posix.tcgetattr(std.posix.STDIN_FILENO); @@ -252,13 +256,11 @@ pub fn main() !void { // Switch to selected TTY if possible open_console_dev: { - const fd = std.c.open(config.console_dev, .{ .ACCMODE = .WRONLY }); - defer _ = std.c.close(fd); - - if (fd < 0) { + const fd = std.posix.open(config.console_dev, .{ .ACCMODE = .WRONLY }, 0) catch { try info_line.setText(lang.err_console_dev); break :open_console_dev; - } + }; + defer std.posix.close(fd); _ = std.c.ioctl(fd, interop.VT_ACTIVATE, config.tty); _ = std.c.ioctl(fd, interop.VT_WAITACTIVE, config.tty); @@ -267,9 +269,9 @@ pub fn main() !void { while (run) { // If there's no input or there's an animation, a resolution change needs to be checked if (!update or config.animation != .none) { - if (!update) std.time.sleep(100_000_000); + if (!update) std.time.sleep(std.time.ns_per_ms * 100); - termbox.tb_present(); // Required to update tb_width(), tb_height() and tb_cell_buffer() + _ = termbox.tb_present(); // Required to update tb_width(), tb_height() and tb_cell_buffer() const width: u64 = @intCast(termbox.tb_width()); const height: u64 = @intCast(termbox.tb_height()); @@ -305,17 +307,7 @@ pub fn main() !void { if (update) { // 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, insert_mode), - .login => login.handle(null, insert_mode) catch { - try info_line.setText(lang.err_alloc); - }, - .password => password.handle(null, insert_mode) catch { - try info_line.setText(lang.err_alloc); - }, - } - - termbox.tb_clear(); + _ = termbox.tb_clear(); switch (config.animation) { .none => {}, @@ -325,7 +317,7 @@ pub fn main() !void { if (config.bigclock and buffer.box_height + (bigclock.HEIGHT + 2) * 2 < buffer.height) draw_big_clock: { const format = "%H:%M"; - const xo = buffer.width / 2 - (format.len * (bigclock.WIDTH + 1)) / 2; + const xo = buffer.width / 2 - @min(buffer.width, (format.len * (bigclock.WIDTH + 1))) / 2; const yo = (buffer.height - buffer.box_height) / 2 - bigclock.HEIGHT - 2; var clock_buf: [format.len + 1:0]u8 = undefined; @@ -341,6 +333,25 @@ pub fn main() !void { buffer.drawBoxCenter(!config.hide_borders, config.blank_box); + if (resolution_changed) { + const coordinates = buffer.calculateComponentCoordinates(); + desktop.position(coordinates.x, coordinates.y + 2, coordinates.visible_length); + login.position(coordinates.x, coordinates.y + 4, coordinates.visible_length); + password.position(coordinates.x, coordinates.y + 6, coordinates.visible_length); + + resolution_changed = false; + } + + switch (active_input) { + .session => desktop.handle(null, insert_mode), + .login => login.handle(null, insert_mode) catch { + try info_line.setText(lang.err_alloc); + }, + .password => password.handle(null, insert_mode) catch { + try info_line.setText(lang.err_alloc); + }, + } + if (config.clock) |clock| draw_clock: { var clock_buf: [32:0]u8 = undefined; const clock_str = interop.timeAsString(&clock_buf, clock) catch { @@ -349,7 +360,7 @@ pub fn main() !void { if (clock_str.len == 0) return error.FormattedTimeEmpty; - buffer.drawLabel(clock_str, buffer.width - clock_str.len, 0); + buffer.drawLabel(clock_str, buffer.width - @min(buffer.width, clock_str.len), 0); } const label_x = buffer.box_x + buffer.margin_box_h; @@ -358,10 +369,7 @@ pub fn main() !void { buffer.drawLabel(lang.login, label_x, label_y + 4); buffer.drawLabel(lang.password, label_x, label_y + 6); - if (info_line.width > 0 and buffer.box_width > info_line.width) { - const x = buffer.box_x + ((buffer.box_width - info_line.width) / 2); - buffer.drawLabel(info_line.text, x, label_y); - } + info_line.draw(buffer); if (!config.hide_key_hints) { var length: u64 = 0; @@ -389,9 +397,13 @@ pub fn main() !void { } } + if (config.box_title) |title| { + buffer.drawConfinedLabel(title, buffer.box_x, buffer.box_y - 1, buffer.box_width); + } + if (config.vi_mode) { const label_txt = if (insert_mode) lang.insert else lang.normal; - buffer.drawLabel(label_txt, buffer.box_x, buffer.box_y - 1); + buffer.drawLabel(label_txt, buffer.box_x, buffer.box_y + buffer.box_height); } draw_lock_state: { @@ -400,21 +412,15 @@ pub fn main() !void { break :draw_lock_state; }; - var lock_state_x = buffer.width - lang.numlock.len; + var lock_state_x = buffer.width - @min(buffer.width, lang.numlock.len); const lock_state_y: u64 = if (config.clock != null) 1 else 0; if (lock_state.numlock) buffer.drawLabel(lang.numlock, lock_state_x, lock_state_y); - lock_state_x -= lang.capslock.len + 1; - if (lock_state.capslock) buffer.drawLabel(lang.capslock, lock_state_x, lock_state_y); - } - if (resolution_changed) { - const coordinates = buffer.calculateComponentCoordinates(); - desktop.position(coordinates.x, coordinates.y + 2, coordinates.visible_length); - login.position(coordinates.x, coordinates.y + 4, coordinates.visible_length); - password.position(coordinates.x, coordinates.y + 6, coordinates.visible_length); - - resolution_changed = false; + if (lock_state_x >= lang.capslock.len + 1) { + lock_state_x -= lang.capslock.len + 1; + if (lock_state.capslock) buffer.drawLabel(lang.capslock, lock_state_x, lock_state_y); + } } desktop.draw(); @@ -423,16 +429,16 @@ pub fn main() !void { update = animate; } else { - std.time.sleep(10_000_000); + std.time.sleep(std.time.ns_per_ms * 10); update = buffer.cascade(); if (!update) { - std.time.sleep(7_000_000_000); + std.time.sleep(std.time.ns_per_s * 7); auth_fails = 0; } } - termbox.tb_present(); + _ = termbox.tb_present(); } var timeout: i32 = -1; @@ -510,9 +516,18 @@ pub fn main() !void { }; update = true; }, + termbox.TB_KEY_BACK_TAB => { + active_input = switch (active_input) { + .session => .password, + .login => .session, + .password => .login, + }; + + update = true; + }, termbox.TB_KEY_ENTER => { if (config.save) save_last_settings: { - var file = std.fs.createFileAbsolute(save_path, .{}) catch break :save_last_settings; + var file = std.fs.cwd().createFile(save_path, .{}) catch break :save_last_settings; defer file.close(); const save_data = Save{ @@ -531,9 +546,15 @@ pub fn main() !void { const password_text = try allocator.dupeZ(u8, password.text.items); defer allocator.free(password_text); + try info_line.setText(lang.authenticating); + InfoLine.clearRendered(allocator, buffer) catch {}; + info_line.draw(buffer); + _ = termbox.tb_present(); + session_pid = try std.posix.fork(); if (session_pid == 0) { - auth.authenticate(config, desktop, login_text, password_text) catch |err| { + const current_environment = desktop.environments.items[desktop.current]; + auth.authenticate(config, current_environment, login_text, password_text) catch |err| { shared_err.writeError(err); std.process.exit(1); }; @@ -556,14 +577,15 @@ pub fn main() !void { } try std.posix.tcsetattr(std.posix.STDIN_FILENO, .FLUSH, tb_termios); - termbox.tb_clear(); + if (auth_fails < 10) { + _ = termbox.tb_clear(); + _ = termbox.tb_present(); + } update = true; var restore_cursor = std.ChildProcess.init(&[_][]const u8{ "/bin/sh", "-c", config.term_restore_cursor_cmd }, allocator); _ = restore_cursor.spawnAndWait() catch .{}; - - termbox.tb_present(); }, else => { if (!insert_mode) { @@ -617,6 +639,10 @@ pub fn main() !void { fn getAuthErrorMsg(err: anyerror, lang: Lang) []const u8 { return switch (err) { error.GetPasswordNameFailed => lang.err_pwnam, + error.GetEnvListFailed => lang.err_envlist, + error.XauthFailed => lang.err_xauth, + error.McookieFailed => lang.err_mcookie, + error.XcbConnectionFailed => lang.err_xcb_conn, error.GroupInitializationFailed => lang.err_user_init, error.SetUserGidFailed => lang.err_user_gid, error.SetUserUidFailed => lang.err_user_uid, diff --git a/src/tui/TerminalBuffer.zig b/src/tui/TerminalBuffer.zig index 8e95886c..eec1a85e 100644 --- a/src/tui/TerminalBuffer.zig +++ b/src/tui/TerminalBuffer.zig @@ -100,34 +100,35 @@ pub fn cascade(self: TerminalBuffer) bool { } pub fn drawBoxCenter(self: *TerminalBuffer, show_borders: bool, blank_box: bool) void { - const x1 = (self.width - self.box_width) / 2; - const y1 = (self.height - self.box_height) / 2; - const x2 = (self.width + self.box_width) / 2; - const y2 = (self.height + self.box_height) / 2; + if (self.width < 2 or self.height < 2) return; + const x1 = (self.width - @min(self.width - 2, self.box_width)) / 2; + const y1 = (self.height - @min(self.height - 2, self.box_height)) / 2; + const x2 = (self.width + @min(self.width, self.box_width)) / 2; + const y2 = (self.height + @min(self.height, self.box_height)) / 2; self.box_x = x1; self.box_y = y1; if (show_borders) { - termbox.tb_change_cell(@intCast(x1 - 1), @intCast(y1 - 1), self.box_chars.left_up, self.border_fg, self.bg); - termbox.tb_change_cell(@intCast(x2), @intCast(y1 - 1), self.box_chars.right_up, self.border_fg, self.bg); - termbox.tb_change_cell(@intCast(x1 - 1), @intCast(y2), self.box_chars.left_down, self.border_fg, self.bg); - termbox.tb_change_cell(@intCast(x2), @intCast(y2), self.box_chars.right_down, self.border_fg, self.bg); + _ = termbox.tb_set_cell(@intCast(x1 - 1), @intCast(y1 - 1), self.box_chars.left_up, self.border_fg, self.bg); + _ = termbox.tb_set_cell(@intCast(x2), @intCast(y1 - 1), self.box_chars.right_up, self.border_fg, self.bg); + _ = termbox.tb_set_cell(@intCast(x1 - 1), @intCast(y2), self.box_chars.left_down, self.border_fg, self.bg); + _ = termbox.tb_set_cell(@intCast(x2), @intCast(y2), self.box_chars.right_down, self.border_fg, self.bg); var c1 = utils.initCell(self.box_chars.top, self.border_fg, self.bg); var c2 = utils.initCell(self.box_chars.bottom, self.border_fg, self.bg); for (0..self.box_width) |i| { - termbox.tb_put_cell(@intCast(x1 + i), @intCast(y1 - 1), &c1); - termbox.tb_put_cell(@intCast(x1 + i), @intCast(y2), &c2); + _ = utils.putCell(@intCast(x1 + i), @intCast(y1 - 1), &c1); + _ = utils.putCell(@intCast(x1 + i), @intCast(y2), &c2); } c1.ch = self.box_chars.left; c2.ch = self.box_chars.right; for (0..self.box_height) |i| { - termbox.tb_put_cell(@intCast(x1 - 1), @intCast(y1 + i), &c1); - termbox.tb_put_cell(@intCast(x2), @intCast(y1 + i), &c2); + _ = utils.putCell(@intCast(x1 - 1), @intCast(y1 + i), &c1); + _ = utils.putCell(@intCast(x2), @intCast(y1 + i), &c2); } } @@ -136,7 +137,7 @@ pub fn drawBoxCenter(self: *TerminalBuffer, show_borders: bool, blank_box: bool) for (0..self.box_height) |y| { for (0..self.box_width) |x| { - termbox.tb_put_cell(@intCast(x1 + x), @intCast(y1 + y), &blank); + _ = utils.putCell(@intCast(x1 + x), @intCast(y1 + y), &blank); } } } @@ -165,7 +166,19 @@ pub fn drawLabel(self: TerminalBuffer, text: []const u8, x: u64, y: u64) void { var i = x; while (utf8.nextCodepoint()) |codepoint| : (i += 1) { - termbox.tb_change_cell(@intCast(i), yc, codepoint, self.fg, self.bg); + _ = termbox.tb_set_cell(@intCast(i), yc, codepoint, self.fg, self.bg); + } +} + +pub fn drawConfinedLabel(self: TerminalBuffer, text: []const u8, x: u64, y: u64, max_length: u64) void { + const yc: c_int = @intCast(y); + const utf8view = std.unicode.Utf8View.init(text) catch return; + var utf8 = utf8view.iterator(); + + var i: usize = 0; + while (utf8.nextCodepoint()) |codepoint| : (i += 1) { + if (i >= max_length) break; + _ = termbox.tb_set_cell(@intCast(i + x), yc, codepoint, self.fg, self.bg); } } @@ -173,5 +186,5 @@ pub fn drawCharMultiple(self: TerminalBuffer, char: u8, x: u64, y: u64, length: const yc: c_int = @intCast(y); const cell = utils.initCell(char, self.fg, self.bg); - for (0..length) |xx| termbox.tb_put_cell(@intCast(x + xx), yc, &cell); + for (0..length) |xx| _ = utils.putCell(@intCast(x + xx), yc, &cell); } diff --git a/src/tui/components/Desktop.zig b/src/tui/components/Desktop.zig index 784918e2..98870d04 100644 --- a/src/tui/components/Desktop.zig +++ b/src/tui/components/Desktop.zig @@ -185,7 +185,7 @@ pub fn handle(self: *Desktop, maybe_event: ?*termbox.tb_event, insert_mode: bool } } - termbox.tb_set_cursor(@intCast(self.x + 2), @intCast(self.y)); + _ = termbox.tb_set_cursor(@intCast(self.x + 2), @intCast(self.y)); } pub fn draw(self: Desktop) void { @@ -198,8 +198,8 @@ pub fn draw(self: Desktop) void { const y = self.buffer.box_y + self.buffer.margin_box_v + 2; self.buffer.drawLabel(environment.specifier, x, y); - termbox.tb_change_cell(@intCast(self.x), @intCast(self.y), '<', self.buffer.fg, self.buffer.bg); - termbox.tb_change_cell(@intCast(self.x + self.visible_length - 1), @intCast(self.y), '>', self.buffer.fg, self.buffer.bg); + _ = termbox.tb_set_cell(@intCast(self.x), @intCast(self.y), '<', self.buffer.fg, self.buffer.bg); + _ = termbox.tb_set_cell(@intCast(self.x + self.visible_length - 1), @intCast(self.y), '>', self.buffer.fg, self.buffer.bg); self.buffer.drawLabel(environment.name, self.x + 2, self.y); } diff --git a/src/tui/components/InfoLine.zig b/src/tui/components/InfoLine.zig index 0a216a92..82d33ee2 100644 --- a/src/tui/components/InfoLine.zig +++ b/src/tui/components/InfoLine.zig @@ -1,4 +1,6 @@ +const std = @import("std"); const utils = @import("../utils.zig"); +const TerminalBuffer = @import("../TerminalBuffer.zig"); const InfoLine = @This(); @@ -9,3 +11,23 @@ pub fn setText(self: *InfoLine, text: []const u8) !void { self.width = if (text.len > 0) try utils.strWidth(text) else 0; self.text = text; } + +pub fn draw(self: InfoLine, buffer: TerminalBuffer) void { + if (self.width > 0 and buffer.box_width > self.width) { + const label_y = buffer.box_y + buffer.margin_box_v; + const x = buffer.box_x + ((buffer.box_width - self.width) / 2); + + buffer.drawLabel(self.text, x, label_y); + } +} + +pub fn clearRendered(allocator: std.mem.Allocator, buffer: TerminalBuffer) !void { + // draw over the area + const y = buffer.box_y + buffer.margin_box_v; + const spaces = try allocator.alloc(u8, buffer.box_width); + defer allocator.free(spaces); + + @memset(spaces, ' '); + + buffer.drawLabel(spaces, buffer.box_x, y); +} diff --git a/src/tui/components/Text.zig b/src/tui/components/Text.zig index fe8b0d16..412476a8 100644 --- a/src/tui/components/Text.zig +++ b/src/tui/components/Text.zig @@ -78,14 +78,21 @@ pub fn handle(self: *Text, maybe_event: ?*termbox.tb_event, insert_mode: bool) ! } } - termbox.tb_set_cursor(@intCast(self.x + (self.cursor - self.visible_start)), @intCast(self.y)); + _ = termbox.tb_set_cursor(@intCast(self.x + (self.cursor - self.visible_start)), @intCast(self.y)); } pub fn draw(self: Text) void { const length = @min(self.text.items.len, self.visible_length); if (length == 0) return; - const visible_slice = if (self.text.items.len > self.visible_length and self.cursor < self.text.items.len) self.text.items[self.visible_start..(self.visible_length + self.visible_start)] else self.text.items[self.visible_start..]; + const visible_slice = vs: { + if (self.text.items.len > self.visible_length and self.cursor < self.text.items.len) { + break :vs self.text.items[self.visible_start..(self.visible_length + self.visible_start)]; + } else { + break :vs self.text.items[self.visible_start..]; + } + }; + self.buffer.drawLabel(visible_slice, self.x, self.y); } diff --git a/src/tui/utils.zig b/src/tui/utils.zig index 37fe2d0c..5b7e067b 100644 --- a/src/tui/utils.zig +++ b/src/tui/utils.zig @@ -3,7 +3,7 @@ const interop = @import("../interop.zig"); const termbox = interop.termbox; -pub fn initCell(ch: u32, fg: u32, bg: u32) termbox.tb_cell { +pub fn initCell(ch: u32, fg: u16, bg: u16) termbox.tb_cell { return .{ .ch = ch, .fg = fg, @@ -11,6 +11,10 @@ pub fn initCell(ch: u32, fg: u32, bg: u32) termbox.tb_cell { }; } +pub fn putCell(x: i32, y: i32, cell: *const termbox.tb_cell) c_int { + return termbox.tb_set_cell(x, y, cell.ch, cell.fg, cell.bg); +} + // Every codepoint is assumed to have a width of 1. // Since ly should be running in a tty, this should be fine. pub fn strWidth(str: []const u8) !u8 {