113 Commits

Author SHA1 Message Date
Jaap Aarts
4bf4de816e Reimplement most of the asynchronous handling 2024-08-24 12:47:09 +02:00
Jaap Aarts
c42cf125d3 Fix processing wrt fingerprint login
This also involves a major split of the authentication code, which allows
the fingerprint and password logins to use the same code.
2024-08-20 17:49:49 +02:00
Jaap Aarts
2db48c50c9 Fix fast blinking cursor when no timeout is set 2024-08-20 17:35:45 +02:00
Jaap Aarts
cc15ab35d0 Add fprintd to the pam file 2024-08-20 17:33:14 +02:00
Jaap Aarts
6bf9b928f2 Implement finger-print authentication
Many thanks to Kerigan for providing the original C code.
This commit mainly posts his code to zig.
Additional features provided by me are:
	- automatically checking every 100ms
	- request new login info for new usernames

Co-authored-by: Kerigan <kerigancreighton@gmail.com>
2024-08-20 17:33:12 +02:00
S41G0N
87ceba4de8 Add installation instructions for Gentoo (#685)
* Adding installation section for Gentoo Linux (ly was recently added into Gentoo's official GURU project)

* replace '$ sudo' with '#'
2024-08-12 00:18:53 +02:00
AnErrupTion
b80c276dad Redirect X11 output to file via shell
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-11 18:27:58 +02:00
llc0930
b84158e1c0 Add option to center env name (#683) 2024-08-09 18:44:49 +02:00
DoctorKnowsBetter
c87d5b4e7a Fix OpenRC service (#682)
Co-authored-by: Your Name <you@example.com>
2024-08-07 17:50:55 +02:00
AnErrupTion
028cb9496a Fix session logging for X11 (somewhat)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-07 17:06:14 +02:00
Kian A.
00c94f8ffd Add Farsi Bigclock (#673)
* Change config type for bigclock

* Seprates big clock's hard codes from its logic

* Adds Farsi (fa) to big clock langs

* Minor changes

* Makes requested changes
2024-08-07 16:47:27 +02:00
AnErrupTion
2bd0d0d4f3 Don't mention log file yet in issue template
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-07 13:41:28 +02:00
AnErrupTion
767bdaf166 Add session logging support
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-07 12:03:34 +02:00
AnErrupTion
c033f5bd03 Use hexadecimal numbers for colors in config
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-06 19:38:02 +02:00
AnErrupTion
096b1a7d44 Refactor brightness handling code
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-06 18:40:24 +02:00
AnErrupTion
f0869f0e13 Add numlock set error + handle 2 more errors
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-06 15:32:31 +02:00
AnErrupTion
1ca53f661e Fix drawn position of sleep key hint
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-06 14:44:48 +02:00
AnErrupTion
8562cf4e29 Update feature request template
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-06 11:11:38 +02:00
AnErrupTion
b5b3317dd8 Add login & logout script support
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-06 11:06:20 +02:00
AnErrupTion
2901b408dc Arrange config alphabetically
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-06 10:07:23 +02:00
AnErrupTion
4e40e32f59 Slightly refactor resolution check
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-05 18:33:22 +02:00
AnErrupTion
5e85618730 Conditionally import login_cap.h with pwd.h
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-05 15:16:58 +02:00
AnErrupTion
2c428f5537 Reduce dependence on tb_cell and tb_cell_buffer()
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-05 11:08:51 +02:00
AnErrupTion
071b7a2182 Make all colors u16
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-05 10:41:34 +02:00
AnErrupTion
1075c923ef Make shell login use setup script
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-05 01:03:11 +02:00
AnErrupTion
391f86f602 Delete old setup scripts
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-05 00:59:29 +02:00
AnErrupTion
6fbbb4eff0 Consolidate xsetup.sh & wsetup.sh into one file
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-05 00:58:29 +02:00
AnErrupTion
c7f70ac78f Handle termbox2 outside of authentication
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-04 20:42:00 +02:00
AnErrupTion
ef86ea19ac Update termbox2
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-04 19:40:26 +02:00
AnErrupTion
37061269a4 Remove config.save_file
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-04 17:08:50 +02:00
AnErrupTion
7b9f03176d FreeBSD fixes
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-04 11:04:23 +02:00
AnErrupTion
b73c78d2fb Remove tput dependency & use termbox2 instead
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-03 21:48:38 +02:00
AnErrupTion
0bbe9c78dd Reduce heap allocations a bit
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-03 15:17:02 +02:00
AnErrupTion
b18f29a81a Load logind PAM modules before required ones
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-03 13:39:50 +02:00
AnErrupTion
cab3a7d214 Delete old save file if it's been migrated
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-03 10:34:13 +02:00
AnErrupTion
8995c590eb Fix CLI note & OpenRC service not using config directory option
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-03 08:36:26 +02:00
AnErrupTion
57d5d7497b Fix PAM module order
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-03 07:17:06 +02:00
AnErrupTion
ce3b310e58 Make authentication fail count configurable
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-02 22:31:35 +02:00
AnErrupTion
5d3cd62434 Swap /usr/bin and /usr/sbin in PATH
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-01 17:02:08 +02:00
AnErrupTion
d40ec873a7 Retrieve gettimeofday() from sys/time.h
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-01 14:23:52 +02:00
AnErrupTion
61f3fadfbf Make code more portable + remove mcookie usage
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-01 13:15:54 +02:00
AnErrupTion
1314c57796 Fix config.brightnessctl missing + bugs
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-01 00:54:00 +02:00
AnErrupTion
872b15c0d4 Fix authentication
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-01 00:39:00 +02:00
AnErrupTion
9b4d381f1e Update zigini (fixes an escaping bug)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-08-01 00:18:46 +02:00
AnErrupTion
bacbacd5fb Merge branch 'master' of https://github.com/fairyglade/ly 2024-07-31 22:35:55 +02:00
AnErrupTion
ee0c00574a Patch resource files + add prefix directory
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-31 22:35:36 +02:00
ello
6df91cac12 Add an animation timeout (#659)
* added animation timeout

* Updated animation timeout to u12

* updated config comment to reflect the new range for animation timeout
2024-07-31 21:49:19 +02:00
AnErrupTion
598fa6a505 I need to stop doing this
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-31 14:32:39 +02:00
AnErrupTion
548a411ae2 Remove maximum length config options + don't localize config parse error
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-31 14:31:28 +02:00
AnErrupTion
48f28e40c4 Fix an oopsie
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-31 14:02:43 +02:00
AnErrupTion
46f9ddd5fc Add translatable string for config parse error
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-31 14:01:44 +02:00
AnErrupTion
a393525212 Support multiple info lines in UI
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-31 13:58:49 +02:00
AnErrupTion
961018e753 Add generic cyclable label & base session component off it
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-31 13:21:54 +02:00
Joaquín Guerra
8b12ade372 Update spanish translation (#668) 2024-07-31 13:14:29 +02:00
AnErrupTion
a64d7efc69 Make asterisk optional (hides password if so)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-31 10:34:27 +02:00
AnErrupTion
b592a11fb0 Improve the config migrator
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-31 09:44:25 +02:00
AnErrupTion
3fedb59fdb Make setting numlock work on BSD + less homebrew interop
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-30 18:38:21 +02:00
AnErrupTion
b1bf89a4cf Only shutdown or restart after deinitializing everything
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-30 11:56:21 +02:00
AnErrupTion
2dec2e0b7f Print data directory deletion in build.zig
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-30 10:54:30 +02:00
AnErrupTion
5f2f21620a Update zigini (fixes incorrect comment parsing)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-30 09:43:56 +02:00
AnErrupTion
48185bdfe0 More verbose output in build.zig
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-29 14:44:56 +02:00
AnErrupTion
f646dddd02 Fix clock & bigclock not updating without input
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-29 14:18:23 +02:00
AnErrupTion
c1f1c8f5c1 Use usize instead of u64 in most places for better 32-bit compatibility
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-29 13:46:14 +02:00
AnErrupTion
ee488ba36e Revert "Redirect stderr to systemd journal in service (#621)"
This reverts commit 3d8d8d67df.

Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-29 12:25:44 +02:00
AnErrupTion
56c210372d Better handle info line messages internally
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-28 23:05:12 +02:00
AnErrupTion
04a0ad3b33 Move Gentoo/OpenRC installation tip to the OpenRC section
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-28 18:17:14 +02:00
AnErrupTion
2dd83b41e8 Incorporate some FreeBSD authentication patches
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-28 13:15:49 +02:00
Moabeat
19d4b195f3 Display error messages differently in info line (#661)
* Add list of error messages to InfoLine.zig

* Change info and error cases according to review

* Add changes from review for width calculation
2024-07-28 13:02:42 +02:00
AnErrupTion
93554d9ba3 Add missing supervise symlink on runit (fixes #610)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-28 11:34:33 +02:00
AnErrupTion
075bf67cef Fix missing brightness localized strings
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-28 00:09:59 +02:00
AnErrupTion
33791a2844 Remove changelog.md
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-28 00:06:08 +02:00
AnErrupTion
8333f7ea77 Update screenshot
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-28 00:05:37 +02:00
AnErrupTion
2bc12549a1 Switch to utmpx
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 23:39:09 +02:00
AnErrupTion
1df890b238 Set PAM_TTY (fixes #248)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 22:31:56 +02:00
AnErrupTion
92c6a38835 Fix ~/.profile not being loaded with Fish
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 22:21:50 +02:00
AnErrupTion
a7e8b55c6e Add COSMIC in the list of tested DEs
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 21:13:55 +02:00
AnErrupTion
b7e1c81ad1 Re-arrange keyring PAM files + integrate with pam_systemd & elogind
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 20:36:45 +02:00
mietinen
dc310ca80e Unlock GNOME Keyring & KWallet on login (#496) 2024-07-27 20:35:24 +02:00
AnErrupTion
da2ea08078 Clarify compile-time dependencies
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 18:38:07 +02:00
AnErrupTion
8c69472065 Allow building without X11 support
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 18:35:58 +02:00
AnErrupTion
0ee28927cf Update French translation
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 18:15:34 +02:00
AnErrupTion
b7934e42d1 Add dinit support
Co-authored-by: Simon Pflaumer <s.pflaumer@murena.io>
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 15:59:02 +02:00
AnErrupTion
e775827c8b Fix possible overflow with TTY ID
Co-authored-by: Kevin Morris <kevr@0cost.org>
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 15:20:42 +02:00
AnErrupTion
9cd58123c4 Fix silly mistake
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 15:02:01 +02:00
AnErrupTion
0cead672da Add s6 support
Co-authored-by: userbook <userbook@devuan>
Co-authored-by: TerminalJunki <158248817+TerminalJunki@users.noreply.github.com>
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 15:00:07 +02:00
AnErrupTion
ce90f91bbf Add issue template for feature requests
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 14:16:40 +02:00
AnErrupTion
23c7175528 Make pre-requisites required
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 14:11:42 +02:00
AnErrupTion
5ea780f806 Continue merge
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 14:09:19 +02:00
AnErrupTion
2eaa473144 Add issue template
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-27 14:06:28 +02:00
Varun Vasan V
a939b82179 Add configurable default vi mode (#660)
- `vi_default_mode` added
- supports `normal` and `insert`
2024-07-27 11:31:55 +02:00
AnErrupTion
2b0301c1d0 Make runit run and finish scripts executable
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-26 21:58:18 +02:00
AnErrupTion
b84e6c9eed Use default PRNG and retrieve better seed
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-26 21:39:27 +02:00
AnErrupTion
49b8697546 Use octal prefix for file modes in build.zig
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-26 18:54:27 +02:00
0xNiffin
1d7a001a0b Add customizable foreground color to CMatrix (#652)
* Final fixes, adding fg_ini as field

* Capital B changed to lowercase
2024-07-22 16:44:49 +02:00
0xNiffin
3dc1482603 fixed 'std' has no member 'ChildProcess' error when building with zig build command. (#651) 2024-07-17 13:27:54 +02:00
jinzhongjia
e4abf79ad5 Support Zig 0.13.0 and setting default TTY at build time (#632)
* feat: support zig `0.13.0`

* 12 compatible

* update clap

* feat: add default tty to build option

* little fix

* update `zigini`
2024-07-12 22:40:01 +02:00
simonfogliato
5f8fbe381c Fix documentation issue about DOOM animation (#647) 2024-07-12 22:37:18 +02:00
liesen
c6d7d177b7 Fix incorrect shebang in xsetup.sh (#640) 2024-07-12 22:32:46 +02:00
tubi16
dc8d143fac Add brightness control support with brightnessctl (#626)
* Added key binds to control brightness

I added keybinds to control brightness with brightnessctl. 

F5 to decrease brightness.
f6 to increase brightness.

* Update src/main.zig

Co-authored-by: ShiningLea <anerruption@disroot.org>

* added proper keybinds and configs for brightness control

* Update src/main.zig

Co-authored-by: ShiningLea <anerruption@disroot.org>

* code improvement and changes

* updated en.ini

---------

Co-authored-by: ShiningLea <anerruption@disroot.org>
2024-07-04 13:17:56 +02:00
AnErrupTion
cbe7b37564 Fix dest_directory embedded in binary
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-03 09:50:56 +02:00
AnErrupTion
4eb4e15e43 Start v1.1.0 development cycle
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2024-07-03 09:44:23 +02:00
jinzhongjia
08f6ce5184 Add minimum_zig_version to build.zig.zon (#630) 2024-07-03 09:22:18 +02:00
アシュ
291e0d836b Various bug fixes and small features (#606)
* Fix stray cursor, integer overflows and other bugs

* check for getenvlist error, shorten code

* fix cascade, set info_line before auth, make code clearer and a bug fix

* Add option to turn on numlock at startup

* Fix setting numlock

* Update build.zig

* Custom info text

* Shift+Tab for previous input

* update changelog and res/config

* Some fixes

* update build.zig

* update build.zig again

* Fix xauth command for some shells and fix building in ReleaseSafe

* Use git describe to get dev version str

* revert change to getLockState (it broke the doom animation)

* add new ly error messages. Only try to make path for pam/exe during install when dest_directory is defined + print warning on error.

* add warning message for workaround
2024-07-02 10:52:38 +02: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
76 changed files with 7737 additions and 3942 deletions

54
.github/ISSUE_TEMPLATE/bug.yml vendored Normal file
View File

@@ -0,0 +1,54 @@
name: Bug report
description: File a bug report.
title: "[Bug] "
labels: ["bug"]
body:
- type: checkboxes
id: prerequisites
attributes:
label: Pre-requisites
description: By submitting this issue, you agree to have done the following.
options:
- label: I have looked for any other duplicate issues
required: true
- type: input
id: version
attributes:
label: Ly version
description: The output of `ly --version`
placeholder: 1.1.0-dev.12+2b0301c
validations:
required: true
- type: textarea
id: observed
attributes:
label: Observed behavior
description: What happened?
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
description: What did you expect to happen instead?
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Steps to reproduce
description: What **exactly** can someone else do in order to observe the problem you observed?
placeholder: |
1. Authenticate with ...
2. Go to ...
3. Create file ...
4. Log out and log back in
5. Observe error
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant logs
description: Please copy and paste any relevant logs, error messages or any other output. This will be automatically formatted into code, so no need for backticks. Screenshots are accepted if they make life easier for you.
render: shell

22
.github/ISSUE_TEMPLATE/feature.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: Feature request
description: Request a new feature or enhancement.
title: "[Feature] "
labels: ["feature"]
body:
- type: checkboxes
id: prerequisites
attributes:
label: Pre-requisites
description: By submitting this issue, you agree to have done the following.
options:
- label: I have looked for any other duplicate issues
required: true
- label: I have confirmed the requested feature doesn't exist in the latest version in development
required: true
- type: textarea
id: wanted
attributes:
label: Wanted behavior
description: What do you want to be added? Describe the behavior clearly.
validations:
required: true

BIN
.github/screenshot.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

6
.gitignore vendored
View File

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

12
.gitmodules vendored
View File

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

476
build.zig Normal file
View File

@@ -0,0 +1,476 @@
const std = @import("std");
const builtin = @import("builtin");
const PatchMap = std.StringHashMap([]const u8);
const min_zig_string = "0.12.0";
const current_zig = builtin.zig_version;
// Implementing zig version detection through compile time
comptime {
const min_zig = std.SemanticVersion.parse(min_zig_string) catch unreachable;
if (current_zig.order(min_zig) == .lt) {
@compileError(std.fmt.comptimePrint("Your Zig version v{} does not meet the minimum build requirement of v{}", .{ current_zig, min_zig }));
}
}
const ly_version = std.SemanticVersion{ .major = 1, .minor = 1, .patch = 0 };
var dest_directory: []const u8 = undefined;
var config_directory: []const u8 = undefined;
var prefix_directory: []const u8 = undefined;
var executable_name: []const u8 = undefined;
var default_tty_str: []const u8 = undefined;
const ProgressNode = if (current_zig.minor == 12) *std.Progress.Node else std.Progress.Node;
pub fn build(b: *std.Build) !void {
dest_directory = b.option([]const u8, "dest_directory", "Specify a destination directory for installation") orelse "";
config_directory = b.option([]const u8, "config_directory", "Specify a default config directory (default is /etc). This path gets embedded into the binary") orelse "/etc";
prefix_directory = b.option([]const u8, "prefix_directory", "Specify a default prefix directory (default is /usr)") orelse "/usr";
executable_name = b.option([]const u8, "name", "Specify installed executable file name (default is ly)") orelse "ly";
const bin_directory = try b.allocator.dupe(u8, config_directory);
config_directory = try std.fs.path.join(b.allocator, &[_][]const u8{ dest_directory, config_directory });
const build_options = b.addOptions();
const version_str = try getVersionStr(b, "ly", ly_version);
const enable_x11_support = b.option(bool, "enable_x11_support", "Enable X11 support (default is on)") orelse true;
const default_tty = b.option(u8, "default_tty", "Set the TTY (default is 2)") orelse 2;
default_tty_str = try std.fmt.allocPrint(b.allocator, "{d}", .{default_tty});
build_options.addOption([]const u8, "config_directory", bin_directory);
build_options.addOption([]const u8, "prefix_directory", prefix_directory);
build_options.addOption([]const u8, "version", version_str);
build_options.addOption(u8, "tty", default_tty);
build_options.addOption(bool, "enable_x11_support", enable_x11_support);
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "ly",
.root_source_file = b.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.addIncludePath(b.path("include"));
exe.linkSystemLibrary("pam");
if (enable_x11_support) exe.linkSystemLibrary("xcb");
exe.linkLibC();
const translate_c = b.addTranslateC(.{
.root_source_file = b.path("include/termbox2.h"),
.target = target,
.optimize = optimize,
});
translate_c.defineCMacroRaw("TB_IMPL");
const termbox2 = translate_c.addModule("termbox2");
exe.root_module.addImport("termbox2", termbox2);
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 = ExeInstaller(true).make;
installexe_step.dependOn(b.getInstallStep());
const installnoconf_step = b.step("installnoconf", "Install Ly without its configuration file");
installnoconf_step.makeFn = ExeInstaller(false).make;
installnoconf_step.dependOn(b.getInstallStep());
const installsystemd_step = b.step("installsystemd", "Install the Ly systemd service");
installsystemd_step.makeFn = ServiceInstaller(.Systemd).make;
installsystemd_step.dependOn(installexe_step);
const installopenrc_step = b.step("installopenrc", "Install the Ly openrc service");
installopenrc_step.makeFn = ServiceInstaller(.Openrc).make;
installopenrc_step.dependOn(installexe_step);
const installrunit_step = b.step("installrunit", "Install the Ly runit service");
installrunit_step.makeFn = ServiceInstaller(.Runit).make;
installrunit_step.dependOn(installexe_step);
const installs6_step = b.step("installs6", "Install the Ly s6 service");
installs6_step.makeFn = ServiceInstaller(.S6).make;
installs6_step.dependOn(installexe_step);
const installdinit_step = b.step("installdinit", "Install the Ly dinit service");
installdinit_step.makeFn = ServiceInstaller(.Dinit).make;
installdinit_step.dependOn(installexe_step);
const uninstallall_step = b.step("uninstallall", "Uninstall Ly and all services");
uninstallall_step.makeFn = uninstallall;
}
pub fn ExeInstaller(install_conf: bool) type {
return struct {
pub fn make(step: *std.Build.Step, _: ProgressNode) !void {
try install_ly(step.owner.allocator, install_conf);
}
};
}
const InitSystem = enum {
Systemd,
Openrc,
Runit,
S6,
Dinit,
};
pub fn ServiceInstaller(comptime init_system: InitSystem) type {
return struct {
pub fn make(step: *std.Build.Step, _: ProgressNode) !void {
const allocator = step.owner.allocator;
var patch_map = PatchMap.init(allocator);
defer patch_map.deinit();
try patch_map.put("$DEFAULT_TTY", default_tty_str);
try patch_map.put("$CONFIG_DIRECTORY", config_directory);
try patch_map.put("$PREFIX_DIRECTORY", prefix_directory);
try patch_map.put("$EXECUTABLE_NAME", executable_name);
switch (init_system) {
.Systemd => {
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/lib/systemd/system" });
std.fs.cwd().makePath(service_path) catch {};
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
defer service_dir.close();
const patched_service = try patchFile(allocator, "res/ly.service", patch_map);
try installText(patched_service, service_dir, service_path, "ly.service", .{ .mode = 0o644 });
},
.Openrc => {
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/init.d" });
std.fs.cwd().makePath(service_path) catch {};
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
defer service_dir.close();
const patched_service = try patchFile(allocator, "res/ly-openrc", patch_map);
try installText(patched_service, service_dir, service_path, executable_name, .{ .mode = 0o755 });
},
.Runit => {
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/sv/ly" });
std.fs.cwd().makePath(service_path) catch {};
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
defer service_dir.close();
const supervise_path = try std.fs.path.join(allocator, &[_][]const u8{ service_path, "supervise" });
const patched_conf = try patchFile(allocator, "res/ly-runit-service/conf", patch_map);
try installText(patched_conf, service_dir, service_path, "conf", .{});
try installFile("res/ly-runit-service/finish", service_dir, service_path, "finish", .{ .override_mode = 0o755 });
const patched_run = try patchFile(allocator, "res/ly-runit-service/run", patch_map);
try installText(patched_run, service_dir, service_path, "run", .{ .mode = 0o755 });
try std.fs.cwd().symLink("/run/runit/supervise.ly", supervise_path, .{});
std.debug.print("info: installed symlink /run/runit/supervise.ly\n", .{});
},
.S6 => {
const admin_service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/s6/adminsv/default/contents.d" });
std.fs.cwd().makePath(admin_service_path) catch {};
var admin_service_dir = std.fs.cwd().openDir(admin_service_path, .{}) catch unreachable;
defer admin_service_dir.close();
const file = try admin_service_dir.createFile("ly-srv", .{});
file.close();
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/s6/sv/ly-srv" });
std.fs.cwd().makePath(service_path) catch {};
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
defer service_dir.close();
const patched_run = try patchFile(allocator, "res/ly-s6/run", patch_map);
try installText(patched_run, service_dir, service_path, "run", .{ .mode = 0o755 });
try installFile("res/ly-s6/type", service_dir, service_path, "type", .{});
},
.Dinit => {
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/dinit.d" });
std.fs.cwd().makePath(service_path) catch {};
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
defer service_dir.close();
const patched_service = try patchFile(allocator, "res/ly-dinit", patch_map);
try installText(patched_service, service_dir, service_path, "ly", .{});
},
}
}
};
}
fn install_ly(allocator: std.mem.Allocator, install_config: bool) !void {
const ly_config_directory = try std.fs.path.join(allocator, &[_][]const u8{ config_directory, "/ly" });
std.fs.cwd().makePath(ly_config_directory) catch {
std.debug.print("warn: {s} already exists as a directory.\n", .{ly_config_directory});
};
const ly_lang_path = try std.fs.path.join(allocator, &[_][]const u8{ config_directory, "/ly/lang" });
std.fs.cwd().makePath(ly_lang_path) catch {
std.debug.print("warn: {s} already exists as a directory.\n", .{config_directory});
};
{
const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/bin" });
if (!std.mem.eql(u8, dest_directory, "")) {
std.fs.cwd().makePath(exe_path) catch {
std.debug.print("warn: {s} already exists as a directory.\n", .{exe_path});
};
}
var executable_dir = std.fs.cwd().openDir(exe_path, .{}) catch unreachable;
defer executable_dir.close();
try installFile("zig-out/bin/ly", executable_dir, exe_path, executable_name, .{});
}
{
var config_dir = std.fs.cwd().openDir(ly_config_directory, .{}) catch unreachable;
defer config_dir.close();
if (install_config) {
var patch_map = PatchMap.init(allocator);
defer patch_map.deinit();
try patch_map.put("$DEFAULT_TTY", default_tty_str);
try patch_map.put("$CONFIG_DIRECTORY", config_directory);
try patch_map.put("$PREFIX_DIRECTORY", prefix_directory);
const patched_config = try patchFile(allocator, "res/config.ini", patch_map);
try installText(patched_config, config_dir, ly_config_directory, "config.ini", .{});
}
{
var patch_map = PatchMap.init(allocator);
defer patch_map.deinit();
try patch_map.put("$CONFIG_DIRECTORY", config_directory);
const patched_setup = try patchFile(allocator, "res/setup.sh", patch_map);
try installText(patched_setup, config_dir, ly_config_directory, "setup.sh", .{ .mode = 0o755 });
}
}
{
var lang_dir = std.fs.cwd().openDir(ly_lang_path, .{}) catch unreachable;
defer lang_dir.close();
try installFile("res/lang/cat.ini", lang_dir, ly_lang_path, "cat.ini", .{});
try installFile("res/lang/cs.ini", lang_dir, ly_lang_path, "cs.ini", .{});
try installFile("res/lang/de.ini", lang_dir, ly_lang_path, "de.ini", .{});
try installFile("res/lang/en.ini", lang_dir, ly_lang_path, "en.ini", .{});
try installFile("res/lang/es.ini", lang_dir, ly_lang_path, "es.ini", .{});
try installFile("res/lang/fr.ini", lang_dir, ly_lang_path, "fr.ini", .{});
try installFile("res/lang/it.ini", lang_dir, ly_lang_path, "it.ini", .{});
try installFile("res/lang/pl.ini", lang_dir, ly_lang_path, "pl.ini", .{});
try installFile("res/lang/pt.ini", lang_dir, ly_lang_path, "pt.ini", .{});
try installFile("res/lang/pt_BR.ini", lang_dir, ly_lang_path, "pt_BR.ini", .{});
try installFile("res/lang/ro.ini", lang_dir, ly_lang_path, "ro.ini", .{});
try installFile("res/lang/ru.ini", lang_dir, ly_lang_path, "ru.ini", .{});
try installFile("res/lang/sr.ini", lang_dir, ly_lang_path, "sr.ini", .{});
try installFile("res/lang/sv.ini", lang_dir, ly_lang_path, "sv.ini", .{});
try installFile("res/lang/tr.ini", lang_dir, ly_lang_path, "tr.ini", .{});
try installFile("res/lang/uk.ini", lang_dir, ly_lang_path, "uk.ini", .{});
}
{
const pam_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/pam.d" });
if (!std.mem.eql(u8, dest_directory, "")) {
std.fs.cwd().makePath(pam_path) catch {
std.debug.print("warn: {s} already exists as a directory.\n", .{pam_path});
};
}
var pam_dir = std.fs.cwd().openDir(pam_path, .{}) catch unreachable;
defer pam_dir.close();
try installFile("res/pam.d/ly", pam_dir, pam_path, "ly", .{ .override_mode = 0o644 });
}
}
pub fn uninstallall(step: *std.Build.Step, _: ProgressNode) !void {
const allocator = step.owner.allocator;
try deleteTree(allocator, config_directory, "/ly", "ly config directory not found");
const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/bin/", executable_name });
var success = true;
std.fs.cwd().deleteFile(exe_path) catch {
std.debug.print("warn: ly executable not found\n", .{});
success = false;
};
if (success) std.debug.print("info: deleted {s}\n", .{exe_path});
try deleteFile(allocator, config_directory, "/pam.d/ly", "ly pam file not found");
try deleteFile(allocator, prefix_directory, "/lib/systemd/system/ly.service", "systemd service not found");
try deleteFile(allocator, config_directory, "/init.d/ly", "openrc service not found");
try deleteTree(allocator, config_directory, "/sv/ly", "runit service not found");
try deleteTree(allocator, config_directory, "/s6/sv/ly-srv", "s6 service not found");
try deleteFile(allocator, config_directory, "/s6/adminsv/default/contents.d/ly-srv", "s6 admin service not found");
try deleteFile(allocator, config_directory, "/dinit.d/ly", "dinit service not found");
}
fn getVersionStr(b: *std.Build, name: []const u8, version: std.SemanticVersion) ![]const u8 {
const version_str = b.fmt("{d}.{d}.{d}", .{ version.major, version.minor, version.patch });
var status: u8 = undefined;
const git_describe_raw = b.runAllowFail(&[_][]const u8{
"git",
"-C",
b.build_root.path orelse ".",
"describe",
"--match",
"*.*.*",
"--tags",
}, &status, .Ignore) catch {
return version_str;
};
var git_describe = std.mem.trim(u8, git_describe_raw, " \n\r");
git_describe = std.mem.trimLeft(u8, git_describe, "v");
switch (std.mem.count(u8, git_describe, "-")) {
0 => {
if (!std.mem.eql(u8, version_str, git_describe)) {
std.debug.print("{s} version '{s}' does not match git tag: '{s}'\n", .{ name, version_str, git_describe });
std.process.exit(1);
}
return version_str;
},
2 => {
// Untagged development build (e.g. 0.10.0-dev.2025+ecf0050a9).
var it = std.mem.splitScalar(u8, git_describe, '-');
const tagged_ancestor = std.mem.trimLeft(u8, it.first(), "v");
const commit_height = it.next().?;
const commit_id = it.next().?;
const ancestor_ver = try std.SemanticVersion.parse(tagged_ancestor);
if (version.order(ancestor_ver) != .gt) {
std.debug.print("{s} version '{}' must be greater than tagged ancestor '{}'\n", .{ name, version, ancestor_ver });
std.process.exit(1);
}
// Check that the commit hash is prefixed with a 'g' (a Git convention).
if (commit_id.len < 1 or commit_id[0] != 'g') {
std.debug.print("Unexpected `git describe` output: {s}\n", .{git_describe});
return version_str;
}
// The version is reformatted in accordance with the https://semver.org specification.
return b.fmt("{s}-dev.{s}+{s}", .{ version_str, commit_height, commit_id[1..] });
},
else => {
std.debug.print("Unexpected `git describe` output: {s}\n", .{git_describe});
return version_str;
},
}
}
fn installFile(
source_file: []const u8,
destination_directory: std.fs.Dir,
destination_directory_path: []const u8,
destination_file: []const u8,
options: std.fs.Dir.CopyFileOptions,
) !void {
try std.fs.cwd().copyFile(source_file, destination_directory, destination_file, options);
std.debug.print("info: installed {s}/{s}\n", .{ destination_directory_path, destination_file });
}
fn patchFile(allocator: std.mem.Allocator, source_file: []const u8, patch_map: PatchMap) ![]const u8 {
var file = try std.fs.cwd().openFile(source_file, .{});
defer file.close();
const reader = file.reader();
var text = try reader.readAllAlloc(allocator, std.math.maxInt(u16));
var iterator = patch_map.iterator();
while (iterator.next()) |kv| {
const new_text = try std.mem.replaceOwned(u8, allocator, text, kv.key_ptr.*, kv.value_ptr.*);
allocator.free(text);
text = new_text;
}
return text;
}
fn installText(
text: []const u8,
destination_directory: std.fs.Dir,
destination_directory_path: []const u8,
destination_file: []const u8,
options: std.fs.File.CreateFlags,
) !void {
var file = try destination_directory.createFile(destination_file, options);
defer file.close();
const writer = file.writer();
try writer.writeAll(text);
std.debug.print("info: installed {s}/{s}\n", .{ destination_directory_path, destination_file });
}
fn deleteFile(
allocator: std.mem.Allocator,
prefix: []const u8,
file: []const u8,
warning: []const u8,
) !void {
const path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix, file });
std.fs.cwd().deleteFile(path) catch |err| {
if (err == error.FileNotFound) {
std.debug.print("warn: {s}\n", .{warning});
return;
}
return err;
};
std.debug.print("info: deleted {s}\n", .{path});
}
fn deleteTree(
allocator: std.mem.Allocator,
prefix: []const u8,
directory: []const u8,
warning: []const u8,
) !void {
const path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix, directory });
var dir = std.fs.cwd().openDir(path, .{}) catch |err| {
if (err == error.FileNotFound) {
std.debug.print("warn: {s}\n", .{warning});
return;
}
return err;
};
dir.close();
try std.fs.cwd().deleteTree(path);
std.debug.print("info: deleted {s}\n", .{path});
}

16
build.zig.zon Normal file
View File

@@ -0,0 +1,16 @@
.{
.name = "ly",
.version = "1.0.0",
.minimum_zig_version = "0.12.0",
.dependencies = .{
.clap = .{
.url = "https://github.com/Hejsil/zig-clap/archive/refs/tags/0.9.1.tar.gz",
.hash = "122062d301a203d003547b414237229b09a7980095061697349f8bef41be9c30266b",
},
.zigini = .{
.url = "https://github.com/Kawaii-Ash/zigini/archive/0bba97a12582928e097f4074cc746c43351ba4c8.tar.gz",
.hash = "12209b971367b4066d40ecad4728e6fdffc4cc4f19356d424c2de57f5b69ac7a619a",
},
},
.paths = .{""},
}

3475
include/termbox2.h Normal file

File diff suppressed because it is too large Load Diff

118
makefile
View File

@@ -1,118 +0,0 @@
NAME = ly
CC = gcc
FLAGS = -std=c99 -pedantic -g
FLAGS+= -Wall -Wextra -Werror=vla -Wno-unused-parameter
#FLAGS+= -DDEBUG
FLAGS+= -DLY_VERSION=\"$(shell git describe --long --tags | sed 's/\([^-]*-g\)/r\1/;s/-/./g')\"
LINK = -lpam -lxcb
VALGRIND = --show-leak-kinds=all --track-origins=yes --leak-check=full --suppressions=../res/valgrind.supp
CMD = ./$(NAME)
OS:= $(shell uname -s)
ifeq ($(OS), Linux)
FLAGS+= -D_DEFAULT_SOURCE
endif
BIND = bin
OBJD = obj
SRCD = src
SUBD = sub
RESD = res
TESTD = tests
DATADIR ?= ${DESTDIR}/etc/ly
FLAGS+= -DDATADIR=\"$(DATADIR)\"
INCL = -I$(SRCD)
INCL+= -I$(SUBD)/ctypes
INCL+= -I$(SUBD)/argoat/src
INCL+= -I$(SUBD)/configator/src
INCL+= -I$(SUBD)/dragonfail/src
INCL+= -I$(SUBD)/termbox_next/src
SRCS = $(SRCD)/main.c
SRCS += $(SRCD)/config.c
SRCS += $(SRCD)/draw.c
SRCS += $(SRCD)/inputs.c
SRCS += $(SRCD)/login.c
SRCS += $(SRCD)/utils.c
SRCS += $(SUBD)/argoat/src/argoat.c
SRCS += $(SUBD)/configator/src/configator.c
SRCS += $(SUBD)/dragonfail/src/dragonfail.c
SRCS_OBJS:= $(patsubst %.c,$(OBJD)/%.o,$(SRCS))
SRCS_OBJS+= $(SUBD)/termbox_next/bin/termbox.a
.PHONY: final
final: $(BIND)/$(NAME)
$(OBJD)/%.o: %.c
@echo "building object $@"
@mkdir -p $(@D)
@$(CC) $(INCL) $(FLAGS) -c -o $@ $<
$(SUBD)/termbox_next/bin/termbox.a:
@echo "building static object $@"
@(cd $(SUBD)/termbox_next && $(MAKE))
$(BIND)/$(NAME): $(SRCS_OBJS)
@echo "compiling executable $@"
@mkdir -p $(@D)
@$(CC) -o $@ $^ $(LINK)
run:
@cd $(BIND) && $(CMD)
leak: leakgrind
leakgrind: $(BIND)/$(NAME)
@rm -f valgrind.log
@cd $(BIND) && valgrind $(VALGRIND) 2> ../valgrind.log $(CMD)
@less valgrind.log
install: $(BIND)/$(NAME)
@echo "installing ly"
@install -dZ ${DESTDIR}/etc/ly
@install -DZ $(BIND)/$(NAME) -t ${DESTDIR}/usr/bin
@install -DZ $(RESD)/config.ini -t ${DESTDIR}/etc/ly
@install -DZ $(RESD)/xsetup.sh -t $(DATADIR)
@install -DZ $(RESD)/wsetup.sh -t $(DATADIR)
@install -dZ $(DATADIR)/lang
@install -DZ $(RESD)/lang/* -t $(DATADIR)/lang
@install -DZ $(RESD)/pam.d/ly -m 644 -t ${DESTDIR}/etc/pam.d
installnoconf: $(BIND)/$(NAME)
@echo "installing ly without the configuration file"
@install -dZ ${DESTDIR}/etc/ly
@install -DZ $(BIND)/$(NAME) -t ${DESTDIR}/usr/bin
@install -DZ $(RESD)/xsetup.sh -t $(DATADIR)
@install -DZ $(RESD)/wsetup.sh -t $(DATADIR)
@install -dZ $(DATADIR)/lang
@install -DZ $(RESD)/lang/* -t $(DATADIR)/lang
@install -DZ $(RESD)/pam.d/ly -m 644 -t ${DESTDIR}/etc/pam.d
installsystemd:
@echo "installing systemd service"
@install -DZ $(RESD)/ly.service -m 644 -t ${DESTDIR}/usr/lib/systemd/system
installopenrc:
@echo "installing openrc service"
@install -DZ $(RESD)/ly-openrc -m 755 -T ${DESTDIR}/etc/init.d/${NAME}
installrunit:
@echo "installing runit service"
@install -DZ $(RESD)/ly-runit-service/* -t ${DESTDIR}/etc/sv/ly
uninstall:
@echo "uninstalling"
@rm -rf ${DESTDIR}/etc/ly
@rm -rf $(DATADIR)
@rm -f ${DESTDIR}/usr/bin/ly
@rm -f ${DESTDIR}/usr/lib/systemd/system/ly.service
@rm -f ${DESTDIR}/etc/pam.d/ly
@rm -f ${DESTDIR}/etc/init.d/${NAME}
@rm -rf ${DESTDIR}/etc/sv/ly
clean:
@echo "cleaning"
@rm -rf $(BIND) $(OBJD) valgrind.log
@(cd $(SUBD)/termbox_next && $(MAKE) clean)

129
readme.md
View File

@@ -1,32 +1,42 @@
# Ly - a TUI display manager
![Ly screenshot](https://user-images.githubusercontent.com/5473047/88958888-65efbf80-d2a1-11ea-8ae5-3f263bce9cce.png "Ly screenshot")
![Ly screenshot](.github/screenshot.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
- libc
- pam
- xcb (optional, required by default; needed for X11 support)
- Runtime (with default config):
- xorg
- xorg-xauth
- shutdown
On Debian-based distros running `apt install build-essential libpam0g-dev libxcb-xkb-dev` as root should install all the dependencies for you.
For Fedora try running `dnf install make automake gcc gcc-c++ kernel-devel pam-devel libxcb-devel`
### Debian
```
# apt install build-essential libpam0g-dev libxcb-xkb-dev
```
### Fedora
**Warning**: You may encounter issues with SELinux on Fedora.
It is recommended to add a rule for Ly as it currently does not ship one.
```
# dnf install kernel-devel pam-devel libxcb-devel
```
## Support
The following desktop environments were tested with success
The following desktop environments were tested with success:
- awesome
- bspwm
- budgie
- cinnamon
- cosmic
- deepin
- dwl
- dwm
- enlightenment
- gnome
@@ -57,7 +67,7 @@ changing the source code won't be necessary :)
## Cloning and Compiling
Clone the repository
```
$ git clone --recurse-submodules https://github.com/fairyglade/ly
$ git clone https://github.com/fairyglade/ly
```
Change the directory to ly
@@ -67,18 +77,18 @@ $ cd ly
Compile
```
$ make
$ zig build
```
Test in the configured tty (tty2 by default)
or a terminal emulator (but desktop environments won't start)
```
# make run
# zig build run
```
Install Ly and the provided systemd service file
```
# make install installsystemd
# zig build installsystemd
```
Enable the service
@@ -93,12 +103,13 @@ disable getty on Ly's tty to prevent "login" from spawning on top of it
```
### OpenRC
**NOTE 1**: On Gentoo, Ly will disable the `display-manager-init` service in order to run.
Clone, compile and test.
Install Ly and the provided OpenRC service
```
# make install installopenrc
# zig build installopenrc
```
Enable the service
@@ -108,16 +119,17 @@ Enable the service
You can edit which tty Ly will start on by editing the `tty` option in the configuration file.
If you choose a tty that already has a login/getty running (has a basic login prompt), then you have to disable the getty so it doesn't respawn on top of ly
If you choose a tty that already has a login/getty running (has a basic login prompt),
then you have to disable getty, so it doesn't respawn on top of ly
```
# rc-update del agetty.tty2
```
### runit
**NOTE 2**: To avoid a console spawning on top on Ly, comment out the appropriate line from /etc/inittab (default is 2).
### runit
```
$ make
# make install installrunit
# zig build installrunit
# ln -s /etc/sv/ly /var/service/
```
@@ -129,18 +141,82 @@ You should as well disable your existing display manager service if needed, e.g.
# rm /var/service/lxdm
```
The agetty service for the tty console where you are running ly should be disabled. For instance, if you are running ly on tty2 (that's the default, check your `/etc/ly/config.ini`) you should disable the agetty-tty2 service like this:
The agetty service for the tty console where you are running ly should be disabled.
For instance, if you are running ly on tty2 (that's the default, check your `/etc/ly/config.ini`)
you should disable the agetty-tty2 service like this:
```
# rm /var/service/agetty-tty2
```
### s6
```
# zig build installs6
```
Then, edit `/etc/s6/config/ttyX.conf` and set `SPAWN="no"`, where X is the TTY ID (e.g. `2`).
Finally, enable the service:
```
# s6-service add default ly-srv
# s6-db-reload
# s6-rc -u change ly-srv
```
### dinit
```
# zig build installdinit
# dinitctl enable ly
```
In addition to the steps above, you will also have to keep a TTY free within `/etc/dinit.d/config/console.conf`.
To do that, change `ACTIVE_CONSOLES` so that the tty that ly should use in `/etc/ly/config.ini` is free.
### 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
```
## Gentoo Installation
You can install ly from the GURU repository:
Note: If the package is masked, you may need to unmask it using ~amd64 keyword:
```bash
# echo 'x11-misc/ly ~amd64' >> /etc/portage/package.accept_keywords
```
1. Enable the GURU repository:
```bash
# eselect repository enable guru
```
2. Sync the GURU repository:
```bash
# emaint sync -r guru
```
3. Install ly from source:
```bash
# emerge --ask x11-misc/ly
```
## Configuration
You can find all the configuration in `/etc/ly/config.ini`.
The file is commented, and includes the default values.
@@ -169,9 +245,10 @@ Take a look at your .xsession if X doesn't start, as it can interfere
## PSX DOOM fire animation
To enable the famous PSX DOOM fire described by [Fabien Sanglard](http://fabiensanglard.net/doom_fire_psx/index.html),
just uncomment `animate = true` in `/etc/ly/config.ini`. You may also
just set `animation = doom` in `/etc/ly/config.ini`. You may also
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.

View File

@@ -1,24 +1,4 @@
# Animation enabled/disabled
#animate = false
# The active animation
# 0 -> PSX DOOM fire (default)
# 1 -> CMatrix
#animation = 0
# format string for clock in top right corner (see strftime specification)
#clock = %c
# enable/disable big clock
#bigclock = true
# The character used to mask the password
#asterisk = *
# Erase password input on failure
#blank_password = false
#The `fg` and `bg` color settings take a digit 0-8 corresponding to:
# The color settings in Ly take a digit 0-8 corresponding to:
#define TB_DEFAULT 0x00
#define TB_BLACK 0x01
#define TB_RED 0x02
@@ -28,113 +8,212 @@
#define TB_MAGENTA 0x06
#define TB_CYAN 0x07
#define TB_WHITE 0x08
# The default color varies, but usually it makes the background black and the foreground white.
# You can also combine these colors with the following style attributes using bitwise OR:
#define TB_BOLD 0x0100
#define TB_UNDERLINE 0x0200
#define TB_REVERSE 0x0400
#define TB_ITALIC 0x0800
#define TB_BLINK 0x1000
#define TB_HI_BLACK 0x2000
#define TB_BRIGHT 0x4000
#define TB_DIM 0x8000
# For example, to set the foreground color to red and bold, you would do 0x02 | 0x0100 = 0x0102.
# Note that you must pre-calculate the value because Ly doesn't parse bitwise OR operations in its config.
#
# 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
# Moreover, to set the VT 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 with Ly 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`.
# config) will be used by Ly for fg = 0x0008.
# The active animation
# none -> Nothing
# doom -> PSX DOOM fire
# matrix -> CMatrix
animation = none
# Stop the animation after some time
# 0 -> Run forever
# 1..2e12 -> Stop the animation after this many seconds
animation_timeout_sec = 0
# The character used to mask the password
# If null, the password will be hidden
# Note: you can use a # by escaping it like so: \#
asterisk = *
# The number of failed authentications before a special animation is played... ;)
auth_fails = 10
# Background color id
#bg = 0
bg = 0x0000
# Foreground color id
#fg = 9
# Change the state and language of the big clock
# none -> Disabled (default)
# en -> English
# fa -> Farsi
bigclock = none
# Blank main box background
# Setting to false will make it transparent
#blank_box = true
blank_box = true
# Remove main box borders
#hide_borders = false
# Border foreground color id
border_fg = 0x0008
# Main box margins
#margin_box_h = 2
#margin_box_v = 1
# Title to show at the top of the main box
# If set to null, none will be shown
box_title = null
# Input boxes length
#input_len = 34
# Brightness increase command
brightness_down_cmd = $PREFIX_DIRECTORY/bin/brightnessctl -q s 10%-
# Max input sizes
#max_desktop_len = 100
#max_login_len = 255
#max_password_len = 255
# Brightness decrease key
brightness_down_key = F5
# Brightness increase command
brightness_up_cmd = $PREFIX_DIRECTORY/bin/brightnessctl -q s +10%
# Input box active by default on startup
#default_input = 2
# Brightness increase key
brightness_up_key = F6
# Load the saved desktop and username
#load = true
# Erase password input on failure
clear_password = false
# Save the current desktop and login as defaults
#save = true
# Format string for clock in top right corner (see strftime specification). Example: %c
# If null, the clock won't be shown
clock = null
# File in which to save and load the default desktop and login
#save_file = /etc/ly/save
# Remove F1/F2 command hints
#hide_f1_commands = false
# Command executed when pressing F1
#shutdown_cmd = /sbin/shutdown -a now
# Command executed when pressing F2
#restart_cmd = /sbin/shutdown -r now
# Active language
# Available languages are found in /etc/ly/lang/
#lang = en
# tty in use
#tty = 2
# CMatrix animation foreground color id
cmatrix_fg = 0x0003
# Console path
#console_dev = /dev/console
console_dev = /dev/console
# Default path
#path = /sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin
# Input box active by default on startup
# Available inputs: info_line, session, login, password
default_input = login
# Error background color id
error_bg = 0x0000
# Error foreground color id
# Default is red and bold: TB_RED | TB_BOLD
error_fg = 0x0102
# Foreground color id
fg = 0x0008
# Remove main box borders
hide_borders = false
# Remove power management command hints
hide_key_hints = false
# Initial text to show on the info line
# If set to null, the info line defaults to the hostname
initial_info_text = null
# Input boxes length
input_len = 34
# Active language
# Available languages are found in $CONFIG_DIRECTORY/ly/lang/
lang = en
# Load the saved desktop and username
load = true
# Command executed when logging in
# If null, no command will be executed
# Important: the code itself must end with `exec "$@"` in order to launch the session!
# You can also set environment variables in there, they'll persist until logout
login_cmd = null
# Command executed when logging out
# If null, no command will be executed
# Important: the session will already be terminated when this command is executed, so
# no need to add `exec "$@"` at the end
logout_cmd = null
# Main box horizontal margin
margin_box_h = 2
# Main box vertical margin
margin_box_v = 1
# Event timeout in milliseconds
#min_refresh_delta = 5
min_refresh_delta = 5
# Set numlock on/off at startup
numlock = false
# Default path
# If null, ly doesn't set a path
path = /sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
# Command executed when pressing restart_key
restart_cmd = /sbin/shutdown -r now
# Specifies the key used for restart (F1-F12)
restart_key = F2
# Save the current desktop and login as defaults
save = true
# Service name (set to ly to use the provided pam config file)
#service_name = ly
service_name = ly
# Terminal reset command (tput is faster)
#term_reset_cmd = /usr/bin/tput reset
# Session log file path
# This will contain stdout and stderr of X11 and Wayland sessions
# By default it's saved in the user's home directory
# Note: this file won't be used in a shell session (due to the need of stdout and stderr)
session_log = ly-session.log
# Cookie generator
#mcookie_cmd = /usr/bin/mcookie
# Setup command
setup_cmd = $CONFIG_DIRECTORY/ly/setup.sh
# Command executed when pressing shutdown_key
shutdown_cmd = /sbin/shutdown -a now
# Wayland setup command
#wayland_cmd = /etc/ly/wsetup.sh
# Specifies the key used for shutdown (F1-F12)
shutdown_key = F1
# Add wayland specifier to session names
#wayland_specifier = false
# Command executed when pressing sleep key (can be null)
sleep_cmd = null
# Specifies the key used for sleep (F1-F12)
sleep_key = F3
# Center the session name.
text_in_center = false
# TTY in use
tty = $DEFAULT_TTY
# Default vi mode
# normal -> normal mode
# insert -> insert mode
vi_default_mode = normal
# Enable vi keybindings
vi_mode = false
# Wayland desktop environments
#waylandsessions = /usr/share/wayland-sessions
# xinitrc
#xinitrc = ~/.xinitrc
waylandsessions = $PREFIX_DIRECTORY/share/wayland-sessions
# Xorg server command
#x_cmd = /usr/bin/X
# Xorg setup command
#x_cmd_setup = /etc/ly/xsetup.sh
x_cmd = $PREFIX_DIRECTORY/bin/X
# Xorg xauthority edition tool
#xauth_cmd = /usr/bin/xauth
xauth_cmd = $PREFIX_DIRECTORY/bin/xauth
# xinitrc
# If null, the xinitrc session will be hidden
xinitrc = ~/.xinitrc
# Xorg desktop environments
#xsessions = /usr/share/xsessions
xsessions = $PREFIX_DIRECTORY/share/xsessions

4
res/lang/cat.ini Executable file → Normal file
View File

@@ -34,12 +34,12 @@ 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
f1 = F1 aturar
f2 = F2 reiniciar
login = iniciar sessió
logout = tancar sessió
numlock = Bloq Num
password = Clau
restart = reiniciar
shell = shell
shutdown = aturar
wayland = wayland
xinitrc = xinitrc

View File

@@ -2,7 +2,7 @@ capslock = capslock
err_alloc = alokace paměti selhala
err_bounds = index je mimo hranice pole
err_chdir = nelze otevřít domovský adresář
err_console_dev = chyba při přístupi do konzole
err_console_dev = chyba při přístupu do konzole
err_dgn_oob = zpráva protokolu
err_domain = neplatná doména
err_hostname = nelze získat název hostitele
@@ -34,12 +34,12 @@ 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í
f1 = F1 vypnout
f2 = F2 restartovat
login = uživatel
logout = odhlášen
numlock = numlock
password = heslo
restart = restartovat
shell = příkazový řádek
shutdown = vypnout
wayland = wayland
xinitrc = xinitrc

View File

@@ -34,12 +34,12 @@ 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
f1 = F1 Herunterfahren
f2 = F2 Neustarten
login = Anmelden
logout = Abgemeldet
numlock = Numtaste
password = Passwort
restart = Neustarten
shell = shell
shutdown = Herunterfahren
wayland = wayland
xinitrc = xinitrc

View File

@@ -1,13 +1,19 @@
authenticating = authenticating...
brightness_down = decrease brightness
brightness_up = increase brightness
capslock = capslock
err_alloc = failed memory allocation
err_bounds = out-of-bounds index
err_brightness_change = failed to change brightness
err_chdir = failed to open home folder
err_console_dev = failed to access console
err_dgn_oob = log message
err_domain = invalid domain
err_envlist = failed to get envlist
err_hostname = failed to get hostname
err_mlock = failed to lock password memory
err_null = null pointer
err_numlock = failed to set numlock
err_pam = pam transaction failed
err_pam_abort = pam transaction aborted
err_pam_acct_expired = account expired
@@ -29,17 +35,25 @@ 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_xauth = xauth command failed
err_xcb_conn = xcb connection failed
err_xsessions_dir = failed to find sessions folder
err_xsessions_open = failed to open sessions folder
f1 = F1 shutdown
f2 = F2 reboot
insert = insert
login = login
logout = logged out
normal = normal
no_x11_support = x11 support disabled at compile-time
numlock = numlock
password = password
restart = reboot
shell = shell
shutdown = shutdown
sleep = sleep
wayland = wayland
xinitrc = xinitrc
x11 = x11

View File

@@ -1,3 +1,6 @@
authenticating = autenticando...
brightness_down = bajar brillo
brightness_up = subir brillo
capslock = Bloq Mayús
err_alloc = asignación de memoria fallida
err_bounds = índice fuera de límites
@@ -34,12 +37,17 @@ 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 sesión
insert = insertar
login = usuario
logout = cerrar sesión
no_x11_support = soporte para x11 deshabilitado en tiempo de compilación
normal = normal
numlock = Bloq Num
other = otro
password = contraseña
restart = reiniciar
shell = shell
shutdown = apagar
sleep = suspender
wayland = wayland
xinitrc = xinitrc

View File

@@ -1,19 +1,25 @@
capslock = verr.maj
authenticating = authentification...
brightness_down = diminuer la luminosité
brightness_up = augmenter la luminosité
capslock = verr.maj
err_alloc = échec d'allocation mémoire
err_bounds = indice hors-limite
err_brightness_change = échec du changement de luminosité
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_envlist = échec de lecture de la liste d'environnement
err_hostname = échec de lecture du nom d'hôte
err_mlock = échec du verrouillage mémoire
err_null = pointeur null
err_numlock = échec de modification du verr.num
err_pam = échec de la transaction pam
err_pam_abort = transaction pam avortée
err_pam_acct_expired = compte expiré
err_pam_auth = erreur d'authentification
err_pam_authok_reqd = tiquet expiré
err_pam_authinfo_unavail = échec de l'obtention des infos utilisateur
err_pam_authok_reqd = tiquet expiré
err_pam_buf = erreur de mémoire tampon
err_pam_cred_err = échec de la modification des identifiants
err_pam_cred_expired = identifiants expirés
@@ -29,17 +35,25 @@ 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 lecture des infos utilisateur
err_unknown = une erreur inconnue est survenue
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_xauth = échec de la commande xauth
err_xcb_conn = échec de la connexion xcb
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
insert = insertion
login = identifiant
logout = déconnection
logout = déconnecté
normal = normal
no_x11_support = support pour x11 désactivé lors de la compilation
numlock = verr.num
password = mot de passe
restart = redémarrer
shell = shell
shutdown = éteindre
sleep = veille
wayland = wayland
xinitrc = xinitrc
x11 = x11

View File

@@ -34,12 +34,12 @@ 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
f1 = F1 arresto
f2 = F2 riavvio
login = username
logout = scollegato
numlock = numlock
password = password
restart = riavvio
shell = shell
shutdown = arresto
wayland = wayland
xinitrc = xinitrc

View File

@@ -34,12 +34,12 @@ 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
f1 = F1 wyłącz
f2 = F2 uruchom ponownie
login = login
logout = wylogowano
numlock = numlock
password = hasło
restart = uruchom ponownie
shell = powłoka
shutdown = wyłącz
wayland = wayland
xinitrc = xinitrc

View File

@@ -34,12 +34,12 @@ 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
f1 = F1 encerrar
f2 = F2 reiniciar
login = iniciar sessão
logout = terminar sessão
numlock = numlock
password = palavra-passe
restart = reiniciar
shell = shell
shutdown = encerrar
wayland = wayland
xinitrc = xinitrc

View File

@@ -34,12 +34,12 @@ 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
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
logout = opreşte sesiunea
numlock = numlock
password = parolă
restart = resetează
shell = shell
shutdown = opreşte sistemul
wayland = wayland
xinitrc = xinitrc

View File

@@ -34,12 +34,12 @@ err_user_init = не удалось инициализировать польз
err_user_uid = не удалось установить UID пользователя
err_xsessions_dir = не удалось найти сессионную папку
err_xsessions_open = не удалось открыть сессионную папку
f1 = F1 выключить
f2 = F2 перезагрузить
login = логин
logout = logged out
numlock = numlock
password = пароль
restart = перезагрузить
shell = shell
shutdown = выключить
wayland = wayland
xinitrc = xinitrc

View File

@@ -34,12 +34,12 @@ 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
f1 = F1 ugasi
f2 = F2 ponovo pokreni
login = korisnik
logout = izlogovan
numlock = numlock
password = lozinka
restart = ponovo pokreni
shell = shell
shutdown = ugasi
wayland = wayland
xinitrc = xinitrc

View File

@@ -34,12 +34,12 @@ 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
f1 = F1 stäng av
f2 = F2 starta om
login = inloggad
login = inloggning
logout = utloggad
numlock = numlock
password = lösenord
restart = starta om
shell = skal
shutdown = stäng av
wayland = wayland
xinitrc = xinitrc

View File

@@ -34,12 +34,12 @@ 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
f1 = F1 makineyi kapat
f2 = F2 yeniden baslat
login = kullanici
logout = oturumdan cikis yapildi
numlock = numlock
password = sifre
restart = yeniden baslat
shell = shell
shutdown = makineyi kapat
wayland = wayland
xinitrc = xinitrc

View File

@@ -34,12 +34,12 @@ err_user_init = не вдалося ініціалізувати користу
err_user_uid = не вдалося змінити UID користувача
err_xsessions_dir = не вдалося знайти каталог сесій
err_xsessions_open = не вдалося відкрити каталог сесій
f1 = F1 вимкнути
f2 = F2 перезавантажити
login = логін
logout = вийти
numlock = numlock
password = пароль
restart = перезавантажити
shell = оболонка
shutdown = вимкнути
wayland = wayland
xinitrc = xinitrc

8
res/ly-dinit Normal file
View File

@@ -0,0 +1,8 @@
type = process
restart = true
smooth-recovery = true
command = $PREFIX_DIRECTORY/bin/$EXE_NAME
depends-on = loginready
termsignal = HUP
# ly needs access to the console while loginready already occupies it
options = shares-console

View File

@@ -20,16 +20,16 @@ then
fi
## Get the tty from the conf file
CONFTTY=$(cat /etc/ly/config.ini | sed -n 's/^tty.*=[^1-9]*// p')
CONFTTY=$(cat $CONFIG_DIRECTORY/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}"
# If CONFTTY is empty then default to $DEFAULT_TTY
TTY="tty${CONFTTY:-$DEFAULT_TTY}"
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"
command_args_foreground="-nl $PREFIX_DIRECTORY/bin/$EXECUTABLE_NAME $TTY $BAUD $TERM"
depend() {
after agetty

View File

@@ -8,5 +8,5 @@ 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}
auxtty=$(/bin/cat $CONFIG_DIRECTORY/ly/config.ini 2>/dev/null 1| /bin/sed -n 's/\(^[[:space:]]*tty[[:space:]]*=[[:space:]]*\)\([[:digit:]][[:digit:]]*\)\(.*\)/\2/p')
TTY=tty${auxtty:-$DEFAULT_TTY}

View File

@@ -10,4 +10,4 @@ elif [ -x /sbin/agetty -o -x /bin/agetty ]; then
GETTY=agetty
fi
exec setsid ${GETTY} ${GETTY_ARGS} -nl /usr/bin/ly "${TTY}" "${BAUD_RATE}" "${TERM_NAME}"
exec setsid ${GETTY} ${GETTY_ARGS} -nl $PREFIX_DIRECTORY/bin/$EXECUTABLE_NAME "${TTY}" "${BAUD_RATE}" "${TERM_NAME}"

2
res/ly-s6/run Normal file
View File

@@ -0,0 +1,2 @@
#!/bin/execlineb -P
exec agetty -L -8 -n -l $PREFIX_DIRECTORY/bin/$EXE_NAME tty$DEFAULT_TTY 115200

1
res/ly-s6/type Normal file
View File

@@ -0,0 +1 @@
longrun

View File

@@ -1,13 +1,14 @@
[Unit]
Description=TUI display manager
After=systemd-user-sessions.service plymouth-quit-wait.service
After=getty@tty2.service
After=getty@tty$DEFAULT_TTY.service
Conflicts=getty@tty$DEFAULT_TTY.service
[Service]
Type=idle
ExecStart=/usr/bin/ly
ExecStart=$PREFIX_DIRECTORY/bin/$EXECUTABLE_NAME
StandardInput=tty
TTYPath=/dev/tty2
TTYPath=/dev/tty$DEFAULT_TTY
TTYReset=yes
TTYVHangup=yes

View File

@@ -1,6 +1,18 @@
#%PAM-1.0
auth sufficient pam_unix.so try_first_pass likeauth nullok # empty-password will not pass this, but will not fail causing the next line to get executed
-auth sufficient pam_fprintd.so # We do not want to get errors when pam_fprintd.so is not present
auth include login
-auth optional pam_gnome_keyring.so
-auth optional pam_kwallet5.so
account include login
password include login
-password optional pam_gnome_keyring.so use_authtok
-session optional pam_systemd.so class=greeter
-session optional pam_elogind.so
session include login
-session optional pam_gnome_keyring.so auto_start
-session optional pam_kwallet5.so auto_start

107
res/setup.sh Executable file
View File

@@ -0,0 +1,107 @@
#!/bin/sh
# Shell environment setup after login
# Copyright (C) 2015-2016 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
# This file is extracted from kde-workspace (kdm/kfrontend/genkdmconf.c)
# Copyright (C) 2001-2005 Oswald Buddenhagen <ossi@kde.org>
# Copyright (C) 2024 The Fairy Glade
# This work is free. You can redistribute it and/or modify it under the
# terms of the Do What The Fuck You Want To Public License, Version 2,
# as published by Sam Hocevar. See the LICENSE file for more details.
# Note that the respective logout scripts are not sourced.
case $SHELL in
*/bash)
[ -z "$BASH" ] && exec $SHELL "$0" "$@"
set +o posix
[ -f "$CONFIG_DIRECTORY"/profile ] && . "$CONFIG_DIRECTORY"/profile
if [ -f "$HOME"/.bash_profile ]; then
. "$HOME"/.bash_profile
elif [ -f "$HOME"/.bash_login ]; then
. "$HOME"/.bash_login
elif [ -f "$HOME"/.profile ]; then
. "$HOME"/.profile
fi
;;
*/zsh)
[ -z "$ZSH_NAME" ] && exec $SHELL "$0" "$@"
[ -d "$CONFIG_DIRECTORY"/zsh ] && zdir="$CONFIG_DIRECTORY"/zsh || zdir="$CONFIG_DIRECTORY"
zhome=${ZDOTDIR:-"$HOME"}
# zshenv is always sourced automatically.
[ -f "$zdir"/zprofile ] && . "$zdir"/zprofile
[ -f "$zhome"/.zprofile ] && . "$zhome"/.zprofile
[ -f "$zdir"/zlogin ] && . "$zdir"/zlogin
[ -f "$zhome"/.zlogin ] && . "$zhome"/.zlogin
emulate -R sh
;;
*/csh|*/tcsh)
# [t]cshrc is always sourced automatically.
# Note that sourcing csh.login after .cshrc is non-standard.
sess_tmp=$(mktemp /tmp/sess-env-XXXXXX)
$SHELL -c "if (-f $CONFIG_DIRECTORY/csh.login) source $CONFIG_DIRECTORY/csh.login; if (-f ~/.login) source ~/.login; /bin/sh -c 'export -p' >! $sess_tmp"
. "$sess_tmp"
rm -f "$sess_tmp"
;;
*/fish)
[ -f "$CONFIG_DIRECTORY"/profile ] && . "$CONFIG_DIRECTORY"/profile
[ -f "$HOME"/.profile ] && . "$HOME"/.profile
sess_tmp=$(mktemp /tmp/sess-env-XXXXXX)
$SHELL --login -c "/bin/sh -c 'export -p' > $sess_tmp"
. "$sess_tmp"
rm -f "$sess_tmp"
;;
*) # Plain sh, ksh, and anything we do not know.
[ -f "$CONFIG_DIRECTORY"/profile ] && . "$CONFIG_DIRECTORY"/profile
[ -f "$HOME"/.profile ] && . "$HOME"/.profile
;;
esac
if [ "$XDG_SESSION_TYPE" = "x11" ]; then
[ -f "$CONFIG_DIRECTORY"/xprofile ] && . "$CONFIG_DIRECTORY"/xprofile
[ -f "$HOME"/.xprofile ] && . "$HOME"/.xprofile
# run all system xinitrc shell scripts.
if [ -d "$CONFIG_DIRECTORY"/X11/xinit/xinitrc.d ]; then
for i in "$CONFIG_DIRECTORY"/X11/xinit/xinitrc.d/* ; do
if [ -x "$i" ]; then
. "$i"
fi
done
fi
# Load Xsession scripts
# OPTIONFILE, USERXSESSION, USERXSESSIONRC and ALTUSERXSESSION are required
# by the scripts to work
xsessionddir="$CONFIG_DIRECTORY"/X11/Xsession.d
export OPTIONFILE="$CONFIG_DIRECTORY"/X11/Xsession.options
export USERXSESSION="$HOME"/.xsession
export USERXSESSIONRC="$HOME"/.xsessionrc
export ALTUSERXSESSION="$HOME"/.Xsession
if [ -d "$xsessionddir" ]; then
for i in $(ls "$xsessionddir"); do
script="$xsessionddir/$i"
echo "Loading X session script $script"
if [ -r "$script" ] && [ -f "$script" ] && expr "$i" : '^[[:alnum:]_-]\+$' > /dev/null; then
. "$script"
fi
done
fi
if [ -d "$CONFIG_DIRECTORY"/X11/Xresources ]; then
for i in "$CONFIG_DIRECTORY"/X11/Xresources/*; do
[ -f "$i" ] && xrdb -merge "$i"
done
elif [ -f "$CONFIG_DIRECTORY"/X11/Xresources ]; then
xrdb -merge "$CONFIG_DIRECTORY"/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"
fi
fi
exec "$@"

View File

@@ -1,54 +0,0 @@
#!/bin/sh
# wayland-session - run as user
# Copyright (C) 2015-2016 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
# This file is extracted from kde-workspace (kdm/kfrontend/genkdmconf.c)
# Copyright (C) 2001-2005 Oswald Buddenhagen <ossi@kde.org>
# Note that the respective logout scripts are not sourced.
case $SHELL in
*/bash)
[ -z "$BASH" ] && exec $SHELL $0 "$@"
set +o posix
[ -f /etc/profile ] && . /etc/profile
if [ -f $HOME/.bash_profile ]; then
. $HOME/.bash_profile
elif [ -f $HOME/.bash_login ]; then
. $HOME/.bash_login
elif [ -f $HOME/.profile ]; then
. $HOME/.profile
fi
;;
*/zsh)
[ -z "$ZSH_NAME" ] && exec $SHELL $0 "$@"
[ -d /etc/zsh ] && zdir=/etc/zsh || zdir=/etc
zhome=${ZDOTDIR:-$HOME}
# zshenv is always sourced automatically.
[ -f $zdir/zprofile ] && . $zdir/zprofile
[ -f $zhome/.zprofile ] && . $zhome/.zprofile
[ -f $zdir/zlogin ] && . $zdir/zlogin
[ -f $zhome/.zlogin ] && . $zhome/.zlogin
emulate -R sh
;;
*/csh|*/tcsh)
# [t]cshrc is always sourced automatically.
# Note that sourcing csh.login after .cshrc is non-standard.
wlsess_tmp=`mktemp /tmp/wlsess-env-XXXXXX`
$SHELL -c "if (-f /etc/csh.login) source /etc/csh.login; if (-f ~/.login) source ~/.login; /bin/sh -c 'export -p' >! $wlsess_tmp"
. $wlsess_tmp
rm -f $wlsess_tmp
;;
*/fish)
[ -f /etc/profile ] && . /etc/profile
xsess_tmp=`mktemp /tmp/xsess-env-XXXXXX`
$SHELL --login -c "/bin/sh -c 'export -p' > $xsess_tmp"
. $xsess_tmp
rm -f $xsess_tmp
;;
*) # Plain sh, ksh, and anything we do not know.
[ -f /etc/profile ] && . /etc/profile
[ -f $HOME/.profile ] && . $HOME/.profile
;;
esac
exec "$@"

View File

@@ -1,103 +0,0 @@
#! /bin/sh
# Xsession - run as user
# Copyright (C) 2016 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
# This file is extracted from kde-workspace (kdm/kfrontend/genkdmconf.c)
# Copyright (C) 2001-2005 Oswald Buddenhagen <ossi@kde.org>
# Note that the respective logout scripts are not sourced.
case $SHELL in
*/bash)
[ -z "$BASH" ] && exec $SHELL $0 "$@"
set +o posix
[ -f /etc/profile ] && . /etc/profile
if [ -f $HOME/.bash_profile ]; then
. $HOME/.bash_profile
elif [ -f $HOME/.bash_login ]; then
. $HOME/.bash_login
elif [ -f $HOME/.profile ]; then
. $HOME/.profile
fi
;;
*/zsh)
[ -z "$ZSH_NAME" ] && exec $SHELL $0 "$@"
[ -d /etc/zsh ] && zdir=/etc/zsh || zdir=/etc
zhome=${ZDOTDIR:-$HOME}
# zshenv is always sourced automatically.
[ -f $zdir/zprofile ] && . $zdir/zprofile
[ -f $zhome/.zprofile ] && . $zhome/.zprofile
[ -f $zdir/zlogin ] && . $zdir/zlogin
[ -f $zhome/.zlogin ] && . $zhome/.zlogin
emulate -R sh
;;
*/csh|*/tcsh)
# [t]cshrc is always sourced automatically.
# Note that sourcing csh.login after .cshrc is non-standard.
xsess_tmp=`mktemp /tmp/xsess-env-XXXXXX`
$SHELL -c "if (-f /etc/csh.login) source /etc/csh.login; if (-f ~/.login) source ~/.login; /bin/sh -c 'export -p' >! $xsess_tmp"
. $xsess_tmp
rm -f $xsess_tmp
;;
*/fish)
[ -f /etc/profile ] && . /etc/profile
xsess_tmp=`mktemp /tmp/xsess-env-XXXXXX`
$SHELL --login -c "/bin/sh -c 'export -p' > $xsess_tmp"
. $xsess_tmp
rm -f $xsess_tmp
;;
*) # Plain sh, ksh, and anything we do not know.
[ -f /etc/profile ] && . /etc/profile
[ -f $HOME/.profile ] && . $HOME/.profile
;;
esac
[ -f /etc/xprofile ] && . /etc/xprofile
[ -f $HOME/.xprofile ] && . $HOME/.xprofile
# run all system xinitrc shell scripts.
if [ -d /etc/X11/xinit/xinitrc.d ]; then
for i in /etc/X11/xinit/xinitrc.d/* ; do
if [ -x "$i" ]; then
. "$i"
fi
done
fi
# Load Xsession scripts
# OPTIONFILE, USERXSESSION, USERXSESSIONRC and ALTUSERXSESSION are required
# by the scripts to work
xsessionddir="/etc/X11/Xsession.d"
OPTIONFILE=/etc/X11/Xsession.options
USERXSESSION=$HOME/.xsession
USERXSESSIONRC=$HOME/.xsessionrc
ALTUSERXSESSION=$HOME/.Xsession
if [ -d "$xsessionddir" ]; then
for i in `ls $xsessionddir`; do
script="$xsessionddir/$i"
echo "Loading X session script $script"
if [ -r "$script" -a -f "$script" ] && expr "$i" : '^[[:alnum:]_-]\+$' > /dev/null; then
. "$script"
fi
done
fi
if [ -d /etc/X11/Xresources ]; then
for i in /etc/X11/Xresources/*; do
[ -f $i ] && xrdb -merge $i
done
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"
fi
if [ -z "$*" ]; then
exec xmessage -center -buttons OK:0 -default OK "Sorry, $DESKTOP_SESSION is no valid session."
else
exec $@
fi

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

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

@@ -0,0 +1,92 @@
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 = [_]utils.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 - @min(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] = toTermboxCell(FIRE[buffer_dest]);
self.terminal_buffer.buffer[source] = toTermboxCell(FIRE[buffer_source]);
}
}
}
fn initBuffer(buffer: []u8, width: usize) 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);
}
fn toTermboxCell(cell: utils.Cell) termbox.tb_cell {
return .{
.ch = cell.ch,
.fg = cell.fg,
.bg = cell.bg,
};
}

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

@@ -0,0 +1,184 @@
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,
fg_ini: u16,
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg_ini: u16) !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,
.fg_ini = fg_ini,
};
}
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: usize = 0;
while (x < self.terminal_buffer.width) : (x += 2) {
var tail: usize = 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: usize = 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: usize = 0;
while (x < buf_width) : (x += 2) {
var y: usize = 1;
while (y <= self.terminal_buffer.height) : (y += 1) {
const dot = self.dots[buf_width * y + x];
var fg = self.fg_ini;
if (dot.value == -1 or dot.value == ' ') {
_ = termbox.tb_set_cell(@intCast(x), @intCast(y - 1), ' ', fg, termbox.TB_DEFAULT);
continue;
}
if (dot.is_head) fg = @intCast(termbox.TB_WHITE | termbox.TB_BOLD);
_ = termbox.tb_set_cell(@intCast(x), @intCast(y - 1), @intCast(dot.value), fg, termbox.TB_DEFAULT);
}
}
}
fn initBuffers(dots: []Dot, lines: []Line, width: usize, height: usize, random: Random) void {
var y: usize = 0;
while (y <= height) : (y += 1) {
var x: usize = 0;
while (x < width) : (x += 2) {
dots[y * width + x].value = -1;
}
}
var x: usize = 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 = ' ';
}
}

599
src/auth.zig Normal file
View File

@@ -0,0 +1,599 @@
const std = @import("std");
const build_options = @import("build_options");
const builtin = @import("builtin");
const enums = @import("enums.zig");
const interop = @import("interop.zig");
const TerminalBuffer = @import("tui/TerminalBuffer.zig");
const Session = @import("tui/components/Session.zig");
const Text = @import("tui/components/Text.zig");
const Config = @import("config/Config.zig");
const Allocator = std.mem.Allocator;
const Md5 = std.crypto.hash.Md5;
const utmp = interop.utmp;
const Utmp = utmp.utmpx;
const SharedError = @import("SharedError.zig");
// When setting the currentLogin you must deallocate the previous currentLogin first
pub var currentLogin: ?[:0]const u8 = null;
pub var asyncPamHandle: ?*interop.pam.pam_handle = null;
pub var current_environment: ?Session.Environment = null;
fn environment_equals(e0: Session.Environment, e1: Session.Environment) bool {
if (!std.mem.eql(u8, e0.cmd, e1.cmd)) {
return false;
}
if (!std.mem.eql(u8, e0.name, e1.name)) {
return false;
}
if (!std.mem.eql(u8, e0.specifier, e1.specifier)) {
return false;
}
if (!(e0.xdg_desktop_names == null and e1.xdg_desktop_names == null) or
(e0.xdg_desktop_names != null and e1.xdg_desktop_names != null and !std.mem.eql(u8, e0.xdg_desktop_names.?, e1.xdg_desktop_names.?)))
{
return false;
}
if (!(e0.xdg_session_desktop == null and e1.xdg_session_desktop == null) or
(e0.xdg_session_desktop != null and e1.xdg_session_desktop != null and !std.mem.eql(u8, e0.xdg_session_desktop.?, e1.xdg_session_desktop.?)))
{
return false;
}
if (e0.display_server != e1.display_server) {
return false;
}
return true;
}
pub fn automaticLogin(config: Config, login: [:0]const u8, environment: Session.Environment, wakesem: *std.Thread.Semaphore) !void {
while (asyncPamHandle == null and currentLogin != null and std.mem.eql(u8, currentLogin.?, login)) {
if (authenticate(config, login, "", environment)) |handle| {
if (currentLogin != null and !std.mem.eql(u8, currentLogin.?, login) and environment_equals(current_environment.?, environment)) {
return;
}
asyncPamHandle = handle;
wakesem.post();
return;
} else |_| {}
}
}
pub fn startAutomaticLogin(allocator: std.mem.Allocator, config: Config, login: Text, environment: Session.Environment, wakesem: *std.Thread.Semaphore) !void {
if (currentLogin) |clogin| {
allocator.free(clogin);
currentLogin = null;
}
const login_text = try allocator.dupeZ(u8, login.text.items);
currentLogin = login_text;
var handle = try std.Thread.spawn(.{}, automaticLogin, .{
config,
login_text,
environment,
wakesem,
});
handle.detach();
}
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,
login: [:0]const u8,
password: [:0]const u8,
environment: Session.Environment,
) !*interop.pam.pam_handle {
var pam_tty_buffer: [6]u8 = undefined;
const pam_tty_str = try std.fmt.bufPrintZ(&pam_tty_buffer, "tty{d}", .{config.tty});
var tty_buffer: [2]u8 = undefined;
const tty_str = try std.fmt.bufPrintZ(&tty_buffer, "{d}", .{config.tty});
// Set the XDG environment variables
setXdgSessionEnv(environment.display_server);
try setXdgEnv(tty_str, environment.xdg_session_desktop orelse "", 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;
// Do the PAM routine
var status = interop.pam.pam_start(config.service_name, null, &conv, &handle);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
errdefer _ = interop.pam.pam_end(handle, status);
// Set PAM_TTY as the current TTY. This is required in case it isn't being set by another PAM module
status = interop.pam.pam_set_item(handle, interop.pam.PAM_TTY, pam_tty_str.ptr);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
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);
errdefer _ = interop.pam.pam_end(handle, status);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
return handle.?;
}
pub fn finaliseAuth(config: Config, environment: Session.Environment, handle: ?*interop.pam.pam_handle, login: [:0]const u8) !void {
var status: c_int = undefined;
defer status = interop.pam.pam_end(handle, status);
defer status = interop.pam.pam_setcred(handle, interop.pam.PAM_DELETE_CRED);
// Open the PAM session
status = interop.pam.pam_open_session(handle, 0);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
defer status = interop.pam.pam_close_session(handle, status);
var pwd: *interop.pwd.passwd = undefined;
{
defer interop.pwd.endpwent();
// Get password structure from username
pwd = interop.pwd.getpwnam(login) orelse return error.GetPasswordNameFailed;
}
// Set user shell if it hasn't already been set
if (pwd.pw_shell == null) {
interop.unistd.setusershell();
pwd.pw_shell = interop.unistd.getusershell();
interop.unistd.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, environment) catch |e| {
shared_err.writeError(e);
std.process.exit(1);
};
std.process.exit(0);
}
var entry = std.mem.zeroes(Utmp);
{
// If an error occurs here, we can send SIGTERM to the session
errdefer cleanup: {
_ = std.posix.kill(child_pid, std.posix.SIG.TERM) catch break :cleanup;
_ = std.posix.waitpid(child_pid, 0);
}
// If we receive SIGTERM, forward it to child_pid
const act = std.posix.Sigaction{
.handler = .{ .handler = &sessionSignalHandler },
.mask = std.posix.empty_sigset,
.flags = 0,
};
try std.posix.sigaction(std.posix.SIG.TERM, &act, null);
try addUtmpEntry(&entry, pwd.pw_name.?, child_pid);
}
// Wait for the session to stop
_ = std.posix.waitpid(child_pid, 0);
removeUtmpEntry(&entry);
if (shared_err.readError()) |err| return err;
}
fn startSession(
config: Config,
pwd: *interop.pwd.passwd,
handle: ?*interop.pam.pam_handle,
environment: Session.Environment,
) !void {
if (builtin.os.tag == .freebsd) {
// FreeBSD has initgroups() in unistd
const status = interop.unistd.initgroups(pwd.pw_name, pwd.pw_gid);
if (status != 0) return error.GroupInitializationFailed;
// FreeBSD sets the GID and UID with setusercontext()
const result = interop.pwd.setusercontext(null, pwd, pwd.pw_uid, interop.pwd.LOGIN_SETALL);
if (result != 0) return error.SetUserUidFailed;
} else {
const status = interop.grp.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: ?[*:null]?[*:0]u8 = interop.pam.pam_getenvlist(handle);
if (pam_env_vars == null) return error.GetEnvListFailed;
const env_list = std.mem.span(pam_env_vars.?);
for (env_list) |env_var| _ = interop.stdlib.putenv(env_var);
// Change to the user's home directory
std.posix.chdirZ(pwd.pw_dir.?) catch return error.ChangeDirectoryFailed;
// Execute what the user requested
switch (environment.display_server) {
.wayland => try executeWaylandCmd(pwd.pw_shell.?, config, environment.cmd),
.shell => try executeShellCmd(pwd.pw_shell.?, config),
.xinitrc, .x11 => if (build_options.enable_x11_support) {
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, environment.cmd, vt);
},
}
}
fn initEnv(pwd: *interop.pwd.passwd, path_env: ?[:0]const u8) !void {
_ = interop.stdlib.setenv("HOME", pwd.pw_dir, 1);
_ = interop.stdlib.setenv("PWD", pwd.pw_dir, 1);
_ = interop.stdlib.setenv("SHELL", pwd.pw_shell, 1);
_ = interop.stdlib.setenv("USER", pwd.pw_name, 1);
_ = interop.stdlib.setenv("LOGNAME", pwd.pw_name, 1);
if (path_env) |path| {
const status = interop.stdlib.setenv("PATH", path, 1);
if (status != 0) return error.SetPathFailed;
}
}
fn setXdgSessionEnv(display_server: enums.DisplayServer) void {
_ = interop.stdlib.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 {
// The "/run/user/%d" directory is not available on FreeBSD. It is much
// better to stick to the defaults and let applications using
// XDG_RUNTIME_DIR to fall back to directories inside user's home
// directory.
if (builtin.os.tag != .freebsd) {
const uid = interop.unistd.getuid();
var uid_buffer: [10 + @sizeOf(u32) + 1]u8 = undefined;
const uid_str = try std.fmt.bufPrintZ(&uid_buffer, "/run/user/{d}", .{uid});
_ = interop.stdlib.setenv("XDG_RUNTIME_DIR", uid_str, 0);
}
_ = interop.stdlib.setenv("XDG_CURRENT_DESKTOP", xdg_desktop_names, 0);
_ = interop.stdlib.setenv("XDG_SESSION_CLASS", "user", 0);
_ = interop.stdlib.setenv("XDG_SESSION_ID", "1", 0);
_ = interop.stdlib.setenv("XDG_SESSION_DESKTOP", desktop_name, 0);
_ = interop.stdlib.setenv("XDG_SEAT", "seat0", 0);
_ = interop.stdlib.setenv("XDG_VTNR", tty_str, 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
@memset(response, 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.?;
},
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.?;
},
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 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 mcookie() [Md5.digest_length * 2]u8 {
var buf: [4096]u8 = undefined;
std.crypto.random.bytes(&buf);
var out: [Md5.digest_length]u8 = undefined;
Md5.hash(&buf, &out, .{});
return std.fmt.bytesToHex(&out, .lower);
}
fn xauth(display_name: [:0]u8, shell: [*:0]const u8, pw_dir: [*:0]const u8, config: Config) !void {
var pwd_buf: [100]u8 = undefined;
const pwd = try std.fmt.bufPrintZ(&pwd_buf, "{s}", .{pw_dir});
const xauthority = try createXauthFile(pwd);
_ = interop.stdlib.setenv("XAUTHORITY", xauthority, 1);
_ = interop.stdlib.setenv("DISPLAY", display_name, 1);
const magic_cookie = mcookie();
const pid = try std.posix.fork();
if (pid == 0) {
const log_file = try redirectStandardStreams(config.session_log, true);
defer log_file.close();
var cmd_buffer: [1024]u8 = undefined;
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} add {s} . {s}", .{ config.xauth_cmd, display_name, magic_cookie }) 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);
}
const status = std.posix.waitpid(pid, 0);
if (status.status != 0) return error.XauthFailed;
}
fn executeShellCmd(shell: [*:0]const u8, config: Config) !void {
// We don't want to redirect stdout and stderr in a shell session
var cmd_buffer: [1024]u8 = undefined;
const cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ config.setup_cmd, config.login_cmd orelse "", shell });
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
return std.posix.execveZ(shell, &args, std.c.environ);
}
fn executeWaylandCmd(shell: [*:0]const u8, config: Config, desktop_cmd: []const u8) !void {
const log_file = try redirectStandardStreams(config.session_log, true);
defer log_file.close();
var cmd_buffer: [1024]u8 = undefined;
const cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ config.setup_cmd, config.login_cmd orelse "", 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);
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} >{s} 2>&1", .{ config.x_cmd, display_name, vt, config.session_log }) 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 error.XcbConnectionFailed;
};
}
// 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} {s} >{s} 2>&1", .{ config.setup_cmd, config.login_cmd orelse "", desktop_cmd, config.session_log }) 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 redirectStandardStreams(session_log: []const u8, create: bool) !std.fs.File {
const log_file = if (create) (try std.fs.cwd().createFile(session_log, .{ .mode = 0o666 })) else (try std.fs.cwd().openFile(session_log, .{ .mode = .read_write }));
try std.posix.dup2(std.posix.STDOUT_FILENO, std.posix.STDERR_FILENO);
try std.posix.dup2(log_file.handle, std.posix.STDOUT_FILENO);
return log_file;
}
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(std.posix.STDIN_FILENO, &buf);
var ttyname_buf: [@sizeOf(@TypeOf(entry.ut_line))]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: [@sizeOf(@TypeOf(entry.ut_user))]u8 = undefined;
_ = try std.fmt.bufPrintZ(&username_buf, "{s}", .{username});
entry.ut_user = username_buf;
var host: [@sizeOf(@TypeOf(entry.ut_host))]u8 = undefined;
host[0] = 0;
entry.ut_host = host;
var tv: interop.system_time.timeval = undefined;
_ = interop.system_time.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.setutxent();
_ = utmp.pututxline(entry);
utmp.endutxent();
}
fn removeUtmpEntry(entry: *Utmp) void {
entry.ut_type = utmp.DEAD_PROCESS;
entry.ut_line[0] = 0;
entry.ut_user[0] = 0;
utmp.setutxent();
_ = utmp.pututxline(entry);
utmp.endutxent();
}
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,
};
}

View File

@@ -1,146 +0,0 @@
#include <stdint.h>
#define CLOCK_W 5
#define CLOCK_H 5
#if defined(__linux__) || defined(__FreeBSD__)
#define X 0x2593
#define _ 0x0000
#else
#define X '#'
#define _ 0
#endif
#if CLOCK_W == 5 && CLOCK_H == 5
uint32_t CLOCK_0[] = {
X,X,X,X,X,
X,X,_,X,X,
X,X,_,X,X,
X,X,_,X,X,
X,X,X,X,X
};
uint32_t CLOCK_1[] = {
_,_,_,X,X,
_,_,_,X,X,
_,_,_,X,X,
_,_,_,X,X,
_,_,_,X,X
};
uint32_t CLOCK_2[] = {
X,X,X,X,X,
_,_,_,X,X,
X,X,X,X,X,
X,X,_,_,_,
X,X,X,X,X
};
uint32_t CLOCK_3[] = {
X,X,X,X,X,
_,_,_,X,X,
X,X,X,X,X,
_,_,_,X,X,
X,X,X,X,X
};
uint32_t CLOCK_4[] = {
X,X,_,X,X,
X,X,_,X,X,
X,X,X,X,X,
_,_,_,X,X,
_,_,_,X,X
};
uint32_t CLOCK_5[] = {
X,X,X,X,X,
X,X,_,_,_,
X,X,X,X,X,
_,_,_,X,X,
X,X,X,X,X
};
uint32_t CLOCK_6[] = {
X,X,X,X,X,
X,X,_,_,_,
X,X,X,X,X,
X,X,_,X,X,
X,X,X,X,X,
};
uint32_t CLOCK_7[] = {
X,X,X,X,X,
_,_,_,X,X,
_,_,_,X,X,
_,_,_,X,X,
_,_,_,X,X
};
uint32_t CLOCK_8[] = {
X,X,X,X,X,
X,X,_,X,X,
X,X,X,X,X,
X,X,_,X,X,
X,X,X,X,X
};
uint32_t CLOCK_9[] = {
X,X,X,X,X,
X,X,_,X,X,
X,X,X,X,X,
_,_,_,X,X,
X,X,X,X,X
};
uint32_t CLOCK_S[] = {
_,_,_,_,_,
_,_,X,_,_,
_,_,_,_,_,
_,_,X,_,_,
_,_,_,_,_
};
uint32_t CLOCK_E[] = {
_,_,_,_,_,
_,_,_,_,_,
_,_,_,_,_,
_,_,_,_,_,
_,_,_,_,_
};
#endif
#undef X
#undef _
static inline uint32_t* CLOCK_N(char c)
{
switch(c)
{
case '0':
return CLOCK_0;
case '1':
return CLOCK_1;
case '2':
return CLOCK_2;
case '3':
return CLOCK_3;
case '4':
return CLOCK_4;
case '5':
return CLOCK_5;
case '6':
return CLOCK_6;
case '7':
return CLOCK_7;
case '8':
return CLOCK_8;
case '9':
return CLOCK_9;
case ':':
return CLOCK_S;
default:
return CLOCK_E;
}
}

58
src/bigclock.zig Normal file
View File

@@ -0,0 +1,58 @@
const std = @import("std");
const interop = @import("interop.zig");
const utils = @import("tui/utils.zig");
const enums = @import("enums.zig");
const Lang = @import("bigclock/Lang.zig");
const en = @import("bigclock/en.zig");
const fa = @import("bigclock/fa.zig");
const termbox = interop.termbox;
const Bigclock = enums.Bigclock;
pub const WIDTH = Lang.WIDTH;
pub const HEIGHT = Lang.HEIGHT;
pub const SIZE = Lang.SIZE;
pub fn clockCell(animate: bool, char: u8, fg: u16, bg: u16, bigclock: Bigclock) [SIZE]utils.Cell {
var cells: [SIZE]utils.Cell = undefined;
var tv: interop.system_time.timeval = undefined;
_ = interop.system_time.gettimeofday(&tv, null);
const clock_chars = toBigNumber(if (animate and char == ':' and @divTrunc(tv.tv_usec, 500000) != 0) ' ' else char, bigclock);
for (0..cells.len) |i| cells[i] = utils.initCell(clock_chars[i], fg, bg);
return cells;
}
pub fn alphaBlit(x: usize, y: usize, tb_width: usize, tb_height: usize, cells: [SIZE]utils.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) utils.putCell(x + xx, y + yy, cell);
}
}
}
fn toBigNumber(char: u8, bigclock: Bigclock) []const u21 {
const locale_chars = switch (bigclock) {
.fa => fa.locale_chars,
.en => en.locale_chars,
.none => unreachable,
};
return switch (char) {
'0' => &locale_chars.ZERO,
'1' => &locale_chars.ONE,
'2' => &locale_chars.TWO,
'3' => &locale_chars.THREE,
'4' => &locale_chars.FOUR,
'5' => &locale_chars.FIVE,
'6' => &locale_chars.SIX,
'7' => &locale_chars.SEVEN,
'8' => &locale_chars.EIGHT,
'9' => &locale_chars.NINE,
':' => &locale_chars.S,
else => &locale_chars.E,
};
}

23
src/bigclock/Lang.zig Normal file
View File

@@ -0,0 +1,23 @@
const builtin = @import("builtin");
pub const WIDTH = 5;
pub const HEIGHT = 5;
pub const SIZE = WIDTH * HEIGHT;
pub const X: u32 = if (builtin.os.tag == .linux or builtin.os.tag.isBSD()) 0x2593 else '#';
pub const O: u32 = 0;
pub const LocaleChars = struct {
ZERO: [SIZE]u21,
ONE: [SIZE]u21,
TWO: [SIZE]u21,
THREE: [SIZE]u21,
FOUR: [SIZE]u21,
FIVE: [SIZE]u21,
SIX: [SIZE]u21,
SEVEN: [SIZE]u21,
EIGHT: [SIZE]u21,
NINE: [SIZE]u21,
S: [SIZE]u21,
E: [SIZE]u21,
};

94
src/bigclock/en.zig Normal file
View File

@@ -0,0 +1,94 @@
const Lang = @import("Lang.zig");
const LocaleChars = Lang.LocaleChars;
const X = Lang.X;
const O = Lang.O;
// zig fmt: off
pub const locale_chars = LocaleChars{
.ZERO = [_]u21{
X,X,X,X,X,
X,X,O,X,X,
X,X,O,X,X,
X,X,O,X,X,
X,X,X,X,X,
},
.ONE = [_]u21{
O,O,O,X,X,
O,O,O,X,X,
O,O,O,X,X,
O,O,O,X,X,
O,O,O,X,X,
},
.TWO = [_]u21{
X,X,X,X,X,
O,O,O,X,X,
X,X,X,X,X,
X,X,O,O,O,
X,X,X,X,X,
},
.THREE = [_]u21{
X,X,X,X,X,
O,O,O,X,X,
X,X,X,X,X,
O,O,O,X,X,
X,X,X,X,X,
},
.FOUR = [_]u21{
X,X,O,X,X,
X,X,O,X,X,
X,X,X,X,X,
O,O,O,X,X,
O,O,O,X,X,
},
.FIVE = [_]u21{
X,X,X,X,X,
X,X,O,O,O,
X,X,X,X,X,
O,O,O,X,X,
X,X,X,X,X,
},
.SIX = [_]u21{
X,X,X,X,X,
X,X,O,O,O,
X,X,X,X,X,
X,X,O,X,X,
X,X,X,X,X,
},
.SEVEN = [_]u21{
X,X,X,X,X,
O,O,O,X,X,
O,O,O,X,X,
O,O,O,X,X,
O,O,O,X,X,
},
.EIGHT = [_]u21{
X,X,X,X,X,
X,X,O,X,X,
X,X,X,X,X,
X,X,O,X,X,
X,X,X,X,X,
},
.NINE = [_]u21{
X,X,X,X,X,
X,X,O,X,X,
X,X,X,X,X,
O,O,O,X,X,
X,X,X,X,X,
},
.S = [_]u21{
O,O,O,O,O,
O,O,X,O,O,
O,O,O,O,O,
O,O,X,O,O,
O,O,O,O,O,
},
.E = [_]u21{
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

94
src/bigclock/fa.zig Normal file
View File

@@ -0,0 +1,94 @@
const Lang = @import("Lang.zig");
const LocaleChars = Lang.LocaleChars;
const X = Lang.X;
const O = Lang.O;
// zig fmt: off
pub const locale_chars = LocaleChars{
.ZERO = [_]u21{
O,O,O,O,O,
O,O,X,O,O,
O,X,O,X,O,
O,O,X,O,O,
O,O,O,O,O,
},
.ONE = [_]u21{
O,O,X,O,O,
O,X,X,O,O,
O,O,X,O,O,
O,O,X,O,O,
O,O,X,O,O,
},
.TWO = [_]u21{
O,X,O,X,O,
O,X,X,X,O,
O,X,O,O,O,
O,X,O,O,O,
O,X,O,O,O,
},
.THREE = [_]u21{
X,O,X,O,X,
X,X,X,X,X,
X,O,O,O,O,
X,O,O,O,O,
X,O,O,O,O,
},
.FOUR = [_]u21{
O,X,O,X,X,
O,X,X,O,O,
O,X,X,X,X,
O,X,O,O,O,
O,X,O,O,O,
},
.FIVE = [_]u21{
O,O,X,X,O,
O,X,O,O,X,
X,O,O,O,X,
X,O,X,O,X,
O,X,O,X,O,
},
.SIX = [_]u21{
O,X,X,O,O,
O,X,O,O,X,
O,O,X,O,O,
O,X,O,O,O,
X,O,O,O,O,
},
.SEVEN = [_]u21{
X,O,O,O,X,
X,O,O,O,X,
O,X,O,X,O,
O,X,O,X,O,
O,O,X,O,O,
},
.EIGHT = [_]u21{
O,O,O,X,O,
O,O,X,O,X,
O,O,X,O,X,
O,X,O,O,X,
O,X,O,O,X,
},
.NINE = [_]u21{
O,X,X,X,O,
O,X,O,X,O,
O,X,X,X,O,
O,O,O,X,O,
O,O,O,X,O,
},
.S = [_]u21{
O,O,O,O,O,
O,O,X,O,O,
O,O,O,O,O,
O,O,X,O,O,
O,O,O,O,O,
},
.E = [_]u21{
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

View File

@@ -1,378 +0,0 @@
#include "configator.h"
#include "config.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#ifndef DEBUG
#define INI_LANG DATADIR "/lang/%s.ini"
#define INI_CONFIG "/etc/ly/config.ini"
#else
#define INI_LANG "../res/lang/%s.ini"
#define INI_CONFIG "../res/config.ini"
#endif
static void lang_handle(void* data, char** pars, const int pars_count)
{
if (*((char**)data) != NULL)
{
free (*((char**)data));
}
*((char**)data) = strdup(*pars);
}
static void config_handle_u8(void* data, char** pars, const int pars_count)
{
if (strcmp(*pars, "") == 0)
{
*((uint8_t*)data) = 0;
}
else
{
*((uint8_t*)data) = atoi(*pars);
}
}
static void config_handle_u16(void* data, char** pars, const int pars_count)
{
if (strcmp(*pars, "") == 0)
{
*((uint16_t*)data) = 0;
}
else
{
*((uint16_t*)data) = atoi(*pars);
}
}
void config_handle_str(void* data, char** pars, const int pars_count)
{
if (*((char**)data) != NULL)
{
free(*((char**)data));
}
*((char**)data) = strdup(*pars);
}
static void config_handle_char(void* data, char** pars, const int pars_count)
{
*((char*)data) = **pars;
}
static void config_handle_bool(void* data, char** pars, const int pars_count)
{
*((bool*)data) = (strcmp("true", *pars) == 0);
}
void lang_load()
{
// must be alphabetically sorted
struct configator_param map_no_section[] =
{
{"capslock", &lang.capslock, lang_handle},
{"err_alloc", &lang.err_alloc, lang_handle},
{"err_bounds", &lang.err_bounds, lang_handle},
{"err_chdir", &lang.err_chdir, lang_handle},
{"err_console_dev", &lang.err_console_dev, lang_handle},
{"err_dgn_oob", &lang.err_dgn_oob, lang_handle},
{"err_domain", &lang.err_domain, lang_handle},
{"err_hostname", &lang.err_hostname, lang_handle},
{"err_mlock", &lang.err_mlock, lang_handle},
{"err_null", &lang.err_null, lang_handle},
{"err_pam", &lang.err_pam, lang_handle},
{"err_pam_abort", &lang.err_pam_abort, lang_handle},
{"err_pam_acct_expired", &lang.err_pam_acct_expired, lang_handle},
{"err_pam_auth", &lang.err_pam_auth, lang_handle},
{"err_pam_authinfo_unavail", &lang.err_pam_authinfo_unavail, lang_handle},
{"err_pam_authok_reqd", &lang.err_pam_authok_reqd, lang_handle},
{"err_pam_buf", &lang.err_pam_buf, lang_handle},
{"err_pam_cred_err", &lang.err_pam_cred_err, lang_handle},
{"err_pam_cred_expired", &lang.err_pam_cred_expired, lang_handle},
{"err_pam_cred_insufficient", &lang.err_pam_cred_insufficient, lang_handle},
{"err_pam_cred_unavail", &lang.err_pam_cred_unavail, lang_handle},
{"err_pam_maxtries", &lang.err_pam_maxtries, lang_handle},
{"err_pam_perm_denied", &lang.err_pam_perm_denied, lang_handle},
{"err_pam_session", &lang.err_pam_session, lang_handle},
{"err_pam_sys", &lang.err_pam_sys, lang_handle},
{"err_pam_user_unknown", &lang.err_pam_user_unknown, lang_handle},
{"err_path", &lang.err_path, lang_handle},
{"err_perm_dir", &lang.err_perm_dir, lang_handle},
{"err_perm_group", &lang.err_perm_group, lang_handle},
{"err_perm_user", &lang.err_perm_user, lang_handle},
{"err_pwnam", &lang.err_pwnam, lang_handle},
{"err_user_gid", &lang.err_user_gid, lang_handle},
{"err_user_init", &lang.err_user_init, lang_handle},
{"err_user_uid", &lang.err_user_uid, lang_handle},
{"err_xsessions_dir", &lang.err_xsessions_dir, lang_handle},
{"err_xsessions_open", &lang.err_xsessions_open, lang_handle},
{"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},
{"bigclock", &config.bigclock, config_handle_bool},
{"blank_box", &config.blank_box, config_handle_bool},
{"blank_password", &config.blank_password, config_handle_bool},
{"clock", &config.clock, config_handle_str},
{"console_dev", &config.console_dev, config_handle_str},
{"default_input", &config.default_input, config_handle_u8},
{"fg", &config.fg, config_handle_u8},
{"hide_borders", &config.hide_borders, config_handle_bool},
{"hide_f1_commands", &config.hide_f1_commands, 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},
{"xinitrc", &config.xinitrc, config_handle_str},
{"x_cmd_setup", &config.x_cmd_setup, config_handle_str},
{"xauth_cmd", &config.xauth_cmd, config_handle_str},
{"xsessions", &config.xsessions, config_handle_str},
};
uint16_t map_len[] = {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.bigclock = false;
config.blank_box = true;
config.blank_password = false;
config.clock = NULL;
config.console_dev = strdup("/dev/console");
config.default_input = LOGIN_INPUT;
config.fg = 9;
config.hide_borders = false;
config.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.xinitrc = strdup("~/.xinitrc");
config.x_cmd_setup = strdup(DATADIR "/xsetup.sh");
config.xauth_cmd = strdup("/usr/bin/xauth");
config.xsessions = strdup("/usr/share/xsessions");
}
void lang_free()
{
free(lang.capslock);
free(lang.err_alloc);
free(lang.err_bounds);
free(lang.err_chdir);
free(lang.err_console_dev);
free(lang.err_dgn_oob);
free(lang.err_domain);
free(lang.err_hostname);
free(lang.err_mlock);
free(lang.err_null);
free(lang.err_pam);
free(lang.err_pam_abort);
free(lang.err_pam_acct_expired);
free(lang.err_pam_auth);
free(lang.err_pam_authinfo_unavail);
free(lang.err_pam_authok_reqd);
free(lang.err_pam_buf);
free(lang.err_pam_cred_err);
free(lang.err_pam_cred_expired);
free(lang.err_pam_cred_insufficient);
free(lang.err_pam_cred_unavail);
free(lang.err_pam_maxtries);
free(lang.err_pam_perm_denied);
free(lang.err_pam_session);
free(lang.err_pam_sys);
free(lang.err_pam_user_unknown);
free(lang.err_path);
free(lang.err_perm_dir);
free(lang.err_perm_group);
free(lang.err_perm_user);
free(lang.err_pwnam);
free(lang.err_user_gid);
free(lang.err_user_init);
free(lang.err_user_uid);
free(lang.err_xsessions_dir);
free(lang.err_xsessions_open);
free(lang.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.clock);
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.xinitrc);
free(config.x_cmd_setup);
free(config.xauth_cmd);
free(config.xsessions);
}

View File

@@ -1,116 +0,0 @@
#ifndef H_LY_CONFIG
#define H_LY_CONFIG
#include <stdbool.h>
#include <stdint.h>
enum INPUTS {
SESSION_SWITCH,
LOGIN_INPUT,
PASSWORD_INPUT,
};
struct lang
{
char* capslock;
char* err_alloc;
char* err_bounds;
char* err_chdir;
char* err_console_dev;
char* err_dgn_oob;
char* err_domain;
char* err_hostname;
char* err_mlock;
char* err_null;
char* err_pam;
char* err_pam_abort;
char* err_pam_acct_expired;
char* err_pam_auth;
char* err_pam_authinfo_unavail;
char* err_pam_authok_reqd;
char* err_pam_buf;
char* err_pam_cred_err;
char* err_pam_cred_expired;
char* err_pam_cred_insufficient;
char* err_pam_cred_unavail;
char* err_pam_maxtries;
char* err_pam_perm_denied;
char* err_pam_session;
char* err_pam_sys;
char* err_pam_user_unknown;
char* err_path;
char* err_perm_dir;
char* err_perm_group;
char* err_perm_user;
char* err_pwnam;
char* err_user_gid;
char* err_user_init;
char* err_user_uid;
char* err_xsessions_dir;
char* err_xsessions_open;
char* f1;
char* f2;
char* login;
char* logout;
char* numlock;
char* password;
char* shell;
char* wayland;
char* xinitrc;
};
struct config
{
bool animate;
uint8_t animation;
char asterisk;
uint8_t bg;
bool bigclock;
bool blank_box;
bool blank_password;
char* clock;
char* console_dev;
uint8_t default_input;
uint8_t fg;
bool hide_borders;
bool hide_f1_commands;
uint8_t input_len;
char* lang;
bool load;
uint8_t margin_box_h;
uint8_t margin_box_v;
uint8_t max_desktop_len;
uint8_t max_login_len;
uint8_t max_password_len;
char* mcookie_cmd;
uint16_t min_refresh_delta;
char* path;
char* restart_cmd;
bool save;
char* save_file;
char* service_name;
char* shutdown_cmd;
char* term_reset_cmd;
uint8_t tty;
char* wayland_cmd;
bool wayland_specifier;
char* waylandsessions;
char* x_cmd;
char* xinitrc;
char* x_cmd_setup;
char* xauth_cmd;
char* xsessions;
};
extern struct lang lang;
extern struct config config;
void config_handle_str(void* data, char** pars, const int pars_count);
void lang_load();
void config_load(const char *cfg_path);
void lang_defaults();
void config_defaults();
void lang_free();
void config_free();
#endif

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

@@ -0,0 +1,61 @@
const build_options = @import("build_options");
const enums = @import("../enums.zig");
const Animation = enums.Animation;
const Input = enums.Input;
const ViMode = enums.ViMode;
const Bigclock = enums.Bigclock;
animation: Animation = .none,
animation_timeout_sec: u12 = 0,
asterisk: ?u8 = '*',
auth_fails: u64 = 10,
bg: u16 = 0,
bigclock: Bigclock = .none,
blank_box: bool = true,
border_fg: u16 = 8,
box_title: ?[]const u8 = null,
brightness_down_cmd: [:0]const u8 = build_options.prefix_directory ++ "/bin/brightnessctl -q s 10%-",
brightness_down_key: []const u8 = "F5",
brightness_up_cmd: [:0]const u8 = build_options.prefix_directory ++ "/bin/brightnessctl -q s +10%",
brightness_up_key: []const u8 = "F6",
clear_password: bool = false,
clock: ?[:0]const u8 = null,
cmatrix_fg: u16 = 3,
console_dev: []const u8 = "/dev/console",
default_input: Input = .login,
error_bg: u16 = 0,
error_fg: u16 = 258,
fg: u16 = 8,
hide_borders: bool = false,
hide_key_hints: bool = false,
initial_info_text: ?[]const u8 = null,
input_len: u8 = 34,
lang: []const u8 = "en",
load: bool = true,
login_cmd: ?[]const u8 = null,
logout_cmd: ?[]const u8 = null,
margin_box_h: u8 = 2,
margin_box_v: u8 = 1,
min_refresh_delta: u16 = 5,
numlock: bool = false,
path: ?[:0]const u8 = "/sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin",
restart_cmd: []const u8 = "/sbin/shutdown -r now",
restart_key: []const u8 = "F2",
save: bool = true,
service_name: [:0]const u8 = "ly",
session_log: []const u8 = "ly-session.log",
setup_cmd: []const u8 = build_options.config_directory ++ "/ly/setup.sh",
shutdown_cmd: []const u8 = "/sbin/shutdown -a now",
shutdown_key: []const u8 = "F1",
sleep_cmd: ?[]const u8 = null,
sleep_key: []const u8 = "F3",
text_in_center: bool = false,
tty: u8 = build_options.tty,
vi_default_mode: ViMode = .normal,
vi_mode: bool = false,
waylandsessions: []const u8 = build_options.prefix_directory ++ "/share/wayland-sessions",
x_cmd: []const u8 = build_options.prefix_directory ++ "/bin/X",
xauth_cmd: []const u8 = build_options.prefix_directory ++ "/bin/xauth",
xinitrc: ?[]const u8 = "~/.xinitrc",
xsessions: []const u8 = build_options.prefix_directory ++ "/share/xsessions",

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

@@ -0,0 +1,61 @@
authenticating: []const u8 = "authenticating...",
brightness_down: []const u8 = "decrease brightness",
brightness_up: []const u8 = "increase brightness",
capslock: []const u8 = "capslock",
err_alloc: []const u8 = "failed memory allocation",
err_bounds: []const u8 = "out-of-bounds index",
err_brightness_change: []const u8 = "failed to change brightness",
err_chdir: []const u8 = "failed to open home folder",
err_config: []const u8 = "unable to parse config file",
err_console_dev: []const u8 = "failed to access console",
err_dgn_oob: []const u8 = "log message",
err_domain: []const u8 = "invalid domain",
err_envlist: []const u8 = "failed to get envlist",
err_hostname: []const u8 = "failed to get hostname",
err_mlock: []const u8 = "failed to lock password memory",
err_null: []const u8 = "null pointer",
err_numlock: []const u8 = "failed to set numlock",
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_xauth: []const u8 = "xauth command failed",
err_xcb_conn: []const u8 = "xcb connection failed",
err_xsessions_dir: []const u8 = "failed to find sessions folder",
err_xsessions_open: []const u8 = "failed to open sessions folder",
insert: []const u8 = "insert",
login: []const u8 = "login:",
logout: []const u8 = "logged out",
normal: []const u8 = "normal",
no_x11_support: []const u8 = "x11 support disabled at compile-time",
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: ?usize = null,

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

@@ -0,0 +1,144 @@
// The migrator ensures compatibility with <=0.6.0 configuration files
const std = @import("std");
const ini = @import("zigini");
const Save = @import("Save.zig");
const enums = @import("../enums.zig");
var temporary_allocator = std.heap.page_allocator;
pub var maybe_animate: ?bool = null;
pub var maybe_save_file: ?[]const u8 = null;
pub var mapped_config_fields = false;
pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniField {
if (std.mem.eql(u8, field.key, "animate")) {
// The option doesn't exist anymore, but we save its value for "animation"
maybe_animate = std.mem.eql(u8, field.value, "true");
mapped_config_fields = true;
return null;
}
if (std.mem.eql(u8, field.key, "animation")) {
// The option now uses a string (which then gets converted into an enum) instead of an integer
// It also combines the previous "animate" and "animation" options
const animation = std.fmt.parseInt(u8, field.value, 10) catch return field;
var mapped_field = field;
mapped_field.value = switch (animation) {
0 => "doom",
1 => "matrix",
else => "none",
};
mapped_config_fields = true;
return mapped_field;
}
if (std.mem.eql(u8, field.key, "blank_password")) {
// The option has simply been renamed
var mapped_field = field;
mapped_field.key = "clear_password";
mapped_config_fields = true;
return mapped_field;
}
if (std.mem.eql(u8, field.key, "default_input")) {
// The option now uses a string (which then gets converted into an enum) instead of an integer
const default_input = std.fmt.parseInt(u8, field.value, 10) catch return field;
var mapped_field = field;
mapped_field.value = switch (default_input) {
0 => "session",
1 => "login",
2 => "password",
else => "login",
};
mapped_config_fields = true;
return mapped_field;
}
if (std.mem.eql(u8, field.key, "save_file")) {
// The option doesn't exist anymore, but we save its value for migration later on
maybe_save_file = temporary_allocator.dupe(u8, field.value) catch return null;
mapped_config_fields = true;
return null;
}
if (std.mem.eql(u8, field.key, "wayland_specifier") or
std.mem.eql(u8, field.key, "max_desktop_len") or
std.mem.eql(u8, field.key, "max_login_len") or
std.mem.eql(u8, field.key, "max_password_len") or
std.mem.eql(u8, field.key, "mcookie_cmd") or
std.mem.eql(u8, field.key, "term_reset_cmd") or
std.mem.eql(u8, field.key, "term_restore_cursor_cmd") or
std.mem.eql(u8, field.key, "x_cmd_setup") or
std.mem.eql(u8, field.key, "wayland_cmd"))
{
// The options don't exist anymore
mapped_config_fields = true;
return null;
}
if (std.mem.eql(u8, field.key, "bigclock")) {
// The option now uses a string (which then gets converted into an enum) instead of an boolean
// It also includes the ability to change active bigclock's language
var mapped_field = field;
if (std.mem.eql(u8, field.value, "true")){
mapped_field.value = "en";
mapped_config_fields = true;
}else if (std.mem.eql(u8, field.value, "false")){
mapped_field.value = "none";
mapped_config_fields = true;
}
return mapped_field;
}
return field;
}
// This is the stuff we only handle after reading the config.
// For example, the "animate" field could come after "animation"
pub fn lateConfigFieldHandler(animation: *enums.Animation) void {
if (maybe_animate) |animate| {
if (!animate) animation.* = .none;
}
}
pub fn tryMigrateSaveFile(user_buf: *[32]u8) Save {
var save = Save{};
if (maybe_save_file) |path| {
defer temporary_allocator.free(path);
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', user_buf.len) 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', session_buf.len) catch return save;
const session_index_str = session_fbs.getWritten();
var session_index: ?usize = null;
if (session_index_str.len > 0) {
session_index = std.fmt.parseUnsigned(usize, 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,997 +0,0 @@
#include "dragonfail.h"
#include "termbox.h"
#include "inputs.h"
#include "utils.h"
#include "config.h"
#include "draw.h"
#include "bigclock.h"
#include <ctype.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <time.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);
uint16_t len_login = strlen(lang.login);
uint16_t len_password = strlen(lang.password);
if (len_login > len_password)
{
buf->labels_max_len = len_login;
}
else
{
buf->labels_max_len = len_password;
}
buf->box_height = 7 + (2 * config.margin_box_v);
buf->box_width =
(2 * config.margin_box_h)
+ (config.input_len + 1)
+ buf->labels_max_len;
#if defined(__linux__) || defined(__FreeBSD__)
buf->box_chars.left_up = 0x250c;
buf->box_chars.left_down = 0x2514;
buf->box_chars.right_up = 0x2510;
buf->box_chars.right_down = 0x2518;
buf->box_chars.top = 0x2500;
buf->box_chars.bot = 0x2500;
buf->box_chars.left = 0x2502;
buf->box_chars.right = 0x2502;
#else
buf->box_chars.left_up = '+';
buf->box_chars.left_down = '+';
buf->box_chars.right_up = '+';
buf->box_chars.right_down= '+';
buf->box_chars.top = '-';
buf->box_chars.bot = '-';
buf->box_chars.left = '|';
buf->box_chars.right = '|';
#endif
}
static void doom_free(struct term_buf* buf);
static void matrix_free(struct term_buf* buf);
void draw_free(struct term_buf* buf)
{
if (config.animate)
{
switch (config.animation)
{
case 0:
doom_free(buf);
break;
case 1:
matrix_free(buf);
break;
}
}
}
void draw_box(struct term_buf* buf)
{
uint16_t box_x = (buf->width - buf->box_width) / 2;
uint16_t box_y = (buf->height - buf->box_height) / 2;
uint16_t box_x2 = (buf->width + buf->box_width) / 2;
uint16_t box_y2 = (buf->height + buf->box_height) / 2;
buf->box_x = box_x;
buf->box_y = box_y;
if (!config.hide_borders)
{
// corners
tb_change_cell(
box_x - 1,
box_y - 1,
buf->box_chars.left_up,
config.fg,
config.bg);
tb_change_cell(
box_x2,
box_y - 1,
buf->box_chars.right_up,
config.fg,
config.bg);
tb_change_cell(
box_x - 1,
box_y2,
buf->box_chars.left_down,
config.fg,
config.bg);
tb_change_cell(
box_x2,
box_y2,
buf->box_chars.right_down,
config.fg,
config.bg);
// top and bottom
struct tb_cell c1 = {buf->box_chars.top, config.fg, config.bg};
struct tb_cell c2 = {buf->box_chars.bot, config.fg, config.bg};
for (uint16_t i = 0; i < buf->box_width; ++i)
{
tb_put_cell(
box_x + i,
box_y - 1,
&c1);
tb_put_cell(
box_x + i,
box_y2,
&c2);
}
// left and right
c1.ch = buf->box_chars.left;
c2.ch = buf->box_chars.right;
for (uint16_t i = 0; i < buf->box_height; ++i)
{
tb_put_cell(
box_x - 1,
box_y + i,
&c1);
tb_put_cell(
box_x2,
box_y + i,
&c2);
}
}
if (config.blank_box)
{
struct tb_cell blank = {' ', config.fg, config.bg};
for (uint16_t i = 0; i < buf->box_height; ++i)
{
for (uint16_t k = 0; k < buf->box_width; ++k)
{
tb_put_cell(
box_x + k,
box_y + i,
&blank);
}
}
}
}
char* time_str(char* fmt, int maxlen)
{
time_t timer;
char* buffer = malloc(maxlen);
struct tm* tm_info;
timer = time(NULL);
tm_info = localtime(&timer);
if (strftime(buffer, maxlen, fmt, tm_info) == 0)
{
buffer[0] = '\0';
}
return buffer;
}
extern inline uint32_t* CLOCK_N(char c);
struct tb_cell* clock_cell(char c)
{
struct tb_cell* cells = malloc(sizeof(struct tb_cell) * CLOCK_W * CLOCK_H);
struct timeval tv;
gettimeofday(&tv, NULL);
if (config.animate && c == ':' && tv.tv_usec / 500000)
{
c = ' ';
}
uint32_t* clockchars = CLOCK_N(c);
for (int i = 0; i < CLOCK_W * CLOCK_H; i++)
{
cells[i].ch = clockchars[i];
cells[i].fg = config.fg;
cells[i].bg = config.bg;
}
return cells;
}
void alpha_blit(struct tb_cell* buf, uint16_t x, uint16_t y, uint16_t w, uint16_t h, struct tb_cell* cells)
{
if (x + w >= tb_width() || y + h >= tb_height())
return;
for (int i = 0; i < h; i++)
{
for (int j = 0; j < w; j++)
{
struct tb_cell cell = cells[i * w + j];
if (cell.ch)
{
buf[(y + i) * tb_width() + (x + j)] = cell;
}
}
}
}
void draw_bigclock(struct term_buf* buf)
{
if (!config.bigclock)
{
return;
}
int xo = buf->width / 2 - (5 * (CLOCK_W + 1)) / 2;
int yo = (buf->height - buf->box_height) / 2 - CLOCK_H - 2;
char* clockstr = time_str("%H:%M", 6);
struct tb_cell* clockcell;
for (int i = 0; i < 5; i++)
{
clockcell = clock_cell(clockstr[i]);
alpha_blit(tb_cell_buffer(), xo + i * (CLOCK_W + 1), yo, CLOCK_W, CLOCK_H, clockcell);
free(clockcell);
}
free(clockstr);
}
void draw_clock(struct term_buf* buf)
{
if (config.clock == NULL || strlen(config.clock) == 0)
{
return;
}
char* clockstr = time_str(config.clock, 32);
int clockstrlen = strlen(clockstr);
struct tb_cell* cells = strn_cell(clockstr, clockstrlen);
tb_blit(buf->width - clockstrlen, 0, clockstrlen, 1, cells);
free(clockstr);
free(cells);
}
struct tb_cell* strn_cell(char* s, uint16_t len) // throws
{
struct tb_cell* cells = malloc((sizeof (struct tb_cell)) * len);
char* s2 = s;
uint32_t c;
if (cells != NULL)
{
for (uint16_t i = 0; i < len; ++i)
{
if ((s2 - s) >= len)
{
break;
}
s2 += utf8_char_to_unicode(&c, s2);
cells[i].ch = c;
cells[i].bg = config.bg;
cells[i].fg = config.fg;
}
}
else
{
dgn_throw(DGN_ALLOC);
}
return cells;
}
struct tb_cell* str_cell(char* s) // throws
{
return strn_cell(s, strlen(s));
}
void draw_labels(struct term_buf* buf) // throws
{
// login text
struct tb_cell* login = str_cell(lang.login);
if (dgn_catch())
{
dgn_reset();
}
else
{
tb_blit(
buf->box_x + config.margin_box_h,
buf->box_y + config.margin_box_v + 4,
strlen(lang.login),
1,
login);
free(login);
}
// password text
struct tb_cell* password = str_cell(lang.password);
if (dgn_catch())
{
dgn_reset();
}
else
{
tb_blit(
buf->box_x + config.margin_box_h,
buf->box_y + config.margin_box_v + 6,
strlen(lang.password),
1,
password);
free(password);
}
if (buf->info_line != NULL)
{
uint16_t len = strlen(buf->info_line);
struct tb_cell* info_cell = str_cell(buf->info_line);
if (dgn_catch())
{
dgn_reset();
}
else
{
tb_blit(
buf->box_x + ((buf->box_width - len) / 2),
buf->box_y + config.margin_box_v,
len,
1,
info_cell);
free(info_cell);
}
}
}
void draw_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
uint16_t 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)
{
uint16_t len = strlen(target->list[target->cur]);
if (len > (target->visible_len - 3))
{
len = target->visible_len - 3;
}
tb_change_cell(
target->x,
target->y,
'<',
config.fg,
config.bg);
tb_change_cell(
target->x + target->visible_len - 1,
target->y,
'>',
config.fg,
config.bg);
for (uint16_t i = 0; i < len; ++ i)
{
tb_change_cell(
target->x + i + 2,
target->y,
target->list[target->cur][i],
config.fg,
config.bg);
}
}
void draw_input(struct text* input)
{
uint16_t len = strlen(input->text);
uint16_t visible_len = input->visible_len;
if (len > visible_len)
{
len = visible_len;
}
struct tb_cell* cells = strn_cell(input->visible_start, len);
if (dgn_catch())
{
dgn_reset();
}
else
{
tb_blit(input->x, input->y, len, 1, cells);
free(cells);
struct tb_cell c1 = {' ', config.fg, config.bg};
for (uint16_t i = input->end - input->visible_start; i < visible_len; ++i)
{
tb_put_cell(
input->x + i,
input->y,
&c1);
}
}
}
void draw_input_mask(struct text* input)
{
uint16_t len = strlen(input->text);
uint16_t visible_len = input->visible_len;
if (len > visible_len)
{
len = visible_len;
}
struct tb_cell c1 = {config.asterisk, config.fg, config.bg};
struct tb_cell c2 = {' ', config.fg, config.bg};
for (uint16_t i = 0; i < visible_len; ++i)
{
if (input->visible_start + i < input->end)
{
tb_put_cell(
input->x + i,
input->y,
&c1);
}
else
{
tb_put_cell(
input->x + i,
input->y,
&c2);
}
}
}
void position_input(
struct term_buf* buf,
struct desktop* desktop,
struct text* login,
struct text* password)
{
uint16_t x = buf->box_x + config.margin_box_h + buf->labels_max_len + 1;
int32_t len = buf->box_x + buf->box_width - config.margin_box_h - x;
if (len < 0)
{
return;
}
desktop->x = x;
desktop->y = buf->box_y + config.margin_box_v + 2;
desktop->visible_len = len;
login->x = x;
login->y = buf->box_y + config.margin_box_v + 4;
login->visible_len = len;
password->x = x;
password->y = buf->box_y + config.margin_box_v + 6;
password->visible_len = len;
}
static void doom_init(struct term_buf* buf)
{
buf->init_width = buf->width;
buf->init_height = buf->height;
buf->astate.doom = malloc(sizeof(struct doom_state));
if (buf->astate.doom == NULL)
{
dgn_throw(DGN_ALLOC);
}
uint16_t tmp_len = buf->width * buf->height;
buf->astate.doom->buf = malloc(tmp_len);
tmp_len -= buf->width;
if (buf->astate.doom->buf == NULL)
{
dgn_throw(DGN_ALLOC);
}
memset(buf->astate.doom->buf, 0, tmp_len);
memset(buf->astate.doom->buf + tmp_len, DOOM_STEPS - 1, buf->width);
}
static void doom_free(struct term_buf* buf)
{
free(buf->astate.doom->buf);
free(buf->astate.doom);
}
// Adapted from cmatrix
static void matrix_init(struct term_buf* buf)
{
buf->init_width = buf->width;
buf->init_height = buf->height;
buf->astate.matrix = malloc(sizeof(struct matrix_state));
struct matrix_state* s = buf->astate.matrix;
if (s == NULL)
{
dgn_throw(DGN_ALLOC);
}
uint16_t len = buf->height + 1;
s->grid = malloc(sizeof(struct matrix_dot*) * len);
if (s->grid == NULL)
{
dgn_throw(DGN_ALLOC);
}
len = (buf->height + 1) * buf->width;
(s->grid)[0] = malloc(sizeof(struct matrix_dot) * len);
if ((s->grid)[0] == NULL)
{
dgn_throw(DGN_ALLOC);
}
for (int i = 1; i <= buf->height; ++i)
{
s->grid[i] = s->grid[i - 1] + buf->width;
if (s->grid[i] == NULL)
{
dgn_throw(DGN_ALLOC);
}
}
s->length = malloc(buf->width * sizeof(int));
if (s->length == NULL)
{
dgn_throw(DGN_ALLOC);
}
s->spaces = malloc(buf->width * sizeof(int));
if (s->spaces == NULL)
{
dgn_throw(DGN_ALLOC);
}
s->updates = malloc(buf->width * sizeof(int));
if (s->updates == NULL)
{
dgn_throw(DGN_ALLOC);
}
// Initialize grid
for (int i = 0; i <= buf->height; ++i)
{
for (int j = 0; j <= buf->width - 1; j += 2)
{
s->grid[i][j].val = -1;
}
}
for (int j = 0; j < buf->width; j += 2)
{
s->spaces[j] = (int) rand() % buf->height + 1;
s->length[j] = (int) rand() % (buf->height - 3) + 3;
s->grid[1][j].val = ' ';
s->updates[j] = (int) rand() % 3 + 1;
}
}
static void matrix_free(struct term_buf* buf)
{
free(buf->astate.matrix->grid[0]);
free(buf->astate.matrix->grid);
free(buf->astate.matrix->length);
free(buf->astate.matrix->spaces);
free(buf->astate.matrix->updates);
free(buf->astate.matrix);
}
void animate_init(struct term_buf* buf)
{
if (config.animate)
{
switch(config.animation)
{
case 0:
{
doom_init(buf);
break;
}
case 1:
{
matrix_init(buf);
break;
}
}
}
}
static void doom(struct term_buf* term_buf)
{
static struct tb_cell fire[DOOM_STEPS] =
{
{' ', 9, 0}, // default
{0x2591, 2, 0}, // red
{0x2592, 2, 0}, // red
{0x2593, 2, 0}, // red
{0x2588, 2, 0}, // red
{0x2591, 4, 2}, // yellow
{0x2592, 4, 2}, // yellow
{0x2593, 4, 2}, // yellow
{0x2588, 4, 2}, // yellow
{0x2591, 8, 4}, // white
{0x2592, 8, 4}, // white
{0x2593, 8, 4}, // white
{0x2588, 8, 4}, // white
};
uint16_t src;
uint16_t random;
uint16_t dst;
uint16_t w = term_buf->init_width;
uint8_t* tmp = term_buf->astate.doom->buf;
if ((term_buf->width != term_buf->init_width) || (term_buf->height != term_buf->init_height))
{
return;
}
struct tb_cell* buf = tb_cell_buffer();
for (uint16_t x = 0; x < w; ++x)
{
for (uint16_t y = 1; y < term_buf->init_height; ++y)
{
src = y * w + x;
random = ((rand() % 7) & 3);
dst = src - random + 1;
if (w > dst)
{
dst = 0;
}
else
{
dst -= w;
}
tmp[dst] = tmp[src] - (random & 1);
if (tmp[dst] > 12)
{
tmp[dst] = 0;
}
buf[dst] = fire[tmp[dst]];
buf[src] = fire[tmp[src]];
}
}
}
// Adapted from cmatrix
static void matrix(struct term_buf* buf)
{
static int frame = 3;
const int frame_delay = 8;
static int count = 0;
bool first_col;
struct matrix_state* s = buf->astate.matrix;
// Allowed codepoints
const int randmin = 33;
const int randnum = 123 - randmin;
// Chars change mid-scroll
const bool changes = true;
if ((buf->width != buf->init_width) || (buf->height != buf->init_height))
{
return;
}
count += 1;
if (count > frame_delay)
{
frame += 1;
if (frame > 4) frame = 1;
count = 0;
for (int j = 0; j < buf->width; j += 2)
{
int tail;
if (frame > s->updates[j])
{
if (s->grid[0][j].val == -1 && s->grid[1][j].val == ' ')
{
if (s->spaces[j] > 0)
{
s->spaces[j]--;
} else {
s->length[j] = (int) rand() % (buf->height - 3) + 3;
s->grid[0][j].val = (int) rand() % randnum + randmin;
s->spaces[j] = (int) rand() % buf->height + 1;
}
}
int i = 0, seg_len = 0;
first_col = 1;
while (i <= buf->height)
{
// Skip over spaces
while (i <= buf->height
&& (s->grid[i][j].val == ' ' || s->grid[i][j].val == -1))
{
i++;
}
if (i > buf->height) break;
// Find the head of this col
tail = i;
seg_len = 0;
while (i <= buf->height
&& (s->grid[i][j].val != ' ' && s->grid[i][j].val != -1))
{
s->grid[i][j].is_head = false;
if (changes)
{
if (rand() % 8 == 0)
s->grid[i][j].val = (int) rand() % randnum + randmin;
}
i++;
seg_len++;
}
// Head's down offscreen
if (i > buf->height)
{
s->grid[tail][j].val = ' ';
continue;
}
s->grid[i][j].val = (int) rand() % randnum + randmin;
s->grid[i][j].is_head = true;
if (seg_len > s->length[j] || !first_col) {
s->grid[tail][j].val = ' ';
s->grid[0][j].val = -1;
}
first_col = 0;
i++;
}
}
}
}
uint32_t blank;
utf8_char_to_unicode(&blank, " ");
for (int j = 0; j < buf->width; j += 2)
{
for (int i = 1; i <= buf->height; ++i)
{
uint32_t c;
int fg = TB_GREEN;
int bg = TB_DEFAULT;
if (s->grid[i][j].val == -1 || s->grid[i][j].val == ' ')
{
tb_change_cell(j, i - 1, blank, fg, bg);
continue;
}
char tmp[2];
tmp[0] = s->grid[i][j].val;
tmp[1] = '\0';
if(utf8_char_to_unicode(&c, tmp))
{
if (s->grid[i][j].is_head)
{
fg = TB_WHITE | TB_BOLD;
}
tb_change_cell(j, i - 1, c, fg, bg);
}
}
}
}
void animate(struct term_buf* buf)
{
buf->width = tb_width();
buf->height = tb_height();
if (config.animate)
{
switch(config.animation)
{
case 0:
{
doom(buf);
break;
}
case 1:
{
matrix(buf);
break;
}
}
}
}
bool cascade(struct term_buf* term_buf, uint8_t* fails)
{
uint16_t width = term_buf->width;
uint16_t height = term_buf->height;
struct tb_cell* buf = tb_cell_buffer();
bool changes = false;
char c_under;
char c;
for (int i = height - 2; i >= 0; --i)
{
for (int k = 0; k < width; ++k)
{
c = buf[i * width + k].ch;
if (isspace(c))
{
continue;
}
c_under = buf[(i + 1) * width + k].ch;
if (!isspace(c_under))
{
continue;
}
if (!changes)
{
changes = true;
}
if ((rand() % 10) > 7)
{
continue;
}
buf[(i + 1) * width + k] = buf[i * width + k];
buf[i * width + k].ch = ' ';
}
}
// stop force-updating
if (!changes)
{
sleep(7);
*fails = 0;
return false;
}
// force-update
return true;
}

View File

@@ -1,92 +0,0 @@
#ifndef H_LY_DRAW
#define H_LY_DRAW
#include "termbox.h"
#include "inputs.h"
#include <stdbool.h>
#include <stdint.h>
struct box
{
uint32_t left_up;
uint32_t left_down;
uint32_t right_up;
uint32_t right_down;
uint32_t top;
uint32_t bot;
uint32_t left;
uint32_t right;
};
struct matrix_dot
{
int val;
bool is_head;
};
struct matrix_state
{
struct matrix_dot** grid;
int* length;
int* spaces;
int* updates;
};
struct doom_state
{
uint8_t* buf;
};
union anim_state
{
struct doom_state* doom;
struct matrix_state* matrix;
};
struct term_buf
{
uint16_t width;
uint16_t height;
uint16_t init_width;
uint16_t init_height;
struct box box_chars;
char* info_line;
uint16_t labels_max_len;
uint16_t box_x;
uint16_t box_y;
uint16_t box_width;
uint16_t box_height;
union anim_state astate;
};
void draw_init(struct term_buf* buf);
void draw_free(struct term_buf* buf);
void draw_box(struct term_buf* buf);
struct tb_cell* strn_cell(char* s, uint16_t len);
struct tb_cell* str_cell(char* s);
void draw_labels(struct term_buf* buf);
void draw_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, uint8_t* fails);
void draw_bigclock(struct term_buf *buf);
void draw_clock(struct term_buf *buf);
#endif

30
src/enums.zig Normal file
View File

@@ -0,0 +1,30 @@
pub const Animation = enum {
none,
doom,
matrix,
};
pub const DisplayServer = enum {
wayland,
shell,
xinitrc,
x11,
};
pub const Input = enum {
info_line,
session,
login,
password,
};
pub const ViMode = enum {
normal,
insert,
};
pub const Bigclock = enum {
none,
en,
fa,
};

View File

@@ -1,285 +0,0 @@
#include "dragonfail.h"
#include "termbox.h"
#include "inputs.h"
#include "config.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <ctype.h>
void handle_desktop(void* input_struct, struct tb_event* event)
{
struct desktop* target = (struct desktop*) input_struct;
if ((event != NULL) && (event->type == TB_EVENT_KEY))
{
if (event->key == TB_KEY_ARROW_LEFT || (event->key == TB_KEY_CTRL_H))
{
input_desktop_right(target);
}
else if (event->key == TB_KEY_ARROW_RIGHT || (event->key == TB_KEY_CTRL_L))
{
input_desktop_left(target);
}
}
tb_set_cursor(target->x + 2, target->y);
}
void handle_text(void* input_struct, struct tb_event* event)
{
struct text* target = (struct text*) input_struct;
if ((event != NULL) && (event->type == TB_EVENT_KEY))
{
if (event->key == TB_KEY_ARROW_LEFT)
{
input_text_left(target);
}
else if (event->key == TB_KEY_ARROW_RIGHT)
{
input_text_right(target);
}
else if (event->key == TB_KEY_DELETE)
{
input_text_delete(target);
}
else if ((event->key == TB_KEY_BACKSPACE)
|| (event->key == TB_KEY_BACKSPACE2))
{
input_text_backspace(target);
}
else if (((event->ch > 31) && (event->ch < 127))
|| (event->key == TB_KEY_SPACE))
{
char buf[7] = {0};
if (event->key == TB_KEY_SPACE)
{
buf[0] = ' ';
}
else
{
utf8_unicode_to_char(buf, event->ch);
}
input_text_write(target, buf[0]);
}
}
tb_set_cursor(
target->x + (target->cur - target->visible_start),
target->y);
}
void input_desktop(struct desktop* target)
{
target->list = NULL;
target->list_simple = NULL;
target->cmd = NULL;
target->display_server = NULL;
target->cur = 0;
target->len = 0;
input_desktop_add(target, strdup(lang.shell), strdup(""), DS_SHELL);
input_desktop_add(target, strdup(lang.xinitrc), strdup(config.xinitrc), DS_XINITRC);
#if 0
input_desktop_add(target, strdup(lang.wayland), strdup(""), DS_WAYLAND);
#endif
}
void input_text(struct text* target, uint64_t len)
{
target->text = malloc(len + 1);
if (target->text == NULL)
{
dgn_throw(DGN_ALLOC);
return;
}
else
{
int ok = mlock(target->text, len + 1);
if (ok < 0)
{
dgn_throw(DGN_MLOCK);
return;
}
memset(target->text, 0, len + 1);
}
target->cur = target->text;
target->end = target->text;
target->visible_start = target->text;
target->len = len;
target->x = 0;
target->y = 0;
}
void input_desktop_free(struct desktop* target)
{
if (target != NULL)
{
for (uint16_t i = 0; i < target->len; ++i)
{
if (target->list[i] != NULL)
{
free(target->list[i]);
}
if (target->cmd[i] != NULL)
{
free(target->cmd[i]);
}
}
free(target->list);
free(target->cmd);
free(target->display_server);
}
}
void input_text_free(struct text* target)
{
memset(target->text, 0, target->len);
munlock(target->text, target->len + 1);
free(target->text);
}
void input_desktop_right(struct desktop* target)
{
++(target->cur);
if (target->cur >= target->len)
{
target->cur = 0;
}
}
void input_desktop_left(struct desktop* target)
{
--(target->cur);
if (target->cur >= target->len)
{
target->cur = target->len - 1;
}
}
void input_desktop_add(
struct desktop* target,
char* name,
char* cmd,
enum display_server display_server)
{
++(target->len);
target->list = realloc(target->list, target->len * (sizeof (char*)));
target->list_simple = realloc(target->list_simple, target->len * (sizeof (char*)));
target->cmd = realloc(target->cmd, target->len * (sizeof (char*)));
target->display_server = realloc(
target->display_server,
target->len * (sizeof (enum display_server)));
target->cur = target->len - 1;
if ((target->list == NULL)
|| (target->cmd == NULL)
|| (target->display_server == NULL))
{
dgn_throw(DGN_ALLOC);
return;
}
target->list[target->cur] = name;
int name_len = strlen(name);
char* name_simple = strdup(name);
if (strstr(name_simple, " ") != NULL)
{
name_simple = strtok(name_simple, " ");
}
for (int i = 0; i < name_len; i++)
{
name_simple[i] = tolower(name_simple[i]);
}
target->list_simple[target->cur] = name_simple;
target->cmd[target->cur] = cmd;
target->display_server[target->cur] = display_server;
}
void input_text_right(struct text* target)
{
if (target->cur < target->end)
{
++(target->cur);
if ((target->cur - target->visible_start) > target->visible_len)
{
++(target->visible_start);
}
}
}
void input_text_left(struct text* target)
{
if (target->cur > target->text)
{
--(target->cur);
if ((target->cur - target->visible_start) < 0)
{
--(target->visible_start);
}
}
}
void input_text_write(struct text* target, char ascii)
{
if (ascii <= 0)
{
return; // unices do not support usernames and passwords other than ascii
}
if ((target->end - target->text + 1) < target->len)
{
// moves the text to the right to add space for the new ascii char
memcpy(target->cur + 1, target->cur, target->end - target->cur);
++(target->end);
// adds the new char and moves the cursor to the right
*(target->cur) = ascii;
input_text_right(target);
}
}
void input_text_delete(struct text* target)
{
if (target->cur < target->end)
{
// moves the text on the right to overwrite the currently pointed char
memcpy(target->cur, target->cur + 1, target->end - target->cur + 1);
--(target->end);
}
}
void input_text_backspace(struct text* target)
{
if (target->cur > target->text)
{
input_text_left(target);
input_text_delete(target);
}
}
void input_text_clear(struct text* target)
{
memset(target->text, 0, target->len + 1);
target->cur = target->text;
target->end = target->text;
target->visible_start = target->text;
}

View File

@@ -1,57 +0,0 @@
#ifndef H_LY_INPUTS
#define H_LY_INPUTS
#include "termbox.h"
#include <stdint.h>
enum display_server {DS_WAYLAND, DS_SHELL, DS_XINITRC, DS_XORG};
struct text
{
char* text;
char* end;
int64_t len;
char* cur;
char* visible_start;
uint16_t visible_len;
uint16_t x;
uint16_t y;
};
struct desktop
{
char** list;
char** list_simple;
char** cmd;
enum display_server* display_server;
uint16_t cur;
uint16_t len;
uint16_t visible_len;
uint16_t x;
uint16_t y;
};
void handle_desktop(void* input_struct, struct tb_event* event);
void handle_text(void* input_struct, struct tb_event* event);
void input_desktop(struct desktop* target);
void input_text(struct text* target, uint64_t len);
void input_desktop_free(struct desktop* target);
void input_text_free(struct text* target);
void input_desktop_right(struct desktop* target);
void input_desktop_left(struct desktop* target);
void input_desktop_add(
struct desktop* target,
char* name,
char* cmd,
enum display_server display_server);
void input_text_right(struct text* target);
void input_text_left(struct text* target);
void input_text_write(struct text* target, char ascii);
void input_text_delete(struct text* target);
void input_text_backspace(struct text* target);
void input_text_clear(struct text* target);
#endif

111
src/interop.zig Normal file
View File

@@ -0,0 +1,111 @@
const std = @import("std");
const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
pub const termbox = @import("termbox2");
pub const pam = @cImport({
@cInclude("security/pam_appl.h");
});
pub const utmp = @cImport({
@cInclude("utmpx.h");
});
// Exists for X11 support only
pub const xcb = @cImport({
@cInclude("xcb/xcb.h");
});
pub const unistd = @cImport({
@cInclude("unistd.h");
});
pub const time = @cImport({
@cInclude("time.h");
});
pub const system_time = @cImport({
@cInclude("sys/time.h");
});
pub const stdlib = @cImport({
@cInclude("stdlib.h");
});
pub const pwd = @cImport({
@cInclude("pwd.h");
// We include a FreeBSD-specific header here since login_cap.h references
// the passwd struct directly, so we can't import it separately'
if (builtin.os.tag == .freebsd) @cInclude("login_cap.h");
});
pub const grp = @cImport({
@cInclude("grp.h");
});
// BSD-specific headers
pub const kbio = @cImport({
@cInclude("sys/kbio.h");
});
// Linux-specific headers
pub const kd = @cImport({
@cInclude("sys/kd.h");
});
pub const vt = @cImport({
@cInclude("sys/vt.h");
});
// Used for getting & setting the lock state
const LedState = if (builtin.os.tag.isBSD()) c_int else c_char;
const get_led_state = if (builtin.os.tag.isBSD()) kbio.KDGETLED else kd.KDGKBLED;
const set_led_state = if (builtin.os.tag.isBSD()) kbio.KDSETLED else kd.KDSKBLED;
const numlock_led = if (builtin.os.tag.isBSD()) kbio.LED_NUM else kd.K_NUMLOCK;
const capslock_led = if (builtin.os.tag.isBSD()) kbio.LED_CAP else kd.K_CAPSLOCK;
pub fn timeAsString(buf: [:0]u8, format: [:0]const u8) ![]u8 {
const timer = std.time.timestamp();
const tm_info = time.localtime(&timer);
const len = time.strftime(buf, buf.len, format, tm_info);
if (len < 0) return error.CannotGetFormattedTime;
return buf[0..len];
}
pub fn switchTty(console_dev: []const u8, tty: u8) !void {
const fd = try std.posix.open(console_dev, .{ .ACCMODE = .WRONLY }, 0);
defer std.posix.close(fd);
_ = std.c.ioctl(fd, vt.VT_ACTIVATE, tty);
_ = std.c.ioctl(fd, vt.VT_WAITACTIVE, tty);
}
pub fn getLockState(console_dev: []const u8) !struct {
numlock: bool,
capslock: bool,
} {
const fd = try std.posix.open(console_dev, .{ .ACCMODE = .RDONLY }, 0);
defer std.posix.close(fd);
var led: LedState = undefined;
_ = std.c.ioctl(fd, get_led_state, &led);
return .{
.numlock = (led & numlock_led) != 0,
.capslock = (led & capslock_led) != 0,
};
}
pub fn setNumlock(val: bool) !void {
var led: LedState = undefined;
_ = std.c.ioctl(0, get_led_state, &led);
const numlock = (led & numlock_led) != 0;
if (numlock != val) {
const status = std.c.ioctl(std.posix.STDIN_FILENO, set_led_state, led ^ numlock_led);
if (status != 0) return error.FailedToSetNumlock;
}
}

View File

@@ -1,715 +0,0 @@
#include "dragonfail.h"
#include "termbox.h"
#include "inputs.h"
#include "draw.h"
#include "utils.h"
#include "config.h"
#include "login.h"
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <security/pam_appl.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <utmp.h>
#include <xcb/xcb.h>
int get_free_display()
{
char xlock[1024];
uint8_t i;
for (i = 0; i < 200; ++i)
{
snprintf(xlock, 1024, "/tmp/.X%d-lock", i);
if (access(xlock, F_OK) == -1)
{
break;
}
}
return i;
}
void reset_terminal(struct passwd* pwd)
{
pid_t pid = fork();
if (pid == 0)
{
execl(pwd->pw_shell, pwd->pw_shell, "-c", config.term_reset_cmd, NULL);
exit(EXIT_SUCCESS);
}
int status;
waitpid(pid, &status, 0);
}
int login_conv(
int num_msg,
const struct pam_message** msg,
struct pam_response** resp,
void* appdata_ptr)
{
*resp = calloc(num_msg, sizeof (struct pam_response));
if (*resp == NULL)
{
return PAM_BUF_ERR;
}
char* username;
char* password;
int ok = PAM_SUCCESS;
int i;
for (i = 0; i < num_msg; ++i)
{
switch (msg[i]->msg_style)
{
case PAM_PROMPT_ECHO_ON:
{
username = ((char**) appdata_ptr)[0];
(*resp)[i].resp = strdup(username);
break;
}
case PAM_PROMPT_ECHO_OFF:
{
password = ((char**) appdata_ptr)[1];
(*resp)[i].resp = strdup(password);
break;
}
case PAM_ERROR_MSG:
{
ok = PAM_CONV_ERR;
break;
}
}
if (ok != PAM_SUCCESS)
{
break;
}
}
if (ok != PAM_SUCCESS)
{
for (i = 0; i < num_msg; ++i)
{
if ((*resp)[i].resp == NULL)
{
continue;
}
free((*resp)[i].resp);
(*resp)[i].resp = NULL;
}
free(*resp);
*resp = NULL;
}
return ok;
}
void pam_diagnose(int error, struct term_buf* buf)
{
switch (error)
{
case PAM_ACCT_EXPIRED:
{
buf->info_line = lang.err_pam_acct_expired;
break;
}
case PAM_AUTH_ERR:
{
buf->info_line = lang.err_pam_auth;
break;
}
case PAM_AUTHINFO_UNAVAIL:
{
buf->info_line = lang.err_pam_authinfo_unavail;
break;
}
case PAM_BUF_ERR:
{
buf->info_line = lang.err_pam_buf;
break;
}
case PAM_CRED_ERR:
{
buf->info_line = lang.err_pam_cred_err;
break;
}
case PAM_CRED_EXPIRED:
{
buf->info_line = lang.err_pam_cred_expired;
break;
}
case PAM_CRED_INSUFFICIENT:
{
buf->info_line = lang.err_pam_cred_insufficient;
break;
}
case PAM_CRED_UNAVAIL:
{
buf->info_line = lang.err_pam_cred_unavail;
break;
}
case PAM_MAXTRIES:
{
buf->info_line = lang.err_pam_maxtries;
break;
}
case PAM_NEW_AUTHTOK_REQD:
{
buf->info_line = lang.err_pam_authok_reqd;
break;
}
case PAM_PERM_DENIED:
{
buf->info_line = lang.err_pam_perm_denied;
break;
}
case PAM_SESSION_ERR:
{
buf->info_line = lang.err_pam_session;
break;
}
case PAM_SYSTEM_ERR:
{
buf->info_line = lang.err_pam_sys;
break;
}
case PAM_USER_UNKNOWN:
{
buf->info_line = lang.err_pam_user_unknown;
break;
}
case PAM_ABORT:
default:
{
buf->info_line = lang.err_pam_abort;
break;
}
}
dgn_throw(DGN_PAM);
}
void env_init(struct passwd* pwd)
{
extern char** environ;
char* term = getenv("TERM");
char* lang = getenv("LANG");
// clean env
environ[0] = NULL;
setenv("TERM", term ? term : "linux", 1);
setenv("HOME", pwd->pw_dir, 1);
setenv("PWD", pwd->pw_dir, 1);
setenv("SHELL", pwd->pw_shell, 1);
setenv("USER", pwd->pw_name, 1);
setenv("LOGNAME", pwd->pw_name, 1);
setenv("LANG", lang ? lang : "C", 1);
// Set PATH if specified in the configuration
if (strlen(config.path))
{
int ok = setenv("PATH", config.path, 1);
if (ok != 0)
{
dgn_throw(DGN_PATH);
}
}
}
void env_xdg_session(const enum display_server display_server)
{
switch (display_server)
{
case DS_WAYLAND:
{
setenv("XDG_SESSION_TYPE", "wayland", 1);
break;
}
case DS_SHELL:
{
setenv("XDG_SESSION_TYPE", "tty", 0);
break;
}
case DS_XINITRC:
case DS_XORG:
{
setenv("XDG_SESSION_TYPE", "x11", 0);
break;
}
}
}
void env_xdg(const char* tty_id, const char* desktop_name)
{
char user[20];
snprintf(user, 20, "/run/user/%d", getuid());
setenv("XDG_RUNTIME_DIR", user, 0);
setenv("XDG_SESSION_CLASS", "user", 0);
setenv("XDG_SESSION_ID", "1", 0);
setenv("XDG_SESSION_DESKTOP", desktop_name, 0);
setenv("XDG_SEAT", "seat0", 0);
setenv("XDG_VTNR", tty_id, 0);
}
void add_utmp_entry(
struct utmp *entry,
char *username,
pid_t display_pid
) {
entry->ut_type = USER_PROCESS;
entry->ut_pid = display_pid;
strcpy(entry->ut_line, ttyname(STDIN_FILENO) + strlen("/dev/"));
/* only correct for ptys named /dev/tty[pqr][0-9a-z] */
strcpy(entry->ut_id, ttyname(STDIN_FILENO) + strlen("/dev/tty"));
time((long int *) &entry->ut_time);
strncpy(entry->ut_user, username, UT_NAMESIZE);
memset(entry->ut_host, 0, UT_HOSTSIZE);
entry->ut_addr = 0;
setutent();
pututline(entry);
}
void remove_utmp_entry(struct utmp *entry) {
entry->ut_type = DEAD_PROCESS;
memset(entry->ut_line, 0, UT_LINESIZE);
entry->ut_time = 0;
memset(entry->ut_user, 0, UT_NAMESIZE);
setutent();
pututline(entry);
endutent();
}
void xauth(const char* display_name, const char* shell, char* pwd)
{
const char* xauth_file = "lyxauth";
char* xauth_dir = getenv("XDG_RUNTIME_DIR");
if ((xauth_dir == NULL) || (*xauth_dir == '\0'))
{
xauth_dir = getenv("XDG_CONFIG_HOME");
struct stat sb;
if ((xauth_dir == NULL) || (*xauth_dir == '\0'))
{
xauth_dir = strdup(pwd);
strcat(xauth_dir, "/.config");
stat(xauth_dir, &sb);
if (S_ISDIR(sb.st_mode))
{
strcat(xauth_dir, "/ly");
}
else
{
xauth_dir = pwd;
xauth_file = ".lyxauth";
}
}
else
{
strcat(xauth_dir, "/ly");
}
// If .config/ly/ or XDG_CONFIG_HOME/ly/ doesn't exist and can't create the directory, use pwd
// Passing pwd beforehand is safe since stat will always evaluate false
stat(xauth_dir, &sb);
if (!S_ISDIR(sb.st_mode) && mkdir(xauth_dir, 0777) == -1)
{
xauth_dir = pwd;
xauth_file = ".lyxauth";
}
}
// trim trailing slashes
int i = strlen(xauth_dir) - 1;
while (xauth_dir[i] == '/') i--;
xauth_dir[i + 1] = '\0';
char xauthority[256];
snprintf(xauthority, 256, "%s/%s", xauth_dir, xauth_file);
setenv("XAUTHORITY", xauthority, 1);
setenv("DISPLAY", display_name, 1);
FILE* fp = fopen(xauthority, "ab+");
if (fp != NULL)
{
fclose(fp);
}
pid_t pid = fork();
if (pid == 0)
{
char cmd[1024];
snprintf(
cmd,
1024,
"%s add %s . `%s`",
config.xauth_cmd,
display_name,
config.mcookie_cmd);
execl(shell, shell, "-c", cmd, NULL);
exit(EXIT_SUCCESS);
}
int status;
waitpid(pid, &status, 0);
}
void xorg(
struct passwd* pwd,
const char* vt,
const char* desktop_cmd)
{
char display_name[4];
snprintf(display_name, 3, ":%d", get_free_display());
xauth(display_name, pwd->pw_shell, pwd->pw_dir);
// start xorg
pid_t pid = fork();
if (pid == 0)
{
char x_cmd[1024];
snprintf(
x_cmd,
1024,
"%s %s %s",
config.x_cmd,
display_name,
vt);
execl(pwd->pw_shell, pwd->pw_shell, "-c", x_cmd, NULL);
exit(EXIT_SUCCESS);
}
int ok;
xcb_connection_t* xcb;
do
{
xcb = xcb_connect(NULL, NULL);
ok = xcb_connection_has_error(xcb);
kill(pid, 0);
}
while((ok != 0) && (errno != ESRCH));
if (ok != 0)
{
return;
}
pid_t xorg_pid = fork();
if (xorg_pid == 0)
{
char de_cmd[1024];
snprintf(
de_cmd,
1024,
"%s %s",
config.x_cmd_setup,
desktop_cmd);
execl(pwd->pw_shell, pwd->pw_shell, "-c", de_cmd, NULL);
exit(EXIT_SUCCESS);
}
int status;
waitpid(xorg_pid, &status, 0);
xcb_disconnect(xcb);
kill(pid, 0);
if (errno != ESRCH)
{
kill(pid, SIGTERM);
waitpid(pid, &status, 0);
}
}
void wayland(
struct passwd* pwd,
const char* desktop_cmd)
{
char cmd[1024];
snprintf(cmd, 1024, "%s %s", config.wayland_cmd, desktop_cmd);
execl(pwd->pw_shell, pwd->pw_shell, "-c", cmd, NULL);
}
void shell(struct passwd* pwd)
{
const char* pos = strrchr(pwd->pw_shell, '/');
char args[1024];
args[0] = '-';
if (pos != NULL)
{
pos = pos + 1;
}
else
{
pos = pwd->pw_shell;
}
strncpy(args + 1, pos, 1023);
execl(pwd->pw_shell, args, NULL);
}
// pam_do performs the pam action specified in pam_action
// on pam_action fail, call diagnose and end pam session
int pam_do(
int (pam_action)(struct pam_handle *, int),
struct pam_handle *handle,
int flags,
struct term_buf *buf)
{
int status = pam_action(handle, flags);
if (status != PAM_SUCCESS) {
pam_diagnose(status, buf);
pam_end(handle, status);
}
return status;
}
void auth(
struct desktop* desktop,
struct text* login,
struct text* password,
struct term_buf* buf)
{
int ok;
char tty_id [3];
snprintf(tty_id, 3, "%d", config.tty);
// Add XDG environment variables
env_xdg_session(desktop->display_server[desktop->cur]);
env_xdg(tty_id, desktop->list_simple[desktop->cur]);
// open pam session
const char* creds[2] = {login->text, password->text};
struct pam_conv conv = {login_conv, creds};
struct pam_handle* handle;
ok = pam_start(config.service_name, NULL, &conv, &handle);
if (ok != PAM_SUCCESS)
{
pam_diagnose(ok, buf);
pam_end(handle, ok);
return;
}
ok = pam_do(pam_authenticate, handle, 0, buf);
if (ok != PAM_SUCCESS)
{
return;
}
ok = pam_do(pam_acct_mgmt, handle, 0, buf);
if (ok != PAM_SUCCESS)
{
return;
}
ok = pam_do(pam_setcred, handle, PAM_ESTABLISH_CRED, buf);
if (ok != PAM_SUCCESS)
{
return;
}
ok = pam_do(pam_open_session, handle, 0, buf);
if (ok != PAM_SUCCESS)
{
return;
}
// clear the credentials
input_text_clear(password);
// get passwd structure
struct passwd* pwd = getpwnam(login->text);
endpwent();
if (pwd == NULL)
{
dgn_throw(DGN_PWNAM);
pam_end(handle, ok);
return;
}
// set user shell
if (pwd->pw_shell[0] == '\0')
{
setusershell();
char* shell = getusershell();
if (shell != NULL)
{
strcpy(pwd->pw_shell, shell);
}
endusershell();
}
// restore regular terminal mode
tb_clear();
tb_present();
tb_shutdown();
// start desktop environment
pid_t pid = fork();
if (pid == 0)
{
// set user info
ok = initgroups(pwd->pw_name, pwd->pw_gid);
if (ok != 0)
{
dgn_throw(DGN_USER_INIT);
exit(EXIT_FAILURE);
}
ok = setgid(pwd->pw_gid);
if (ok != 0)
{
dgn_throw(DGN_USER_GID);
exit(EXIT_FAILURE);
}
ok = setuid(pwd->pw_uid);
if (ok != 0)
{
dgn_throw(DGN_USER_UID);
exit(EXIT_FAILURE);
}
// get a display
char vt[5];
snprintf(vt, 5, "vt%d", config.tty);
// set env (this clears the environment)
env_init(pwd);
// Re-add XDG environment variables from lines 508,509
env_xdg_session(desktop->display_server[desktop->cur]);
env_xdg(tty_id, desktop->list_simple[desktop->cur]);
if (dgn_catch())
{
exit(EXIT_FAILURE);
}
// add pam variables
char** env = pam_getenvlist(handle);
for (uint16_t i = 0; env && env[i]; ++i)
{
putenv(env[i]);
}
// execute
int ok = chdir(pwd->pw_dir);
if (ok != 0)
{
dgn_throw(DGN_CHDIR);
exit(EXIT_FAILURE);
}
reset_terminal(pwd);
switch (desktop->display_server[desktop->cur])
{
case DS_WAYLAND:
{
wayland(pwd, desktop->cmd[desktop->cur]);
break;
}
case DS_SHELL:
{
shell(pwd);
break;
}
case DS_XINITRC:
case DS_XORG:
{
xorg(pwd, vt, desktop->cmd[desktop->cur]);
break;
}
}
exit(EXIT_SUCCESS);
}
// add utmp audit
struct utmp entry;
add_utmp_entry(&entry, pwd->pw_name, pid);
// wait for the session to stop
int status;
waitpid(pid, &status, 0);
remove_utmp_entry(&entry);
reset_terminal(pwd);
// reinit termbox
tb_init();
tb_select_output_mode(TB_OUTPUT_NORMAL);
// reload the desktop environment list on logout
input_desktop_free(desktop);
input_desktop(desktop);
desktop_load(desktop);
// close pam session
ok = pam_do(pam_close_session, handle, 0, buf);
if (ok != PAM_SUCCESS)
{
return;
}
ok = pam_do(pam_setcred, handle, PAM_DELETE_CRED, buf);
if (ok != PAM_SUCCESS)
{
return;
}
ok = pam_end(handle, 0);
if (ok != PAM_SUCCESS)
{
pam_diagnose(ok, buf);
}
}

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,356 +0,0 @@
#include "argoat.h"
#include "configator.h"
#include "dragonfail.h"
#include "termbox.h"
#include "draw.h"
#include "inputs.h"
#include "login.h"
#include "utils.h"
#include "config.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdlib.h>
#define ARG_COUNT 7
#ifndef LY_VERSION
#define LY_VERSION "0.6.0"
#endif
// global
struct lang lang;
struct config config;
// args handles
void arg_help(void* data, char** pars, const int pars_count)
{
printf("If you want to configure Ly, please check the config file, usually located at /etc/ly/config.ini.\n");
exit(0);
}
void arg_version(void* data, char** pars, const int pars_count)
{
printf("Ly version %s\n", LY_VERSION);
exit(0);
}
// low-level error messages
void log_init(char** log)
{
log[DGN_OK] = lang.err_dgn_oob;
log[DGN_NULL] = lang.err_null;
log[DGN_ALLOC] = lang.err_alloc;
log[DGN_BOUNDS] = lang.err_bounds;
log[DGN_DOMAIN] = lang.err_domain;
log[DGN_MLOCK] = lang.err_mlock;
log[DGN_XSESSIONS_DIR] = lang.err_xsessions_dir;
log[DGN_XSESSIONS_OPEN] = lang.err_xsessions_open;
log[DGN_PATH] = lang.err_path;
log[DGN_CHDIR] = lang.err_chdir;
log[DGN_PWNAM] = lang.err_pwnam;
log[DGN_USER_INIT] = lang.err_user_init;
log[DGN_USER_GID] = lang.err_user_gid;
log[DGN_USER_UID] = lang.err_user_uid;
log[DGN_PAM] = lang.err_pam;
log[DGN_HOSTNAME] = lang.err_hostname;
}
void arg_config(void* data, char** pars, const int pars_count)
{
*((char **)data) = *pars;
}
// ly!
int main(int argc, char** argv)
{
// init error lib
log_init(dgn_init());
// load config
config_defaults();
lang_defaults();
char *config_path = NULL;
// parse args
const struct argoat_sprig sprigs[ARG_COUNT] =
{
{NULL, 0, NULL, NULL},
{"config", 0, &config_path, arg_config},
{"c", 0, &config_path, arg_config},
{"help", 0, NULL, arg_help},
{"h", 0, NULL, arg_help},
{"version", 0, NULL, arg_version},
{"v", 0, NULL, arg_version},
};
struct argoat args = {sprigs, ARG_COUNT, NULL, 0, 0};
argoat_graze(&args, argc, argv);
// init inputs
struct desktop desktop;
struct text login;
struct text password;
input_desktop(&desktop);
input_text(&login, config.max_login_len);
input_text(&password, config.max_password_len);
if (dgn_catch())
{
config_free();
lang_free();
return 1;
}
config_load(config_path);
lang_load();
void* input_structs[3] =
{
(void*) &desktop,
(void*) &login,
(void*) &password,
};
void (*input_handles[3]) (void*, struct tb_event*) =
{
handle_desktop,
handle_text,
handle_text,
};
desktop_load(&desktop);
load(&desktop, &login);
// start termbox
tb_init();
tb_select_output_mode(TB_OUTPUT_NORMAL);
tb_clear();
// init visible elements
struct tb_event event;
struct term_buf buf;
//Place the curser on the login field if there is no saved username, if there is, place the curser on the password field
uint8_t active_input;
if (config.default_input == LOGIN_INPUT && login.text != login.end){
active_input = PASSWORD_INPUT;
}
else{
active_input = config.default_input;
}
// init drawing stuff
draw_init(&buf);
// draw_box and position_input are called because they need to be
// called before *input_handles[active_input] for the cursor to be
// positioned correctly
draw_box(&buf);
position_input(&buf, &desktop, &login, &password);
(*input_handles[active_input])(input_structs[active_input], NULL);
if (config.animate)
{
animate_init(&buf);
if (dgn_catch())
{
config.animate = false;
dgn_reset();
}
}
// init state info
int error;
bool run = true;
bool update = true;
bool reboot = false;
bool shutdown = false;
uint8_t auth_fails = 0;
switch_tty(&buf);
// main loop
while (run)
{
if (update)
{
if (auth_fails < 10)
{
(*input_handles[active_input])(input_structs[active_input], NULL);
tb_clear();
animate(&buf);
draw_bigclock(&buf);
draw_box(&buf);
draw_clock(&buf);
draw_labels(&buf);
if(!config.hide_f1_commands)
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();
}
int timeout = -1;
if (config.animate)
{
timeout = config.min_refresh_delta;
}
else
{
struct timeval tv;
gettimeofday(&tv, NULL);
if (config.bigclock)
timeout = (60 - tv.tv_sec % 60) * 1000 - tv.tv_usec / 1000 + 1;
if (config.clock)
timeout = 1000 - tv.tv_usec / 1000 + 1;
}
if (timeout == -1)
{
error = tb_poll_event(&event);
}
else
{
error = tb_peek_event(&event, timeout);
}
if (error < 0)
{
continue;
}
if (event.type == TB_EVENT_KEY)
{
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]);
update = true;
}
break;
case TB_KEY_CTRL_K:
case TB_KEY_ARROW_UP:
if (active_input > 0)
{
--active_input;
update = true;
}
break;
case TB_KEY_CTRL_J:
case TB_KEY_ARROW_DOWN:
if (active_input < 2)
{
++active_input;
update = true;
}
break;
case TB_KEY_TAB:
++active_input;
if (active_input > 2)
{
active_input = SESSION_SWITCH;
}
update = true;
break;
case TB_KEY_ENTER:
save(&desktop, &login);
auth(&desktop, &login, &password, &buf);
update = true;
if (dgn_catch())
{
++auth_fails;
// move focus back to password input
active_input = PASSWORD_INPUT;
if (dgn_output_code() != DGN_PAM)
{
buf.info_line = dgn_output_log();
}
if (config.blank_password)
{
input_text_clear(&password);
}
dgn_reset();
}
else
{
buf.info_line = lang.logout;
}
load(&desktop, &login);
system("tput cnorm");
break;
default:
(*input_handles[active_input])(
input_structs[active_input],
&event);
update = true;
break;
}
}
}
// stop termbox
tb_shutdown();
// free inputs
input_desktop_free(&desktop);
input_text_free(&login);
input_text_free(&password);
free_hostname();
// unload config
draw_free(&buf);
lang_free();
if (shutdown)
{
execl("/bin/sh", "sh", "-c", config.shutdown_cmd, NULL);
}
else if (reboot)
{
execl("/bin/sh", "sh", "-c", config.restart_cmd, NULL);
}
config_free();
return 0;
}

