mirror of https://github.com/fairyglade/ly
Compare commits
5 Commits
4ee2b3ecc7
...
7506d6a7d5
Author | SHA1 | Date |
---|---|---|
ShiningLea | 7506d6a7d5 | 3 weeks ago |
ShiningLea | a7615a33e0 | 3 weeks ago |
Jan Černý | 0586f3424a | 3 weeks ago |
Motodavide | b457b454ae | 3 weeks ago |
ShiningLea | d8d2d5a8bf | 3 weeks ago |
@ -1,3 +1,4 @@
|
||||
bin
|
||||
obj
|
||||
valgrind.log
|
||||
.idea/
|
||||
zig-cache/
|
||||
zig-out/
|
||||
valgrind.log
|
@ -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
|
@ -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 });
|
||||
}
|
||||
}
|
@ -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 = .{""},
|
||||
}
|
@ -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.
|
@ -0,0 +1,7 @@
|
||||
bin
|
||||
obj
|
||||
src/demo/*.o
|
||||
src/demo/keyboard
|
||||
src/demo/output
|
||||
src/demo/paint
|
||||
src/demo/truecolor
|
@ -0,0 +1,19 @@
|
||||
Copyright (C) 2010-2013 nsf <no.smile.face@gmail.com>
|
||||
|
||||
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.
|
@ -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/
|
@ -0,0 +1,827 @@
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#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;
|
||||
}
|
@ -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
|
@ -0,0 +1,156 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#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;
|
||||
}
|
@ -0,0 +1,183 @@
|
||||
#include "../termbox.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -0,0 +1,319 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#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;
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#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));
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
#ifndef H_MEMSTREAM
|
||||
#define H_MEMSTREAM
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
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
|
@ -0,0 +1,195 @@
|
||||
#include "ringbuffer.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h> // 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
#ifndef H_RINGBUFFER
|
||||
#define H_RINGBUFFER
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#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
|
@ -0,0 +1,412 @@
|
||||
#include <sys/stat.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
#ifndef H_TERM
|
||||
#define H_TERM
|
||||
|
||||
#include "termbox.h"
|
||||
#include "ringbuffer.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#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
|
@ -0,0 +1,885 @@
|
||||
#include "term.h"
|
||||
#include "termbox.h"
|
||||
#include "memstream.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/time.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <wchar.h>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,307 @@
|
||||
#ifndef H_TERMBOX
|
||||
#define H_TERMBOX
|
||||
#include <stdint.h>
|
||||
|
||||
// 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
|
@ -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;
|
||||
}
|
@ -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
|
@ -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)
|
@ -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)
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
@ -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 = ' ';
|
||||
}
|
||||
}
|
@ -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,
|
||||
};
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
@ -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,
|
||||
};
|
||||
}
|
@ -1,385 +0,0 @@
|
||||
#include "configator.h"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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);
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
#ifndef H_LY_CONFIG
|
||||
#define H_LY_CONFIG
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
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
|
@ -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",
|
@ -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",
|
@ -0,0 +1,2 @@
|
||||
user: ?[]const u8 = null,
|
||||
session_index: ?u64 = null,
|
@ -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;
|
||||
}
|
@ -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
|
File diff suppressed because it is too large
Load Diff
@ -1,92 +0,0 @@
|
||||
#ifndef H_LY_DRAW
|
||||
#define H_LY_DRAW
|
||||
|
||||
#include "termbox.h"
|
||||
#include "inputs.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
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
|
@ -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,
|
||||
};
|
@ -1,285 +0,0 @@
|
||||
#include "dragonfail.h"
|
||||
#include "termbox.h"
|
||||
#include "inputs.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <ctype.h>
|
||||
|
||||
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;
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
#ifndef H_LY_INPUTS
|
||||
#define H_LY_INPUTS
|
||||
|
||||
#include "termbox.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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
|
@ -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,
|
||||
};
|
||||
}
|
@ -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 <errno.h>
|
||||
#include <grp.h>
|
||||
#include <pwd.h>
|
||||
#include <security/pam_appl.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <utmp.h>
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
@ -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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#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;
|
||||
}
|
@ -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 <str> 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,
|
||||
};
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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();
|
||||
}
|
@ -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;
|
||||
}
|
@ -1,276 +0,0 @@
|
||||
#include "configator.h"
|
||||
#include "dragonfail.h"
|
||||
#include "inputs.h"
|
||||
#include "config.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if defined(__DragonFly__) || defined(__FreeBSD__)
|
||||
#include <sys/consio.h>
|
||||
#else // linux
|
||||
#include <linux/vt.h>
|
||||
#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);
|
||||
}
|
@ -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
|
@ -1 +0,0 @@
|
||||
Subproject commit e1844c4c94b70bb351ec2bd2ac6bb320ee793d8f
|
@ -1 +0,0 @@
|
||||
Subproject commit 8cec1786196ae6f6a8b35e66181277457f2a2bb2
|
@ -1 +0,0 @@
|
||||
Subproject commit 15bd3299bf3e49bd6734bff385cb0392cd2fa502
|
@ -1 +0,0 @@
|
||||
Subproject commit d961a8122210010e7c2c8f201e61170c13d319b4
|
Loading…
Reference in New Issue