116 Commits

Author SHA1 Message Date
Muki Kiboigo
ab462e91c2 authenticate and run on current virtual terminal 2024-06-19 16:19:35 -07:00
Muki Kiboigo
6d3808553e templated systemd file 2024-06-19 16:17:42 -07:00
Muki Kiboigo
3d8d8d67df Redirect stderr to systemd journal in service (#621) 2024-06-19 21:49:30 +02:00
ShiningLea
7506d6a7d5 Clarify how to update + add DWL (closes #551, #547) 2024-05-09 21:09:10 +02:00
ShiningLea
a7615a33e0 Clarify status of dependencies 2024-05-09 20:25:34 +02:00
Jan Černý
0586f3424a Update cs.ini (#559) 2024-05-09 20:04:35 +02:00
Motodavide
b457b454ae Added Gentoo tip to readme.md (#591) 2024-05-09 20:03:57 +02:00
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>
2024-05-09 15:30:12 +02:00
Bruno Rodríguez
4ee2b3ecc7 Backup config if exists when installing (#536)
When installing, checks if `${DESTDIR}/etc/ly/config.ini` exists and
if it does, copies it to `${DESTDIR}/etc/ly/config.ini.old`
2023-09-10 15:30:47 +02:00
lolicon0930
2ca870cfc5 Check if the clock is on. (closes #521) (#522)
If the clock is on, then change the numlock and capslock state positions.
2023-06-25 21:41:38 +02:00
lolicon0930
42bf929756 Add option to change shutdown/reboot keys (#487)
Include options in the configuration to change which function keys to use for shutdown and reboot.
Fix config.map_len size in src/config.c.
Add missing defaults in config_defaults() in src/config.c.
2023-06-20 13:22:38 +02:00
Luna Jernberg
0edb0012ab Update sv.ini (#519)
* Update sv.ini

Better Swedish word for the context

* Update sv.ini

Fix
2023-06-18 23:08:18 +02:00
AnErrupTion
1c2be475ad Exit when asking for version 2023-06-15 09:30:09 +02:00
AnErrupTion
d775efb414 Rename GIT_VERSION_STRING to LY_VERSION, don't allow shutdown and reboot at same time 2023-06-15 09:26:34 +02:00
AnErrupTion
57a8eecb1a Add labwc to supported DEs list 2023-06-15 09:21:26 +02:00
AnErrupTion
cbd82146e2 Fix code syntax issues 2023-06-15 09:00:31 +02:00
Colin
1124c126f9 Clocks (#461)
* Added a big clock

* fixed clock timing when animation is turned off

* fix memory leak and segfault

* rename clock to bigclock

* Added formattable clock

* fix clock position on first draw

don't rely on box_x and box_y to position the clock, because it might not be initialized in the first frame.

* fix memory leak
2023-06-15 08:57:37 +02:00
AnErrupTion
f9848f648b Rename "captation" to "lecture" in res/lang/fr.ini
Closes #398
2023-06-15 00:32:33 +02:00
Albert Akmukhametov
93cb8394d2 Added location of Xresources for XDG Base Dir Spec (#251) 2023-06-14 23:33:16 +02:00
Ranny Archer
faa76def08 Ctrl+{hjkl} Moving cursor (but not in text) (#492) 2023-06-14 23:24:00 +02:00
AnErrupTion
0b87d6c030 Remove broken workaround for Sway in res/wsetup.sh 2023-06-14 23:19:21 +02:00
Christian Heusel
627ebe3326 update the archlinux installation instructions (#505)
Signed-off-by: Christian Heusel <christian@heusel.eu>
2023-06-14 21:42:26 +02:00
Bruno Rodríguez
a8ea5d3e99 Tidy up runit service scripts (#479)
* Fix the script installation path (install everything only on /etc/sv/ly)

* Update the runit scripts to get the TTY value from the /etc/ly/config.ini file

* Update readme.md
2023-06-14 21:42:01 +02:00
Luna Jernberg
8c4ba3bd40 Add Swedish Translation (#495)
* Add Swedish Translation

Create sv.ini for Swedish translation

* Update sv.ini

Updated to some feedback from Vorpal in #archlinux-offtopic @ Libera
2023-06-14 21:36:17 +02:00
Vladyslav Prudius
c03ec1d15b Ukrainian and Russian localizations fixing (#503)
* Fix Ukrainian translation

* Fix Russian translation
2023-06-14 21:35:36 +02:00
Joseth Ariel B
c64f806a68 fix typos in spanish translation (#477) 2023-01-12 21:48:57 +01:00
Alfred Roos
24f017e09c Fixed wrong argument in inputs.s input_desktop (#469) 2022-12-08 23:29:23 +01:00
AnErrupTion
33662480e9 Potential fix for Sway (closes #433) 2022-12-07 12:50:48 +01:00
AnErrupTion
a7b01a721b Increase length for UID to 20 (closes #444) 2022-12-07 12:35:35 +01:00
Simon Struck
77eb456410 Re-add missing environment variables (#446) 2022-12-07 12:33:12 +01:00
AnErrupTion
19153760d3 Fix segfault (closes #434, #435) 2022-12-07 12:20:23 +01:00
Jordi Altayó
2ffb86213b Fedora dependencies (#436)
* Fedora dependencies

* Fedora dependencies 

Comments from @AnErrupTion
2022-09-07 13:45:21 +02:00
Daniel Haarhoff
5db09ce104 Make it easy to install ly on runit systems e.g. void (#431)
* Make it easy to install ly on runit systems e.g. void

* Tidy up runit section in readme

Co-authored-by: Daniel Haarhoff <daniel@rknt.de>
2022-08-28 12:53:58 +02:00
ClientCrash
99edd83429 add German lang translation (#355)
* added german translation

* Replaced umlauts in german translations

Replaced umlauts in german translations with alternative spelling method to avoid any encoding problems etc.

* updated translation

Co-authored-by: Leif G <46281254+4ctiv@users.noreply.github.com>
2022-08-22 17:58:12 +02:00
Cavernosa
59aae77f49 Fixes for the XDG Base Dirs implementation (#330) (#419)
Fix XDG Base Dirs implementation
2022-08-14 20:08:08 +02:00
Cavernosa
0cefb3da8e OpenRC service and support for more inits (#368)
Add OpenRC service and support for other init services

Co-authored-by: Jonathan <78560204+wncry@users.noreply.github.com>
Co-authored-by: MadcowOG <null>
Co-authored-by: MadcowOG <88654251+MadcowOG@users.noreply.github.com>
2022-08-13 10:44:53 +02:00
ShiningLea
309b97df8d Improve Arch instructions 2022-08-12 23:35:31 +02:00
stevensonmt
253ee005ad Improve config documentation (#414)
* Improve config documentation

update readme to provide some direction on customization options.

* move color config info to config.ini

Info on configuring color palette moving from README to config.ini comments

* Add comments for color config
2022-08-12 22:02:22 +02:00
ShiningLea
09ee6293bd Merge dev into master (closes #182) (#425)
Merge dev into master branch
Co-authored-by: AnErrupTion <anerruption@disroot.org>
2022-08-12 21:27:06 +02:00
ShiningLea
86d035346e Update README to use new organization name 2022-08-12 18:51:58 +02:00
ShiningLea
8c536d09ed Merge pull request #394 from hugok79/patch-1
Fix pt and pt_BR translation translation
2022-08-12 18:32:27 +02:00
ShiningLea
13020184cf Merge pull request #362 from KR1470R/ukrainian_lang
Add Ukrainian Translation
2022-08-12 15:11:02 +02:00
ShiningLea
07ca027ac6 Merge pull request #353 from jakubhyza/master
Czech lang translation
2022-08-12 15:10:10 +02:00
ShiningLea
0a798831fe Merge pull request #399 from Cavernosa/patch-1
Organized config.ini and readme.md
2022-07-24 15:25:57 +02:00
Cavernosa
4e24154cac Add awesome wm and some corrections 2022-07-16 14:53:34 +00:00
Cavernosa
b5d3ef0a70 Merge branch 'fairyglade:master' into patch-1 2022-07-16 14:28:12 +00:00
ShiningLea
3556e39374 Merge pull request #400 from KR1470R/aur
added link for aur package in readme
2022-06-09 23:28:08 +02:00
KR1470R
8d6218ee62 added link 2022-06-09 12:54:12 +03:00
Cavernosa
4208ba0e0f Organized config.ini
Grouped related things together and changed some comments
2022-06-08 04:02:31 +00:00
Hugo Carvalho
b051607c65 Fix pt and pt_BR translation translation
The "pt" translation was in brazilian portuguese as such, I correctly translated the translation to portuguese(pt) and renamed the other one to pt_BR.
2022-06-03 14:45:39 +01:00
KRIPTOR
0599af2cce fix ukrainian_lang 2 2022-06-03 12:37:11 +03:00
Jakub Hýža
f6de0eb086 Required changes - shell 2022-06-03 10:51:49 +02:00
Jakub Hýža
3c139d3eaf Required changes 2022-06-03 10:44:35 +02:00
AnErrupTion
354b1493c7 Merge remote-tracking branch 'origin/master' 2022-06-03 10:21:58 +02:00
AnErrupTion
7d5eb449c3 Add missing step in README 2022-06-03 10:21:51 +02:00
ShiningLea
9364ffcea9 Merge pull request #330 from UtkarshVerma/xdg
Use XDG_RUNTIME_DIR for storing Xauthority
2022-06-03 10:16:37 +02:00
AnErrupTion
e158bb5e9d Remove semicolons in language files 2022-06-03 10:13:54 +02:00
ShiningLea
2e1a57648b Merge pull request #393 from UtkarshVerma/xinitrc
Make `xinitrc` path configurable
2022-06-03 10:04:35 +02:00
ShiningLea
4b5374c13f Merge pull request #379 from KR1470R/aur
add instruction of aur package installation to readme
2022-06-03 10:03:24 +02:00
AnErrupTion
5945982d99 Merge remote-tracking branch 'origin/master' 2022-06-03 10:02:06 +02:00
AnErrupTion
723a008adf Helpful message when doing --help (closes #391 #390 #346) 2022-06-03 09:59:33 +02:00
Utkarsh Verma
802ad7b204 Merge branch 'master' of github.com:fairyglade/ly into xinitrc 2022-06-03 07:43:36 +05:30
Utkarsh Verma
079f68696b Make xinitrc path configurable 2022-06-03 07:43:19 +05:30
Turion64
73e8193b2e Merge pull request #388 from TheArctesian/patch-1
Update readme.md
2022-05-31 22:04:04 +02:00
The Arctesian
1fad38aad9 Update readme.md 2022-05-30 21:27:49 +08:00
KRIPTOR
9e33f90d24 Update readme.md 2022-05-25 19:39:15 +03:00
Utkarsh Verma
38aacc6b1c Implement fallback logic for XAUTHORITY 2022-05-24 07:41:35 +05:30
KRIPTOR
83c17b7b78 Update readme.md 2022-05-23 22:20:45 +03:00
KR1470R
f485840d89 add aur support 2022-05-22 22:26:32 +03:00
Turion64
767120c701 Add spectrwm 2022-05-18 06:49:14 +02:00
Turion64
cc5a50ff32 Merge pull request #367 from donno2048/patch-1
Use `uint16_t` on `put_cell` iterations
2022-05-17 06:40:14 +02:00
Elisha Hollander
77d186b802 use uint16_t on put_cell iterations 2022-05-17 01:36:36 +03:00
Turion64
8eb487ae9e Merge pull request #354 from SapphireAmoeba5/master
Make TAB key loop through U.I elements infinitely
2022-05-16 23:24:19 +02:00
Turion64
dec2ef14e0 Merge pull request #352 from kefoster951/master
Changed active_input selection to be based on default config
2022-05-16 23:23:26 +02:00
KR1470R
63aeec1869 fix ukrainian_lang 2022-05-14 17:47:50 +03:00
KR1470R
9da5a5e136 add ukrainian lang 2022-05-14 16:09:31 +03:00
SapphireAmoeba5
ffa8fbe77b Replaced 0 with SESSION_SWITCH 2022-05-13 19:03:01 -03:00
SapphireAmoeba5
ac2b255021 Make TAB key loop through U.I elements 2022-05-13 13:23:14 -03:00
kefoster951
bd04d69b4d Changed default input to login field 2022-05-13 09:12:24 -04:00
kefoster951
a078a07ee7 Update main.c 2022-05-13 09:11:00 -04:00
Jakub Hýža
c84de06b91 Czech lang 2022-05-13 07:11:32 +02:00
Turion64
78e2fd1a21 Merge pull request #297 from gerardet46/catalan-translation
Catalan translation
2022-05-13 06:42:03 +02:00
Qontinuum
da5b08abf2 Check if lang is NULL and use shorter check (#303) 2022-05-12 22:37:43 +02:00
kefoster951
559f2f7370 Update main.c
changed how the curser placement works to be based on if there is a saved username or not
2022-05-12 11:05:23 -04:00
gerardet46
d29d65dee5 Remove semicolons and fixed a typo 2022-05-04 15:17:44 +02:00
Turion64
1890da4799 Merge pull request #312 from mr-durmus/master
Add Turkish language
2022-04-26 15:28:57 +02:00
Rasit
6ea31612cd Semicolons and parentheses removed. Missing translation translated. 2022-04-26 02:43:03 +03:00
Rasit
443616e4ed Merge branch 'fairyglade:master' into master 2022-04-26 02:39:06 +03:00
jakobrs
c0366fe805 Use tb_poll_event instead of tb_peek_event (#320)
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.
2022-04-25 20:52:22 +02:00
Baptiste Daroussin
7ed6631197 FreeBSD deserves nice drawing as well (#340) 2022-04-24 12:40:08 +02:00
yobleck
fa978c8add add qtile to list of supported de/wm (#286)
caveat: only tested on one machine
2022-04-24 11:55:16 +02:00
Stale
6245639daf Matrix scrolling text animation (#283) 2022-04-24 11:54:50 +02:00
Raen
9fd7779972 Allow en.ini load and add config to hide f1 commands (#281) 2022-04-23 19:17:56 +02:00
plasmoduck
2ca457aada Update readme.md (#252)
add dwm to list. Been using with dwm on multiple machines inc FreeBSD
2022-04-23 19:04:56 +02:00
Andrew Vos
9c1e218900 Add bspwm to list of tested desktop environments (#230) 2022-04-23 19:02:29 +02:00
Turion64
df6d906f6d Merge pull request #245 from kpetrilli/it_language
Add IT translation
2022-04-21 21:25:03 +02:00
Turion64
c762cdc20c Merge pull request #321 from jorrmungandrr/master
Added serbian translation
2022-04-21 21:17:39 +02:00
Turion64
d7930053c6 Merge pull request #325 from USBashka/master
Fix Russian translation
2022-04-21 21:13:26 +02:00
Turion64
b6681e78a9 Merge pull request #327 from RDKRACZ/Add-Polish
Create pl.ini
2022-04-21 21:12:03 +02:00
AnErrupTion
25034d1536 Set XDG_SESSION_ID to fix a few bugs 2022-04-19 21:46:40 +02:00
AnErrupTion
27c5673116 Set XDG_SESSION_TYPE earlier to fix some bugs 2022-04-19 21:36:58 +02:00
Utkarsh Verma
908ebf8964 Use XDG_RUNTIME_DIR for storing Xauthority 2022-01-02 15:55:36 +05:30
K0RR
dc0221259f Update pl.ini
Missed these two
2021-12-26 12:52:49 +01:00
K0RR
79a4ff470d Create pl.ini 2021-12-26 12:50:48 +01:00
USBashka
6023565814 Fix ru translation
Replaced Е with Ё where needed
2021-12-16 23:11:36 +04:00
nullgemm
609b3f9ddc fix readme 2021-12-10 12:58:14 +01:00
nullgemm
7597bcdfc2 update readme 2021-12-10 12:57:05 +01:00
nullgemm
ccc39d2bed update submodules 2021-12-10 12:55:11 +01:00
nullgemm
a20d5195a6 remove git submodules helper 2021-12-10 12:49:59 +01:00
nullgemm
b3d5756a0a remove git remote helper 2021-12-09 17:58:23 +01:00
Јован Ђокић-Шумарац
fad4e4dd84 Added serbian translation 2021-11-27 18:07:56 +01:00
00101111
9d4aea06b0 Add Turkish language 2021-10-23 19:42:03 +03:00
nullgemm
4094d2182a add missing include 2021-10-07 15:58:27 +02:00
nullgemm
b448fc97b2 remove the ctypes submodule 2021-10-07 15:54:13 +02:00
GerrySoft
1ca229b5c8 translation for catalan 2021-07-27 18:57:18 +02:00
kpetrilli
c5012ef11b Add IT
Added translation file for Italian language
2020-10-26 16:51:05 +01:00
87 changed files with 7376 additions and 3181 deletions

15
.gitea
View File

@@ -1,15 +0,0 @@
[submodule "sub/argoat"]
path = sub/argoat
url = https://git.nullgemm.fr/nullgemm/argoat.git
[submodule "sub/configator"]
path = sub/configator
url = https://git.nullgemm.fr/nullgemm/configator.git
[submodule "sub/ctypes"]
path = sub/ctypes
url = https://git.nullgemm.fr/nullgemm/ctypes.git
[submodule "sub/dragonfail"]
path = sub/dragonfail
url = https://git.nullgemm.fr/nullgemm/dragonfail.git
[submodule "sub/termbox_next"]
path = sub/termbox_next
url = https://git.nullgemm.fr/nullgemm/termbox_next.git

15
.github
View File

@@ -1,15 +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/ctypes"]
path = sub/ctypes
url = https://github.com/nullgemm/ctypes.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

8
.gitignore vendored
View File

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

221
build.zig Normal file
View File

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

15
build.zig.zon Normal file
View File

@@ -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 = .{""},
}

47
changelog.md Normal file
View File

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

7
dep/termbox_next/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
bin
obj
src/demo/*.o
src/demo/keyboard
src/demo/output
src/demo/paint
src/demo/truecolor

19
dep/termbox_next/license Normal file
View File

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

View File

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

View File

@@ -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;
}

View File

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

View File

@@ -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;
}

View File

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

View File

@@ -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;
}

View File

@@ -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;
}

View File

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

View File

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

View File

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

View File

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

412
dep/termbox_next/src/term.c Normal file
View File

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

View File

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

View File

@@ -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;
}
}
}

View File

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

106
dep/termbox_next/src/utf8.c Normal file
View File

@@ -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;
}

View File

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

View File

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

127
makefile
View File

@@ -1,127 +0,0 @@
NAME = ly
CC = gcc
FLAGS = -std=c99 -pedantic -g
FLAGS+= -Wall -Wextra -Werror=vla -Wno-unused-parameter
#FLAGS+= -DDEBUG
FLAGS+= -DGIT_VERSION_STRING=\"$(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"
@install -dZ ${DESTDIR}/etc/ly
@install -DZ $(BIND)/$(NAME) -t ${DESTDIR}/usr/bin
@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)/ly.service -m 644 -t ${DESTDIR}/usr/lib/systemd/system
@install -DZ $(RESD)/pam.d/ly -m 644 -t ${DESTDIR}/etc/pam.d
installnoconf: $(BIND)/$(NAME)
@echo "installing 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)/ly.service -m 644 -t ${DESTDIR}/usr/lib/systemd/system
@install -DZ $(RESD)/pam.d/ly -m 644 -t ${DESTDIR}/etc/pam.d
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
clean:
@echo "cleaning"
@rm -rf $(BIND) $(OBJD) valgrind.log
@(cd $(SUBD)/termbox_next && $(MAKE) clean)
remotes:
@echo "registering remotes"
@git remote add github git@github.com:nullgemm/$(NAME).git
@git remote add gitea ssh://git@git.nullgem.fr:2999/nullgemm/$(NAME).git
github:
@echo "sourcing submodules from https://github.com"
@cp .github .gitmodules
@git submodule sync
@git submodule update --init --remote
@cd $(SUBD)/argoat && make github
@git submodule update --init --recursive --remote
gitea:
@echo "sourcing submodules from personal server"
@cp .gitea .gitmodules
@git submodule sync
@git submodule update --init --remote
@cd $(SUBD)/argoat && make gitea
@git submodule update --init --recursive --remote

150
readme.md
View File

@@ -1,39 +1,60 @@
# Ly - a TUI display manager
![Ly screenshot](https://user-images.githubusercontent.com/5473047/88958888-65efbf80-d2a1-11ea-8ae5-3f263bce9cce.png "Ly screenshot")
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
- Compile-time:
- zig 0.12.0
- a C standard library
- pam
- xcb
- Runtime (with default config):
- 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.
### Debian
```
# apt install build-essential libpam0g-dev libxcb-xkb-dev
```
### Fedora
**Warning**: You may encounter issues with SELinux on Fedora.
It is recommended to add a rule for Ly as it currently does not ship one.
```
# dnf install kernel-devel pam-devel libxcb-devel
```
## Support
The following desktop environments were tested with success
The following desktop environments were tested with success:
- awesome
- bspwm
- budgie
- cinnamon
- deepin
- dwl
- dwm
- enlightenment
- gnome
- i3
- kde
- labwc
- lxde
- lxqt
- mate
- sway
- xfce
- pantheon
- maxx
- pantheon
- qtile
- spectrwm
- sway
- windowmaker
- xfce
- xmonad
Ly should work with any X desktop environment, and provides
basic wayland support (sway works very well, for example).
@@ -47,39 +68,107 @@ changing the source code won't be necessary :)
## Cloning and Compiling
Clone the repository
```
git clone https://github.com/nullgemm/ly.git
$ git clone https://github.com/fairyglade/ly
```
Fetch submodules
Change the directory to ly
```
make github
$ cd ly
```
Compile
```
make
$ zig build
```
Test in the configured tty (tty2 by default)
or a terminal emulator (but desktop environments won't start)
```
sudo make run
# zig build run
```
Install Ly and the provided systemd service file
```
sudo make install
# zig build installsystemd
```
Enable the service
Enable the service (this will spawn on tty2 and tty9)
```
sudo systemctl enable ly.service
# systemctl enable ly@tty2.service
# systemctl enable ly@tty9.service
```
If you need to switch between ttys after Ly's start you also have to
disable getty on Ly's tty to prevent "login" from spawning on top of it
```
sudo systemctl disable getty@tty2.service
# systemctl disable getty@tty2.service
```
If you have multiple ttys setup with systemd, the tty option will be used as your default tty.
### 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
```
# zig build installopenrc
```
Enable the service
```
# rc-update add ly
```
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 getty, so it doesn't respawn on top of ly
```
# rc-update del agetty.tty2
```
### runit
```
# 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`
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:
```
# 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/):
```
$ sudo pacman -S ly
```
## Configuration
@@ -94,12 +183,10 @@ while on the desktop field (above the login field).
## .xinitrc
If your .xinitrc doesn't work make sure it is executable and includes a shebang.
This file is supposed to be a shell script! Quoting from xinit's man page:
```
If no specific client program is given on the command line, xinit will look for
a file in the user's home directory called .xinitrc to run as a shell script to
start up client programs.
```
On ArchLinux, the example .xinitrc (/etc/X11/xinit/xinitrc) starts like this:
> If no specific client program is given on the command line, xinit will look for a file in the user's home directory called .xinitrc to run as a shell script to start up client programs.
On Arch Linux, the example .xinitrc (/etc/X11/xinit/xinitrc) starts like this:
```
#!/bin/sh
```
@@ -118,3 +205,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).

View File

@@ -1,104 +1,153 @@
# animation enabled
#animate = false
#animate = true
# The active animation
# none -> Nothing (default)
# doom -> PSX DOOM fire
# matrix -> CMatrix
animation = none
# the active animation (only animation '0' available for now)
#animation = 0
# Format string for clock in top right corner (see strftime specification). Example: %c
clock = null
# the char used to mask the password
#asterisk = *
#asterisk = o
# Enable/disable big clock
bigclock = false
# background color id
#bg = 0
# The character used to mask the password
asterisk = *
# blank main box
#blank_box = true
# Erase password input on failure
clear_password = false
# erase password input on failure
#blank_password = false
#blank_password = true
# Enable vi keybindings
vi_mode = false
# console path
#console_dev = /dev/console
# 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
#define TB_GREEN 0x03
#define TB_YELLOW 0x04
#define TB_BLUE 0x05
#define TB_MAGENTA 0x06
#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
# config) will be used by `ly` for `fg = 8`.
# input active by default on startup
#default_input = 2
# Background color id
bg = 0
# foreground color id
#fg = 9
# Foreground color id
fg = 8
# remove main box borders
#hide_borders = false
#hide_borders = true
# Border color
border_fg = 8
# number of visible chars on an input
#input_len = 34
# Blank main box background
# Setting to false will make it transparent
blank_box = true
# active language
#lang = en
#lang = fr
# Remove main box borders
hide_borders = false
# load the saved desktop and login
#load = true
# Main box margins
margin_box_h = 2
margin_box_v = 1
# main box margins
#margin_box_h = 2
#margin_box_v = 1
# Input boxes length
input_len = 34
# total input sizes
#max_desktop_len = 100
#max_login_len = 255
#max_password_len = 255
# Max input sizes
max_desktop_len = 100
max_login_len = 255
max_password_len = 255
# cookie generator
#mcookie_cmd = /usr/bin/mcookie
# Input box active by default on startup
# Available inputs: session, login, password
default_input = login
# event timeout in milliseconds
#min_refresh_delta = 5
# Load the saved desktop and username
load = true
# default path
#path = /sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin
# Save the current desktop and login as defaults
save = true
# command executed when pressing F2
#restart_cmd = /sbin/shutdown -r now
# 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 the current desktop and login as defaults
#save = true
# Remove power management command hints
hide_key_hints = false
# file in which to save and load the default desktop and login
#save_file = /etc/ly/save
# Specifies the key used for shutdown (F1-F12)
shutdown_key = F1
# service name (set to ly to use the provided pam config file)
#service_name = ly
# Specifies the key used for restart (F1-F12)
restart_key = F2
# command executed when pressing F1
#shutdown_cmd = /sbin/shutdown -a now
# Specifies the key used for sleep (F1-F12)
sleep_key = F3
# terminal reset command (tput is faster)
#term_reset_cmd = /usr/bin/tput reset
# Command executed when pressing shutdown_key
shutdown_cmd = /sbin/shutdown -a now
# tty in use
#tty = 2
# Command executed when pressing restart_key
restart_cmd = /sbin/shutdown -r now
# wayland setup command
#wayland_cmd = /etc/ly/wsetup.sh
# Command executed when pressing sleep key (can be null)
sleep_cmd = null
# add wayland specifier to session names
#wayland_specifier = false
#wayland_specifier = true
# Active language
# Available languages are found in /etc/ly/lang/
lang = en
# wayland desktop environments
#waylandsessions = /usr/share/wayland-sessions
# TTY in use
tty = 2
# xorg server command
#x_cmd = /usr/bin/X
# Console path
console_dev = /dev/console
# xorg setup command
#x_cmd_setup = /etc/ly/xsetup.sh
# Default path. If null, ly doesn't set a path.
path = /sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin
# xorg xauthority edition tool
#xauth_cmd = /usr/bin/xauth
# Event timeout in milliseconds
min_refresh_delta = 5
# xorg desktop environments
#xsessions = /usr/share/xsessions
# Service name (set to ly to use the provided pam config file)
service_name = ly
# Terminal reset command (tput is faster)
term_reset_cmd = /usr/bin/tput reset
# Terminal restore cursor command
term_restore_cursor_cmd = /usr/bin/tput cnorm
# Cookie generator
mcookie_cmd = /usr/bin/mcookie
# Wayland setup command
wayland_cmd = /etc/ly/wsetup.sh
# Wayland desktop environments
waylandsessions = /usr/share/wayland-sessions
# xinitrc (hidden if null)
xinitrc = ~/.xinitrc
# Xorg server command
x_cmd = /usr/bin/X
# Xorg setup command
x_cmd_setup = /etc/ly/xsetup.sh
# Xorg xauthority edition tool
xauth_cmd = /usr/bin/xauth
# Xorg desktop environments
xsessions = /usr/share/xsessions

45
res/lang/cat.ini Normal file
View File

@@ -0,0 +1,45 @@
capslock = Bloq Majús
err_alloc = falla d'assignació de memòria
err_bounds = índex fora de límit
err_chdir = error al obrir carpeta home
err_console_dev = error al accedir a la consola
err_dgn_oob = missatge de registre
err_domain = domini invàlid
err_hostname = error al obtenir el nom del host
err_mlock = error al bloquejar la clau de memòria
err_null = punter nul
err_pam = error en la transacció pam
err_pam_abort = transacció pam avortada
err_pam_acct_expired = compte expirat
err_pam_auth = error d'autenticació
err_pam_authinfo_unavail = error al obtenir informació de l'usuari
err_pam_authok_reqd = token expirat
err_pam_buf = error de la memòria intermitja
err_pam_cred_err = error al establir les credencials
err_pam_cred_expired = credencials expirades
err_pam_cred_insufficient = credencials insuficients
err_pam_cred_unavail = error al obtenir credencials
err_pam_maxtries = s'ha assolit al màxim nombre d'intents
err_pam_perm_denied = permís denegat
err_pam_session = error de sessió
err_pam_sys = error de sistema
err_pam_user_unknown = usuari desconegut
err_path = error al establir la ruta
err_perm_dir = error al canviar de directori actual
err_perm_group = error al degradar els permisos de grup
err_perm_user = error al degradar els permisos de l'usuari
err_pwnam = error al obtenir la informació de l'usuari
err_user_gid = error al establir el GID de l'usuari
err_user_init = error al inicialitzar usuari
err_user_uid = error al establir el UID de l'usuari
err_xsessions_dir = error al cercar la carpeta de sessions
err_xsessions_open = error al obrir la carpeta de sessions
login = iniciar sessió
logout = tancar sessió
numlock = Bloq Num
password = Clau
restart = reiniciar
shell = shell
shutdown = aturar
wayland = wayland
xinitrc = xinitrc

45
res/lang/cs.ini Normal file
View File

@@ -0,0 +1,45 @@
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řístupu do konzole
err_dgn_oob = zpráva protokolu
err_domain = neplatná doména
err_hostname = nelze získat název hostitele
err_mlock = uzamčení paměti hesel selhalo
err_null = nulový ukazatel
err_pam = pam transakce selhala
err_pam_abort = pam transakce přerušena
err_pam_acct_expired = platnost účtu vypršela
err_pam_auth = chyba autentizace
err_pam_authinfo_unavail = nelze získat informace o uživateli
err_pam_authok_reqd = platnost tokenu vypršela
err_pam_buf = chyba vyrovnávací paměti
err_pam_cred_err = nelze nastavit pověření
err_pam_cred_expired = platnost pověření vypršela
err_pam_cred_insufficient = nedostatečné pověření
err_pam_cred_unavail = nepodařilo se získat pověření
err_pam_maxtries = byl dosažen maximální počet pokusů
err_pam_perm_denied = přístup odepřen
err_pam_session = chyba relace
err_pam_sys = systemová chyba
err_pam_user_unknown = neznámý uživatel
err_path = nepodařilo se nastavit cestu
err_perm_dir = nepodařilo se změnit adresář
err_perm_group = nepodařilo se snížit skupinová oprávnění
err_perm_user = nepodařilo se snížit uživatelská oprávnění
err_pwnam = nelze získat informace o uživateli
err_user_gid = nastavení GID uživatele selhalo
err_user_init = inicializace uživatele selhala
err_user_uid = nastavení UID uživateli selhalo
err_xsessions_dir = nepodařilo se najít složku relací
err_xsessions_open = nepodařilo se otevřít složku relací
login = uživatel
logout = odhlášen
numlock = numlock
password = heslo
restart = restartovat
shell = příkazový řádek
shutdown = vypnout
wayland = wayland
xinitrc = xinitrc

45
res/lang/de.ini Normal file
View File

@@ -0,0 +1,45 @@
capslock = Feststelltaste
err_alloc = Speicherzuweisung fehlgeschlagen
err_bounds = Listenindex ist außerhalb des Bereichs
err_chdir = Fehler beim oeffnen des home-ordners
err_console_dev = Zugriff auf die Konsole fehlgeschlagen
err_dgn_oob = Protokoll Nachricht
err_domain = Unzulaessige domain
err_hostname = Holen des Hostnames fehlgeschlagen
err_mlock = Abschließen des Passwortspeichers fehlgeschlagen
err_null = Null Zeiger
err_pam = pam Transaktion fehlgeschlagen
err_pam_abort = pam Transaktion abgebrochen
err_pam_acct_expired = Benutzerkonto abgelaufen
err_pam_auth = Authentifizierungs Fehler
err_pam_authinfo_unavail = holen der Benutzerinformationen fehlgeschlagen
err_pam_authok_reqd = Schluessel abgelaufen
err_pam_buf = Speicherpufferfehler
err_pam_cred_err = Fehler beim setzen der Anmeldedaten
err_pam_cred_expired = Anmeldedaten abgelaufen
err_pam_cred_insufficient = Anmeldedaten unzureichend
err_pam_cred_unavail = Fehler beim holen der Anmeldedaten
err_pam_maxtries = Maximale Versuche erreicht
err_pam_perm_denied = Zugriff Verweigert
err_pam_session = Sitzungsfehler
err_pam_sys = Systemfehler
err_pam_user_unknown = Unbekannter Nutzer
err_path = Fehler beim setzen des Pfades
err_perm_dir = Fehler beim wechseln des Ordners
err_perm_group = Fehler beim heruntersetzen der Gruppen Berechtigungen
err_perm_user = Fehler beim heruntersetzen der Nutzer Berechtigungen
err_pwnam = Holen der Benutzerinformationen fehlgeschlagen
err_user_gid = Fehler beim setzen der Gruppen Id des Nutzers
err_user_init = Initialisierung des Nutzers fehlgeschlagen
err_user_uid = Setzen der Benutzer Id fehlgeschlagen
err_xsessions_dir = Fehler beim finden des Sitzungsordners
err_xsessions_open = Fehler beim öffnen des Sitzungsordners
login = Anmelden
logout = Abgemeldet
numlock = Numtaste
password = Passwort
restart = Neustarten
shell = shell
shutdown = Herunterfahren
wayland = wayland
xinitrc = xinitrc

View File

@@ -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
f1 = F1 shutdown
f2 = F2 reboot
login = login:
insert = insert
login = login
logout = logged out
normal = normal
numlock = numlock
password = password:
password = password
restart = reboot
shell = shell
shutdown = shutdown
sleep = sleep
wayland = wayland
xinitrc = xinitrc
x11 = x11

View File

@@ -15,7 +15,7 @@ err_pam_auth = error de autenticación
err_pam_authinfo_unavail = error al obtener información del usuario
err_pam_authok_reqd = token expirado
err_pam_buf = error de la memoria intermedia
err_pam_cred_err = error al establecer la credenciales
err_pam_cred_err = error al establecer las credenciales
err_pam_cred_expired = credenciales expiradas
err_pam_cred_insufficient = credenciales insuficientes
err_pam_cred_unavail = error al obtener credenciales
@@ -30,16 +30,16 @@ err_perm_group = error al degradar los permisos del grupo
err_perm_user = error al degradar los permisos del usuario
err_pwnam = error al obtener la información del usuario
err_user_gid = error al establecer el GID del usuario
err_user_init = errpr al inicializar usuario
err_user_init = error al inicializar usuario
err_user_uid = error al establecer el UID del usuario
err_xsessions_dir = error al buscar la carpeta de sesiones
err_xsessions_open = error al abrir la carpeta de sesiones
f1 = F1 apagar
f2 = F2 reiniciar
login = iniciar sesion:
logout = cerrar sesion
numlock = Bloq Num
password = contraseña:
login = iniciar sesión
logout = cerrar sesión
numlock = Bloq Num
password = contraseña
restart = reiniciar
shell = shell
shutdown = apagar
wayland = wayland
xinitrc = xinitrc

View File

@@ -5,7 +5,7 @@ err_chdir = échec de l'ouverture du répertoire home
err_console_dev = échec d'accès à la console
err_dgn_oob = message
err_domain = domaine invalide
err_hostname = échec de captation du nom d'hôte
err_hostname = échec de lecture du nom d'hôte
err_mlock = échec du verrouillage mémoire
err_null = pointeur null
err_pam = échec de la transaction pam
@@ -20,7 +20,7 @@ err_pam_cred_expired = identifiants expirés
err_pam_cred_insufficient = identifiants insuffisants
err_pam_cred_unavail = échec de l'obtention des identifiants
err_pam_maxtries = limite d'essais atteinte
err_pam_perm_denied = permission refusée
err_pam_perm_denied = permission refusée
err_pam_session = erreur de session
err_pam_sys = erreur système
err_pam_user_unknown = utilisateur inconnu
@@ -28,18 +28,18 @@ err_path = échec de la modification du path
err_perm_dir = échec de changement de répertoire
err_perm_group = échec du déclassement des permissions de groupe
err_perm_user = échec du déclassement des permissions utilisateur
err_pwnam = échec de captation des infos utilisateur
err_pwnam = échec de lecture des infos utilisateur
err_user_gid = échec de modification du GID
err_user_init = échec d'initialisation de l'utilisateur
err_user_uid = échec de modification du UID
err_xsessions_dir = échec de la recherche du dossier de sessions
err_xsessions_open = échec de l'ouverture du dossier de sessions
f1 = F1 éteindre
f2 = F2 redémarrer
login = identifiant :
login = identifiant
logout = déconnection
numlock = verr.num
password = mot de passe :
password = mot de passe
restart = redémarrer
shell = shell
shutdown = éteindre
wayland = wayland
xinitrc = xinitrc

45
res/lang/it.ini Normal file
View File

@@ -0,0 +1,45 @@
capslock = capslock
err_alloc = impossibile allocare memoria
err_bounds = indice fuori limite
err_chdir = impossibile aprire home directory
err_console_dev = impossibile aprire console
err_dgn_oob = messaggio log
err_domain = dominio non valido
err_hostname = impossibile ottenere hostname
err_mlock = impossibile ottenere lock per la password in memoria
err_null = puntatore nullo
err_pam = transazione PAM fallita
err_pam_abort = transazione PAM interrotta
err_pam_acct_expired = account scaduto
err_pam_auth = errore di autenticazione
err_pam_authinfo_unavail = impossibile ottenere informazioni utente
err_pam_authok_reqd = token scaduto
err_pam_buf = errore buffer memoria
err_pam_cred_err = impossibile impostare credenziali
err_pam_cred_expired = credenziali scadute
err_pam_cred_insufficient = credenziali insufficienti
err_pam_cred_unavail = impossibile ottenere credenziali
err_pam_maxtries = raggiunto limite tentativi
err_pam_perm_denied = permesso negato
err_pam_session = errore di sessione
err_pam_sys = errore di sistema
err_pam_user_unknown = utente sconosciuto
err_path = impossibile impostare percorso
err_perm_dir = impossibile cambiare directory corrente
err_perm_group = impossibile ridurre permessi gruppo
err_perm_user = impossibile ridurre permessi utente
err_pwnam = impossibile ottenere dati utente
err_user_gid = impossibile impostare GID utente
err_user_init = impossibile inizializzare utente
err_user_uid = impossible impostare UID utente
err_xsessions_dir = impossibile localizzare cartella sessioni
err_xsessions_open = impossibile aprire cartella sessioni
login = username
logout = scollegato
numlock = numlock
password = password
restart = riavvio
shell = shell
shutdown = arresto
wayland = wayland
xinitrc = xinitrc

45
res/lang/pl.ini Normal file
View File

@@ -0,0 +1,45 @@
capslock = capslock
err_alloc = nieudana alokacja pamięci
err_bounds = indeks poza granicami
err_chdir = nie udało się otworzyć folderu domowego
err_console_dev = nie udało się uzyskać dostępu do konsoli
err_dgn_oob = wiadomość loga
err_domain = niepoprawna domena
err_hostname = nie udało się uzyskać nazwy hosta
err_mlock = nie udało się zablokować pamięci haseł
err_null = wskaźnik zerowy
err_pam = transakcja pam nieudana
err_pam_abort = transakcja pam przerwana
err_pam_acct_expired = konto wygasło
err_pam_auth = błąd autentyfikacji
err_pam_authinfo_unavail = nie udało się zdobyć informacji o użytkowniku
err_pam_authok_reqd = token wygasł
err_pam_buf = błąd bufora pamięci
err_pam_cred_err = nie udało się ustawić uwierzytelnienia
err_pam_cred_expired = uwierzytelnienie wygasło
err_pam_cred_insufficient = niewystarczające uwierzytelnienie
err_pam_cred_unavail = nie udało się uzyskać uwierzytelnienia
err_pam_maxtries = osiągnięto limit prób
err_pam_perm_denied = brak uprawnień
err_pam_session = błąd sesji
err_pam_sys = błąd systemu
err_pam_user_unknown = nieznany użytkownik
err_path = nie udało się ustawić ścieżki
err_perm_dir = nie udało się zmienić obecnego katalogu
err_perm_group = nie udało się obniżyć uprawnień grupy
err_perm_user = nie udało się obniżyć uprawnień użytkownika
err_pwnam = nie udało się uzyskać informacji o użytkowniku
err_user_gid = nie udało się ustawić GID użytkownika
err_user_init = nie udało się zainicjalizować użytkownika
err_user_uid = nie udało się ustawić UID użytkownika
err_xsessions_dir = nie udało się znaleźć folderu sesji
err_xsessions_open = nie udało się otworzyć folderu sesji
login = login
logout = wylogowano
numlock = numlock
password = hasło
restart = uruchom ponownie
shell = powłoka
shutdown = wyłącz
wayland = wayland
xinitrc = xinitrc

View File

@@ -1,45 +1,45 @@
capslock = caixa alta
err_alloc = alocação de memória malsucedida
capslock = capslock
err_alloc = erro na atribuição de memória
err_bounds = índice fora de limites
err_chdir = não foi possível abrir o diretório home
err_console_dev = não foi possível acessar o console
err_dgn_oob = mensagem de log
err_chdir = erro ao abrir a pasta home
err_console_dev = erro ao aceder à consola
err_dgn_oob = mensagem de registo
err_domain = domínio inválido
err_hostname = não foi possível obter o nome do host
err_mlock = bloqueio da memória de senha malsucedido
err_hostname = erro ao obter o nome do host
err_mlock = erro de bloqueio de memória
err_null = ponteiro nulo
err_pam = transação pam malsucedida
err_pam = erro na transação pam
err_pam_abort = transação pam abortada
err_pam_acct_expired = conta expirada
err_pam_auth = erro de autenticação
err_pam_authinfo_unavail = não foi possível obter informações do usuário
err_pam_authok_reqd = token expirada
err_pam_authinfo_unavail = erro ao obter informação do utilizador
err_pam_authok_reqd = token expirado
err_pam_buf = erro de buffer de memória
err_pam_cred_err = erro para definir credenciais
err_pam_cred_err = erro ao definir credenciais
err_pam_cred_expired = credenciais expiradas
err_pam_cred_insufficient = credenciais insuficientes
err_pam_cred_unavail = não foi possível obter credenciais
err_pam_cred_unavail = erro ao obter credenciais
err_pam_maxtries = limite máximo de tentativas atingido
err_pam_perm_denied = permissão negada
err_pam_session = erro de sessão
err_pam_sys = erro de sistema
err_pam_user_unknown = usuário desconhecido
err_path = não foi possível definir o caminho
err_perm_dir = não foi possível alterar o diretório atual
err_perm_group = não foi possível reduzir as permissões de grupo
err_perm_user = não foi possível reduzir as permissões de usuário
err_pwnam = não foi possível obter informações do usuário
err_user_gid = não foi possível definir o GID do usuário
err_user_init = não foi possível iniciar o usuário
err_user_uid = não foi possível definir o UID do usuário
err_xsessions_dir = não foi possível encontrar a pasta das sessões
err_xsessions_open = não foi possível abrir a pasta das sessões
f1 = F1 desligar
f2 = F2 reiniciar
login = conectar:
logout = desconectado
err_pam_user_unknown = utilizador desconhecido
err_path = erro ao definir o caminho de acesso
err_perm_dir = erro ao alterar o diretório atual
err_perm_group = erro ao reduzir as permissões do grupo
err_perm_user = erro ao reduzir as permissões do utilizador
err_pwnam = erro ao obter informação do utilizador
err_user_gid = erro ao definir o GID do utilizador
err_user_init = erro ao iniciar o utilizador
err_user_uid = erro ao definir o UID do utilizador
err_xsessions_dir = erro ao localizar a pasta das sessões
err_xsessions_open = erro ao abrir a pasta das sessões
login = iniciar sessão
logout = terminar sessão
numlock = numlock
password = senha:
password = palavra-passe
restart = reiniciar
shell = shell
shutdown = encerrar
wayland = wayland
xinitrc = xinitrc

45
res/lang/pt_BR.ini Normal file
View File

@@ -0,0 +1,45 @@
capslock = caixa alta
err_alloc = alocação de memória malsucedida
err_bounds = índice fora de limites
err_chdir = não foi possível abrir o diretório home
err_console_dev = não foi possível acessar o console
err_dgn_oob = mensagem de log
err_domain = domínio inválido
err_hostname = não foi possível obter o nome do host
err_mlock = bloqueio da memória de senha malsucedido
err_null = ponteiro nulo
err_pam = transação pam malsucedida
err_pam_abort = transação pam abortada
err_pam_acct_expired = conta expirada
err_pam_auth = erro de autenticação
err_pam_authinfo_unavail = não foi possível obter informações do usuário
err_pam_authok_reqd = token expirada
err_pam_buf = erro de buffer de memória
err_pam_cred_err = erro para definir credenciais
err_pam_cred_expired = credenciais expiradas
err_pam_cred_insufficient = credenciais insuficientes
err_pam_cred_unavail = não foi possível obter credenciais
err_pam_maxtries = limite máximo de tentativas atingido
err_pam_perm_denied = permissão negada
err_pam_session = erro de sessão
err_pam_sys = erro de sistema
err_pam_user_unknown = usuário desconhecido
err_path = não foi possível definir o caminho
err_perm_dir = não foi possível alterar o diretório atual
err_perm_group = não foi possível reduzir as permissões de grupo
err_perm_user = não foi possível reduzir as permissões de usuário
err_pwnam = não foi possível obter informações do usuário
err_user_gid = não foi possível definir o GID do usuário
err_user_init = não foi possível iniciar o usuário
err_user_uid = não foi possível definir o UID do usuário
err_xsessions_dir = não foi possível encontrar a pasta das sessões
err_xsessions_open = não foi possível abrir a pasta das sessões
login = conectar
logout = desconectado
numlock = numlock
password = senha
restart = reiniciar
shell = shell
shutdown = desligar
wayland = wayland
xinitrc = xinitrc

View File

@@ -34,12 +34,12 @@ err_perm_user = nu s-a putut face downgrade permisiunilor de utilizator
f1 = F1 opreşte sistemul
f2 = F2 resetează
login = utilizator:
login = utilizator
logout = opreşte sesiunea
numlock = numlock
password = parolă:
password = parolă
restart = resetează
shell = shell
shutdown = opreşte sistemul
wayland = wayland
xinitrc = xinitrc

View File

@@ -10,17 +10,17 @@ err_mlock = сбой блокировки памяти
err_null = нулевой указатель
err_pam = pam транзакция не удалась
err_pam_abort = pam транзакция прервана
err_pam_acct_expired = срок действия аккаунта истек
err_pam_acct_expired = срок действия аккаунта истёк
err_pam_auth = ошибка аутентификации
err_pam_authinfo_unavail = не удалось получить информацию о пользователе
err_pam_authok_reqd = токен истек
err_pam_authok_reqd = токен истёк
err_pam_buf = ошибка буфера памяти
err_pam_cred_err = не удалось установить полномочия
err_pam_cred_expired = полномочия истекли
err_pam_cred_insufficient = недостаточо полномочий
err_pam_cred_insufficient = недостаточно полномочий
err_pam_cred_unavail = не удалось получить полномочия
err_pam_maxtries = лимит попыток исчерпан
err_pam_perm_denied = доступ запрещен
err_pam_perm_denied = доступ запрещён
err_pam_session = ошибка сессии
err_pam_sys = системная ошибка
err_pam_user_unknown = неизвестный пользователь
@@ -34,12 +34,12 @@ err_user_init = не удалось инициализировать польз
err_user_uid = не удалось установить UID пользователя
err_xsessions_dir = не удалось найти сессионную папку
err_xsessions_open = не удалось открыть сессионную папку
f1 = F1 выключить
f2 = F2 перезагрузить
login = логин:
login = логин
logout = logged out
numlock = numlock
password = пароль:
password = пароль
restart = перезагрузить
shell = shell
shutdown = выключить
wayland = wayland
xinitrc = xinitrc

45
res/lang/sr.ini Normal file
View File

@@ -0,0 +1,45 @@
capslock = capslock
err_alloc = neuspijesna alokacija memorije
err_bounds = izvan granica indeksa
err_chdir = neuspijesno otvaranje home foldera
err_console_dev = neuspijesno pristupanje konzoli
err_dgn_oob = log poruka
err_domain = nevazeci domen
err_hostname = neuspijesno trazenje hostname-a
err_mlock = neuspijesno zakljucavanje memorije lozinke
err_null = null pokazivac
err_pam = pam transakcija neuspijesna
err_pam_abort = pam transakcija prekinuta
err_pam_acct_expired = nalog istekao
err_pam_auth = greska pri autentikaciji
err_pam_authinfo_unavail = neuspjelo uzimanje informacija o korisniku
err_pam_authok_reqd = token istekao
err_pam_buf = greska bafera memorije
err_pam_cred_err = neuspjelo postavljanje kredencijala
err_pam_cred_expired = kredencijali istekli
err_pam_cred_insufficient = nedovoljni kredencijali
err_pam_cred_unavail = neuspjelo uzimanje kredencijala
err_pam_maxtries = dostignut maksimalan broj pokusaja
err_pam_perm_denied = nedozovoljeno
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_group = neuspjesno snizavanje dozvola grupe
err_perm_user = neuspijesno snizavanje dozvola korisnika
err_pwnam = neuspijesno skupljanje informacija o korisniku
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
login = korisnik
logout = izlogovan
numlock = numlock
password = lozinka
restart = ponovo pokreni
shell = shell
shutdown = ugasi
wayland = wayland
xinitrc = xinitrc

45
res/lang/sv.ini Normal file
View File

@@ -0,0 +1,45 @@
capslock = capslock
err_alloc = misslyckad minnesallokering
err_bounds = utanför banan index
err_chdir = misslyckades att öppna hemkatalog
err_console_dev = misslyckades att komma åt konsol
err_dgn_oob = loggmeddelande
err_domain = okänd domän
err_hostname = misslyckades att hämta värdnamn
err_mlock = misslyckades att låsa lösenordsminne
err_null = nullpekare
err_pam = pam-transaktion misslyckades
err_pam_abort = pam-transaktion avbröts
err_pam_acct_expired = konto upphört
err_pam_auth = autentiseringsfel
err_pam_authinfo_unavail = misslyckades att hämta användarinfo
err_pam_authok_reqd = token utgången
err_pam_buf = minnesbuffer fel
err_pam_cred_err = misslyckades att ställa in inloggningsuppgifter
err_pam_cred_expired = inloggningsuppgifter upphörda
err_pam_cred_insufficient = otillräckliga inloggningsuppgifter
err_pam_cred_unavail = misslyckades att hämta inloggningsuppgifter
err_pam_maxtries = nådde maximal försöksgräns
err_pam_perm_denied = åtkomst nekad
err_pam_session = sessionsfel
err_pam_sys = systemfel
err_pam_user_unknown = okänd användare
err_path = misslyckades att ställa in sökväg
err_perm_dir = misslyckades att ändra aktuell katalog
err_perm_group = misslyckades att nergradera gruppbehörigheter
err_perm_user = misslyckades att nergradera användarbehörigheter
err_pwnam = misslyckades att hämta användarinfo
err_user_gid = misslyckades att ställa in användar-GID
err_user_init = misslyckades att initialisera användaren
err_user_uid = misslyckades att ställa in användar-UID
err_xsessions_dir = misslyckades att hitta sessionskatalog
err_xsessions_open = misslyckades att öppna sessionskatalog
login = inloggning
logout = utloggad
numlock = numlock
password = lösenord
restart = starta om
shell = skal
shutdown = stäng av
wayland = wayland
xinitrc = xinitrc

45
res/lang/tr.ini Normal file
View File

@@ -0,0 +1,45 @@
capslock = capslock
err_alloc = basarisiz bellek ayirma
err_bounds = sinirlarin disinda dizin
err_chdir = ev klasoru acilamadi
err_console_dev = konsola erisilemedi
err_dgn_oob = log mesaji
err_domain = gecersiz etki alani
err_hostname = ana bilgisayar adi alinamadi
err_mlock = parola bellegi kilitlenemedi
err_null = bos isaretci hatasi
err_pam = pam islemi basarisiz oldu
err_pam_abort = pam islemi durduruldu
err_pam_acct_expired = hesabin suresi dolmus
err_pam_auth = kimlik dogrulama hatasi
err_pam_authinfo_unavail = kullanici bilgileri getirilirken hata olustu
err_pam_authok_reqd = suresi dolmus token
err_pam_buf = bellek arabellegi hatasi
err_pam_cred_err = kimlik bilgileri ayarlanamadi
err_pam_cred_expired = kimlik bilgilerinin suresi dolmus
err_pam_cred_insufficient = yetersiz kimlik bilgileri
err_pam_cred_unavail = kimlik bilgileri alinamadi
err_pam_maxtries = en fazla deneme sinirina ulasildi
err_pam_perm_denied = izin reddedildi
err_pam_session = oturum hatasi
err_pam_sys = sistem hatasi
err_pam_user_unknown = bilinmeyen kullanici
err_path = yol ayarlanamadi
err_perm_dir = gecerli dizin degistirilemedi
err_perm_group = grup izinleri dusurulemedi
err_perm_user = kullanici izinleri dusurulemedi
err_pwnam = kullanici bilgileri alinamadi
err_user_gid = kullanici icin GID ayarlanamadi
err_user_init = kullanici oturumu baslatilamadi
err_user_uid = kullanici icin UID ayarlanamadi
err_xsessions_dir = oturumlar klasoru bulunamadi
err_xsessions_open = oturumlar klasoru acilamadi
login = kullanici
logout = oturumdan cikis yapildi
numlock = numlock
password = sifre
restart = yeniden baslat
shell = shell
shutdown = makineyi kapat
wayland = wayland
xinitrc = xinitrc

45
res/lang/uk.ini Normal file
View File

@@ -0,0 +1,45 @@
capslock = capslock
err_alloc = невдале виділення пам'яті
err_bounds = поза межами індексу
err_chdir = не вдалося відкрити домашній каталог
err_console_dev = невдалий доступ до консолі
err_dgn_oob = повідомлення журналу (log)
err_domain = недійсний домен
err_hostname = не вдалося отримати ім'я хосту
err_mlock = збій блокування пам'яті
err_null = нульовий вказівник
err_pam = невдала pam транзакція
err_pam_abort = pam транзакція перервана
err_pam_acct_expired = термін дії акаунту вичерпано
err_pam_auth = помилка автентифікації
err_pam_authinfo_unavail = не вдалося отримати дані користувача
err_pam_authok_reqd = термін дії токена вичерпано
err_pam_buf = помилка буферу пам'яті
err_pam_cred_err = не вдалося змінити облікові дані
err_pam_cred_expired = термін дії повноважень вичерпано
err_pam_cred_insufficient = недостатньо облікових даних
err_pam_cred_unavail = не вдалося отримати облікові дані
err_pam_maxtries = вичерпано ліміт спроб
err_pam_perm_denied = відмовлено у доступі
err_pam_session = помилка сесії
err_pam_sys = системна помилка
err_pam_user_unknown = невідомий користувач
err_path = не вдалося змінити шлях
err_perm_dir = не вдалося змінити поточний каталог
err_perm_group = не вдалося понизити права доступу групи
err_perm_user = не вдалося понизити права доступу користувача
err_pwnam = не вдалося отримати дані користувача
err_user_gid = не вдалося змінити GID користувача
err_user_init = не вдалося ініціалізувати користувача
err_user_uid = не вдалося змінити UID користувача
err_xsessions_dir = не вдалося знайти каталог сесій
err_xsessions_open = не вдалося відкрити каталог сесій
login = логін
logout = вийти
numlock = numlock
password = пароль
restart = перезавантажити
shell = оболонка
shutdown = вимкнути
wayland = wayland
xinitrc = xinitrc

38
res/ly-openrc Normal file
View File

@@ -0,0 +1,38 @@
#!/sbin/openrc-run
name="ly"
description="TUI Display Manager"
## Supervisor daemon
supervisor=supervise-daemon
respawn_period=60
pidfile=/run/"${RC_SVCNAME}.pid"
## Check for getty or agetty
if [ -x /sbin/getty ] || [ -x /bin/getty ];
then
# busybox
commandB="/sbin/getty"
elif [ -x /sbin/agetty ] || [ -x /bin/agetty ];
then
# util-linux
commandUL="/sbin/agetty"
fi
## Get the tty from the conf file
CONFTTY=$(cat /etc/ly/config.ini | sed -n 's/^tty.*=[^1-9]*// p')
## The execution vars
# If CONFTTY is empty then default to 2
TTY="tty${CONFTTY:-2}"
TERM=linux
BAUD=38400
# If we don't have getty then we should have agetty
command=${commandB:-$commandUL}
command_args_foreground="-nl /usr/bin/ly $TTY $BAUD $TERM"
depend() {
after agetty
provide display-manager
want elogind
}

12
res/ly-runit-service/conf Normal file
View File

@@ -0,0 +1,12 @@
if [ -x /sbin/agetty -o -x /bin/agetty ]; then
# util-linux specific settings
if [ "${tty}" = "tty1" ]; then
GETTY_ARGS="--noclear"
fi
fi
BAUD_RATE=38400
TERM_NAME=linux
auxtty=$(/bin/cat /etc/ly/config.ini 2>/dev/null 1| /bin/sed -n 's/\(^[[:space:]]*tty[[:space:]]*=[[:space:]]*\)\([[:digit:]][[:digit:]]*\)\(.*\)/\2/p')
TTY=tty${auxtty:-2}

View File

@@ -0,0 +1,4 @@
#!/bin/sh
[ -r conf ] && . ./conf
exec utmpset -w ${TTY}

13
res/ly-runit-service/run Normal file
View File

@@ -0,0 +1,13 @@
#!/bin/sh
[ -r conf ] && . ./conf
if [ -x /sbin/getty -o -x /bin/getty ]; then
# busybox
GETTY=getty
elif [ -x /sbin/agetty -o -x /bin/agetty ]; then
# util-linux
GETTY=agetty
fi
exec setsid ${GETTY} ${GETTY_ARGS} -nl /usr/bin/ly "${TTY}" "${BAUD_RATE}" "${TERM_NAME}"

View File

@@ -1,15 +0,0 @@
[Unit]
Description=TUI display manager
After=systemd-user-sessions.service plymouth-quit-wait.service
After=getty@tty2.service
[Service]
Type=idle
ExecStart=/usr/bin/ly
StandardInput=tty
TTYPath=/dev/tty2
TTYReset=yes
TTYVHangup=yes
[Install]
Alias=display-manager.service

18
res/ly@.service Normal file
View File

@@ -0,0 +1,18 @@
[Unit]
Description=TUI display manager (on %I)
After=systemd-user-sessions.service plymouth-quit-wait.service
After=getty@%I.service
Conflicts=getty@%I.service
[Service]
Type=idle
ExecStart=/usr/bin/ly
StandardError=journal
StandardInput=tty
TTYPath=/dev/%I
TTYReset=yes
TTYVHangup=yes
[Install]
WantedBy=multi-user.target
# Alias=display-manager.service

View File

@@ -51,4 +51,4 @@ case $SHELL in
;;
esac
exec $@
exec "$@"

View File

@@ -90,6 +90,7 @@ elif [ -f /etc/X11/Xresources ]; then
xrdb -merge /etc/X11/Xresources
fi
[ -f $HOME/.Xresources ] && xrdb -merge $HOME/.Xresources
[ -f $XDG_CONFIG_HOME/X11/Xresources ] && xrdb -merge $XDG_CONFIG_HOME/X11/Xresources
if [ -f "$USERXSESSION" ]; then
. "$USERXSESSION"

39
src/SharedError.zig Normal file
View File

@@ -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;
}

84
src/animations/Doom.zig Normal file
View File

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

181
src/animations/Matrix.zig Normal file
View File

@@ -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 = ' ';
}
}

489
src/auth.zig Normal file
View File

@@ -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,
};
}

140
src/bigclock.zig Normal file
View File

@@ -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,
};
}

View File

@@ -1,367 +0,0 @@
#include "configator.h"
#include "config.h"
#include <stddef.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)
{
*((u8*)data) = 0;
}
else
{
*((u8*)data) = atoi(*pars);
}
}
static void config_handle_u16(void* data, char** pars, const int pars_count)
{
if (strcmp(*pars, "") == 0)
{
*((u16*)data) = 0;
}
else
{
*((u16*)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},
{"f1", &lang.f1, lang_handle},
{"f2", &lang.f2, lang_handle},
{"login", &lang.login, lang_handle},
{"logout", &lang.logout, lang_handle},
{"numlock", &lang.numlock, lang_handle},
{"password", &lang.password, lang_handle},
{"shell", &lang.shell, 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},
{"blank_box", &config.blank_box, config_handle_bool},
{"blank_password", &config.blank_password, config_handle_bool},
{"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},
{"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},
{"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},
{"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},
{"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[] = {34};
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.f1 = strdup("F1 shutdown");
lang.f2 = strdup("F2 reboot");
lang.login = strdup("login:");
lang.logout = strdup("logged out");
lang.numlock = strdup("numlock");
lang.password = strdup("password:");
lang.shell = strdup("shell");
lang.wayland = strdup("wayland");
lang.xinitrc = strdup("xinitrc");
}
void config_defaults()
{
config.animate = false;
config.animation = 0;
config.asterisk = '*';
config.bg = 0;
config.blank_box = true;
config.blank_password = false;
config.console_dev = strdup("/dev/console");
config.default_input = PASSWORD_INPUT;
config.fg = 9;
config.hide_borders = 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.save = true;
config.save_file = strdup("/etc/ly/save");
config.service_name = strdup("ly");
config.shutdown_cmd = strdup("/sbin/shutdown -a now");
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.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.f1);
free(lang.f2);
free(lang.login);
free(lang.logout);
free(lang.numlock);
free(lang.password);
free(lang.shell);
free(lang.wayland);
free(lang.xinitrc);
}
void config_free()
{
free(config.console_dev);
free(config.lang);
free(config.mcookie_cmd);
free(config.path);
free(config.restart_cmd);
free(config.save_file);
free(config.service_name);
free(config.shutdown_cmd);
free(config.term_reset_cmd);
free(config.wayland_cmd);
free(config.waylandsessions);
free(config.x_cmd);
free(config.x_cmd_setup);
free(config.xauth_cmd);
free(config.xsessions);
}

View File

@@ -1,111 +0,0 @@
#ifndef H_LY_CONFIG
#define H_LY_CONFIG
#include "ctypes.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* f1;
char* f2;
char* login;
char* logout;
char* numlock;
char* password;
char* shell;
char* wayland;
char* xinitrc;
};
struct config
{
bool animate;
u8 animation;
char asterisk;
u8 bg;
bool blank_box;
bool blank_password;
char* console_dev;
u8 default_input;
u8 fg;
bool hide_borders;
u8 input_len;
char* lang;
bool load;
u8 margin_box_h;
u8 margin_box_v;
u8 max_desktop_len;
u8 max_login_len;
u8 max_password_len;
char* mcookie_cmd;
u16 min_refresh_delta;
char* path;
char* restart_cmd;
bool save;
char* save_file;
char* service_name;
char* shutdown_cmd;
char* term_reset_cmd;
u8 tty;
char* wayland_cmd;
bool wayland_specifier;
char* waylandsessions;
char* x_cmd;
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

50
src/config/Config.zig Normal file
View File

@@ -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",

51
src/config/Lang.zig Normal file
View File

@@ -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",

2
src/config/Save.zig Normal file
View File

@@ -0,0 +1,2 @@
user: ?[]const u8 = null,
session_index: ?u64 = null,

30
src/config/migrator.zig Normal file
View File

@@ -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;
}

View File

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

View File

@@ -1,647 +0,0 @@
#include "dragonfail.h"
#include "termbox.h"
#include "ctypes.h"
#include "inputs.h"
#include "utils.h"
#include "config.h"
#include "draw.h"
#include <ctype.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
#if defined(__DragonFly__) || defined(__FreeBSD__)
#include <sys/kbio.h>
#else // linux
#include <linux/kd.h>
#endif
#define DOOM_STEPS 13
void draw_init(struct term_buf* buf)
{
buf->width = tb_width();
buf->height = tb_height();
hostname(&buf->info_line);
u16 len_login = strlen(lang.login);
u16 len_password = strlen(lang.password);
if (len_login > len_password)
{
buf->labels_max_len = len_login;
}
else
{
buf->labels_max_len = len_password;
}
buf->box_height = 7 + (2 * config.margin_box_v);
buf->box_width =
(2 * config.margin_box_h)
+ (config.input_len + 1)
+ buf->labels_max_len;
#if defined(__linux__)
buf->box_chars.left_up = 0x250c;
buf->box_chars.left_down = 0x2514;
buf->box_chars.right_up = 0x2510;
buf->box_chars.right_down = 0x2518;
buf->box_chars.top = 0x2500;
buf->box_chars.bot = 0x2500;
buf->box_chars.left = 0x2502;
buf->box_chars.right = 0x2502;
#else
buf->box_chars.left_up = '+';
buf->box_chars.left_down = '+';
buf->box_chars.right_up = '+';
buf->box_chars.right_down= '+';
buf->box_chars.top = '-';
buf->box_chars.bot = '-';
buf->box_chars.left = '|';
buf->box_chars.right = '|';
#endif
}
void draw_free(struct term_buf* buf)
{
if (config.animate)
{
free(buf->tmp_buf);
}
}
void draw_box(struct term_buf* buf)
{
u16 box_x = (buf->width - buf->box_width) / 2;
u16 box_y = (buf->height - buf->box_height) / 2;
u16 box_x2 = (buf->width + buf->box_width) / 2;
u16 box_y2 = (buf->height + buf->box_height) / 2;
buf->box_x = box_x;
buf->box_y = box_y;
if (!config.hide_borders)
{
// corners
tb_change_cell(
box_x - 1,
box_y - 1,
buf->box_chars.left_up,
config.fg,
config.bg);
tb_change_cell(
box_x2,
box_y - 1,
buf->box_chars.right_up,
config.fg,
config.bg);
tb_change_cell(
box_x - 1,
box_y2,
buf->box_chars.left_down,
config.fg,
config.bg);
tb_change_cell(
box_x2,
box_y2,
buf->box_chars.right_down,
config.fg,
config.bg);
// top and bottom
struct tb_cell c1 = {buf->box_chars.top, config.fg, config.bg};
struct tb_cell c2 = {buf->box_chars.bot, config.fg, config.bg};
for (u8 i = 0; i < buf->box_width; ++i)
{
tb_put_cell(
box_x + i,
box_y - 1,
&c1);
tb_put_cell(
box_x + i,
box_y2,
&c2);
}
// left and right
c1.ch = buf->box_chars.left;
c2.ch = buf->box_chars.right;
for (u8 i = 0; i < buf->box_height; ++i)
{
tb_put_cell(
box_x - 1,
box_y + i,
&c1);
tb_put_cell(
box_x2,
box_y + i,
&c2);
}
}
if (config.blank_box)
{
struct tb_cell blank = {' ', config.fg, config.bg};
for (u8 i = 0; i < buf->box_height; ++i)
{
for (u8 k = 0; k < buf->box_width; ++k)
{
tb_put_cell(
box_x + k,
box_y + i,
&blank);
}
}
}
}
struct tb_cell* strn_cell(char* s, u16 len) // throws
{
struct tb_cell* cells = malloc((sizeof (struct tb_cell)) * len);
char* s2 = s;
u32 c;
if (cells != NULL)
{
for (u16 i = 0; i < len; ++i)
{
if ((s2 - s) >= len)
{
break;
}
s2 += utf8_char_to_unicode(&c, s2);
cells[i].ch = c;
cells[i].bg = config.bg;
cells[i].fg = config.fg;
}
}
else
{
dgn_throw(DGN_ALLOC);
}
return cells;
}
struct tb_cell* str_cell(char* s) // throws
{
return strn_cell(s, strlen(s));
}
void draw_labels(struct term_buf* buf) // throws
{
// login text
struct tb_cell* login = str_cell(lang.login);
if (dgn_catch())
{
dgn_reset();
}
else
{
tb_blit(
buf->box_x + config.margin_box_h,
buf->box_y + config.margin_box_v + 4,
strlen(lang.login),
1,
login);
free(login);
}
// password text
struct tb_cell* password = str_cell(lang.password);
if (dgn_catch())
{
dgn_reset();
}
else
{
tb_blit(
buf->box_x + config.margin_box_h,
buf->box_y + config.margin_box_v + 6,
strlen(lang.password),
1,
password);
free(password);
}
if (buf->info_line != NULL)
{
u16 len = strlen(buf->info_line);
struct tb_cell* info_cell = str_cell(buf->info_line);
if (dgn_catch())
{
dgn_reset();
}
else
{
tb_blit(
buf->box_x + ((buf->box_width - len) / 2),
buf->box_y + config.margin_box_v,
len,
1,
info_cell);
free(info_cell);
}
}
}
void draw_f_commands()
{
struct tb_cell* f1 = str_cell(lang.f1);
if (dgn_catch())
{
dgn_reset();
}
else
{
tb_blit(0, 0, strlen(lang.f1), 1, f1);
free(f1);
}
struct tb_cell* f2 = str_cell(lang.f2);
if (dgn_catch())
{
dgn_reset();
}
else
{
tb_blit(strlen(lang.f1) + 1, 0, strlen(lang.f2), 1, f2);
free(f2);
}
}
void draw_lock_state(struct term_buf* buf)
{
// get values
int fd = open(config.console_dev, O_RDONLY);
if (fd < 0)
{
buf->info_line = lang.err_console_dev;
return;
}
bool numlock_on;
bool capslock_on;
#if defined(__DragonFly__) || defined(__FreeBSD__)
int led;
ioctl(fd, KDGETLED, &led);
numlock_on = led & LED_NUM;
capslock_on = led & LED_CAP;
#else // linux
char led;
ioctl(fd, KDGKBLED, &led);
numlock_on = led & K_NUMLOCK;
capslock_on = led & K_CAPSLOCK;
#endif
close(fd);
// print text
u16 pos_x = buf->width - strlen(lang.numlock);
if (numlock_on)
{
struct tb_cell* numlock = str_cell(lang.numlock);
if (dgn_catch())
{
dgn_reset();
}
else
{
tb_blit(pos_x, 0, strlen(lang.numlock), 1, numlock);
free(numlock);
}
}
pos_x -= strlen(lang.capslock) + 1;
if (capslock_on)
{
struct tb_cell* capslock = str_cell(lang.capslock);
if (dgn_catch())
{
dgn_reset();
}
else
{
tb_blit(pos_x, 0, strlen(lang.capslock), 1, capslock);
free(capslock);
}
}
}
void draw_desktop(struct desktop* target)
{
u16 len = strlen(target->list[target->cur]);
if (len > (target->visible_len - 3))
{
len = target->visible_len - 3;
}
tb_change_cell(
target->x,
target->y,
'<',
config.fg,
config.bg);
tb_change_cell(
target->x + target->visible_len - 1,
target->y,
'>',
config.fg,
config.bg);
for (u16 i = 0; i < len; ++ i)
{
tb_change_cell(
target->x + i + 2,
target->y,
target->list[target->cur][i],
config.fg,
config.bg);
}
}
void draw_input(struct text* input)
{
u16 len = strlen(input->text);
u16 visible_len = input->visible_len;
if (len > visible_len)
{
len = visible_len;
}
struct tb_cell* cells = strn_cell(input->visible_start, len);
if (dgn_catch())
{
dgn_reset();
}
else
{
tb_blit(input->x, input->y, len, 1, cells);
free(cells);
struct tb_cell c1 = {' ', config.fg, config.bg};
for (u16 i = input->end - input->visible_start; i < visible_len; ++i)
{
tb_put_cell(
input->x + i,
input->y,
&c1);
}
}
}
void draw_input_mask(struct text* input)
{
u16 len = strlen(input->text);
u16 visible_len = input->visible_len;
if (len > visible_len)
{
len = visible_len;
}
struct tb_cell c1 = {config.asterisk, config.fg, config.bg};
struct tb_cell c2 = {' ', config.fg, config.bg};
for (u16 i = 0; i < visible_len; ++i)
{
if (input->visible_start + i < input->end)
{
tb_put_cell(
input->x + i,
input->y,
&c1);
}
else
{
tb_put_cell(
input->x + i,
input->y,
&c2);
}
}
}
void position_input(
struct term_buf* buf,
struct desktop* desktop,
struct text* login,
struct text* password)
{
u16 x = buf->box_x + config.margin_box_h + buf->labels_max_len + 1;
i32 len = buf->box_x + buf->box_width - config.margin_box_h - x;
if (len < 0)
{
return;
}
desktop->x = x;
desktop->y = buf->box_y + config.margin_box_v + 2;
desktop->visible_len = len;
login->x = x;
login->y = buf->box_y + config.margin_box_v + 4;
login->visible_len = len;
password->x = x;
password->y = buf->box_y + config.margin_box_v + 6;
password->visible_len = len;
}
static void doom_init(struct term_buf* buf)
{
buf->init_width = buf->width;
buf->init_height = buf->height;
u16 tmp_len = buf->width * buf->height;
buf->tmp_buf = malloc(tmp_len);
tmp_len -= buf->width;
if (buf->tmp_buf == NULL)
{
dgn_throw(DGN_ALLOC);
}
memset(buf->tmp_buf, 0, tmp_len);
memset(buf->tmp_buf + tmp_len, DOOM_STEPS - 1, buf->width);
}
void animate_init(struct term_buf* buf)
{
if (config.animate)
{
switch(config.animation)
{
default:
{
doom_init(buf);
break;
}
}
}
}
static void doom(struct term_buf* term_buf)
{
static struct tb_cell fire[DOOM_STEPS] =
{
{' ', 9, 0}, // default
{0x2591, 2, 0}, // red
{0x2592, 2, 0}, // red
{0x2593, 2, 0}, // red
{0x2588, 2, 0}, // red
{0x2591, 4, 2}, // yellow
{0x2592, 4, 2}, // yellow
{0x2593, 4, 2}, // yellow
{0x2588, 4, 2}, // yellow
{0x2591, 8, 4}, // white
{0x2592, 8, 4}, // white
{0x2593, 8, 4}, // white
{0x2588, 8, 4}, // white
};
u16 src;
u16 random;
u16 dst;
u16 w = term_buf->init_width;
u8* tmp = term_buf->tmp_buf;
if ((term_buf->width != term_buf->init_width) || (term_buf->height != term_buf->init_height))
{
return;
}
struct tb_cell* buf = tb_cell_buffer();
for (u16 x = 0; x < w; ++x)
{
for (u16 y = 1; y < term_buf->init_height; ++y)
{
src = y * w + x;
random = ((rand() % 7) & 3);
dst = src - random + 1;
if (w > dst)
{
dst = 0;
}
else
{
dst -= w;
}
tmp[dst] = tmp[src] - (random & 1);
if (tmp[dst] > 12)
{
tmp[dst] = 0;
}
buf[dst] = fire[tmp[dst]];
buf[src] = fire[tmp[src]];
}
}
}
void animate(struct term_buf* buf)
{
buf->width = tb_width();
buf->height = tb_height();
if (config.animate)
{
switch(config.animation)
{
default:
{
doom(buf);
break;
}
}
}
}
bool cascade(struct term_buf* term_buf, u8* fails)
{
u16 width = term_buf->width;
u16 height = term_buf->height;
struct tb_cell* buf = tb_cell_buffer();
bool changes = false;
char c_under;
char c;
for (int i = height - 2; i >= 0; --i)
{
for (int k = 0; k < width; ++k)
{
c = buf[i * width + k].ch;
if (isspace(c))
{
continue;
}
c_under = buf[(i + 1) * width + k].ch;
if (!isspace(c_under))
{
continue;
}
if (!changes)
{
changes = true;
}
if ((rand() % 10) > 7)
{
continue;
}
buf[(i + 1) * width + k] = buf[i * width + k];
buf[i * width + k].ch = ' ';
}
}
// stop force-updating
if (!changes)
{
sleep(7);
*fails = 0;
return false;
}
// force-update
return true;
}

View File

@@ -1,63 +0,0 @@
#ifndef H_LY_DRAW
#define H_LY_DRAW
#include "termbox.h"
#include "ctypes.h"
#include "inputs.h"
struct box
{
u32 left_up;
u32 left_down;
u32 right_up;
u32 right_down;
u32 top;
u32 bot;
u32 left;
u32 right;
};
struct term_buf
{
u16 width;
u16 height;
u16 init_width;
u16 init_height;
struct box box_chars;
char* info_line;
u16 labels_max_len;
u16 box_x;
u16 box_y;
u16 box_width;
u16 box_height;
u8* tmp_buf;
};
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, u16 len);
struct tb_cell* str_cell(char* s);
void draw_labels(struct term_buf* buf);
void draw_f_commands();
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, u8* fails);
#endif

18
src/enums.zig Normal file
View File

@@ -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,
};

View File

@@ -1,268 +0,0 @@
#include "dragonfail.h"
#include "termbox.h"
#include "ctypes.h"
#include "inputs.h"
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <sys/mman.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)
{
input_desktop_right(target);
}
else if (event->key == TB_KEY_ARROW_RIGHT)
{
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->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("~/.xinitrc"), DS_XINITRC);
#if 0
input_desktop_add(target, strdup(lang.wayland), strdup(""), DS_WAYLAND);
#endif
}
void input_text(struct text* target, u64 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 (u16 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->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;
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;
}

View File

@@ -1,55 +0,0 @@
#ifndef H_LY_INPUTS
#define H_LY_INPUTS
#include "termbox.h"
#include "ctypes.h"
enum display_server {DS_WAYLAND, DS_SHELL, DS_XINITRC, DS_XORG};
struct text
{
char* text;
char* end;
i64 len;
char* cur;
char* visible_start;
u16 visible_len;
u16 x;
u16 y;
};
struct desktop
{
char** list;
char** cmd;
enum display_server* display_server;
u16 cur;
u16 len;
u16 visible_len;
u16 x;
u16 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, u64 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

113
src/interop.zig Normal file
View File

@@ -0,0 +1,113 @@
const std = @import("std");
const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
pub const termbox = @cImport({
@cInclude("termbox.h");
});
pub const vt = @cImport({
@cInclude("linux/vt.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_GETSTATE: c_int = 0x5603;
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,
};
}

View File

@@ -1,680 +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 <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];
u8 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;
if (term != NULL)
{
setenv("TERM", term, 1);
}
else
{
setenv("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, 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(const char* tty_id, const enum display_server display_server)
{
char user[15];
snprintf(user, 15, "/run/user/%d", getuid());
setenv("XDG_RUNTIME_DIR", user, 0);
setenv("XDG_SESSION_CLASS", "user", 0);
setenv("XDG_SEAT", "seat0", 0);
setenv("XDG_VTNR", tty_id, 0);
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 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, const char* dir)
{
char xauthority[256];
snprintf(xauthority, 256, "%s/%s", dir, ".lyxauth");
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)
{
// generate xauthority file
const char* xauth_dir = getenv("XDG_CONFIG_HOME");
if ((xauth_dir == NULL) || (*xauth_dir == '\0'))
{
xauth_dir = pwd->pw_dir;
}
char display_name[4];
snprintf(display_name, 3, ":%d", get_free_display());
xauth(display_name, pwd->pw_shell, xauth_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;
// 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 tty_id [3];
char vt[5];
snprintf(tty_id, 3, "%d", config.tty);
snprintf(vt, 5, "vt%d", config.tty);
// set env
env_init(pwd);
if (dgn_catch())
{
exit(EXIT_FAILURE);
}
// add pam variables
char** env = pam_getenvlist(handle);
for (u16 i = 0; env && env[i]; ++i)
{
putenv(env[i]);
}
// add xdg variables
env_xdg(tty_id, desktop->display_server[desktop->cur]);
// 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);
}
}

View File

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

View File

@@ -1,315 +0,0 @@
#include "argoat.h"
#include "configator.h"
#include "dragonfail.h"
#include "termbox.h"
#include "ctypes.h"
#include "draw.h"
#include "inputs.h"
#include "login.h"
#include "utils.h"
#include "config.h"
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#define ARG_COUNT 7
// things you can define:
// GIT_VERSION_STRING
// global
struct lang lang;
struct config config;
// args handles
void arg_help(void* data, char** pars, const int pars_count)
{
printf("RTFM\n");
}
void arg_version(void* data, char** pars, const int pars_count)
{
#ifdef GIT_VERSION_STRING
printf("Ly version %s\n", GIT_VERSION_STRING);
#else
printf("Ly version unknown\n");
#endif
}
// 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);
if (strcmp(config.lang, "en") != 0)
{
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;
u8 active_input = config.default_input;
(*input_handles[active_input])(input_structs[active_input], NULL);
// init drawing stuff
draw_init(&buf);
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;
u8 auth_fails = 0;
switch_tty(&buf);
// main loop
while (run)
{
if (update)
{
if (auth_fails < 10)
{
tb_clear();
animate(&buf);
draw_box(&buf);
draw_labels(&buf);
draw_f_commands();
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();
}
error = tb_peek_event(&event, config.min_refresh_delta);
if (error < 0)
{
continue;
}
if (event.type == TB_EVENT_KEY)
{
switch (event.key)
{
case TB_KEY_F1:
shutdown = true;
run = false;
break;
case TB_KEY_F2:
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]);
}
break;
case TB_KEY_ARROW_UP:
if (active_input > 0)
{
--active_input;
update = true;
}
break;
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 = PASSWORD_INPUT;
}
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);
}
if (reboot)
{
execl("/bin/sh", "sh", "-c", config.restart_cmd, NULL);
}
config_free();
return 0;
}

647
src/main.zig Normal file
View File

@@ -0,0 +1,647 @@
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();
{
// This will set the current active VT as our TTY.
var vtstat: interop.vt.vt_stat = undefined;
_ = std.c.ioctl(std.c.STDIN_FILENO, interop.VT_GETSTATE, &vtstat);
config.tty = @intCast(vtstat.v_active);
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,
};
}

177
src/tui/TerminalBuffer.zig Normal file
View File

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

View File

@@ -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;
}

View File

@@ -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;
}

142
src/tui/components/Text.zig Normal file
View File

@@ -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();
}

22
src/tui/utils.zig Normal file
View File

@@ -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;
}

View File

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

View File

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

Submodule sub/argoat deleted from 76d1e23b5e

Submodule sub/configator deleted from 8cec178619

Submodule sub/ctypes deleted from eb4b36559d

Submodule sub/dragonfail deleted from 0a2492c6aa

Submodule sub/termbox_next deleted from 23fff64470