889
src/main.zig Normal file
View File

@@ -0,0 +1,889 @@
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 Session = @import("tui/components/Session.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;
const unistd = interop.unistd;
const temporary_allocator = std.heap.page_allocator;
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);
}
fn eventThreadMain(event: *?termbox.tb_event, event_error: *c_int, timeout: *c_int, wake: *std.Thread.Semaphore, cont: *std.Thread.Semaphore) void {
while (true) {
cont.wait();
var e: termbox.tb_event = undefined;
event_error.* = termbox.tb_peek_event(&e, timeout.*);
event.* = e;
wake.post();
}
}
pub fn main() !void {
var shutdown = false;
var restart = false;
var shutdown_cmd: []const u8 = undefined;
var restart_cmd: []const u8 = undefined;
const stderr = std.io.getStdErr().writer();
defer {
// If we can't shutdown or restart due to an error, we print it to standard error. If that fails, just bail out
if (shutdown) {
const shutdown_error = std.process.execv(temporary_allocator, &[_][]const u8{ "/bin/sh", "-c", shutdown_cmd });
stderr.print("error: couldn't shutdown: {any}\n", .{shutdown_error}) catch std.process.exit(1);
} else if (restart) {
const restart_error = std.process.execv(temporary_allocator, &[_][]const u8{ "/bin/sh", "-c", restart_cmd });
stderr.print("error: couldn't restart: {any}\n", .{restart_error}) catch std.process.exit(1);
} else {
// The user has quit Ly using Ctrl+C
temporary_allocator.free(shutdown_cmd);
temporary_allocator.free(restart_cmd);
}
}
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
// to be able to stop the animation after some time
var tv_zero: interop.system_time.timeval = undefined;
_ = interop.system_time.gettimeofday(&tv_zero, null);
var animation_timed_out: bool = false;
const allocator = gpa.allocator();
// 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 config_load_failed = false;
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 located at " ++ build_options.config_directory ++ "/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.config_directory ++ "/ly/save.ini";
var save_path_alloc = false;
defer {
if (save_path_alloc) allocator.free(save_path);
}
const comment_characters = "#";
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.readFileToStruct(config_path, comment_characters, migrator.configFieldHandler) catch _config: {
config_load_failed = true;
break :_config 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, comment_characters, null) 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, comment_characters, null) catch migrator.tryMigrateSaveFile(&user_buf);
}
migrator.lateConfigFieldHandler(&config.animation);
} else {
const config_path = build_options.config_directory ++ "/ly/config.ini";
config = config_ini.readFileToStruct(config_path, comment_characters, migrator.configFieldHandler) catch _config: {
config_load_failed = true;
break :_config Config{};
};
const lang_path = try std.fmt.allocPrint(allocator, "{s}/ly/lang/{s}.ini", .{ build_options.config_directory, config.lang });
defer allocator.free(lang_path);
lang = lang_ini.readFileToStruct(lang_path, comment_characters, null) catch Lang{};
if (config.load) {
var user_buf: [32]u8 = undefined;
save = save_ini.readFileToStruct(save_path, comment_characters, null) catch migrator.tryMigrateSaveFile(&user_buf);
}
migrator.lateConfigFieldHandler(&config.animation);
}
// if (migrator.mapped_config_fields) save_migrated_config: {
// var file = try std.fs.cwd().createFile(config_path, .{});
// defer file.close();
// const writer = file.writer();
// ini.writeFromStruct(config, writer, null, true, .{}) catch {
// break :save_migrated_config;
// };
// }
// These strings only end up getting freed if the user quits Ly using Ctrl+C, which is fine since in the other cases
// we end up shutting down or restarting the system
shutdown_cmd = try temporary_allocator.dupe(u8, config.shutdown_cmd);
restart_cmd = try temporary_allocator.dupe(u8, config.restart_cmd);
// 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_set_output_mode(termbox.TB_OUTPUT_NORMAL);
_ = termbox.tb_clear();
// Needed to reset termbox after auth
const tb_termios = try std.posix.tcgetattr(std.posix.STDIN_FILENO);
// Initialize terminal buffer
const labels_max_length = @max(lang.login.len, lang.password.len);
var seed: u64 = undefined;
std.crypto.random.bytes(std.mem.asBytes(&seed)); // Get a random seed for the PRNG (used by animations)
var prng = std.Random.DefaultPrng.init(seed);
const random = prng.random();
var buffer = TerminalBuffer.init(config, labels_max_length, random);
// Initialize components
var info_line = InfoLine.init(allocator, &buffer);
defer info_line.deinit();
if (config_load_failed) {
// We can't localize this since the config failed to load so we'd fallback to the default language anyway
try info_line.addMessage("unable to parse config file", config.error_bg, config.error_fg);
}
interop.setNumlock(config.numlock) catch {
try info_line.addMessage(lang.err_numlock, config.error_bg, config.error_fg);
};
var session = Session.init(allocator, &buffer, lang);
defer session.deinit();
session.addEnvironment(.{ .Name = lang.shell }, null, .shell) catch {
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
};
if (build_options.enable_x11_support) {
if (config.xinitrc) |xinitrc| {
session.addEnvironment(.{ .Name = lang.xinitrc, .Exec = xinitrc }, null, .xinitrc) catch {
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
};
}
} else {
try info_line.addMessage(lang.no_x11_support, config.bg, config.fg);
}
if (config.initial_info_text) |text| {
try info_line.addMessage(text, config.bg, config.fg);
} else get_host_name: {
// Initialize information line with host name
var name_buf: [std.posix.HOST_NAME_MAX]u8 = undefined;
const hostname = std.posix.gethostname(&name_buf) catch {
try info_line.addMessage(lang.err_hostname, config.error_bg, config.error_fg);
break :get_host_name;
};
try info_line.addMessage(hostname, config.bg, config.fg);
}
try session.crawl(config.waylandsessions, .wayland);
if (build_options.enable_x11_support) try session.crawl(config.xsessions, .x11);
var login = Text.init(allocator, &buffer, false, null);
defer login.deinit();
var password = Text.init(allocator, &buffer, true, config.asterisk);
defer password.deinit();
var active_input = config.default_input;
var insert_mode = !config.vi_mode or config.vi_default_mode == .insert;
// 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 < session.label.list.items.len) session.label.current = session_index;
}
}
// Place components on the screen
{
buffer.drawBoxCenter(!config.hide_borders, config.blank_box);
const coordinates = buffer.calculateComponentCoordinates();
info_line.label.position(coordinates.start_x, coordinates.y, coordinates.full_visible_length, null);
session.label.position(coordinates.x, coordinates.y + 2, coordinates.visible_length, config.text_in_center);
login.position(coordinates.x, coordinates.y + 4, coordinates.visible_length);
password.position(coordinates.x, coordinates.y + 6, coordinates.visible_length);
switch (active_input) {
.info_line => info_line.label.handle(null, insert_mode),
.session => session.label.handle(null, insert_mode),
.login => login.handle(null, insert_mode) catch {
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
},
.password => password.handle(null, insert_mode) catch {
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
},
}
}
// 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, config.cmatrix_fg),
}
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);
const sleep_len = try utils.strWidth(lang.sleep);
const brightness_down_key = try std.fmt.parseInt(u8, config.brightness_down_key[1..], 10);
const brightness_down_len = try utils.strWidth(lang.brightness_down);
const brightness_up_key = try std.fmt.parseInt(u8, config.brightness_up_key[1..], 10);
const brightness_up_len = try utils.strWidth(lang.brightness_up);
var event_timeout: c_int = std.math.maxInt(c_int);
var event_error: c_int = undefined;
var event: ?termbox.tb_event = null;
var wake: std.Thread.Semaphore = .{};
var doneEvent: std.Thread.Semaphore = .{};
var run = true;
var update = true;
var resolution_changed = false;
var auth_fails: u64 = 0;
// Switch to selected TTY if possible
interop.switchTty(config.console_dev, config.tty) catch {
try info_line.addMessage(lang.err_console_dev, config.error_bg, config.error_fg);
};
doneEvent.post();
const thandle = try std.Thread.spawn(.{}, eventThreadMain, .{ &event, &event_error, &event_timeout, &wake, &doneEvent });
thandle.detach();
{
const current_environment = session.label.list.items[session.label.current];
try auth.startAutomaticLogin(allocator, config, login, current_environment, &wake);
}
defer allocator.free(auth.currentLogin.?);
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(std.time.ns_per_ms * 100);
_ = termbox.tb_present(); // Required to update tb_width(), tb_height() and tb_cell_buffer()
const width: usize = @intCast(termbox.tb_width());
const height: usize = @intCast(termbox.tb_height());
if (width != buffer.width or height != buffer.height) {
// If it did change, then update the cell buffer, reallocate the current animation's buffers, and force a draw update
buffer.width = width;
buffer.height = height;
buffer.buffer = termbox.tb_cell_buffer();
switch (config.animation) {
.none => {},
.doom => doom.realloc() catch {
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
},
.matrix => matrix.realloc() catch {
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
},
}
update = true;
resolution_changed = 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 < config.auth_fails) {
_ = termbox.tb_clear();
if (!animation_timed_out) {
switch (config.animation) {
.none => {},
.doom => doom.draw(),
.matrix => matrix.draw(),
}
}
if (config.bigclock != .none and buffer.box_height + (bigclock.HEIGHT + 2) * 2 < buffer.height) draw_big_clock: {
const format = "%H:%M";
const xo = buffer.width / 2 - @min(buffer.width, (format.len * (bigclock.WIDTH + 1))) / 2;
const yo = (buffer.height - buffer.box_height) / 2 - bigclock.HEIGHT - 2;
var clock_buf: [format.len + 1:0]u8 = undefined;
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, config.bigclock);
bigclock.alphaBlit(xo + i * (bigclock.WIDTH + 1), yo, buffer.width, buffer.height, clock_cell);
}
}
buffer.drawBoxCenter(!config.hide_borders, config.blank_box);
if (resolution_changed) {
const coordinates = buffer.calculateComponentCoordinates();
info_line.label.position(coordinates.start_x, coordinates.y, coordinates.full_visible_length, null);
session.label.position(coordinates.x, coordinates.y + 2, coordinates.visible_length, config.text_in_center);
login.position(coordinates.x, coordinates.y + 4, coordinates.visible_length);
password.position(coordinates.x, coordinates.y + 6, coordinates.visible_length);
resolution_changed = false;
}
switch (active_input) {
.info_line => info_line.label.handle(null, insert_mode),
.session => session.label.handle(null, insert_mode),
.login => login.handle(null, insert_mode) catch {
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
},
.password => password.handle(null, insert_mode) catch {
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
},
}
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 - @min(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);
info_line.label.draw();
if (!config.hide_key_hints) {
var length: usize = 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);
length += sleep_len + 1;
}
buffer.drawLabel(config.brightness_down_key, length, 0);
length += config.brightness_down_key.len + 1;
buffer.drawLabel(" ", length - 1, 0);
buffer.drawLabel(lang.brightness_down, length, 0);
length += brightness_down_len + 1;
buffer.drawLabel(config.brightness_up_key, length, 0);
length += config.brightness_up_key.len + 1;
buffer.drawLabel(" ", length - 1, 0);
buffer.drawLabel(lang.brightness_up, length, 0);
length += brightness_up_len + 1;
}
if (config.box_title) |title| {
buffer.drawConfinedLabel(title, buffer.box_x, buffer.box_y - 1, buffer.box_width);
}
if (config.vi_mode) {
const label_txt = if (insert_mode) lang.insert else lang.normal;
buffer.drawLabel(label_txt, buffer.box_x, buffer.box_y + buffer.box_height);
}
draw_lock_state: {
const lock_state = interop.getLockState(config.console_dev) catch {
try info_line.addMessage(lang.err_console_dev, config.error_bg, config.error_fg);
break :draw_lock_state;
};
var lock_state_x = buffer.width - @min(buffer.width, lang.numlock.len);
const lock_state_y: usize = if (config.clock != null) 1 else 0;
if (lock_state.numlock) buffer.drawLabel(lang.numlock, lock_state_x, lock_state_y);
if (lock_state_x >= lang.capslock.len + 1) {
lock_state_x -= lang.capslock.len + 1;
if (lock_state.capslock) buffer.drawLabel(lang.capslock, lock_state_x, lock_state_y);
}
}
session.label.draw();
login.draw();
password.draw();
} else {
std.time.sleep(std.time.ns_per_ms * 10);
update = buffer.cascade();
if (!update) {
std.time.sleep(std.time.ns_per_s * 7);
auth_fails = 0;
}
}
_ = termbox.tb_present();
}
var timeout: u64 = std.math.maxInt(u64);
// Calculate the maximum timeout based on current animations, or the (big) clock. If there's none, we wait for 100ms instead
if (animate and !animation_timed_out) {
timeout = config.min_refresh_delta;
// check how long we have been running so we can turn off the animation
var tv: interop.system_time.timeval = undefined;
_ = interop.system_time.gettimeofday(&tv, null);
if (config.animation_timeout_sec > 0 and tv.tv_sec - tv_zero.tv_sec > config.animation_timeout_sec) {
animation_timed_out = true;
switch (config.animation) {
.none => {},
.doom => doom.deinit(),
.matrix => matrix.deinit(),
}
}
} else if (config.bigclock != .none and config.clock == null) {
var tv: interop.system_time.timeval = undefined;
_ = interop.system_time.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 >= config.auth_fails) {
var tv: interop.system_time.timeval = undefined;
_ = interop.system_time.gettimeofday(&tv, null);
timeout = @intCast(1000 - @divTrunc(tv.tv_usec, 1000) + 1);
}
var skipEvent: bool = false;
_ = wake.timedWait(timeout) catch .{};
if (auth.asyncPamHandle) |handle| {
var shared_err = try SharedError.init();
defer shared_err.deinit();
{
const login_text = try allocator.dupeZ(u8, login.text.items);
defer allocator.free(login_text);
try info_line.addMessage(lang.authenticating, config.error_bg, config.error_fg);
InfoLine.clearRendered(allocator, buffer) catch {
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
};
info_line.label.draw();
_ = termbox.tb_present();
session_pid = try std.posix.fork();
if (session_pid == 0) {
const current_environment = session.label.list.items[session.label.current];
auth.finaliseAuth(config, current_environment, handle, login_text) catch |err| {
shared_err.writeError(err);
std.process.exit(1);
};
std.process.exit(0);
}
_ = std.posix.waitpid(session_pid, 0);
session_pid = -1;
}
auth.asyncPamHandle = null;
const auth_err = shared_err.readError();
if (auth_err) |err| {
try info_line.addMessage(getAuthErrorMsg(err, lang), config.error_bg, config.error_fg);
// We don't want to start login back in instantly. The user must first edit
// the login/desktop in order to login. Only in case of a failed login (so not a logout)
// should we automatically restart it.
const current_environment = session.label.list.items[session.label.current];
try auth.startAutomaticLogin(allocator, config, login, current_environment, &wake);
} else {
try info_line.addMessage(lang.logout, config.error_bg, config.error_fg);
}
try std.posix.tcsetattr(std.posix.STDIN_FILENO, .FLUSH, tb_termios);
_ = termbox.tb_clear();
_ = termbox.tb_present();
update = true;
_ = termbox.tb_set_cursor(0, 0);
_ = termbox.tb_present();
} else if (event) |e| {
defer doneEvent.post();
update = timeout != -1;
skipEvent = event_error < 0;
if (skipEvent or e.type != termbox.TB_EVENT_KEY) continue;
switch (e.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 - e.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.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", sleep_cmd }, allocator);
_ = sleep.spawnAndWait() catch .{};
}
} else if (pressed_key == brightness_down_key) {
var brightness = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", config.brightness_down_cmd }, allocator);
_ = brightness.spawnAndWait() catch .{};
} else if (pressed_key == brightness_up_key) {
var brightness = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", config.brightness_up_cmd }, allocator);
_ = brightness.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, .info_line => .info_line,
.login => .session,
.password => .login,
};
update = true;
},
termbox.TB_KEY_CTRL_J, termbox.TB_KEY_ARROW_DOWN => {
if (active_input == .login) {
const current_environment = session.label.list.items[session.label.current];
try auth.startAutomaticLogin(allocator, config, login, current_environment, &wake);
}
active_input = switch (active_input) {
.info_line => .session,
.session => .login,
.login, .password => .password,
};
update = true;
},
termbox.TB_KEY_TAB => {
if (active_input == .login) {
const current_environment = session.label.list.items[session.label.current];
try auth.startAutomaticLogin(allocator, config, login, current_environment, &wake);
}
active_input = switch (active_input) {
.info_line => .session,
.session => .login,
.login => .password,
.password => .info_line,
};
update = true;
},
termbox.TB_KEY_BACK_TAB => {
if (active_input == .info_line) {
const current_environment = session.label.list.items[session.label.current];
try auth.startAutomaticLogin(allocator, config, login, current_environment, &wake);
}
active_input = switch (active_input) {
.info_line => .password,
.session => .info_line,
.login => .session,
.password => .login,
};
update = true;
},
termbox.TB_KEY_ENTER => {
try info_line.addMessage(lang.authenticating, config.bg, config.fg);
InfoLine.clearRendered(allocator, buffer) catch {
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
};
info_line.label.draw();
_ = termbox.tb_present();
if (config.save) save_last_settings: {
var file = std.fs.cwd().createFile(save_path, .{}) catch break :save_last_settings;
defer file.close();
const save_data = Save{
.user = login.text.items,
.session_index = session.label.current,
};
ini.writeFromStruct(save_data, file.writer(), null, true, .{}) catch break :save_last_settings;
// Delete previous save file if it exists
if (migrator.maybe_save_file) |path| std.fs.cwd().deleteFile(path) catch {};
}
var shared_err = try SharedError.init();
defer shared_err.deinit();
{
const login_text = try allocator.dupeZ(u8, login.text.items);
defer allocator.free(login_text);
const password_text = try allocator.dupeZ(u8, password.text.items);
defer allocator.free(password_text);
// Give up control on the TTY
_ = termbox.tb_shutdown();
session_pid = try std.posix.fork();
const current_environment = session.label.list.items[session.label.current];
if (auth.authenticate(config, login_text, password_text, current_environment)) |handle| {
if (session_pid == 0) {
auth.finaliseAuth(config, current_environment, handle, login_text) catch |err| {
shared_err.writeError(err);
std.process.exit(1);
};
std.process.exit(0);
}
_ = std.posix.waitpid(session_pid, 0);
} else |err| {
shared_err.writeError(err);
}
session_pid = -1;
}
// Take back control of the TTY
_ = termbox.tb_init();
_ = termbox.tb_set_output_mode(termbox.TB_OUTPUT_NORMAL);
const auth_err = shared_err.readError();
if (auth_err) |err| {
auth_fails += 1;
active_input = .password;
try info_line.addMessage(getAuthErrorMsg(err, lang), config.error_bg, config.error_fg);
if (config.clear_password or err != error.PamAuthError) password.clear();
} else {
if (config.logout_cmd) |logout_cmd| {
var logout_process = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", logout_cmd }, allocator);
_ = logout_process.spawnAndWait() catch .{};
}
password.clear();
try info_line.addMessage(lang.logout, config.bg, config.fg);
}
try std.posix.tcsetattr(std.posix.STDIN_FILENO, .FLUSH, tb_termios);
if (auth_fails < config.auth_fails) _ = termbox.tb_clear();
update = true;
// Restore the cursor
_ = termbox.tb_set_cursor(0, 0);
_ = termbox.tb_present();
},
else => {
if (!insert_mode) {
switch (e.ch) {
'k' => {
active_input = switch (active_input) {
.session, .info_line => .info_line,
.login => .session,
.password => .login,
};
update = true;
continue;
},
'j' => {
if (active_input == .login) {
const current_environment = session.label.list.items[session.label.current];
try auth.startAutomaticLogin(allocator, config, login, current_environment, &wake);
}
active_input = switch (active_input) {
.info_line => .session,
.session => .login,
.login, .password => .password,
};
update = true;
continue;
},
'i' => {
insert_mode = true;
update = true;
continue;
},
else => {},
}
}
switch (active_input) {
.info_line => info_line.label.handle(&event.?, insert_mode),
.session => session.label.handle(&event.?, insert_mode),
.login => login.handle(&event.?, insert_mode) catch {
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
},
.password => password.handle(&event.?, insert_mode) catch {
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
},
}
update = true;
},
}
}
}
}
fn getAuthErrorMsg(err: anyerror, lang: Lang) []const u8 {
return switch (err) {
error.GetPasswordNameFailed => lang.err_pwnam,
error.GetEnvListFailed => lang.err_envlist,
error.XauthFailed => lang.err_xauth,
error.XcbConnectionFailed => lang.err_xcb_conn,
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,
};
}

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

