diff --git a/.gitignore b/.gitignore index 65ddc50..60f36fa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -bin -obj -valgrind.log +.idea/ +zig-cache/ +zig-out/ +valgrind.log \ No newline at end of file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 250adbe..0000000 --- a/.gitmodules +++ /dev/null @@ -1,12 +0,0 @@ -[submodule "sub/argoat"] - path = sub/argoat - url = https://github.com/nullgemm/argoat.git -[submodule "sub/configator"] - path = sub/configator - url = https://github.com/nullgemm/configator.git -[submodule "sub/dragonfail"] - path = sub/dragonfail - url = https://github.com/nullgemm/dragonfail.git -[submodule "sub/termbox_next"] - path = sub/termbox_next - url = https://github.com/nullgemm/termbox_next.git diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..13c5140 --- /dev/null +++ b/build.zig @@ -0,0 +1,221 @@ +const std = @import("std"); + +const ly_version = std.SemanticVersion{ .major = 1, .minor = 0, .patch = 0, .build = "dev" }; + +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)"); + + 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, "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" }, + .target = target, + .optimize = optimize, + }); + + const zigini = b.dependency("zigini", .{ .target = target, .optimize = optimize }); + exe.root_module.addImport("zigini", zigini.module("zigini")); + + exe.root_module.addOptions("build_options", build_options); + + const clap = b.dependency("clap", .{ .target = target, .optimize = optimize }); + exe.root_module.addImport("clap", clap.module("clap")); + + exe.linkSystemLibrary("pam"); + exe.linkSystemLibrary("xcb"); + exe.linkLibC(); + + exe.addIncludePath(.{ .path = "dep/termbox_next/src" }); + + 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 }); + + b.installArtifact(exe); + + const run_cmd = b.addRunArtifact(exe); + + run_cmd.step.dependOn(b.getInstallStep()); + + if (b.args) |args| run_cmd.addArgs(args); + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + + const installexe_step = b.step("installexe", "Install Ly"); + installexe_step.makeFn = installexe; + installexe_step.dependOn(b.getInstallStep()); + + const installnoconf_step = b.step("installnoconf", "Install Ly without its configuration file"); + installnoconf_step.makeFn = installnoconf; + installnoconf_step.dependOn(b.getInstallStep()); + + const installsystemd_step = b.step("installsystemd", "Install the Ly systemd service"); + installsystemd_step.makeFn = installsystemd; + installsystemd_step.dependOn(installexe_step); + + const installopenrc_step = b.step("installopenrc", "Install the Ly openrc service"); + installopenrc_step.makeFn = installopenrc; + installopenrc_step.dependOn(installexe_step); + + const installrunit_step = b.step("installrunit", "Install the Ly runit service"); + installrunit_step.makeFn = installrunit; + 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", .{}); + }; + + 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", .{}); + }; +} + +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", .{}); + }; + + std.fs.makeDirAbsolute("/etc/ly/lang") catch { + std.debug.print("warn: /etc/ly/lang already exists as a directory.\n", .{}); + }; + + var current_dir = std.fs.cwd(); + + { + var executable_dir = std.fs.openDirAbsolute("/usr/bin", .{}) catch unreachable; + defer executable_dir.close(); + + try current_dir.copyFile("zig-out/bin/ly", executable_dir, "ly", .{}); + } + + { + var config_dir = std.fs.openDirAbsolute("/etc/ly", .{}) catch unreachable; + defer config_dir.close(); + + if (install_config) { + try current_dir.copyFile("res/config.ini", config_dir, "config.ini", .{}); + } + try current_dir.copyFile("res/xsetup.sh", config_dir, "xsetup.sh", .{}); + try current_dir.copyFile("res/wsetup.sh", config_dir, "wsetup.sh", .{}); + } + + { + var lang_dir = std.fs.openDirAbsolute("/etc/ly/lang", .{}) catch unreachable; + defer lang_dir.close(); + + try current_dir.copyFile("res/lang/cat.ini", lang_dir, "cat.ini", .{}); + try current_dir.copyFile("res/lang/cs.ini", lang_dir, "cs.ini", .{}); + try current_dir.copyFile("res/lang/de.ini", lang_dir, "de.ini", .{}); + try current_dir.copyFile("res/lang/en.ini", lang_dir, "en.ini", .{}); + try current_dir.copyFile("res/lang/es.ini", lang_dir, "es.ini", .{}); + try current_dir.copyFile("res/lang/fr.ini", lang_dir, "fr.ini", .{}); + try current_dir.copyFile("res/lang/it.ini", lang_dir, "it.ini", .{}); + try current_dir.copyFile("res/lang/pl.ini", lang_dir, "pl.ini", .{}); + try current_dir.copyFile("res/lang/pt.ini", lang_dir, "pt.ini", .{}); + try current_dir.copyFile("res/lang/pt_BR.ini", lang_dir, "pt_BR.ini", .{}); + try current_dir.copyFile("res/lang/ro.ini", lang_dir, "ro.ini", .{}); + try current_dir.copyFile("res/lang/ru.ini", lang_dir, "ru.ini", .{}); + try current_dir.copyFile("res/lang/sr.ini", lang_dir, "sr.ini", .{}); + try current_dir.copyFile("res/lang/sv.ini", lang_dir, "sv.ini", .{}); + try current_dir.copyFile("res/lang/tr.ini", lang_dir, "tr.ini", .{}); + try current_dir.copyFile("res/lang/uk.ini", lang_dir, "uk.ini", .{}); + } + + { + var pam_dir = std.fs.openDirAbsolute("/etc/pam.d", .{}) catch unreachable; + defer pam_dir.close(); + + try current_dir.copyFile("res/pam.d/ly", pam_dir, "ly", .{ .override_mode = 644 }); + } +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..0594da1 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,15 @@ +.{ + .name = "ly", + .version = "1.0.0", + .dependencies = .{ + .clap = .{ + .url = "https://github.com/Hejsil/zig-clap/archive/8c98e6404b22aafc0184e999d8f068b81cc22fa1.tar.gz", + .hash = "122014e73fd712190e109950837b97f6143f02d7e2b6986e1db70b6f4aadb5ba6a0d", + }, + .zigini = .{ + .url = "https://github.com/Kawaii-Ash/zigini/archive/ce1f322482099db058f5d9fdd05fbfa255d79723.tar.gz", + .hash = "1220e7a99793a0430e0a7c0b938cb3c98321035bc297e21cd0e2413cf740b4923b9f", + }, + }, + .paths = .{""}, +} diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..2a1be49 --- /dev/null +++ b/changelog.md @@ -0,0 +1,47 @@ +# Zig Rewrite (Version 1.0.0) + +## Config Options + +res/config.ini contains all of the available config options and their default values. + +### Additions + ++ `border_fg` has been introduced to change the color of the borders. ++ `term_restore_cursor_cmd` should restore the cursor to it's usual state. ++ `vi_mode` to enable vi keybindings. ++ `sleep_key` and `sleep_cmd`. + +Note: `sleep_cmd` is unset by default, meaning it's hidden and has no effect. + +### Changes + ++ xinitrc can be set to null to hide it. ++ `blank_password` has been renamed to `clear_password`. ++ `save_file` has been deprecated and will be removed in a future version. + +### Removals + ++ `wayland_specifier` has been removed. + +## Save File + +The save file is now in .ini format and stored in the same directory as the config. +Older save files will be migrated to the new format. + +Example: + +```ini +user = ash +session_index = 0 +``` + +## Misc + ++ Display server name added next to selected session. ++ getty@tty2 has been added as a conflict in res/ly.service, so if it is running, ly should still be able to start. ++ `XDG_CURRENT_DESKTOP` is now set by ly. ++ LANG is no longer set by ly. ++ X Server PID is fetched from /tmp/X{d}.lock to be able to kill the process since it detaches. ++ 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. diff --git a/dep/termbox_next/.gitignore b/dep/termbox_next/.gitignore new file mode 100644 index 0000000..afcccde --- /dev/null +++ b/dep/termbox_next/.gitignore @@ -0,0 +1,7 @@ +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 new file mode 100644 index 0000000..e9bb4ea --- /dev/null +++ b/dep/termbox_next/license @@ -0,0 +1,19 @@ +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 new file mode 100644 index 0000000..d20e68d --- /dev/null +++ b/dep/termbox_next/readme.md @@ -0,0 +1,57 @@ +# 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 new file mode 100644 index 0000000..3c25c5a --- /dev/null +++ b/dep/termbox_next/src/demo/keyboard.c @@ -0,0 +1,827 @@ +#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 new file mode 100644 index 0000000..74ec8dd --- /dev/null +++ b/dep/termbox_next/src/demo/makefile @@ -0,0 +1,30 @@ +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 new file mode 100644 index 0000000..447975e --- /dev/null +++ b/dep/termbox_next/src/demo/output.c @@ -0,0 +1,156 @@ +#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 new file mode 100644 index 0000000..1f68c2d --- /dev/null +++ b/dep/termbox_next/src/demo/paint.c @@ -0,0 +1,183 @@ +#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 new file mode 100644 index 0000000..33609fd --- /dev/null +++ b/dep/termbox_next/src/demo/truecolor.c @@ -0,0 +1,69 @@ +#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 new file mode 100644 index 0000000..2d3f84d --- /dev/null +++ b/dep/termbox_next/src/input.c @@ -0,0 +1,319 @@ +#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 new file mode 100644 index 0000000..d218b54 --- /dev/null +++ b/dep/termbox_next/src/memstream.c @@ -0,0 +1,36 @@ +#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 new file mode 100644 index 0000000..c5d864a --- /dev/null +++ b/dep/termbox_next/src/memstream.h @@ -0,0 +1,20 @@ +#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 new file mode 100644 index 0000000..a8de0f6 --- /dev/null +++ b/dep/termbox_next/src/ringbuffer.c @@ -0,0 +1,195 @@ +#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 new file mode 100644 index 0000000..9a8b0d6 --- /dev/null +++ b/dep/termbox_next/src/ringbuffer.h @@ -0,0 +1,26 @@ +#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 new file mode 100644 index 0000000..c94f394 --- /dev/null +++ b/dep/termbox_next/src/term.c @@ -0,0 +1,412 @@ +#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 new file mode 100644 index 0000000..8f4d93d --- /dev/null +++ b/dep/termbox_next/src/term.h @@ -0,0 +1,38 @@ +#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 new file mode 100644 index 0000000..82e30fc --- /dev/null +++ b/dep/termbox_next/src/termbox.c @@ -0,0 +1,885 @@ +#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 new file mode 100644 index 0000000..c3cbcb6 --- /dev/null +++ b/dep/termbox_next/src/termbox.h @@ -0,0 +1,307 @@ +#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 new file mode 100644 index 0000000..43efd7f --- /dev/null +++ b/dep/termbox_next/src/utf8.c @@ -0,0 +1,106 @@ +#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 new file mode 100644 index 0000000..a296bc3 --- /dev/null +++ b/dep/termbox_next/tools/astylerc @@ -0,0 +1,27 @@ +--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 new file mode 100755 index 0000000..596c3c4 --- /dev/null +++ b/dep/termbox_next/tools/collect_terminfo.py @@ -0,0 +1,108 @@ +#!/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/makefile b/makefile deleted file mode 100644 index b59470d..0000000 --- a/makefile +++ /dev/null @@ -1,120 +0,0 @@ -NAME = ly -CC = gcc -FLAGS = -std=c99 -pedantic -g -FLAGS+= -Wall -Wextra -Werror=vla -Wno-unused-parameter -#FLAGS+= -DDEBUG -FLAGS+= -DLY_VERSION=\"$(shell git describe --long --tags | sed 's/\([^-]*-g\)/r\1/;s/-/./g')\" -LINK = -lpam -lxcb -VALGRIND = --show-leak-kinds=all --track-origins=yes --leak-check=full --suppressions=../res/valgrind.supp -CMD = ./$(NAME) - -OS:= $(shell uname -s) -ifeq ($(OS), Linux) - FLAGS+= -D_DEFAULT_SOURCE -endif - -BIND = bin -OBJD = obj -SRCD = src -SUBD = sub -RESD = res -TESTD = tests - -DATADIR ?= ${DESTDIR}/etc/ly -FLAGS+= -DDATADIR=\"$(DATADIR)\" - -INCL = -I$(SRCD) -INCL+= -I$(SUBD)/ctypes -INCL+= -I$(SUBD)/argoat/src -INCL+= -I$(SUBD)/configator/src -INCL+= -I$(SUBD)/dragonfail/src -INCL+= -I$(SUBD)/termbox_next/src - -SRCS = $(SRCD)/main.c -SRCS += $(SRCD)/config.c -SRCS += $(SRCD)/draw.c -SRCS += $(SRCD)/inputs.c -SRCS += $(SRCD)/login.c -SRCS += $(SRCD)/utils.c -SRCS += $(SUBD)/argoat/src/argoat.c -SRCS += $(SUBD)/configator/src/configator.c -SRCS += $(SUBD)/dragonfail/src/dragonfail.c - -SRCS_OBJS:= $(patsubst %.c,$(OBJD)/%.o,$(SRCS)) -SRCS_OBJS+= $(SUBD)/termbox_next/bin/termbox.a - -.PHONY: final -final: $(BIND)/$(NAME) - -$(OBJD)/%.o: %.c - @echo "building object $@" - @mkdir -p $(@D) - @$(CC) $(INCL) $(FLAGS) -c -o $@ $< - -$(SUBD)/termbox_next/bin/termbox.a: - @echo "building static object $@" - @(cd $(SUBD)/termbox_next && $(MAKE)) - -$(BIND)/$(NAME): $(SRCS_OBJS) - @echo "compiling executable $@" - @mkdir -p $(@D) - @$(CC) -o $@ $^ $(LINK) - -run: - @cd $(BIND) && $(CMD) - -leak: leakgrind -leakgrind: $(BIND)/$(NAME) - @rm -f valgrind.log - @cd $(BIND) && valgrind $(VALGRIND) 2> ../valgrind.log $(CMD) - @less valgrind.log - -install: $(BIND)/$(NAME) - @echo "installing ly" - @install -dZ ${DESTDIR}/etc/ly - @install -DZ $(BIND)/$(NAME) -t ${DESTDIR}/usr/bin - @if [ -e ${DESTDIR}/etc/ly/config.ini ]; then \ - cp ${DESTDIR}/etc/ly/config.ini ${DESTDIR}/etc/ly/config.ini.old; fi - @install -DZ $(RESD)/config.ini -t ${DESTDIR}/etc/ly - @install -DZ $(RESD)/xsetup.sh -t $(DATADIR) - @install -DZ $(RESD)/wsetup.sh -t $(DATADIR) - @install -dZ $(DATADIR)/lang - @install -DZ $(RESD)/lang/* -t $(DATADIR)/lang - @install -DZ $(RESD)/pam.d/ly -m 644 -t ${DESTDIR}/etc/pam.d - -installnoconf: $(BIND)/$(NAME) - @echo "installing ly without the configuration file" - @install -dZ ${DESTDIR}/etc/ly - @install -DZ $(BIND)/$(NAME) -t ${DESTDIR}/usr/bin - @install -DZ $(RESD)/xsetup.sh -t $(DATADIR) - @install -DZ $(RESD)/wsetup.sh -t $(DATADIR) - @install -dZ $(DATADIR)/lang - @install -DZ $(RESD)/lang/* -t $(DATADIR)/lang - @install -DZ $(RESD)/pam.d/ly -m 644 -t ${DESTDIR}/etc/pam.d - -installsystemd: - @echo "installing systemd service" - @install -DZ $(RESD)/ly.service -m 644 -t ${DESTDIR}/usr/lib/systemd/system - -installopenrc: - @echo "installing openrc service" - @install -DZ $(RESD)/ly-openrc -m 755 -T ${DESTDIR}/etc/init.d/${NAME} - -installrunit: - @echo "installing runit service" - @install -DZ $(RESD)/ly-runit-service/* -t ${DESTDIR}/etc/sv/ly - -uninstall: - @echo "uninstalling" - @rm -rf ${DESTDIR}/etc/ly - @rm -rf $(DATADIR) - @rm -f ${DESTDIR}/usr/bin/ly - @rm -f ${DESTDIR}/usr/lib/systemd/system/ly.service - @rm -f ${DESTDIR}/etc/pam.d/ly - @rm -f ${DESTDIR}/etc/init.d/${NAME} - @rm -rf ${DESTDIR}/etc/sv/ly - -clean: - @echo "cleaning" - @rm -rf $(BIND) $(OBJD) valgrind.log - @(cd $(SUBD)/termbox_next && $(MAKE) clean) diff --git a/readme.md b/readme.md index 820741f..3400461 100644 --- a/readme.md +++ b/readme.md @@ -5,9 +5,8 @@ Ly is a lightweight TUI (ncurses-like) display manager for Linux and BSD. ## Dependencies - - a C99 compiler (tested with tcc and gcc) + - zig 0.12.0 - a C standard library - - GNU make - pam - xcb - xorg @@ -16,18 +15,27 @@ Ly is a lightweight TUI (ncurses-like) display manager for Linux and BSD. - tput - shutdown -On Debian-based distros running `apt install build-essential libpam0g-dev libxcb-xkb-dev` as root should install all the dependencies for you. -For Fedora try running `dnf install make automake gcc gcc-c++ kernel-devel pam-devel libxcb-devel` +### Debian +``` +# apt install build-essential libpam0g-dev libxcb-xkb-dev +``` + +### Fedora +**Warning**: You may encounter issues with SELinux on Fedora. +It is recommended to add a rule for Ly as it currently does not ship one. + +``` +# dnf install kernel-devel pam-devel libxcb-devel +``` ## Support -The following desktop environments were tested with success - +The following desktop environments were tested with success: - awesome - bspwm - budgie - cinnamon - deepin - - dwm + - dwm - enlightenment - gnome - i3 @@ -36,12 +44,12 @@ The following desktop environments were tested with success - lxde - lxqt - mate - - maxx + - maxx - pantheon - qtile - spectrwm - sway - - windowmaker + - windowmaker - xfce - xmonad @@ -57,7 +65,7 @@ changing the source code won't be necessary :) ## Cloning and Compiling Clone the repository ``` -$ git clone --recurse-submodules https://github.com/fairyglade/ly +$ git clone https://github.com/fairyglade/ly ``` Change the directory to ly @@ -67,18 +75,18 @@ $ cd ly Compile ``` -$ make +$ zig build ``` Test in the configured tty (tty2 by default) or a terminal emulator (but desktop environments won't start) ``` -# make run +# zig build run ``` Install Ly and the provided systemd service file ``` -# make install installsystemd +# zig build installsystemd ``` Enable the service @@ -94,11 +102,13 @@ disable getty on Ly's tty to prevent "login" from spawning on top of it ### OpenRC +**NOTE**: On Gentoo, Ly will disable the `display-manager-init` service in order to run. + Clone, compile and test. Install Ly and the provided OpenRC service ``` -# make install installopenrc +# zig build installopenrc ``` Enable the service @@ -108,7 +118,8 @@ Enable the service You can edit which tty Ly will start on by editing the `tty` option in the configuration file. -If you choose a tty that already has a login/getty running (has a basic login prompt), then you have to disable the getty so it doesn't respawn on top of ly +If you choose a tty that already has a login/getty running (has a basic login prompt), +then you have to disable getty, so it doesn't respawn on top of ly ``` # rc-update del agetty.tty2 ``` @@ -116,12 +127,11 @@ If you choose a tty that already has a login/getty running (has a basic login pr ### runit ``` -$ make -# make install installrunit +# zig build installrunit # ln -s /etc/sv/ly /var/service/ ``` -By default, ly will run on tty2. To change the tty it must be set in `/etc/ly/config.ini` +By default, ly will run on tty2. To change the tty it must be set in `/etc/ly/config.ini` You should as well disable your existing display manager service if needed, e.g.: @@ -129,7 +139,9 @@ You should as well disable your existing display manager service if needed, e.g. # rm /var/service/lxdm ``` -The agetty service for the tty console where you are running ly should be disabled. For instance, if you are running ly on tty2 (that's the default, check your `/etc/ly/config.ini`) you should disable the agetty-tty2 service like this: +The agetty service for the tty console where you are running ly should be disabled. +For instance, if you are running ly on tty2 (that's the default, check your `/etc/ly/config.ini`) +you should disable the agetty-tty2 service like this: ``` # rm /var/service/agetty-tty2 diff --git a/res/config.ini b/res/config.ini index be89557..65c6baa 100644 --- a/res/config.ini +++ b/res/config.ini @@ -1,24 +1,25 @@ -# Animation enabled/disabled -#animate = false - # The active animation -# 0 -> PSX DOOM fire (default) -# 1 -> CMatrix -#animation = 0 +# none -> Nothing (default) +# doom -> PSX DOOM fire +# matrix -> CMatrix +animation = none -# format string for clock in top right corner (see strftime specification) -#clock = %c +# Format string for clock in top right corner (see strftime specification). Example: %c +clock = null -# enable/disable big clock -#bigclock = true +# Enable/disable big clock +bigclock = false # The character used to mask the password -#asterisk = * +asterisk = * # Erase password input on failure -#blank_password = false +clear_password = false -#The `fg` and `bg` color settings take a digit 0-8 corresponding to: +# Enable vi keybindings +vi_mode = false + +# The `fg` and `bg` color settings take a digit 0-8 corresponding to: #define TB_DEFAULT 0x00 #define TB_BLACK 0x01 #define TB_RED 0x02 @@ -29,118 +30,124 @@ #define TB_CYAN 0x07 #define TB_WHITE 0x08 # -# Setting both to zero makes `bg` black and `fg` white. To set the actual color palette you are encouraged to use another tool -# such as [mkinitcpio-colors](https://github.com/evanpurkhiser/mkinitcpio-colors). Note that the color palette defined with -# `mkinitcpio-colors` takes 16 colors (0-15), only values 0-8 are valid for `ly` config and these values do not correspond -# exactly. For instance, in defining palettes with `mkinitcpio-colors` the order is black, dark red, dark green, brown, dark -# blue, dark purple, dark cyan, light gray, dark gray, bright red, bright green, yellow, bright blue, bright purple, bright -# cyan, and white, indexed in that order 0 through 15. For example, the color defined for white (indexed at 15 in the mkinitcpio +# Setting both to zero makes `bg` black and `fg` white. To set the actual color palette you are encouraged to use another tool +# such as [mkinitcpio-colors](https://github.com/evanpurkhiser/mkinitcpio-colors). Note that the color palette defined with +# `mkinitcpio-colors` takes 16 colors (0-15), only values 0-8 are valid for `ly` config and these values do not correspond +# exactly. For instance, in defining palettes with `mkinitcpio-colors` the order is black, dark red, dark green, brown, dark +# blue, dark purple, dark cyan, light gray, dark gray, bright red, bright green, yellow, bright blue, bright purple, bright +# cyan, and white, indexed in that order 0 through 15. For example, the color defined for white (indexed at 15 in the mkinitcpio # config) will be used by `ly` for `fg = 8`. # Background color id -#bg = 0 +bg = 0 # Foreground color id -#fg = 9 +fg = 8 + +# Border color +border_fg = 8 # Blank main box background # Setting to false will make it transparent -#blank_box = true +blank_box = true # Remove main box borders -#hide_borders = false +hide_borders = false # Main box margins -#margin_box_h = 2 -#margin_box_v = 1 +margin_box_h = 2 +margin_box_v = 1 # Input boxes length -#input_len = 34 +input_len = 34 # Max input sizes -#max_desktop_len = 100 -#max_login_len = 255 -#max_password_len = 255 - +max_desktop_len = 100 +max_login_len = 255 +max_password_len = 255 # Input box active by default on startup -#default_input = 2 +# Available inputs: session, login, password +default_input = login # Load the saved desktop and username -#load = true +load = true # Save the current desktop and login as defaults -#save = true +save = true +# Deprecated - Will be removed in a future version +# New save files are now loaded from the same directory as the config +# Currently used to migrate old save files to the new version # File in which to save and load the default desktop and login -#save_file = /etc/ly/save - +save_file = /etc/ly/save # Remove power management command hints -#hide_key_hints = false +hide_key_hints = false -# Specifies the key used for shutdown -#shutdown_key = F1 +# Specifies the key used for shutdown (F1-F12) +shutdown_key = F1 -# Specifies the key used for restart -#restart_key = F2 +# Specifies the key used for restart (F1-F12) +restart_key = F2 + +# Specifies the key used for sleep (F1-F12) +sleep_key = F3 # Command executed when pressing shutdown_key -#shutdown_cmd = /sbin/shutdown -a now +shutdown_cmd = /sbin/shutdown -a now # Command executed when pressing restart_key -#restart_cmd = /sbin/shutdown -r now +restart_cmd = /sbin/shutdown -r now +# Command executed when pressing sleep key (can be null) +sleep_cmd = null # Active language # Available languages are found in /etc/ly/lang/ -#lang = en +lang = en - -# tty in use -#tty = 2 +# TTY in use +tty = 2 # Console path -#console_dev = /dev/console - -# Default path -#path = /sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin +console_dev = /dev/console +# Default path. If null, ly doesn't set a path. +path = /sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin # Event timeout in milliseconds -#min_refresh_delta = 5 +min_refresh_delta = 5 # Service name (set to ly to use the provided pam config file) -#service_name = ly +service_name = ly # Terminal reset command (tput is faster) -#term_reset_cmd = /usr/bin/tput reset +term_reset_cmd = /usr/bin/tput reset + +# Terminal restore cursor command +term_restore_cursor_cmd = /usr/bin/tput cnorm # Cookie generator -#mcookie_cmd = /usr/bin/mcookie - +mcookie_cmd = /usr/bin/mcookie # Wayland setup command -#wayland_cmd = /etc/ly/wsetup.sh - -# Add wayland specifier to session names -#wayland_specifier = false +wayland_cmd = /etc/ly/wsetup.sh # Wayland desktop environments -#waylandsessions = /usr/share/wayland-sessions +waylandsessions = /usr/share/wayland-sessions - -# xinitrc -#xinitrc = ~/.xinitrc +# xinitrc (hidden if null) +xinitrc = ~/.xinitrc # Xorg server command -#x_cmd = /usr/bin/X +x_cmd = /usr/bin/X # Xorg setup command -#x_cmd_setup = /etc/ly/xsetup.sh +x_cmd_setup = /etc/ly/xsetup.sh # Xorg xauthority edition tool -#xauth_cmd = /usr/bin/xauth +xauth_cmd = /usr/bin/xauth # Xorg desktop environments -#xsessions = /usr/share/xsessions +xsessions = /usr/share/xsessions diff --git a/res/lang/en.ini b/res/lang/en.ini index 71776e5..766dc86 100644 --- a/res/lang/en.ini +++ b/res/lang/en.ini @@ -29,17 +29,22 @@ err_perm_dir = failed to change current directory err_perm_group = failed to downgrade group permissions err_perm_user = failed to downgrade user permissions err_pwnam = failed to get user info +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_xsessions_dir = failed to find sessions folder err_xsessions_open = failed to open sessions folder +insert = insert login = login logout = logged out +normal = normal numlock = numlock password = password restart = reboot shell = shell shutdown = shutdown +sleep = sleep wayland = wayland xinitrc = xinitrc +x11 = x11 diff --git a/res/lang/sr.ini b/res/lang/sr.ini index 0375a51..2f685e3 100644 --- a/res/lang/sr.ini +++ b/res/lang/sr.ini @@ -25,7 +25,7 @@ err_pam_session = greska sesije err_pam_sys = greska sistema err_pam_user_unknown = nepoznat korisnik err_path = neuspjelo postavljanje path-a -err_perm_dir = neuspjelo mijenjanje foldera +err_perm_dir = neuspjelo mijenjanje foldera err_perm_group = neuspjesno snizavanje dozvola grupe err_perm_user = neuspijesno snizavanje dozvola korisnika err_pwnam = neuspijesno skupljanje informacija o korisniku @@ -33,7 +33,7 @@ err_user_gid = neuspijesno postavljanje korisničkog GID-a err_user_init = neuspijensa inicijalizacija korisnika err_user_uid = neuspijesno postavljanje UID-a korisnika err_xsessions_dir = neuspijesno pronalazenje foldera sesija -err_xsessions_open = neuspijesno otvaranje foldera sesija +err_xsessions_open = neuspijesno otvaranje foldera sesija login = korisnik logout = izlogovan numlock = numlock diff --git a/res/ly.service b/res/ly.service index 135b987..2fd120a 100644 --- a/res/ly.service +++ b/res/ly.service @@ -2,6 +2,7 @@ Description=TUI display manager After=systemd-user-sessions.service plymouth-quit-wait.service After=getty@tty2.service +Conflicts=getty@tty2.service [Service] Type=idle diff --git a/src/SharedError.zig b/src/SharedError.zig new file mode 100644 index 0000000..dcf1f79 --- /dev/null +++ b/src/SharedError.zig @@ -0,0 +1,39 @@ +const std = @import("std"); + +const ErrInt = std.meta.Int(.unsigned, @bitSizeOf(anyerror)); + +const ErrorHandler = packed struct { + has_error: bool = false, + err_int: ErrInt = 0, +}; + +const SharedError = @This(); + +data: []align(std.mem.page_size) u8, + +pub fn init() !SharedError { + const data = try std.posix.mmap(null, @sizeOf(ErrorHandler), std.posix.PROT.READ | std.posix.PROT.WRITE, .{ .TYPE = .SHARED, .ANONYMOUS = true }, -1, 0); + + return .{ .data = data }; +} + +pub fn deinit(self: *SharedError) void { + std.posix.munmap(self.data); +} + +pub fn writeError(self: SharedError, err: anyerror) void { + var buf_stream = std.io.fixedBufferStream(self.data); + const writer = buf_stream.writer(); + writer.writeStruct(ErrorHandler{ .has_error = true, .err_int = @intFromError(err) }) catch {}; +} + +pub fn readError(self: SharedError) ?anyerror { + var buf_stream = std.io.fixedBufferStream(self.data); + const reader = buf_stream.reader(); + const err_handler = try reader.readStruct(ErrorHandler); + + if (err_handler.has_error) + return @errorFromInt(err_handler.err_int); + + return null; +} diff --git a/src/animations/Doom.zig b/src/animations/Doom.zig new file mode 100644 index 0000000..b4d435e --- /dev/null +++ b/src/animations/Doom.zig @@ -0,0 +1,84 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const TerminalBuffer = @import("../tui/TerminalBuffer.zig"); +const utils = @import("../tui/utils.zig"); + +const interop = @import("../interop.zig"); +const termbox = interop.termbox; + +const Doom = @This(); + +pub const STEPS = 13; +pub const FIRE = [_]termbox.tb_cell{ + utils.initCell(' ', 9, 0), + utils.initCell(0x2591, 2, 0), // Red + utils.initCell(0x2592, 2, 0), // Red + utils.initCell(0x2593, 2, 0), // Red + utils.initCell(0x2588, 2, 0), // Red + utils.initCell(0x2591, 4, 2), // Yellow + utils.initCell(0x2592, 4, 2), // Yellow + utils.initCell(0x2593, 4, 2), // Yellow + utils.initCell(0x2588, 4, 2), // Yellow + utils.initCell(0x2591, 8, 4), // White + utils.initCell(0x2592, 8, 4), // White + utils.initCell(0x2593, 8, 4), // White + utils.initCell(0x2588, 8, 4), // White +}; + +allocator: Allocator, +terminal_buffer: *TerminalBuffer, +buffer: []u8, + +pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer) !Doom { + const buffer = try allocator.alloc(u8, terminal_buffer.width * terminal_buffer.height); + initBuffer(buffer, terminal_buffer.width); + + return .{ + .allocator = allocator, + .terminal_buffer = terminal_buffer, + .buffer = buffer, + }; +} + +pub fn deinit(self: Doom) void { + self.allocator.free(self.buffer); +} + +pub fn realloc(self: *Doom) !void { + const buffer = try self.allocator.realloc(self.buffer, self.terminal_buffer.width * self.terminal_buffer.height); + initBuffer(buffer, self.terminal_buffer.width); + self.buffer = buffer; +} + +pub fn draw(self: Doom) void { + for (0..self.terminal_buffer.width) |x| { + for (1..self.terminal_buffer.height) |y| { + const source = y * self.terminal_buffer.width + x; + const random = (self.terminal_buffer.random.int(u16) % 7) & 3; + + var dest = source - random + 1; + if (self.terminal_buffer.width > dest) dest = 0 else dest -= self.terminal_buffer.width; + + const buffer_source = self.buffer[source]; + const buffer_dest_offset = random & 1; + + if (buffer_source < buffer_dest_offset) continue; + + var buffer_dest = buffer_source - buffer_dest_offset; + if (buffer_dest > 12) buffer_dest = 0; + self.buffer[dest] = @intCast(buffer_dest); + + self.terminal_buffer.buffer[dest] = FIRE[buffer_dest]; + self.terminal_buffer.buffer[source] = FIRE[buffer_source]; + } + } +} + +fn initBuffer(buffer: []u8, width: u64) void { + const length = buffer.len - width; + const slice_start = buffer[0..length]; + const slice_end = buffer[length..]; + + @memset(slice_start, 0); + @memset(slice_end, STEPS - 1); +} diff --git a/src/animations/Matrix.zig b/src/animations/Matrix.zig new file mode 100644 index 0000000..fbacc41 --- /dev/null +++ b/src/animations/Matrix.zig @@ -0,0 +1,181 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const Random = std.rand.Random; +const TerminalBuffer = @import("../tui/TerminalBuffer.zig"); + +const interop = @import("../interop.zig"); +const termbox = interop.termbox; + +pub const FRAME_DELAY: u64 = 8; + +// Allowed codepoints +pub const MIN_CODEPOINT: isize = 33; +pub const MAX_CODEPOINT: isize = 123 - MIN_CODEPOINT; + +// Characters change mid-scroll +pub const MID_SCROLL_CHANGE = true; + +const Matrix = @This(); + +pub const Dot = struct { + value: isize, + is_head: bool, +}; + +pub const Line = struct { + space: isize, + length: isize, + update: isize, +}; + +allocator: Allocator, +terminal_buffer: *TerminalBuffer, +dots: []Dot, +lines: []Line, +frame: u64, +count: u64, + +pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer) !Matrix { + const dots = try allocator.alloc(Dot, terminal_buffer.width * (terminal_buffer.height + 1)); + const lines = try allocator.alloc(Line, terminal_buffer.width); + + initBuffers(dots, lines, terminal_buffer.width, terminal_buffer.height, terminal_buffer.random); + + return .{ + .allocator = allocator, + .terminal_buffer = terminal_buffer, + .dots = dots, + .lines = lines, + .frame = 3, + .count = 0, + }; +} + +pub fn deinit(self: Matrix) void { + self.allocator.free(self.dots); + self.allocator.free(self.lines); +} + +pub fn realloc(self: *Matrix) !void { + const dots = try self.allocator.realloc(self.dots, self.terminal_buffer.width * (self.terminal_buffer.height + 1)); + const lines = try self.allocator.realloc(self.lines, self.terminal_buffer.width); + + initBuffers(dots, lines, self.terminal_buffer.width, self.terminal_buffer.height, self.terminal_buffer.random); + + self.dots = dots; + self.lines = lines; +} + +pub fn draw(self: *Matrix) void { + const buf_height = self.terminal_buffer.height; + const buf_width = self.terminal_buffer.width; + self.count += 1; + if (self.count > FRAME_DELAY) { + self.frame += 1; + if (self.frame > 4) self.frame = 1; + self.count = 0; + + var x: u64 = 0; + while (x < self.terminal_buffer.width) : (x += 2) { + var tail: u64 = 0; + var line = &self.lines[x]; + if (self.frame <= line.update) continue; + + if (self.dots[x].value == -1 and self.dots[self.terminal_buffer.width + x].value == ' ') { + if (line.space > 0) { + line.space -= 1; + } else { + const randint = self.terminal_buffer.random.int(i16); + const h: isize = @intCast(self.terminal_buffer.height); + line.length = @mod(randint, h - 3) + 3; + self.dots[x].value = @mod(randint, MAX_CODEPOINT) + MIN_CODEPOINT; + line.space = @mod(randint, h + 1); + } + } + + var y: u64 = 0; + var first_col = true; + var seg_len: u64 = 0; + height_it: while (y <= buf_height) : (y += 1) { + var dot = &self.dots[buf_width * y + x]; + // Skip over spaces + while (y <= buf_height and (dot.value == ' ' or dot.value == -1)) { + y += 1; + if (y > buf_height) break :height_it; + dot = &self.dots[buf_width * y + x]; + } + + // Find the head of this column + tail = y; + seg_len = 0; + while (y <= buf_height and dot.value != ' ' and dot.value != -1) { + dot.is_head = false; + if (MID_SCROLL_CHANGE) { + const randint = self.terminal_buffer.random.int(i16); + if (@mod(randint, 8) == 0) { + dot.value = @mod(randint, MAX_CODEPOINT) + MIN_CODEPOINT; + } + } + + y += 1; + seg_len += 1; + // Head's down offscreen + if (y > buf_height) { + self.dots[buf_width * tail + x].value = ' '; + break :height_it; + } + dot = &self.dots[buf_width * y + x]; + } + + const randint = self.terminal_buffer.random.int(i16); + dot.value = @mod(randint, MAX_CODEPOINT) + MIN_CODEPOINT; + dot.is_head = true; + + if (seg_len > line.length or !first_col) { + self.dots[buf_width * tail + x].value = ' '; + self.dots[x].value = -1; + } + first_col = false; + } + } + } + + var x: u64 = 0; + while (x < buf_width) : (x += 2) { + 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); + + if (dot.value == -1 or dot.value == ' ') { + termbox.tb_change_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); + } + } +} + +fn initBuffers(dots: []Dot, lines: []Line, width: u64, height: u64, random: Random) void { + var y: u64 = 0; + while (y <= height) : (y += 1) { + var x: u64 = 0; + while (x < width) : (x += 2) { + dots[y * width + x].value = -1; + } + } + + var x: u64 = 0; + while (x < width) : (x += 2) { + var line = lines[x]; + const h: isize = @intCast(height); + line.space = @mod(random.int(i16), h) + 1; + line.length = @mod(random.int(i16), h - 3) + 3; + line.update = @mod(random.int(i16), 3) + 1; + lines[x] = line; + + dots[width + x].value = ' '; + } +} diff --git a/src/auth.zig b/src/auth.zig new file mode 100644 index 0000000..4b061a5 --- /dev/null +++ b/src/auth.zig @@ -0,0 +1,489 @@ +const std = @import("std"); +const enums = @import("enums.zig"); +const interop = @import("interop.zig"); +const TerminalBuffer = @import("tui/TerminalBuffer.zig"); +const Desktop = @import("tui/components/Desktop.zig"); +const Text = @import("tui/components/Text.zig"); +const Config = @import("config/Config.zig"); +const Allocator = std.mem.Allocator; +const utmp = interop.utmp; +const Utmp = utmp.utmp; +const SharedError = @import("SharedError.zig"); + +var xorg_pid: std.posix.pid_t = 0; +pub fn xorgSignalHandler(i: c_int) callconv(.C) void { + if (xorg_pid > 0) _ = std.c.kill(xorg_pid, i); +} + +var child_pid: std.posix.pid_t = 0; +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 { + 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); + try setXdgEnv(tty_str, current_environment.xdg_session_desktop, current_environment.xdg_desktop_names orelse ""); + + // Open the PAM session + var credentials = [_:null]?[*:0]const u8{ login, password }; + + const conv = interop.pam.pam_conv{ + .conv = loginConv, + .appdata_ptr = @ptrCast(&credentials), + }; + var handle: ?*interop.pam.pam_handle = undefined; + + var status = interop.pam.pam_start(config.service_name.ptr, null, &conv, &handle); + if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); + + // Do the PAM routine + status = interop.pam.pam_authenticate(handle, 0); + if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); + + status = interop.pam.pam_acct_mgmt(handle, 0); + if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); + + status = interop.pam.pam_setcred(handle, interop.pam.PAM_ESTABLISH_CRED); + if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); + + status = interop.pam.pam_open_session(handle, 0); + if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); + + var pwd: *interop.passwd = undefined; + { + defer interop.endpwent(); + + // Get password structure from username + pwd = interop.getpwnam(login.ptr) orelse return error.GetPasswordNameFailed; + } + + // Set user shell if it hasn't already been set + if (pwd.pw_shell[0] == 0) { + interop.setusershell(); + pwd.pw_shell = interop.getusershell(); + interop.endusershell(); + } + + var shared_err = try SharedError.init(); + defer shared_err.deinit(); + + child_pid = try std.posix.fork(); + if (child_pid == 0) { + startSession(config, pwd, handle, current_environment) catch |e| { + shared_err.writeError(e); + std.process.exit(1); + }; + std.process.exit(0); + } + + var entry: Utmp = std.mem.zeroes(Utmp); + addUtmpEntry(&entry, pwd.pw_name, child_pid) catch {}; + + // 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); + + // Wait for the session to stop + _ = std.posix.waitpid(child_pid, 0); + + removeUtmpEntry(&entry); + + 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; +} + +fn startSession( + config: Config, + pwd: *interop.passwd, + handle: ?*interop.pam.pam_handle, + current_environment: Desktop.Environment, +) !void { + var status: c_int = 0; + 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; + std.posix.setuid(pwd.pw_uid) catch return error.SetUserUidFailed; + + // Set up the environment + try initEnv(pwd, config.path); + + // Set the PAM variables + const pam_env_vars = interop.pam.pam_getenvlist(handle); + + 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); + } + + // Execute what the user requested + std.posix.chdirZ(pwd.pw_dir) catch return error.ChangeDirectoryFailed; + + try resetTerminal(pwd.pw_shell, config.term_reset_cmd); + + switch (current_environment.display_server) { + .wayland => try executeWaylandCmd(pwd.pw_shell, config.wayland_cmd, current_environment.cmd), + .shell => try executeShellCmd(pwd.pw_shell), + .xinitrc, .x11 => { + var vt_buf: [5]u8 = undefined; + const vt = try std.fmt.bufPrint(&vt_buf, "vt{d}", .{config.tty}); + try executeX11Cmd(pwd.pw_shell, pwd.pw_dir, config, current_environment.cmd, vt); + }, + } +} + +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); + _ = interop.setenv("USER", pwd.pw_name, 1); + _ = interop.setenv("LOGNAME", pwd.pw_name, 1); + + if (path_env) |path| { + const status = interop.setenv("PATH", path, 1); + if (status != 0) return error.SetPathFailed; + } +} + +fn setXdgSessionEnv(display_server: enums.DisplayServer) void { + _ = interop.setenv("XDG_SESSION_TYPE", switch (display_server) { + .wayland => "wayland", + .shell => "tty", + .xinitrc, .x11 => "x11", + }, 0); +} + +fn setXdgEnv(tty_str: [:0]u8, desktop_name: [:0]const u8, xdg_desktop_names: [:0]const u8) !void { + const uid = interop.getuid(); + var uid_buffer: [10 + @sizeOf(u32) + 1]u8 = undefined; + const uid_str = try std.fmt.bufPrintZ(&uid_buffer, "/run/user/{d}", .{uid}); + + _ = interop.setenv("XDG_CURRENT_DESKTOP", xdg_desktop_names.ptr, 0); + _ = interop.setenv("XDG_RUNTIME_DIR", uid_str.ptr, 0); + _ = interop.setenv("XDG_SESSION_CLASS", "user", 0); + _ = interop.setenv("XDG_SESSION_ID", "1", 0); + _ = interop.setenv("XDG_SESSION_DESKTOP", desktop_name.ptr, 0); + _ = interop.setenv("XDG_SEAT", "seat0", 0); + _ = interop.setenv("XDG_VTNR", tty_str.ptr, 0); +} + +fn loginConv( + num_msg: c_int, + msg: ?[*]?*const interop.pam.pam_message, + resp: ?*?[*]interop.pam.pam_response, + appdata_ptr: ?*anyopaque, +) callconv(.C) c_int { + const message_count: u32 = @intCast(num_msg); + const messages = msg.?; + + const allocator = std.heap.c_allocator; + const response = allocator.alloc(interop.pam.pam_response, message_count) catch return interop.pam.PAM_BUF_ERR; + + // 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); + + var username: ?[:0]u8 = null; + var password: ?[:0]u8 = null; + var status: c_int = interop.pam.PAM_SUCCESS; + + for (0..message_count) |i| set_credentials: { + switch (messages[i].?.msg_style) { + interop.pam.PAM_PROMPT_ECHO_ON => { + const data: [*][*:0]u8 = @ptrCast(@alignCast(appdata_ptr)); + username = allocator.dupeZ(u8, std.mem.span(data[0])) catch { + status = interop.pam.PAM_BUF_ERR; + break :set_credentials; + }; + response[i].resp = username.?.ptr; + }, + interop.pam.PAM_PROMPT_ECHO_OFF => { + const data: [*][*:0]u8 = @ptrCast(@alignCast(appdata_ptr)); + password = allocator.dupeZ(u8, std.mem.span(data[1])) catch { + status = interop.pam.PAM_BUF_ERR; + break :set_credentials; + }; + response[i].resp = password.?.ptr; + }, + interop.pam.PAM_ERROR_MSG => { + status = interop.pam.PAM_CONV_ERR; + break :set_credentials; + }, + else => {}, + } + } + + if (status != interop.pam.PAM_SUCCESS) { + // Memory is freed by pam otherwise + allocator.free(response); + if (username != null) allocator.free(username.?); + if (password != null) allocator.free(password.?); + } else { + resp.?.* = response.ptr; + } + + return status; +} + +fn resetTerminal(shell: [*:0]const u8, term_reset_cmd: [:0]const u8) !void { + const pid = try std.posix.fork(); + if (pid == 0) { + const args = [_:null]?[*:0]const u8{ shell, "-c", term_reset_cmd }; + std.posix.execveZ(shell, &args, std.c.environ) catch {}; + std.process.exit(1); + } + + _ = std.posix.waitpid(pid, 0); +} + +fn getFreeDisplay() !u8 { + var buf: [15]u8 = undefined; + var i: u8 = 0; + while (i < 200) : (i += 1) { + const xlock = try std.fmt.bufPrint(&buf, "/tmp/.X{d}-lock", .{i}); + std.posix.access(xlock, std.posix.F_OK) catch break; + } + return i; +} + +fn getXPid(display_num: u8) !i32 { + var buf: [15]u8 = undefined; + const file_name = try std.fmt.bufPrint(&buf, "/tmp/.X{d}-lock", .{display_num}); + const file = try std.fs.openFileAbsolute(file_name, .{}); + defer file.close(); + + var file_buf: [20]u8 = undefined; + var fbs = std.io.fixedBufferStream(&file_buf); + + _ = try file.reader().streamUntilDelimiter(fbs.writer(), '\n', 20); + const line = fbs.getWritten(); + + return std.fmt.parseInt(i32, std.mem.trim(u8, line, " "), 10); +} + +fn createXauthFile(pwd: [:0]const u8) ![:0]const u8 { + var xauth_buf: [100]u8 = undefined; + var xauth_dir: [:0]const u8 = undefined; + const xdg_rt_dir = std.posix.getenv("XDG_RUNTIME_DIR"); + var xauth_file: []const u8 = "lyxauth"; + + if (xdg_rt_dir == null) { + const xdg_cfg_home = std.posix.getenv("XDG_CONFIG_HOME"); + var sb: std.c.Stat = undefined; + if (xdg_cfg_home == null) { + xauth_dir = try std.fmt.bufPrintZ(&xauth_buf, "{s}/.config", .{pwd}); + _ = std.c.stat(xauth_dir, &sb); + const mode = sb.mode & std.posix.S.IFMT; + if (mode == std.posix.S.IFDIR) { + xauth_dir = try std.fmt.bufPrintZ(&xauth_buf, "{s}/ly", .{xauth_dir}); + } else { + xauth_dir = pwd; + xauth_file = ".lyxauth"; + } + } else { + xauth_dir = try std.fmt.bufPrintZ(&xauth_buf, "{s}/ly", .{xdg_cfg_home.?}); + } + + _ = std.c.stat(xauth_dir, &sb); + const mode = sb.mode & std.posix.S.IFMT; + if (mode != std.posix.S.IFDIR) { + std.posix.mkdir(xauth_dir, 777) catch { + xauth_dir = pwd; + xauth_file = ".lyxauth"; + }; + } + } else { + xauth_dir = xdg_rt_dir.?; + } + + // Trim trailing slashes + var i = xauth_dir.len - 1; + while (xauth_dir[i] == '/') i -= 1; + const trimmed_xauth_dir = xauth_dir[0 .. i + 1]; + + var buf: [256]u8 = undefined; + const xauthority: [:0]u8 = try std.fmt.bufPrintZ(&buf, "{s}/{s}", .{ trimmed_xauth_dir, xauth_file }); + const file = try std.fs.createFileAbsoluteZ(xauthority, .{}); + file.close(); + + 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 { + var pwd_buf: [100]u8 = undefined; + const pwd = try std.fmt.bufPrintZ(&pwd_buf, "{s}", .{pw_dir}); + + const xauthority = try createXauthFile(pwd); + _ = interop.setenv("XAUTHORITY", xauthority, 1); + _ = interop.setenv("DISPLAY", display_name, 1); + + 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 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); +} + +fn executeShellCmd(shell: [*:0]const u8) !void { + const args = [_:null]?[*:0]const u8{shell}; + return std.posix.execveZ(shell, &args, std.c.environ); +} + +fn executeWaylandCmd(shell: [*:0]const u8, wayland_cmd: []const u8, desktop_cmd: []const u8) !void { + var cmd_buffer: [1024]u8 = undefined; + const cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} {s}", .{ wayland_cmd, desktop_cmd }); + const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str }; + return std.posix.execveZ(shell, &args, std.c.environ); +} + +fn executeX11Cmd(shell: [*:0]const u8, pw_dir: [*:0]const u8, config: Config, desktop_cmd: []const u8, vt: []const u8) !void { + const display_num = try getFreeDisplay(); + var buf: [5]u8 = undefined; + const display_name = try std.fmt.bufPrintZ(&buf, ":{d}", .{display_num}); + try xauth(display_name, shell, pw_dir, config.xauth_cmd, config.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} {s} {s}", .{ config.x_cmd, display_name, vt }) 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); + } + + var ok: c_int = undefined; + var xcb: ?*interop.xcb.xcb_connection_t = null; + while (ok != 0) { + 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; + }; + } + + // X Server detaches from the process. + // PID can be fetched from /tmp/X{d}.lock + const x_pid = try getXPid(display_num); + + xorg_pid = try std.posix.fork(); + if (xorg_pid == 0) { + var cmd_buffer: [1024]u8 = undefined; + const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s}", .{ config.x_cmd_setup, desktop_cmd }) 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); + } + + // If we receive SIGTERM, clean up by killing the xorg_pid process + const act = std.posix.Sigaction{ + .handler = .{ .handler = &xorgSignalHandler }, + .mask = std.posix.empty_sigset, + .flags = 0, + }; + try std.posix.sigaction(std.posix.SIG.TERM, &act, null); + + _ = std.posix.waitpid(xorg_pid, 0); + interop.xcb.xcb_disconnect(xcb); + + std.posix.kill(x_pid, 0) catch return; + std.posix.kill(x_pid, std.posix.SIG.TERM) catch {}; + + var status: c_int = 0; + _ = std.c.waitpid(x_pid, &status, 0); +} + +fn addUtmpEntry(entry: *Utmp, username: [*:0]const u8, pid: c_int) !void { + entry.ut_type = utmp.USER_PROCESS; + entry.ut_pid = pid; + + var buf: [4096]u8 = undefined; + const ttyname = try std.os.getFdPath(0, &buf); + + var ttyname_buf: [32]u8 = undefined; + _ = try std.fmt.bufPrintZ(&ttyname_buf, "{s}", .{ttyname["/dev/".len..]}); + + entry.ut_line = ttyname_buf; + entry.ut_id = ttyname_buf["tty".len..7].*; + + var username_buf: [32]u8 = undefined; + _ = try std.fmt.bufPrintZ(&username_buf, "{s}", .{username}); + + entry.ut_user = username_buf; + + var host: [256]u8 = undefined; + host[0] = 0; + entry.ut_host = host; + + var tv: std.c.timeval = undefined; + _ = std.c.gettimeofday(&tv, null); + + entry.ut_tv = .{ + .tv_sec = @intCast(tv.tv_sec), + .tv_usec = @intCast(tv.tv_usec), + }; + entry.ut_addr_v6[0] = 0; + + utmp.setutent(); + _ = utmp.pututline(entry); + utmp.endutent(); +} + +fn removeUtmpEntry(entry: *Utmp) void { + entry.ut_type = utmp.DEAD_PROCESS; + entry.ut_line[0] = 0; + entry.ut_user[0] = 0; + utmp.setutent(); + _ = utmp.pututline(entry); + utmp.endutent(); +} + +fn pamDiagnose(status: c_int) anyerror { + return switch (status) { + interop.pam.PAM_ACCT_EXPIRED => return error.PamAccountExpired, + interop.pam.PAM_AUTH_ERR => return error.PamAuthError, + interop.pam.PAM_AUTHINFO_UNAVAIL => return error.PamAuthInfoUnavailable, + interop.pam.PAM_BUF_ERR => return error.PamBufferError, + interop.pam.PAM_CRED_ERR => return error.PamCredentialsError, + interop.pam.PAM_CRED_EXPIRED => return error.PamCredentialsExpired, + interop.pam.PAM_CRED_INSUFFICIENT => return error.PamCredentialsInsufficient, + interop.pam.PAM_CRED_UNAVAIL => return error.PamCredentialsUnavailable, + interop.pam.PAM_MAXTRIES => return error.PamMaximumTries, + interop.pam.PAM_NEW_AUTHTOK_REQD => return error.PamNewAuthTokenRequired, + interop.pam.PAM_PERM_DENIED => return error.PamPermissionDenied, + interop.pam.PAM_SESSION_ERR => return error.PamSessionError, + interop.pam.PAM_SYSTEM_ERR => return error.PamSystemError, + interop.pam.PAM_USER_UNKNOWN => return error.PamUserUnknown, + else => return error.PamAbort, + }; +} diff --git a/src/bigclock.h b/src/bigclock.h deleted file mode 100644 index 2de834a..0000000 --- a/src/bigclock.h +++ /dev/null @@ -1,146 +0,0 @@ -#include - -#define CLOCK_W 5 -#define CLOCK_H 5 - -#if defined(__linux__) || defined(__FreeBSD__) - #define X 0x2593 - #define _ 0x0000 -#else - #define X '#' - #define _ 0 -#endif - -#if CLOCK_W == 5 && CLOCK_H == 5 - -uint32_t CLOCK_0[] = { - X,X,X,X,X, - X,X,_,X,X, - X,X,_,X,X, - X,X,_,X,X, - X,X,X,X,X -}; - -uint32_t CLOCK_1[] = { - _,_,_,X,X, - _,_,_,X,X, - _,_,_,X,X, - _,_,_,X,X, - _,_,_,X,X -}; - -uint32_t CLOCK_2[] = { - X,X,X,X,X, - _,_,_,X,X, - X,X,X,X,X, - X,X,_,_,_, - X,X,X,X,X -}; - -uint32_t CLOCK_3[] = { - X,X,X,X,X, - _,_,_,X,X, - X,X,X,X,X, - _,_,_,X,X, - X,X,X,X,X -}; - -uint32_t CLOCK_4[] = { - X,X,_,X,X, - X,X,_,X,X, - X,X,X,X,X, - _,_,_,X,X, - _,_,_,X,X -}; - -uint32_t CLOCK_5[] = { - X,X,X,X,X, - X,X,_,_,_, - X,X,X,X,X, - _,_,_,X,X, - X,X,X,X,X -}; - -uint32_t CLOCK_6[] = { - X,X,X,X,X, - X,X,_,_,_, - X,X,X,X,X, - X,X,_,X,X, - X,X,X,X,X, -}; - -uint32_t CLOCK_7[] = { - X,X,X,X,X, - _,_,_,X,X, - _,_,_,X,X, - _,_,_,X,X, - _,_,_,X,X -}; - -uint32_t CLOCK_8[] = { - X,X,X,X,X, - X,X,_,X,X, - X,X,X,X,X, - X,X,_,X,X, - X,X,X,X,X -}; - -uint32_t CLOCK_9[] = { - X,X,X,X,X, - X,X,_,X,X, - X,X,X,X,X, - _,_,_,X,X, - X,X,X,X,X -}; - -uint32_t CLOCK_S[] = { - _,_,_,_,_, - _,_,X,_,_, - _,_,_,_,_, - _,_,X,_,_, - _,_,_,_,_ -}; - -uint32_t CLOCK_E[] = { - _,_,_,_,_, - _,_,_,_,_, - _,_,_,_,_, - _,_,_,_,_, - _,_,_,_,_ -}; - -#endif - -#undef X -#undef _ - -static inline uint32_t* CLOCK_N(char c) -{ - switch(c) - { - case '0': - return CLOCK_0; - case '1': - return CLOCK_1; - case '2': - return CLOCK_2; - case '3': - return CLOCK_3; - case '4': - return CLOCK_4; - case '5': - return CLOCK_5; - case '6': - return CLOCK_6; - case '7': - return CLOCK_7; - case '8': - return CLOCK_8; - case '9': - return CLOCK_9; - case ':': - return CLOCK_S; - default: - return CLOCK_E; - } -} diff --git a/src/bigclock.zig b/src/bigclock.zig new file mode 100644 index 0000000..46d157c --- /dev/null +++ b/src/bigclock.zig @@ -0,0 +1,140 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const interop = @import("interop.zig"); +const utils = @import("tui/utils.zig"); + +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 SIZE = WIDTH * HEIGHT; + +// zig fmt: off +const ZERO = [_]u32{ + 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{ + 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{ + 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{ + 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{ + 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{ + 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{ + 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{ + 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{ + 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{ + 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{ + 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{ + O,O,O,O,O, + O,O,O,O,O, + O,O,O,O,O, + O,O,O,O,O, + O,O,O,O,O, +}; +// zig fmt: on + +pub fn clockCell(animate: bool, char: u8, fg: u8, bg: u8) [SIZE]termbox.tb_cell { + var cells: [SIZE]termbox.tb_cell = undefined; + + var tv: std.c.timeval = undefined; + _ = std.c.gettimeofday(&tv, null); + + const clock_chars = toBigNumber(if (animate and char == ':' and @divTrunc(tv.tv_usec, 500000) != 0) ' ' else char); + for (0..cells.len) |i| cells[i] = utils.initCell(clock_chars[i], fg, bg); + + return cells; +} + +pub fn alphaBlit(buffer: [*]termbox.tb_cell, x: u64, y: u64, tb_width: u64, tb_height: u64, cells: [SIZE]termbox.tb_cell) void { + if (x + WIDTH >= tb_width or y + HEIGHT >= tb_height) return; + + for (0..HEIGHT) |yy| { + for (0..WIDTH) |xx| { + const cell = cells[yy * WIDTH + xx]; + if (cell.ch != 0) buffer[(y + yy) * tb_width + (x + xx)] = cell; + } + } +} + +fn toBigNumber(char: u8) []const u32 { + return switch (char) { + '0' => &ZERO, + '1' => &ONE, + '2' => &TWO, + '3' => &THREE, + '4' => &FOUR, + '5' => &FIVE, + '6' => &SIX, + '7' => &SEVEN, + '8' => &EIGHT, + '9' => &NINE, + ':' => &S, + else => &E, + }; +} diff --git a/src/config.c b/src/config.c deleted file mode 100644 index f7a1c00..0000000 --- a/src/config.c +++ /dev/null @@ -1,385 +0,0 @@ -#include "configator.h" - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#ifndef DEBUG - #define INI_LANG DATADIR "/lang/%s.ini" - #define INI_CONFIG "/etc/ly/config.ini" -#else - #define INI_LANG "../res/lang/%s.ini" - #define INI_CONFIG "../res/config.ini" -#endif - -static void lang_handle(void* data, char** pars, const int pars_count) -{ - if (*((char**)data) != NULL) - { - free (*((char**)data)); - } - - *((char**)data) = strdup(*pars); -} - -static void config_handle_u8(void* data, char** pars, const int pars_count) -{ - if (strcmp(*pars, "") == 0) - { - *((uint8_t*)data) = 0; - } - else - { - *((uint8_t*)data) = atoi(*pars); - } -} - -static void config_handle_u16(void* data, char** pars, const int pars_count) -{ - if (strcmp(*pars, "") == 0) - { - *((uint16_t*)data) = 0; - } - else - { - *((uint16_t*)data) = atoi(*pars); - } -} - -void config_handle_str(void* data, char** pars, const int pars_count) -{ - if (*((char**)data) != NULL) - { - free(*((char**)data)); - } - - *((char**)data) = strdup(*pars); -} - -static void config_handle_char(void* data, char** pars, const int pars_count) -{ - *((char*)data) = **pars; -} - -static void config_handle_bool(void* data, char** pars, const int pars_count) -{ - *((bool*)data) = (strcmp("true", *pars) == 0); -} - -void lang_load() -{ - // must be alphabetically sorted - struct configator_param map_no_section[] = - { - {"capslock", &lang.capslock, lang_handle}, - {"err_alloc", &lang.err_alloc, lang_handle}, - {"err_bounds", &lang.err_bounds, lang_handle}, - {"err_chdir", &lang.err_chdir, lang_handle}, - {"err_console_dev", &lang.err_console_dev, lang_handle}, - {"err_dgn_oob", &lang.err_dgn_oob, lang_handle}, - {"err_domain", &lang.err_domain, lang_handle}, - {"err_hostname", &lang.err_hostname, lang_handle}, - {"err_mlock", &lang.err_mlock, lang_handle}, - {"err_null", &lang.err_null, lang_handle}, - {"err_pam", &lang.err_pam, lang_handle}, - {"err_pam_abort", &lang.err_pam_abort, lang_handle}, - {"err_pam_acct_expired", &lang.err_pam_acct_expired, lang_handle}, - {"err_pam_auth", &lang.err_pam_auth, lang_handle}, - {"err_pam_authinfo_unavail", &lang.err_pam_authinfo_unavail, lang_handle}, - {"err_pam_authok_reqd", &lang.err_pam_authok_reqd, lang_handle}, - {"err_pam_buf", &lang.err_pam_buf, lang_handle}, - {"err_pam_cred_err", &lang.err_pam_cred_err, lang_handle}, - {"err_pam_cred_expired", &lang.err_pam_cred_expired, lang_handle}, - {"err_pam_cred_insufficient", &lang.err_pam_cred_insufficient, lang_handle}, - {"err_pam_cred_unavail", &lang.err_pam_cred_unavail, lang_handle}, - {"err_pam_maxtries", &lang.err_pam_maxtries, lang_handle}, - {"err_pam_perm_denied", &lang.err_pam_perm_denied, lang_handle}, - {"err_pam_session", &lang.err_pam_session, lang_handle}, - {"err_pam_sys", &lang.err_pam_sys, lang_handle}, - {"err_pam_user_unknown", &lang.err_pam_user_unknown, lang_handle}, - {"err_path", &lang.err_path, lang_handle}, - {"err_perm_dir", &lang.err_perm_dir, lang_handle}, - {"err_perm_group", &lang.err_perm_group, lang_handle}, - {"err_perm_user", &lang.err_perm_user, lang_handle}, - {"err_pwnam", &lang.err_pwnam, lang_handle}, - {"err_user_gid", &lang.err_user_gid, lang_handle}, - {"err_user_init", &lang.err_user_init, lang_handle}, - {"err_user_uid", &lang.err_user_uid, lang_handle}, - {"err_xsessions_dir", &lang.err_xsessions_dir, lang_handle}, - {"err_xsessions_open", &lang.err_xsessions_open, lang_handle}, - {"login", &lang.login, lang_handle}, - {"logout", &lang.logout, lang_handle}, - {"numlock", &lang.numlock, lang_handle}, - {"password", &lang.password, lang_handle}, - {"restart", &lang.restart, lang_handle}, - {"shell", &lang.shell, lang_handle}, - {"shutdown", &lang.shutdown, lang_handle}, - {"wayland", &lang.wayland, lang_handle}, - {"xinitrc", &lang.xinitrc, lang_handle}, - }; - - uint16_t map_len[] = {45}; - struct configator_param* map[] = - { - map_no_section, - }; - - uint16_t sections_len = 0; - struct configator_param* sections = NULL; - - struct configator lang; - lang.map = map; - lang.map_len = map_len; - lang.sections = sections; - lang.sections_len = sections_len; - - char file[256]; - snprintf(file, 256, INI_LANG, config.lang); - - if (access(file, F_OK) != -1) - { - configator(&lang, file); - } -} - -void config_load(const char *cfg_path) -{ - if (cfg_path == NULL) - { - cfg_path = INI_CONFIG; - } - // must be alphabetically sorted - struct configator_param map_no_section[] = - { - {"animate", &config.animate, config_handle_bool}, - {"animation", &config.animation, config_handle_u8}, - {"asterisk", &config.asterisk, config_handle_char}, - {"bg", &config.bg, config_handle_u8}, - {"bigclock", &config.bigclock, config_handle_bool}, - {"blank_box", &config.blank_box, config_handle_bool}, - {"blank_password", &config.blank_password, config_handle_bool}, - {"clock", &config.clock, config_handle_str}, - {"console_dev", &config.console_dev, config_handle_str}, - {"default_input", &config.default_input, config_handle_u8}, - {"fg", &config.fg, config_handle_u8}, - {"hide_borders", &config.hide_borders, config_handle_bool}, - {"hide_key_hints", &config.hide_key_hints, config_handle_bool}, - {"input_len", &config.input_len, config_handle_u8}, - {"lang", &config.lang, config_handle_str}, - {"load", &config.load, config_handle_bool}, - {"margin_box_h", &config.margin_box_h, config_handle_u8}, - {"margin_box_v", &config.margin_box_v, config_handle_u8}, - {"max_desktop_len", &config.max_desktop_len, config_handle_u8}, - {"max_login_len", &config.max_login_len, config_handle_u8}, - {"max_password_len", &config.max_password_len, config_handle_u8}, - {"mcookie_cmd", &config.mcookie_cmd, config_handle_str}, - {"min_refresh_delta", &config.min_refresh_delta, config_handle_u16}, - {"path", &config.path, config_handle_str}, - {"restart_cmd", &config.restart_cmd, config_handle_str}, - {"restart_key", &config.restart_key, config_handle_str}, - {"save", &config.save, config_handle_bool}, - {"save_file", &config.save_file, config_handle_str}, - {"service_name", &config.service_name, config_handle_str}, - {"shutdown_cmd", &config.shutdown_cmd, config_handle_str}, - {"shutdown_key", &config.shutdown_key, config_handle_str}, - {"term_reset_cmd", &config.term_reset_cmd, config_handle_str}, - {"tty", &config.tty, config_handle_u8}, - {"wayland_cmd", &config.wayland_cmd, config_handle_str}, - {"wayland_specifier", &config.wayland_specifier, config_handle_bool}, - {"waylandsessions", &config.waylandsessions, config_handle_str}, - {"x_cmd", &config.x_cmd, config_handle_str}, - {"xinitrc", &config.xinitrc, config_handle_str}, - {"x_cmd_setup", &config.x_cmd_setup, config_handle_str}, - {"xauth_cmd", &config.xauth_cmd, config_handle_str}, - {"xsessions", &config.xsessions, config_handle_str}, - }; - - uint16_t map_len[] = {41}; - struct configator_param* map[] = - { - map_no_section, - }; - - uint16_t sections_len = 0; - struct configator_param* sections = NULL; - - struct configator config; - config.map = map; - config.map_len = map_len; - config.sections = sections; - config.sections_len = sections_len; - - configator(&config, (char *) cfg_path); -} - -void lang_defaults() -{ - lang.capslock = strdup("capslock"); - lang.err_alloc = strdup("failed memory allocation"); - lang.err_bounds = strdup("out-of-bounds index"); - lang.err_chdir = strdup("failed to open home folder"); - lang.err_console_dev = strdup("failed to access console"); - lang.err_dgn_oob = strdup("log message"); - lang.err_domain = strdup("invalid domain"); - lang.err_hostname = strdup("failed to get hostname"); - lang.err_mlock = strdup("failed to lock password memory"); - lang.err_null = strdup("null pointer"); - lang.err_pam = strdup("pam transaction failed"); - lang.err_pam_abort = strdup("pam transaction aborted"); - lang.err_pam_acct_expired = strdup("account expired"); - lang.err_pam_auth = strdup("authentication error"); - lang.err_pam_authinfo_unavail = strdup("failed to get user info"); - lang.err_pam_authok_reqd = strdup("token expired"); - lang.err_pam_buf = strdup("memory buffer error"); - lang.err_pam_cred_err = strdup("failed to set credentials"); - lang.err_pam_cred_expired = strdup("credentials expired"); - lang.err_pam_cred_insufficient = strdup("insufficient credentials"); - lang.err_pam_cred_unavail = strdup("failed to get credentials"); - lang.err_pam_maxtries = strdup("reached maximum tries limit"); - lang.err_pam_perm_denied = strdup("permission denied"); - lang.err_pam_session = strdup("session error"); - lang.err_pam_sys = strdup("system error"); - lang.err_pam_user_unknown = strdup("unknown user"); - lang.err_path = strdup("failed to set path"); - lang.err_perm_dir = strdup("failed to change current directory"); - lang.err_perm_group = strdup("failed to downgrade group permissions"); - lang.err_perm_user = strdup("failed to downgrade user permissions"); - lang.err_pwnam = strdup("failed to get user info"); - lang.err_user_gid = strdup("failed to set user GID"); - lang.err_user_init = strdup("failed to initialize user"); - lang.err_user_uid = strdup("failed to set user UID"); - lang.err_xsessions_dir = strdup("failed to find sessions folder"); - lang.err_xsessions_open = strdup("failed to open sessions folder"); - lang.login = strdup("login:"); - lang.logout = strdup("logged out"); - lang.numlock = strdup("numlock"); - lang.password = strdup("password:"); - lang.restart = strdup("reboot"); - lang.shell = strdup("shell"); - lang.shutdown = strdup("shutdown"); - lang.wayland = strdup("wayland"); - lang.xinitrc = strdup("xinitrc"); -} - -void config_defaults() -{ - config.animate = false; - config.animation = 0; - config.asterisk = '*'; - config.bg = 0; - config.bigclock = false; - config.blank_box = true; - config.blank_password = false; - config.clock = NULL; - config.console_dev = strdup("/dev/console"); - config.default_input = LOGIN_INPUT; - config.fg = 9; - config.hide_borders = false; - config.hide_key_hints = false; - config.input_len = 34; - config.lang = strdup("en"); - config.load = true; - config.margin_box_h = 2; - config.margin_box_v = 1; - config.max_desktop_len = 100; - config.max_login_len = 255; - config.max_password_len = 255; - config.mcookie_cmd = strdup("/usr/bin/mcookie"); - config.min_refresh_delta = 5; - config.path = strdup("/sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin"); - config.restart_cmd = strdup("/sbin/shutdown -r now"); - config.restart_key = strdup("F2"); - config.save = true; - config.save_file = strdup("/etc/ly/save"); - config.service_name = strdup("ly"); - config.shutdown_cmd = strdup("/sbin/shutdown -a now"); - config.shutdown_key = strdup("F1"); - config.term_reset_cmd = strdup("/usr/bin/tput reset"); - config.tty = 2; - config.wayland_cmd = strdup(DATADIR "/wsetup.sh"); - config.wayland_specifier = false; - config.waylandsessions = strdup("/usr/share/wayland-sessions"); - config.x_cmd = strdup("/usr/bin/X"); - config.xinitrc = strdup("~/.xinitrc"); - config.x_cmd_setup = strdup(DATADIR "/xsetup.sh"); - config.xauth_cmd = strdup("/usr/bin/xauth"); - config.xsessions = strdup("/usr/share/xsessions"); -} - -void lang_free() -{ - free(lang.capslock); - free(lang.err_alloc); - free(lang.err_bounds); - free(lang.err_chdir); - free(lang.err_console_dev); - free(lang.err_dgn_oob); - free(lang.err_domain); - free(lang.err_hostname); - free(lang.err_mlock); - free(lang.err_null); - free(lang.err_pam); - free(lang.err_pam_abort); - free(lang.err_pam_acct_expired); - free(lang.err_pam_auth); - free(lang.err_pam_authinfo_unavail); - free(lang.err_pam_authok_reqd); - free(lang.err_pam_buf); - free(lang.err_pam_cred_err); - free(lang.err_pam_cred_expired); - free(lang.err_pam_cred_insufficient); - free(lang.err_pam_cred_unavail); - free(lang.err_pam_maxtries); - free(lang.err_pam_perm_denied); - free(lang.err_pam_session); - free(lang.err_pam_sys); - free(lang.err_pam_user_unknown); - free(lang.err_path); - free(lang.err_perm_dir); - free(lang.err_perm_group); - free(lang.err_perm_user); - free(lang.err_pwnam); - free(lang.err_user_gid); - free(lang.err_user_init); - free(lang.err_user_uid); - free(lang.err_xsessions_dir); - free(lang.err_xsessions_open); - free(lang.login); - free(lang.logout); - free(lang.numlock); - free(lang.password); - free(lang.restart); - free(lang.shell); - free(lang.shutdown); - free(lang.wayland); - free(lang.xinitrc); -} - -void config_free() -{ - free(config.clock); - free(config.console_dev); - free(config.lang); - free(config.mcookie_cmd); - free(config.path); - free(config.restart_cmd); - free(config.restart_key); - free(config.save_file); - free(config.service_name); - free(config.shutdown_cmd); - free(config.shutdown_key); - free(config.term_reset_cmd); - free(config.wayland_cmd); - free(config.waylandsessions); - free(config.x_cmd); - free(config.xinitrc); - free(config.x_cmd_setup); - free(config.xauth_cmd); - free(config.xsessions); -} diff --git a/src/config.h b/src/config.h deleted file mode 100644 index 6a54f60..0000000 --- a/src/config.h +++ /dev/null @@ -1,118 +0,0 @@ -#ifndef H_LY_CONFIG -#define H_LY_CONFIG - -#include -#include - -enum INPUTS { - SESSION_SWITCH, - LOGIN_INPUT, - PASSWORD_INPUT, -}; - -struct lang -{ - char* capslock; - char* err_alloc; - char* err_bounds; - char* err_chdir; - char* err_console_dev; - char* err_dgn_oob; - char* err_domain; - char* err_hostname; - char* err_mlock; - char* err_null; - char* err_pam; - char* err_pam_abort; - char* err_pam_acct_expired; - char* err_pam_auth; - char* err_pam_authinfo_unavail; - char* err_pam_authok_reqd; - char* err_pam_buf; - char* err_pam_cred_err; - char* err_pam_cred_expired; - char* err_pam_cred_insufficient; - char* err_pam_cred_unavail; - char* err_pam_maxtries; - char* err_pam_perm_denied; - char* err_pam_session; - char* err_pam_sys; - char* err_pam_user_unknown; - char* err_path; - char* err_perm_dir; - char* err_perm_group; - char* err_perm_user; - char* err_pwnam; - char* err_user_gid; - char* err_user_init; - char* err_user_uid; - char* err_xsessions_dir; - char* err_xsessions_open; - char* login; - char* logout; - char* numlock; - char* password; - char* restart; - char* shell; - char* shutdown; - char* wayland; - char* xinitrc; -}; - -struct config -{ - bool animate; - uint8_t animation; - char asterisk; - uint8_t bg; - bool bigclock; - bool blank_box; - bool blank_password; - char* clock; - char* console_dev; - uint8_t default_input; - uint8_t fg; - bool hide_borders; - bool hide_key_hints; - uint8_t input_len; - char* lang; - bool load; - uint8_t margin_box_h; - uint8_t margin_box_v; - uint8_t max_desktop_len; - uint8_t max_login_len; - uint8_t max_password_len; - char* mcookie_cmd; - uint16_t min_refresh_delta; - char* path; - char* restart_cmd; - char* restart_key; - bool save; - char* save_file; - char* service_name; - char* shutdown_cmd; - char* shutdown_key; - char* term_reset_cmd; - uint8_t tty; - char* wayland_cmd; - bool wayland_specifier; - char* waylandsessions; - char* x_cmd; - char* xinitrc; - char* x_cmd_setup; - char* xauth_cmd; - char* xsessions; -}; - -extern struct lang lang; -extern struct config config; - -void config_handle_str(void* data, char** pars, const int pars_count); -void lang_load(); -void config_load(const char *cfg_path); -void lang_defaults(); -void config_defaults(); -void lang_free(); -void config_free(); - -#endif diff --git a/src/config/Config.zig b/src/config/Config.zig new file mode 100644 index 0000000..b5fdeda --- /dev/null +++ b/src/config/Config.zig @@ -0,0 +1,50 @@ +const build_options = @import("build_options"); +const enums = @import("../enums.zig"); + +const Animation = enums.Animation; +const Input = enums.Input; + +animation: Animation = .none, +asterisk: u8 = '*', +bg: u8 = 0, +bigclock: bool = false, +blank_box: bool = true, +border_fg: u8 = 8, +clear_password: bool = false, +clock: ?[:0]const u8 = null, +console_dev: [:0]const u8 = "/dev/console", +default_input: Input = .login, +fg: u8 = 8, +hide_borders: bool = false, +hide_key_hints: bool = false, +input_len: u8 = 34, +lang: []const u8 = "en", +load: bool = true, +margin_box_h: u8 = 2, +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", +min_refresh_delta: u16 = 5, +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", +save: bool = true, +save_file: []const u8 = "/etc/ly/save", +service_name: [:0]const u8 = "ly", +shutdown_cmd: []const u8 = "/sbin/shutdown -a now", +shutdown_key: []const u8 = "F1", +sleep_cmd: ?[]const u8 = null, +sleep_key: []const u8 = "F3", +term_reset_cmd: [:0]const u8 = "/usr/bin/tput reset", +term_restore_cursor_cmd: []const u8 = "/usr/bin/tput cnorm", +tty: u8 = 2, +vi_mode: bool = false, +wayland_cmd: []const u8 = build_options.data_directory ++ "/wsetup.sh", +waylandsessions: []const u8 = "/usr/share/wayland-sessions", +x_cmd: []const u8 = "/usr/bin/X", +xinitrc: ?[]const u8 = "~/.xinitrc", +x_cmd_setup: []const u8 = build_options.data_directory ++ "/xsetup.sh", +xauth_cmd: []const u8 = "/usr/bin/xauth", +xsessions: []const u8 = "/usr/share/xsessions", diff --git a/src/config/Lang.zig b/src/config/Lang.zig new file mode 100644 index 0000000..0333e0b --- /dev/null +++ b/src/config/Lang.zig @@ -0,0 +1,51 @@ +capslock: []const u8 = "capslock", +err_alloc: []const u8 = "failed memory allocation", +err_bounds: []const u8 = "out-of-bounds index", +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_hostname: []const u8 = "failed to get hostname", +err_mlock: []const u8 = "failed to lock password memory", +err_null: []const u8 = "null pointer", +err_pam: []const u8 = "pam transaction failed", +err_pam_abort: []const u8 = "pam transaction aborted", +err_pam_acct_expired: []const u8 = "account expired", +err_pam_auth: []const u8 = "authentication error", +err_pam_authinfo_unavail: []const u8 = "failed to get user info", +err_pam_authok_reqd: []const u8 = "token expired", +err_pam_buf: []const u8 = "memory buffer error", +err_pam_cred_err: []const u8 = "failed to set credentials", +err_pam_cred_expired: []const u8 = "credentials expired", +err_pam_cred_insufficient: []const u8 = "insufficient credentials", +err_pam_cred_unavail: []const u8 = "failed to get credentials", +err_pam_maxtries: []const u8 = "reached maximum tries limit", +err_pam_perm_denied: []const u8 = "permission denied", +err_pam_session: []const u8 = "session error", +err_pam_sys: []const u8 = "system error", +err_pam_user_unknown: []const u8 = "unknown user", +err_path: []const u8 = "failed to set path", +err_perm_dir: []const u8 = "failed to change current directory", +err_perm_group: []const u8 = "failed to downgrade group permissions", +err_perm_user: []const u8 = "failed to downgrade user permissions", +err_pwnam: []const u8 = "failed to get user info", +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_xsessions_dir: []const u8 = "failed to find sessions folder", +err_xsessions_open: []const u8 = "failed to open sessions folder", +insert: []const u8 = "insert", +login: []const u8 = "login:", +logout: []const u8 = "logged out", +normal: []const u8 = "normal", +numlock: []const u8 = "numlock", +other: []const u8 = "other", +password: []const u8 = "password:", +restart: []const u8 = "reboot", +shell: [:0]const u8 = "shell", +shutdown: []const u8 = "shutdown", +sleep: []const u8 = "sleep", +wayland: []const u8 = "wayland", +xinitrc: [:0]const u8 = "xinitrc", +x11: []const u8 = "x11", diff --git a/src/config/Save.zig b/src/config/Save.zig new file mode 100644 index 0000000..972c2b7 --- /dev/null +++ b/src/config/Save.zig @@ -0,0 +1,2 @@ +user: ?[]const u8 = null, +session_index: ?u64 = null, diff --git a/src/config/migrator.zig b/src/config/migrator.zig new file mode 100644 index 0000000..0ae6001 --- /dev/null +++ b/src/config/migrator.zig @@ -0,0 +1,30 @@ +const std = @import("std"); +const ini = @import("zigini"); +const Save = @import("Save.zig"); + +pub fn tryMigrateSaveFile(user_buf: *[32]u8, path: []const u8) Save { + var save = Save{}; + + var file = std.fs.openFileAbsolute(path, .{}) catch return save; + defer file.close(); + + const reader = file.reader(); + + var user_fbs = std.io.fixedBufferStream(user_buf); + reader.streamUntilDelimiter(user_fbs.writer(), '\n', 32) catch return save; + const user = user_fbs.getWritten(); + if (user.len > 0) save.user = user; + + var session_buf: [20]u8 = undefined; + var session_fbs = std.io.fixedBufferStream(&session_buf); + reader.streamUntilDelimiter(session_fbs.writer(), '\n', 20) catch {}; + + const session_index_str = session_fbs.getWritten(); + var session_index: ?u64 = null; + if (session_index_str.len > 0) { + session_index = std.fmt.parseUnsigned(u64, session_index_str, 10) catch return save; + } + save.session_index = session_index; + + return save; +} diff --git a/src/dragonfail_error.h b/src/dragonfail_error.h deleted file mode 100644 index f5ead2f..0000000 --- a/src/dragonfail_error.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef H_DRAGONFAIL_ERROR -#define H_DRAGONFAIL_ERROR - -enum dgn_error -{ - DGN_OK, // do not remove - - DGN_NULL, - DGN_ALLOC, - DGN_BOUNDS, - DGN_DOMAIN, - DGN_MLOCK, - DGN_XSESSIONS_DIR, - DGN_XSESSIONS_OPEN, - DGN_PATH, - DGN_CHDIR, - DGN_PWNAM, - DGN_USER_INIT, - DGN_USER_GID, - DGN_USER_UID, - DGN_PAM, - DGN_HOSTNAME, - - DGN_SIZE, // do not remove -}; - -#endif diff --git a/src/draw.c b/src/draw.c deleted file mode 100644 index 0679244..0000000 --- a/src/draw.c +++ /dev/null @@ -1,1026 +0,0 @@ -#include "dragonfail.h" -#include "termbox.h" - -#include "inputs.h" -#include "utils.h" -#include "config.h" -#include "draw.h" -#include "bigclock.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(__DragonFly__) || defined(__FreeBSD__) - #include -#else // linux - #include -#endif - -#define DOOM_STEPS 13 - -void draw_init(struct term_buf* buf) -{ - buf->width = tb_width(); - buf->height = tb_height(); - hostname(&buf->info_line); - - uint16_t len_login = strlen(lang.login); - uint16_t len_password = strlen(lang.password); - - if (len_login > len_password) - { - buf->labels_max_len = len_login; - } - else - { - buf->labels_max_len = len_password; - } - - buf->box_height = 7 + (2 * config.margin_box_v); - buf->box_width = - (2 * config.margin_box_h) - + (config.input_len + 1) - + buf->labels_max_len; - -#if defined(__linux__) || defined(__FreeBSD__) - buf->box_chars.left_up = 0x250c; - buf->box_chars.left_down = 0x2514; - buf->box_chars.right_up = 0x2510; - buf->box_chars.right_down = 0x2518; - buf->box_chars.top = 0x2500; - buf->box_chars.bot = 0x2500; - buf->box_chars.left = 0x2502; - buf->box_chars.right = 0x2502; -#else - buf->box_chars.left_up = '+'; - buf->box_chars.left_down = '+'; - buf->box_chars.right_up = '+'; - buf->box_chars.right_down= '+'; - buf->box_chars.top = '-'; - buf->box_chars.bot = '-'; - buf->box_chars.left = '|'; - buf->box_chars.right = '|'; -#endif -} - -static void doom_free(struct term_buf* buf); -static void matrix_free(struct term_buf* buf); - -void draw_free(struct term_buf* buf) -{ - if (config.animate) - { - switch (config.animation) - { - case 0: - doom_free(buf); - break; - case 1: - matrix_free(buf); - break; - } - } -} - -void draw_box(struct term_buf* buf) -{ - uint16_t box_x = (buf->width - buf->box_width) / 2; - uint16_t box_y = (buf->height - buf->box_height) / 2; - uint16_t box_x2 = (buf->width + buf->box_width) / 2; - uint16_t box_y2 = (buf->height + buf->box_height) / 2; - buf->box_x = box_x; - buf->box_y = box_y; - - if (!config.hide_borders) - { - // corners - tb_change_cell( - box_x - 1, - box_y - 1, - buf->box_chars.left_up, - config.fg, - config.bg); - tb_change_cell( - box_x2, - box_y - 1, - buf->box_chars.right_up, - config.fg, - config.bg); - tb_change_cell( - box_x - 1, - box_y2, - buf->box_chars.left_down, - config.fg, - config.bg); - tb_change_cell( - box_x2, - box_y2, - buf->box_chars.right_down, - config.fg, - config.bg); - - // top and bottom - struct tb_cell c1 = {buf->box_chars.top, config.fg, config.bg}; - struct tb_cell c2 = {buf->box_chars.bot, config.fg, config.bg}; - - for (uint16_t i = 0; i < buf->box_width; ++i) - { - tb_put_cell( - box_x + i, - box_y - 1, - &c1); - tb_put_cell( - box_x + i, - box_y2, - &c2); - } - - // left and right - c1.ch = buf->box_chars.left; - c2.ch = buf->box_chars.right; - - for (uint16_t i = 0; i < buf->box_height; ++i) - { - tb_put_cell( - box_x - 1, - box_y + i, - &c1); - - tb_put_cell( - box_x2, - box_y + i, - &c2); - } - } - - if (config.blank_box) - { - struct tb_cell blank = {' ', config.fg, config.bg}; - - for (uint16_t i = 0; i < buf->box_height; ++i) - { - for (uint16_t k = 0; k < buf->box_width; ++k) - { - tb_put_cell( - box_x + k, - box_y + i, - &blank); - } - } - } -} - -char* time_str(char* fmt, int maxlen) -{ - time_t timer; - char* buffer = malloc(maxlen); - struct tm* tm_info; - - timer = time(NULL); - tm_info = localtime(&timer); - - if (strftime(buffer, maxlen, fmt, tm_info) == 0) - { - buffer[0] = '\0'; - } - - return buffer; -} - -extern inline uint32_t* CLOCK_N(char c); - -struct tb_cell* clock_cell(char c) -{ - struct tb_cell* cells = malloc(sizeof(struct tb_cell) * CLOCK_W * CLOCK_H); - - struct timeval tv; - gettimeofday(&tv, NULL); - if (config.animate && c == ':' && tv.tv_usec / 500000) - { - c = ' '; - } - uint32_t* clockchars = CLOCK_N(c); - - for (int i = 0; i < CLOCK_W * CLOCK_H; i++) - { - cells[i].ch = clockchars[i]; - cells[i].fg = config.fg; - cells[i].bg = config.bg; - } - - return cells; -} - -void alpha_blit(struct tb_cell* buf, uint16_t x, uint16_t y, uint16_t w, uint16_t h, struct tb_cell* cells) -{ - if (x + w >= tb_width() || y + h >= tb_height()) - return; - - for (int i = 0; i < h; i++) - { - for (int j = 0; j < w; j++) - { - struct tb_cell cell = cells[i * w + j]; - if (cell.ch) - { - buf[(y + i) * tb_width() + (x + j)] = cell; - } - } - } -} - -void draw_bigclock(struct term_buf* buf) -{ - if (!config.bigclock) - { - return; - } - - int xo = buf->width / 2 - (5 * (CLOCK_W + 1)) / 2; - int yo = (buf->height - buf->box_height) / 2 - CLOCK_H - 2; - - char* clockstr = time_str("%H:%M", 6); - struct tb_cell* clockcell; - - for (int i = 0; i < 5; i++) - { - clockcell = clock_cell(clockstr[i]); - alpha_blit(tb_cell_buffer(), xo + i * (CLOCK_W + 1), yo, CLOCK_W, CLOCK_H, clockcell); - free(clockcell); - } - - free(clockstr); -} - -void draw_clock(struct term_buf* buf) -{ - if (config.clock == NULL || strlen(config.clock) == 0) - { - return; - } - - char* clockstr = time_str(config.clock, 32); - int clockstrlen = strlen(clockstr); - - struct tb_cell* cells = strn_cell(clockstr, clockstrlen); - tb_blit(buf->width - clockstrlen, 0, clockstrlen, 1, cells); - - free(clockstr); - free(cells); -} - -struct tb_cell* strn_cell(char* s, uint16_t len) // throws -{ - struct tb_cell* cells = malloc((sizeof (struct tb_cell)) * len); - char* s2 = s; - uint32_t c; - - if (cells != NULL) - { - for (uint16_t i = 0; i < len; ++i) - { - if ((s2 - s) >= len) - { - break; - } - - s2 += utf8_char_to_unicode(&c, s2); - - cells[i].ch = c; - cells[i].bg = config.bg; - cells[i].fg = config.fg; - } - } - else - { - dgn_throw(DGN_ALLOC); - } - - return cells; -} - -struct tb_cell* str_cell(char* s) // throws -{ - return strn_cell(s, strlen(s)); -} - -void draw_labels(struct term_buf* buf) // throws -{ - // login text - struct tb_cell* login = str_cell(lang.login); - - if (dgn_catch()) - { - dgn_reset(); - } - else - { - tb_blit( - buf->box_x + config.margin_box_h, - buf->box_y + config.margin_box_v + 4, - strlen(lang.login), - 1, - login); - free(login); - } - - // password text - struct tb_cell* password = str_cell(lang.password); - - if (dgn_catch()) - { - dgn_reset(); - } - else - { - tb_blit( - buf->box_x + config.margin_box_h, - buf->box_y + config.margin_box_v + 6, - strlen(lang.password), - 1, - password); - free(password); - } - - if (buf->info_line != NULL) - { - uint16_t len = strlen(buf->info_line); - struct tb_cell* info_cell = str_cell(buf->info_line); - - if (dgn_catch()) - { - dgn_reset(); - } - else - { - tb_blit( - buf->box_x + ((buf->box_width - len) / 2), - buf->box_y + config.margin_box_v, - len, - 1, - info_cell); - free(info_cell); - } - } -} - -void draw_key_hints() -{ - struct tb_cell* shutdown_key = str_cell(config.shutdown_key); - int len = strlen(config.shutdown_key); - if (dgn_catch()) - { - dgn_reset(); - } - else - { - tb_blit(0, 0, len, 1, shutdown_key); - free(shutdown_key); - } - - struct tb_cell* shutdown = str_cell(lang.shutdown); - len += 1; - if (dgn_catch()) - { - dgn_reset(); - } - else - { - tb_blit(len, 0, strlen(lang.shutdown), 1, shutdown); - free(shutdown); - } - - struct tb_cell* restart_key = str_cell(config.restart_key); - len += strlen(lang.shutdown) + 1; - if (dgn_catch()) - { - dgn_reset(); - } - else - { - tb_blit(len, 0, strlen(config.restart_key), 1, restart_key); - free(restart_key); - } - - struct tb_cell* restart = str_cell(lang.restart); - len += strlen(config.restart_key) + 1; - if (dgn_catch()) - { - dgn_reset(); - } - else - { - tb_blit(len, 0, strlen(lang.restart), 1, restart); - free(restart); - } -} - -void draw_lock_state(struct term_buf* buf) -{ - // get values - int fd = open(config.console_dev, O_RDONLY); - - if (fd < 0) - { - buf->info_line = lang.err_console_dev; - return; - } - - bool numlock_on; - bool capslock_on; - -#if defined(__DragonFly__) || defined(__FreeBSD__) - int led; - ioctl(fd, KDGETLED, &led); - numlock_on = led & LED_NUM; - capslock_on = led & LED_CAP; -#else // linux - char led; - ioctl(fd, KDGKBLED, &led); - numlock_on = led & K_NUMLOCK; - capslock_on = led & K_CAPSLOCK; -#endif - - close(fd); - - // print text - uint16_t pos_x = buf->width - strlen(lang.numlock); - uint16_t pos_y = 1; - if (config.clock == NULL || strlen(config.clock) == 0) - { - pos_y = 0; - } - - if (numlock_on) - { - struct tb_cell* numlock = str_cell(lang.numlock); - - if (dgn_catch()) - { - dgn_reset(); - } - else - { - tb_blit(pos_x, pos_y, strlen(lang.numlock), 1, numlock); - free(numlock); - } - } - - pos_x -= strlen(lang.capslock) + 1; - - if (capslock_on) - { - struct tb_cell* capslock = str_cell(lang.capslock); - - if (dgn_catch()) - { - dgn_reset(); - } - else - { - tb_blit(pos_x, pos_y, strlen(lang.capslock), 1, capslock); - free(capslock); - } - } -} - -void draw_desktop(struct desktop* target) -{ - uint16_t len = strlen(target->list[target->cur]); - - if (len > (target->visible_len - 3)) - { - len = target->visible_len - 3; - } - - tb_change_cell( - target->x, - target->y, - '<', - config.fg, - config.bg); - - tb_change_cell( - target->x + target->visible_len - 1, - target->y, - '>', - config.fg, - config.bg); - - for (uint16_t i = 0; i < len; ++ i) - { - tb_change_cell( - target->x + i + 2, - target->y, - target->list[target->cur][i], - config.fg, - config.bg); - } -} - -void draw_input(struct text* input) -{ - uint16_t len = strlen(input->text); - uint16_t visible_len = input->visible_len; - - if (len > visible_len) - { - len = visible_len; - } - - struct tb_cell* cells = strn_cell(input->visible_start, len); - - if (dgn_catch()) - { - dgn_reset(); - } - else - { - tb_blit(input->x, input->y, len, 1, cells); - free(cells); - - struct tb_cell c1 = {' ', config.fg, config.bg}; - - for (uint16_t i = input->end - input->visible_start; i < visible_len; ++i) - { - tb_put_cell( - input->x + i, - input->y, - &c1); - } - } -} - -void draw_input_mask(struct text* input) -{ - uint16_t len = strlen(input->text); - uint16_t visible_len = input->visible_len; - - if (len > visible_len) - { - len = visible_len; - } - - struct tb_cell c1 = {config.asterisk, config.fg, config.bg}; - struct tb_cell c2 = {' ', config.fg, config.bg}; - - for (uint16_t i = 0; i < visible_len; ++i) - { - if (input->visible_start + i < input->end) - { - tb_put_cell( - input->x + i, - input->y, - &c1); - } - else - { - tb_put_cell( - input->x + i, - input->y, - &c2); - } - } -} - -void position_input( - struct term_buf* buf, - struct desktop* desktop, - struct text* login, - struct text* password) -{ - uint16_t x = buf->box_x + config.margin_box_h + buf->labels_max_len + 1; - int32_t len = buf->box_x + buf->box_width - config.margin_box_h - x; - - if (len < 0) - { - return; - } - - desktop->x = x; - desktop->y = buf->box_y + config.margin_box_v + 2; - desktop->visible_len = len; - - login->x = x; - login->y = buf->box_y + config.margin_box_v + 4; - login->visible_len = len; - - password->x = x; - password->y = buf->box_y + config.margin_box_v + 6; - password->visible_len = len; -} - -static void doom_init(struct term_buf* buf) -{ - buf->init_width = buf->width; - buf->init_height = buf->height; - buf->astate.doom = malloc(sizeof(struct doom_state)); - - if (buf->astate.doom == NULL) - { - dgn_throw(DGN_ALLOC); - } - - uint16_t tmp_len = buf->width * buf->height; - buf->astate.doom->buf = malloc(tmp_len); - tmp_len -= buf->width; - - if (buf->astate.doom->buf == NULL) - { - dgn_throw(DGN_ALLOC); - } - - memset(buf->astate.doom->buf, 0, tmp_len); - memset(buf->astate.doom->buf + tmp_len, DOOM_STEPS - 1, buf->width); -} - -static void doom_free(struct term_buf* buf) -{ - free(buf->astate.doom->buf); - free(buf->astate.doom); -} - -// Adapted from cmatrix -static void matrix_init(struct term_buf* buf) -{ - buf->init_width = buf->width; - buf->init_height = buf->height; - buf->astate.matrix = malloc(sizeof(struct matrix_state)); - struct matrix_state* s = buf->astate.matrix; - - if (s == NULL) - { - dgn_throw(DGN_ALLOC); - } - - uint16_t len = buf->height + 1; - s->grid = malloc(sizeof(struct matrix_dot*) * len); - - if (s->grid == NULL) - { - dgn_throw(DGN_ALLOC); - } - - len = (buf->height + 1) * buf->width; - (s->grid)[0] = malloc(sizeof(struct matrix_dot) * len); - - if ((s->grid)[0] == NULL) - { - dgn_throw(DGN_ALLOC); - } - - for (int i = 1; i <= buf->height; ++i) - { - s->grid[i] = s->grid[i - 1] + buf->width; - - if (s->grid[i] == NULL) - { - dgn_throw(DGN_ALLOC); - } - } - - s->length = malloc(buf->width * sizeof(int)); - - if (s->length == NULL) - { - dgn_throw(DGN_ALLOC); - } - - s->spaces = malloc(buf->width * sizeof(int)); - - if (s->spaces == NULL) - { - dgn_throw(DGN_ALLOC); - } - - s->updates = malloc(buf->width * sizeof(int)); - - if (s->updates == NULL) - { - dgn_throw(DGN_ALLOC); - } - - // Initialize grid - for (int i = 0; i <= buf->height; ++i) - { - for (int j = 0; j <= buf->width - 1; j += 2) - { - s->grid[i][j].val = -1; - } - } - - for (int j = 0; j < buf->width; j += 2) - { - s->spaces[j] = (int) rand() % buf->height + 1; - s->length[j] = (int) rand() % (buf->height - 3) + 3; - s->grid[1][j].val = ' '; - s->updates[j] = (int) rand() % 3 + 1; - } -} - -static void matrix_free(struct term_buf* buf) -{ - free(buf->astate.matrix->grid[0]); - free(buf->astate.matrix->grid); - free(buf->astate.matrix->length); - free(buf->astate.matrix->spaces); - free(buf->astate.matrix->updates); - free(buf->astate.matrix); -} - -void animate_init(struct term_buf* buf) -{ - if (config.animate) - { - switch(config.animation) - { - case 0: - { - doom_init(buf); - break; - } - case 1: - { - matrix_init(buf); - break; - } - } - } -} - -static void doom(struct term_buf* term_buf) -{ - static struct tb_cell fire[DOOM_STEPS] = - { - {' ', 9, 0}, // default - {0x2591, 2, 0}, // red - {0x2592, 2, 0}, // red - {0x2593, 2, 0}, // red - {0x2588, 2, 0}, // red - {0x2591, 4, 2}, // yellow - {0x2592, 4, 2}, // yellow - {0x2593, 4, 2}, // yellow - {0x2588, 4, 2}, // yellow - {0x2591, 8, 4}, // white - {0x2592, 8, 4}, // white - {0x2593, 8, 4}, // white - {0x2588, 8, 4}, // white - }; - - uint16_t src; - uint16_t random; - uint16_t dst; - - uint16_t w = term_buf->init_width; - uint8_t* tmp = term_buf->astate.doom->buf; - - if ((term_buf->width != term_buf->init_width) || (term_buf->height != term_buf->init_height)) - { - return; - } - - struct tb_cell* buf = tb_cell_buffer(); - - for (uint16_t x = 0; x < w; ++x) - { - for (uint16_t y = 1; y < term_buf->init_height; ++y) - { - src = y * w + x; - random = ((rand() % 7) & 3); - dst = src - random + 1; - - if (w > dst) - { - dst = 0; - } - else - { - dst -= w; - } - - tmp[dst] = tmp[src] - (random & 1); - - if (tmp[dst] > 12) - { - tmp[dst] = 0; - } - - buf[dst] = fire[tmp[dst]]; - buf[src] = fire[tmp[src]]; - } - } -} - -// Adapted from cmatrix -static void matrix(struct term_buf* buf) -{ - static int frame = 3; - const int frame_delay = 8; - static int count = 0; - bool first_col; - struct matrix_state* s = buf->astate.matrix; - - // Allowed codepoints - const int randmin = 33; - const int randnum = 123 - randmin; - // Chars change mid-scroll - const bool changes = true; - - if ((buf->width != buf->init_width) || (buf->height != buf->init_height)) - { - return; - } - - count += 1; - if (count > frame_delay) - { - frame += 1; - if (frame > 4) frame = 1; - count = 0; - - for (int j = 0; j < buf->width; j += 2) - { - int tail; - if (frame > s->updates[j]) - { - if (s->grid[0][j].val == -1 && s->grid[1][j].val == ' ') - { - if (s->spaces[j] > 0) - { - s->spaces[j]--; - } else { - s->length[j] = (int) rand() % (buf->height - 3) + 3; - s->grid[0][j].val = (int) rand() % randnum + randmin; - s->spaces[j] = (int) rand() % buf->height + 1; - } - } - - int i = 0, seg_len = 0; - first_col = 1; - while (i <= buf->height) - { - // Skip over spaces - while (i <= buf->height - && (s->grid[i][j].val == ' ' || s->grid[i][j].val == -1)) - { - i++; - } - - if (i > buf->height) break; - - // Find the head of this col - tail = i; - seg_len = 0; - while (i <= buf->height - && (s->grid[i][j].val != ' ' && s->grid[i][j].val != -1)) - { - s->grid[i][j].is_head = false; - if (changes) - { - if (rand() % 8 == 0) - s->grid[i][j].val = (int) rand() % randnum + randmin; - } - i++; - seg_len++; - } - - // Head's down offscreen - if (i > buf->height) - { - s->grid[tail][j].val = ' '; - continue; - } - - s->grid[i][j].val = (int) rand() % randnum + randmin; - s->grid[i][j].is_head = true; - - if (seg_len > s->length[j] || !first_col) { - s->grid[tail][j].val = ' '; - s->grid[0][j].val = -1; - } - first_col = 0; - i++; - } - } - } - } - - uint32_t blank; - utf8_char_to_unicode(&blank, " "); - - for (int j = 0; j < buf->width; j += 2) - { - for (int i = 1; i <= buf->height; ++i) - { - uint32_t c; - int fg = TB_GREEN; - int bg = TB_DEFAULT; - - if (s->grid[i][j].val == -1 || s->grid[i][j].val == ' ') - { - tb_change_cell(j, i - 1, blank, fg, bg); - continue; - } - - char tmp[2]; - tmp[0] = s->grid[i][j].val; - tmp[1] = '\0'; - if(utf8_char_to_unicode(&c, tmp)) - { - if (s->grid[i][j].is_head) - { - fg = TB_WHITE | TB_BOLD; - } - tb_change_cell(j, i - 1, c, fg, bg); - } - } - } -} - -void animate(struct term_buf* buf) -{ - buf->width = tb_width(); - buf->height = tb_height(); - - if (config.animate) - { - switch(config.animation) - { - case 0: - { - doom(buf); - break; - } - case 1: - { - matrix(buf); - break; - } - } - } -} - -bool cascade(struct term_buf* term_buf, uint8_t* fails) -{ - uint16_t width = term_buf->width; - uint16_t height = term_buf->height; - - struct tb_cell* buf = tb_cell_buffer(); - bool changes = false; - char c_under; - char c; - - for (int i = height - 2; i >= 0; --i) - { - for (int k = 0; k < width; ++k) - { - c = buf[i * width + k].ch; - - if (isspace(c)) - { - continue; - } - - c_under = buf[(i + 1) * width + k].ch; - - if (!isspace(c_under)) - { - continue; - } - - if (!changes) - { - changes = true; - } - - if ((rand() % 10) > 7) - { - continue; - } - - buf[(i + 1) * width + k] = buf[i * width + k]; - buf[i * width + k].ch = ' '; - } - } - - // stop force-updating - if (!changes) - { - sleep(7); - *fails = 0; - - return false; - } - - // force-update - return true; -} diff --git a/src/draw.h b/src/draw.h deleted file mode 100644 index b8a64c4..0000000 --- a/src/draw.h +++ /dev/null @@ -1,92 +0,0 @@ -#ifndef H_LY_DRAW -#define H_LY_DRAW - -#include "termbox.h" -#include "inputs.h" - -#include -#include - -struct box -{ - uint32_t left_up; - uint32_t left_down; - uint32_t right_up; - uint32_t right_down; - uint32_t top; - uint32_t bot; - uint32_t left; - uint32_t right; -}; - -struct matrix_dot -{ - int val; - bool is_head; -}; - -struct matrix_state -{ - struct matrix_dot** grid; - int* length; - int* spaces; - int* updates; -}; - -struct doom_state -{ - uint8_t* buf; -}; - -union anim_state -{ - struct doom_state* doom; - struct matrix_state* matrix; -}; - -struct term_buf -{ - uint16_t width; - uint16_t height; - uint16_t init_width; - uint16_t init_height; - - struct box box_chars; - char* info_line; - uint16_t labels_max_len; - uint16_t box_x; - uint16_t box_y; - uint16_t box_width; - uint16_t box_height; - - union anim_state astate; -}; - -void draw_init(struct term_buf* buf); -void draw_free(struct term_buf* buf); -void draw_box(struct term_buf* buf); - -struct tb_cell* strn_cell(char* s, uint16_t len); -struct tb_cell* str_cell(char* s); - -void draw_labels(struct term_buf* buf); -void draw_key_hints(); -void draw_lock_state(struct term_buf* buf); -void draw_desktop(struct desktop* target); -void draw_input(struct text* input); -void draw_input_mask(struct text* input); - -void position_input( - struct term_buf* buf, - struct desktop* desktop, - struct text* login, - struct text* password); - -void animate_init(struct term_buf* buf); -void animate(struct term_buf* buf); -bool cascade(struct term_buf* buf, uint8_t* fails); - -void draw_bigclock(struct term_buf *buf); -void draw_clock(struct term_buf *buf); - -#endif diff --git a/src/enums.zig b/src/enums.zig new file mode 100644 index 0000000..4cfa564 --- /dev/null +++ b/src/enums.zig @@ -0,0 +1,18 @@ +pub const Animation = enum { + none, + doom, + matrix, +}; + +pub const DisplayServer = enum { + wayland, + shell, + xinitrc, + x11, +}; + +pub const Input = enum { + session, + login, + password, +}; diff --git a/src/inputs.c b/src/inputs.c deleted file mode 100644 index 1753677..0000000 --- a/src/inputs.c +++ /dev/null @@ -1,285 +0,0 @@ -#include "dragonfail.h" -#include "termbox.h" -#include "inputs.h" -#include "config.h" - -#include -#include -#include -#include -#include - -void handle_desktop(void* input_struct, struct tb_event* event) -{ - struct desktop* target = (struct desktop*) input_struct; - - if ((event != NULL) && (event->type == TB_EVENT_KEY)) - { - if (event->key == TB_KEY_ARROW_LEFT || (event->key == TB_KEY_CTRL_H)) - { - input_desktop_right(target); - } - else if (event->key == TB_KEY_ARROW_RIGHT || (event->key == TB_KEY_CTRL_L)) - { - input_desktop_left(target); - } - } - - tb_set_cursor(target->x + 2, target->y); -} - -void handle_text(void* input_struct, struct tb_event* event) -{ - struct text* target = (struct text*) input_struct; - - if ((event != NULL) && (event->type == TB_EVENT_KEY)) - { - if (event->key == TB_KEY_ARROW_LEFT) - { - input_text_left(target); - } - else if (event->key == TB_KEY_ARROW_RIGHT) - { - input_text_right(target); - } - else if (event->key == TB_KEY_DELETE) - { - input_text_delete(target); - } - else if ((event->key == TB_KEY_BACKSPACE) - || (event->key == TB_KEY_BACKSPACE2)) - { - input_text_backspace(target); - } - else if (((event->ch > 31) && (event->ch < 127)) - || (event->key == TB_KEY_SPACE)) - { - char buf[7] = {0}; - - if (event->key == TB_KEY_SPACE) - { - buf[0] = ' '; - } - else - { - utf8_unicode_to_char(buf, event->ch); - } - - input_text_write(target, buf[0]); - } - } - - tb_set_cursor( - target->x + (target->cur - target->visible_start), - target->y); -} - -void input_desktop(struct desktop* target) -{ - target->list = NULL; - target->list_simple = NULL; - target->cmd = NULL; - target->display_server = NULL; - target->cur = 0; - target->len = 0; - - input_desktop_add(target, strdup(lang.shell), strdup(""), DS_SHELL); - input_desktop_add(target, strdup(lang.xinitrc), strdup(config.xinitrc), DS_XINITRC); -#if 0 - input_desktop_add(target, strdup(lang.wayland), strdup(""), DS_WAYLAND); -#endif -} - -void input_text(struct text* target, uint64_t len) -{ - target->text = malloc(len + 1); - - if (target->text == NULL) - { - dgn_throw(DGN_ALLOC); - return; - } - else - { - int ok = mlock(target->text, len + 1); - - if (ok < 0) - { - dgn_throw(DGN_MLOCK); - return; - } - - memset(target->text, 0, len + 1); - } - - target->cur = target->text; - target->end = target->text; - target->visible_start = target->text; - target->len = len; - target->x = 0; - target->y = 0; -} - -void input_desktop_free(struct desktop* target) -{ - if (target != NULL) - { - for (uint16_t i = 0; i < target->len; ++i) - { - if (target->list[i] != NULL) - { - free(target->list[i]); - } - - if (target->cmd[i] != NULL) - { - free(target->cmd[i]); - } - } - - free(target->list); - free(target->cmd); - free(target->display_server); - } -} - -void input_text_free(struct text* target) -{ - memset(target->text, 0, target->len); - munlock(target->text, target->len + 1); - free(target->text); -} - -void input_desktop_right(struct desktop* target) -{ - ++(target->cur); - - if (target->cur >= target->len) - { - target->cur = 0; - } -} - -void input_desktop_left(struct desktop* target) -{ - --(target->cur); - - if (target->cur >= target->len) - { - target->cur = target->len - 1; - } -} - -void input_desktop_add( - struct desktop* target, - char* name, - char* cmd, - enum display_server display_server) -{ - ++(target->len); - target->list = realloc(target->list, target->len * (sizeof (char*))); - target->list_simple = realloc(target->list_simple, target->len * (sizeof (char*))); - target->cmd = realloc(target->cmd, target->len * (sizeof (char*))); - target->display_server = realloc( - target->display_server, - target->len * (sizeof (enum display_server))); - target->cur = target->len - 1; - - if ((target->list == NULL) - || (target->cmd == NULL) - || (target->display_server == NULL)) - { - dgn_throw(DGN_ALLOC); - return; - } - - target->list[target->cur] = name; - - int name_len = strlen(name); - char* name_simple = strdup(name); - - if (strstr(name_simple, " ") != NULL) - { - name_simple = strtok(name_simple, " "); - } - - for (int i = 0; i < name_len; i++) - { - name_simple[i] = tolower(name_simple[i]); - } - - target->list_simple[target->cur] = name_simple; - target->cmd[target->cur] = cmd; - target->display_server[target->cur] = display_server; -} - -void input_text_right(struct text* target) -{ - if (target->cur < target->end) - { - ++(target->cur); - - if ((target->cur - target->visible_start) > target->visible_len) - { - ++(target->visible_start); - } - } -} - -void input_text_left(struct text* target) -{ - if (target->cur > target->text) - { - --(target->cur); - - if ((target->cur - target->visible_start) < 0) - { - --(target->visible_start); - } - } -} - -void input_text_write(struct text* target, char ascii) -{ - if (ascii <= 0) - { - return; // unices do not support usernames and passwords other than ascii - } - - if ((target->end - target->text + 1) < target->len) - { - // moves the text to the right to add space for the new ascii char - memcpy(target->cur + 1, target->cur, target->end - target->cur); - ++(target->end); - // adds the new char and moves the cursor to the right - *(target->cur) = ascii; - input_text_right(target); - } -} - -void input_text_delete(struct text* target) -{ - if (target->cur < target->end) - { - // moves the text on the right to overwrite the currently pointed char - memcpy(target->cur, target->cur + 1, target->end - target->cur + 1); - --(target->end); - } -} - -void input_text_backspace(struct text* target) -{ - if (target->cur > target->text) - { - input_text_left(target); - input_text_delete(target); - } -} - -void input_text_clear(struct text* target) -{ - memset(target->text, 0, target->len + 1); - target->cur = target->text; - target->end = target->text; - target->visible_start = target->text; -} diff --git a/src/inputs.h b/src/inputs.h deleted file mode 100644 index d0f0f61..0000000 --- a/src/inputs.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef H_LY_INPUTS -#define H_LY_INPUTS - -#include "termbox.h" - -#include - -enum display_server {DS_WAYLAND, DS_SHELL, DS_XINITRC, DS_XORG}; - -struct text -{ - char* text; - char* end; - int64_t len; - char* cur; - char* visible_start; - uint16_t visible_len; - - uint16_t x; - uint16_t y; -}; - -struct desktop -{ - char** list; - char** list_simple; - char** cmd; - enum display_server* display_server; - - uint16_t cur; - uint16_t len; - uint16_t visible_len; - uint16_t x; - uint16_t y; -}; - -void handle_desktop(void* input_struct, struct tb_event* event); -void handle_text(void* input_struct, struct tb_event* event); -void input_desktop(struct desktop* target); -void input_text(struct text* target, uint64_t len); -void input_desktop_free(struct desktop* target); -void input_text_free(struct text* target); -void input_desktop_right(struct desktop* target); -void input_desktop_left(struct desktop* target); -void input_desktop_add( - struct desktop* target, - char* name, - char* cmd, - enum display_server display_server); -void input_text_right(struct text* target); -void input_text_left(struct text* target); -void input_text_write(struct text* target, char ascii); -void input_text_delete(struct text* target); -void input_text_backspace(struct text* target); -void input_text_clear(struct text* target); - -#endif diff --git a/src/interop.zig b/src/interop.zig new file mode 100644 index 0000000..a053747 --- /dev/null +++ b/src/interop.zig @@ -0,0 +1,108 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const Allocator = std.mem.Allocator; + +pub const termbox = @cImport({ + @cInclude("termbox.h"); +}); + +pub const pam = @cImport({ + @cInclude("security/pam_appl.h"); +}); + +pub const utmp = @cImport({ + @cInclude("utmp.h"); +}); + +pub const xcb = @cImport({ + @cInclude("xcb/xcb.h"); +}); + +pub const c_size = u64; +pub const c_uid = u32; +pub const c_gid = u32; +pub const c_time = c_long; +pub const tm = extern struct { + tm_sec: c_int, + tm_min: c_int, + tm_hour: c_int, + tm_mday: c_int, + tm_mon: c_int, + tm_year: c_int, + tm_wday: c_int, + tm_yday: c_int, + tm_isdst: c_int, +}; +pub const passwd = extern struct { + pw_name: [*:0]u8, + pw_passwd: [*:0]u8, + + pw_uid: c_uid, + pw_gid: c_gid, + pw_gecos: [*:0]u8, + pw_dir: [*:0]u8, + pw_shell: [*:0]u8, +}; + +pub const VT_ACTIVATE: c_int = 0x5606; +pub const VT_WAITACTIVE: c_int = 0x5607; + +pub const KDGETLED: c_int = 0x4B31; +pub const KDGKBLED: c_int = 0x4B64; + +pub const LED_NUM: c_int = 0x02; +pub const LED_CAP: c_int = 0x04; + +pub const K_NUMLOCK: c_int = 0x02; +pub const K_CAPSLOCK: c_int = 0x04; + +pub extern "c" fn localtime(timer: *const c_time) *tm; +pub extern "c" fn strftime(str: [*:0]u8, maxsize: c_size, format: [*:0]const u8, timeptr: *const tm) c_size; +pub extern "c" fn setenv(name: [*:0]const u8, value: [*:0]const u8, overwrite: c_int) c_int; +pub extern "c" fn putenv(name: [*:0]u8) c_int; +pub extern "c" fn getuid() c_uid; +pub extern "c" fn getpwnam(name: [*:0]const u8) ?*passwd; +pub extern "c" fn endpwent() void; +pub extern "c" fn setusershell() void; +pub extern "c" fn getusershell() [*:0]u8; +pub extern "c" fn endusershell() void; +pub extern "c" fn initgroups(user: [*:0]const u8, group: c_gid) c_int; + +pub fn timeAsString(buf: [:0]u8, format: [:0]const u8) ![]u8 { + const timer = std.time.timestamp(); + const tm_info = localtime(&timer); + + const len = strftime(buf, buf.len, format, tm_info); + if (len < 0) return error.CannotGetFormattedTime; + + return buf[0..len]; +} + +pub fn getLockState(console_dev: [:0]const u8) !struct { + numlock: bool, + capslock: bool, +} { + const fd = std.c.open(console_dev, .{ .ACCMODE = .RDONLY }); + if (fd < 0) return error.CannotOpenConsoleDev; + defer _ = std.c.close(fd); + + var numlock = false; + var capslock = false; + + if (builtin.os.tag.isBSD()) { + var led: c_int = undefined; + _ = std.c.ioctl(fd, KDGETLED, &led); + numlock = (led & LED_NUM) != 0; + capslock = (led & LED_CAP) != 0; + } else { + var led: c_char = undefined; + _ = std.c.ioctl(fd, KDGKBLED, &led); + numlock = (led & K_NUMLOCK) != 0; + capslock = (led & K_CAPSLOCK) != 0; + } + + return .{ + .numlock = numlock, + .capslock = capslock, + }; +} diff --git a/src/login.c b/src/login.c deleted file mode 100644 index ece1ee6..0000000 --- a/src/login.c +++ /dev/null @@ -1,715 +0,0 @@ -#include "dragonfail.h" -#include "termbox.h" - -#include "inputs.h" -#include "draw.h" -#include "utils.h" -#include "config.h" -#include "login.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -int get_free_display() -{ - char xlock[1024]; - uint8_t i; - - for (i = 0; i < 200; ++i) - { - snprintf(xlock, 1024, "/tmp/.X%d-lock", i); - - if (access(xlock, F_OK) == -1) - { - break; - } - } - - return i; -} - -void reset_terminal(struct passwd* pwd) -{ - pid_t pid = fork(); - - if (pid == 0) - { - execl(pwd->pw_shell, pwd->pw_shell, "-c", config.term_reset_cmd, NULL); - exit(EXIT_SUCCESS); - } - - int status; - waitpid(pid, &status, 0); -} - -int login_conv( - int num_msg, - const struct pam_message** msg, - struct pam_response** resp, - void* appdata_ptr) -{ - *resp = calloc(num_msg, sizeof (struct pam_response)); - - if (*resp == NULL) - { - return PAM_BUF_ERR; - } - - char* username; - char* password; - int ok = PAM_SUCCESS; - int i; - - for (i = 0; i < num_msg; ++i) - { - switch (msg[i]->msg_style) - { - case PAM_PROMPT_ECHO_ON: - { - username = ((char**) appdata_ptr)[0]; - (*resp)[i].resp = strdup(username); - break; - } - case PAM_PROMPT_ECHO_OFF: - { - password = ((char**) appdata_ptr)[1]; - (*resp)[i].resp = strdup(password); - break; - } - case PAM_ERROR_MSG: - { - ok = PAM_CONV_ERR; - break; - } - } - - if (ok != PAM_SUCCESS) - { - break; - } - } - - if (ok != PAM_SUCCESS) - { - for (i = 0; i < num_msg; ++i) - { - if ((*resp)[i].resp == NULL) - { - continue; - } - - free((*resp)[i].resp); - (*resp)[i].resp = NULL; - } - - free(*resp); - *resp = NULL; - } - - return ok; -} - -void pam_diagnose(int error, struct term_buf* buf) -{ - switch (error) - { - case PAM_ACCT_EXPIRED: - { - buf->info_line = lang.err_pam_acct_expired; - break; - } - case PAM_AUTH_ERR: - { - buf->info_line = lang.err_pam_auth; - break; - } - case PAM_AUTHINFO_UNAVAIL: - { - buf->info_line = lang.err_pam_authinfo_unavail; - break; - } - case PAM_BUF_ERR: - { - buf->info_line = lang.err_pam_buf; - break; - } - case PAM_CRED_ERR: - { - buf->info_line = lang.err_pam_cred_err; - break; - } - case PAM_CRED_EXPIRED: - { - buf->info_line = lang.err_pam_cred_expired; - break; - } - case PAM_CRED_INSUFFICIENT: - { - buf->info_line = lang.err_pam_cred_insufficient; - break; - } - case PAM_CRED_UNAVAIL: - { - buf->info_line = lang.err_pam_cred_unavail; - break; - } - case PAM_MAXTRIES: - { - buf->info_line = lang.err_pam_maxtries; - break; - } - case PAM_NEW_AUTHTOK_REQD: - { - buf->info_line = lang.err_pam_authok_reqd; - break; - } - case PAM_PERM_DENIED: - { - buf->info_line = lang.err_pam_perm_denied; - break; - } - case PAM_SESSION_ERR: - { - buf->info_line = lang.err_pam_session; - break; - } - case PAM_SYSTEM_ERR: - { - buf->info_line = lang.err_pam_sys; - break; - } - case PAM_USER_UNKNOWN: - { - buf->info_line = lang.err_pam_user_unknown; - break; - } - case PAM_ABORT: - default: - { - buf->info_line = lang.err_pam_abort; - break; - } - } - - dgn_throw(DGN_PAM); -} - -void env_init(struct passwd* pwd) -{ - extern char** environ; - char* term = getenv("TERM"); - char* lang = getenv("LANG"); - // clean env - environ[0] = NULL; - - setenv("TERM", term ? term : "linux", 1); - setenv("HOME", pwd->pw_dir, 1); - setenv("PWD", pwd->pw_dir, 1); - setenv("SHELL", pwd->pw_shell, 1); - setenv("USER", pwd->pw_name, 1); - setenv("LOGNAME", pwd->pw_name, 1); - setenv("LANG", lang ? lang : "C", 1); - - // Set PATH if specified in the configuration - if (strlen(config.path)) - { - int ok = setenv("PATH", config.path, 1); - - if (ok != 0) - { - dgn_throw(DGN_PATH); - } - } -} - -void env_xdg_session(const enum display_server display_server) -{ - switch (display_server) - { - case DS_WAYLAND: - { - setenv("XDG_SESSION_TYPE", "wayland", 1); - break; - } - case DS_SHELL: - { - setenv("XDG_SESSION_TYPE", "tty", 0); - break; - } - case DS_XINITRC: - case DS_XORG: - { - setenv("XDG_SESSION_TYPE", "x11", 0); - break; - } - } -} - -void env_xdg(const char* tty_id, const char* desktop_name) -{ - char user[20]; - snprintf(user, 20, "/run/user/%d", getuid()); - setenv("XDG_RUNTIME_DIR", user, 0); - setenv("XDG_SESSION_CLASS", "user", 0); - setenv("XDG_SESSION_ID", "1", 0); - setenv("XDG_SESSION_DESKTOP", desktop_name, 0); - setenv("XDG_SEAT", "seat0", 0); - setenv("XDG_VTNR", tty_id, 0); -} - -void add_utmp_entry( - struct utmp *entry, - char *username, - pid_t display_pid -) { - entry->ut_type = USER_PROCESS; - entry->ut_pid = display_pid; - strcpy(entry->ut_line, ttyname(STDIN_FILENO) + strlen("/dev/")); - - /* only correct for ptys named /dev/tty[pqr][0-9a-z] */ - strcpy(entry->ut_id, ttyname(STDIN_FILENO) + strlen("/dev/tty")); - - time((long int *) &entry->ut_time); - - strncpy(entry->ut_user, username, UT_NAMESIZE); - memset(entry->ut_host, 0, UT_HOSTSIZE); - entry->ut_addr = 0; - setutent(); - - pututline(entry); -} - -void remove_utmp_entry(struct utmp *entry) { - entry->ut_type = DEAD_PROCESS; - memset(entry->ut_line, 0, UT_LINESIZE); - entry->ut_time = 0; - memset(entry->ut_user, 0, UT_NAMESIZE); - setutent(); - pututline(entry); - endutent(); -} - -void xauth(const char* display_name, const char* shell, char* pwd) -{ - const char* xauth_file = "lyxauth"; - char* xauth_dir = getenv("XDG_RUNTIME_DIR"); - if ((xauth_dir == NULL) || (*xauth_dir == '\0')) - { - xauth_dir = getenv("XDG_CONFIG_HOME"); - struct stat sb; - if ((xauth_dir == NULL) || (*xauth_dir == '\0')) - { - xauth_dir = strdup(pwd); - strcat(xauth_dir, "/.config"); - stat(xauth_dir, &sb); - if (S_ISDIR(sb.st_mode)) - { - strcat(xauth_dir, "/ly"); - } - else - { - xauth_dir = pwd; - xauth_file = ".lyxauth"; - } - } - else - { - strcat(xauth_dir, "/ly"); - } - - // If .config/ly/ or XDG_CONFIG_HOME/ly/ doesn't exist and can't create the directory, use pwd - // Passing pwd beforehand is safe since stat will always evaluate false - stat(xauth_dir, &sb); - if (!S_ISDIR(sb.st_mode) && mkdir(xauth_dir, 0777) == -1) - { - xauth_dir = pwd; - xauth_file = ".lyxauth"; - } - } - - // trim trailing slashes - int i = strlen(xauth_dir) - 1; - while (xauth_dir[i] == '/') i--; - xauth_dir[i + 1] = '\0'; - - char xauthority[256]; - snprintf(xauthority, 256, "%s/%s", xauth_dir, xauth_file); - setenv("XAUTHORITY", xauthority, 1); - setenv("DISPLAY", display_name, 1); - - FILE* fp = fopen(xauthority, "ab+"); - - if (fp != NULL) - { - fclose(fp); - } - - pid_t pid = fork(); - - if (pid == 0) - { - char cmd[1024]; - snprintf( - cmd, - 1024, - "%s add %s . `%s`", - config.xauth_cmd, - display_name, - config.mcookie_cmd); - execl(shell, shell, "-c", cmd, NULL); - exit(EXIT_SUCCESS); - } - - int status; - waitpid(pid, &status, 0); -} - -void xorg( - struct passwd* pwd, - const char* vt, - const char* desktop_cmd) -{ - char display_name[4]; - - snprintf(display_name, 3, ":%d", get_free_display()); - xauth(display_name, pwd->pw_shell, pwd->pw_dir); - - // start xorg - pid_t pid = fork(); - - if (pid == 0) - { - char x_cmd[1024]; - snprintf( - x_cmd, - 1024, - "%s %s %s", - config.x_cmd, - display_name, - vt); - execl(pwd->pw_shell, pwd->pw_shell, "-c", x_cmd, NULL); - exit(EXIT_SUCCESS); - } - - int ok; - xcb_connection_t* xcb; - - do - { - xcb = xcb_connect(NULL, NULL); - ok = xcb_connection_has_error(xcb); - kill(pid, 0); - } - while((ok != 0) && (errno != ESRCH)); - - if (ok != 0) - { - return; - } - - pid_t xorg_pid = fork(); - - if (xorg_pid == 0) - { - char de_cmd[1024]; - snprintf( - de_cmd, - 1024, - "%s %s", - config.x_cmd_setup, - desktop_cmd); - execl(pwd->pw_shell, pwd->pw_shell, "-c", de_cmd, NULL); - exit(EXIT_SUCCESS); - } - - int status; - waitpid(xorg_pid, &status, 0); - xcb_disconnect(xcb); - kill(pid, 0); - - if (errno != ESRCH) - { - kill(pid, SIGTERM); - waitpid(pid, &status, 0); - } -} - -void wayland( - struct passwd* pwd, - const char* desktop_cmd) -{ - - char cmd[1024]; - snprintf(cmd, 1024, "%s %s", config.wayland_cmd, desktop_cmd); - execl(pwd->pw_shell, pwd->pw_shell, "-c", cmd, NULL); -} - -void shell(struct passwd* pwd) -{ - const char* pos = strrchr(pwd->pw_shell, '/'); - char args[1024]; - args[0] = '-'; - - if (pos != NULL) - { - pos = pos + 1; - } - else - { - pos = pwd->pw_shell; - } - - strncpy(args + 1, pos, 1023); - execl(pwd->pw_shell, args, NULL); -} - -// pam_do performs the pam action specified in pam_action -// on pam_action fail, call diagnose and end pam session -int pam_do( - int (pam_action)(struct pam_handle *, int), - struct pam_handle *handle, - int flags, - struct term_buf *buf) -{ - int status = pam_action(handle, flags); - - if (status != PAM_SUCCESS) { - pam_diagnose(status, buf); - pam_end(handle, status); - } - - return status; -} - -void auth( - struct desktop* desktop, - struct text* login, - struct text* password, - struct term_buf* buf) -{ - int ok; - - char tty_id [3]; - snprintf(tty_id, 3, "%d", config.tty); - - // Add XDG environment variables - env_xdg_session(desktop->display_server[desktop->cur]); - env_xdg(tty_id, desktop->list_simple[desktop->cur]); - - // open pam session - const char* creds[2] = {login->text, password->text}; - struct pam_conv conv = {login_conv, creds}; - struct pam_handle* handle; - - ok = pam_start(config.service_name, NULL, &conv, &handle); - - if (ok != PAM_SUCCESS) - { - pam_diagnose(ok, buf); - pam_end(handle, ok); - return; - } - - ok = pam_do(pam_authenticate, handle, 0, buf); - - if (ok != PAM_SUCCESS) - { - return; - } - - ok = pam_do(pam_acct_mgmt, handle, 0, buf); - - if (ok != PAM_SUCCESS) - { - return; - } - - ok = pam_do(pam_setcred, handle, PAM_ESTABLISH_CRED, buf); - - if (ok != PAM_SUCCESS) - { - return; - } - - ok = pam_do(pam_open_session, handle, 0, buf); - - if (ok != PAM_SUCCESS) - { - return; - } - - // clear the credentials - input_text_clear(password); - - // get passwd structure - struct passwd* pwd = getpwnam(login->text); - endpwent(); - - if (pwd == NULL) - { - dgn_throw(DGN_PWNAM); - pam_end(handle, ok); - return; - } - - // set user shell - if (pwd->pw_shell[0] == '\0') - { - setusershell(); - - char* shell = getusershell(); - - if (shell != NULL) - { - strcpy(pwd->pw_shell, shell); - } - - endusershell(); - } - - // restore regular terminal mode - tb_clear(); - tb_present(); - tb_shutdown(); - - // start desktop environment - pid_t pid = fork(); - - if (pid == 0) - { - // set user info - ok = initgroups(pwd->pw_name, pwd->pw_gid); - - if (ok != 0) - { - dgn_throw(DGN_USER_INIT); - exit(EXIT_FAILURE); - } - - ok = setgid(pwd->pw_gid); - - if (ok != 0) - { - dgn_throw(DGN_USER_GID); - exit(EXIT_FAILURE); - } - - ok = setuid(pwd->pw_uid); - - if (ok != 0) - { - dgn_throw(DGN_USER_UID); - exit(EXIT_FAILURE); - } - - // get a display - char vt[5]; - snprintf(vt, 5, "vt%d", config.tty); - - // set env (this clears the environment) - env_init(pwd); - // Re-add XDG environment variables from lines 508,509 - env_xdg_session(desktop->display_server[desktop->cur]); - env_xdg(tty_id, desktop->list_simple[desktop->cur]); - - if (dgn_catch()) - { - exit(EXIT_FAILURE); - } - - // add pam variables - char** env = pam_getenvlist(handle); - - for (uint16_t i = 0; env && env[i]; ++i) - { - putenv(env[i]); - } - - // execute - int ok = chdir(pwd->pw_dir); - - if (ok != 0) - { - dgn_throw(DGN_CHDIR); - exit(EXIT_FAILURE); - } - - reset_terminal(pwd); - switch (desktop->display_server[desktop->cur]) - { - case DS_WAYLAND: - { - wayland(pwd, desktop->cmd[desktop->cur]); - break; - } - case DS_SHELL: - { - shell(pwd); - break; - } - case DS_XINITRC: - case DS_XORG: - { - xorg(pwd, vt, desktop->cmd[desktop->cur]); - break; - } - } - - exit(EXIT_SUCCESS); - } - - // add utmp audit - struct utmp entry; - add_utmp_entry(&entry, pwd->pw_name, pid); - - // wait for the session to stop - int status; - waitpid(pid, &status, 0); - remove_utmp_entry(&entry); - - reset_terminal(pwd); - - // reinit termbox - tb_init(); - tb_select_output_mode(TB_OUTPUT_NORMAL); - - // reload the desktop environment list on logout - input_desktop_free(desktop); - input_desktop(desktop); - desktop_load(desktop); - - // close pam session - ok = pam_do(pam_close_session, handle, 0, buf); - - if (ok != PAM_SUCCESS) - { - return; - } - - ok = pam_do(pam_setcred, handle, PAM_DELETE_CRED, buf); - - if (ok != PAM_SUCCESS) - { - return; - } - - ok = pam_end(handle, 0); - - if (ok != PAM_SUCCESS) - { - pam_diagnose(ok, buf); - } -} - diff --git a/src/login.h b/src/login.h deleted file mode 100644 index 3fee58f..0000000 --- a/src/login.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef H_LY_LOGIN -#define H_LY_LOGIN - -#include "draw.h" -#include "inputs.h" - -void auth( - struct desktop* desktop, - struct text* login, - struct text* password, - struct term_buf* buf); - -#endif diff --git a/src/main.c b/src/main.c deleted file mode 100644 index 5a62119..0000000 --- a/src/main.c +++ /dev/null @@ -1,381 +0,0 @@ -#include "argoat.h" -#include "configator.h" -#include "dragonfail.h" -#include "termbox.h" - -#include "draw.h" -#include "inputs.h" -#include "login.h" -#include "utils.h" -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#define ARG_COUNT 7 - -#ifndef LY_VERSION -#define LY_VERSION "0.6.0" -#endif - -// global -struct lang lang; -struct config config; - -// args handles -void arg_help(void* data, char** pars, const int pars_count) -{ - printf("If you want to configure Ly, please check the config file, usually located at /etc/ly/config.ini.\n"); - exit(0); -} - -void arg_version(void* data, char** pars, const int pars_count) -{ - printf("Ly version %s\n", LY_VERSION); - exit(0); -} - -// low-level error messages -void log_init(char** log) -{ - log[DGN_OK] = lang.err_dgn_oob; - log[DGN_NULL] = lang.err_null; - log[DGN_ALLOC] = lang.err_alloc; - log[DGN_BOUNDS] = lang.err_bounds; - log[DGN_DOMAIN] = lang.err_domain; - log[DGN_MLOCK] = lang.err_mlock; - log[DGN_XSESSIONS_DIR] = lang.err_xsessions_dir; - log[DGN_XSESSIONS_OPEN] = lang.err_xsessions_open; - log[DGN_PATH] = lang.err_path; - log[DGN_CHDIR] = lang.err_chdir; - log[DGN_PWNAM] = lang.err_pwnam; - log[DGN_USER_INIT] = lang.err_user_init; - log[DGN_USER_GID] = lang.err_user_gid; - log[DGN_USER_UID] = lang.err_user_uid; - log[DGN_PAM] = lang.err_pam; - log[DGN_HOSTNAME] = lang.err_hostname; -} - -void arg_config(void* data, char** pars, const int pars_count) -{ - *((char **)data) = *pars; -} - -// ly! -int main(int argc, char** argv) -{ - // init error lib - log_init(dgn_init()); - - // load config - config_defaults(); - lang_defaults(); - - char *config_path = NULL; - // parse args - const struct argoat_sprig sprigs[ARG_COUNT] = - { - {NULL, 0, NULL, NULL}, - {"config", 0, &config_path, arg_config}, - {"c", 0, &config_path, arg_config}, - {"help", 0, NULL, arg_help}, - {"h", 0, NULL, arg_help}, - {"version", 0, NULL, arg_version}, - {"v", 0, NULL, arg_version}, - }; - - struct argoat args = {sprigs, ARG_COUNT, NULL, 0, 0}; - argoat_graze(&args, argc, argv); - - // init inputs - struct desktop desktop; - struct text login; - struct text password; - input_desktop(&desktop); - input_text(&login, config.max_login_len); - input_text(&password, config.max_password_len); - - if (dgn_catch()) - { - config_free(); - lang_free(); - return 1; - } - - config_load(config_path); - lang_load(); - - void* input_structs[3] = - { - (void*) &desktop, - (void*) &login, - (void*) &password, - }; - - void (*input_handles[3]) (void*, struct tb_event*) = - { - handle_desktop, - handle_text, - handle_text, - }; - - desktop_load(&desktop); - load(&desktop, &login); - - // start termbox - tb_init(); - tb_select_output_mode(TB_OUTPUT_NORMAL); - tb_clear(); - - // init visible elements - struct tb_event event; - struct term_buf buf; - - //Place the curser on the login field if there is no saved username, if there is, place the curser on the password field - uint8_t active_input; - if (config.default_input == LOGIN_INPUT && login.text != login.end){ - active_input = PASSWORD_INPUT; - } - else{ - active_input = config.default_input; - } - - - // init drawing stuff - draw_init(&buf); - - // draw_box and position_input are called because they need to be - // called before *input_handles[active_input] for the cursor to be - // positioned correctly - draw_box(&buf); - position_input(&buf, &desktop, &login, &password); - (*input_handles[active_input])(input_structs[active_input], NULL); - - if (config.animate) - { - animate_init(&buf); - - if (dgn_catch()) - { - config.animate = false; - dgn_reset(); - } - } - - // init state info - int error; - bool run = true; - bool update = true; - bool reboot = false; - bool shutdown = false; - uint8_t auth_fails = 0; - - switch_tty(&buf); - - // main loop - while (run) - { - if (update) - { - if (auth_fails < 10) - { - (*input_handles[active_input])(input_structs[active_input], NULL); - tb_clear(); - animate(&buf); - draw_bigclock(&buf); - draw_box(&buf); - draw_clock(&buf); - draw_labels(&buf); - if(!config.hide_key_hints) - draw_key_hints(); - draw_lock_state(&buf); - position_input(&buf, &desktop, &login, &password); - draw_desktop(&desktop); - draw_input(&login); - draw_input_mask(&password); - update = config.animate; - } - else - { - usleep(10000); - update = cascade(&buf, &auth_fails); - } - - tb_present(); - } - - int timeout = -1; - - if (config.animate) - { - timeout = config.min_refresh_delta; - } - else - { - struct timeval tv; - gettimeofday(&tv, NULL); - if (config.bigclock) - timeout = (60 - tv.tv_sec % 60) * 1000 - tv.tv_usec / 1000 + 1; - if (config.clock) - timeout = 1000 - tv.tv_usec / 1000 + 1; - } - - if (timeout == -1) - { - error = tb_poll_event(&event); - } - else - { - error = tb_peek_event(&event, timeout); - } - - if (error < 0) - { - continue; - } - - if (event.type == TB_EVENT_KEY) - { - char shutdown_key[4]; - memset(shutdown_key, '\0', sizeof(shutdown_key)); - strcpy(shutdown_key, config.shutdown_key); - memcpy(shutdown_key, "0", 1); - - char restart_key[4]; - memset(restart_key, '\0', sizeof(restart_key)); - strcpy(restart_key, config.restart_key); - memcpy(restart_key, "0", 1); - - switch (event.key) - { - case TB_KEY_F1: - case TB_KEY_F2: - case TB_KEY_F3: - case TB_KEY_F4: - case TB_KEY_F5: - case TB_KEY_F6: - case TB_KEY_F7: - case TB_KEY_F8: - case TB_KEY_F9: - case TB_KEY_F10: - case TB_KEY_F11: - case TB_KEY_F12: - if( 0xFFFF - event.key + 1 == atoi(shutdown_key) ) - { - shutdown = true; - run = false; - } - if( 0xFFFF - event.key + 1 == atoi(restart_key) ) - { - reboot = true; - run = false; - } - break; - case TB_KEY_CTRL_C: - run = false; - break; - case TB_KEY_CTRL_U: - if (active_input > 0) - { - input_text_clear(input_structs[active_input]); - update = true; - } - break; - case TB_KEY_CTRL_K: - case TB_KEY_ARROW_UP: - if (active_input > 0) - { - --active_input; - update = true; - } - break; - case TB_KEY_CTRL_J: - case TB_KEY_ARROW_DOWN: - if (active_input < 2) - { - ++active_input; - update = true; - } - break; - case TB_KEY_TAB: - ++active_input; - - if (active_input > 2) - { - active_input = SESSION_SWITCH; - } - update = true; - break; - case TB_KEY_ENTER: - save(&desktop, &login); - auth(&desktop, &login, &password, &buf); - update = true; - - if (dgn_catch()) - { - ++auth_fails; - // move focus back to password input - active_input = PASSWORD_INPUT; - - if (dgn_output_code() != DGN_PAM) - { - buf.info_line = dgn_output_log(); - } - - if (config.blank_password) - { - input_text_clear(&password); - } - - dgn_reset(); - } - else - { - buf.info_line = lang.logout; - } - - load(&desktop, &login); - system("tput cnorm"); - break; - default: - (*input_handles[active_input])( - input_structs[active_input], - &event); - update = true; - break; - } - } - } - - // stop termbox - tb_shutdown(); - - // free inputs - input_desktop_free(&desktop); - input_text_free(&login); - input_text_free(&password); - free_hostname(); - - // unload config - draw_free(&buf); - lang_free(); - - if (shutdown) - { - execl("/bin/sh", "sh", "-c", config.shutdown_cmd, NULL); - } - else if (reboot) - { - execl("/bin/sh", "sh", "-c", config.restart_cmd, NULL); - } - - config_free(); - - return 0; -} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..bff277b --- /dev/null +++ b/src/main.zig @@ -0,0 +1,642 @@ +const std = @import("std"); +const build_options = @import("build_options"); +const builtin = @import("builtin"); +const clap = @import("clap"); +const ini = @import("zigini"); +const auth = @import("auth.zig"); +const bigclock = @import("bigclock.zig"); +const interop = @import("interop.zig"); +const Doom = @import("animations/Doom.zig"); +const Matrix = @import("animations/Matrix.zig"); +const TerminalBuffer = @import("tui/TerminalBuffer.zig"); +const Desktop = @import("tui/components/Desktop.zig"); +const Text = @import("tui/components/Text.zig"); +const InfoLine = @import("tui/components/InfoLine.zig"); +const Config = @import("config/Config.zig"); +const Lang = @import("config/Lang.zig"); +const Save = @import("config/Save.zig"); +const migrator = @import("config/migrator.zig"); +const SharedError = @import("SharedError.zig"); +const utils = @import("tui/utils.zig"); + +const Ini = ini.Ini; +const termbox = interop.termbox; + +var session_pid: std.posix.pid_t = -1; +pub fn signalHandler(i: c_int) callconv(.C) void { + if (session_pid == 0) return; + + // Forward signal to session to clean up + if (session_pid > 0) { + _ = std.c.kill(session_pid, i); + var status: c_int = 0; + _ = std.c.waitpid(session_pid, &status, 0); + } + + termbox.tb_shutdown(); + std.c.exit(i); +} + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + + const allocator = gpa.allocator(); + const stderr = std.io.getStdErr().writer(); + + // Load arguments + const params = comptime clap.parseParamsComptime( + \\-h, --help Shows all commands. + \\-v, --version Shows the version of Ly. + \\-c, --config Overrides the default configuration path. Example: --config /usr/share/ly + ); + + var diag = clap.Diagnostic{}; + var res = clap.parse(clap.Help, ¶ms, clap.parsers.default, .{ .diagnostic = &diag, .allocator = allocator }) catch |err| { + diag.report(stderr, err) catch {}; + return err; + }; + defer res.deinit(); + + var config: Config = undefined; + var lang: Lang = undefined; + var save: Save = undefined; + var info_line = InfoLine{}; + + if (res.args.help != 0) { + try clap.help(stderr, clap.Help, ¶ms, .{}); + + _ = try stderr.write("Note: if you want to configure Ly, please check the config file, which is usually located at /etc/ly/config.ini.\n"); + std.process.exit(0); + } + if (res.args.version != 0) { + _ = try stderr.write("Ly version " ++ build_options.version ++ "\n"); + std.process.exit(0); + } + + // Load configuration file + var config_ini = Ini(Config).init(allocator); + defer config_ini.deinit(); + + var lang_ini = Ini(Lang).init(allocator); + defer lang_ini.deinit(); + + var save_ini = Ini(Save).init(allocator); + defer save_ini.deinit(); + + var save_path: []const u8 = build_options.data_directory ++ "/save.ini"; + var save_path_alloc = false; + defer { + if (save_path_alloc) allocator.free(save_path); + } + + // Compatibility with v0.6.0 + const mapped_config_fields = .{.{ "blank_password", "clear_password" }}; + + if (res.args.config) |s| { + const trailing_slash = if (s[s.len - 1] != '/') "/" else ""; + + const config_path = try std.fmt.allocPrint(allocator, "{s}{s}config.ini", .{ s, trailing_slash }); + defer allocator.free(config_path); + + config = config_ini.readFileToStructWithMap(config_path, mapped_config_fields) catch Config{}; + + const lang_path = try std.fmt.allocPrint(allocator, "{s}{s}lang/{s}.ini", .{ s, trailing_slash, config.lang }); + defer allocator.free(lang_path); + + lang = lang_ini.readFileToStruct(lang_path) catch Lang{}; + + if (config.load) { + save_path = try std.fmt.allocPrint(allocator, "{s}{s}save.ini", .{ s, trailing_slash }); + save_path_alloc = true; + + var user_buf: [32]u8 = undefined; + save = save_ini.readFileToStruct(save_path) catch migrator.tryMigrateSaveFile(&user_buf, config.save_file); + } + } else { + config = config_ini.readFileToStructWithMap(build_options.data_directory ++ "/config.ini", mapped_config_fields) catch Config{}; + + const lang_path = try std.fmt.allocPrint(allocator, "{s}/lang/{s}.ini", .{ build_options.data_directory, config.lang }); + defer allocator.free(lang_path); + + lang = lang_ini.readFileToStruct(lang_path) catch Lang{}; + + if (config.load) { + var user_buf: [32]u8 = undefined; + save = save_ini.readFileToStruct(save_path) catch migrator.tryMigrateSaveFile(&user_buf, config.save_file); + } + } + + // Initialize information line with host name + get_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); + break :get_host_name; + }; + try info_line.setText(hostname); + } + + // Initialize termbox + _ = termbox.tb_init(); + defer termbox.tb_shutdown(); + + const act = std.posix.Sigaction{ + .handler = .{ .handler = &signalHandler }, + .mask = std.posix.empty_sigset, + .flags = 0, + }; + try std.posix.sigaction(std.posix.SIG.TERM, &act, null); + + _ = termbox.tb_select_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); + + // Initialize terminal buffer + const labels_max_length = @max(lang.login.len, lang.password.len); + + var buffer = TerminalBuffer.init(config, labels_max_length); + + // Initialize components + var desktop = try Desktop.init(allocator, &buffer, config.max_desktop_len, lang); + defer desktop.deinit(); + + desktop.addEnvironment(.{ .Name = lang.shell }, "", .shell) catch { + try info_line.setText(lang.err_alloc); + }; + if (config.xinitrc) |xinitrc| { + desktop.addEnvironment(.{ .Name = lang.xinitrc, .Exec = xinitrc }, "", .xinitrc) catch { + try info_line.setText(lang.err_alloc); + }; + } + + try desktop.crawl(config.waylandsessions, .wayland); + try desktop.crawl(config.xsessions, .x11); + + var login = try Text.init(allocator, &buffer, config.max_login_len); + defer login.deinit(); + + var password = try Text.init(allocator, &buffer, config.max_password_len); + defer password.deinit(); + + var active_input = config.default_input; + var insert_mode = !config.vi_mode; + + // Load last saved username and desktop selection, if any + if (config.load) { + if (save.user) |user| { + try login.text.appendSlice(user); + login.end = user.len; + login.cursor = login.end; + active_input = .password; + } + + if (save.session_index) |session_index| { + if (session_index < desktop.environments.items.len) desktop.current = session_index; + } + } + + // Place components on the screen + { + buffer.drawBoxCenter(!config.hide_borders, config.blank_box); + + 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); + + 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); + }, + } + } + + // Initialize the animation, if any + var doom: Doom = undefined; + var matrix: Matrix = undefined; + + switch (config.animation) { + .none => {}, + .doom => doom = try Doom.init(allocator, &buffer), + .matrix => matrix = try Matrix.init(allocator, &buffer), + } + defer { + switch (config.animation) { + .none => {}, + .doom => doom.deinit(), + .matrix => matrix.deinit(), + } + } + + const animate = config.animation != .none; + const shutdown_key = try std.fmt.parseInt(u8, config.shutdown_key[1..], 10); + const shutdown_len = try utils.strWidth(lang.shutdown); + const restart_key = try std.fmt.parseInt(u8, config.restart_key[1..], 10); + const restart_len = try utils.strWidth(lang.restart); + const sleep_key = try std.fmt.parseInt(u8, config.sleep_key[1..], 10); + + var event: termbox.tb_event = undefined; + var run = true; + var update = true; + var resolution_changed = false; + var shutdown = false; + var restart = false; + var auth_fails: u64 = 0; + + // 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) { + try info_line.setText(lang.err_console_dev); + break :open_console_dev; + } + + _ = std.c.ioctl(fd, interop.VT_ACTIVATE, config.tty); + _ = std.c.ioctl(fd, interop.VT_WAITACTIVE, config.tty); + } + + 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); + + 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()); + + if (width != buffer.width) { + buffer.width = width; + resolution_changed = true; + } + + if (height != buffer.height) { + buffer.height = height; + resolution_changed = true; + } + + // If it did change, then update the cell buffer, reallocate the current animation's buffers, and force a draw update + if (resolution_changed) { + buffer.buffer = termbox.tb_cell_buffer(); + + switch (config.animation) { + .none => {}, + .doom => doom.realloc() catch { + try info_line.setText(lang.err_alloc); + }, + .matrix => matrix.realloc() catch { + try info_line.setText(lang.err_alloc); + }, + } + + update = true; + } + } + + 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(); + + switch (config.animation) { + .none => {}, + .doom => doom.draw(), + .matrix => matrix.draw(), + } + + 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 yo = (buffer.height - buffer.box_height) / 2 - bigclock.HEIGHT - 2; + + var clock_buf: [format.len + 1:0]u8 = undefined; + const clock_str = interop.timeAsString(&clock_buf, format) catch { + break :draw_big_clock; + }; + + for (clock_str, 0..) |c, i| { + const clock_cell = bigclock.clockCell(animate, c, buffer.fg, buffer.bg); + bigclock.alphaBlit(buffer.buffer, xo + i * (bigclock.WIDTH + 1), yo, buffer.width, buffer.height, clock_cell); + } + } + + buffer.drawBoxCenter(!config.hide_borders, config.blank_box); + + if (config.clock) |clock| draw_clock: { + var clock_buf: [32:0]u8 = undefined; + const clock_str = interop.timeAsString(&clock_buf, clock) catch { + break :draw_clock; + }; + + if (clock_str.len == 0) return error.FormattedTimeEmpty; + + buffer.drawLabel(clock_str, buffer.width - clock_str.len, 0); + } + + const label_x = buffer.box_x + buffer.margin_box_h; + const label_y = buffer.box_y + buffer.margin_box_v; + + 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); + } + + if (!config.hide_key_hints) { + var length: u64 = 0; + + buffer.drawLabel(config.shutdown_key, length, 0); + length += config.shutdown_key.len + 1; + buffer.drawLabel(" ", length - 1, 0); + + buffer.drawLabel(lang.shutdown, length, 0); + length += shutdown_len + 1; + + buffer.drawLabel(config.restart_key, length, 0); + length += config.restart_key.len + 1; + buffer.drawLabel(" ", length - 1, 0); + + buffer.drawLabel(lang.restart, length, 0); + length += restart_len + 1; + + if (config.sleep_cmd != null) { + buffer.drawLabel(config.sleep_key, length, 0); + length += config.sleep_key.len + 1; + buffer.drawLabel(" ", length - 1, 0); + + buffer.drawLabel(lang.sleep, length, 0); + } + } + + 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); + } + + draw_lock_state: { + const lock_state = interop.getLockState(config.console_dev) catch { + try info_line.setText(lang.err_console_dev); + break :draw_lock_state; + }; + + var lock_state_x = 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; + } + + desktop.draw(); + login.draw(); + password.drawMasked(config.asterisk); + + update = animate; + } else { + std.time.sleep(10_000_000); + update = buffer.cascade(); + + if (!update) { + std.time.sleep(7_000_000_000); + auth_fails = 0; + } + } + + termbox.tb_present(); + } + + var timeout: i32 = -1; + + // Calculate the maximum timeout based on current animations, or the (big) clock. If there's none, we wait for the event indefinitely instead + if (animate) { + timeout = config.min_refresh_delta; + } else if (config.bigclock and config.clock == null) { + var tv: std.c.timeval = undefined; + _ = std.c.gettimeofday(&tv, null); + + timeout = @intCast((60 - @rem(tv.tv_sec, 60)) * 1000 - @divTrunc(tv.tv_usec, 1000) + 1); + } else if (config.clock != null or auth_fails >= 10) { + var tv: std.c.timeval = undefined; + _ = std.c.gettimeofday(&tv, null); + + timeout = @intCast(1000 - @divTrunc(tv.tv_usec, 1000) + 1); + } + + const event_error = if (timeout == -1) termbox.tb_poll_event(&event) else termbox.tb_peek_event(&event, timeout); + + if (event_error < 0 or event.type != termbox.TB_EVENT_KEY) continue; + + switch (event.key) { + termbox.TB_KEY_ESC => { + if (config.vi_mode and insert_mode) { + insert_mode = false; + update = true; + } + }, + termbox.TB_KEY_F12...termbox.TB_KEY_F1 => { + const pressed_key = 0xFFFF - event.key + 1; + if (pressed_key == shutdown_key) { + shutdown = true; + run = false; + } else if (pressed_key == restart_key) { + restart = true; + run = false; + } else if (pressed_key == sleep_key) { + if (config.sleep_cmd) |sleep_cmd| { + var sleep = std.ChildProcess.init(&[_][]const u8{ "/bin/sh", "-c", sleep_cmd }, allocator); + _ = sleep.spawnAndWait() catch .{}; + } + } + }, + termbox.TB_KEY_CTRL_C => run = false, + termbox.TB_KEY_CTRL_U => { + if (active_input == .login) { + login.clear(); + update = true; + } else if (active_input == .password) { + password.clear(); + update = true; + } + }, + termbox.TB_KEY_CTRL_K, termbox.TB_KEY_ARROW_UP => { + active_input = switch (active_input) { + .session, .login => .session, + .password => .login, + }; + update = true; + }, + termbox.TB_KEY_CTRL_J, termbox.TB_KEY_ARROW_DOWN => { + active_input = switch (active_input) { + .session => .login, + .login, .password => .password, + }; + update = true; + }, + termbox.TB_KEY_TAB => { + active_input = switch (active_input) { + .session => .login, + .login => .password, + .password => .session, + }; + update = true; + }, + termbox.TB_KEY_ENTER => { + if (config.save) save_last_settings: { + var file = std.fs.createFileAbsolute(save_path, .{}) catch break :save_last_settings; + defer file.close(); + + const save_data = Save{ + .user = login.text.items, + .session_index = desktop.current, + }; + ini.writeFromStruct(save_data, file.writer(), null) catch break :save_last_settings; + } + + var shared_err = try SharedError.init(); + defer shared_err.deinit(); + + { + const login_text = try allocator.dupeZ(u8, login.text.items); + defer allocator.free(login_text); + const password_text = try allocator.dupeZ(u8, password.text.items); + defer allocator.free(password_text); + + session_pid = try std.posix.fork(); + if (session_pid == 0) { + auth.authenticate(config, desktop, login_text, password_text) catch |err| { + shared_err.writeError(err); + std.process.exit(1); + }; + std.process.exit(0); + } + + _ = std.posix.waitpid(session_pid, 0); + session_pid = -1; + } + + const auth_err = shared_err.readError(); + if (auth_err) |err| { + auth_fails += 1; + active_input = .password; + try info_line.setText(getAuthErrorMsg(err, lang)); + if (config.clear_password or err != error.PamAuthError) password.clear(); + } else { + password.clear(); + try info_line.setText(lang.logout); + } + + try std.posix.tcsetattr(std.posix.STDIN_FILENO, .FLUSH, tb_termios); + termbox.tb_clear(); + + 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) { + switch (event.ch) { + 'k' => { + active_input = switch (active_input) { + .session, .login => .session, + .password => .login, + }; + update = true; + continue; + }, + 'j' => { + active_input = switch (active_input) { + .session => .login, + .login, .password => .password, + }; + update = true; + continue; + }, + 'i' => { + insert_mode = true; + update = true; + continue; + }, + else => {}, + } + } + + switch (active_input) { + .session => desktop.handle(&event, insert_mode), + .login => login.handle(&event, insert_mode) catch { + try info_line.setText(lang.err_alloc); + }, + .password => password.handle(&event, insert_mode) catch { + try info_line.setText(lang.err_alloc); + }, + } + update = true; + }, + } + } + + if (shutdown) { + return std.process.execv(allocator, &[_][]const u8{ "/bin/sh", "-c", config.shutdown_cmd }); + } else if (restart) { + return std.process.execv(allocator, &[_][]const u8{ "/bin/sh", "-c", config.restart_cmd }); + } +} + +fn getAuthErrorMsg(err: anyerror, lang: Lang) []const u8 { + return switch (err) { + error.GetPasswordNameFailed => lang.err_pwnam, + error.GroupInitializationFailed => lang.err_user_init, + error.SetUserGidFailed => lang.err_user_gid, + error.SetUserUidFailed => lang.err_user_uid, + error.ChangeDirectoryFailed => lang.err_perm_dir, + error.SetPathFailed => lang.err_path, + error.PamAccountExpired => lang.err_pam_acct_expired, + error.PamAuthError => lang.err_pam_auth, + error.PamAuthInfoUnavailable => lang.err_pam_authinfo_unavail, + error.PamBufferError => lang.err_pam_buf, + error.PamCredentialsError => lang.err_pam_cred_err, + error.PamCredentialsExpired => lang.err_pam_cred_expired, + error.PamCredentialsInsufficient => lang.err_pam_cred_insufficient, + error.PamCredentialsUnavailable => lang.err_pam_cred_unavail, + error.PamMaximumTries => lang.err_pam_maxtries, + error.PamNewAuthTokenRequired => lang.err_pam_authok_reqd, + error.PamPermissionDenied => lang.err_pam_perm_denied, + error.PamSessionError => lang.err_pam_session, + error.PamSystemError => lang.err_pam_sys, + error.PamUserUnknown => lang.err_pam_user_unknown, + error.PamAbort => lang.err_pam_abort, + else => lang.err_unknown, + }; +} diff --git a/src/tui/TerminalBuffer.zig b/src/tui/TerminalBuffer.zig new file mode 100644 index 0000000..8e95886 --- /dev/null +++ b/src/tui/TerminalBuffer.zig @@ -0,0 +1,177 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const interop = @import("../interop.zig"); +const utils = @import("utils.zig"); +const Config = @import("../config/Config.zig"); + +const Random = std.rand.Random; + +const termbox = interop.termbox; + +const TerminalBuffer = @This(); + +random: Random, +width: u64, +height: u64, +buffer: [*]termbox.tb_cell, +fg: u8, +bg: u8, +border_fg: u8, +box_chars: struct { + left_up: u32, + left_down: u32, + right_up: u32, + right_down: u32, + top: u32, + bottom: u32, + left: u32, + right: u32, +}, +labels_max_length: u64, +box_x: u64, +box_y: u64, +box_width: u64, +box_height: u64, +margin_box_v: u8, +margin_box_h: u8, + +pub fn init(config: Config, labels_max_length: u64) TerminalBuffer { + var prng = std.rand.Isaac64.init(@intCast(std.time.timestamp())); + + return .{ + .random = prng.random(), + .width = @intCast(termbox.tb_width()), + .height = @intCast(termbox.tb_height()), + .buffer = termbox.tb_cell_buffer(), + .fg = config.fg, + .bg = config.bg, + .border_fg = config.border_fg, + .box_chars = if (builtin.os.tag == .linux or builtin.os.tag.isBSD()) .{ + .left_up = 0x250C, + .left_down = 0x2514, + .right_up = 0x2510, + .right_down = 0x2518, + .top = 0x2500, + .bottom = 0x2500, + .left = 0x2502, + .right = 0x2502, + } else .{ + .left_up = '+', + .left_down = '+', + .right_up = '+', + .right_down = '+', + .top = '-', + .bottom = '-', + .left = '|', + .right = '|', + }, + .labels_max_length = labels_max_length, + .box_x = 0, + .box_y = 0, + .box_width = (2 * config.margin_box_h) + config.input_len + 1 + labels_max_length, + .box_height = 7 + (2 * config.margin_box_v), + .margin_box_v = config.margin_box_v, + .margin_box_h = config.margin_box_h, + }; +} + +pub fn cascade(self: TerminalBuffer) bool { + var changes = false; + + var y = self.height - 2; + while (y > 0) : (y -= 1) { + for (0..self.width) |x| { + const c: u8 = @truncate(self.buffer[(y - 1) * self.width + x].ch); + if (std.ascii.isWhitespace(c)) continue; + + const c_under: u8 = @truncate(self.buffer[y * self.width + x].ch); + if (!std.ascii.isWhitespace(c_under)) continue; + + changes = true; + + if ((self.random.int(u16) % 10) > 7) continue; + + self.buffer[y * self.width + x] = self.buffer[(y - 1) * self.width + x]; + self.buffer[(y - 1) * self.width + x].ch = ' '; + } + } + + return changes; +} + +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; + + 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); + + 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); + } + + 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); + } + } + + if (blank_box) { + const blank = utils.initCell(' ', self.fg, self.bg); + + for (0..self.box_height) |y| { + for (0..self.box_width) |x| { + termbox.tb_put_cell(@intCast(x1 + x), @intCast(y1 + y), &blank); + } + } + } +} + +pub fn calculateComponentCoordinates(self: TerminalBuffer) struct { + x: u64, + y: u64, + visible_length: u64, +} { + const x = self.box_x + self.margin_box_h + self.labels_max_length + 1; + const y = self.box_y + self.margin_box_v; + const visible_length = self.box_x + self.box_width - self.margin_box_h - x; + + return .{ + .x = x, + .y = y, + .visible_length = visible_length, + }; +} + +pub fn drawLabel(self: TerminalBuffer, text: []const u8, x: u64, y: u64) void { + const yc: c_int = @intCast(y); + const utf8view = std.unicode.Utf8View.init(text) catch return; + var utf8 = utf8view.iterator(); + + var i = x; + while (utf8.nextCodepoint()) |codepoint| : (i += 1) { + termbox.tb_change_cell(@intCast(i), yc, codepoint, self.fg, self.bg); + } +} + +pub fn drawCharMultiple(self: TerminalBuffer, char: u8, x: u64, y: u64, length: u64) void { + 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); +} diff --git a/src/tui/components/Desktop.zig b/src/tui/components/Desktop.zig new file mode 100644 index 0000000..784918e --- /dev/null +++ b/src/tui/components/Desktop.zig @@ -0,0 +1,223 @@ +const std = @import("std"); +const enums = @import("../../enums.zig"); +const interop = @import("../../interop.zig"); +const TerminalBuffer = @import("../TerminalBuffer.zig"); +const Ini = @import("zigini").Ini; +const Lang = @import("../../config/Lang.zig"); + +const Allocator = std.mem.Allocator; +const EnvironmentList = std.ArrayList(Environment); + +const DisplayServer = enums.DisplayServer; + +const termbox = interop.termbox; + +const Desktop = @This(); + +pub const Environment = struct { + entry_ini: ?Ini(Entry) = null, + name: [:0]const u8 = "", + xdg_session_desktop: [:0]const u8 = "", + xdg_desktop_names: ?[:0]const u8 = "", + cmd: []const u8 = "", + specifier: []const u8 = "", + display_server: DisplayServer = .wayland, +}; + +const DesktopEntry = struct { + Exec: []const u8 = "", + Name: [:0]const u8 = "", + DesktopNames: ?[]const u8 = null, +}; + +pub const Entry = struct { @"Desktop Entry": DesktopEntry = DesktopEntry{} }; + +allocator: Allocator, +buffer: *TerminalBuffer, +environments: EnvironmentList, +current: u64, +visible_length: u64, +x: u64, +y: u64, +lang: Lang, + +pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: u64, lang: Lang) !Desktop { + return .{ + .allocator = allocator, + .buffer = buffer, + .environments = try EnvironmentList.initCapacity(allocator, max_length), + .current = 0, + .visible_length = 0, + .x = 0, + .y = 0, + .lang = lang, + }; +} + +pub fn deinit(self: Desktop) void { + for (self.environments.items) |*environment| { + if (environment.entry_ini) |*entry_ini| entry_ini.deinit(); + if (environment.xdg_desktop_names) |desktop_name| self.allocator.free(desktop_name); + self.allocator.free(environment.xdg_session_desktop); + } + + self.environments.deinit(); +} + +pub fn position(self: *Desktop, x: u64, y: u64, visible_length: u64) void { + self.x = x; + self.y = y; + self.visible_length = visible_length; +} + +pub fn addEnvironment(self: *Desktop, entry: DesktopEntry, xdg_session_desktop: []const u8, display_server: DisplayServer) !void { + var xdg_desktop_names: ?[:0]const u8 = null; + if (entry.DesktopNames) |desktop_names| { + const desktop_names_z = try self.allocator.dupeZ(u8, desktop_names); + for (desktop_names_z) |*c| { + if (c.* == ';') c.* = ':'; + } + xdg_desktop_names = desktop_names_z; + } + + errdefer { + if (xdg_desktop_names) |desktop_names| self.allocator.free(desktop_names); + } + + const session_desktop = try self.allocator.dupeZ(u8, xdg_session_desktop); + errdefer self.allocator.free(session_desktop); + + try self.environments.append(.{ + .entry_ini = null, + .name = entry.Name, + .xdg_session_desktop = session_desktop, + .xdg_desktop_names = xdg_desktop_names, + .cmd = entry.Exec, + .specifier = switch (display_server) { + .wayland => self.lang.wayland, + .x11 => self.lang.x11, + else => self.lang.other, + }, + .display_server = display_server, + }); + + self.current = self.environments.items.len - 1; +} + +pub fn addEnvironmentWithIni(self: *Desktop, entry_ini: Ini(Entry), xdg_session_desktop: []const u8, display_server: DisplayServer) !void { + const entry = entry_ini.data.@"Desktop Entry"; + var xdg_desktop_names: ?[:0]const u8 = null; + if (entry.DesktopNames) |desktop_names| { + const desktop_names_z = try self.allocator.dupeZ(u8, desktop_names); + for (desktop_names_z) |*c| { + if (c.* == ';') c.* = ':'; + } + xdg_desktop_names = desktop_names_z; + } + + errdefer { + if (xdg_desktop_names) |desktop_names| self.allocator.free(desktop_names); + } + + const session_desktop = try self.allocator.dupeZ(u8, xdg_session_desktop); + errdefer self.allocator.free(session_desktop); + + try self.environments.append(.{ + .entry_ini = entry_ini, + .name = entry.Name, + .xdg_session_desktop = session_desktop, + .xdg_desktop_names = xdg_desktop_names, + .cmd = entry.Exec, + .specifier = switch (display_server) { + .wayland => self.lang.wayland, + .x11 => self.lang.x11, + else => self.lang.other, + }, + .display_server = display_server, + }); + + self.current = self.environments.items.len - 1; +} + +pub fn crawl(self: *Desktop, path: []const u8, display_server: DisplayServer) !void { + var iterable_directory = std.fs.openDirAbsolute(path, .{ .iterate = true }) catch return; + defer iterable_directory.close(); + + var iterator = iterable_directory.iterate(); + while (try iterator.next()) |item| { + if (!std.mem.eql(u8, std.fs.path.extension(item.name), ".desktop")) continue; + + const entry_path = try std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ path, item.name }); + defer self.allocator.free(entry_path); + var entry_ini = Ini(Entry).init(self.allocator); + _ = try entry_ini.readFileToStruct(entry_path); + errdefer entry_ini.deinit(); + + var xdg_session_desktop: []const u8 = undefined; + const maybe_desktop_names = entry_ini.data.@"Desktop Entry".DesktopNames; + if (maybe_desktop_names) |desktop_names| { + xdg_session_desktop = std.mem.sliceTo(desktop_names, ';'); + } else { + // if DesktopNames is empty, we'll take the name of the session file + xdg_session_desktop = std.fs.path.stem(item.name); + } + + try self.addEnvironmentWithIni(entry_ini, xdg_session_desktop, display_server); + } +} + +pub fn handle(self: *Desktop, maybe_event: ?*termbox.tb_event, insert_mode: bool) void { + if (maybe_event) |event| blk: { + if (event.type != termbox.TB_EVENT_KEY) break :blk; + + switch (event.key) { + termbox.TB_KEY_ARROW_LEFT, termbox.TB_KEY_CTRL_H => self.goLeft(), + termbox.TB_KEY_ARROW_RIGHT, termbox.TB_KEY_CTRL_L => self.goRight(), + else => { + if (!insert_mode) { + switch (event.ch) { + 'h' => self.goLeft(), + 'l' => self.goRight(), + else => {}, + } + } + }, + } + } + + termbox.tb_set_cursor(@intCast(self.x + 2), @intCast(self.y)); +} + +pub fn draw(self: Desktop) void { + const environment = self.environments.items[self.current]; + + const length = @min(environment.name.len, self.visible_length - 3); + if (length == 0) return; + + const x = self.buffer.box_x + self.buffer.margin_box_h; + 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); + + self.buffer.drawLabel(environment.name, self.x + 2, self.y); +} + +fn goLeft(self: *Desktop) void { + if (self.current == 0) { + self.current = self.environments.items.len - 1; + return; + } + + self.current -= 1; +} + +fn goRight(self: *Desktop) void { + if (self.current == self.environments.items.len - 1) { + self.current = 0; + return; + } + + self.current += 1; +} diff --git a/src/tui/components/InfoLine.zig b/src/tui/components/InfoLine.zig new file mode 100644 index 0000000..0a216a9 --- /dev/null +++ b/src/tui/components/InfoLine.zig @@ -0,0 +1,11 @@ +const utils = @import("../utils.zig"); + +const InfoLine = @This(); + +text: []const u8 = "", +width: u8 = 0, + +pub fn setText(self: *InfoLine, text: []const u8) !void { + self.width = if (text.len > 0) try utils.strWidth(text) else 0; + self.text = text; +} diff --git a/src/tui/components/Text.zig b/src/tui/components/Text.zig new file mode 100644 index 0000000..fe8b0d1 --- /dev/null +++ b/src/tui/components/Text.zig @@ -0,0 +1,142 @@ +const std = @import("std"); +const interop = @import("../../interop.zig"); +const TerminalBuffer = @import("../TerminalBuffer.zig"); +const utils = @import("../utils.zig"); + +const Allocator = std.mem.Allocator; +const DynamicString = std.ArrayList(u8); + +const termbox = interop.termbox; + +const Text = @This(); + +allocator: Allocator, +buffer: *TerminalBuffer, +text: DynamicString, +end: u64, +cursor: u64, +visible_start: u64, +visible_length: u64, +x: u64, +y: u64, + +pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: u64) !Text { + const text = try DynamicString.initCapacity(allocator, max_length); + + return .{ + .allocator = allocator, + .buffer = buffer, + .text = text, + .end = 0, + .cursor = 0, + .visible_start = 0, + .visible_length = 0, + .x = 0, + .y = 0, + }; +} + +pub fn deinit(self: Text) void { + self.text.deinit(); +} + +pub fn position(self: *Text, x: u64, y: u64, visible_length: u64) void { + self.x = x; + self.y = y; + self.visible_length = visible_length; +} + +pub fn handle(self: *Text, maybe_event: ?*termbox.tb_event, insert_mode: bool) !void { + if (maybe_event) |event| blk: { + if (event.type != termbox.TB_EVENT_KEY) break :blk; + + switch (event.key) { + termbox.TB_KEY_ARROW_LEFT => self.goLeft(), + termbox.TB_KEY_ARROW_RIGHT => self.goRight(), + termbox.TB_KEY_DELETE => self.delete(), + termbox.TB_KEY_BACKSPACE, termbox.TB_KEY_BACKSPACE2 => { + if (insert_mode) { + self.backspace(); + } else { + self.goLeft(); + } + }, + termbox.TB_KEY_SPACE => try self.write(' '), + else => { + if (event.ch > 31 and event.ch < 127) { + if (insert_mode) { + try self.write(@intCast(event.ch)); + } else { + switch (event.ch) { + 'h' => self.goLeft(), + 'l' => self.goRight(), + else => {}, + } + } + } + }, + } + } + + 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..]; + self.buffer.drawLabel(visible_slice, self.x, self.y); +} + +pub fn drawMasked(self: Text, mask: u8) void { + const length = @min(self.text.items.len, self.visible_length - 1); + if (length == 0) return; + + self.buffer.drawCharMultiple(mask, self.x, self.y, length); +} + +pub fn clear(self: *Text) void { + self.text.clearRetainingCapacity(); + self.end = 0; + self.cursor = 0; + self.visible_start = 0; +} + +fn goLeft(self: *Text) void { + if (self.cursor == 0) return; + if (self.visible_start > 0) self.visible_start -= 1; + + self.cursor -= 1; +} + +fn goRight(self: *Text) void { + if (self.cursor >= self.end) return; + if (self.cursor - self.visible_start == self.visible_length - 1) self.visible_start += 1; + + self.cursor += 1; +} + +fn delete(self: *Text) void { + if (self.cursor >= self.end) return; + + _ = self.text.orderedRemove(self.cursor); + + self.end -= 1; +} + +fn backspace(self: *Text) void { + if (self.cursor == 0) return; + + self.goLeft(); + self.delete(); +} + +fn write(self: *Text, char: u8) !void { + if (char == 0) return; + + try self.text.insert(self.cursor, char); + + self.end += 1; + self.goRight(); +} diff --git a/src/tui/utils.zig b/src/tui/utils.zig new file mode 100644 index 0000000..37fe2d0 --- /dev/null +++ b/src/tui/utils.zig @@ -0,0 +1,22 @@ +const std = @import("std"); +const interop = @import("../interop.zig"); + +const termbox = interop.termbox; + +pub fn initCell(ch: u32, fg: u32, bg: u32) termbox.tb_cell { + return .{ + .ch = ch, + .fg = fg, + .bg = 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 { + const utf8view = try std.unicode.Utf8View.init(str); + var utf8 = utf8view.iterator(); + var i: u8 = 0; + while (utf8.nextCodepoint()) |_| i += 1; + return i; +} diff --git a/src/utils.c b/src/utils.c deleted file mode 100644 index b7bfabc..0000000 --- a/src/utils.c +++ /dev/null @@ -1,276 +0,0 @@ -#include "configator.h" -#include "dragonfail.h" -#include "inputs.h" -#include "config.h" -#include "utils.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(__DragonFly__) || defined(__FreeBSD__) - #include -#else // linux - #include -#endif - -void desktop_crawl( - struct desktop* target, - char* sessions, - enum display_server server) -{ - DIR* dir; - struct dirent* dir_info; - int ok; - - ok = access(sessions, F_OK); - - if (ok == -1) - { - dgn_throw(DGN_XSESSIONS_DIR); - return; - } - - dir = opendir(sessions); - - if (dir == NULL) - { - dgn_throw(DGN_XSESSIONS_OPEN); - return; - } - - char* name = NULL; - char* exec = NULL; - - struct configator_param map_desktop[] = - { - {"Exec", &exec, config_handle_str}, - {"Name", &name, config_handle_str}, - }; - - struct configator_param* map[] = - { - NULL, - map_desktop, - }; - - struct configator_param sections[] = - { - {"Desktop Entry", NULL, NULL}, - }; - - uint16_t map_len[] = {0, 2}; - uint16_t sections_len = 1; - - struct configator desktop_config; - desktop_config.map = map; - desktop_config.map_len = map_len; - desktop_config.sections = sections; - desktop_config.sections_len = sections_len; - -#if defined(NAME_MAX) - char path[NAME_MAX]; -#elif defined(_POSIX_PATH_MAX) - char path[_POSIX_PATH_MAX]; -#else - char path[1024]; -#endif - - dir_info = readdir(dir); - - while (dir_info != NULL) - { - if ((dir_info->d_name)[0] == '.') - { - dir_info = readdir(dir); - continue; - } - - snprintf(path, (sizeof (path)) - 1, "%s/", sessions); - strncat(path, dir_info->d_name, (sizeof (path)) - 1); - configator(&desktop_config, path); - - // if these are wayland sessions, add " (Wayland)" to their names, - // as long as their names don't already contain that string - if (server == DS_WAYLAND && config.wayland_specifier) - { - const char wayland_specifier[] = " (Wayland)"; - if (strstr(name, wayland_specifier) == NULL) - { - name = realloc(name, (strlen(name) + sizeof(wayland_specifier) + 1)); - // using strcat is safe because the string is constant - strcat(name, wayland_specifier); - } - } - - if ((name != NULL) && (exec != NULL)) - { - input_desktop_add(target, name, exec, server); - } - - name = NULL; - exec = NULL; - dir_info = readdir(dir); - } - - closedir(dir); -} - -void desktop_load(struct desktop* target) -{ - // we don't care about desktop environments presence - // because the fallback shell is always available - // so we just dismiss any "throw" for now - int err = 0; - - desktop_crawl(target, config.waylandsessions, DS_WAYLAND); - - if (dgn_catch()) - { - ++err; - dgn_reset(); - } - - desktop_crawl(target, config.xsessions, DS_XORG); - - if (dgn_catch()) - { - ++err; - dgn_reset(); - } -} - -static char* hostname_backup = NULL; - -void hostname(char** out) -{ - if (hostname_backup != NULL) - { - *out = hostname_backup; - return; - } - - int maxlen = sysconf(_SC_HOST_NAME_MAX); - - if (maxlen < 0) - { - maxlen = _POSIX_HOST_NAME_MAX; - } - - hostname_backup = malloc(maxlen + 1); - - if (hostname_backup == NULL) - { - dgn_throw(DGN_ALLOC); - return; - } - - if (gethostname(hostname_backup, maxlen) < 0) - { - dgn_throw(DGN_HOSTNAME); - return; - } - - hostname_backup[maxlen] = '\0'; - *out = hostname_backup; -} - -void free_hostname() -{ - free(hostname_backup); -} - -void switch_tty(struct term_buf* buf) -{ - FILE* console = fopen(config.console_dev, "w"); - - if (console == NULL) - { - buf->info_line = lang.err_console_dev; - return; - } - - int fd = fileno(console); - - ioctl(fd, VT_ACTIVATE, config.tty); - ioctl(fd, VT_WAITACTIVE, config.tty); - - fclose(console); -} - -void save(struct desktop* desktop, struct text* login) -{ - if (config.save) - { - FILE* fp = fopen(config.save_file, "wb+"); - - if (fp != NULL) - { - fprintf(fp, "%s\n%d", login->text, desktop->cur); - fclose(fp); - } - } -} - -void load(struct desktop* desktop, struct text* login) -{ - if (!config.load) - { - return; - } - - FILE* fp = fopen(config.save_file, "rb"); - - if (fp == NULL) - { - return; - } - - char* line = malloc(config.max_login_len + 1); - - if (line == NULL) - { - fclose(fp); - return; - } - - if (fgets(line, config.max_login_len + 1, fp)) - { - int len = strlen(line); - strncpy(login->text, line, login->len); - - if (len == 0) - { - login->end = login->text; - } - else - { - login->end = login->text + len - 1; - login->text[len - 1] = '\0'; - } - } - else - { - fclose(fp); - free(line); - return; - } - - if (fgets(line, config.max_login_len + 1, fp)) - { - int saved_cur = abs(atoi(line)); - - if (saved_cur < desktop->len) - { - desktop->cur = saved_cur; - } - } - - fclose(fp); - free(line); -} diff --git a/src/utils.h b/src/utils.h deleted file mode 100644 index 0913580..0000000 --- a/src/utils.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef H_LY_UTILS -#define H_LY_UTILS - -#include "draw.h" -#include "inputs.h" -#include "config.h" - -void desktop_load(struct desktop* target); -void hostname(char** out); -void free_hostname(); -void switch_tty(struct term_buf* buf); -void save(struct desktop* desktop, struct text* login); -void load(struct desktop* desktop, struct text* login); - -#endif diff --git a/sub/argoat b/sub/argoat deleted file mode 160000 index e1844c4..0000000 --- a/sub/argoat +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e1844c4c94b70bb351ec2bd2ac6bb320ee793d8f diff --git a/sub/configator b/sub/configator deleted file mode 160000 index 8cec178..0000000 --- a/sub/configator +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8cec1786196ae6f6a8b35e66181277457f2a2bb2 diff --git a/sub/dragonfail b/sub/dragonfail deleted file mode 160000 index 15bd329..0000000 --- a/sub/dragonfail +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 15bd3299bf3e49bd6734bff385cb0392cd2fa502 diff --git a/sub/termbox_next b/sub/termbox_next deleted file mode 160000 index d961a81..0000000 --- a/sub/termbox_next +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d961a8122210010e7c2c8f201e61170c13d319b4