Compare commits

...

5 Commits

Author SHA1 Message Date
ShiningLea 7506d6a7d5
Clarify how to update + add DWL (closes #551, #547) 3 weeks ago
ShiningLea a7615a33e0
Clarify status of dependencies 3 weeks ago
Jan Černý 0586f3424a
Update cs.ini (#559) 3 weeks ago
Motodavide b457b454ae
Added Gentoo tip to readme.md (#591) 3 weeks ago
ShiningLea d8d2d5a8bf
Nobody expects the Ziguanas (#517)
* Add build.zig, remove makefile, add .idea directory to .gitignore

* Remove submodules, add projects directly

* Remove submodules

* Add projects

* Rename sub/ to dep/, remove makefiles

* Rewrite main.c

* Remove Argoat dependency

* Remove unused dependencies

* Rewrite config.c

* Add files

* Change default fg to 8 in config.ini

* Partially rewrite utils.c

* Use Zig package manager

* Rewrite INPUTS enum in Zig

* Commit unfinished full rewrite (Zig 0.11.0)
What needs to be dealt with:
- Matrix animation
- Authentication part
- Testing on actual TTY (not just virtual console)

Signed-off-by: AnErrupTion <anerruption@disroot.org>

* Implement more (untested) authentication code

Signed-off-by: AnErrupTion <anerruption@disroot.org>

* Fix some bugs (hopefully)

Signed-off-by: AnErrupTion <anerruption@disroot.org>

* Try to fix some more bugs

Signed-off-by: AnErrupTion <anerruption@disroot.org>

* Oops, forgot to allocate hehe

Signed-off-by: AnErrupTion <anerruption@disroot.org>

* Changes in the Zig rewrite (#596)

* Everything

* make matrix.zig a bit cleaner

* make long lines shorter and add changelog

* vi mode

* update changelog

* get errors from child process and (hopefully) fix some other things

* fix utmp entry

* run authentication in a child process

* update changelog

* small code improvements

* change that

* clear terminal on SIGTERM

* Remove LogFile

* moved ini to a lib, fixed alternative langs

* fix logging out

* oops

* code improvements

* consistency

* clearing the env isn't needed anymore (afaik)

* replace vi_mode with a bool

* type aliases, avoiding zeroes(), breaking a long line

* lowercase insert/normal, merge conditionals, code improvements

* Add experimental save file migrator + bug fixes + add "-dev" version suffix

Signed-off-by: AnErrupTion <anerruption@disroot.org>

* Resolve conflicts

Signed-off-by: AnErrupTion <anerruption@disroot.org>

* Clean up when SIGTERM is received (#597)

* clean up child processes on SIGTERM

* small code improvement

* consistency.. i guess?

* Properly set XDG_CURRENT_DESKTOP

Signed-off-by: AnErrupTion <anerruption@disroot.org>

* Zig 0.12.0 and more! (#599)

* less alloc, update migrator, get DesktopNames from .desktop

* small cleanup

* Update zigini to improve compatibility with old config

* Code improvements

* Update to zig version 0.12.0

* Some fixes

* tiny changes

* remove useless comment

* migrator changes, and small things

* set XDG env vars differently

* free memory on error when appending environments

* Fix out of bounds issue when using the Delete key

Signed-off-by: AnErrupTion <anerruption@disroot.org>

* Update zig-ini to fix configuration issue (#603)

* Mention display-manager-init for Gentoo/OpenRC in readme.md

Signed-off-by: AnErrupTion <anerruption@disroot.org>

* Tidy up readme.md

Signed-off-by: AnErrupTion <anerruption@disroot.org>

* Fix authentication in a few edge cases (#604)

* fix loginConv and auth

* fix potential mem leak with configs

* BIG changes

---------

Signed-off-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: アシュ <120780645+Kawaii-Ash@users.noreply.github.com>
3 weeks ago

7
.gitignore vendored

@ -1,3 +1,4 @@
bin
obj
valgrind.log
.idea/
zig-cache/
zig-out/
valgrind.log

12
.gitmodules vendored

@ -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)

@ -5,28 +5,39 @@
Ly is a lightweight TUI (ncurses-like) display manager for Linux and BSD.
## Dependencies
- a C99 compiler (tested with tcc and gcc)
- a C standard library
- GNU make
- pam
- xcb
- xorg
- xorg-xauth
- mcookie
- tput
- shutdown
On Debian-based distros running `apt install build-essential libpam0g-dev libxcb-xkb-dev` as root should install all the dependencies for you.
For Fedora try running `dnf install make automake gcc gcc-c++ kernel-devel pam-devel libxcb-devel`
- Compile-time:
- zig 0.12.0
- a C standard library
- pam
- xcb
- Runtime (with default config):
- xorg
- xorg-xauth
- mcookie
- tput
- shutdown
## Support
The following desktop environments were tested with success
### Debian
```
# apt install build-essential libpam0g-dev libxcb-xkb-dev
```
### Fedora
**Warning**: You may encounter issues with SELinux on Fedora.
It is recommended to add a rule for Ly as it currently does not ship one.
```
# dnf install kernel-devel pam-devel libxcb-devel
```
## Support
The following desktop environments were tested with success:
- awesome
- bspwm
- budgie
- cinnamon
- deepin
- dwl
- dwm
- enlightenment
- gnome
@ -57,7 +68,7 @@ changing the source code won't be necessary :)
## Cloning and Compiling
Clone the repository
```
$ git clone --recurse-submodules https://github.com/fairyglade/ly
$ git clone https://github.com/fairyglade/ly
```
Change the directory to ly
@ -67,18 +78,18 @@ $ cd ly
Compile
```
$ make
$ zig build
```
Test in the configured tty (tty2 by default)
or a terminal emulator (but desktop environments won't start)
```
# make run
# zig build run
```
Install Ly and the provided systemd service file
```
# make install installsystemd
# zig build installsystemd
```
Enable the service
@ -93,12 +104,13 @@ disable getty on Ly's tty to prevent "login" from spawning on top of it
```
### OpenRC
**NOTE**: On Gentoo, Ly will disable the `display-manager-init` service in order to run.
Clone, compile and test.
Install Ly and the provided OpenRC service
```
# make install installopenrc
# zig build installopenrc
```
Enable the service
@ -108,20 +120,19 @@ Enable the service
You can edit which tty Ly will start on by editing the `tty` option in the configuration file.
If you choose a tty that already has a login/getty running (has a basic login prompt), then you have to disable the getty so it doesn't respawn on top of ly
If you choose a tty that already has a login/getty running (has a basic login prompt),
then you have to disable getty, so it doesn't respawn on top of ly
```
# rc-update del agetty.tty2
```
### runit
```
$ make
# make install installrunit
# zig build installrunit
# ln -s /etc/sv/ly /var/service/
```
By default, ly will run on tty2. To change the tty it must be set in `/etc/ly/config.ini`
By default, ly will run on tty2. To change the tty it must be set in `/etc/ly/config.ini`
You should as well disable your existing display manager service if needed, e.g.:
@ -129,12 +140,28 @@ You should as well disable your existing display manager service if needed, e.g.
# rm /var/service/lxdm
```
The agetty service for the tty console where you are running ly should be disabled. For instance, if you are running ly on tty2 (that's the default, check your `/etc/ly/config.ini`) you should disable the agetty-tty2 service like this:
The agetty service for the tty console where you are running ly should be disabled.
For instance, if you are running ly on tty2 (that's the default, check your `/etc/ly/config.ini`)
you should disable the agetty-tty2 service like this:
```
# rm /var/service/agetty-tty2
```
### Updating
You can also install Ly without copying the system service and the configuration file. That's
called *updating*. To update, simply run:
```
# zig build installnoconf
```
If you want to also copy the default config file (but still not the system service), run:
```
# zig build installexe
```
## Arch Linux Installation
You can install ly from the [`[extra]` repos](https://archlinux.org/packages/extra/x86_64/ly/):
```
@ -175,3 +202,6 @@ disable the main box borders with `hide_borders = true`.
## Additional Information
The name "Ly" is a tribute to the fairy from the game Rayman.
Ly was tested by oxodao, who is some seriously awesome dude.
## Gentoo (OpenRC) installation tip
To avoid a console spawning on top on Ly, comment out the appropriate line from /etc/inittab (default is 2).

@ -1,24 +1,25 @@
# Animation enabled/disabled
#animate = false
# The active animation
# 0 -> PSX DOOM fire (default)
# 1 -> CMatrix
#animation = 0
# none -> Nothing (default)
# doom -> PSX DOOM fire
# matrix -> CMatrix
animation = none
# format string for clock in top right corner (see strftime specification)
#clock = %c
# Format string for clock in top right corner (see strftime specification). Example: %c
clock = null
# enable/disable big clock
#bigclock = true
# Enable/disable big clock
bigclock = false
# The character used to mask the password
#asterisk = *
asterisk = *
# Erase password input on failure
#blank_password = false
clear_password = false
# Enable vi keybindings
vi_mode = false
#The `fg` and `bg` color settings take a digit 0-8 corresponding to:
# The `fg` and `bg` color settings take a digit 0-8 corresponding to:
#define TB_DEFAULT 0x00
#define TB_BLACK 0x01
#define TB_RED 0x02
@ -29,118 +30,124 @@
#define TB_CYAN 0x07
#define TB_WHITE 0x08
#
# Setting both to zero makes `bg` black and `fg` white. To set the actual color palette you are encouraged to use another tool
# such as [mkinitcpio-colors](https://github.com/evanpurkhiser/mkinitcpio-colors). Note that the color palette defined with
# `mkinitcpio-colors` takes 16 colors (0-15), only values 0-8 are valid for `ly` config and these values do not correspond
# exactly. For instance, in defining palettes with `mkinitcpio-colors` the order is black, dark red, dark green, brown, dark
# blue, dark purple, dark cyan, light gray, dark gray, bright red, bright green, yellow, bright blue, bright purple, bright
# cyan, and white, indexed in that order 0 through 15. For example, the color defined for white (indexed at 15 in the mkinitcpio
# Setting both to zero makes `bg` black and `fg` white. To set the actual color palette you are encouraged to use another tool
# such as [mkinitcpio-colors](https://github.com/evanpurkhiser/mkinitcpio-colors). Note that the color palette defined with
# `mkinitcpio-colors` takes 16 colors (0-15), only values 0-8 are valid for `ly` config and these values do not correspond
# exactly. For instance, in defining palettes with `mkinitcpio-colors` the order is black, dark red, dark green, brown, dark
# blue, dark purple, dark cyan, light gray, dark gray, bright red, bright green, yellow, bright blue, bright purple, bright
# cyan, and white, indexed in that order 0 through 15. For example, the color defined for white (indexed at 15 in the mkinitcpio
# config) will be used by `ly` for `fg = 8`.
# Background color id
#bg = 0
bg = 0
# Foreground color id
#fg = 9
fg = 8
# Border color
border_fg = 8
# Blank main box background
# Setting to false will make it transparent
#blank_box = true
blank_box = true
# Remove main box borders
#hide_borders = false
hide_borders = false
# Main box margins
#margin_box_h = 2
#margin_box_v = 1
margin_box_h = 2
margin_box_v = 1
# Input boxes length
#input_len = 34
input_len = 34
# Max input sizes
#max_desktop_len = 100
#max_login_len = 255
#max_password_len = 255
max_desktop_len = 100
max_login_len = 255
max_password_len = 255
# Input box active by default on startup
#default_input = 2
# Available inputs: session, login, password
default_input = login
# Load the saved desktop and username
#load = true
load = true
# Save the current desktop and login as defaults
#save = true
save = true
# Deprecated - Will be removed in a future version
# New save files are now loaded from the same directory as the config
# Currently used to migrate old save files to the new version
# File in which to save and load the default desktop and login
#save_file = /etc/ly/save
save_file = /etc/ly/save
# Remove power management command hints
#hide_key_hints = false
hide_key_hints = false
# Specifies the key used for shutdown (F1-F12)
shutdown_key = F1
# Specifies the key used for shutdown
#shutdown_key = F1
# Specifies the key used for restart (F1-F12)
restart_key = F2
# Specifies the key used for restart
#restart_key = F2
# Specifies the key used for sleep (F1-F12)
sleep_key = F3
# Command executed when pressing shutdown_key
#shutdown_cmd = /sbin/shutdown -a now
shutdown_cmd = /sbin/shutdown -a now
# Command executed when pressing restart_key
#restart_cmd = /sbin/shutdown -r now
restart_cmd = /sbin/shutdown -r now
# Command executed when pressing sleep key (can be null)
sleep_cmd = null
# Active language
# Available languages are found in /etc/ly/lang/
#lang = en
lang = en
# tty in use
#tty = 2
# TTY in use
tty = 2
# Console path
#console_dev = /dev/console
# Default path
#path = /sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin
console_dev = /dev/console
# Default path. If null, ly doesn't set a path.
path = /sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin
# Event timeout in milliseconds
#min_refresh_delta = 5
min_refresh_delta = 5
# Service name (set to ly to use the provided pam config file)
#service_name = ly
service_name = ly
# Terminal reset command (tput is faster)
#term_reset_cmd = /usr/bin/tput reset
term_reset_cmd = /usr/bin/tput reset
# Cookie generator
#mcookie_cmd = /usr/bin/mcookie
# Terminal restore cursor command
term_restore_cursor_cmd = /usr/bin/tput cnorm
# Cookie generator
mcookie_cmd = /usr/bin/mcookie
# Wayland setup command
#wayland_cmd = /etc/ly/wsetup.sh
# Add wayland specifier to session names
#wayland_specifier = false
wayland_cmd = /etc/ly/wsetup.sh
# Wayland desktop environments
#waylandsessions = /usr/share/wayland-sessions
waylandsessions = /usr/share/wayland-sessions
# xinitrc
#xinitrc = ~/.xinitrc
# xinitrc (hidden if null)
xinitrc = ~/.xinitrc
# Xorg server command
#x_cmd = /usr/bin/X
x_cmd = /usr/bin/X
# Xorg setup command
#x_cmd_setup = /etc/ly/xsetup.sh
x_cmd_setup = /etc/ly/xsetup.sh
# Xorg xauthority edition tool
#xauth_cmd = /usr/bin/xauth
xauth_cmd = /usr/bin/xauth
# Xorg desktop environments
#xsessions = /usr/share/xsessions
xsessions = /usr/share/xsessions

@ -2,7 +2,7 @@ capslock = capslock
err_alloc = alokace paměti selhala
err_bounds = index je mimo hranice pole
err_chdir = nelze otevřít domovský adresář
err_console_dev = chyba při přístupi do konzole
err_console_dev = chyba při přístupu do konzole
err_dgn_oob = zpráva protokolu
err_domain = neplatná doména
err_hostname = nelze získat název hostitele

@ -29,17 +29,22 @@ err_perm_dir = failed to change current directory
err_perm_group = failed to downgrade group permissions
err_perm_user = failed to downgrade user permissions
err_pwnam = failed to get user info
err_unknown = an unknown error occurred
err_user_gid = failed to set user GID
err_user_init = failed to initialize user
err_user_uid = failed to set user UID
err_xsessions_dir = failed to find sessions folder
err_xsessions_open = failed to open sessions folder
insert = insert
login = login
logout = logged out
normal = normal
numlock = numlock
password = password
restart = reboot
shell = shell
shutdown = shutdown
sleep = sleep
wayland = wayland
xinitrc = xinitrc
x11 = x11

@ -25,7 +25,7 @@ err_pam_session = greska sesije
err_pam_sys = greska sistema
err_pam_user_unknown = nepoznat korisnik
err_path = neuspjelo postavljanje path-a
err_perm_dir = neuspjelo mijenjanje foldera
err_perm_dir = neuspjelo mijenjanje foldera
err_perm_group = neuspjesno snizavanje dozvola grupe
err_perm_user = neuspijesno snizavanje dozvola korisnika
err_pwnam = neuspijesno skupljanje informacija o korisniku
@ -33,7 +33,7 @@ err_user_gid = neuspijesno postavljanje korisničkog GID-a
err_user_init = neuspijensa inicijalizacija korisnika
err_user_uid = neuspijesno postavljanje UID-a korisnika
err_xsessions_dir = neuspijesno pronalazenje foldera sesija
err_xsessions_open = neuspijesno otvaranje foldera sesija
err_xsessions_open = neuspijesno otvaranje foldera sesija
login = korisnik
logout = izlogovan
numlock = numlock

@ -2,6 +2,7 @@
Description=TUI display manager
After=systemd-user-sessions.service plymouth-quit-wait.service
After=getty@tty2.service
Conflicts=getty@tty2.service
[Service]
Type=idle

@ -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, &params, 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, &params, .{});
_ = 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…
Cancel
Save