@@ -0,0 +1,199 @@
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.Random;
const termbox = interop.termbox;
const TerminalBuffer = @This();
random: Random,
width: usize,
height: usize,
buffer: [*]termbox.tb_cell,
fg: u16,
bg: u16,
border_fg: u16,
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: usize,
box_x: usize,
box_y: usize,
box_width: usize,
box_height: usize,
margin_box_v: u8,
margin_box_h: u8,
pub fn init(config: Config, labels_max_length: usize, random: Random) TerminalBuffer {
return .{
.random = 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 changed = false;
var y = self.height - 2;
while (y > 0) : (y -= 1) {
for (0..self.width) |x| {
const cell = self.buffer[(y - 1) * self.width + x];
const cell_under = self.buffer[y * self.width + x];
const char: u8 = @truncate(cell.ch);
if (std.ascii.isWhitespace(char)) continue;
const char_under: u8 = @truncate(cell_under.ch);
if (!std.ascii.isWhitespace(char_under)) continue;
changed = true;
if ((self.random.int(u16) % 10) > 7) continue;
_ = termbox.tb_set_cell(@intCast(x), @intCast(y), cell.ch, cell.fg, cell.bg);
_ = termbox.tb_set_cell(@intCast(x), @intCast(y - 1), ' ', cell_under.fg, cell_under.bg);
}
}
return changed;
}
pub fn drawBoxCenter(self: *TerminalBuffer, show_borders: bool, blank_box: bool) void {
if (self.width < 2 or self.height < 2) return;
const x1 = (self.width - @min(self.width - 2, self.box_width)) / 2;
const y1 = (self.height - @min(self.height - 2, self.box_height)) / 2;
const x2 = (self.width + @min(self.width, self.box_width)) / 2;
const y2 = (self.height + @min(self.height, self.box_height)) / 2;
self.box_x = x1;
self.box_y = y1;
if (show_borders) {
_ = termbox.tb_set_cell(@intCast(x1 - 1), @intCast(y1 - 1), self.box_chars.left_up, self.border_fg, self.bg);
_ = termbox.tb_set_cell(@intCast(x2), @intCast(y1 - 1), self.box_chars.right_up, self.border_fg, self.bg);
_ = termbox.tb_set_cell(@intCast(x1 - 1), @intCast(y2), self.box_chars.left_down, self.border_fg, self.bg);
_ = termbox.tb_set_cell(@intCast(x2), @intCast(y2), self.box_chars.right_down, self.border_fg, self.bg);
var c1 = utils.initCell(self.box_chars.top, self.border_fg, self.bg);
var c2 = utils.initCell(self.box_chars.bottom, self.border_fg, self.bg);
for (0..self.box_width) |i| {
utils.putCell(x1 + i, y1 - 1, c1);
utils.putCell(x1 + i, y2, c2);
}
c1.ch = self.box_chars.left;
c2.ch = self.box_chars.right;
for (0..self.box_height) |i| {
utils.putCell(x1 - 1, y1 + i, c1);
utils.putCell(x2, 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| {
utils.putCell(x1 + x, y1 + y, blank);
}
}
}
}
pub fn calculateComponentCoordinates(self: TerminalBuffer) struct {
start_x: usize,
x: usize,
y: usize,
full_visible_length: usize,
visible_length: usize,
} {
const start_x = self.box_x + self.margin_box_h;
const x = start_x + self.labels_max_length + 1;
const y = self.box_y + self.margin_box_v;
const full_visible_length = self.box_x + self.box_width - self.margin_box_h - start_x;
const visible_length = self.box_x + self.box_width - self.margin_box_h - x;
return .{
.start_x = start_x,
.x = x,
.y = y,
.full_visible_length = full_visible_length,
.visible_length = visible_length,
};
}
pub fn drawLabel(self: TerminalBuffer, text: []const u8, x: usize, y: usize) void {
drawColorLabel(text, x, y, self.fg, self.bg);
}
pub fn drawColorLabel(text: []const u8, x: usize, y: usize, fg: u16, bg: u16) 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_set_cell(@intCast(i), yc, codepoint, fg, bg);
}
}
pub fn drawConfinedLabel(self: TerminalBuffer, text: []const u8, x: usize, y: usize, max_length: usize) void {
const yc: c_int = @intCast(y);
const utf8view = std.unicode.Utf8View.init(text) catch return;
var utf8 = utf8view.iterator();
var i: usize = 0;
while (utf8.nextCodepoint()) |codepoint| : (i += 1) {
if (i >= max_length) break;
_ = termbox.tb_set_cell(@intCast(i + x), yc, codepoint, self.fg, self.bg);
}
}
pub fn drawCharMultiple(self: TerminalBuffer, char: u8, x: usize, y: usize, length: usize) void {
const cell = utils.initCell(char, self.fg, self.bg);
for (0..length) |xx| utils.putCell(x + xx, y, cell);
}

View File

@@ -0,0 +1,61 @@
const std = @import("std");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const generic = @import("generic.zig");
const utils = @import("../utils.zig");
const Allocator = std.mem.Allocator;
const MessageLabel = generic.CyclableLabel(Message);
const InfoLine = @This();
const Message = struct {
width: u8,
text: []const u8,
bg: u16,
fg: u16,
};
label: MessageLabel,
pub fn init(allocator: Allocator, buffer: *TerminalBuffer) InfoLine {
return .{
.label = MessageLabel.init(allocator, buffer, drawItem),
};
}
pub fn deinit(self: InfoLine) void {
self.label.deinit();
}
pub fn addMessage(self: *InfoLine, text: []const u8, bg: u16, fg: u16) !void {
if (text.len == 0) return;
try self.label.addItem(.{
.width = try utils.strWidth(text),
.text = text,
.bg = bg,
.fg = fg,
});
}
pub fn clearRendered(allocator: Allocator, buffer: TerminalBuffer) !void {
// Draw over the area
const y = buffer.box_y + buffer.margin_box_v;
const spaces = try allocator.alloc(u8, buffer.box_width);
defer allocator.free(spaces);
@memset(spaces, ' ');
buffer.drawLabel(spaces, buffer.box_x, y);
}
fn drawItem(label: *MessageLabel, message: Message, _: usize, _: usize) bool {
if (message.width == 0 or label.buffer.box_width <= message.width) return false;
const x = label.buffer.box_x + ((label.buffer.box_width - message.width) / 2);
label.first_char_x = x + message.width;
TerminalBuffer.drawColorLabel(message.text, x, label.y, message.fg, message.bg);
return true;
}

View File

@@ -0,0 +1,142 @@
const std = @import("std");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const enums = @import("../../enums.zig");
const generic = @import("generic.zig");
const Ini = @import("zigini").Ini;
const Lang = @import("../../config/Lang.zig");
const Allocator = std.mem.Allocator;
const DisplayServer = enums.DisplayServer;
const EnvironmentLabel = generic.CyclableLabel(Environment);
const Session = @This();
pub const Environment = struct {
entry_ini: ?Ini(Entry) = null,
name: [:0]const u8 = "",
xdg_session_desktop: ?[:0]const u8 = null,
xdg_desktop_names: ?[:0]const u8 = null,
cmd: []const u8 = "",
specifier: []const u8 = "",
display_server: DisplayServer = .wayland,
};
const DesktopEntry = struct {
Exec: []const u8 = "",
Name: [:0]const u8 = "",
DesktopNames: ?[:0]u8 = null,
};
pub const Entry = struct { @"Desktop Entry": DesktopEntry = .{} };
label: EnvironmentLabel,
lang: Lang,
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, lang: Lang) Session {
return .{
.label = EnvironmentLabel.init(allocator, buffer, drawItem),
.lang = lang,
};
}
pub fn deinit(self: Session) void {
for (self.label.list.items) |*environment| {
if (environment.entry_ini) |*entry_ini| entry_ini.deinit();
if (environment.xdg_session_desktop) |session_desktop| self.label.allocator.free(session_desktop);
}
self.label.deinit();
}
pub fn addEnvironment(self: *Session, entry: DesktopEntry, xdg_session_desktop: ?[:0]const u8, display_server: DisplayServer) !void {
var xdg_desktop_names: ?[:0]const u8 = null;
if (entry.DesktopNames) |desktop_names| {
for (desktop_names) |*c| {
if (c.* == ';') c.* = ':';
}
xdg_desktop_names = desktop_names;
}
try self.label.addItem(.{
.entry_ini = null,
.name = entry.Name,
.xdg_session_desktop = xdg_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,
});
}
pub fn addEnvironmentWithIni(self: *Session, entry_ini: Ini(Entry), xdg_session_desktop: ?[:0]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| {
for (desktop_names) |*c| {
if (c.* == ';') c.* = ':';
}
xdg_desktop_names = desktop_names;
}
try self.label.addItem(.{
.entry_ini = entry_ini,
.name = entry.Name,
.xdg_session_desktop = xdg_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,
});
}
pub fn crawl(self: *Session, 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.label.allocator, "{s}/{s}", .{ path, item.name });
defer self.label.allocator.free(entry_path);
var entry_ini = Ini(Entry).init(self.label.allocator);
_ = try entry_ini.readFileToStruct(entry_path, "#", null);
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);
}
const session_desktop = try self.label.allocator.dupeZ(u8, xdg_session_desktop);
errdefer self.label.allocator.free(session_desktop);
try self.addEnvironmentWithIni(entry_ini, session_desktop, display_server);
}
}
fn drawItem(label: *EnvironmentLabel, environment: Environment, x: usize, y: usize) bool {
const length = @min(environment.name.len, label.visible_length - 3);
if (length == 0) return false;
const nx = if (label.text_in_center) (label.x + (label.visible_length - environment.name.len) / 2) else (label.x + 2);
label.first_char_x = nx + environment.name.len;
label.buffer.drawLabel(environment.specifier, x, y);
label.buffer.drawLabel(environment.name, nx, label.y);
return true;
}

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

@@ -0,0 +1,161 @@
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: usize,
cursor: usize,
visible_start: usize,
visible_length: usize,
x: usize,
y: usize,
masked: bool,
maybe_mask: ?u8,
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, masked: bool, maybe_mask: ?u8) Text {
const text = DynamicString.init(allocator);
return .{
.allocator = allocator,
.buffer = buffer,
.text = text,
.end = 0,
.cursor = 0,
.visible_start = 0,
.visible_length = 0,
.x = 0,
.y = 0,
.masked = masked,
.maybe_mask = maybe_mask,
};
}
pub fn deinit(self: Text) void {
self.text.deinit();
}
pub fn position(self: *Text, x: usize, y: usize, visible_length: usize) 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 => {},
}
}
}
},
}
}
if (self.masked and self.maybe_mask == null) {
_ = termbox.tb_set_cursor(@intCast(self.x), @intCast(self.y));
return;
}
_ = termbox.tb_set_cursor(@intCast(self.x + (self.cursor - self.visible_start)), @intCast(self.y));
}
pub fn draw(self: Text) void {
if (self.masked) {
if (self.maybe_mask) |mask| {
const length = @min(self.text.items.len, self.visible_length - 1);
if (length == 0) return;
self.buffer.drawCharMultiple(mask, self.x, self.y, length);
}
return;
}
const length = @min(self.text.items.len, self.visible_length);
if (length == 0) return;
const visible_slice = vs: {
if (self.text.items.len > self.visible_length and self.cursor < self.text.items.len) {
break :vs self.text.items[self.visible_start..(self.visible_length + self.visible_start)];
} else {
break :vs self.text.items[self.visible_start..];
}
};
self.buffer.drawLabel(visible_slice, self.x, self.y);
}
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();
}

View File

@@ -0,0 +1,115 @@
const std = @import("std");
const enums = @import("../../enums.zig");
const interop = @import("../../interop.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
pub fn CyclableLabel(comptime ItemType: type) type {
return struct {
const Allocator = std.mem.Allocator;
const ItemList = std.ArrayList(ItemType);
const DrawItemFn = *const fn (*Self, ItemType, usize, usize) bool;
const termbox = interop.termbox;
const Self = @This();
allocator: Allocator,
buffer: *TerminalBuffer,
list: ItemList,
current: usize,
visible_length: usize,
x: usize,
y: usize,
first_char_x: usize,
text_in_center: bool,
draw_item_fn: DrawItemFn,
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, draw_item_fn: DrawItemFn) Self {
return .{
.allocator = allocator,
.buffer = buffer,
.list = ItemList.init(allocator),
.current = 0,
.visible_length = 0,
.x = 0,
.y = 0,
.first_char_x = 0,
.text_in_center = false,
.draw_item_fn = draw_item_fn,
};
}
pub fn deinit(self: Self) void {
self.list.deinit();
}
pub fn position(self: *Self, x: usize, y: usize, visible_length: usize, text_in_center: ?bool) void {
self.x = x;
self.y = y;
self.visible_length = visible_length;
self.first_char_x = x + 2;
if (text_in_center) |value| {
self.text_in_center = value;
}
}
pub fn addItem(self: *Self, item: ItemType) !void {
try self.list.append(item);
self.current = self.list.items.len - 1;
}
pub fn handle(self: *Self, 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.first_char_x), @intCast(self.y));
}
pub fn draw(self: *Self) void {
if (self.list.items.len == 0) return;
const current_item = self.list.items[self.current];
const x = self.buffer.box_x + self.buffer.margin_box_h;
const y = self.buffer.box_y + self.buffer.margin_box_v + 2;
const continue_drawing = @call(.auto, self.draw_item_fn, .{ self, current_item, x, y });
if (!continue_drawing) return;
_ = termbox.tb_set_cell(@intCast(self.x), @intCast(self.y), '<', self.buffer.fg, self.buffer.bg);
_ = termbox.tb_set_cell(@intCast(self.x + self.visible_length - 1), @intCast(self.y), '>', self.buffer.fg, self.buffer.bg);
}
fn goLeft(self: *Self) void {
if (self.current == 0) {
self.current = self.list.items.len - 1;
return;
}
self.current -= 1;
}
fn goRight(self: *Self) void {
if (self.current == self.list.items.len - 1) {
self.current = 0;
return;
}
self.current += 1;
}
};
}

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

@@ -0,0 +1,32 @@
const std = @import("std");
const interop = @import("../interop.zig");
const termbox = interop.termbox;
pub const Cell = struct {
ch: u32,
fg: u16,
bg: u16,
};
pub fn initCell(ch: u32, fg: u16, bg: u16) Cell {
return .{
.ch = ch,
.fg = fg,
.bg = bg,
};
}
pub fn putCell(x: usize, y: usize, cell: Cell) void {
_ = termbox.tb_set_cell(@intCast(x), @intCast(y), cell.ch, cell.fg, cell.bg);
}
// Every codepoint is assumed to have a width of 1.
// Since ly should be running in a tty, this should be fine.
pub fn strWidth(str: []const u8) !u8 {
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 <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#if defined(__DragonFly__) || defined(__FreeBSD__)
#include <sys/consio.h>
#else // linux
#include <linux/vt.h>
#endif
void desktop_crawl(
struct desktop* target,
char* sessions,
enum display_server server)
{
DIR* dir;
struct dirent* dir_info;
int ok;
ok = access(sessions, F_OK);
if (ok == -1)
{
dgn_throw(DGN_XSESSIONS_DIR);
return;
}
dir = opendir(sessions);
if (dir == NULL)
{
dgn_throw(DGN_XSESSIONS_OPEN);
return;
}
char* name = NULL;
char* exec = NULL;
struct configator_param map_desktop[] =
{
{"Exec", &exec, config_handle_str},
{"Name", &name, config_handle_str},
};
struct configator_param* map[] =
{
NULL,
map_desktop,
};
struct configator_param sections[] =
{
{"Desktop Entry", NULL, NULL},
};
uint16_t map_len[] = {0, 2};
uint16_t sections_len = 1;
struct configator desktop_config;
desktop_config.map = map;
desktop_config.map_len = map_len;
desktop_config.sections = sections;
desktop_config.sections_len = sections_len;
#if defined(NAME_MAX)
char path[NAME_MAX];
#elif defined(_POSIX_PATH_MAX)
char path[_POSIX_PATH_MAX];
#else
char path[1024];
#endif
dir_info = readdir(dir);
while (dir_info != NULL)
{
if ((dir_info->d_name)[0] == '.')
{
dir_info = readdir(dir);
continue;
}
snprintf(path, (sizeof (path)) - 1, "%s/", sessions);
strncat(path, dir_info->d_name, (sizeof (path)) - 1);
configator(&desktop_config, path);
// if these are wayland sessions, add " (Wayland)" to their names,
// as long as their names don't already contain that string
if (server == DS_WAYLAND && config.wayland_specifier)
{
const char wayland_specifier[] = " (Wayland)";
if (strstr(name, wayland_specifier) == NULL)
{
name = realloc(name, (strlen(name) + sizeof(wayland_specifier) + 1));
// using strcat is safe because the string is constant
strcat(name, wayland_specifier);
}
}
if ((name != NULL) && (exec != NULL))
{
input_desktop_add(target, name, exec, server);
}
name = NULL;
exec = NULL;
dir_info = readdir(dir);
}
closedir(dir);
}
void desktop_load(struct desktop* target)
{
// we don't care about desktop environments presence
// because the fallback shell is always available
// so we just dismiss any "throw" for now
int err = 0;
desktop_crawl(target, config.waylandsessions, DS_WAYLAND);
if (dgn_catch())
{
++err;
dgn_reset();
}
desktop_crawl(target, config.xsessions, DS_XORG);
if (dgn_catch())
{
++err;
dgn_reset();
}
}
static char* hostname_backup = NULL;
void hostname(char** out)
{
if (hostname_backup != NULL)
{
*out = hostname_backup;
return;
}
int maxlen = sysconf(_SC_HOST_NAME_MAX);
if (maxlen < 0)
{
maxlen = _POSIX_HOST_NAME_MAX;
}
hostname_backup = malloc(maxlen + 1);
if (hostname_backup == NULL)
{
dgn_throw(DGN_ALLOC);
return;
}
if (gethostname(hostname_backup, maxlen) < 0)
{
dgn_throw(DGN_HOSTNAME);
return;
}
hostname_backup[maxlen] = '\0';
*out = hostname_backup;
}
void free_hostname()
{
free(hostname_backup);
}
void switch_tty(struct term_buf* buf)
{
FILE* console = fopen(config.console_dev, "w");
if (console == NULL)
{
buf->info_line = lang.err_console_dev;
return;
}
int fd = fileno(console);
ioctl(fd, VT_ACTIVATE, config.tty);
ioctl(fd, VT_WAITACTIVE, config.tty);
fclose(console);
}
void save(struct desktop* desktop, struct text* login)
{
if (config.save)
{
FILE* fp = fopen(config.save_file, "wb+");
if (fp != NULL)
{
fprintf(fp, "%s\n%d", login->text, desktop->cur);
fclose(fp);
}
}
}
void load(struct desktop* desktop, struct text* login)
{
if (!config.load)
{
return;
}
FILE* fp = fopen(config.save_file, "rb");
if (fp == NULL)
{
return;
}
char* line = malloc(config.max_login_len + 1);
if (line == NULL)
{
fclose(fp);
return;
}
if (fgets(line, config.max_login_len + 1, fp))
{
int len = strlen(line);
strncpy(login->text, line, login->len);
if (len == 0)
{
login->end = login->text;
}
else
{
login->end = login->text + len - 1;
login->text[len - 1] = '\0';
}
}
else
{
fclose(fp);
free(line);
return;
}
if (fgets(line, config.max_login_len + 1, fp))
{
int saved_cur = abs(atoi(line));
if (saved_cur < desktop->len)
{
desktop->cur = saved_cur;
}
}
fclose(fp);
free(line);
}

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 e1844c4c94

Submodule sub/configator deleted from 8cec178619

Submodule sub/dragonfail deleted from 15bd3299bf

Submodule sub/termbox_next deleted from d961a81222